diff --git a/Dashboard.Common/Drawing/DrawCall.cs b/Dashboard.Common/Drawing/DrawCall.cs index 33fb52c..7eabfeb 100644 --- a/Dashboard.Common/Drawing/DrawCall.cs +++ b/Dashboard.Common/Drawing/DrawCall.cs @@ -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 Textures { get; init; } = []; + public List Textures { get; init; } = []; public List Attributes { get; init; } = []; public List 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 UniformData { get; init; } = ReadOnlyMemory.Empty; public IBuffer? UniformBuffer { get; init; } } } diff --git a/Dashboard.OpenGL/Drawing/DirectRendering.cs b/Dashboard.OpenGL/Drawing/DirectRendering.cs index f6c72e7..ea6eb41 100644 --- a/Dashboard.OpenGL/Drawing/DirectRendering.cs +++ b/Dashboard.OpenGL/Drawing/DirectRendering.cs @@ -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); + + } } } } diff --git a/Dashboard.OpenGL/Drawing/GLStateExtensions.cs b/Dashboard.OpenGL/Drawing/GLStateExtensions.cs new file mode 100644 index 0000000..dad97be --- /dev/null +++ b/Dashboard.OpenGL/Drawing/GLStateExtensions.cs @@ -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 uniformData = call.UniformData.Span; + + switch (uniform.Type) + { + case UniformType.I1: + { + ReadOnlySpan span = CastSpan(uniformData, uniform.Offset, uniform.Size); + GL.Uniform1i(uniform.Location, span.Length, span); + break; + } + case UniformType.I2: + { + ReadOnlySpan span = CastSpan(uniformData, uniform.Offset, uniform.Size); + GL.Uniform2i(uniform.Location, span.Length, span); + break; + } + case UniformType.I3: + { + ReadOnlySpan span = CastSpan(uniformData, uniform.Offset, uniform.Size); + GL.Uniform3i(uniform.Location, span.Length, span); + break; + } + case UniformType.I4: + { + ReadOnlySpan span = CastSpan(uniformData, uniform.Offset, uniform.Size); + GL.Uniform4i(uniform.Location, span.Length, span); + break; + } + case UniformType.F1: + { + ReadOnlySpan span = CastSpan(uniformData, uniform.Offset, uniform.Size); + GL.Uniform1f(uniform.Location, span.Length, span); + break; + } + case UniformType.F2: + { + ReadOnlySpan span = CastSpan(uniformData, uniform.Offset, uniform.Size); + GL.Uniform2f(uniform.Location, span.Length, span); + break; + } + case UniformType.F3: + { + ReadOnlySpan span = CastSpan(uniformData, uniform.Offset, uniform.Size); + GL.Uniform3f(uniform.Location, span.Length, span); + break; + } + case UniformType.F4: + { + ReadOnlySpan span = CastSpan(uniformData, uniform.Offset, uniform.Size); + GL.Uniform4f(uniform.Location, span.Length, span); + break; + } + case UniformType.Mat2: + { + ReadOnlySpan span = CastSpan(uniformData, uniform.Offset, uniform.Size); + GL.UniformMatrix2f(uniform.Location, span.Length, transpose, span); + break; + } + case UniformType.Mat3: + { + ReadOnlySpan span = CastSpan(uniformData, uniform.Offset, uniform.Size); + GL.UniformMatrix3f(uniform.Location, span.Length, transpose, span); + break; + } + case UniformType.Mat4: + { + ReadOnlySpan span = CastSpan(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 CastSpan(ReadOnlySpan bytes, long offset, long size) + where T : unmanaged + { + return MemoryMarshal.Cast(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); + } + } + } +} diff --git a/Dashboard.OpenGL/Drawing/KhronosShaderHelper.cs b/Dashboard.OpenGL/Drawing/KhronosShaderHelper.cs index e717ea3..660acbf 100644 --- a/Dashboard.OpenGL/Drawing/KhronosShaderHelper.cs +++ b/Dashboard.OpenGL/Drawing/KhronosShaderHelper.cs @@ -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); diff --git a/Dashboard/Controls/Form.cs b/Dashboard/Controls/Form.cs index 2ed48c7..1ff83e1 100644 --- a/Dashboard/Controls/Form.cs +++ b/Dashboard/Controls/Form.cs @@ -60,6 +60,8 @@ namespace Dashboard.Controls dcb.ResetScissor(); dcb.ResetTransforms(); + dcb.ClearDepth(); + if (Background is SolidColorBrush solidColorBrush) dcb.ClearColor(solidColorBrush.Color); diff --git a/tests/Dashboard.TestApplication/Program.cs b/tests/Dashboard.TestApplication/Program.cs index dbd44b2..3e9a9a7 100644 --- a/tests/Dashboard.TestApplication/Program.cs +++ b/tests/Dashboard.TestApplication/Program.cs @@ -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)),