From 61a6d0bdc0d3a2b8e0a57f2648a6ddd9e370c848 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 13 Apr 2022 03:06:14 +0000 Subject: [PATCH] Font Rendering (#18) Adds a font rendering system based on Wellspring. Reviewed-on: https://gitea.moonside.games/MoonsideGames/MoonWorks/pulls/18 --- .gitmodules | 3 ++ MoonWorks.csproj | 1 + MoonWorks.dll.config | 4 ++ MoonWorks.sln | 11 ++++ lib/WellspringCS | 1 + src/Graphics/CommandBuffer.cs | 2 + src/Graphics/Font/Packer.cs | 76 ++++++++++++++++++++++++++++ src/Graphics/Font/Structs.cs | 23 +++++++++ src/Graphics/Font/TextBatch.cs | 91 ++++++++++++++++++++++++++++++++++ src/MoonWorksDllMap.cs | 1 + 10 files changed, 213 insertions(+) create mode 160000 lib/WellspringCS create mode 100644 src/Graphics/Font/Packer.cs create mode 100644 src/Graphics/Font/Structs.cs create mode 100644 src/Graphics/Font/TextBatch.cs diff --git a/.gitmodules b/.gitmodules index b841a37..481755f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/RefreshCS"] path = lib/RefreshCS url = https://gitea.moonside.games/MoonsideGames/RefreshCS.git +[submodule "lib/WellspringCS"] + path = lib/WellspringCS + url = https://gitea.moonside.games/MoonsideGames/WellspringCS.git diff --git a/MoonWorks.csproj b/MoonWorks.csproj index 6b8a1b1..88105d4 100644 --- a/MoonWorks.csproj +++ b/MoonWorks.csproj @@ -14,6 +14,7 @@ + diff --git a/MoonWorks.dll.config b/MoonWorks.dll.config index 52020e4..d384162 100644 --- a/MoonWorks.dll.config +++ b/MoonWorks.dll.config @@ -11,4 +11,8 @@ + + + + diff --git a/MoonWorks.sln b/MoonWorks.sln index 1c66aa8..e536276 100644 --- a/MoonWorks.sln +++ b/MoonWorks.sln @@ -14,6 +14,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FAudio-CS.Core", "lib\FAudi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefreshCS", "lib\RefreshCS\RefreshCS.csproj", "{AD7C94E4-0AFA-44CA-889C-110142369893}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{69D3788D-6C57-44F7-A912-B201AE6D7C04}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WellspringCS", "lib\WellspringCS\WellspringCS.csproj", "{0DD7B866-773C-4A86-8580-F436DAA28989}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -36,6 +40,10 @@ Global {AD7C94E4-0AFA-44CA-889C-110142369893}.Debug|x64.Build.0 = Debug|x64 {AD7C94E4-0AFA-44CA-889C-110142369893}.Release|x64.ActiveCfg = Release|x64 {AD7C94E4-0AFA-44CA-889C-110142369893}.Release|x64.Build.0 = Release|x64 + {0DD7B866-773C-4A86-8580-F436DAA28989}.Debug|x64.ActiveCfg = Debug|x64 + {0DD7B866-773C-4A86-8580-F436DAA28989}.Debug|x64.Build.0 = Debug|x64 + {0DD7B866-773C-4A86-8580-F436DAA28989}.Release|x64.ActiveCfg = Release|x64 + {0DD7B866-773C-4A86-8580-F436DAA28989}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -43,4 +51,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3D68FAA-3165-43C7-95B3-D845F0DAA918} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0DD7B866-773C-4A86-8580-F436DAA28989} = {69D3788D-6C57-44F7-A912-B201AE6D7C04} + EndGlobalSection EndGlobal diff --git a/lib/WellspringCS b/lib/WellspringCS new file mode 160000 index 0000000..4b21707 --- /dev/null +++ b/lib/WellspringCS @@ -0,0 +1 @@ +Subproject commit 4b21707900c6d75184f0d5373a0676fe31da956b diff --git a/src/Graphics/CommandBuffer.cs b/src/Graphics/CommandBuffer.cs index a296145..5afbb57 100644 --- a/src/Graphics/CommandBuffer.cs +++ b/src/Graphics/CommandBuffer.cs @@ -21,6 +21,8 @@ namespace MoonWorks.Graphics Handle = handle; } + // FIXME: we can probably use the NativeMemory functions to not have to generate arrays here + /// /// Begins a render pass. /// All render state, resource binding, and draw commands must be made within a render pass. diff --git a/src/Graphics/Font/Packer.cs b/src/Graphics/Font/Packer.cs new file mode 100644 index 0000000..2005201 --- /dev/null +++ b/src/Graphics/Font/Packer.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using WellspringCS; + +namespace MoonWorks.Graphics.Font +{ + public class Packer : IDisposable + { + public IntPtr Handle { get; } + public Texture Texture { get; } + + private bool IsDisposed; + + public unsafe Packer(GraphicsDevice graphicsDevice, string path, uint textureWidth, uint textureHeight, uint padding = 1) + { + var bytes = File.ReadAllBytes(path); + fixed (byte* pByte = &bytes[0]) + { + Handle = Wellspring.Wellspring_CreatePacker((IntPtr) pByte, (uint) bytes.Length, textureWidth, textureHeight, 0, padding); + } + + Texture = Texture.CreateTexture2D(graphicsDevice, textureWidth, textureHeight, TextureFormat.R8, TextureUsageFlags.Sampler); + } + + public unsafe bool PackFontRanges(params FontRange[] fontRanges) + { + fixed (FontRange *pFontRanges = &fontRanges[0]) + { + var nativeSize = fontRanges.Length * Marshal.SizeOf(); + void* fontRangeMemory = NativeMemory.Alloc((nuint) fontRanges.Length, (nuint) Marshal.SizeOf()); + System.Buffer.MemoryCopy(pFontRanges, fontRangeMemory, nativeSize, nativeSize); + + var result = Wellspring.Wellspring_PackFontRanges(Handle, (IntPtr) fontRangeMemory, (uint) fontRanges.Length); + + NativeMemory.Free(fontRangeMemory); + + return result > 0; + } + } + + public unsafe void SetTextureData(CommandBuffer commandBuffer) + { + var pixelDataPointer = Wellspring.Wellspring_GetPixelDataPointer(Handle); + commandBuffer.SetTextureData(Texture, pixelDataPointer, Texture.Width * Texture.Height); + } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + Texture.Dispose(); + } + + Wellspring.Wellspring_DestroyPacker(Handle); + + IsDisposed = true; + } + } + + ~Packer() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Graphics/Font/Structs.cs b/src/Graphics/Font/Structs.cs new file mode 100644 index 0000000..37082f1 --- /dev/null +++ b/src/Graphics/Font/Structs.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; +using MoonWorks.Math; + +namespace MoonWorks.Graphics.Font +{ + [StructLayout(LayoutKind.Sequential)] + public struct FontRange + { + public uint FontSize; + public uint FirstCodepoint; + public uint NumChars; + public byte OversampleH; + public byte OversampleV; + } + + [StructLayout(LayoutKind.Sequential)] + public struct Vertex + { + public Vector3 Position; + public Vector2 TexCoord; + public Color Color; + } +} diff --git a/src/Graphics/Font/TextBatch.cs b/src/Graphics/Font/TextBatch.cs new file mode 100644 index 0000000..ce246cd --- /dev/null +++ b/src/Graphics/Font/TextBatch.cs @@ -0,0 +1,91 @@ +using System; +using WellspringCS; + +namespace MoonWorks.Graphics.Font +{ + public class TextBatch + { + private GraphicsDevice GraphicsDevice { get; } + public IntPtr Handle { get; } + + public Buffer VertexBuffer { get; protected set; } = null; + public Buffer IndexBuffer { get; protected set; } = null; + public Texture Texture { get; protected set; } + public uint PrimitiveCount { get; protected set; } + + public TextBatch(GraphicsDevice graphicsDevice) + { + GraphicsDevice = graphicsDevice; + Handle = Wellspring.Wellspring_CreateTextBatch(); + } + + public void Start(Packer packer) + { + Wellspring.Wellspring_StartTextBatch(Handle, packer.Handle); + Texture = packer.Texture; + PrimitiveCount = 0; + } + + public unsafe void Draw(float x, float y, float depth, Color color, string text) + { + fixed (char* chars = text) + { + var byteCount = System.Text.Encoding.UTF8.GetByteCount(text); + var bytes = stackalloc byte[byteCount]; + System.Text.Encoding.UTF8.GetBytes(chars, text.Length, bytes, byteCount); + + var result = Wellspring.Wellspring_Draw( + Handle, + x, + y, + depth, + new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A }, + (IntPtr) bytes, + (uint) byteCount + ); + + if (result == 0) + { + throw new System.ArgumentException("Could not decode string!"); + } + + PrimitiveCount += (uint) (text.Length * 2); + } + } + + // Call this after you have made all the Draw calls you want. + public unsafe void UploadBufferData(CommandBuffer commandBuffer) + { + Wellspring.Wellspring_GetBufferData( + Handle, + out IntPtr vertexDataPointer, + out uint vertexDataLengthInBytes, + out IntPtr indexDataPointer, + out uint indexDataLengthInBytes + ); + + if (VertexBuffer == null) + { + VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes); + } + else if (VertexBuffer.Size < vertexDataLengthInBytes) + { + VertexBuffer.Dispose(); + VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes); + } + + if (IndexBuffer == null) + { + IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes); + } + else if (IndexBuffer.Size < indexDataLengthInBytes) + { + IndexBuffer.Dispose(); + IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Index, indexDataLengthInBytes); + } + + commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes); + commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes); + } + } +} diff --git a/src/MoonWorksDllMap.cs b/src/MoonWorksDllMap.cs index 663cc58..5164838 100644 --- a/src/MoonWorksDllMap.cs +++ b/src/MoonWorksDllMap.cs @@ -195,6 +195,7 @@ namespace MoonWorks NativeLibrary.SetDllImportResolver(typeof(SDL2.SDL).Assembly, MapAndLoad); NativeLibrary.SetDllImportResolver(typeof(RefreshCS.Refresh).Assembly, MapAndLoad); NativeLibrary.SetDllImportResolver(typeof(FAudio).Assembly, MapAndLoad); + NativeLibrary.SetDllImportResolver(typeof(WellspringCS.Wellspring).Assembly, MapAndLoad); } #endregion