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