using System.Drawing; using OpenTK.Graphics.OpenGL; using System.Numerics; using OTK = OpenTK.Mathematics; namespace Dashboard.Drawing.OpenGL.Executors { public class BaseCommandExecutor : IInitializer, ICommandExecutor { private int _program = 0; private readonly MappableBumpAllocator _commands = new MappableBumpAllocator(); private readonly DrawCallRecorder _calls = new DrawCallRecorder(); public bool IsInitialized { get; private set; } public IEnumerable Extensions { get; } = new[] { "DB_base" }; public IContextExecutor Executor { get; private set; } = null!; public void Initialize() { if (IsInitialized) return; if (Executor == null) throw new Exception("Executor has not been set."); IsInitialized = true; LoadShaders(); } public void SetContextExecutor(IContextExecutor executor) { Executor = executor; } public void BeginFrame() { } public void BeginDraw() { _commands.Initialize(); _calls.Initialize(); Size size = Executor.Context.FramebufferSize; Executor.TransformStack.Push(OTK.Matrix4.CreateOrthographicOffCenter( 0, size.Width, size.Height, 0, 1, -1)); GL.Viewport(0, 0, size.Width, size.Height); } public void EndDraw() { _commands.Unmap(); GL.UseProgram(_program); _calls.CommandBuffer = _commands.Handle; _calls.Execute(); } public void EndFrame() { _commands.Clear(); _calls.Clear(); } public void ProcessCommand(ICommandFrame frame) { switch (frame.Command.Name) { case "Point": DrawBasePoint(frame); break; case "Line": DrawBaseLine(frame); break; case "RectF": case "RectS": case "RectFS": DrawRect(frame); break; } } private void DrawBasePoint(ICommandFrame frame) { ref CommandInfo info = ref _commands.Take(out int index); PointCommandArgs args = frame.GetParameter(); info = new CommandInfo() { Type = SimpleDrawCommand.Point, Arg0 = args.Size, }; SetCommandCommonBrush(ref info, args.Brush, args.Brush); _calls.Transforms(Executor.TransformStack.Top); _calls.Begin(PrimitiveType.Triangles); _calls.CommandIndex(index); DrawPoint(args.Position, args.Depth, args.Size); _calls.End(); } private void DrawPoint(Vector2 position, float depth, float diameter) { // Draw a point as a isocles triangle. const float adjust = 1.1f; const float cos30 = 0.8660254038f; Vector2 top = adjust * new Vector2(0, -cos30); Vector2 left = adjust * new Vector2(-cos30, 0.5f); Vector2 right = adjust * new Vector2(cos30, 0.5f); _calls.TexCoords2(top); _calls.Vertex3(new Vector3(position + top * diameter, depth)); _calls.TexCoords2(left); _calls.Vertex3(new Vector3(position + left * diameter, depth)); _calls.TexCoords2(right); _calls.Vertex3(new Vector3(position + right * diameter, depth)); } private void DrawBaseLine(ICommandFrame frame) { ref CommandInfo info = ref _commands.Take(out int index); LineCommandArgs args = frame.GetParameter(); info = new CommandInfo() { Type = SimpleDrawCommand.Line, Arg0 = 0.5f * args.Size / (args.End - args.Start).Length(), }; SetCommandCommonBrush(ref info, args.Brush, args.Brush); _calls.Transforms(Executor.TransformStack.Top); _calls.Begin(PrimitiveType.Triangles); _calls.CommandIndex(index); DrawLine(args.Start, args.End, args.Depth, args.Size); _calls.End(); } private void DrawLine(Vector2 start, Vector2 end, float depth, float width) { float radius = 0.5f * width; Vector2 segment = end - start; float length = segment.Length(); float ratio = radius / length; Vector2 n = ratio * segment; Vector2 t = new Vector2(-n.Y, n.X); Vector2 t00 = new Vector2(-ratio, -ratio); Vector2 t10 = new Vector2(1+ratio, -ratio); Vector2 t01 = new Vector2(-ratio, +ratio); Vector2 t11 = new Vector2(1+ratio, +ratio); Vector3 x00 = new Vector3(start - n - t, depth); Vector3 x10 = new Vector3(end + n - t, depth); Vector3 x01 = new Vector3(start - n + t, depth); Vector3 x11 = new Vector3(end + n + t, depth); _calls.TexCoords2(t00); _calls.Vertex3(x00); _calls.TexCoords2(t01); _calls.Vertex3(x01); _calls.TexCoords2(t11); _calls.Vertex3(x11); _calls.TexCoords2(t00); _calls.Vertex3(x00); _calls.TexCoords2(t11); _calls.Vertex3(x11); _calls.TexCoords2(t10); _calls.Vertex3(x10); } private void DrawRect(ICommandFrame frame) { ref CommandInfo info = ref _commands.Take(out int index); RectCommandArgs args = frame.GetParameter(); Vector2 size = Vector2.Abs(args.End - args.Start); float aspect = size.X / size.Y; float border = args.StrikeSize; float normRad = args.StrikeSize / size.Y; float wideRad = aspect * normRad; int flags = 0; switch (frame.Command.Name) { case "RectF": flags |= 1; break; case "RectS": flags |= 2; break; case "RectFS": flags |= 3; break; } switch (args.BorderKind) { case BorderKind.Inset: flags |= 2 << 2; break; case BorderKind.Outset: flags |= 1 << 2; break; } info = new CommandInfo() { Type = SimpleDrawCommand.Rect, Flags = flags, Arg0 = aspect, Arg1 = normRad, }; SetCommandCommonBrush(ref info, args.FillBrush, args.StrikeBrush); _calls.Transforms(Executor.TransformStack.Top); _calls.Begin(PrimitiveType.Triangles); _calls.CommandIndex(index); Vector2 t00 = new Vector2(-wideRad, -normRad); Vector2 t10 = new Vector2(1+wideRad, -normRad); Vector2 t01 = new Vector2(-wideRad, 1+normRad); Vector2 t11 = new Vector2(1+wideRad, 1+normRad); Vector3 x00 = new Vector3(args.Start.X - border, args.Start.Y - border, args.Depth); Vector3 x10 = new Vector3(args.End.X + border, args.Start.Y - border, args.Depth); Vector3 x01 = new Vector3(args.Start.X - border, args.End.Y + border, args.Depth); Vector3 x11 = new Vector3(args.End.X + border, args.End.Y + border, args.Depth); _calls.TexCoords2(t00); _calls.Vertex3(x00); _calls.TexCoords2(t01); _calls.Vertex3(x01); _calls.TexCoords2(t11); _calls.Vertex3(x11); _calls.TexCoords2(t00); _calls.Vertex3(x00); _calls.TexCoords2(t11); _calls.Vertex3(x11); _calls.TexCoords2(t10); _calls.Vertex3(x10); _calls.End(); } protected void SetCommandCommonBrush(ref CommandInfo info, IBrush? fill, IBrush? border) { switch (fill?.Kind.Name) { case "DB_Brush_solid": SolidBrush solid = (SolidBrush)fill; Vector4 color = new Vector4(solid.Color.R/255f, solid.Color.G/255f, solid.Color.B/255f, solid.Color.A/255f); info.FgColor = color; break; case "DB_Brush_gradient": GradientBrush gradient = (GradientBrush)fill; GradientUniformBuffer gradients = Executor.ResourcePool.GetResourceManager(); gradients.Initialize(); GradientUniformBuffer.Entry entry = gradients.InternGradient(gradient.Gradient); info.FgGradientIndex = entry.Offset; info.FgGradientCount = entry.Count; break; case null: // Craete a magenta brush for this. info.FgColor = new Vector4(1, 0, 1, 1); break; } switch (border?.Kind.Name) { case "DB_Brush_solid": SolidBrush solid = (SolidBrush)border; Vector4 color = new Vector4(solid.Color.R/255f, solid.Color.G/255f, solid.Color.B/255f, solid.Color.A/255f); info.BgColor = color; break; case "DB_Brush_gradient": GradientBrush gradient = (GradientBrush)border; GradientUniformBuffer gradients = Executor.ResourcePool.GetResourceManager(); gradients.Initialize(); GradientUniformBuffer.Entry entry = gradients.InternGradient(gradient.Gradient); info.BgGradientIndex = entry.Offset; info.BgGradientCount = entry.Count; break; case null: // Craete a magenta brush for this. info.BgColor = new Vector4(1, 0, 1, 1); break; } } private void LoadShaders() { using Stream vsource = FetchEmbeddedResource("Dashboard.Drawing.OpenGL.Executors.simple.vert"); using Stream fsource = FetchEmbeddedResource("Dashboard.Drawing.OpenGL.Executors.simple.frag"); int vs = ShaderUtil.CompileShader(ShaderType.VertexShader, vsource); int fs = ShaderUtil.CompileShader(ShaderType.FragmentShader, fsource); _program = ShaderUtil.LinkProgram(vs, fs, new [] { "a_v3Position", "a_v2TexCoords", "a_iCmdIndex", }); GL.DeleteShader(vs); GL.DeleteShader(fs); GL.UniformBlockBinding(_program, GL.GetUniformBlockIndex(_program, "CommandBlock"), 0); } private static Stream FetchEmbeddedResource(string name) { return typeof(BaseCommandExecutor).Assembly.GetManifestResourceStream(name)!; } } }