diff --git a/Dashboard.Drawing.OpenGL/DrawCallRecorder.cs b/Dashboard.Drawing.OpenGL/DrawCallRecorder.cs
index 5b6a482..58e6a77 100644
--- a/Dashboard.Drawing.OpenGL/DrawCallRecorder.cs
+++ b/Dashboard.Drawing.OpenGL/DrawCallRecorder.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL;
@@ -224,4 +225,243 @@ namespace Dashboard.Drawing.OpenGL
public int CmdIndex;
}
}
+
+ ///
+ /// A customizable immediate mode draw call queue, for the modern OpenGL user.
+ ///
+ /// The call info type.
+ /// The vertex structure.
+ public abstract class DrawCallRecorder : IGLDisposable, IInitializer
+ where TVertex : unmanaged
+ {
+ ///
+ /// The vertex array for this queue.
+ ///
+ public int Vao { get; private set; }
+
+ ///
+ /// The vertex buffer for this queue.
+ ///
+ public int Vbo { get; private set; }
+
+ ///
+ /// Number of calls recorded in this queue.
+ ///
+ public int CallCount => Calls.Count;
+
+ ///
+ /// The number of total vertices recorded.
+ ///
+ public int TotalVertices => Vertices.Count;
+
+ ///
+ /// The latest draw call info.
+ ///
+ public ref TCall CurrentCall => ref _currentCall;
+
+ ///
+ /// The latest vertex emitted.
+ ///
+ public ref TVertex CurrentVertex => ref _currentVertex;
+
+ ///
+ /// True if currently recording a draw call.
+ ///
+ public bool InCall => _primitiveMode != 0;
+
+ ///
+ /// Size of one vertex.
+ ///
+ protected int VertexSize => Unsafe.SizeOf();
+
+ ///
+ /// The list of draw calls.
+ ///
+ protected List Calls { get; } = new List();
+
+ ///
+ /// The list of all vertices.
+ ///
+ protected List Vertices { get; } = new List();
+
+ ///
+ /// The value to write for the draw call info at the start of a call.
+ ///
+ [Pure] protected virtual TCall DefaultCall => default(TCall);
+
+ ///
+ /// The value to write for last vertex at the start of a call.
+ ///
+ [Pure] protected virtual TVertex DefaultVertex => default;
+
+ private int _start = 0;
+ private int _count = 0;
+ private int _primitiveMode = 0;
+ private TCall _currentCall;
+ private TVertex _currentVertex;
+
+ protected DrawCallRecorder()
+ {
+ _currentCall = DefaultCall;
+ _currentVertex = DefaultVertex;
+ }
+
+ ///
+ /// Record a draw call directly.
+ ///
+ /// The primitive type to use.
+ /// The call info structure to use
+ /// The list of vertices to use.
+ /// You attempted to use this function during another draw call.
+ public void DrawArrays(PrimitiveType type, in TCall callInfo, ReadOnlySpan vertices)
+ {
+ if (InCall)
+ throw new InvalidOperationException("Cannot use draw arrays in the middle of an ongoing immediate-mode call.");
+
+ DrawCall call = new DrawCall()
+ {
+ Type = type,
+ Start = Vertices.Count,
+ Count = vertices.Length,
+ CallInfo = callInfo,
+ };
+
+ Vertices.AddRange(vertices);
+ Calls.Add(call);
+ }
+
+ ///
+ /// Start a draw call.
+ ///
+ /// The primitive type for the call.
+ public void Begin(PrimitiveType type) => Begin(type, DefaultCall);
+
+ ///
+ /// Start a draw call.
+ ///
+ /// The primitive type for the call.
+ /// The call info.
+ /// You attempted to create a draw call within a draw call.
+ public void Begin(PrimitiveType type, TCall callInfo)
+ {
+ if (InCall)
+ throw new InvalidOperationException("Attempt to begin new draw call before finishing previous one.");
+
+ _primitiveMode = (int)type;
+ _start = Vertices.Count;
+ _count = 0;
+ CurrentCall = callInfo;
+ CurrentVertex = DefaultVertex;
+ }
+
+ ///
+ /// Emit the latest or modified vertex.
+ ///
+ public void Vertex()
+ {
+ Vertex(CurrentVertex);
+ }
+
+ ///
+ /// Emit a vertex.
+ ///
+ /// The vertex to emit.
+ public void Vertex(in TVertex vertex)
+ {
+ Vertices.Add(CurrentVertex = vertex);
+ _count++;
+ }
+
+ ///
+ /// End the current call.
+ ///
+ /// You tried to end a call that you didn't begin recording.
+ public void End()
+ {
+ if (!InCall)
+ throw new InvalidOperationException("Attempt to end draw call before starting one.");
+
+ Calls.Add(new DrawCall()
+ {
+ Start = _start,
+ Count = _count,
+ Type = (PrimitiveType)_primitiveMode,
+ CallInfo = CurrentCall,
+ });
+
+ _primitiveMode = 0;
+ }
+
+ ///
+ /// Called by the execution engine before a draw call is executed.
+ ///
+ /// The call to prepare.
+ protected abstract void PrepareCall(in TCall call);
+
+ ///
+ /// Set the vertex format for the and created by the recorder.
+ ///
+ protected abstract void SetVertexFormat();
+
+ ///
+ /// Execute all the recorded draw calls.
+ ///
+ public void Execute()
+ {
+ GL.BindVertexArray(Vao);
+ GL.BindBuffer(BufferTarget.ArrayBuffer, Vbo);
+
+ ReadOnlySpan vertices = CollectionsMarshal.AsSpan(Vertices);
+ GL.BufferData(BufferTarget.ArrayBuffer, Vertices.Count * VertexSize, vertices, BufferUsage.DynamicDraw);
+
+ foreach (DrawCall call in Calls)
+ {
+ PrepareCall(call.CallInfo);
+ GL.DrawArrays(call.Type, call.Start, call.Count);
+ }
+ }
+
+ ///
+ /// Clear the draw call queue.
+ ///
+ public void Clear()
+ {
+ Vertices.Clear();
+ Calls.Clear();
+ }
+
+ public void Dispose()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose(bool safeExit)
+ {
+ throw new NotImplementedException();
+ }
+
+ public bool IsInitialized { get; private set; }
+ public void Initialize()
+ {
+ if (IsInitialized)
+ return;
+ IsInitialized = true;
+
+ Vao = GL.CreateVertexArray();
+ Vbo = GL.CreateBuffer();
+
+ GL.BindVertexArray(Vao);
+ GL.BindBuffer(BufferTarget.ArrayBuffer, Vbo);
+
+ SetVertexFormat();
+ }
+
+ protected struct DrawCall
+ {
+ public PrimitiveType Type;
+ public int Start;
+ public int Count;
+ public TCall CallInfo;
+ }
+ }
}