Font Rendering (#18)

Adds a font rendering system based on Wellspring.

Reviewed-on: MoonsideGames/MoonWorks#18
main
cosmonaut 2022-04-13 03:06:14 +00:00
parent 72c9dd4bda
commit 61a6d0bdc0
10 changed files with 213 additions and 0 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -14,6 +14,7 @@
<ProjectReference Include=".\lib\SDL2-CS\SDL2-CS.Core.csproj" />
<ProjectReference Include=".\lib\RefreshCS\RefreshCS.csproj" />
<ProjectReference Include=".\lib\FAudio\csharp\FAudio-CS.Core.csproj" />
<ProjectReference Include=".\lib\WellspringCS\WellspringCS.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -11,4 +11,8 @@
<dllmap dll="FAudio" os="windows" target="FAudio.dll"/>
<dllmap dll="FAudio" os="osx" target="libFAudio.0.dylib"/>
<dllmap dll="FAudio" os="linux,freebsd,netbsd" target="libFAudio.so.0"/>
<dllmap dll="Wellspring" os="windows" target="Wellspring.dll"/>
<dllmap dll="Wellspring" os="osx" target="libWellspring.0.dylib"/>
<dllmap dll="Wellspring" os="linux,freebsd,netbsd" target="libWellspring.so.0"/>
</configuration>

View File

@ -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

1
lib/WellspringCS Submodule

@ -0,0 +1 @@
Subproject commit 4b21707900c6d75184f0d5373a0676fe31da956b

View File

@ -21,6 +21,8 @@ namespace MoonWorks.Graphics
Handle = handle;
}
// FIXME: we can probably use the NativeMemory functions to not have to generate arrays here
/// <summary>
/// Begins a render pass.
/// All render state, resource binding, and draw commands must be made within a render pass.

View File

@ -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<Wellspring.FontRange>();
void* fontRangeMemory = NativeMemory.Alloc((nuint) fontRanges.Length, (nuint) Marshal.SizeOf<Wellspring.FontRange>());
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);
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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