using System.Numerics; using System.Reflection; using System.Runtime.InteropServices; using BlurgText; using Dashboard.Drawing; using Dashboard.OpenGL; using OpenTK.Graphics.OpenGL; using OPENGL = OpenTK.Graphics.OpenGL; namespace Dashboard.BlurgText.OpenGL { public class BlurgGLExtension : BlurgDcExtension { private readonly List _textures = new List(); private int _program = 0; private int _transformsLocation = -1; private int _atlasLocation = -1; private int _borderWidthLocation = -1; private int _borderColorLocation = -1; private int _fillColorLocation = -1; private int _vertexArray = 0; public override Blurg Blurg { get; } public bool SystemFontsEnabled { get; set; } public bool IsDisposed { get; private set; } = false; public override string DriverName => "BlurgText"; public override string DriverVendor => "Dashboard and BlurgText"; public override Version DriverVersion => new Version(1, 0); private new GLDeviceContext Context => (GLDeviceContext)base.Context; public BlurgGLExtension() { Blurg = new Blurg(AllocateTexture, UpdateTexture); SystemFontsEnabled = Blurg.EnableSystemFonts(); } ~BlurgGLExtension() { Dispose(false); } public BlurgFontProxy InternFont(IFont font) { BlurgTextExtension appExtension = Application.ExtensionRequire(); if (font is FontInfo fontInfo) { return (BlurgFontProxy)appExtension.Load(fontInfo); } else if (font is BlurgFontProxy blurgFont) { if (blurgFont.Owner != Blurg) { if (blurgFont.Path == null) return (BlurgFontProxy)appExtension.Load(new FontInfo(blurgFont.Family, blurgFont.Weight, blurgFont.Slant, blurgFont.Stretch)); else return (BlurgFontProxy)appExtension.Load(blurgFont.Path); } else { return blurgFont; } } else { throw new Exception("Unsupported font resource."); } } private void UseProgram() { if (_program != 0) { GL.UseProgram(_program); return; } Assembly self = typeof(BlurgGLExtension).Assembly; using Stream vsource = self.GetManifestResourceStream("Dashboard.BlurgText.OpenGL.text.vert")!; using Stream fsource = self.GetManifestResourceStream("Dashboard.BlurgText.OpenGL.text.frag")!; int vs = ShaderUtil.CompileShader(ShaderType.VertexShader, vsource); int fs = ShaderUtil.CompileShader(ShaderType.FragmentShader, fsource); _program = ShaderUtil.LinkProgram(vs, fs, [ "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"); GL.UseProgram(_program); GL.Uniform1i(_atlasLocation, 0); } private void UpdateTexture(IntPtr texture, IntPtr buffer, int x, int y, int width, int height) { GL.BindTexture(TextureTarget.Texture2d, (int)texture); GL.TexSubImage2D(TextureTarget.Texture2d, 0, x, y, width, height, OPENGL.PixelFormat.Rgba, PixelType.UnsignedByte, buffer); // GL.TexSubImage2D(TextureTarget.Texture2d, 0, x, y, width, height, OPENGL.PixelFormat.Red, PixelType.Byte, buffer); } private IntPtr AllocateTexture(int width, int height) { int texture = GL.GenTexture(); GL.BindTexture(TextureTarget.Texture2d, texture); GL.TexImage2D(TextureTarget.Texture2d, 0, InternalFormat.Rgba, width, height, 0, OPENGL.PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); // GL.TexImage2D(TextureTarget.Texture2d, 0, InternalFormat.R8, width, height, 0, OPENGL.PixelFormat.Red, PixelType.Byte, IntPtr.Zero); GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleR, (int)TextureSwizzle.One); // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleG, (int)TextureSwizzle.One); // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleB, (int)TextureSwizzle.One); // GL.TexParameteri(TextureTarget.Texture2d, TextureParameterName.TextureSwizzleA, (int)TextureSwizzle.Red); _textures.Add(texture); return texture; } protected virtual void Dispose(bool disposing) { if (IsDisposed) return; IsDisposed = true; if (disposing) { GC.SuppressFinalize(this); foreach (int texture in _textures) { Context.Collector.DeleteTexture(texture); } } } public override void DrawBlurgResult(BlurgResult result, Vector3 position) { if (result.Count == 0) return; Matrix4x4 view = Matrix4x4.CreateTranslation(position) * Context.ExtensionRequire().Transforms; List drawCalls = new List(); List vertices = new List(); List indices = new List(); int offset = 0; int count = 0; DrawCall prototype = default; for (int i = 0; i < result.Count; i++) { BlurgRect rect = result[i]; int texture = (int)rect.UserData; Vector4 fillColor = new Vector4(rect.Color.R / 255f, rect.Color.G / 255f, rect.Color.B / 255f, rect.Color.A / 255f); if (i == 0) { prototype = new DrawCall(0, 0, texture, fillColor); } else if (prototype.Texture != texture || prototype.FillColor != fillColor) { drawCalls.Add(prototype with { Count = count, Offset = offset }); prototype = new DrawCall(0, 0, texture, fillColor); offset += count; count = 0; } vertices.Add(new Vertex(rect.X, rect.Y, 0, rect.U0, rect.V0)); vertices.Add(new Vertex(rect.X + rect.Width, rect.Y, 0, rect.U1, rect.V0)); vertices.Add(new Vertex(rect.X + rect.Width, rect.Y + rect.Height, 0, rect.U1, rect.V1)); vertices.Add(new Vertex(rect.X, rect.Y + rect.Height, 0, rect.U0, rect.V1)); indices.Add((ushort)(vertices.Count - 4)); indices.Add((ushort)(vertices.Count - 3)); indices.Add((ushort)(vertices.Count - 2)); indices.Add((ushort)(vertices.Count - 4)); indices.Add((ushort)(vertices.Count - 2)); indices.Add((ushort)(vertices.Count - 1)); count += 6; } drawCalls.Add(prototype with { Count = count, Offset = offset }); if (_vertexArray == 0) { _vertexArray = GL.GenVertexArray(); } GL.BindVertexArray(_vertexArray); Span buffers = stackalloc int[2]; GL.GenBuffers(2, buffers); GL.BindBuffer(BufferTarget.ArrayBuffer, buffers[0]); GL.BufferData(BufferTarget.ArrayBuffer, vertices.Count * Vertex.Size, (ReadOnlySpan)CollectionsMarshal.AsSpan(vertices), BufferUsage.StaticRead); GL.BindBuffer(BufferTarget.ElementArrayBuffer, buffers[1]); GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Count * sizeof(ushort), (ReadOnlySpan)CollectionsMarshal.AsSpan(indices), BufferUsage.StaticRead); GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, Vertex.Size, Vertex.PositionOffset); GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, Vertex.Size, Vertex.TexCoordOffset); GL.EnableVertexAttribArray(0); GL.EnableVertexAttribArray(1); UseProgram(); GL.UniformMatrix4f(_transformsLocation, 1, true, in view); GL.ActiveTexture(TextureUnit.Texture0); foreach (DrawCall call in drawCalls) { GL.BindTexture(TextureTarget.Texture2d, call.Texture); GL.Uniform4f(_fillColorLocation, call.FillColor.X, call.FillColor.Y, call.FillColor.Z, call.FillColor.W); GL.DrawElements(PrimitiveType.Triangles, call.Count, DrawElementsType.UnsignedShort, call.Offset); } GL.DeleteBuffers(2, buffers); } public override void Dispose() { Dispose(true); } [StructLayout(LayoutKind.Explicit, Size = Size)] private struct Vertex(Vector3 position, Vector2 texCoord) { [FieldOffset(PositionOffset)] public Vector3 Position = position; [FieldOffset(TexCoordOffset)] public Vector2 TexCoord = texCoord; public Vertex(float x, float y, float z, float u, float v) : this(new Vector3(x, y, z), new Vector2(u, v)) { } public const int Size = 8 * sizeof(float); public const int PositionOffset = 0 * sizeof(float); public const int TexCoordOffset = 4 * sizeof(float); } private struct DrawCall(int offset, int count, int texture, Vector4 fillColor) { public int Offset = offset; public int Count = count; public int Texture = texture; public Vector4 FillColor = fillColor; } } }