Refactor rendering interfaces and implementations

- Refactored DirectRendering class to support new IDirectRendering interface and manage textures directly.
- Enhanced ImmediateMode to utilize ImmediateVertex struct for immediate rendering calls.
- Updated Image class to use IDirectRendering for texture creation.
- Modified test application to demonstrate immediate mode rendering with new structures.
- Removed obsolete code related to previous rendering abstractions.
This commit is contained in:
2026-08-23 21:44:05 +03:00
parent bda8e173c9
commit 3b20c53088
15 changed files with 485 additions and 259 deletions
+29 -12
View File
@@ -187,17 +187,8 @@ namespace Dashboard.Drawing
public record struct UniformDescriptor(int Location, UniformType Type, long Offset, long Size); public record struct UniformDescriptor(int Location, UniformType Type, long Offset, long Size);
public struct DrawCall(MeshPrimitive primitive, VertexSpecification vertexSpec, int first, int count) public struct PipelineState()
{ {
public IShader? ShaderPipeline { get; init; }
public VertexSpecification VertexSpecification { get; init; } = vertexSpec;
public MeshPrimitive Primitive { get; init; } = primitive;
public int First { get; init; } = first;
public int Count { get; init; } = count;
public int BaseVertex { get; init; } = 0;
public long Offset { get; init; } = 0;
public Box2d ViewportRegion { get; init; } = new Box2d(Vector2.Zero, Vector2.PositiveInfinity); public Box2d ViewportRegion { get; init; } = new Box2d(Vector2.Zero, Vector2.PositiveInfinity);
public Box2d ScissorRegion { get; init; } = new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity); public Box2d ScissorRegion { get; init; } = new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity);
public WindingOrder FrontFace { get; init; } = WindingOrder.Counterclockwise; public WindingOrder FrontFace { get; init; } = WindingOrder.Counterclockwise;
@@ -205,15 +196,41 @@ namespace Dashboard.Drawing
public BlendMode BlendMode { get; init; } = BlendMode.Normal; public BlendMode BlendMode { get; init; } = BlendMode.Normal;
public DepthMode DepthMode { get; init; } = DepthMode.Enabled; public DepthMode DepthMode { get; init; } = DepthMode.Enabled;
public StencilMode StencilMode { get; init; } = StencilMode.Off; public StencilMode StencilMode { get; init; } = StencilMode.Off;
public bool EnableRestart { get; init; } = false;
public long RestartIndex { get; init; } = -1;
public bool RedMask { get; init; } = true; public bool RedMask { get; init; } = true;
public bool GreenMask { get; init; } = true; public bool GreenMask { get; init; } = true;
public bool BlueMask { get; init; } = true; public bool BlueMask { get; init; } = true;
public bool AlphaMask { get; init; } = true; public bool AlphaMask { get; init; } = true;
public float PointSize { get; init; }= 1.0f; public float PointSize { get; init; }= 1.0f;
public float LineWidth { get; init; }= 1.0f; public float LineWidth { get; init; }= 1.0f;
}
public struct DrawCall(MeshPrimitive primitive, VertexSpecification vertexSpec, int first, int count)
{
public IShader? ShaderPipeline { get; init; }
public PipelineState PipelineState { get; init; } = new PipelineState();
public VertexSpecification VertexSpecification { get; init; } = vertexSpec;
public MeshPrimitive Primitive { get; init; } = primitive;
public int First { get; init; } = first;
public int Count { get; init; } = count;
public int BaseVertex { get; init; } = 0;
public long Offset { get; init; } = 0;
public bool EnableRestart { get; init; } = false;
public long RestartIndex { get; init; } = -1;
public Box2d ViewportRegion => PipelineState.ViewportRegion;
public Box2d ScissorRegion => PipelineState.ScissorRegion;
public WindingOrder FrontFace => PipelineState.FrontFace;
public FaceCulling CullMode => PipelineState.CullMode;
public BlendMode BlendMode => PipelineState.BlendMode;
public DepthMode DepthMode => PipelineState.DepthMode;
public StencilMode StencilMode => PipelineState.StencilMode;
public bool RedMask => PipelineState.RedMask;
public bool GreenMask => PipelineState.GreenMask;
public bool BlueMask => PipelineState.BlueMask;
public bool AlphaMask => PipelineState.AlphaMask;
public float PointSize => PipelineState.PointSize;
public float LineWidth => PipelineState.LineWidth;
public List<ITexture?> Textures { get; init; } = []; public List<ITexture?> Textures { get; init; } = [];
public List<UniformDescriptor> Uniforms { get; init; } = []; public List<UniformDescriptor> Uniforms { get; init; } = [];
+43
View File
@@ -0,0 +1,43 @@
namespace Dashboard.Drawing
{
public enum BufferAccessPattern
{
/// <summary>
/// No hint given. Not recommended.
/// </summary>
DontCare,
/// <summary>
/// For buffers which have a long lifetime, but the contents are initialized once.
/// </summary>
Static,
/// <summary>
/// For buffers which have a long lifetime, but the contents can be updated every frame.
/// </summary>
Stream,
/// <summary>
/// For buffers which have a short lifetime, used for streaming resources to the graphics hardware.
/// </summary>
Upload,
/// <summary>
/// For buffers which have a short lifetime, used for streaming resources from the graphics hardware.
/// </summary>
Download,
}
public interface IBuffer : IDisposable
{
public BufferAccessPattern AccessPattern { get; }
public long Size { get; }
void Reallocate(long newSize);
Span<T> Map<T>(long offset, int count = -1) where T : unmanaged;
void Unmap();
void Read<T>(long offset, Span<T> span) where T : unmanaged;
void Write<T>(long offset, ReadOnlySpan<T> span) where T : unmanaged;
}
}
+36 -53
View File
@@ -2,66 +2,49 @@ using Dashboard.Pal;
namespace Dashboard.Drawing namespace Dashboard.Drawing
{ {
public interface IShader : IDisposable
{
}
/// <summary>
/// Base interface for creating shader pipelines.
/// </summary>
public interface IShaderCreateInfo
{
}
public enum BufferAccessPattern
{
/// <summary>
/// No hint given. Not recommended.
/// </summary>
DontCare,
/// <summary>
/// For buffers which have a long lifetime, but the contents are initialized once.
/// </summary>
Static,
/// <summary>
/// For buffers which have a long lifetime, but the contents can be updated every frame.
/// </summary>
Stream,
/// <summary>
/// For buffers which have a short lifetime, used for streaming resources to the graphics hardware.
/// </summary>
Upload,
/// <summary>
/// For buffers which have a short lifetime, used for streaming resources from the graphics hardware.
/// </summary>
Download,
}
public interface IBuffer : IDisposable
{
public BufferAccessPattern AccessPattern { get; }
public long Size { get; }
void Reallocate(long newSize);
Span<T> Map<T>(long offset, int count = -1) where T : unmanaged;
void Unmap();
void Read<T>(long offset, Span<T> span) where T : unmanaged;
void Write<T>(long offset, ReadOnlySpan<T> span) where T : unmanaged;
}
/// <summary> /// <summary>
/// This extension provides direct access to the backend rendering pipeline without writing with a higher level /// This extension provides direct access to the backend rendering pipeline without writing with a higher level
/// abstraction than the rendering backend. /// abstraction than the rendering backend.
/// </summary> /// </summary>
public interface IDirectRendering : IDeviceContextExtension public interface IDirectRendering : IDeviceContextExtension
{ {
public IShader CreatePipeline<T>(T createInfo) where T : IShaderCreateInfo; /// <summary>
public IBuffer CreateBuffer(BufferAccessPattern pattern, long size); /// Create a shader program.
/// </summary>
/// <typeparam name="T">Type of the shader create info object.</typeparam>
/// <param name="createInfo">An object which describes how the shader is created.</param>
/// <returns>A shader program object.</returns>
/// <seealso cref="SpirvShaderCreateInfo"/>
/// <seealso cref="GlslShaderCreateInfo"/>
/// <seealso cref="GlslComputeShaderCreateInfo"/>
IShader CreateShader<T>(T createInfo) where T : IShaderCreateInfo;
/// <summary>
/// Create a texture object.
/// </summary>
/// <param name="type">Type of texture to create.</param>
/// <returns>An empty texture object.</returns>
ITexture CreateTexture(TextureType type);
/// <summary>
/// Create a buffer object.
/// </summary>
/// <param name="pattern">Buffer access pattern hint.</param>
/// <param name="size">Initial capacity of the buffer in bytes.</param>
/// <returns>An empty buffer object.</returns>
IBuffer CreateBuffer(BufferAccessPattern pattern, long size);
/// <summary>
/// Enqueue a draw call.
/// </summary>
/// <param name="drawCall">The draw call to enqueue.</param>
void Draw(DrawCall drawCall); void Draw(DrawCall drawCall);
} }
[Obsolete("Use IDirectRendering instead.")]
public interface ITextureExtension : IDeviceContextExtension
{
/// <inheritdoc cref="IDirectRendering.CreateTexture(TextureType)"/>
ITexture CreateTexture(TextureType type);
}
} }
@@ -1,5 +1,7 @@
using System.Data;
using System.Drawing; using System.Drawing;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices;
using Dashboard.Layout; using Dashboard.Layout;
using Dashboard.Pal; using Dashboard.Pal;
@@ -7,8 +9,57 @@ namespace Dashboard.Drawing
{ {
public record struct RectangleDrawInfo(Vector2 Position, ComputedBox Box, Brush Fill, Brush? Border = null); public record struct RectangleDrawInfo(Vector2 Position, ComputedBox Box, Brush Fill, Brush? Border = null);
[StructLayout(LayoutKind.Explicit, Size = Size)]
public struct ImmediateVertex()
{
[field: FieldOffset(PosOffset)]
public Vector3 Position { get; init; }= Vector3.Zero;
[field: FieldOffset(TexCoordsOffset)]
public Vector2 TexCoords { get; init; } = Vector2.Zero;
[field: FieldOffset(ColorOffset)]
public Vector4 Color { get; init; } = Vector4.One;
public ImmediateVertex(Vector3 position, Vector2 texCoords, Vector4 color) : this()
{
Position = position;
TexCoords = texCoords;
Color = color;
}
public ImmediateVertex(Vector3 position, Vector2 texCoords, Color color)
: this(
position,
texCoords,
new Vector4(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f))
{
}
public const int Size = 16 * sizeof(float);
public const int PosOffset = 0 * sizeof(float);
public const int TexCoordsOffset = 4 * sizeof(float);
public const int ColorOffset = 8 * sizeof(float);
}
public struct ImmediateDrawCall(MeshPrimitive primitive, ReadOnlyMemory<ImmediateVertex> vertices)
{
public MeshPrimitive Primitive { get; init; } = primitive;
public ReadOnlyMemory<ImmediateVertex> Vertices { get; init; } = vertices;
public PipelineState PipelineState { get; init; } = new PipelineState();
public Matrix4x4 Transforms { get; init; } = Matrix4x4.Identity;
public ITexture? Texture { get; init; } = null;
}
public interface IImmediateMode : IDeviceContextExtension public interface IImmediateMode : IDeviceContextExtension
{ {
/// <summary>
/// Enqueue a draw with the immediate mode shader pipeline.
/// </summary>
/// <param name="call">Information about this draw call.</param>
void DrawImmediate(ImmediateDrawCall call);
// TODO: move these to their own extension?
void Line(Vector2 a, Vector2 b, float width, float depth, Vector4 color); void Line(Vector2 a, Vector2 b, float width, float depth, Vector4 color);
void Rectangle(Box2d rectangle, float depth, Vector4 color); void Rectangle(Box2d rectangle, float depth, Vector4 color);
void Rectangle(in RectangleDrawInfo rectangle); void Rectangle(in RectangleDrawInfo rectangle);
+21
View File
@@ -0,0 +1,21 @@
using System.Collections.Immutable;
namespace Dashboard.Drawing
{
/// <summary>
/// Base interface for creating shader pipelines.
/// </summary>
public interface IShaderCreateInfo
{
}
public readonly record struct ShaderMappingProperty(string Name, int Index);
public interface IShader : IDisposable
{
public ImmutableList<ShaderMappingProperty> Attributes { get; }
public ImmutableList<ShaderMappingProperty> Uniforms { get; }
public ImmutableList<ShaderMappingProperty> Blocks { get; }
public ImmutableList<ShaderMappingProperty> Textures { get; }
}
}
@@ -1,13 +1,7 @@
using System.Drawing; using System.Drawing;
using Dashboard.Pal;
namespace Dashboard.Drawing namespace Dashboard.Drawing
{ {
public interface ITextureExtension : IDeviceContextExtension
{
ITexture CreateTexture(TextureType type);
}
public enum TextureType public enum TextureType
{ {
Texture1D, Texture1D,
+1 -1
View File
@@ -30,7 +30,7 @@ namespace Dashboard.Drawing
if (Textures.TryGetValue(dc, out ITexture? texture)) if (Textures.TryGetValue(dc, out ITexture? texture))
return texture; return texture;
ITextureExtension ext = dc.ExtensionRequire<ITextureExtension>(); IDirectRendering ext = dc.ExtensionRequire<IDirectRendering>();
texture = ext.CreateTexture(Type); texture = ext.CreateTexture(Type);
texture.SetStorage(Format, Width, Height, Depth, Levels); texture.SetStorage(Format, Width, Height, Depth, Levels);
for (int i = 0; i < Levels; i++) for (int i = 0; i < Levels; i++)
+39 -4
View File
@@ -6,22 +6,38 @@ using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL.Drawing namespace Dashboard.OpenGL.Drawing
{ {
public class DirectRendering : IDirectRendering public class DirectRendering : IDirectRendering, ITextureExtension
{ {
public DeviceContext Context { get; private set; } = null!; public GLDeviceContext Context { get; private set; } = null!;
public string DriverName { get; } = "Dashboard OpenGL"; public string DriverName { get; } = "Dashboard OpenGL";
public string DriverVendor { get; } = "Dashboard"; public string DriverVendor { get; } = "Dashboard";
public Version DriverVersion { get; } = new Version(0, 1); public Version DriverVersion { get; } = new Version(0, 1);
IContextBase IContextExtensionBase.Context => Context; IContextBase IContextExtensionBase.Context => Context;
DeviceContext IContextExtensionBase<DeviceContext>.Context => Context;
public bool SupportsArbTextureStorage { get; private set; }
public bool SupportsAnisotropy { get; private set; }
private int _vao = -1; private int _vao = -1;
private List<GLTexture> _textures = new List<GLTexture>();
public void Dispose() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
public void Require(DeviceContext context) => Context = context; public void Require(DeviceContext context)
{
Context = (GLDeviceContext)context;
SupportsArbTextureStorage = Context.DriverVersion >= new Version(4, 2) ||
Context.IsGLExtensionAvailable("GL_ARB_texture_storage");
SupportsAnisotropy = Context.DriverVersion >= new Version() ||
Context.IsGLExtensionAvailable("GL_EXT_texture_filter_anisotropic") ||
Context.IsGLExtensionAvailable("GL_ARB_texture_filter_anisotropic");
}
public void Require(IContextBase context) => Require((DeviceContext)context); public void Require(IContextBase context) => Require((DeviceContext)context);
public void Begin() public void Begin()
@@ -37,7 +53,7 @@ namespace Dashboard.OpenGL.Drawing
} }
public IShader CreatePipeline<T>(T createInfo) where T : IShaderCreateInfo public IShader CreateShader<T>(T createInfo) where T : IShaderCreateInfo
{ {
int program = createInfo switch int program = createInfo switch
{ {
@@ -74,5 +90,24 @@ namespace Dashboard.OpenGL.Drawing
} }
} }
public GLTexture CreateTexture(TextureType type)
{
GLTexture texture = new GLTexture(this, type);
lock (_textures) _textures.Add(texture);
return texture;
}
internal void TextureDisposed(GLTexture texture)
{
lock (_textures) _textures.Remove(texture);
}
ITexture ITextureExtension.CreateTexture(TextureType type) => CreateTexture(type);
ITexture IDirectRendering.CreateTexture(TextureType type)
{
return CreateTexture(type);
}
} }
} }
+125 -3
View File
@@ -1,11 +1,133 @@
using System.Collections.Immutable;
using System.Security.Cryptography.X509Certificates;
using Dashboard.Drawing; using Dashboard.Drawing;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL.Drawing namespace Dashboard.OpenGL.Drawing
{ {
public class GLShader(GLDeviceContext context, int handle) : IShader public class GLShader : IShader
{ {
public GLDeviceContext Context { get; } = context; public GLDeviceContext Context { get; }
public int Handle { get; } = handle; public int Handle { get; }
public ImmutableList<ShaderMappingProperty> Attributes { get; }
public ImmutableList<ShaderMappingProperty> Uniforms { get; }
public ImmutableList<ShaderMappingProperty> Blocks { get; }
public ImmutableList<ShaderMappingProperty> Textures { get; }
public GLShader(GLDeviceContext context, int handle)
{
Context = context;
Handle = handle;
List<ShaderMappingProperty> list = new List<ShaderMappingProperty>();
List<ShaderMappingProperty> list2 = new List<ShaderMappingProperty>();
int count = GL.GetProgrami(handle, ProgramProperty.ActiveAttributes);
for (int i = 0; i < count; i++)
{
list.Add(GetAttribute(handle, i));
}
Attributes = list.ToImmutableList();
list.Clear();
count = GL.GetProgrami(handle, ProgramProperty.ActiveUniforms);
for (int i = 0; i < count; i++)
{
var property = GetUniform(handle, i, out bool isSampler);
list.Add(property);
if (isSampler)
list2.Add(property);
}
Uniforms = list.ToImmutableList();
Textures = list2.ToImmutableList();
list.Clear();
count = GL.GetProgrami(handle, ProgramProperty.ActiveUniformBlocks);
for (int i = 0; i < count; i++)
{
var property = GetBlock(handle, i);
list.Add(property);
}
Blocks = list.ToImmutableList();
}
private static ShaderMappingProperty GetAttribute(int program, int index)
{
string name = GL.GetActiveAttrib(program, (uint)index, 260, out _, out _, out _);
int location = GL.GetAttribLocation(program, name);
return new ShaderMappingProperty(name, location);
}
private static ShaderMappingProperty GetUniform(int program, int index, out bool isSampler)
{
string name = GL.GetActiveUniform(program, (uint)index, 260, out _, out _, out OpenTK.Graphics.OpenGL.UniformType type);
int location = GL.GetUniformLocation(program, name);
isSampler = type switch
{
OpenTK.Graphics.OpenGL.UniformType.Sampler1D or
OpenTK.Graphics.OpenGL.UniformType.Sampler1DArray or
OpenTK.Graphics.OpenGL.UniformType.Sampler1DShadow or
OpenTK.Graphics.OpenGL.UniformType.Sampler1DArrayShadow or
OpenTK.Graphics.OpenGL.UniformType.IntSampler1D or
OpenTK.Graphics.OpenGL.UniformType.IntSampler1DArray or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler1D or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler1DArray or
OpenTK.Graphics.OpenGL.UniformType.Sampler2D or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DShadow or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DRect or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DRectShadow or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DArray or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DArrayShadow or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DMultisample or
OpenTK.Graphics.OpenGL.UniformType.Sampler2DMultisampleArray or
OpenTK.Graphics.OpenGL.UniformType.IntSampler2D or
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DRect or
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DArray or
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DMultisample or
OpenTK.Graphics.OpenGL.UniformType.IntSampler2DMultisampleArray or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2D or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DRect or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DArray or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DMultisample or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler2DMultisampleArray or
OpenTK.Graphics.OpenGL.UniformType.Sampler3D or
OpenTK.Graphics.OpenGL.UniformType.IntSampler3D or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSampler3D or
OpenTK.Graphics.OpenGL.UniformType.SamplerCube or
OpenTK.Graphics.OpenGL.UniformType.SamplerCubeShadow or
OpenTK.Graphics.OpenGL.UniformType.SamplerCubeMapArray or
OpenTK.Graphics.OpenGL.UniformType.SamplerCubeMapArrayShadow or
OpenTK.Graphics.OpenGL.UniformType.IntSamplerCube or
OpenTK.Graphics.OpenGL.UniformType.IntSamplerCubeMapArray or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSamplerCube or
OpenTK.Graphics.OpenGL.UniformType.UnsignedIntSamplerCubeMapArray
=> true,
_ => false,
};
return new ShaderMappingProperty(name, location);
}
private static ShaderMappingProperty GetBlock(int program, int index)
{
int size = 0;
unsafe
{
GL.GetActiveUniformBlockiv(program, (uint)index, UniformBlockPName.UniformBlockDataSize, &size);
}
GL.GetActiveUniformBlockName(program, (uint)index, 1024, out _, out string name);
return new ShaderMappingProperty(name, index);
}
public void Dispose() public void Dispose()
{ {
+53 -33
View File
@@ -221,16 +221,12 @@ namespace Dashboard.OpenGL.Drawing
} }
} }
extension(DrawCall call) extension(PipelineState state)
{ {
public void UsePipeline() => GL.UseProgram((call.ShaderPipeline as GLShader)?.Handle ?? 0);
// TODO: clamp to viewport size
public void SetViewport(Vector2 size) public void SetViewport(Vector2 size)
{ {
Vector2 min = Vector2.Max(Vector2.Zero, call.ViewportRegion.Min); Vector2 min = Vector2.Max(Vector2.Zero, state.ViewportRegion.Min);
Vector2 max = Vector2.Min(size, call.ViewportRegion.Max); Vector2 max = Vector2.Min(size, state.ViewportRegion.Max);
GL.Viewport( GL.Viewport(
(int)Math.Round(min.X), (int)Math.Round(min.X),
@@ -239,9 +235,10 @@ namespace Dashboard.OpenGL.Drawing
(int)Math.Round(max.Y - min.Y)); (int)Math.Round(max.Y - min.Y));
} }
// TODO: clamp to viewport size
public void SetScissor(Vector2 size) public void SetScissor(Vector2 size)
{ {
if (call.ScissorRegion == new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity)) if (state.ScissorRegion == new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity))
{ {
GL.Disable(EnableCap.ScissorTest); GL.Disable(EnableCap.ScissorTest);
return; return;
@@ -249,15 +246,15 @@ namespace Dashboard.OpenGL.Drawing
GL.Enable(EnableCap.ScissorTest); GL.Enable(EnableCap.ScissorTest);
GL.Scissor( GL.Scissor(
(int)Math.Round(call.ViewportRegion.Left), (int)Math.Round(state.ViewportRegion.Left),
(int)Math.Round(size.Y - call.ViewportRegion.Top), (int)Math.Round(size.Y - state.ViewportRegion.Top),
(int)Math.Round(call.ViewportRegion.Right - call.ViewportRegion.Left), (int)Math.Round(state.ViewportRegion.Right - state.ViewportRegion.Left),
(int)Math.Round(call.ViewportRegion.Bottom - call.ViewportRegion.Top)); (int)Math.Round(state.ViewportRegion.Bottom - state.ViewportRegion.Top));
} }
public void SetFrontFace() public void SetFrontFace()
{ {
GL.FrontFace(call.FrontFace switch GL.FrontFace(state.FrontFace switch
{ {
WindingOrder.Clockwise => FrontFaceDirection.Cw, WindingOrder.Clockwise => FrontFaceDirection.Cw,
WindingOrder.Counterclockwise => FrontFaceDirection.Ccw, WindingOrder.Counterclockwise => FrontFaceDirection.Ccw,
@@ -267,7 +264,7 @@ namespace Dashboard.OpenGL.Drawing
public void SetCullMode() public void SetCullMode()
{ {
if (call.CullMode == FaceCulling.None) if (state.CullMode == FaceCulling.None)
{ {
GL.Disable(EnableCap.CullFace); GL.Disable(EnableCap.CullFace);
return; return;
@@ -275,7 +272,7 @@ namespace Dashboard.OpenGL.Drawing
GL.Enable(EnableCap.CullFace); GL.Enable(EnableCap.CullFace);
GL.CullFace(call.CullMode switch GL.CullFace(state.CullMode switch
{ {
FaceCulling.Both => TriangleFace.FrontAndBack, FaceCulling.Both => TriangleFace.FrontAndBack,
FaceCulling.Front => TriangleFace.Front, FaceCulling.Front => TriangleFace.Front,
@@ -284,9 +281,47 @@ namespace Dashboard.OpenGL.Drawing
}); });
} }
public void SetBlendMode() => call.BlendMode.SetAll(); public void SetBlendMode() => state.BlendMode.SetAll();
public void SetDepthMode() => call.DepthMode.SetAll(); public void SetDepthMode() => state.DepthMode.SetAll();
public void SetStencilMode() => call.StencilMode.SetAll(); public void SetStencilMode() => state.StencilMode.SetAll();
public void SetColorMask()
{
GL.ColorMask(state.RedMask, state.GreenMask, state.BlueMask, state.AlphaMask);
}
public void SetPointSize()
{
GL.PointSize(state.PointSize);
}
public void SetLineWidth()
{
GL.LineWidth(state.LineWidth);
}
}
extension(DrawCall call)
{
public void UsePipeline() => GL.UseProgram((call.ShaderPipeline as GLShader)?.Handle ?? 0);
public void SetViewport(Vector2 size) => call.PipelineState.SetViewport(size);
public void SetScissor(Vector2 size) => call.PipelineState.SetViewport(size);
public void SetFrontFace() => call.PipelineState.SetFrontFace();
public void SetCullMode() => call.PipelineState.SetCullMode();
public void SetBlendMode() => call.PipelineState.BlendMode.SetAll();
public void SetDepthMode() => call.PipelineState.DepthMode.SetAll();
public void SetStencilMode() => call.PipelineState.StencilMode.SetAll();
public void SetColorMask() => call.PipelineState.SetColorMask();
public void SetPointSize() => call.PipelineState.SetPointSize();
public void SetLineWidth() => call.PipelineState.SetLineWidth();
public void SetPrimitiveRestart() public void SetPrimitiveRestart()
{ {
@@ -300,21 +335,6 @@ namespace Dashboard.OpenGL.Drawing
GL.PrimitiveRestartIndex((uint)call.RestartIndex); 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() public void UseTextures()
{ {
int i = 0; int i = 0;
@@ -1,61 +1,12 @@
using System.Drawing; using System.Drawing;
using Dashboard.Drawing; using Dashboard.Drawing;
using Dashboard.Pal; using Dashboard.OpenGL.Drawing;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
using OGL = OpenTK.Graphics.OpenGL; using OGL = OpenTK.Graphics.OpenGL;
namespace Dashboard.OpenGL namespace Dashboard.OpenGL
{ {
public class GLTextureExtension : ITextureExtension, IContextExtensionBase<GLDeviceContext> public class GLTexture(DirectRendering extension, TextureType type) : ITexture
{
public string DriverName => "Dashboard OpenGL Texture Extension";
public string DriverVendor => "Dashboard";
public Version DriverVersion => new Version(0, 1, 0);
public GLDeviceContext Context { get; private set; } = null!;
public bool SupportsArbTextureStorage { get; private set; }
public bool SupportsAnisotropy { get; private set; }
IContextBase IContextExtensionBase.Context => Context;
DeviceContext IContextExtensionBase<DeviceContext>.Context => Context;
private List<GLTexture> _textures = new List<GLTexture>();
public void Dispose()
{
}
public void Require(GLDeviceContext context)
{
Context = context;
SupportsArbTextureStorage = Context.DriverVersion >= new Version(4, 2) ||
Context.IsGLExtensionAvailable("GL_ARB_texture_storage");
SupportsAnisotropy = Context.DriverVersion >= new Version() ||
Context.IsGLExtensionAvailable("GL_EXT_texture_filter_anisotropic") ||
Context.IsGLExtensionAvailable("GL_ARB_texture_filter_anisotropic");
}
public void Require(DeviceContext context) => Require((GLDeviceContext)context);
public void Require(IContextBase context) => Require((GLDeviceContext)context);
public GLTexture CreateTexture(TextureType type)
{
GLTexture texture = new GLTexture(this, type);
lock (_textures) _textures.Add(texture);
return texture;
}
internal void TextureDisposed(GLTexture texture)
{
lock (_textures) _textures.Remove(texture);
}
ITexture ITextureExtension.CreateTexture(TextureType type) => CreateTexture(type);
}
public class GLTexture(GLTextureExtension extension, TextureType type) : ITexture
{ {
public int Handle { get; private set; } = 0; public int Handle { get; private set; } = 0;
public bool IsValid => Handle != 0; public bool IsValid => Handle != 0;
@@ -87,7 +38,7 @@ namespace Dashboard.OpenGL
_ => throw new NotSupportedException() _ => throw new NotSupportedException()
}; };
private GLTextureExtension Extension { get; } = extension; private DirectRendering Extension { get; } = extension;
private GLDeviceContext Context => Extension.Context; private GLDeviceContext Context => Extension.Context;
~GLTexture() ~GLTexture()
+77 -43
View File
@@ -1,7 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using System.Drawing; using System.Drawing;
using System.Numerics; using System.Numerics;
using System.Runtime; using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Dashboard.Drawing; using Dashboard.Drawing;
using Dashboard.Pal; using Dashboard.Pal;
using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.OpenGL;
@@ -16,14 +19,16 @@ namespace Dashboard.OpenGL.Drawing
public DeviceContext Context { get; private set; } = null!; public DeviceContext Context { get; private set; } = null!;
private int _program; private IDirectRendering _dr;
private GLShader _program;
private uint _program_apos; private uint _program_apos;
private uint _program_atexcoord; private uint _program_atexcoord;
private uint _program_acolor; private uint _program_acolor;
private int _program_transforms; private int _program_transforms;
private int _program_image; private int _program_image;
private int _vao; private int _vao;
private int _white; private GLTexture _white;
public void Dispose() public void Dispose()
{ {
@@ -32,43 +37,36 @@ namespace Dashboard.OpenGL.Drawing
public void Require(DeviceContext context) public void Require(DeviceContext context)
{ {
Context = context; Context = context;
_dr = context.ExtensionRequire<IDirectRendering>();
_program = GL.CreateProgram(); GlslShaderCreateInfo shader;
int vs = GL.CreateShader(ShaderType.VertexShader); using (StreamReader vertex = new StreamReader(GetType().Assembly
using (StreamReader reader = new StreamReader(GetType().Assembly
.GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.vert")!)) .GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.vert")!))
{ using (StreamReader fragment = new StreamReader(GetType().Assembly
GL.ShaderSource(vs, reader.ReadToEnd());
}
GL.CompileShader(vs);
GL.AttachShader(_program, vs);
int fs = GL.CreateShader(ShaderType.FragmentShader);
using (StreamReader reader = new StreamReader(GetType().Assembly
.GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.frag")!)) .GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.frag")!))
{ {
GL.ShaderSource(fs, reader.ReadToEnd()); shader = new GlslShaderCreateInfo()
{
VertexShader = vertex.ReadToEnd(),
FragmentShader = fragment.ReadToEnd(),
};
} }
GL.CompileShader(fs); _program = (GLShader)_dr.CreateShader(shader);
GL.AttachShader(_program, fs);
GL.LinkProgram(_program); _program_apos = (uint)GL.GetAttribLocation(_program.Handle, "aPos");
GL.DeleteShader(vs); GL.DeleteShader(fs); _program_atexcoord = (uint)GL.GetAttribLocation(_program.Handle, "aTexCoords");
_program_acolor = (uint)GL.GetAttribLocation(_program.Handle, "aColor");
_program_apos = (uint)GL.GetAttribLocation(_program, "aPos"); _program_transforms = GL.GetUniformLocation(_program.Handle, "transforms");
_program_atexcoord = (uint)GL.GetAttribLocation(_program, "aTexCoords"); _program_image = GL.GetUniformLocation(_program.Handle, "image");
_program_acolor = (uint)GL.GetAttribLocation(_program, "aColor");
_program_transforms = GL.GetUniformLocation(_program, "transforms"); _white = (GLTexture)_dr.CreateTexture(TextureType.Texture2D);
_program_image = GL.GetUniformLocation(_program, "image"); _white.SetStorage(PixelFormat.Rgb8I, 1, 1, 1, 1);
GL.GenTexture(out _white); GL.BindTexture(TextureTarget.Texture2D, _white.Handle);
GL.BindTexture(TextureTarget.Texture2D, _white); GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 1, 1, OpenTK.Graphics.OpenGL.PixelFormat.Rgb, PixelType.Byte, stackalloc byte[] { 0xFF, 0xFF, 0xFF, 0xFF });
GL.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgb, 1, 1, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgb, PixelType.Byte, IntPtr.Zero);
GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int)All.One); GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int)All.One);
GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int)All.One); GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int)All.One);
GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int)All.One); GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int)All.One);
@@ -113,10 +111,10 @@ namespace Dashboard.OpenGL.Drawing
Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms; Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms;
GL.UseProgram(_program); GL.UseProgram(_program.Handle);
GL.ActiveTexture(TextureUnit.Texture0); GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, _white); GL.BindTexture(TextureTarget.Texture2D, _white.Handle);
GL.UniformMatrix4f(_program_transforms, 1, true, ref view); GL.UniformMatrix4f(_program_transforms, 1, true, ref view);
GL.Uniform1i(_program_image, 0); GL.Uniform1i(_program_image, 0);
@@ -152,10 +150,10 @@ namespace Dashboard.OpenGL.Drawing
Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms; Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms;
GL.UseProgram(_program); GL.UseProgram(_program.Handle);
GL.ActiveTexture(TextureUnit.Texture0); GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, _white); GL.BindTexture(TextureTarget.Texture2D, _white.Handle);
GL.UniformMatrix4f(_program_transforms, 1, true, ref view); GL.UniformMatrix4f(_program_transforms, 1, true, ref view);
GL.Uniform1i(_program_image, 0); GL.Uniform1i(_program_image, 0);
@@ -204,7 +202,7 @@ namespace Dashboard.OpenGL.Drawing
GL.EnableVertexAttribArray(_program_acolor); GL.EnableVertexAttribArray(_program_acolor);
Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms; Matrix4x4 view = Context.ExtensionRequire<IDeviceContextBase>().Transforms;
GL.UseProgram(_program); GL.UseProgram(_program.Handle);
GL.ActiveTexture(TextureUnit.Texture0); GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture2D, ((GLTexture)texture).Handle); GL.BindTexture(TextureTarget.Texture2D, ((GLTexture)texture).Handle);
@@ -224,17 +222,53 @@ namespace Dashboard.OpenGL.Drawing
Require((DeviceContext)context); Require((DeviceContext)context);
} }
[StructLayout(LayoutKind.Explicit, Pack = sizeof(float) * 4, Size = Size)] public void DrawImmediate(ImmediateDrawCall call)
private struct ImmediateVertex(Vector3 position, Vector2 texCoords, Vector4 color)
{ {
[FieldOffset(PosOffset)] public Vector3 Position = position; // This is a terrible implementation as it stands but we can improve this immensely later on.
[FieldOffset(TexCoordsOffset)] public Vector2 TexCoords = texCoords; IDirectRendering dr = Context.ExtensionRequire<IDirectRendering>();
[FieldOffset(ColorOffset)] public Vector4 Color = color;
public const int Size = 16 * sizeof(float); int size = call.Vertices.Length * ImmediateVertex.Size;
public const int PosOffset = 0 * sizeof(float);
public const int TexCoordsOffset = 4 * sizeof(float); IBuffer buffer = dr.CreateBuffer(BufferAccessPattern.Stream, size);
public const int ColorOffset = 8 * sizeof(float); buffer.Write(0, call.Vertices.Span);
VertexSpecification spec = CreateVertexSpec(buffer);
ReadOnlyMemory<byte> uniforms = MemoryMarshal.AsBytes(stackalloc Matrix4x4[] {call.Transforms}).ToArray();
DrawCall drawCall = new DrawCall(call.Primitive, spec, 0, call.Vertices.Length)
{
ShaderPipeline = _program,
PipelineState = call.PipelineState,
UniformData = uniforms,
Uniforms = [
new UniformDescriptor() {
Location = _program_transforms,
Type = Dashboard.Drawing.UniformType.Mat4,
Offset = 0,
Size = Unsafe.SizeOf<Matrix4x4>(),
}
],
Textures = [call.Texture ?? _white]
};
dr.Draw(drawCall);
buffer.Dispose();
}
private VertexSpecification CreateVertexSpec(IBuffer buffer)
{
return new VertexSpecification(
[
new VertexAttribute((int)_program_apos, 3, VertexAttributeType.Float, ImmediateVertex.PosOffset, ImmediateVertex.Size),
new VertexAttribute((int)_program_atexcoord, 2, VertexAttributeType.Float, ImmediateVertex.TexCoordsOffset, ImmediateVertex.Size),
new VertexAttribute((int)_program_acolor, 4, VertexAttributeType.Float, ImmediateVertex.ColorOffset, ImmediateVertex.Size)
],
[
buffer,
buffer,
buffer
]
);
} }
} }
} }
-1
View File
@@ -72,7 +72,6 @@ namespace Dashboard.OpenGL
Extensions = extensions.ToImmutableHashSet(); Extensions = extensions.ToImmutableHashSet();
ExtensionPreload<DeviceContextBase>(); ExtensionPreload<DeviceContextBase>();
ExtensionPreload<GLTextureExtension>();
ExtensionPreload<ImmediateMode>(); ExtensionPreload<ImmediateMode>();
ExtensionPreload<DirectRendering>(); ExtensionPreload<DirectRendering>();
} }
-1
View File
@@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D6CCC74
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication", "tests\Dashboard.TestApplication\Dashboard.TestApplication.csproj", "{7C90B90B-DF31-439B-9080-CD805383B014}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication", "tests\Dashboard.TestApplication\Dashboard.TestApplication.csproj", "{7C90B90B-DF31-439B-9080-CD805383B014}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject
{1BDFEF50-C907-42C8-B63B-E4F6F585CFB5} = {1BDFEF50-C907-42C8-B63B-E4F6F585CFB5}
{49A62F46-AC1C-4240-8615-020D4FBBF964} = {49A62F46-AC1C-4240-8615-020D4FBBF964} {49A62F46-AC1C-4240-8615-020D4FBBF964} = {49A62F46-AC1C-4240-8615-020D4FBBF964}
EndProjectSection EndProjectSection
EndProject EndProject
+7 -50
View File
@@ -74,64 +74,21 @@ TK.Window.SetMode(window.WindowHandle, WindowMode.Normal);
window.DeviceContext.ExtensionRequire<IDeviceContextBase>().ScaleOverride = 1.5f; window.DeviceContext.ExtensionRequire<IDeviceContextBase>().ScaleOverride = 1.5f;
IDirectRendering direct = window.DeviceContext.ExtensionRequire<IDirectRendering>(); IDirectRendering direct = window.DeviceContext.ExtensionRequire<IDirectRendering>();
IBuffer vertex = direct.CreateBuffer(BufferAccessPattern.Static, 1024); IImmediateMode imm = window.DeviceContext.ExtensionRequire<IImmediateMode>();
float[] vertices = new float[] ImmediateVertex[] vertices = new ImmediateVertex[]
{ // x, y, z, _, r, g, b, a { // x, y, z, r, g, b, a
-0.5f, -0.5f, 0.0f, 0.0f, 1, 0, 0, 1, new ImmediateVertex(new System.Numerics.Vector3(-0.5f, -0.5f, 0.0f), System.Numerics.Vector2.Zero, new System.Numerics.Vector4(1, 0, 0, 1)),
+0.5f, -0.5f, 0.0f, 0.0f, 0, 1, 0, 1, new ImmediateVertex(new System.Numerics.Vector3(+0.5f, -0.5f, 0.0f), System.Numerics.Vector2.Zero, new System.Numerics.Vector4(0, 1, 0, 1)),
+0.0f, +0.5f, 0.0f, 0.0f, 0, 0, 1, 1, new ImmediateVertex(new System.Numerics.Vector3(+0.0f, +0.5f, 0.0f), System.Numerics.Vector2.Zero, new System.Numerics.Vector4(0, 0, 1, 1)),
}; };
vertex.Write(0, vertices);
IShader shader = direct.CreatePipeline(new GlslShaderCreateInfo()
{
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;
}
""",
});
VertexSpecification spec = new VertexSpecification(
[
new VertexAttribute(0, 3, VertexAttributeType.Float, 0, 8 * sizeof(float)),
new VertexAttribute(1, 4, VertexAttributeType.Float, 4 * sizeof(float), 8 * sizeof(float))
],
[vertex, vertex]
);
DrawCall call = new DrawCall(MeshPrimitive.Triangle, spec, 0, 3)
{
ShaderPipeline = shader
};
window.EventRaised += (sender, eventArgs) => window.EventRaised += (sender, eventArgs) =>
{ {
if (eventArgs is not PaintEventArgs paint) if (eventArgs is not PaintEventArgs paint)
return; return;
paint.DeviceContext.ExtensionRequire<IDirectRendering>().Draw(call); imm.DrawImmediate(new ImmediateDrawCall(MeshPrimitive.Triangle, vertices.AsMemory()));
}; };
app.Run(true, source.Token); app.Run(true, source.Token);