From 1d3f22f6cef73d09d6b755b008d60994b029d725 Mon Sep 17 00:00:00 2001 From: "H. Utku Maden" Date: Sat, 20 Aug 2022 12:57:57 +0300 Subject: [PATCH] Clean up the font test code slightly. --- Quik.OpenTK/Class1.cs | 7 - Quik.OpenTK/OpenGLTexture.cs | 126 ++++++++++++++++++ Quik.OpenTK/OpenGLTextureManager.cs | 28 ++++ Quik/IQuikTexture.cs | 48 +++++++ Quik/IQuikTextureManager.cs | 31 +++++ Quik/QuikContext.cs | 27 ++++ Quik/QuikDraw.cs | 2 + Quik/QuikImageFormat.cs | 12 ++ Quik/Typography/IQuikFontManager.cs | 22 +++ Quik/Typography/QuikFont.cs | 2 +- Quik/VertexGenerator/QuikVertexGenerator.cs | 12 +- QuikTestApplication/Program.cs | 46 ++++--- .../QuikTestApplication.csproj | 1 + QuikTestApplication/TestFont.cs | 36 ++++- 14 files changed, 364 insertions(+), 36 deletions(-) delete mode 100644 Quik.OpenTK/Class1.cs create mode 100644 Quik.OpenTK/OpenGLTexture.cs create mode 100644 Quik.OpenTK/OpenGLTextureManager.cs create mode 100644 Quik/IQuikTexture.cs create mode 100644 Quik/IQuikTextureManager.cs create mode 100644 Quik/QuikImageFormat.cs create mode 100644 Quik/Typography/IQuikFontManager.cs diff --git a/Quik.OpenTK/Class1.cs b/Quik.OpenTK/Class1.cs deleted file mode 100644 index 4a1662c..0000000 --- a/Quik.OpenTK/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Quik.OpenTK -{ - public class Class1 - { - - } -} diff --git a/Quik.OpenTK/OpenGLTexture.cs b/Quik.OpenTK/OpenGLTexture.cs new file mode 100644 index 0000000..25feada --- /dev/null +++ b/Quik.OpenTK/OpenGLTexture.cs @@ -0,0 +1,126 @@ +using System; +using OpenTK.Graphics.OpenGL4; + +namespace Quik.OpenTK +{ + public class OpenGLTexture : IQuikTexture + { + public int TextureId { get; private set; } + private OpenGLTextureManager Manager { get; } + + internal OpenGLTexture(OpenGLTextureManager manager, QuikImageFormat format, QuikVec2 size, bool mipmaps) + { + Manager = manager; + Mipmaps = mipmaps; + Width = (int)size.X; + Height = (int)size.Y; + + TextureId = GL.GenTexture(); + + GL.BindTexture(TextureTarget.Texture2D, TextureId); + + GL.TexParameter( + TextureTarget.Texture2D, + TextureParameterName.TextureMinFilter, + (int) (mipmaps ? TextureMinFilter.LinearMipmapNearest : TextureMinFilter.Linear)); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); + + GL.TexImage2D( + TextureTarget.Texture2D, + 0, + (PixelInternalFormat)GetGlImageFormat(format), + Width, Height, 0, + PixelFormat.Rgba, PixelType.UnsignedByte, + IntPtr.Zero); + } + + ~OpenGLTexture() + { + Dispose(false); + } + + /// + public bool Equals(IQuikTexture other) => + other is OpenGLTexture && ((OpenGLTexture)other).TextureId == TextureId; + + private bool _isDisposed = false; + private void Dispose(bool disposing) + { + if (_isDisposed) return; + Manager?.Reclaim(this); + _isDisposed = true; + } + + /// + public void Dispose() => Dispose(true); + + /// + public int Width { get; } + /// + public int Height { get; } + /// + public bool Mipmaps { get; } + + /// + public void Image(IntPtr data, QuikImageFormat format, QuikVec2 size, int level) + { + GL.BindTexture(TextureTarget.Texture2D, TextureId); + GL.TexSubImage2D(TextureTarget.Texture2D, level, 0, 0, Width, Height, GetGlImageFormat(format), GetGlDataFormat(format), data); + } + + /// + public void SubImage(IntPtr data, QuikImageFormat format, QuikRectangle location, int level) + { + GL.BindTexture(TextureTarget.Texture2D, TextureId); + GL.TexSubImage2D( + TextureTarget.Texture2D, + level, + (int)location.Left, + (int)location.Bottom, + (int)location.Size.X, + (int)location.Size.Y, + GetGlImageFormat(format), + GetGlDataFormat(format), + data); + } + + /// + public void GenerateMipMaps() + { + GL.BindTexture(TextureTarget.Texture2D, TextureId); + GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); + } + + private static PixelFormat GetGlImageFormat(QuikImageFormat format) + { + switch (format) + { + case QuikImageFormat.RedF: case QuikImageFormat.RedU8: + return PixelFormat.Red; + case QuikImageFormat.RgbF: case QuikImageFormat.RgbU8: + return PixelFormat.Rgb; + case QuikImageFormat.RgbaF: case QuikImageFormat.RgbaU8: + return PixelFormat.Rgba; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private static PixelType GetGlDataFormat(QuikImageFormat format) + { + switch (format) + { + case QuikImageFormat.RedF: + case QuikImageFormat.RgbaF: + case QuikImageFormat.RgbF: + return PixelType.Float; + case QuikImageFormat.RedU8: + case QuikImageFormat.RgbaU8: + case QuikImageFormat.RgbU8: + return PixelType.UnsignedByte; + default: + throw new ArgumentOutOfRangeException(); + } + } + } +} \ No newline at end of file diff --git a/Quik.OpenTK/OpenGLTextureManager.cs b/Quik.OpenTK/OpenGLTextureManager.cs new file mode 100644 index 0000000..5c8374e --- /dev/null +++ b/Quik.OpenTK/OpenGLTextureManager.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using OpenTK.Graphics.OpenGL4; + +namespace Quik.OpenTK +{ + public class OpenGLTextureManager : IQuikTextureManager + { + public QuikContext Context { get; set; } + + private List _reclaimList = new List(); + + public IQuikTexture CreateTexture(QuikVec2 size, bool mipmaps, QuikImageFormat format) + { + return new OpenGLTexture(this, format, size, mipmaps); + } + + internal void Reclaim(OpenGLTexture texture) + { + _reclaimList.Add(texture.TextureId); + } + + public void Clear() + { + GL.DeleteTextures(_reclaimList.Count, _reclaimList.ToArray()); + _reclaimList.Clear(); + } + } +} diff --git a/Quik/IQuikTexture.cs b/Quik/IQuikTexture.cs new file mode 100644 index 0000000..172976a --- /dev/null +++ b/Quik/IQuikTexture.cs @@ -0,0 +1,48 @@ +using System; + +namespace Quik +{ + /// + /// Interface for texture instances. + /// + public interface IQuikTexture : IEquatable, IDisposable + { + /// + /// Width of the texture. + /// + int Width { get; } + + /// + /// Height of the texture. + /// + int Height { get; } + + /// + /// True if the texture can have mipmaps. + /// + bool Mipmaps { get; } + + /// + /// Upload texture data. + /// + /// Pointer to data. + /// Color format of the data. + /// Size of the texture data. + /// Mip level. + void Image(IntPtr data, QuikImageFormat format, QuikVec2 size, int level); + + /// + /// Upload texture data. + /// + /// Pointer to data. + /// Color format for the data. + /// Location of the data in the texture. + /// Mip level. + void SubImage(IntPtr data, QuikImageFormat format, QuikRectangle location, int level); + + /// + /// Generate the mip maps for the texture. + /// + void GenerateMipMaps(); + } +} \ No newline at end of file diff --git a/Quik/IQuikTextureManager.cs b/Quik/IQuikTextureManager.cs new file mode 100644 index 0000000..2439be6 --- /dev/null +++ b/Quik/IQuikTextureManager.cs @@ -0,0 +1,31 @@ +namespace Quik +{ + /// + /// Interface for QUIK texture managers. + /// + public interface IQuikTextureManager + { + /// + /// The context that owns the texture manager. + /// + QuikContext Context { get; set; } + + /// + /// Create a texture. + /// + /// Size of the texture. + /// True in order to allow mipmaps. + /// The color format of the internal texture. + /// Handle to a texture object. + /// + /// All parameters are hints. If there is a situation where the texture does not + /// have mip levels, or format cannot be specified, ignore these parameters. + /// + IQuikTexture CreateTexture(QuikVec2 size, bool mipmaps, QuikImageFormat format); + + /// + /// A function called on context clear. (useful for discarding old textures) + /// + void Clear(); + } +} \ No newline at end of file diff --git a/Quik/QuikContext.cs b/Quik/QuikContext.cs index 6687880..d6eb4a4 100644 --- a/Quik/QuikContext.cs +++ b/Quik/QuikContext.cs @@ -12,6 +12,16 @@ namespace Quik /// public QuikDraw Draw { get; } = new QuikDraw(); + /// + /// The object responsible for managing textures. + /// + public IQuikTextureManager TextureManager { get; } + + /// + /// The object responsible for managing fonts. + /// + public IQuikFontManager FontManager { get; } + public QuikStrokeStyle DefaultStroke { get; set; } = new QuikStrokeStyle(new QuikColor(0xaaaaaaff), 4); public QuikFillStyle DefaultFill { get; set; } = new QuikFillStyle() @@ -20,5 +30,22 @@ namespace Quik }; public QuikFont DefaultFont { get; set; } + + public QuikContext(IQuikTextureManager textureManager, IQuikFontManager fontManager) + { + TextureManager = textureManager; + FontManager = fontManager; + TextureManager.Context = FontManager.Context = this; + } + + /// + /// Clear the context. + /// + public void Clear() + { + Draw.Clear(); + TextureManager.Clear(); + FontManager.Clear(); + } } } \ No newline at end of file diff --git a/Quik/QuikDraw.cs b/Quik/QuikDraw.cs index 7d978af..6dcd463 100644 --- a/Quik/QuikDraw.cs +++ b/Quik/QuikDraw.cs @@ -14,6 +14,8 @@ namespace Quik /// public Queue Commands { get; } = new Queue(); + public void Clear() => Commands.Clear(); + public void Mask(QuikRectangle bounds) => Commands.Enqueue(new QuikCommandMask(bounds)); public void Line(QuikLine line) => Commands.Enqueue(new QuikCommandLine(line)); public void Line(params QuikLine[] lines) => Commands.Enqueue(new QuikCommandLines(lines)); diff --git a/Quik/QuikImageFormat.cs b/Quik/QuikImageFormat.cs new file mode 100644 index 0000000..986b232 --- /dev/null +++ b/Quik/QuikImageFormat.cs @@ -0,0 +1,12 @@ +namespace Quik +{ + public enum QuikImageFormat + { + RedU8, + RgbU8, + RgbaU8, + RedF, + RgbF, + RgbaF + } +} \ No newline at end of file diff --git a/Quik/Typography/IQuikFontManager.cs b/Quik/Typography/IQuikFontManager.cs new file mode 100644 index 0000000..feac801 --- /dev/null +++ b/Quik/Typography/IQuikFontManager.cs @@ -0,0 +1,22 @@ +namespace Quik.Typography +{ + public interface IQuikFontManager + { + /// + /// The context owning the font manager. + /// + QuikContext Context { get; set; } + + /// + /// Function called on clear. + /// + void Clear(); + + /// + /// Get a font object for the given font. + /// + /// The font style to fetch. + /// The font. + QuikFont GetFont(QuikFontStyle fontStyle); + } +} \ No newline at end of file diff --git a/Quik/Typography/QuikFont.cs b/Quik/Typography/QuikFont.cs index e13b751..18baf64 100644 --- a/Quik/Typography/QuikFont.cs +++ b/Quik/Typography/QuikFont.cs @@ -9,6 +9,6 @@ namespace Quik.Typography public QuikFontStyle FontStyle => Style; public abstract bool HasCharacter(int character); - public abstract void GetCharacter(int character, out int texture, out QuikGlyph glyph); + public abstract void GetCharacter(int character, out IQuikTexture texture, out QuikGlyph glyph); } } \ No newline at end of file diff --git a/Quik/VertexGenerator/QuikVertexGenerator.cs b/Quik/VertexGenerator/QuikVertexGenerator.cs index 8cd88a9..70cbf86 100644 --- a/Quik/VertexGenerator/QuikVertexGenerator.cs +++ b/Quik/VertexGenerator/QuikVertexGenerator.cs @@ -1407,7 +1407,7 @@ namespace Quik.VertexGenerator private void RenderCharacter(QuikCommandPutChar chr) { - Context.DefaultFont.GetCharacter(chr.Character, out int texture, out QuikGlyph metrics); + Context.DefaultFont.GetCharacter(chr.Character, out IQuikTexture texture, out QuikGlyph metrics); QuikVertex a, b, c, d; a = b = c = d = new QuikVertex() {Color = new QuikColor(0xffffffff)}; @@ -1439,19 +1439,19 @@ namespace Quik.VertexGenerator { short startElement = (short)_elementBufferPointer; QuikFont font = Context.DefaultFont; - QuikVertex vertex = new QuikVertex() {Color = new QuikColor(0xff7777ff)}; + QuikVertex vertex = new QuikVertex() {Color = new QuikColor(0x000000ff)}; QuikVec2 pointer = text.Position; - int texture = -1; + IQuikTexture texture = null; for (int i = 0; i < text.Text.Length; i++) { int chr = text.Text[i]; QuikGlyph metrics; - int ntex; + IQuikTexture ntex; font.GetCharacter(chr, out ntex, out metrics); - if (ntex != texture && texture != -1) + if (ntex != texture && texture != null) { QuikDrawCall call = CallTemplate; call.Texture = texture; @@ -1510,6 +1510,6 @@ namespace Quik.VertexGenerator public short Count; public QuikRectangle Bounds; public bool ClearStencil; - public int Texture; + public IQuikTexture Texture; } } \ No newline at end of file diff --git a/QuikTestApplication/Program.cs b/QuikTestApplication/Program.cs index 44f1148..d47a8d2 100644 --- a/QuikTestApplication/Program.cs +++ b/QuikTestApplication/Program.cs @@ -7,6 +7,8 @@ using OpenTK.Mathematics; using OpenTK.Windowing.Common; using OpenTK.Windowing.Desktop; using OpenTK.Windowing.GraphicsLibraryFramework; +using Quik.OpenTK; +using Quik.Typography; namespace QuikTestApplication { @@ -52,7 +54,7 @@ void main() window.Context.MakeCurrent(); GL.LoadBindings(new GLFWBindingsContext()); - QuikContext context = new QuikContext(); + QuikContext context = new QuikContext(new OpenGLTextureManager(), new TextFontManager()); QuikVertexGenerator gen = new QuikVertexGenerator(context); GL.Enable(EnableCap.Multisample); @@ -143,20 +145,7 @@ void main() GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Nearest); - TestFont font = new TestFont(); - font.TextureBase = GL.GenTexture(); - GL.BindTexture(TextureTarget.Texture2D, font.TextureBase); - GL.TexImage2D( - TextureTarget.Texture2D, - 0, - PixelInternalFormat.Rgb, - (int)TestFont.TextureSize.X, (int)TestFont.TextureSize.Y, 0, - PixelFormat.Rgba, PixelType.UnsignedByte, - TestFont.Texture); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Nearest); - - context.DefaultFont = font; + context.DefaultFont = context.FontManager.GetFont(null); window.Context.SwapInterval = 0; @@ -168,6 +157,8 @@ void main() GL.ClearColor(1,1,1,1); GL.Clear(ClearBufferMask.ColorBufferBit); + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); Matrix4 matrix = Matrix4.CreateOrthographicOffCenter( 0, @@ -239,7 +230,9 @@ void main() foreach (QuikDrawCall call in gen.DrawCalls) { - GL.BindTexture(TextureTarget.Texture2D, call.Texture == 0 ? whiteTexture : call.Texture); + GL.BindTexture( + TextureTarget.Texture2D, + call.Texture == null ? whiteTexture : (call.Texture as OpenGLTexture).TextureId); GL.DrawElements(BeginMode.Triangles, call.Count, DrawElementsType.UnsignedShort, call.Offset); } @@ -254,5 +247,26 @@ void main() window.Context.SwapBuffers(); } } + + public class TextFontManager : IQuikFontManager + { + public QuikContext Context { get; set; } + + private TestFont _font; + + public void Clear() + { + } + + public QuikFont GetFont(QuikFontStyle fontStyle) + { + if (_font is null) + { + _font = new TestFont(Context); + } + + return _font; + } + } } } \ No newline at end of file diff --git a/QuikTestApplication/QuikTestApplication.csproj b/QuikTestApplication/QuikTestApplication.csproj index b4f36c4..0c7591c 100644 --- a/QuikTestApplication/QuikTestApplication.csproj +++ b/QuikTestApplication/QuikTestApplication.csproj @@ -3,6 +3,7 @@ Exe net6.0 + true diff --git a/QuikTestApplication/TestFont.cs b/QuikTestApplication/TestFont.cs index 66977ee..e2f051b 100644 --- a/QuikTestApplication/TestFont.cs +++ b/QuikTestApplication/TestFont.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Xml; using Quik; using Quik.Typography; @@ -15,10 +17,22 @@ namespace QuikTestApplication private static HashSet _characters = new HashSet(); private static Dictionary _glyphs = new Dictionary(); - public static byte[] Texture { get; private set; } = Array.Empty(); + public static byte[] TextureData { get; private set; } = Array.Empty(); public static QuikVec2 TextureSize { get; private set; } - public int TextureBase { get; set; } = 1; + public IQuikTexture Texture { get; } + + public TestFont(QuikContext context) + { + Texture = context.TextureManager.CreateTexture(TextureSize, false, QuikImageFormat.RgbaU8); + unsafe + { + fixed (byte* ptr = &TextureData[0]) + { + Texture.Image((IntPtr)ptr, QuikImageFormat.RgbaU8, TextureSize, 0); + } + } + } static TestFont() { @@ -75,8 +89,18 @@ namespace QuikTestApplication using (Stream str = typeof(TestFont).Assembly.GetManifestResourceStream("QuikTestApplication.font.dat")) { - Texture = new byte[(int)str.Length]; - str.Read(Texture, 0, (int)str.Length); + TextureData = new byte[(int)str.Length]; + str.Read(TextureData, 0, (int)str.Length); + } + + // Evil pointer stuff. Beware! + Span head = MemoryMarshal.Cast(TextureData); + for (int i = 0; i < head.Length; i++) + { + if (head[i] == 0xff000000) + { + head[i] = 0x00000000; + } } } @@ -85,14 +109,14 @@ namespace QuikTestApplication return _characters.Contains((char) character); } - public override void GetCharacter(int character, out int texture, out QuikGlyph glyph) + public override void GetCharacter(int character, out IQuikTexture texture, out QuikGlyph glyph) { if (!_glyphs.TryGetValue(character, out glyph)) { glyph = _glyphs['?']; } - texture = TextureBase; + texture = Texture; } } } \ No newline at end of file