aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Desktop/GpuTimer.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Godosa.Desktop/GpuTimer.cs')
-rw-r--r--src/Godosa.Desktop/GpuTimer.cs71
1 files changed, 71 insertions, 0 deletions
diff --git a/src/Godosa.Desktop/GpuTimer.cs b/src/Godosa.Desktop/GpuTimer.cs
new file mode 100644
index 0000000..11076e8
--- /dev/null
+++ b/src/Godosa.Desktop/GpuTimer.cs
@@ -0,0 +1,71 @@
+using System;
+using Silk.NET.OpenGL;
+
+namespace Godosa.Desktop;
+
+/// <summary>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).</summary>
+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;
+ }
+
+ /// <summary>Seconds for each finished query since the last call.</summary>
+ public void Collect(GL gl, Action<double> 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;
+ }
+}