using System.Reflection; using System.Runtime.InteropServices; using BlurgText; using Dashboard.Drawing.OpenGL.Text; using OpenTK.Graphics.OpenGL; using OpenTK.Mathematics; namespace Dashboard.Drawing.OpenGL.Executors { public class TextCommandExecutor : ICommandExecutor, IInitializer { public IEnumerable Extensions { get; } = new[] { "DB_Text" }; public IContextExecutor Executor { get; private set; } private BlurgEngine Engine => Executor.ResourcePool.GetResourceManager(); public bool IsInitialized { get; private set; } private DrawCallRecorder _recorder; private int _program = 0; private int _transformsLocation = -1; private int _atlasLocation = -1; private int _borderWidthLocation = -1; private int _borderColorLocation = -1; private int _fillColorLocation = -1; public TextCommandExecutor() { Executor = null!; _recorder = new DrawCallRecorder(this); } public void Initialize() { if (IsInitialized) return; IsInitialized = true; Assembly self = typeof(TextCommandExecutor).Assembly; using Stream vsource = self.GetManifestResourceStream("Dashboard.Drawing.OpenGL.Executors.text.vert")!; using Stream fsource = self.GetManifestResourceStream("Dashboard.Drawing.OpenGL.Executors.text.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", }); GL.DeleteShader(vs); GL.DeleteShader(fs); _transformsLocation = GL.GetUniformLocation(_program, "m4Transforms"); _atlasLocation = GL.GetUniformLocation(_program, "txAtlas"); _borderWidthLocation = GL.GetUniformLocation(_program, "fBorderWidth"); _borderColorLocation = GL.GetUniformLocation(_program, "v4BorderColor"); _fillColorLocation = GL.GetUniformLocation(_program, "v4FillColor"); _recorder.Initialize(); } public void SetContextExecutor(IContextExecutor executor) { Executor = executor; } public void BeginFrame() { } public void BeginDraw() { _recorder.Clear(); } public void EndDraw() { GL.UseProgram(_program); GL.Enable(EnableCap.Blend); GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); _recorder.Execute(); GL.Disable(EnableCap.Blend); } public void EndFrame() { } public void ProcessCommand(ICommandFrame frame) { switch (frame.Command.Name) { case "Text": DrawText(frame); break; } } private void DrawText(ICommandFrame frame) { TextCommandArgs args = frame.GetParameter(); DbBlurgFont font = Engine.InternFont(args.Font); BlurgColor color; switch (args.TextBrush) { case SolidBrush solid: color = new BlurgColor() { R = solid.Color.R, G = solid.Color.G, B = solid.Color.B, A = solid.Color.A, }; break; default: color = new BlurgColor() { R = 255, G = 0, B = 255, A = 255 }; break; } BlurgResult? result = Engine.Blurg.BuildString(font.Font, font.Size, color, args.Text); if (result == null) return; Vector3 position = new Vector3(args.Position.X, args.Position.Y, args.Position.Z); ExecuteBlurgResult(result, position); result.Dispose(); } private void ExecuteBlurgResult(BlurgResult result, Vector3 position) { Matrix4 transforms = Executor.TransformStack.Top; for (int i = 0; i < result.Count; i++) { BlurgRect rect = result[i]; int texture = (int)rect.UserData; Vector4 color = new Vector4(rect.Color.R / 255f, rect.Color.G / 255f, rect.Color.B / 255f, rect.Color.A / 255f); if (i == 0) { _recorder.Begin(PrimitiveType.Triangles, new Call() { Texture = texture, FillColor = color, Transforms = transforms, }); } else if ( _recorder.CurrentCall.Texture != texture || _recorder.CurrentCall.FillColor != color) { _recorder.End(); Call call = new Call() { Texture = texture, FillColor = color, Transforms = transforms, }; _recorder.Begin(PrimitiveType.Triangles, call); } Vector3 p00 = new Vector3(rect.X, rect.Y, 0) + position; Vector3 p10 = p00 + new Vector3(rect.Width, 0, 0); Vector3 p11 = p00 + new Vector3(rect.Width, rect.Height, 0); Vector3 p01 = p00 + new Vector3(0, rect.Height, 0); Vector2 uv00 = new Vector2(rect.U0, rect.V0); Vector2 uv10 = new Vector2(rect.U1, rect.V0); Vector2 uv11 = new Vector2(rect.U1, rect.V1); Vector2 uv01 = new Vector2(rect.U0, rect.V1); _recorder.Vertex(p00, uv00); _recorder.Vertex(p10, uv10); _recorder.Vertex(p11, uv11); _recorder.Vertex(p00, uv00); _recorder.Vertex(p11, uv11); _recorder.Vertex(p01, uv01); } _recorder.End(); } private struct Call { public Matrix4 Transforms = Matrix4.Identity; public int Texture = 0; public float BorderWidth = 0f; public Vector4 FillColor = Vector4.One; public Vector4 BorderColor = new Vector4(0,0,0,1); public Call() { } } [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))] private struct Vertex { [FieldOffset(0)] public Vector3 Position; [FieldOffset(4 * sizeof(float))] public Vector2 TexCoords; } private class DrawCallRecorder : DrawCallRecorder { private TextCommandExecutor Executor { get; } public DrawCallRecorder(TextCommandExecutor executor) { Executor = executor; } public void Vertex(Vector3 position, Vector2 texCoords) { Vertex(new Vertex(){Position = position, TexCoords = texCoords}); } protected override void PrepareCall(in Call call) { Matrix4 transforms = call.Transforms; GL.UniformMatrix4f(Executor._transformsLocation, 1, true, ref transforms); GL.Uniform1f(Executor._borderWidthLocation, call.BorderWidth); GL.Uniform4f(Executor._borderColorLocation, 1, in call.BorderColor); GL.Uniform4f(Executor._fillColorLocation, 1, in call.FillColor); GL.Uniform1i(Executor._atlasLocation, 0); GL.ActiveTexture(TextureUnit.Texture0); GL.BindTexture(TextureTarget.Texture2d, call.Texture); } protected override void SetVertexFormat() { GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, VertexSize, 0); GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, VertexSize, 4*sizeof(float)); GL.EnableVertexAttribArray(0); GL.EnableVertexAttribArray(1); } } } }