Clean up the font test code slightly.

This commit is contained in:
H. Utku Maden 2022-08-20 12:57:57 +03:00
parent bf47915491
commit 1d3f22f6ce
14 changed files with 364 additions and 36 deletions

@ -1,7 +0,0 @@
namespace Quik.OpenTK
{
public class Class1
{
}
}

@ -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);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public void Dispose() => Dispose(true);
/// <inheritdoc />
public int Width { get; }
/// <inheritdoc />
public int Height { get; }
/// <inheritdoc />
public bool Mipmaps { get; }
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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();
}
}
}
}

@ -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<int> _reclaimList = new List<int>();
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();
}
}
}

48
Quik/IQuikTexture.cs Normal file

@ -0,0 +1,48 @@
using System;
namespace Quik
{
/// <summary>
/// Interface for texture instances.
/// </summary>
public interface IQuikTexture : IEquatable<IQuikTexture>, IDisposable
{
/// <summary>
/// Width of the texture.
/// </summary>
int Width { get; }
/// <summary>
/// Height of the texture.
/// </summary>
int Height { get; }
/// <summary>
/// True if the texture can have mipmaps.
/// </summary>
bool Mipmaps { get; }
/// <summary>
/// Upload texture data.
/// </summary>
/// <param name="data">Pointer to data.</param>
/// <param name="format">Color format of the data.</param>
/// <param name="size">Size of the texture data.</param>
/// <param name="level">Mip level.</param>
void Image(IntPtr data, QuikImageFormat format, QuikVec2 size, int level);
/// <summary>
/// Upload texture data.
/// </summary>
/// <param name="data">Pointer to data.</param>
/// <param name="format">Color format for the data.</param>
/// <param name="location">Location of the data in the texture.</param>
/// <param name="level">Mip level.</param>
void SubImage(IntPtr data, QuikImageFormat format, QuikRectangle location, int level);
/// <summary>
/// Generate the mip maps for the texture.
/// </summary>
void GenerateMipMaps();
}
}

@ -0,0 +1,31 @@
namespace Quik
{
/// <summary>
/// Interface for QUIK texture managers.
/// </summary>
public interface IQuikTextureManager
{
/// <summary>
/// The context that owns the texture manager.
/// </summary>
QuikContext Context { get; set; }
/// <summary>
/// Create a texture.
/// </summary>
/// <param name="size">Size of the texture.</param>
/// <param name="mipmaps">True in order to allow mipmaps.</param>
/// <param name="format">The color format of the internal texture.</param>
/// <returns>Handle to a texture object.</returns>
/// <remarks>
/// 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.
/// </remarks>
IQuikTexture CreateTexture(QuikVec2 size, bool mipmaps, QuikImageFormat format);
/// <summary>
/// A function called on context clear. (useful for discarding old textures)
/// </summary>
void Clear();
}
}

@ -12,6 +12,16 @@ namespace Quik
/// </summary> /// </summary>
public QuikDraw Draw { get; } = new QuikDraw(); public QuikDraw Draw { get; } = new QuikDraw();
/// <summary>
/// The object responsible for managing textures.
/// </summary>
public IQuikTextureManager TextureManager { get; }
/// <summary>
/// The object responsible for managing fonts.
/// </summary>
public IQuikFontManager FontManager { get; }
public QuikStrokeStyle DefaultStroke { get; set; } = new QuikStrokeStyle(new QuikColor(0xaaaaaaff), 4); public QuikStrokeStyle DefaultStroke { get; set; } = new QuikStrokeStyle(new QuikColor(0xaaaaaaff), 4);
public QuikFillStyle DefaultFill { get; set; } = new QuikFillStyle() public QuikFillStyle DefaultFill { get; set; } = new QuikFillStyle()
@ -20,5 +30,22 @@ namespace Quik
}; };
public QuikFont DefaultFont { get; set; } public QuikFont DefaultFont { get; set; }
public QuikContext(IQuikTextureManager textureManager, IQuikFontManager fontManager)
{
TextureManager = textureManager;
FontManager = fontManager;
TextureManager.Context = FontManager.Context = this;
}
/// <summary>
/// Clear the context.
/// </summary>
public void Clear()
{
Draw.Clear();
TextureManager.Clear();
FontManager.Clear();
}
} }
} }

@ -14,6 +14,8 @@ namespace Quik
/// </summary> /// </summary>
public Queue<QuikCommand> Commands { get; } = new Queue<QuikCommand>(); public Queue<QuikCommand> Commands { get; } = new Queue<QuikCommand>();
public void Clear() => Commands.Clear();
public void Mask(QuikRectangle bounds) => Commands.Enqueue(new QuikCommandMask(bounds)); public void Mask(QuikRectangle bounds) => Commands.Enqueue(new QuikCommandMask(bounds));
public void Line(QuikLine line) => Commands.Enqueue(new QuikCommandLine(line)); public void Line(QuikLine line) => Commands.Enqueue(new QuikCommandLine(line));
public void Line(params QuikLine[] lines) => Commands.Enqueue(new QuikCommandLines(lines)); public void Line(params QuikLine[] lines) => Commands.Enqueue(new QuikCommandLines(lines));

12
Quik/QuikImageFormat.cs Normal file

@ -0,0 +1,12 @@
namespace Quik
{
public enum QuikImageFormat
{
RedU8,
RgbU8,
RgbaU8,
RedF,
RgbF,
RgbaF
}
}

@ -0,0 +1,22 @@
namespace Quik.Typography
{
public interface IQuikFontManager
{
/// <summary>
/// The context owning the font manager.
/// </summary>
QuikContext Context { get; set; }
/// <summary>
/// Function called on clear.
/// </summary>
void Clear();
/// <summary>
/// Get a font object for the given font.
/// </summary>
/// <param name="fontStyle">The font style to fetch.</param>
/// <returns>The font.</returns>
QuikFont GetFont(QuikFontStyle fontStyle);
}
}

