using System; using Silk.NET.OpenGL; namespace Godosa.Desktop; /// GPU time per frame from GL_TIME_ELAPSED queries (game-loop spec ยง6). A small /// ring so reading a result never stalls on the frame still in flight; disables itself /// when the context has no timer queries (e.g. GLES without the extension). public sealed class GpuTimer { private const int Ring = 4; private readonly uint[] _queries = new uint[Ring]; private readonly bool[] _pending = new bool[Ring]; private int _current; private bool _open; public bool Supported { get; private set; } public void Init(GL gl) { try { string version = gl.GetStringS(StringName.Version) ?? ""; if (version.Contains("OpenGL ES", StringComparison.Ordinal)) return; for (int i = 0; i < Ring; i++) _queries[i] = gl.GenQuery(); Supported = gl.GetError() == GLEnum.NoError; } catch (Exception) { Supported = false; } } public void Begin(GL gl) { if (!Supported || _pending[_current]) return; // slot still in flight: skip this frame gl.BeginQuery(QueryTarget.TimeElapsed, _queries[_current]); _open = true; } public void End(GL gl) { if (!_open) return; gl.EndQuery(QueryTarget.TimeElapsed); _pending[_current] = true; _open = false; _current = (_current + 1) % Ring; } /// Seconds for each finished query since the last call. public void Collect(GL gl, Action onResult) { if (!Supported) return; for (int i = 0; i < Ring; i++) { if (!_pending[i]) continue; gl.GetQueryObject(_queries[i], QueryObjectParameterName.ResultAvailable, out int available); if (available == 0) continue; gl.GetQueryObject(_queries[i], QueryObjectParameterName.Result, out ulong nanoseconds); _pending[i] = false; onResult(nanoseconds / 1e9); } } public void Delete(GL gl) { if (!Supported) return; for (int i = 0; i < Ring; i++) gl.DeleteQuery(_queries[i]); Supported = false; } }