Dashboard/Quik/VertexGenerator/QuikVertexGenerator.cs

310 lines
11 KiB
C#

using System;
namespace Quik.VertexGenerator
{
/// <summary>
/// Generates vertices from draw commands for GPU APIs like OpenGL.
/// </summary>
public class QuikVertexGenerator
{
// There is a very specific reason I am not using lists like a regular
// person would use. It has to do with the fact that there is no way
// to access the internal pointer of a System.Collections.Generic.List<>
// in older versions of .NET. Avoiding a copy of an entire vertex buffer
// would be very much appreciated by many devs. So please don't be
// "smart" around this code.
// - mixed.
/// <summary>
/// Controls the buffer granularity.
/// </summary>
private const int BufferGranularity = 4096;
/// <summary>
/// List of vertices.
/// </summary>
private QuikVertex[] _vertexBuffer = new QuikVertex[BufferGranularity];
/// <summary>
/// Pointer into the vertex buffer.
/// </summary>
private int _vertexBufferPointer = 0;
private float _vertexBufferUsage = 0;
/// <summary>
/// List of element indices.
/// </summary>
private short[] _elementBuffer = new short[BufferGranularity];
/// <summary>
/// Pointer into the element buffer.
/// </summary>
private int _elementBufferPointer = 0;
private float _elementBufferUsage;
private long _bufferUsageCounter;
/// <summary>
/// Get a reference to the vertex buffer.
/// </summary>
public QuikVertex[] VertexBuffer => _vertexBuffer;
/// <summary>
/// Number of vertices in the vertex buffer.
/// </summary>
public int VertexCount => _vertexBufferPointer;
/// <summary>
/// Get a reference to the element buffer.
/// </summary>
public short[] ElementBuffer => _elementBuffer;
/// <summary>
/// Number of elements in the element buffer.
/// </summary>
public int ElementCount => _elementBufferPointer;
public float CurveGranularity { get; set; } = 0.5f;
public QuikContext Context { get; }
public QuikVertexGenerator(QuikContext context)
{
Context = context;
}
/// <summary>
/// Expands the vertex buffer by the buffer granularity constant.
/// </summary>
private void ExpandVertexBuffer()
{
Array.Resize(ref _vertexBuffer, _vertexBuffer.Length + BufferGranularity);
}
/// <summary>
/// Expands the element buffer by the buffer granularity constant.
/// </summary>
private void ExpandElementBuffer()
{
Array.Resize(ref _elementBuffer, _elementBuffer.Length + BufferGranularity);
}
/// <summary>
/// Add vertices to the list.
/// </summary>
/// <param name="vertices">The list of vertices to add.</param>
private void AddVertex(params QuikVertex[] vertices)
{
int requiredCapacity = _vertexBufferPointer + vertices.Length;
while (requiredCapacity > _vertexBuffer.Length)
{
ExpandVertexBuffer();
}
Array.Copy(vertices, 0, _vertexBuffer, _vertexBufferPointer, vertices.Length);
_vertexBufferPointer += vertices.Length;
}
/// <summary>
/// Add element indices to the list.
/// </summary>
/// <param name="indices">The list of indices to add.</param>
private void AddElement(params short[] indices)
{
int requiredCapacity = _elementBufferPointer + indices.Length;
while (requiredCapacity > _elementBuffer.Length)
{
ExpandElementBuffer();
}
Array.Copy(indices, 0, _elementBuffer, _elementBufferPointer, indices.Length);
_elementBufferPointer += indices.Length;
}
private void MovingAverage(ref float average, long sampleCounter, int newSample)
{
// Thanks to stackoverflow for a neat formula.
// https://stackoverflow.com/questions/12636613/how-to-calculate-moving-average-without-keeping-the-count-and-data-total
const float order = 4;
average = average + (newSample - average) / Math.Min(sampleCounter + 1, order);
}
private bool _renderStencilMask = false;
private QuikRectangle _bounds = new QuikRectangle(
float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity);
/// <summary>
/// Clear the drawing buffers.
/// </summary>
public void Clear()
{
int newVertexSize;
int newElementSize;
_bufferUsageCounter++;
MovingAverage(ref _vertexBufferUsage, _bufferUsageCounter, _elementBufferPointer);
MovingAverage(ref _elementBufferUsage, _bufferUsageCounter, _elementBufferPointer);
newVertexSize = (int)(Math.Ceiling(_vertexBufferUsage / BufferGranularity) * BufferGranularity);
newElementSize = (int)(Math.Ceiling(_elementBufferUsage / BufferGranularity) * BufferGranularity);
Array.Resize(ref _vertexBuffer, newVertexSize);
Array.Resize(ref _elementBuffer, newElementSize);
_vertexBufferPointer = 0;
_elementBufferPointer = 0;
}
public QuikDrawCall? ConsumeCommand(QuikCommand command)
{
QuikDrawCall call = new QuikDrawCall()
{
Target = _renderStencilMask ? QuikRenderTarget.Stencil : QuikRenderTarget.Color
};
switch (command.Type)
{
case QuikCommandType.StencilMaskClear:
call.ClearStencil = true;
break;
case QuikCommandType.StencilMaskBegin:
_renderStencilMask = true;
call.Target = QuikRenderTarget.Stencil;
break;
case QuikCommandType.StencilMaskEnd:
_renderStencilMask = false;
call.Target = QuikRenderTarget.Color;
break;
case QuikCommandType.Line:
RenderLine(ref call, command as QuikCommandLine);
return call;
case QuikCommandType.Lines:
RenderLine(ref call, command as QuikCommandLines);
return call;
}
return null;
}
/// <summary>
/// Renders a line.
/// </summary>
/// <param name="call">The draw call to generate.</param>
/// <param name="line">The line to draw.</param>
private void RenderLine(ref QuikDrawCall call, QuikCommandLine line)
{
// Skip over stipple patterns for now.
QuikStrokeStyle style = line.Style ?? Context.DefaultStroke;
int endCapResolution; // Resolution of the end cap.
short startOffset = (short) _vertexBufferPointer; // Starting index pointer.
QuikVec2 tangent;
QuikVec2 normal;
tangent = (line.Line.End - line.Line.Start).Normalize();
normal = new QuikVec2() {X = -tangent.Y, Y = tangent.X};
QuikVertex baseVertex = new QuikVertex() {Color = style.Color };
QuikVertex startA = baseVertex, startB = baseVertex;
QuikVertex endA = baseVertex, endB = baseVertex;
startA.Position = line.Line.Start + style.Width / 2 * normal;
startB.Position = line.Line.Start - style.Width / 2 * normal;
endA.Position = line.Line.End + style.Width / 2 * normal;
endB.Position = line.Line.End - style.Width / 2 * normal;
// Add the major line vertices.
AddVertex(startA, startB, endA, endB);
// Add the line indices.
AddElement(
(short) (startOffset + 1),
(short) (startOffset + 2),
(short) (startOffset + 0),
(short) (startOffset + 1),
(short) (startOffset + 3),
(short) (startOffset + 2)
);
// Now calculate the end caps.
endCapResolution = (int)Math.Ceiling(MathF.PI * style.Width * CurveGranularity);
// Construct start cap.
QuikVertex circlePoint = baseVertex;
short lastIndex = startOffset;
for (int i = 0; i < endCapResolution; i++)
{
float angle = (float) (i + 1) / (endCapResolution + 1) * MathF.PI;
float cosT = MathF.Cos(angle);
float sinT = MathF.Sin(angle);
QuikVec2 displacement = new QuikVec2()
{
X = normal.X * cosT - normal.Y * sinT,
Y = normal.X * sinT + normal.Y * cosT
} * (style.Width / 2);
circlePoint.Position = line.Line.Start + displacement;
AddVertex(circlePoint);
AddElement(
(short)(startOffset + 1),
lastIndex,
(short)(_vertexBufferPointer - 1));
lastIndex = (short) (_vertexBufferPointer - 1);
}
// Construct end cap.
lastIndex = (short)(startOffset + 2);
for (int i = 0; i < endCapResolution; i++)
{
float angle = -(float) (i + 1) / (endCapResolution + 1) * MathF.PI;
float cosT = MathF.Cos(angle);
float sinT = MathF.Sin(angle);
QuikVec2 displacement = new QuikVec2()
{
X = normal.X * cosT - normal.Y * sinT,
Y = normal.X * sinT + normal.Y * cosT
} * (style.Width / 2);
circlePoint.Position = line.Line.End + displacement;
AddVertex(circlePoint);
AddElement(
(short)(startOffset + 3),
lastIndex,
(short)(_vertexBufferPointer - 1));
lastIndex = (short) (_vertexBufferPointer - 1);
}
call.Offset =(short) (startOffset * 2);
call.Count = (short) (_elementBufferPointer - startOffset);
}
private void RenderLine(ref QuikDrawCall call, QuikCommandLines lines)
{
}
}
public enum QuikRenderTarget
{
Color,
Stencil
}
public struct QuikDrawCall
{
public QuikRenderTarget Target;
public short Offset;
public short Count;
public QuikRectangle Bounds;
public bool ClearStencil;
}
}