468 lines
15 KiB
C#
468 lines
15 KiB
C#
using System.Diagnostics.Contracts;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using OpenTK.Graphics.OpenGL;
|
|
using OpenTK.Mathematics;
|
|
using Vector2 = System.Numerics.Vector2;
|
|
using Vector3 = System.Numerics.Vector3;
|
|
|
|
namespace Dashboard.Drawing.OpenGL
|
|
{
|
|
public class DrawCallRecorder : IGLDisposable, IInitializer
|
|
{
|
|
private int _vao = 0;
|
|
private int _vbo = 0;
|
|
private readonly List<DrawVertex> _vertices = new List<DrawVertex>();
|
|
private readonly List<DrawCall> _calls = new List<DrawCall>();
|
|
|
|
private int _start = 0;
|
|
private int _count = 0;
|
|
private int _primitives = 0;
|
|
private Vector3 _charCoords;
|
|
private int _cmdIndex;
|
|
private int _texture0, _texture1, _texture2, _texture3;
|
|
private TextureTarget _target0, _target1, _target2, _target3;
|
|
private Matrix4 _transforms = Matrix4.Identity;
|
|
|
|
public int CommandModulus = 64;
|
|
public int CommandBuffer = 0;
|
|
public int CommandSize = 64;
|
|
|
|
private int CommandByteSize => CommandModulus * CommandSize;
|
|
|
|
public int TransformsLocation { get; set; }
|
|
|
|
public void Transforms(in Matrix4 transforms)
|
|
{
|
|
_transforms = transforms;
|
|
}
|
|
|
|
public void Begin(PrimitiveType type)
|
|
{
|
|
if (_primitives != 0)
|
|
throw new InvalidOperationException("Attempt to begin new draw call before finishing previous one.");
|
|
|
|
_primitives = (int)type;
|
|
_start = _vertices.Count;
|
|
_count = 0;
|
|
}
|
|
|
|
public void TexCoords2(Vector2 texCoords)
|
|
{
|
|
_charCoords = new Vector3(texCoords, 0);
|
|
}
|
|
|
|
public void CharCoords(Vector3 charCoords)
|
|
{
|
|
_charCoords = charCoords;
|
|
}
|
|
|
|
public void CommandIndex(int index)
|
|
{
|
|
_cmdIndex = index;
|
|
}
|
|
|
|
public void Vertex3(Vector3 vertex)
|
|
{
|
|
_vertices.Add(new DrawVertex()
|
|
{
|
|
Position = vertex,
|
|
CharCoords = _charCoords,
|
|
CmdIndex = _cmdIndex % CommandModulus,
|
|
});
|
|
_count++;
|
|
}
|
|
|
|
public void End()
|
|
{
|
|
if (_primitives == 0)
|
|
throw new InvalidOperationException("Attempt to end draw call before starting one.");
|
|
|
|
_calls.Add(
|
|
new DrawCall()
|
|
{
|
|
Type = (PrimitiveType)_primitives,
|
|
Start = _start,
|
|
Count = _count,
|
|
CmdIndex = _cmdIndex,
|
|
Target0 = _target0,
|
|
Target1 = _target1,
|
|
Target2 = _target2,
|
|
Target3 = _target3,
|
|
Texture0 = _texture0,
|
|
Texture1 = _texture1,
|
|
Texture2 = _texture2,
|
|
Texture3 = _texture3,
|
|
Transforms = _transforms,
|
|
});
|
|
|
|
_primitives = 0;
|
|
}
|
|
|
|
public void BindTexture(TextureTarget target, int texture) => BindTexture(target, 0, texture);
|
|
|
|
public void BindTexture(TextureTarget target, int unit, int texture)
|
|
{
|
|
switch (unit)
|
|
{
|
|
case 0:
|
|
_texture0 = 0;
|
|
_target0 = target;
|
|
break;
|
|
case 1:
|
|
_texture1 = 0;
|
|
_target1 = target;
|
|
break;
|
|
case 2:
|
|
_texture2 = 0;
|
|
_target2 = target;
|
|
break;
|
|
case 3:
|
|
_texture3 = 0;
|
|
_target3 = target;
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(unit), "I did not write support for more than 4 textures.");
|
|
}
|
|
}
|
|
|
|
public void DrawArrays(PrimitiveType type, int first, int count)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Execute()
|
|
{
|
|
GL.BindVertexArray(_vao);
|
|
GL.BindBuffer(BufferTarget.ArrayBuffer, _vbo);
|
|
|
|
ReadOnlySpan<DrawVertex> vertices = CollectionsMarshal.AsSpan(_vertices);
|
|
GL.BufferData(BufferTarget.ArrayBuffer, _vertices.Count * Unsafe.SizeOf<DrawVertex>(), vertices, BufferUsage.DynamicDraw);
|
|
|
|
foreach (DrawCall call in _calls)
|
|
{
|
|
GL.BindBufferRange(BufferTarget.UniformBuffer, 0, CommandBuffer, call.CmdIndex / CommandModulus * CommandByteSize, CommandByteSize);
|
|
GL.ActiveTexture(TextureUnit.Texture0);
|
|
GL.BindTexture(call.Target0, call.Texture0);
|
|
GL.ActiveTexture(TextureUnit.Texture1);
|
|
GL.BindTexture(call.Target1, call.Texture1);
|
|
GL.ActiveTexture(TextureUnit.Texture2);
|
|
GL.BindTexture(call.Target2, call.Texture2);
|
|
GL.ActiveTexture(TextureUnit.Texture3);
|
|
GL.BindTexture(call.Target3, call.Texture3);
|
|
|
|
Matrix4 transforms = call.Transforms;
|
|
GL.UniformMatrix4f(TransformsLocation, 1, true, ref transforms);
|
|
GL.DrawArrays(call.Type, call.Start, call.Count);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 32, 0);
|
|
GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, 32, 16);
|
|
GL.VertexAttribIPointer(2, 1, VertexAttribIType.Int, 32, 28);
|
|
GL.EnableVertexAttribArray(0);
|
|
GL.EnableVertexAttribArray(1);
|
|
GL.EnableVertexAttribArray(2);
|
|
}
|
|
|
|
private struct DrawCall
|
|
{
|
|
public PrimitiveType Type;
|
|
public int Start;
|
|
public int Count;
|
|
public int CmdIndex;
|
|
|
|
public int Texture0;
|
|
public int Texture1;
|
|
public int Texture2;
|
|
public int Texture3;
|
|
|
|
public TextureTarget Target0;
|
|
public TextureTarget Target1;
|
|
public TextureTarget Target2;
|
|
public TextureTarget Target3;
|
|
|
|
public Matrix4 Transforms;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Explicit, Size = 32)]
|
|
private struct DrawVertex
|
|
{
|
|
[FieldOffset(0)]
|
|
public Vector3 Position;
|
|
[FieldOffset(16)]
|
|
public Vector3 CharCoords;
|
|
[FieldOffset(28)]
|
|
public int CmdIndex;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A customizable immediate mode draw call queue, for the modern OpenGL user.
|
|
/// </summary>
|
|
/// <typeparam name="TCall">The call info type.</typeparam>
|
|
/// <typeparam name="TVertex">The vertex structure.</typeparam>
|
|
public abstract class DrawCallRecorder<TCall, TVertex> : IGLDisposable, IInitializer
|
|
where TVertex : unmanaged
|
|
{
|
|
/// <summary>
|
|
/// The vertex array for this queue.
|
|
/// </summary>
|
|
public int Vao { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The vertex buffer for this queue.
|
|
/// </summary>
|
|
public int Vbo { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Number of calls recorded in this queue.
|
|
/// </summary>
|
|
public int CallCount => Calls.Count;
|
|
|
|
/// <summary>
|
|
/// The number of total vertices recorded.
|
|
/// </summary>
|
|
public int TotalVertices => Vertices.Count;
|
|
|
|
/// <summary>
|
|
/// The latest draw call info.
|
|
/// </summary>
|
|
public ref TCall CurrentCall => ref _currentCall;
|
|
|
|
/// <summary>
|
|
/// The latest vertex emitted.
|
|
/// </summary>
|
|
public ref TVertex CurrentVertex => ref _currentVertex;
|
|
|
|
/// <summary>
|
|
/// True if currently recording a draw call.
|
|
/// </summary>
|
|
public bool InCall => _primitiveMode != 0;
|
|
|
|
/// <summary>
|
|
/// Size of one vertex.
|
|
/// </summary>
|
|
protected int VertexSize => Unsafe.SizeOf<TVertex>();
|
|
|
|
/// <summary>
|
|
/// The list of draw calls.
|
|
/// </summary>
|
|
protected List<DrawCall> Calls { get; } = new List<DrawCall>();
|
|
|
|
/// <summary>
|
|
/// The list of all vertices.
|
|
/// </summary>
|
|
protected List<TVertex> Vertices { get; } = new List<TVertex>();
|
|
|
|
/// <summary>
|
|
/// The value to write for the draw call info at the start of a call.
|
|
/// </summary>
|
|
[Pure] protected virtual TCall DefaultCall => default(TCall);
|
|
|
|
/// <summary>
|
|
/// The value to write for last vertex at the start of a call.
|
|
/// </summary>
|
|
[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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Record a draw call directly.
|
|
/// </summary>
|
|
/// <param name="type">The primitive type to use.</param>
|
|
/// <param name="callInfo">The call info structure to use</param>
|
|
/// <param name="vertices">The list of vertices to use.</param>
|
|
/// <exception cref="InvalidOperationException">You attempted to use this function during another draw call.</exception>
|
|
public void DrawArrays(PrimitiveType type, in TCall callInfo, ReadOnlySpan<TVertex> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start a draw call.
|
|
/// </summary>
|
|
/// <param name="type">The primitive type for the call.</param>
|
|
public void Begin(PrimitiveType type) => Begin(type, DefaultCall);
|
|
|
|
/// <summary>
|
|
/// Start a draw call.
|
|
/// </summary>
|
|
/// <param name="type">The primitive type for the call.</param>
|
|
/// <param name="callInfo">The call info.</param>
|
|
/// <exception cref="InvalidOperationException">You attempted to create a draw call within a draw call.</exception>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emit the latest or modified vertex.
|
|
/// </summary>
|
|
public void Vertex()
|
|
{
|
|
Vertex(CurrentVertex);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Emit a vertex.
|
|
/// </summary>
|
|
/// <param name="vertex">The vertex to emit.</param>
|
|
public void Vertex(in TVertex vertex)
|
|
{
|
|
Vertices.Add(CurrentVertex = vertex);
|
|
_count++;
|
|
}
|
|
|
|
/// <summary>
|
|
/// End the current call.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">You tried to end a call that you didn't begin recording.</exception>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by the execution engine before a draw call is executed.
|
|
/// </summary>
|
|
/// <param name="call">The call to prepare.</param>
|
|
protected abstract void PrepareCall(in TCall call);
|
|
|
|
/// <summary>
|
|
/// Set the vertex format for the <see cref="Vao"/> and <see cref="Vbo"/> created by the recorder.
|
|
/// </summary>
|
|
protected abstract void SetVertexFormat();
|
|
|
|
/// <summary>
|
|
/// Execute all the recorded draw calls.
|
|
/// </summary>
|
|
public void Execute()
|
|
{
|
|
GL.BindVertexArray(Vao);
|
|
GL.BindBuffer(BufferTarget.ArrayBuffer, Vbo);
|
|
|
|
ReadOnlySpan<TVertex> 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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear the draw call queue.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|