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;
}
}