diff --git a/Dashboard.Common/Drawing/DrawCall.cs b/Dashboard.Common/Drawing/DrawCall.cs index 87c4fd9..0570af1 100644 --- a/Dashboard.Common/Drawing/DrawCall.cs +++ b/Dashboard.Common/Drawing/DrawCall.cs @@ -187,17 +187,8 @@ namespace Dashboard.Drawing 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 ScissorRegion { get; init; } = new Box2d(Vector2.PositiveInfinity, Vector2.PositiveInfinity); public WindingOrder FrontFace { get; init; } = WindingOrder.Counterclockwise; @@ -205,15 +196,41 @@ namespace Dashboard.Drawing public BlendMode BlendMode { get; init; } = BlendMode.Normal; public DepthMode DepthMode { get; init; } = DepthMode.Enabled; 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 GreenMask { get; init; } = true; public bool BlueMask { get; init; } = true; public bool AlphaMask { get; init; } = true; public float PointSize { 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 Textures { get; init; } = []; public List Uniforms { get; init; } = []; diff --git a/Dashboard.Common/Drawing/IBuffer.cs b/Dashboard.Common/Drawing/IBuffer.cs new file mode 100644 index 0000000..394fc74 --- /dev/null +++ b/Dashboard.Common/Drawing/IBuffer.cs @@ -0,0 +1,43 @@ +namespace Dashboard.Drawing +{ + public enum BufferAccessPattern + { + /// + /// No hint given. Not recommended. + /// + DontCare, + + /// + /// For buffers which have a long lifetime, but the contents are initialized once. + /// + Static, + + /// + /// For buffers which have a long lifetime, but the contents can be updated every frame. + /// + Stream, + + /// + /// For buffers which have a short lifetime, used for streaming resources to the graphics hardware. + /// + Upload, + + /// + /// For buffers which have a short lifetime, used for streaming resources from the graphics hardware. + /// + Download, + } + + + public interface IBuffer : IDisposable + { + public BufferAccessPattern AccessPattern { get; } + public long Size { get; } + + void Reallocate(long newSize); + Span Map(long offset, int count = -1) where T : unmanaged; + void Unmap(); + void Read(long offset, Span span) where T : unmanaged; + void Write(long offset, ReadOnlySpan span) where T : unmanaged; + } +} diff --git a/Dashboard.Common/Drawing/IDirectRendering.cs b/Dashboard.Common/Drawing/IDirectRendering.cs index e7383a1..53eaf3f 100644 --- a/Dashboard.Common/Drawing/IDirectRendering.cs +++ b/Dashboard.Common/Drawing/IDirectRendering.cs @@ -2,66 +2,49 @@ using Dashboard.Pal; namespace Dashboard.Drawing { - public interface IShader : IDisposable - { - } - - /// - /// Base interface for creating shader pipelines. - /// - public interface IShaderCreateInfo - { - } - - public enum BufferAccessPattern - { - /// - /// No hint given. Not recommended. - /// - DontCare, - - /// - /// For buffers which have a long lifetime, but the contents are initialized once. - /// - Static, - - /// - /// For buffers which have a long lifetime, but the contents can be updated every frame. - /// - Stream, - - /// - /// For buffers which have a short lifetime, used for streaming resources to the graphics hardware. - /// - Upload, - - /// - /// For buffers which have a short lifetime, used for streaming resources from the graphics hardware. - /// - Download, - } - - public interface IBuffer : IDisposable - { - public BufferAccessPattern AccessPattern { get; } - public long Size { get; } - - void Reallocate(long newSize); - Span Map(long offset, int count = -1) where T : unmanaged; - void Unmap(); - void Read(long offset, Span span) where T : unmanaged; - void Write(long offset, ReadOnlySpan span) where T : unmanaged; - } - /// /// This extension provides direct access to the backend rendering pipeline without writing with a higher level /// abstraction than the rendering backend. /// public interface IDirectRendering : IDeviceContextExtension { - public IShader CreatePipeline(T createInfo) where T : IShaderCreateInfo; - public IBuffer CreateBuffer(BufferAccessPattern pattern, long size); + /// + /// Create a shader program. + /// + /// Type of the shader create info object. + /// An object which describes how the shader is created. + /// A shader program object. + /// + /// + /// + IShader CreateShader(T createInfo) where T : IShaderCreateInfo; + /// + /// Create a texture object. + /// + /// Type of texture to create. + /// An empty texture object. + ITexture CreateTexture(TextureType type); + + /// + /// Create a buffer object. + /// + /// Buffer access pattern hint. + /// Initial capacity of the buffer in bytes. + /// An empty buffer object. + IBuffer CreateBuffer(BufferAccessPattern pattern, long size); + + /// + /// Enqueue a draw call. + /// + /// The draw call to enqueue. void Draw(DrawCall drawCall); } + + [Obsolete("Use IDirectRendering instead.")] + public interface ITextureExtension : IDeviceContextExtension + { + /// + ITexture CreateTexture(TextureType type); + } } diff --git a/Dashboard.Common/Drawing/IImmediateMode.cs b/Dashboard.Common/Drawing/IImmediateMode.cs index 9e3089a..bb13af2 100644 --- a/Dashboard.Common/Drawing/IImmediateMode.cs +++ b/Dashboard.Common/Drawing/IImmediateMode.cs @@ -1,5 +1,7 @@ +using System.Data; using System.Drawing; using System.Numerics; +using System.Runtime.InteropServices; using Dashboard.Layout; using Dashboard.Pal; @@ -7,8 +9,57 @@ namespace Dashboard.Drawing { 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 vertices) + { + public MeshPrimitive Primitive { get; init; } = primitive; + public ReadOnlyMemory 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 { + /// + /// Enqueue a draw with the immediate mode shader pipeline. + /// + /// Information about this draw call. + void DrawImmediate(ImmediateDrawCall call); + + // TODO: move these to their own extension? void Line(Vector2 a, Vector2 b, float width, float depth, Vector4 color); void Rectangle(Box2d rectangle, float depth, Vector4 color); void Rectangle(in RectangleDrawInfo rectangle); diff --git a/Dashboard.Common/Drawing/IShader.cs b/Dashboard.Common/Drawing/IShader.cs new file mode 100644 index 0000000..e3237d9 --- /dev/null +++ b/Dashboard.Common/Drawing/IShader.cs @@ -0,0 +1,21 @@ +using System.Collections.Immutable; + +namespace Dashboard.Drawing +{ + /// + /// Base interface for creating shader pipelines. + /// + public interface IShaderCreateInfo + { + } + + public readonly record struct ShaderMappingProperty(string Name, int Index); + + public interface IShader : IDisposable + { + public ImmutableList Attributes { get; } + public ImmutableList Uniforms { get; } + public ImmutableList Blocks { get; } + public ImmutableList Textures { get; } + } +} diff --git a/Dashboard.Common/Drawing/ITextureExtension.cs b/Dashboard.Common/Drawing/ITexture.cs similarity index 92% rename from Dashboard.Common/Drawing/ITextureExtension.cs rename to Dashboard.Common/Drawing/ITexture.cs index 91ba007..c189359 100644 --- a/Dashboard.Common/Drawing/ITextureExtension.cs +++ b/Dashboard.Common/Drawing/ITexture.cs @@ -1,13 +1,7 @@ using System.Drawing; -using Dashboard.Pal; namespace Dashboard.Drawing { - public interface ITextureExtension : IDeviceContextExtension - { - ITexture CreateTexture(TextureType type); - } - public enum TextureType { Texture1D, diff --git a/Dashboard.Common/Drawing/Image.cs b/Dashboard.Common/Drawing/Image.cs index 528be5b..a737e0a 100644 --- a/Dashboard.Common/Drawing/Image.cs +++ b/Dashboard.Common/Drawing/Image.cs @@ -30,7 +30,7 @@ namespace Dashboard.Drawing if (Textures.TryGetValue(dc, out ITexture? texture)) return texture; - ITextureExtension ext = dc.ExtensionRequire(); + IDirectRendering ext = dc.ExtensionRequire(); texture = ext.CreateTexture(Type); texture.SetStorage(Format, Width, Height, Depth, Levels); for (int i = 0; i < Levels; i++) diff --git a/Dashboard.OpenGL/Drawing/DirectRendering.cs b/Dashboard.OpenGL/Drawing/DirectRendering.cs index 67f9c4a..dc1fdcb 100644 --- a/Dashboard.OpenGL/Drawing/DirectRendering.cs +++ b/Dashboard.OpenGL/Drawing/DirectRendering.cs @@ -6,22 +6,38 @@ using OpenTK.Graphics.OpenGL; 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 DriverVendor { get; } = "Dashboard"; public Version DriverVersion { get; } = new Version(0, 1); IContextBase IContextExtensionBase.Context => Context; + DeviceContext IContextExtensionBase.Context => Context; + + public bool SupportsArbTextureStorage { get; private set; } + public bool SupportsAnisotropy { get; private set; } private int _vao = -1; + private List _textures = new List(); + public void Dispose() { 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 Begin() @@ -37,7 +53,7 @@ namespace Dashboard.OpenGL.Drawing } - public IShader CreatePipeline(T createInfo) where T : IShaderCreateInfo + public IShader CreateShader(T createInfo) where T : IShaderCreateInfo { 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); + } } } diff --git a/Dashboard.OpenGL/Drawing/GLShader.cs b/Dashboard.OpenGL/Drawing/GLShader.cs index 2ed48f4..6cf9399 100644 --- a/Dashboard.OpenGL/Drawing/GLShader.cs +++ b/Dashboard.OpenGL/Drawing/GLShader.cs @@ -1,11 +1,133 @@ +using System.Collections.Immutable; +using System.Security.Cryptography.X509Certificates; using Dashboard.Drawing; +using OpenTK.Graphics.OpenGL; namespace Dashboard.OpenGL.Drawing { - public class GLShader(GLDeviceContext context, int handle) : IShader + public class GLShader : IShader { - public GLDeviceContext Context { get; } = context; - public int Handle { get; } = handle; + public GLDeviceContext Context { get; } + public int Handle { get; } + + public ImmutableList Attributes { get; } + + public ImmutableList Uniforms { get; } + + public ImmutableList Blocks { get; } + + public ImmutableList Textures { get; } + + public GLShader(GLDeviceContext context, int handle) + { + Context = context; + Handle = handle; + + List list = new List(); + List list2 = new List(); + + 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() { diff --git a/Dashboard.OpenGL/Drawing/GLStateExtensions.cs b/Dashboard.OpenGL/Drawing/GLStateExtensions.cs index 7182605..614a9ac 100644 --- a/Dashboard.OpenGL/Drawing/GLStateExtensions.cs +++ b/Dashboard.OpenGL/Drawing/GLStateExtensions.cs @@ -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) { - Vector2 min = Vector2.Max(Vector2.Zero, call.ViewportRegion.Min); - Vector2 max = Vector2.Min(size, call.ViewportRegion.Max); + Vector2 min = Vector2.Max(Vector2.Zero, state.ViewportRegion.Min); + Vector2 max = Vector2.Min(size, state.ViewportRegion.Max); GL.Viewport( (int)Math.Round(min.X), @@ -239,9 +235,10 @@ namespace Dashboard.OpenGL.Drawing (int)Math.Round(max.Y - min.Y)); } + // TODO: clamp to viewport 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); return; @@ -249,15 +246,15 @@ namespace Dashboard.OpenGL.Drawing 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)); + (int)Math.Round(state.ViewportRegion.Left), + (int)Math.Round(size.Y - state.ViewportRegion.Top), + (int)Math.Round(state.ViewportRegion.Right - state.ViewportRegion.Left), + (int)Math.Round(state.ViewportRegion.Bottom - state.ViewportRegion.Top)); } public void SetFrontFace() { - GL.FrontFace(call.FrontFace switch + GL.FrontFace(state.FrontFace switch { WindingOrder.Clockwise => FrontFaceDirection.Cw, WindingOrder.Counterclockwise => FrontFaceDirection.Ccw, @@ -267,7 +264,7 @@ namespace Dashboard.OpenGL.Drawing public void SetCullMode() { - if (call.CullMode == FaceCulling.None) + if (state.CullMode == FaceCulling.None) { GL.Disable(EnableCap.CullFace); return; @@ -275,7 +272,7 @@ namespace Dashboard.OpenGL.Drawing GL.Enable(EnableCap.CullFace); - GL.CullFace(call.CullMode switch + GL.CullFace(state.CullMode switch { FaceCulling.Both => TriangleFace.FrontAndBack, FaceCulling.Front => TriangleFace.Front, @@ -284,9 +281,47 @@ namespace Dashboard.OpenGL.Drawing }); } - public void SetBlendMode() => call.BlendMode.SetAll(); - public void SetDepthMode() => call.DepthMode.SetAll(); - public void SetStencilMode() => call.StencilMode.SetAll(); + public void SetBlendMode() => state.BlendMode.SetAll(); + public void SetDepthMode() => state.DepthMode.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() { @@ -300,21 +335,6 @@ namespace Dashboard.OpenGL.Drawing 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; diff --git a/Dashboard.OpenGL/GLTextureExtension.cs b/Dashboard.OpenGL/Drawing/GLTexture.cs similarity index 81% rename from Dashboard.OpenGL/GLTextureExtension.cs rename to Dashboard.OpenGL/Drawing/GLTexture.cs index 4ead9ad..2bee6cc 100644 --- a/Dashboard.OpenGL/GLTextureExtension.cs +++ b/Dashboard.OpenGL/Drawing/GLTexture.cs @@ -1,61 +1,12 @@ using System.Drawing; using Dashboard.Drawing; -using Dashboard.Pal; +using Dashboard.OpenGL.Drawing; using OpenTK.Graphics.OpenGL; using OGL = OpenTK.Graphics.OpenGL; namespace Dashboard.OpenGL { - public class GLTextureExtension : ITextureExtension, IContextExtensionBase - { - 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.Context => Context; - - private List _textures = new List(); - - 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 class GLTexture(DirectRendering extension, TextureType type) : ITexture { public int Handle { get; private set; } = 0; public bool IsValid => Handle != 0; @@ -87,7 +38,7 @@ namespace Dashboard.OpenGL _ => throw new NotSupportedException() }; - private GLTextureExtension Extension { get; } = extension; + private DirectRendering Extension { get; } = extension; private GLDeviceContext Context => Extension.Context; ~GLTexture() diff --git a/Dashboard.OpenGL/Drawing/ImmediateMode.cs b/Dashboard.OpenGL/Drawing/ImmediateMode.cs index da2c246..6a38e7a 100644 --- a/Dashboard.OpenGL/Drawing/ImmediateMode.cs +++ b/Dashboard.OpenGL/Drawing/ImmediateMode.cs @@ -1,7 +1,10 @@ +using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Numerics; using System.Runtime; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Security.Cryptography; using Dashboard.Drawing; using Dashboard.Pal; using OpenTK.Graphics.OpenGL; @@ -16,14 +19,16 @@ namespace Dashboard.OpenGL.Drawing public DeviceContext Context { get; private set; } = null!; - private int _program; + private IDirectRendering _dr; + private GLShader _program; + private uint _program_apos; private uint _program_atexcoord; private uint _program_acolor; private int _program_transforms; private int _program_image; private int _vao; - private int _white; + private GLTexture _white; public void Dispose() { @@ -32,43 +37,36 @@ namespace Dashboard.OpenGL.Drawing public void Require(DeviceContext context) { Context = context; + _dr = context.ExtensionRequire(); - _program = GL.CreateProgram(); + GlslShaderCreateInfo shader; - int vs = GL.CreateShader(ShaderType.VertexShader); - - using (StreamReader reader = new StreamReader(GetType().Assembly + using (StreamReader vertex = new StreamReader(GetType().Assembly .GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.vert")!)) - { - 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 + using (StreamReader fragment = new StreamReader(GetType().Assembly .GetManifestResourceStream("Dashboard.OpenGL.Drawing.immediate.frag")!)) { - GL.ShaderSource(fs, reader.ReadToEnd()); + shader = new GlslShaderCreateInfo() + { + VertexShader = vertex.ReadToEnd(), + FragmentShader = fragment.ReadToEnd(), + }; } - GL.CompileShader(fs); - GL.AttachShader(_program, fs); + _program = (GLShader)_dr.CreateShader(shader); - GL.LinkProgram(_program); - GL.DeleteShader(vs); GL.DeleteShader(fs); + _program_apos = (uint)GL.GetAttribLocation(_program.Handle, "aPos"); + _program_atexcoord = (uint)GL.GetAttribLocation(_program.Handle, "aTexCoords"); + _program_acolor = (uint)GL.GetAttribLocation(_program.Handle, "aColor"); - _program_apos = (uint)GL.GetAttribLocation(_program, "aPos"); - _program_atexcoord = (uint)GL.GetAttribLocation(_program, "aTexCoords"); - _program_acolor = (uint)GL.GetAttribLocation(_program, "aColor"); + _program_transforms = GL.GetUniformLocation(_program.Handle, "transforms"); + _program_image = GL.GetUniformLocation(_program.Handle, "image"); - _program_transforms = GL.GetUniformLocation(_program, "transforms"); - _program_image = GL.GetUniformLocation(_program, "image"); - - GL.GenTexture(out _white); - GL.BindTexture(TextureTarget.Texture2D, _white); - GL.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgb, 1, 1, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgb, PixelType.Byte, IntPtr.Zero); + _white = (GLTexture)_dr.CreateTexture(TextureType.Texture2D); + _white.SetStorage(PixelFormat.Rgb8I, 1, 1, 1, 1); + + GL.BindTexture(TextureTarget.Texture2D, _white.Handle); + GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 1, 1, OpenTK.Graphics.OpenGL.PixelFormat.Rgb, PixelType.Byte, stackalloc byte[] { 0xFF, 0xFF, 0xFF, 0xFF }); GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, (int)All.One); GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, (int)All.One); GL.TexParameteri(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, (int)All.One); @@ -113,10 +111,10 @@ namespace Dashboard.OpenGL.Drawing Matrix4x4 view = Context.ExtensionRequire().Transforms; - GL.UseProgram(_program); + GL.UseProgram(_program.Handle); GL.ActiveTexture(TextureUnit.Texture0); - GL.BindTexture(TextureTarget.Texture2D, _white); + GL.BindTexture(TextureTarget.Texture2D, _white.Handle); GL.UniformMatrix4f(_program_transforms, 1, true, ref view); GL.Uniform1i(_program_image, 0); @@ -152,10 +150,10 @@ namespace Dashboard.OpenGL.Drawing Matrix4x4 view = Context.ExtensionRequire().Transforms; - GL.UseProgram(_program); + GL.UseProgram(_program.Handle); GL.ActiveTexture(TextureUnit.Texture0); - GL.BindTexture(TextureTarget.Texture2D, _white); + GL.BindTexture(TextureTarget.Texture2D, _white.Handle); GL.UniformMatrix4f(_program_transforms, 1, true, ref view); GL.Uniform1i(_program_image, 0); @@ -204,7 +202,7 @@ namespace Dashboard.OpenGL.Drawing GL.EnableVertexAttribArray(_program_acolor); Matrix4x4 view = Context.ExtensionRequire().Transforms; - GL.UseProgram(_program); + GL.UseProgram(_program.Handle); GL.ActiveTexture(TextureUnit.Texture0); GL.BindTexture(TextureTarget.Texture2D, ((GLTexture)texture).Handle); @@ -224,17 +222,53 @@ namespace Dashboard.OpenGL.Drawing Require((DeviceContext)context); } - [StructLayout(LayoutKind.Explicit, Pack = sizeof(float) * 4, Size = Size)] - private struct ImmediateVertex(Vector3 position, Vector2 texCoords, Vector4 color) + public void DrawImmediate(ImmediateDrawCall call) { - [FieldOffset(PosOffset)] public Vector3 Position = position; - [FieldOffset(TexCoordsOffset)] public Vector2 TexCoords = texCoords; - [FieldOffset(ColorOffset)] public Vector4 Color = color; + // This is a terrible implementation as it stands but we can improve this immensely later on. + IDirectRendering dr = Context.ExtensionRequire(); - 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); + int size = call.Vertices.Length * ImmediateVertex.Size; + + IBuffer buffer = dr.CreateBuffer(BufferAccessPattern.Stream, size); + buffer.Write(0, call.Vertices.Span); + + VertexSpecification spec = CreateVertexSpec(buffer); + ReadOnlyMemory 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(), + } + ], + 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 + ] + ); } } } diff --git a/Dashboard.OpenGL/GLDeviceContext.cs b/Dashboard.OpenGL/GLDeviceContext.cs index e3e6bfe..6bb5d03 100644 --- a/Dashboard.OpenGL/GLDeviceContext.cs +++ b/Dashboard.OpenGL/GLDeviceContext.cs @@ -72,7 +72,6 @@ namespace Dashboard.OpenGL Extensions = extensions.ToImmutableHashSet(); ExtensionPreload(); - ExtensionPreload(); ExtensionPreload(); ExtensionPreload(); } diff --git a/Dashboard.sln b/Dashboard.sln index 52bb0d4..1771816 100644 --- a/Dashboard.sln +++ b/Dashboard.sln @@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D6CCC74 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dashboard.TestApplication", "tests\Dashboard.TestApplication\Dashboard.TestApplication.csproj", "{7C90B90B-DF31-439B-9080-CD805383B014}" ProjectSection(ProjectDependencies) = postProject - {1BDFEF50-C907-42C8-B63B-E4F6F585CFB5} = {1BDFEF50-C907-42C8-B63B-E4F6F585CFB5} {49A62F46-AC1C-4240-8615-020D4FBBF964} = {49A62F46-AC1C-4240-8615-020D4FBBF964} EndProjectSection EndProject diff --git a/tests/Dashboard.TestApplication/Program.cs b/tests/Dashboard.TestApplication/Program.cs index a98e0ac..403fc9c 100644 --- a/tests/Dashboard.TestApplication/Program.cs +++ b/tests/Dashboard.TestApplication/Program.cs @@ -74,64 +74,21 @@ TK.Window.SetMode(window.WindowHandle, WindowMode.Normal); window.DeviceContext.ExtensionRequire().ScaleOverride = 1.5f; IDirectRendering direct = window.DeviceContext.ExtensionRequire(); -IBuffer vertex = direct.CreateBuffer(BufferAccessPattern.Static, 1024); -float[] vertices = new float[] -{ // x, y, z, _, r, g, b, a - -0.5f, -0.5f, 0.0f, 0.0f, 1, 0, 0, 1, - +0.5f, -0.5f, 0.0f, 0.0f, 0, 1, 0, 1, - +0.0f, +0.5f, 0.0f, 0.0f, 0, 0, 1, 1, +IImmediateMode imm = window.DeviceContext.ExtensionRequire(); +ImmediateVertex[] vertices = new ImmediateVertex[] +{ // x, y, z, r, g, b, a + new ImmediateVertex(new System.Numerics.Vector3(-0.5f, -0.5f, 0.0f), System.Numerics.Vector2.Zero, new System.Numerics.Vector4(1, 0, 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)), + 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) => { if (eventArgs is not PaintEventArgs paint) return; - paint.DeviceContext.ExtensionRequire().Draw(call); + imm.DrawImmediate(new ImmediateDrawCall(MeshPrimitive.Triangle, vertices.AsMemory())); }; app.Run(true, source.Token);