@ -9,6 +9,6 @@ namespace Quik.Typography
public QuikFontStyle FontStyle => Style; public QuikFontStyle FontStyle => Style;
public abstract bool HasCharacter(int character); 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);
} }
} }

@ -1407,7 +1407,7 @@ namespace Quik.VertexGenerator
private void RenderCharacter(QuikCommandPutChar chr) 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; QuikVertex a, b, c, d;
a = b = c = d = new QuikVertex() {Color = new QuikColor(0xffffffff)}; a = b = c = d = new QuikVertex() {Color = new QuikColor(0xffffffff)};
@ -1439,19 +1439,19 @@ namespace Quik.VertexGenerator
{ {
short startElement = (short)_elementBufferPointer; short startElement = (short)_elementBufferPointer;
QuikFont font = Context.DefaultFont; QuikFont font = Context.DefaultFont;
QuikVertex vertex = new QuikVertex() {Color = new QuikColor(0xff7777ff)}; QuikVertex vertex = new QuikVertex() {Color = new QuikColor(0x000000ff)};
QuikVec2 pointer = text.Position; QuikVec2 pointer = text.Position;
int texture = -1; IQuikTexture texture = null;
for (int i = 0; i < text.Text.Length; i++) for (int i = 0; i < text.Text.Length; i++)
{ {
int chr = text.Text[i]; int chr = text.Text[i];
QuikGlyph metrics; QuikGlyph metrics;
int ntex; IQuikTexture ntex;
font.GetCharacter(chr, out ntex, out metrics); font.GetCharacter(chr, out ntex, out metrics);
if (ntex != texture && texture != -1) if (ntex != texture && texture != null)
{ {
QuikDrawCall call = CallTemplate; QuikDrawCall call = CallTemplate;
call.Texture = texture; call.Texture = texture;
@ -1510,6 +1510,6 @@ namespace Quik.VertexGenerator
public short Count; public short Count;
public QuikRectangle Bounds; public QuikRectangle Bounds;
public bool ClearStencil; public bool ClearStencil;
public int Texture; public IQuikTexture Texture;
} }
} }

@ -7,6 +7,8 @@ using OpenTK.Mathematics;
using OpenTK.Windowing.Common; using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop; using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.GraphicsLibraryFramework;
using Quik.OpenTK;
using Quik.Typography;
namespace QuikTestApplication namespace QuikTestApplication
{ {
@ -52,7 +54,7 @@ void main()
window.Context.MakeCurrent(); window.Context.MakeCurrent();
GL.LoadBindings(new GLFWBindingsContext()); GL.LoadBindings(new GLFWBindingsContext());
QuikContext context = new QuikContext(); QuikContext context = new QuikContext(new OpenGLTextureManager(), new TextFontManager());
QuikVertexGenerator gen = new QuikVertexGenerator(context); QuikVertexGenerator gen = new QuikVertexGenerator(context);
GL.Enable(EnableCap.Multisample); GL.Enable(EnableCap.Multisample);
@ -143,20 +145,7 @@ void main()
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Nearest); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Nearest);
TestFont font = new TestFont(); context.DefaultFont = context.FontManager.GetFont(null);
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;
window.Context.SwapInterval = 0; window.Context.SwapInterval = 0;
@ -168,6 +157,8 @@ void main()
GL.ClearColor(1,1,1,1); GL.ClearColor(1,1,1,1);
GL.Clear(ClearBufferMask.ColorBufferBit); GL.Clear(ClearBufferMask.ColorBufferBit);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
Matrix4 matrix = Matrix4.CreateOrthographicOffCenter( Matrix4 matrix = Matrix4.CreateOrthographicOffCenter(
0, 0,
@ -239,7 +230,9 @@ void main()
foreach (QuikDrawCall call in gen.DrawCalls) 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); GL.DrawElements(BeginMode.Triangles, call.Count, DrawElementsType.UnsignedShort, call.Offset);
} }
@ -254,5 +247,26 @@ void main()
window.Context.SwapBuffers(); 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;
}
}
} }
} }

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Xml; using System.Xml;
using Quik; using Quik;
using Quik.Typography; using Quik.Typography;
@ -15,10 +17,22 @@ namespace QuikTestApplication
private static HashSet<int> _characters = new HashSet<int>(); private static HashSet<int> _characters = new HashSet<int>();
private static Dictionary<int, QuikGlyph> _glyphs = new Dictionary<int, QuikGlyph>(); private static Dictionary<int, QuikGlyph> _glyphs = new Dictionary<int, QuikGlyph>();
public static byte[] Texture { get; private set; } = Array.Empty<byte>(); public static byte[] TextureData { get; private set; } = Array.Empty<byte>();
public static QuikVec2 TextureSize { get; private set; } 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() static TestFont()
{ {
@ -75,8 +89,18 @@ namespace QuikTestApplication
using (Stream str = typeof(TestFont).Assembly.GetManifestResourceStream("QuikTestApplication.font.dat")) using (Stream str = typeof(TestFont).Assembly.GetManifestResourceStream("QuikTestApplication.font.dat"))
{ {
Texture = new byte[(int)str.Length]; TextureData = new byte[(int)str.Length];
str.Read(Texture, 0, (int)str.Length); str.Read(TextureData, 0, (int)str.Length);
}
// Evil pointer stuff. Beware!
Span<uint> head = MemoryMarshal.Cast<byte, uint>(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); 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)) if (!_glyphs.TryGetValue(character, out glyph))
{ {
glyph = _glyphs['?']; glyph = _glyphs['?'];
} }
texture = TextureBase; texture = Texture;
} }
} }
} }