start implementing MSDF
parent
385783a846
commit
8af0d3399b
|
@ -0,0 +1,43 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MoonWorks.Graphics.Font
|
||||
{
|
||||
/* UTF-8 Decoder */
|
||||
|
||||
/* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
* See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
||||
*/
|
||||
|
||||
public static class Decoder
|
||||
{
|
||||
static byte[] utf8d = new byte[] {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
|
||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
|
||||
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
|
||||
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
|
||||
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
|
||||
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
|
||||
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
|
||||
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
|
||||
};
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe uint Decode(uint* state, uint* codep, uint dbyte) {
|
||||
uint type = utf8d[dbyte];
|
||||
|
||||
*codep = (uint) ((*state != 0) ?
|
||||
(dbyte & 0x3fu) | (*codep << 6) :
|
||||
(0xff >> (int) type) & (dbyte));
|
||||
|
||||
*state = utf8d[256 + *state*16 + type];
|
||||
return *state;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace MoonWorks.Graphics.Font.MSDF;
|
||||
|
||||
public class Font : IDisposable
|
||||
{
|
||||
public Texture Texture { get; }
|
||||
internal AtlasData AtlasData;
|
||||
|
||||
static JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
IncludeFields = true
|
||||
};
|
||||
static AtlasDataContext Context = new AtlasDataContext(SerializerOptions);
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
public static Font Load(
|
||||
GraphicsDevice graphicsDevice,
|
||||
CommandBuffer commandBuffer,
|
||||
string jsonPath
|
||||
) {
|
||||
var atlasData = (AtlasData) JsonSerializer.Deserialize(File.ReadAllText(jsonPath), typeof(AtlasData), Context);
|
||||
var imagePath = Path.ChangeExtension(jsonPath, ".png");
|
||||
var texture = Texture.FromImageFile(graphicsDevice, commandBuffer, imagePath);
|
||||
|
||||
return new Font(texture, atlasData);
|
||||
}
|
||||
|
||||
private Font(Texture texture, AtlasData atlasData)
|
||||
{
|
||||
Texture = texture;
|
||||
AtlasData = atlasData;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Texture.Dispose();
|
||||
}
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
using System.Text.Json.Serialization;
|
||||
using MoonWorks.Math.Float;
|
||||
|
||||
namespace MoonWorks.Graphics.Font.MSDF;
|
||||
|
||||
[JsonSerializable(typeof(AtlasData))]
|
||||
internal partial class AtlasDataContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
// Reads from an atlas generated by msdf-atlas-gen
|
||||
public struct AtlasData
|
||||
{
|
||||
public Atlas Atlas;
|
||||
public Metrics Metrics;
|
||||
public Glyph[] Glyphs;
|
||||
}
|
||||
|
||||
public struct Atlas
|
||||
{
|
||||
public FieldType Type;
|
||||
public int DistanceRange;
|
||||
public int Size;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public Origin YOrigin;
|
||||
}
|
||||
|
||||
public struct Metrics
|
||||
{
|
||||
public int EmSize;
|
||||
public float LineHeight;
|
||||
public float Ascender;
|
||||
public float Descender;
|
||||
public float UnderlineY;
|
||||
public float UnderlineThickness;
|
||||
}
|
||||
|
||||
public struct Glyph
|
||||
{
|
||||
public uint Unicode;
|
||||
public float Advance;
|
||||
public Bounds PlaneBounds;
|
||||
public Bounds AtlasBounds;
|
||||
}
|
||||
|
||||
public struct Bounds
|
||||
{
|
||||
public float Left;
|
||||
public float Bottom;
|
||||
public float Right;
|
||||
public float Top;
|
||||
}
|
||||
|
||||
public enum FieldType
|
||||
{
|
||||
Hardmask,
|
||||
Softmask,
|
||||
SDF,
|
||||
PSDF,
|
||||
MSDF,
|
||||
MTSDF
|
||||
}
|
||||
|
||||
public enum Origin
|
||||
{
|
||||
Top,
|
||||
Bottom
|
||||
}
|
||||
|
||||
public struct FontVertex : IVertexType
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Vector2 TexCoord;
|
||||
public Color Color;
|
||||
|
||||
private static readonly VertexElementFormat[] vertexElementFormats = new VertexElementFormat[]
|
||||
{
|
||||
VertexElementFormat.Vector3,
|
||||
VertexElementFormat.Vector2,
|
||||
VertexElementFormat.Color
|
||||
};
|
||||
|
||||
public static VertexElementFormat[] Formats => vertexElementFormats;
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonWorks.Graphics.Font.MSDF;
|
||||
|
||||
public unsafe class TextBatch : IDisposable
|
||||
{
|
||||
public const int MAX_CHARS = 4096;
|
||||
public const int MAX_VERTICES = MAX_CHARS * 4;
|
||||
public const int MAX_INDICES = MAX_CHARS * 6;
|
||||
|
||||
public GraphicsDevice GraphicsDevice;
|
||||
public Font Font;
|
||||
|
||||
public Buffer VertexBuffer;
|
||||
public Buffer IndexBuffer;
|
||||
|
||||
public byte* VertexData;
|
||||
public byte* IndexData;
|
||||
|
||||
private int CurrentVertexDataLengthInBytes;
|
||||
private int CurrentIndexDataLengthInBytes;
|
||||
|
||||
private byte* StringBytes;
|
||||
private int StringBytesCount = 16;
|
||||
|
||||
private bool IsDisposed;
|
||||
|
||||
public TextBatch(GraphicsDevice graphicsDevice)
|
||||
{
|
||||
GraphicsDevice = graphicsDevice;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
VertexBuffer = Buffer.Create<FontVertex>(GraphicsDevice, BufferUsageFlags.Vertex, MAX_VERTICES);
|
||||
IndexBuffer = Buffer.Create<uint>(GraphicsDevice, BufferUsageFlags.Index, MAX_INDICES);
|
||||
VertexData = (byte*) NativeMemory.Alloc((nuint) (MAX_VERTICES * Unsafe.SizeOf<FontVertex>()));
|
||||
IndexData = (byte*) NativeMemory.Alloc((nuint) (MAX_INDICES * Unsafe.SizeOf<uint>()));
|
||||
StringBytes = (byte*) NativeMemory.Alloc(128);
|
||||
}
|
||||
|
||||
public void AddText(
|
||||
string text,
|
||||
float x,
|
||||
float y,
|
||||
float depth,
|
||||
Color color,
|
||||
HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
|
||||
) {
|
||||
uint decodeState;
|
||||
uint codepoint;
|
||||
|
||||
var byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
|
||||
|
||||
if (StringBytesCount < byteCount)
|
||||
{
|
||||
StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount);
|
||||
StringBytesCount = byteCount;
|
||||
}
|
||||
|
||||
fixed (char* chars = text)
|
||||
{
|
||||
var bytesWritten = System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount);
|
||||
|
||||
y += GetVerticalAlignOffset(verticalAlignment);
|
||||
|
||||
if (horizontalAlignment == HorizontalAlignment.Right)
|
||||
{
|
||||
// TODO: check text bounds to adjust alignment
|
||||
}
|
||||
|
||||
for (var i = 0; i < bytesWritten; i += 1)
|
||||
{
|
||||
if (Decoder.Decode(&decodeState, &codepoint, StringBytes[i]) > 0)
|
||||
{
|
||||
if (decodeState == 1)
|
||||
{
|
||||
// Something went wrong!
|
||||
return;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: we need to convert AtlasData so that codepoints are looked up by key
|
||||
if (IsWhitespace(codepoint))
|
||||
{
|
||||
x += GetHorizontalAdvance(codepoint);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: draw the rest of the owl
|
||||
}
|
||||
}
|
||||
|
||||
// Call this after you have made all the Draw calls you want.
|
||||
public void UploadBufferData(CommandBuffer commandBuffer)
|
||||
{
|
||||
if (CurrentVertexDataLengthInBytes > 0 && CurrentIndexDataLengthInBytes > 0)
|
||||
{
|
||||
commandBuffer.SetBufferData(VertexBuffer, (nint) VertexData, 0, (uint) CurrentVertexDataLengthInBytes);
|
||||
commandBuffer.SetBufferData(IndexBuffer, (nint) IndexData, 0, (uint) CurrentIndexDataLengthInBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private float GetHorizontalAdvance(uint codepoint)
|
||||
{
|
||||
Font.AtlasData.Glyphs
|
||||
}
|
||||
|
||||
private float GetVerticalAlignOffset(VerticalAlignment verticalAlignment)
|
||||
{
|
||||
switch (verticalAlignment)
|
||||
{
|
||||
case VerticalAlignment.Baseline:
|
||||
return 0;
|
||||
|
||||
case VerticalAlignment.Top:
|
||||
return Font.AtlasData.Metrics.Ascender;
|
||||
|
||||
case VerticalAlignment.Middle:
|
||||
return (Font.AtlasData.Metrics.Ascender + Font.AtlasData.Metrics.Descender) / 2f;
|
||||
|
||||
case VerticalAlignment.Bottom:
|
||||
return Font.AtlasData.Metrics.Descender;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsWhitespace(uint codepoint)
|
||||
{
|
||||
switch (codepoint)
|
||||
{
|
||||
case 0x0020:
|
||||
case 0x00A0:
|
||||
case 0x1680:
|
||||
case 0x202F:
|
||||
case 0x205F:
|
||||
case 0x3000:
|
||||
return true;
|
||||
|
||||
default:
|
||||
if (codepoint > 0x2000 && codepoint <= 0x200A)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
VertexBuffer.Dispose();
|
||||
IndexBuffer.Dispose();
|
||||
}
|
||||
|
||||
NativeMemory.Free(VertexData);
|
||||
NativeMemory.Free(IndexData);
|
||||
NativeMemory.Free(StringBytes);
|
||||
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
~TextBatch()
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue