Implement the first version of the direct rendering API.

This commit is contained in:
2026-08-02 16:44:17 +03:00
parent 18cccf1e41
commit fe96499512
6 changed files with 550 additions and 17 deletions
+16 -10
View File
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Numerics;
namespace Dashboard.Drawing
{
@@ -31,11 +32,9 @@ namespace Dashboard.Drawing
UnsignedByte,
UnsignedShort,
UnsignedInt,
UnsignedLong,
Byte,
Short,
Int,
Long,
Half,
Float,
Double,
@@ -48,7 +47,6 @@ namespace Dashboard.Drawing
UnsignedLong,
Short,
Int,
Long,
}
public enum BlendFunction
@@ -138,10 +136,12 @@ namespace Dashboard.Drawing
public readonly record struct BlendMode(bool Enabled, BlendFactor Color, BlendFactor Alpha)
{
[MemberNotNullWhen(true, nameof(Unified))]
[MemberNotNullWhen(false, nameof(Unified), nameof(UnifiedEquation))]
public bool IsSeparate => Color != Alpha;
public BlendFactor? Unified => IsSeparate ? null : Color;
public BlendEquation Equation { get; init; } = BlendEquation.Add;
public BlendEquation? UnifiedEquation => IsSeparate ? null : ColorEquation;
public BlendEquation ColorEquation { get; init; } = BlendEquation.Add;
public BlendEquation AlphaEquation { get; init; } = BlendEquation.Add;
public Color Constant { get; init; } = System.Drawing.Color.Black;
public BlendMode(BlendFunction source, BlendFunction destination)
@@ -176,13 +176,13 @@ namespace Dashboard.Drawing
public int Divisor { get; init; } = 1;
}
public record struct UniformDescriptor(UniformType Type, long Offset, long Size);
public record struct UniformDescriptor(int Location, UniformType Type, long Offset, long Size);
public struct DrawCall(MeshPrimitive primitive, IBuffer vertexBuffer)
public struct DrawCall(MeshPrimitive primitive, IBuffer vertexBuffer, int first, int count)
{
public IShader? ShaderPipeline { get; init; }
public Box2d ViewportRegion { get; init; }
public Box2d ScissorRegion { get; init; }
public Box2d ViewportRegion { get; init; } = new Box2d(Vector2.Zero, Vector2.PositiveInfinity);
public Box2d ScissorRegion { get; init; } = new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity);
public WindingOrder FrontFace { get; init; } = WindingOrder.Counterclockwise;
public FaceCulling CullMode { get; init; } = FaceCulling.Back;
public BlendMode BlendMode { get; init; } = BlendMode.Normal;
@@ -198,13 +198,19 @@ namespace Dashboard.Drawing
public float LineWidth { get; init; }= 1.0f;
public MeshPrimitive Primitive { get; init; } = primitive;
public List<ITexture> Textures { get; init; } = [];
public List<ITexture?> Textures { get; init; } = [];
public List<VertexAttribute> Attributes { get; init; } = [];
public List<UniformDescriptor> Uniforms { get; init; } = [];
public IndexType IndexType { get; init; } = IndexType.UnsignedShort;
public int First { get; init; } = first;
public int Count { get; init; } = count;
public long Offset { get; init; } = 0;
public int BaseVertex { get; init; } = 0;
public IBuffer VertexBuffer { get; init; } = vertexBuffer;
public IBuffer? ElementBuffer { get; init; }
public ReadOnlyMemory<byte> UniformData { get; init; } = ReadOnlyMemory<byte>.Empty;
public IBuffer? UniformBuffer { get; init; }
}
}
+24 -3
View File
@@ -1,3 +1,5 @@
using System.Drawing;
using System.Numerics;
using Dashboard.Drawing;
using Dashboard.Pal;
using OpenTK.Graphics.OpenGL;
@@ -12,10 +14,11 @@ namespace Dashboard.OpenGL.Drawing
public Version DriverVersion { get; } = new Version(0, 1);
IContextBase IContextExtensionBase.Context => Context;
private int _vao = -1;
public void Dispose()
{
GC.SuppressFinalize(this);
throw new NotImplementedException();
}
public void Require(DeviceContext context) => Context = context;
@@ -23,7 +26,10 @@ namespace Dashboard.OpenGL.Drawing
public void Begin()
{
if (_vao == -1)
{
GL.CreateVertexArray(out _vao);
}
}
public void End()
@@ -51,7 +57,22 @@ namespace Dashboard.OpenGL.Drawing
public void Draw(DrawCall drawCall)
{
throw new NotImplementedException();
Vector2 size = Context.FramebufferSize;
drawCall.SetAll(size, _vao, false);
if (drawCall.ElementBuffer is null)
{
GL.DrawArrays(drawCall.Primitive.OpenGL, drawCall.First, drawCall.Count);
}
else if (drawCall.BaseVertex == 0)
{
GL.DrawElements(drawCall.Primitive.OpenGL, drawCall.Count, drawCall.IndexType.OpenGL, (nint)drawCall.Offset);
}
else
{
GL.DrawElementsBaseVertex(drawCall.Primitive.OpenGL, drawCall.Count, drawCall.IndexType.OpenGL, (nint)drawCall.Offset, drawCall.BaseVertex);
}
}
}
}
@@ -0,0 +1,474 @@
using System.Drawing;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using Dashboard.Drawing;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using UniformType = Dashboard.Drawing.UniformType;
using Vector2 = System.Numerics.Vector2;
namespace Dashboard.OpenGL.Drawing
{
public static class GLStateExtensions
{
extension(VertexAttributeType type)
{
public All OpenGL => type switch
{
VertexAttributeType.UnsignedByte => All.UnsignedByte,
VertexAttributeType.UnsignedShort => All.UnsignedShort,
VertexAttributeType.UnsignedInt => All.UnsignedInt,
VertexAttributeType.Byte => All.Byte,
VertexAttributeType.Short => All.Short,
VertexAttributeType.Int => All.Int,
VertexAttributeType.Half => All.HalfFloat,
VertexAttributeType.Float => All.Float,
VertexAttributeType.Double => All.Double,
_ => throw new ArgumentOutOfRangeException(),
};
public VertexAttribType VertexAttribType => (VertexAttribType)type.OpenGL;
public VertexAttribPointerType VertexAttribPointerType => (VertexAttribPointerType)type.OpenGL;
}
extension(TextureType type)
{
public TextureTarget OpenGL => type switch
{
TextureType.Texture1D => TextureTarget.Texture1D,
TextureType.Texture2D => TextureTarget.Texture2D,
TextureType.Texture2DArray => TextureTarget.Texture2DArray,
TextureType.Texture2DCube => TextureTarget.TextureCubeMap,
TextureType.Texture3D => TextureTarget.Texture3D,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(BlendEquation eq)
{
public BlendEquationMode OpenGL => eq switch
{
BlendEquation.Add => BlendEquationMode.FuncAdd,
BlendEquation.Subtract => BlendEquationMode.FuncSubtract,
BlendEquation.Max => BlendEquationMode.Max,
BlendEquation.Min => BlendEquationMode.Min,
BlendEquation.ReverseSubtract => BlendEquationMode.FuncReverseSubtract,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(BlendFunction mode)
{
public BlendingFactor OpenGL => mode switch
{
BlendFunction.One => BlendingFactor.One,
BlendFunction.Zero => BlendingFactor.Zero,
BlendFunction.SourceAlpha => BlendingFactor.SrcAlpha,
BlendFunction.OneMinusSourceAlpha => BlendingFactor.OneMinusSrcAlpha,
BlendFunction.DestinationAlpha => BlendingFactor.DstAlpha,
BlendFunction.OneMinusDestinationAlpha => BlendingFactor.OneMinusDstAlpha,
BlendFunction.SourceColor => BlendingFactor.SrcColor,
BlendFunction.OneMinusSourceColor => BlendingFactor.OneMinusSrcColor,
BlendFunction.DestinationColor => BlendingFactor.DstColor,
BlendFunction.OneMinusDestinationColor => BlendingFactor.OneMinusDstColor,
BlendFunction.ConstantColor => BlendingFactor.OneMinusConstantColor,
BlendFunction.OneMinusConstantColor => BlendingFactor.OneMinusConstantColor,
BlendFunction.ConstantAlpha => BlendingFactor.OneMinusConstantAlpha,
BlendFunction.OneMinusConstantAlpha => BlendingFactor.OneMinusConstantAlpha,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(TestFunction test)
{
public All OpenGL => test switch
{
TestFunction.Always => All.Always,
TestFunction.Equal => All.Equal,
TestFunction.Greater => All.Greater,
TestFunction.GreaterThanOrEqual => All.Gequal,
TestFunction.LessThan => All.Less,
TestFunction.LessThanOrEqual => All.Lequal,
TestFunction.Never => All.Never,
TestFunction.NotEqual => All.Notequal,
_ => throw new ArgumentOutOfRangeException(),
};
public DepthFunction DepthFunction => (DepthFunction)test.OpenGL;
public StencilFunction StencilFunction => (StencilFunction)test.OpenGL;
}
extension(StencilOperation op)
{
public StencilOp OpenGL => op switch
{
StencilOperation.Keep => StencilOp.Keep,
StencilOperation.Zero => StencilOp.Zero,
StencilOperation.Replace => StencilOp.Replace,
StencilOperation.Increment => StencilOp.Incr,
StencilOperation.UncheckedIncrement => StencilOp.IncrWrap,
StencilOperation.Decrement => StencilOp.Decr,
StencilOperation.UncheckedDecrement => StencilOp.DecrWrap,
StencilOperation.Invert => StencilOp.Invert,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(MeshPrimitive primitive)
{
public PrimitiveType OpenGL => primitive switch
{
MeshPrimitive.Line => PrimitiveType.Lines,
MeshPrimitive.Point => PrimitiveType.Points,
MeshPrimitive.Triangle => PrimitiveType.Triangles,
MeshPrimitive.TriangleFan => PrimitiveType.TriangleFan,
MeshPrimitive.TriangleStrip => PrimitiveType.TriangleStrip,
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(IndexType index)
{
public DrawElementsType OpenGL => index switch
{
IndexType.Int or IndexType.UnsignedInt => DrawElementsType.UnsignedInt,
IndexType.Short or IndexType.UnsignedShort => DrawElementsType.UnsignedShort,
IndexType.UnsignedLong => throw new NotSupportedException(),
_ => throw new ArgumentOutOfRangeException(),
};
}
extension(BlendMode mode)
{
public void SetEnabled()
{
if (mode.Enabled)
GL.Enable(EnableCap.Blend);
else
GL.Disable(EnableCap.Blend);
}
public void SetMode()
{
if (mode.IsSeparate)
{
GL.BlendEquationSeparate(mode.ColorEquation.OpenGL, mode.AlphaEquation.OpenGL);
GL.BlendFuncSeparate(
mode.Color.Source.OpenGL, mode.Color.Destination.OpenGL,
mode.Alpha.Source.OpenGL, mode.Alpha.Destination.OpenGL);
}
else
{
GL.BlendEquation(mode.UnifiedEquation.Value.OpenGL);
GL.BlendFunc(mode.Unified.Value.Source.OpenGL, mode.Unified.Value.Destination.OpenGL);
}
}
public void SetConstant()
{
GL.BlendColor(mode.Constant.R / 255f, mode.Constant.G / 255f,mode.Constant.B / 255f,mode.Constant.A / 255f);
}
public void SetAll()
{
mode.SetEnabled();
mode.SetConstant();
mode.SetMode();
}
}
extension(DepthMode mode)
{
public void SetEnabled()
{
if (mode.Test)
GL.Enable(EnableCap.DepthTest);
else
GL.Disable(EnableCap.DepthTest);
}
public void SetMask() => GL.DepthMask(mode.Mask);
public void SetFunction() => GL.DepthFunc(mode.Function.DepthFunction);
public void SetAll()
{
mode.SetEnabled();
mode.SetMask();
mode.SetFunction();
}
}
extension(StencilMode mode)
{
public void SetEnabled()
{
if (mode.Enabled)
GL.Enable(EnableCap.StencilTest);
else
GL.Disable(EnableCap.StencilTest);
}
public void SetFunction() => GL.StencilFunc(mode.Function.StencilFunction, mode.Reference, (uint)mode.Mask);
public void SetOperations() => GL.StencilOp(mode.Fail.OpenGL, mode.DepthFail.OpenGL, mode.Pass.OpenGL);
public void SetAll()
{
mode.SetEnabled();
mode.SetFunction();
mode.SetOperations();
}
}
extension(DrawCall call)
{
public void UsePipeline() => GL.UseProgram((call.ShaderPipeline as GLShader)?.Handle ?? 0);
// TODO: clamp to viewport size
public void SetViewport(Vector2 size)
{
Vector2 min = Vector2.Max(Vector2.Zero, call.ViewportRegion.Min);
Vector2 max = Vector2.Min(size, call.ViewportRegion.Max);
GL.Viewport(
(int)Math.Round(min.X),
(int)Math.Round(size.Y - max.Y),
(int)Math.Round(max.X - min.X),
(int)Math.Round(max.Y - min.Y));
}
public void SetScissor(Vector2 size)
{
if (call.ScissorRegion == new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity))
{
GL.Disable(EnableCap.ScissorTest);
return;
}
GL.Enable(EnableCap.ScissorTest);
GL.Scissor(
(int)Math.Round(call.ViewportRegion.Left),
(int)Math.Round(size.Y - call.ViewportRegion.Top),
(int)Math.Round(call.ViewportRegion.Right - call.ViewportRegion.Left),
(int)Math.Round(call.ViewportRegion.Bottom - call.ViewportRegion.Top));
}
public void SetFrontFace()
{
GL.FrontFace(call.FrontFace switch
{
WindingOrder.Clockwise => FrontFaceDirection.Cw,
WindingOrder.Counterclockwise => FrontFaceDirection.Ccw,
_ => throw new ArgumentOutOfRangeException(),
});
}
public void SetCullMode()
{
if (call.CullMode == FaceCulling.None)
{
GL.Disable(EnableCap.CullFace);
return;
}
GL.Enable(EnableCap.CullFace);
GL.CullFace(call.CullMode switch
{
FaceCulling.Both => TriangleFace.FrontAndBack,
FaceCulling.Front => TriangleFace.Front,
FaceCulling.Back => TriangleFace.Back,
_ => throw new ArgumentOutOfRangeException(),
});
}
public void SetBlendMode() => call.BlendMode.SetAll();
public void SetDepthMode() => call.DepthMode.SetAll();
public void SetStencilMode() => call.StencilMode.SetAll();
public void SetPrimitiveRestart()
{
if (!call.EnableRestart)
{
GL.Disable(EnableCap.PrimitiveRestart);
return;
}
GL.Enable(EnableCap.PrimitiveRestart);
GL.PrimitiveRestartIndex((uint)call.RestartIndex);
}
public void SetColorMask()
{
GL.ColorMask(call.RedMask, call.GreenMask, call.BlueMask, call.AlphaMask);
}
public void SetPointSize()
{
GL.PointSize(call.PointSize);
}
public void SetLineWidth()
{
GL.LineWidth(call.LineWidth);
}
public void UseTextures()
{
int i = 0;
foreach (ITexture? texture in call.Textures)
{
GL.ActiveTexture((TextureUnit)((int)TextureUnit.Texture0 + i));
if (texture is GLTexture glTexture)
{
GL.BindTexture(glTexture.Type.OpenGL, glTexture.Handle);
}
else
{
GL.BindTexture(TextureTarget.Texture2D, 0);
}
}
}
public void UpdateVertexArrays(int vao)
{
GL.BindVertexArray(vao);
if (call.ElementBuffer != null)
{
GL.BindBuffer(BufferTarget.ElementArrayBuffer, (call.ElementBuffer as GLBuffer)?.Handle ?? 0);
}
GL.BindBuffer(BufferTarget.ArrayBuffer, (call.VertexBuffer as GLBuffer)?.Handle ?? 0);
foreach(VertexAttribute attrib in call.Attributes)
{
GL.VertexAttribPointer(
(uint)attrib.Location,
attrib.Components,
attrib.Type.VertexAttribPointerType,
false,
(int)attrib.Stride,
(nint)attrib.Offset);
GL.EnableVertexAttribArray((uint)attrib.Location);
}
}
public void UpdateUniforms(bool transpose = false)
{
int bufferIndex = 0;
foreach (UniformDescriptor uniform in call.Uniforms)
{
ReadOnlySpan<byte> uniformData = call.UniformData.Span;
switch (uniform.Type)
{
case UniformType.I1:
{
ReadOnlySpan<int> span = CastSpan<int>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform1i(uniform.Location, span.Length, span);
break;
}
case UniformType.I2:
{
ReadOnlySpan<Vector2i> span = CastSpan<Vector2i>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform2i(uniform.Location, span.Length, span);
break;
}
case UniformType.I3:
{
ReadOnlySpan<Vector3i> span = CastSpan<Vector3i>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform3i(uniform.Location, span.Length, span);
break;
}
case UniformType.I4:
{
ReadOnlySpan<Vector4i> span = CastSpan<Vector4i>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform4i(uniform.Location, span.Length, span);
break;
}
case UniformType.F1:
{
ReadOnlySpan<float> span = CastSpan<float>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform1f(uniform.Location, span.Length, span);
break;
}
case UniformType.F2:
{
ReadOnlySpan<Vector2> span = CastSpan<Vector2>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform2f(uniform.Location, span.Length, span);
break;
}
case UniformType.F3:
{
ReadOnlySpan<Vector3> span = CastSpan<Vector3>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform3f(uniform.Location, span.Length, span);
break;
}
case UniformType.F4:
{
ReadOnlySpan<Vector4> span = CastSpan<Vector4>(uniformData, uniform.Offset, uniform.Size);
GL.Uniform4f(uniform.Location, span.Length, span);
break;
}
case UniformType.Mat2:
{
ReadOnlySpan<Matrix2> span = CastSpan<Matrix2>(uniformData, uniform.Offset, uniform.Size);
GL.UniformMatrix2f(uniform.Location, span.Length, transpose, span);
break;
}
case UniformType.Mat3:
{
ReadOnlySpan<Matrix3> span = CastSpan<Matrix3>(uniformData, uniform.Offset, uniform.Size);
GL.UniformMatrix3f(uniform.Location, span.Length, transpose, span);
break;
}
case UniformType.Mat4:
{
ReadOnlySpan<Matrix4> span = CastSpan<Matrix4>(uniformData, uniform.Offset, uniform.Size);
GL.UniformMatrix4f(uniform.Location, span.Length, transpose, span);
break;
}
case UniformType.Buffer:
{
int buffer = (call.UniformBuffer as GLBuffer)?.Handle ?? 0;
int index = bufferIndex++;
GL.BindBufferRange(BufferTarget.UniformBuffer, (uint)index, buffer, (nint)uniform.Offset, (int)uniform.Size);
break;
}
default: throw new NotSupportedException();
}
}
return;
ReadOnlySpan<T> CastSpan<T>(ReadOnlySpan<byte> bytes, long offset, long size)
where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(bytes.Slice((int)offset, (int)size));
}
}
public void SetAll(Vector2 size, int vao, bool transpose = false)
{
call.UsePipeline();
call.SetViewport(size); call.SetScissor(size);
call.SetFrontFace();
call.SetCullMode();
call.SetBlendMode();
call.SetDepthMode();
call.SetStencilMode();
call.SetPrimitiveRestart();
call.SetColorMask();
call.SetDepthMode();
call.SetPointSize();
call.SetLineWidth();
call.UseTextures();
call.UpdateUniforms(transpose);
call.UpdateVertexArrays(vao);
}
}
}
}
@@ -37,7 +37,7 @@ namespace Dashboard.OpenGL.Drawing
GL.LinkProgram(program);
GL.GetProgrami(program, ProgramProperty.LinkStatus, out int flag);
if (flag == 0)
if (flag != 0)
return program;
GL.GetProgramInfoLog(program, out log);
+2
View File
@@ -60,6 +60,8 @@ namespace Dashboard.Controls
dcb.ResetScissor();
dcb.ResetTransforms();
dcb.ClearDepth();
if (Background is SolidColorBrush solidColorBrush)
dcb.ClearColor(solidColorBrush.Color);
+33 -3
View File
@@ -24,7 +24,7 @@ Application app = new Pal2Application(new ToolkitOptions()
{
GraphicsApiHints = new OpenGLGraphicsApiHints()
{
Version = new Version(3, 2),
Version = new Version(4, 1),
ForwardCompatibleFlag = true,
DebugFlag = true,
Profile = OpenGLProfile.Core,
@@ -82,9 +82,39 @@ float[] vertices = new float[]
};
vertex.Write(0, vertices);
DrawCall call = new DrawCall(MeshPrimitive.Triangle, vertex)
IShader shader = direct.CreatePipeline(new GlslShaderCreateInfo()
{
ShaderPipeline = null,
VertexShader =
"""
#version 410
layout (location = 0) in vec3 apos;
layout (location = 1) in vec4 acolor;
out vec4 vcolor;
void main()
{
gl_Position = vec4(apos, 1);
vcolor = acolor;
}
""",
FragmentShader =
"""
#version 410
in vec4 vcolor;
out vec4 fcolor;
void main()
{
fcolor = vcolor;
}
""",
});
DrawCall call = new DrawCall(MeshPrimitive.Triangle, vertex, 0, 3)
{
ShaderPipeline = shader,
Attributes =
[
new VertexAttribute(0, 3, VertexAttributeType.Float, 0, 8 * sizeof(float)),