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); } } } }