aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Desktop/GpuTimer.cs
blob: 11076e822598cbfdf00aa18ba03e07a55f362cc1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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;
    }
}