Compare commits

...

4 Commits

4 changed files with 267 additions and 4 deletions

@ -1,3 +1,4 @@
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
@ -224,4 +225,243 @@ namespace Dashboard.Drawing.OpenGL
public int CmdIndex; 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;
}
}
} }

@ -23,4 +23,20 @@ namespace Dashboard.Drawing.OpenGL
/// </summary> /// </summary>
event Action Disposed; event Action Disposed;
} }
/// <summary>
/// Extension interface for GL contexts in a DPI-aware environment.
/// </summary>
public interface IDpiAwareGLContext : IGLContext
{
/// <summary>
/// Dpi for current context.
/// </summary>
public float Dpi { get; }
/// <summary>
/// Scale for the current context. This will be used to scale drawn geometry.
/// </summary>
public float Scale { get; }
}
} }

@ -106,6 +106,7 @@ namespace Dashboard.Drawing
int sz = ToVlq(cmdIndex, cmd); int sz = ToVlq(cmdIndex, cmd);
sz += ToVlq(param.Length, cmd[sz..]); sz += ToVlq(param.Length, cmd[sz..]);
_commandStream.Write(cmd[..sz]); _commandStream.Write(cmd[..sz]);
_commandStream.Write(param);
} }
else else
{ {

@ -7,10 +7,11 @@ namespace Dashboard.Drawing
{ {
public class TextExtension : DrawExtension public class TextExtension : DrawExtension
{ {
public TextCommand TextCommand { get; } = new TextCommand(); public TextCommand TextCommand { get; }
private TextExtension() : base("DB_Text", new [] { FontExtension.Instance, BrushExtension.Instance }) private TextExtension() : base("DB_Text", new [] { FontExtension.Instance, BrushExtension.Instance })
{ {
TextCommand = new TextCommand(this);
} }
public static readonly TextExtension Instance = new TextExtension(); public static readonly TextExtension Instance = new TextExtension();
@ -19,12 +20,17 @@ namespace Dashboard.Drawing
public class TextCommand : IDrawCommand<TextCommandArgs> public class TextCommand : IDrawCommand<TextCommandArgs>
{ {
public string Name { get; } = "Text"; public string Name { get; } = "Text";
public IDrawExtension Extension { get; } = TextExtension.Instance; public IDrawExtension Extension { get; }
public int Length { get; } = -1; public int Length { get; } = -1;
public TextCommand(TextExtension ext)
{
Extension = ext;
}
public int WriteParams(DrawQueue queue, TextCommandArgs obj, Span<byte> param) public int WriteParams(DrawQueue queue, TextCommandArgs obj, Span<byte> param)
{ {
int size = Unsafe.SizeOf<Header>() + obj.Text.Length + sizeof(char); int size = Unsafe.SizeOf<Header>() + obj.Text.Length * sizeof(char) + sizeof(char);
if (param.Length < size) if (param.Length < size)
return size; return size;
@ -52,7 +58,7 @@ namespace Dashboard.Drawing
Header header = MemoryMarshal.Cast<byte, Header>(param[0..Unsafe.SizeOf<Header>()])[0]; Header header = MemoryMarshal.Cast<byte, Header>(param[0..Unsafe.SizeOf<Header>()])[0];
ReadOnlySpan<char> text = MemoryMarshal.Cast<byte, char>(param[Unsafe.SizeOf<Header>()..]); ReadOnlySpan<char> text = MemoryMarshal.Cast<byte, char>(param[Unsafe.SizeOf<Header>()..]);
if (header.BorderBrush == -1 || header.BorderRadius == 0) if (header.BorderBrush != -1 && header.BorderRadius != 0)
{ {
return new TextCommandArgs( return new TextCommandArgs(
(IFont)queue.Resources[header.Font], (IFont)queue.Resources[header.Font],