From ad5c88aa522c2d128f97780b63839cee5cee6f84 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 5 Dec 2023 19:03:52 -0800 Subject: [PATCH 01/17] start implementing MSDF --- src/Graphics/Font/MSDF/Decoder.cs | 43 +++++++ src/Graphics/Font/MSDF/Font.cs | 58 +++++++++ src/Graphics/Font/MSDF/Structs.cs | 85 +++++++++++++ src/Graphics/Font/MSDF/TextBatch.cs | 188 ++++++++++++++++++++++++++++ 4 files changed, 374 insertions(+) create mode 100644 src/Graphics/Font/MSDF/Decoder.cs create mode 100644 src/Graphics/Font/MSDF/Font.cs create mode 100644 src/Graphics/Font/MSDF/Structs.cs create mode 100644 src/Graphics/Font/MSDF/TextBatch.cs diff --git a/src/Graphics/Font/MSDF/Decoder.cs b/src/Graphics/Font/MSDF/Decoder.cs new file mode 100644 index 0000000..effae24 --- /dev/null +++ b/src/Graphics/Font/MSDF/Decoder.cs @@ -0,0 +1,43 @@ +using System.Runtime.CompilerServices; + +namespace MoonWorks.Graphics.Font +{ + /* UTF-8 Decoder */ + + /* Copyright (c) 2008-2009 Bjoern Hoehrmann + * 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; + } + + } +} diff --git a/src/Graphics/Font/MSDF/Font.cs b/src/Graphics/Font/MSDF/Font.cs new file mode 100644 index 0000000..c0ea1c8 --- /dev/null +++ b/src/Graphics/Font/MSDF/Font.cs @@ -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); + } +} diff --git a/src/Graphics/Font/MSDF/Structs.cs b/src/Graphics/Font/MSDF/Structs.cs new file mode 100644 index 0000000..cedfc41 --- /dev/null +++ b/src/Graphics/Font/MSDF/Structs.cs @@ -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; +} diff --git a/src/Graphics/Font/MSDF/TextBatch.cs b/src/Graphics/Font/MSDF/TextBatch.cs new file mode 100644 index 0000000..7e4f285 --- /dev/null +++ b/src/Graphics/Font/MSDF/TextBatch.cs @@ -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(GraphicsDevice, BufferUsageFlags.Vertex, MAX_VERTICES); + IndexBuffer = Buffer.Create(GraphicsDevice, BufferUsageFlags.Index, MAX_INDICES); + VertexData = (byte*) NativeMemory.Alloc((nuint) (MAX_VERTICES * Unsafe.SizeOf())); + IndexData = (byte*) NativeMemory.Alloc((nuint) (MAX_INDICES * Unsafe.SizeOf())); + 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); + } +} -- 2.25.1 From 2143418347f8f3297cee18678bd4c8dcbbcf40b2 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 6 Dec 2023 17:33:17 -0800 Subject: [PATCH 02/17] restructure for wellspring API update --- src/Graphics/Font/Font.cs | 108 +++++++++++++--- src/Graphics/Font/MSDF/Decoder.cs | 43 ------- src/Graphics/Font/MSDF/Font.cs | 58 --------- src/Graphics/Font/MSDF/Structs.cs | 85 ------------- src/Graphics/Font/MSDF/TextBatch.cs | 188 ---------------------------- src/Graphics/Font/Packer.cs | 103 --------------- src/Graphics/Font/Structs.cs | 9 -- src/Graphics/Font/TextBatch.cs | 93 +++++++++----- 8 files changed, 152 insertions(+), 535 deletions(-) delete mode 100644 src/Graphics/Font/MSDF/Decoder.cs delete mode 100644 src/Graphics/Font/MSDF/Font.cs delete mode 100644 src/Graphics/Font/MSDF/Structs.cs delete mode 100644 src/Graphics/Font/MSDF/TextBatch.cs delete mode 100644 src/Graphics/Font/Packer.cs diff --git a/src/Graphics/Font/Font.cs b/src/Graphics/Font/Font.cs index cf4982e..024b3df 100644 --- a/src/Graphics/Font/Font.cs +++ b/src/Graphics/Font/Font.cs @@ -5,29 +5,107 @@ using WellspringCS; namespace MoonWorks.Graphics.Font { - public class Font : IDisposable - { - public IntPtr Handle { get; } + public unsafe class Font : IDisposable + { + public Texture Texture { get; } + + internal IntPtr Handle { get; } + + private byte* StringBytes; + private int StringBytesLength; private bool IsDisposed; - public unsafe Font(string path) - { - var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read); - var fileByteBuffer = NativeMemory.Alloc((nuint) fileStream.Length); - var fileByteSpan = new Span(fileByteBuffer, (int) fileStream.Length); - fileStream.ReadExactly(fileByteSpan); - fileStream.Close(); + public unsafe static Font Load( + GraphicsDevice graphicsDevice, + CommandBuffer commandBuffer, + string fontPath + ) { + var fontFileStream = new FileStream(fontPath, FileMode.Open, FileAccess.Read); + var fontFileByteBuffer = NativeMemory.Alloc((nuint) fontFileStream.Length); + var fontFileByteSpan = new Span(fontFileByteBuffer, (int) fontFileStream.Length); + fontFileStream.ReadExactly(fontFileByteSpan); + fontFileStream.Close(); - Handle = Wellspring.Wellspring_CreateFont((IntPtr) fileByteBuffer, (uint) fileByteSpan.Length); + var atlasFileStream = new FileStream(Path.ChangeExtension(fontPath, ".json"), FileMode.Open, FileAccess.Read); + var atlasFileByteBuffer = NativeMemory.Alloc((nuint) atlasFileStream.Length); + var atlasFileByteSpan = new Span(atlasFileByteBuffer, (int) atlasFileStream.Length); + atlasFileStream.ReadExactly(atlasFileByteSpan); + atlasFileStream.Close(); - NativeMemory.Free(fileByteBuffer); - } + var handle = Wellspring.Wellspring_CreateFont( + (IntPtr) fontFileByteBuffer, + (uint) fontFileByteSpan.Length, + (IntPtr) atlasFileByteBuffer, + (uint) atlasFileByteSpan.Length + ); + + var texture = Texture.FromImageFile(graphicsDevice, commandBuffer, Path.ChangeExtension(fontPath, ".png")); + + NativeMemory.Free(fontFileByteBuffer); + NativeMemory.Free(atlasFileByteBuffer); + + return new Font(handle, texture); + } + + private Font(IntPtr handle, Texture texture) + { + Handle = handle; + Texture = texture; + + StringBytesLength = 32; + StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength); + } + + public unsafe bool TextBounds( + string text, + float x, + float y, + HorizontalAlignment horizontalAlignment, + VerticalAlignment verticalAlignment, + out Wellspring.Rectangle rectangle + ) { + var byteCount = System.Text.Encoding.UTF8.GetByteCount(text); + + if (StringBytesLength < byteCount) + { + StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount); + } + + fixed (char* chars = text) + { + System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount); + + var result = Wellspring.Wellspring_TextBounds( + Handle, + x, + y, + (Wellspring.HorizontalAlignment) horizontalAlignment, + (Wellspring.VerticalAlignment) verticalAlignment, + (IntPtr) StringBytes, + (uint) byteCount, + out rectangle + ); + + if (result == 0) + { + Logger.LogWarn("Could not decode string: " + text); + return false; + } + } + + return true; + } protected virtual void Dispose(bool disposing) { if (!IsDisposed) { + if (disposing) + { + Texture.Dispose(); + } + Wellspring.Wellspring_DestroyFont(Handle); IsDisposed = true; } @@ -35,8 +113,8 @@ namespace MoonWorks.Graphics.Font ~Font() { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: false); + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); } public void Dispose() diff --git a/src/Graphics/Font/MSDF/Decoder.cs b/src/Graphics/Font/MSDF/Decoder.cs deleted file mode 100644 index effae24..0000000 --- a/src/Graphics/Font/MSDF/Decoder.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace MoonWorks.Graphics.Font -{ - /* UTF-8 Decoder */ - - /* Copyright (c) 2008-2009 Bjoern Hoehrmann - * 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; - } - - } -} diff --git a/src/Graphics/Font/MSDF/Font.cs b/src/Graphics/Font/MSDF/Font.cs deleted file mode 100644 index c0ea1c8..0000000 --- a/src/Graphics/Font/MSDF/Font.cs +++ /dev/null @@ -1,58 +0,0 @@ -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); - } -} diff --git a/src/Graphics/Font/MSDF/Structs.cs b/src/Graphics/Font/MSDF/Structs.cs deleted file mode 100644 index cedfc41..0000000 --- a/src/Graphics/Font/MSDF/Structs.cs +++ /dev/null @@ -1,85 +0,0 @@ -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; -} diff --git a/src/Graphics/Font/MSDF/TextBatch.cs b/src/Graphics/Font/MSDF/TextBatch.cs deleted file mode 100644 index 7e4f285..0000000 --- a/src/Graphics/Font/MSDF/TextBatch.cs +++ /dev/null @@ -1,188 +0,0 @@ -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(GraphicsDevice, BufferUsageFlags.Vertex, MAX_VERTICES); - IndexBuffer = Buffer.Create(GraphicsDevice, BufferUsageFlags.Index, MAX_INDICES); - VertexData = (byte*) NativeMemory.Alloc((nuint) (MAX_VERTICES * Unsafe.SizeOf())); - IndexData = (byte*) NativeMemory.Alloc((nuint) (MAX_INDICES * Unsafe.SizeOf())); - 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); - } -} diff --git a/src/Graphics/Font/Packer.cs b/src/Graphics/Font/Packer.cs deleted file mode 100644 index 9fa053c..0000000 --- a/src/Graphics/Font/Packer.cs +++ /dev/null @@ -1,103 +0,0 @@ -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; } - - public Font Font { get; } - - private byte[] StringBytes; - - private bool IsDisposed; - - public unsafe Packer(GraphicsDevice graphicsDevice, Font font, float fontSize, uint textureWidth, uint textureHeight, uint padding = 1) - { - Font = font; - Handle = Wellspring.Wellspring_CreatePacker(Font.Handle, fontSize, textureWidth, textureHeight, 0, padding); - Texture = Texture.CreateTexture2D(graphicsDevice, textureWidth, textureHeight, TextureFormat.R8, TextureUsageFlags.Sampler); - StringBytes = new byte[128]; - } - - public unsafe bool PackFontRanges(params FontRange[] fontRanges) - { - fixed (FontRange *pFontRanges = &fontRanges[0]) - { - var nativeSize = fontRanges.Length * Marshal.SizeOf(); - var result = Wellspring.Wellspring_PackFontRanges(Handle, (IntPtr) pFontRanges, (uint) fontRanges.Length); - return result > 0; - } - } - - public unsafe void SetTextureData(CommandBuffer commandBuffer) - { - var pixelDataPointer = Wellspring.Wellspring_GetPixelDataPointer(Handle); - commandBuffer.SetTextureData(Texture, pixelDataPointer, Texture.Width * Texture.Height); - } - - public unsafe void TextBounds( - string text, - float x, - float y, - HorizontalAlignment horizontalAlignment, - VerticalAlignment verticalAlignment, - out Wellspring.Rectangle rectangle - ) { - var byteCount = System.Text.Encoding.UTF8.GetByteCount(text); - - if (StringBytes.Length < byteCount) - { - System.Array.Resize(ref StringBytes, byteCount); - } - - fixed (char* chars = text) - fixed (byte* bytes = StringBytes) - { - System.Text.Encoding.UTF8.GetBytes(chars, text.Length, bytes, byteCount); - Wellspring.Wellspring_TextBounds( - Handle, - x, - y, - (Wellspring.HorizontalAlignment) horizontalAlignment, - (Wellspring.VerticalAlignment) verticalAlignment, - (IntPtr) bytes, - (uint) byteCount, - out rectangle - ); - } - } - - 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 index 8231039..b5b1e5a 100644 --- a/src/Graphics/Font/Structs.cs +++ b/src/Graphics/Font/Structs.cs @@ -3,15 +3,6 @@ using MoonWorks.Math.Float; namespace MoonWorks.Graphics.Font { - [StructLayout(LayoutKind.Sequential)] - public struct FontRange - { - public uint FirstCodepoint; - public uint NumChars; - public byte OversampleH; - public byte OversampleV; - } - [StructLayout(LayoutKind.Sequential)] public struct Vertex { diff --git a/src/Graphics/Font/TextBatch.cs b/src/Graphics/Font/TextBatch.cs index 119fbde..c043909 100644 --- a/src/Graphics/Font/TextBatch.cs +++ b/src/Graphics/Font/TextBatch.cs @@ -1,35 +1,49 @@ using System; +using System.Runtime.InteropServices; using WellspringCS; namespace MoonWorks.Graphics.Font { - public class TextBatch + 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; + 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; } - private byte[] StringBytes; + private Font CurrentFont; + + private byte* StringBytes; + private int StringBytesLength; + + private bool IsDisposed; public TextBatch(GraphicsDevice graphicsDevice) { GraphicsDevice = graphicsDevice; Handle = Wellspring.Wellspring_CreateTextBatch(); - StringBytes = new byte[128]; + + StringBytesLength = 128; + StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength); + + VertexBuffer = Buffer.Create(GraphicsDevice, BufferUsageFlags.Vertex, MAX_VERTICES); + IndexBuffer = Buffer.Create(GraphicsDevice, BufferUsageFlags.Index, MAX_INDICES); } - public void Start(Packer packer) + public void Start(Font font) { - Wellspring.Wellspring_StartTextBatch(Handle, packer.Handle); - Texture = packer.Texture; + Wellspring.Wellspring_StartTextBatch(Handle, font.Handle); + CurrentFont = font; PrimitiveCount = 0; } - public unsafe void Draw( + public unsafe bool Add( string text, float x, float y, @@ -40,15 +54,14 @@ namespace MoonWorks.Graphics.Font ) { var byteCount = System.Text.Encoding.UTF8.GetByteCount(text); - if (StringBytes.Length < byteCount) + if (StringBytesLength < byteCount) { - System.Array.Resize(ref StringBytes, byteCount); + StringBytes = (byte*) NativeMemory.Realloc(StringBytes, (nuint) byteCount); } fixed (char* chars = text) - fixed (byte* bytes = StringBytes) { - System.Text.Encoding.UTF8.GetBytes(chars, text.Length, bytes, byteCount); + System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount); var result = Wellspring.Wellspring_Draw( Handle, @@ -58,15 +71,18 @@ namespace MoonWorks.Graphics.Font new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A }, (Wellspring.HorizontalAlignment) horizontalAlignment, (Wellspring.VerticalAlignment) verticalAlignment, - (IntPtr) bytes, + (IntPtr) StringBytes, (uint) byteCount ); if (result == 0) { - throw new System.ArgumentException("Could not decode string!"); + Logger.LogWarn("Could not decode string: " + text); + return false; } } + + return true; } // Call this after you have made all the Draw calls you want. @@ -81,26 +97,6 @@ namespace MoonWorks.Graphics.Font 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); - } - if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0) { commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes); @@ -109,5 +105,34 @@ namespace MoonWorks.Graphics.Font PrimitiveCount = vertexCount / 2; // FIXME: is this jank? } + + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + VertexBuffer.Dispose(); + IndexBuffer.Dispose(); + } + + 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); + } } } -- 2.25.1 From 26df8fa77c29f316de36f7d79fc2b38a1d88be39 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 6 Dec 2023 17:34:02 -0800 Subject: [PATCH 03/17] wellspringCS update --- lib/WellspringCS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WellspringCS b/lib/WellspringCS index f8872ba..0ffacd6 160000 --- a/lib/WellspringCS +++ b/lib/WellspringCS @@ -1 +1 @@ -Subproject commit f8872bae59e394b0f8a35224bb39ab8fd041af97 +Subproject commit 0ffacd6aec80d94cf45a871bd50c9a9321f4ec0d -- 2.25.1 From 420f5caee9adc01822c53c444324776968072ebd Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 7 Dec 2023 22:54:58 -0800 Subject: [PATCH 04/17] font scaling tweaks --- lib/WellspringCS | 2 +- src/Graphics/Font/Font.cs | 15 ++++++++------- src/Graphics/Font/TextBatch.cs | 4 +++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/WellspringCS b/lib/WellspringCS index 0ffacd6..82093d2 160000 --- a/lib/WellspringCS +++ b/lib/WellspringCS @@ -1 +1 @@ -Subproject commit 0ffacd6aec80d94cf45a871bd50c9a9321f4ec0d +Subproject commit 82093d2b0d4ee8510820213cbcd248418528a88c diff --git a/src/Graphics/Font/Font.cs b/src/Graphics/Font/Font.cs index 024b3df..eafe6d2 100644 --- a/src/Graphics/Font/Font.cs +++ b/src/Graphics/Font/Font.cs @@ -8,6 +8,7 @@ namespace MoonWorks.Graphics.Font public unsafe class Font : IDisposable { public Texture Texture { get; } + public float PixelsPerEm { get; } internal IntPtr Handle { get; } @@ -37,7 +38,8 @@ namespace MoonWorks.Graphics.Font (IntPtr) fontFileByteBuffer, (uint) fontFileByteSpan.Length, (IntPtr) atlasFileByteBuffer, - (uint) atlasFileByteSpan.Length + (uint) atlasFileByteSpan.Length, + out float pixelsPerEm ); var texture = Texture.FromImageFile(graphicsDevice, commandBuffer, Path.ChangeExtension(fontPath, ".png")); @@ -45,13 +47,14 @@ namespace MoonWorks.Graphics.Font NativeMemory.Free(fontFileByteBuffer); NativeMemory.Free(atlasFileByteBuffer); - return new Font(handle, texture); + return new Font(handle, texture, pixelsPerEm); } - private Font(IntPtr handle, Texture texture) + private Font(IntPtr handle, Texture texture, float pixelsPerEm) { Handle = handle; Texture = texture; + PixelsPerEm = pixelsPerEm; StringBytesLength = 32; StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength); @@ -59,8 +62,7 @@ namespace MoonWorks.Graphics.Font public unsafe bool TextBounds( string text, - float x, - float y, + int pixelSize, HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment, out Wellspring.Rectangle rectangle @@ -78,8 +80,7 @@ namespace MoonWorks.Graphics.Font var result = Wellspring.Wellspring_TextBounds( Handle, - x, - y, + pixelSize, (Wellspring.HorizontalAlignment) horizontalAlignment, (Wellspring.VerticalAlignment) verticalAlignment, (IntPtr) StringBytes, diff --git a/src/Graphics/Font/TextBatch.cs b/src/Graphics/Font/TextBatch.cs index c043909..d9abf6a 100644 --- a/src/Graphics/Font/TextBatch.cs +++ b/src/Graphics/Font/TextBatch.cs @@ -17,7 +17,7 @@ namespace MoonWorks.Graphics.Font public Buffer IndexBuffer { get; protected set; } = null; public uint PrimitiveCount { get; protected set; } - private Font CurrentFont; + public Font CurrentFont { get; private set; } private byte* StringBytes; private int StringBytesLength; @@ -47,6 +47,7 @@ namespace MoonWorks.Graphics.Font string text, float x, float y, + int pixelSize, float depth, Color color, HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left, @@ -67,6 +68,7 @@ namespace MoonWorks.Graphics.Font Handle, x, y, + pixelSize, depth, new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A }, (Wellspring.HorizontalAlignment) horizontalAlignment, -- 2.25.1 From 16b96d533a95c0afa617515261924c5bbd658827 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 7 Dec 2023 23:50:34 -0800 Subject: [PATCH 05/17] add DistanceRange to Font --- lib/WellspringCS | 2 +- src/Graphics/Font/Font.cs | 9 ++++++--- src/Graphics/Font/TextBatch.cs | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/WellspringCS b/lib/WellspringCS index 82093d2..9d62a80 160000 --- a/lib/WellspringCS +++ b/lib/WellspringCS @@ -1 +1 @@ -Subproject commit 82093d2b0d4ee8510820213cbcd248418528a88c +Subproject commit 9d62a80dc7d5f339d5384bfb4d2954087151409c diff --git a/src/Graphics/Font/Font.cs b/src/Graphics/Font/Font.cs index eafe6d2..a38f899 100644 --- a/src/Graphics/Font/Font.cs +++ b/src/Graphics/Font/Font.cs @@ -9,6 +9,7 @@ namespace MoonWorks.Graphics.Font { public Texture Texture { get; } public float PixelsPerEm { get; } + public float DistanceRange { get; } internal IntPtr Handle { get; } @@ -39,7 +40,8 @@ namespace MoonWorks.Graphics.Font (uint) fontFileByteSpan.Length, (IntPtr) atlasFileByteBuffer, (uint) atlasFileByteSpan.Length, - out float pixelsPerEm + out float pixelsPerEm, + out float distanceRange ); var texture = Texture.FromImageFile(graphicsDevice, commandBuffer, Path.ChangeExtension(fontPath, ".png")); @@ -47,14 +49,15 @@ namespace MoonWorks.Graphics.Font NativeMemory.Free(fontFileByteBuffer); NativeMemory.Free(atlasFileByteBuffer); - return new Font(handle, texture, pixelsPerEm); + return new Font(handle, texture, pixelsPerEm, distanceRange); } - private Font(IntPtr handle, Texture texture, float pixelsPerEm) + private Font(IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange) { Handle = handle; Texture = texture; PixelsPerEm = pixelsPerEm; + DistanceRange = distanceRange; StringBytesLength = 32; StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength); diff --git a/src/Graphics/Font/TextBatch.cs b/src/Graphics/Font/TextBatch.cs index d9abf6a..d492626 100644 --- a/src/Graphics/Font/TextBatch.cs +++ b/src/Graphics/Font/TextBatch.cs @@ -119,6 +119,7 @@ namespace MoonWorks.Graphics.Font } NativeMemory.Free(StringBytes); + Wellspring.Wellspring_DestroyTextBatch(Handle); IsDisposed = true; } -- 2.25.1 From b24b8a3869af38fc74a2bb9b66cb9e9ed392fa91 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Sat, 9 Dec 2023 17:41:08 -0800 Subject: [PATCH 06/17] update TextBatch Add call --- lib/WellspringCS | 2 +- src/Graphics/Font/TextBatch.cs | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/WellspringCS b/lib/WellspringCS index 9d62a80..6e98795 160000 --- a/lib/WellspringCS +++ b/lib/WellspringCS @@ -1 +1 @@ -Subproject commit 9d62a80dc7d5f339d5384bfb4d2954087151409c +Subproject commit 6e9879505978c6a7f05bc8ee1145587703ede0bf diff --git a/src/Graphics/Font/TextBatch.cs b/src/Graphics/Font/TextBatch.cs index d492626..390727c 100644 --- a/src/Graphics/Font/TextBatch.cs +++ b/src/Graphics/Font/TextBatch.cs @@ -45,10 +45,7 @@ namespace MoonWorks.Graphics.Font public unsafe bool Add( string text, - float x, - float y, int pixelSize, - float depth, Color color, HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left, VerticalAlignment verticalAlignment = VerticalAlignment.Baseline @@ -64,12 +61,9 @@ namespace MoonWorks.Graphics.Font { System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount); - var result = Wellspring.Wellspring_Draw( + var result = Wellspring.Wellspring_AddToTextBatch( Handle, - x, - y, pixelSize, - depth, new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A }, (Wellspring.HorizontalAlignment) horizontalAlignment, (Wellspring.VerticalAlignment) verticalAlignment, -- 2.25.1 From aa9baf2dffa5e28668b93cb1d5ad6e6040418600 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 12 Dec 2023 12:43:28 -0800 Subject: [PATCH 07/17] Improve tracking of resources --- src/Graphics/Font/Font.cs | 25 ++------ src/Graphics/Font/TextBatch.cs | 26 ++------- src/Graphics/GraphicsDevice.cs | 12 +++- src/Graphics/GraphicsResource.cs | 18 ++---- src/Graphics/RefreshResource.cs | 31 ++++++++++ src/Graphics/Resources/Buffer.cs | 2 +- src/Graphics/Resources/ComputePipeline.cs | 2 +- src/Graphics/Resources/Fence.cs | 2 +- src/Graphics/Resources/GraphicsPipeline.cs | 2 +- src/Graphics/Resources/Sampler.cs | 2 +- src/Graphics/Resources/ShaderModule.cs | 2 +- src/Graphics/Resources/Texture.cs | 2 +- src/Video/VideoAV1.cs | 23 ++++++-- src/Video/VideoAV1Stream.cs | 30 ++-------- src/Video/VideoPlayer.cs | 67 ++++++++++++---------- 15 files changed, 125 insertions(+), 121 deletions(-) create mode 100644 src/Graphics/RefreshResource.cs diff --git a/src/Graphics/Font/Font.cs b/src/Graphics/Font/Font.cs index a38f899..b1af731 100644 --- a/src/Graphics/Font/Font.cs +++ b/src/Graphics/Font/Font.cs @@ -5,7 +5,7 @@ using WellspringCS; namespace MoonWorks.Graphics.Font { - public unsafe class Font : IDisposable + public unsafe class Font : GraphicsResource { public Texture Texture { get; } public float PixelsPerEm { get; } @@ -16,8 +16,6 @@ namespace MoonWorks.Graphics.Font private byte* StringBytes; private int StringBytesLength; - private bool IsDisposed; - public unsafe static Font Load( GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, @@ -49,10 +47,10 @@ namespace MoonWorks.Graphics.Font NativeMemory.Free(fontFileByteBuffer); NativeMemory.Free(atlasFileByteBuffer); - return new Font(handle, texture, pixelsPerEm, distanceRange); + return new Font(graphicsDevice, handle, texture, pixelsPerEm, distanceRange); } - private Font(IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange) + private Font(GraphicsDevice device, IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange) : base(device) { Handle = handle; Texture = texture; @@ -101,7 +99,7 @@ namespace MoonWorks.Graphics.Font return true; } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { if (!IsDisposed) { @@ -111,21 +109,8 @@ namespace MoonWorks.Graphics.Font } Wellspring.Wellspring_DestroyFont(Handle); - IsDisposed = true; } - } - - ~Font() - { - // 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); + base.Dispose(disposing); } } } diff --git a/src/Graphics/Font/TextBatch.cs b/src/Graphics/Font/TextBatch.cs index 390727c..a3b5e28 100644 --- a/src/Graphics/Font/TextBatch.cs +++ b/src/Graphics/Font/TextBatch.cs @@ -4,7 +4,7 @@ using WellspringCS; namespace MoonWorks.Graphics.Font { - public unsafe class TextBatch : IDisposable + public unsafe class TextBatch : GraphicsResource { public const int MAX_CHARS = 4096; public const int MAX_VERTICES = MAX_CHARS * 4; @@ -22,11 +22,9 @@ namespace MoonWorks.Graphics.Font private byte* StringBytes; private int StringBytesLength; - private bool IsDisposed; - - public TextBatch(GraphicsDevice graphicsDevice) + public TextBatch(GraphicsDevice device) : base(device) { - GraphicsDevice = graphicsDevice; + GraphicsDevice = device; Handle = Wellspring.Wellspring_CreateTextBatch(); StringBytesLength = 128; @@ -102,7 +100,7 @@ namespace MoonWorks.Graphics.Font PrimitiveCount = vertexCount / 2; // FIXME: is this jank? } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { if (!IsDisposed) { @@ -114,22 +112,8 @@ namespace MoonWorks.Graphics.Font NativeMemory.Free(StringBytes); Wellspring.Wellspring_DestroyTextBatch(Handle); - - 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); + base.Dispose(disposing); } } } diff --git a/src/Graphics/GraphicsDevice.cs b/src/Graphics/GraphicsDevice.cs index 4fe5bca..9920b09 100644 --- a/src/Graphics/GraphicsDevice.cs +++ b/src/Graphics/GraphicsDevice.cs @@ -1,8 +1,8 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; +using MoonWorks.Video; using RefreshCS; namespace MoonWorks.Graphics @@ -363,6 +363,16 @@ namespace MoonWorks.Graphics { lock (resources) { + // Dispose video players first to avoid race condition on threaded decoding + foreach (var resource in resources) + { + if (resource.Target is VideoPlayer player) + { + player.Dispose(); + } + } + + // Dispose everything else foreach (var resource in resources) { if (resource.Target is IDisposable disposable) diff --git a/src/Graphics/GraphicsResource.cs b/src/Graphics/GraphicsResource.cs index 7eeccf9..87d8576 100644 --- a/src/Graphics/GraphicsResource.cs +++ b/src/Graphics/GraphicsResource.cs @@ -1,6 +1,5 @@ using System; using System.Runtime.InteropServices; -using System.Threading; namespace MoonWorks.Graphics { @@ -8,13 +7,11 @@ namespace MoonWorks.Graphics public abstract class GraphicsResource : IDisposable { public GraphicsDevice Device { get; } - public IntPtr Handle { get => handle; internal set => handle = value; } - private nint handle; - - public bool IsDisposed { get; private set; } - protected abstract Action QueueDestroyFunction { get; } private GCHandle SelfReference; + + public bool IsDisposed { get; private set; } + protected GraphicsResource(GraphicsDevice device) { Device = device; @@ -23,7 +20,7 @@ namespace MoonWorks.Graphics Device.AddResourceReference(SelfReference); } - protected void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) { if (!IsDisposed) { @@ -33,13 +30,6 @@ namespace MoonWorks.Graphics SelfReference.Free(); } - // Atomically call destroy function in case this is called from the finalizer thread - var toDispose = Interlocked.Exchange(ref handle, IntPtr.Zero); - if (toDispose != IntPtr.Zero) - { - QueueDestroyFunction(Device.Handle, toDispose); - } - IsDisposed = true; } } diff --git a/src/Graphics/RefreshResource.cs b/src/Graphics/RefreshResource.cs new file mode 100644 index 0000000..875d645 --- /dev/null +++ b/src/Graphics/RefreshResource.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace MoonWorks.Graphics; + +public abstract class RefreshResource : GraphicsResource +{ + public IntPtr Handle { get => handle; internal set => handle = value; } + private IntPtr handle; + + protected abstract Action QueueDestroyFunction { get; } + + protected RefreshResource(GraphicsDevice device) : base(device) + { + } + + protected override void Dispose(bool disposing) + { + if (!IsDisposed) + { + // Atomically call destroy function in case this is called from the finalizer thread + var toDispose = Interlocked.Exchange(ref handle, IntPtr.Zero); + if (toDispose != IntPtr.Zero) + { + QueueDestroyFunction(Device.Handle, toDispose); + } + } + base.Dispose(disposing); + } +} diff --git a/src/Graphics/Resources/Buffer.cs b/src/Graphics/Resources/Buffer.cs index 8563ac9..b034940 100644 --- a/src/Graphics/Resources/Buffer.cs +++ b/src/Graphics/Resources/Buffer.cs @@ -7,7 +7,7 @@ namespace MoonWorks.Graphics /// /// Buffers are generic data containers that can be used by the GPU. /// - public class Buffer : GraphicsResource + public class Buffer : RefreshResource { protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroyBuffer; diff --git a/src/Graphics/Resources/ComputePipeline.cs b/src/Graphics/Resources/ComputePipeline.cs index 4454501..19312c2 100644 --- a/src/Graphics/Resources/ComputePipeline.cs +++ b/src/Graphics/Resources/ComputePipeline.cs @@ -6,7 +6,7 @@ namespace MoonWorks.Graphics /// /// Compute pipelines perform arbitrary parallel processing on input data. /// - public class ComputePipeline : GraphicsResource + public class ComputePipeline : RefreshResource { protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroyComputePipeline; diff --git a/src/Graphics/Resources/Fence.cs b/src/Graphics/Resources/Fence.cs index fd380ae..223620e 100644 --- a/src/Graphics/Resources/Fence.cs +++ b/src/Graphics/Resources/Fence.cs @@ -10,7 +10,7 @@ namespace MoonWorks.Graphics /// The Fence object itself is basically just a wrapper for the Refresh_Fence.
/// The internal handle is replaced so that we can pool Fence objects to manage garbage. /// - public class Fence : GraphicsResource + public class Fence : RefreshResource { protected override Action QueueDestroyFunction => Refresh.Refresh_ReleaseFence; diff --git a/src/Graphics/Resources/GraphicsPipeline.cs b/src/Graphics/Resources/GraphicsPipeline.cs index a1e7506..421e9c5 100644 --- a/src/Graphics/Resources/GraphicsPipeline.cs +++ b/src/Graphics/Resources/GraphicsPipeline.cs @@ -8,7 +8,7 @@ namespace MoonWorks.Graphics /// Graphics pipelines encapsulate all of the render state in a single object.
/// These pipelines are bound before draw calls are issued. /// - public class GraphicsPipeline : GraphicsResource + public class GraphicsPipeline : RefreshResource { protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroyGraphicsPipeline; diff --git a/src/Graphics/Resources/Sampler.cs b/src/Graphics/Resources/Sampler.cs index 39463ad..ac52301 100644 --- a/src/Graphics/Resources/Sampler.cs +++ b/src/Graphics/Resources/Sampler.cs @@ -6,7 +6,7 @@ namespace MoonWorks.Graphics /// /// A sampler specifies how a texture will be sampled in a shader. /// - public class Sampler : GraphicsResource + public class Sampler : RefreshResource { protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroySampler; diff --git a/src/Graphics/Resources/ShaderModule.cs b/src/Graphics/Resources/ShaderModule.cs index 61f8779..fad27cb 100644 --- a/src/Graphics/Resources/ShaderModule.cs +++ b/src/Graphics/Resources/ShaderModule.cs @@ -8,7 +8,7 @@ namespace MoonWorks.Graphics /// /// Shader modules expect input in Refresh bytecode format. /// - public class ShaderModule : GraphicsResource + public class ShaderModule : RefreshResource { protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroyShaderModule; diff --git a/src/Graphics/Resources/Texture.cs b/src/Graphics/Resources/Texture.cs index 8f1c9d0..7a8fadb 100644 --- a/src/Graphics/Resources/Texture.cs +++ b/src/Graphics/Resources/Texture.cs @@ -8,7 +8,7 @@ namespace MoonWorks.Graphics /// /// A container for pixel data. /// - public class Texture : GraphicsResource + public class Texture : RefreshResource { public uint Width { get; internal set; } public uint Height { get; internal set; } diff --git a/src/Video/VideoAV1.cs b/src/Video/VideoAV1.cs index 9aaf157..b1eb607 100644 --- a/src/Video/VideoAV1.cs +++ b/src/Video/VideoAV1.cs @@ -1,12 +1,13 @@ using System; using System.IO; +using MoonWorks.Graphics; namespace MoonWorks.Video { /// /// This class takes in a filename for AV1 data in .obu (open bitstream unit) format /// - public unsafe class VideoAV1 + public unsafe class VideoAV1 : GraphicsResource { public string Filename { get; } @@ -28,7 +29,7 @@ namespace MoonWorks.Video /// /// Opens an AV1 file so it can be loaded by VideoPlayer. You must also provide a playback framerate. /// - public VideoAV1(string filename, double framesPerSecond) + public VideoAV1(GraphicsDevice device, string filename, double framesPerSecond) : base(device) { if (!File.Exists(filename)) { @@ -67,8 +68,22 @@ namespace MoonWorks.Video Filename = filename; - StreamA = new VideoAV1Stream(this); - StreamB = new VideoAV1Stream(this); + StreamA = new VideoAV1Stream(device, this); + StreamB = new VideoAV1Stream(device, this); + } + + // NOTE: if you call this while a VideoPlayer is playing the stream, your program will explode + protected override void Dispose(bool disposing) + { + if (!IsDisposed) + { + if (disposing) + { + StreamA.Dispose(); + StreamB.Dispose(); + } + } + base.Dispose(disposing); } } } diff --git a/src/Video/VideoAV1Stream.cs b/src/Video/VideoAV1Stream.cs index ea89f8c..cc40aa0 100644 --- a/src/Video/VideoAV1Stream.cs +++ b/src/Video/VideoAV1Stream.cs @@ -1,8 +1,9 @@ using System; +using MoonWorks.Graphics; namespace MoonWorks.Video { - internal class VideoAV1Stream + internal class VideoAV1Stream : GraphicsResource { public IntPtr Handle => handle; IntPtr handle; @@ -19,9 +20,7 @@ namespace MoonWorks.Video public bool FrameDataUpdated { get; set; } - bool IsDisposed; - - public VideoAV1Stream(VideoAV1 video) + public VideoAV1Stream(GraphicsDevice device, VideoAV1 video) : base(device) { if (Dav1dfile.df_fopen(video.Filename, out handle) == 0) { @@ -71,32 +70,13 @@ namespace MoonWorks.Video } } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { if (!IsDisposed) { - if (disposing) - { - // dispose managed state (managed objects) - } - - // free unmanaged resources (unmanaged objects) Dav1dfile.df_close(Handle); - - IsDisposed = true; } - } - - ~VideoAV1Stream() - { - 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); + base.Dispose(disposing); } } } diff --git a/src/Video/VideoPlayer.cs b/src/Video/VideoPlayer.cs index 826e7b1..cf14eeb 100644 --- a/src/Video/VideoPlayer.cs +++ b/src/Video/VideoPlayer.cs @@ -8,7 +8,7 @@ namespace MoonWorks.Video /// /// A structure for continuous decoding of AV1 videos and rendering them into a texture. /// - public unsafe class VideoPlayer : IDisposable + public unsafe class VideoPlayer : GraphicsResource { public Texture RenderTexture { get; private set; } = null; public VideoState State { get; private set; } = VideoState.Stopped; @@ -18,6 +18,10 @@ namespace MoonWorks.Video private VideoAV1 Video = null; private VideoAV1Stream CurrentStream = null; + private Task ReadNextFrameTask; + private Task ResetStreamATask; + private Task ResetStreamBTask; + private GraphicsDevice GraphicsDevice; private Texture yTexture = null; private Texture uTexture = null; @@ -30,17 +34,15 @@ namespace MoonWorks.Video private double lastTimestamp; private double timeElapsed; - private bool disposed; - - public VideoPlayer(GraphicsDevice graphicsDevice) + public VideoPlayer(GraphicsDevice device) : base(device) { - GraphicsDevice = graphicsDevice; + GraphicsDevice = device; if (GraphicsDevice.VideoPipeline == null) { throw new InvalidOperationException("Missing video shaders!"); } - LinearSampler = new Sampler(graphicsDevice, SamplerCreateInfo.LinearClamp); + LinearSampler = new Sampler(device, SamplerCreateInfo.LinearClamp); timer = new Stopwatch(); } @@ -168,6 +170,8 @@ namespace MoonWorks.Video public void Unload() { Stop(); + ResetStreamATask?.Wait(); + ResetStreamBTask?.Wait(); Video = null; } @@ -194,7 +198,8 @@ namespace MoonWorks.Video } currentFrame = thisFrame; - Task.Run(CurrentStream.ReadNextFrame).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); + ReadNextFrameTask = Task.Run(CurrentStream.ReadNextFrame); + ReadNextFrameTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); } if (CurrentStream.Ended) @@ -202,7 +207,17 @@ namespace MoonWorks.Video timer.Stop(); timer.Reset(); - Task.Run(CurrentStream.Reset).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); + var task = Task.Run(CurrentStream.Reset); + task.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); + + if (CurrentStream == Video.StreamA) + { + ResetStreamATask = task; + } + else + { + ResetStreamBTask = task; + } if (Loop) { @@ -280,8 +295,12 @@ namespace MoonWorks.Video private void InitializeDav1dStream() { - Task.Run(Video.StreamA.Reset).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); - Task.Run(Video.StreamB.Reset).ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); + ReadNextFrameTask?.Wait(); + + ResetStreamATask = Task.Run(Video.StreamA.Reset); + ResetStreamATask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); + ResetStreamBTask = Task.Run(Video.StreamB.Reset); + ResetStreamBTask.ContinueWith(HandleTaskException, TaskContinuationOptions.OnlyOnFaulted); CurrentStream = Video.StreamA; currentFrame = -1; @@ -289,37 +308,27 @@ namespace MoonWorks.Video private static void HandleTaskException(Task task) { - throw task.Exception; + if (task.Exception.InnerException is not TaskCanceledException) + { + throw task.Exception; + } } - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { - if (!disposed) + if (!IsDisposed) { if (disposing) { - // dispose managed state (managed objects) + Unload(); + RenderTexture.Dispose(); yTexture.Dispose(); uTexture.Dispose(); vTexture.Dispose(); } - - disposed = true; } - } - - ~VideoPlayer() - { - // 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); + base.Dispose(disposing); } } } -- 2.25.1 From 6d1acc37e9db8e2d9856d8ba7eedac9732343923 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 12 Dec 2023 18:52:03 -0800 Subject: [PATCH 08/17] Embed video shaders --- MoonWorks.csproj | 9 +++ lib/dav1dfile | 2 +- src/Game.cs | 3 +- src/Graphics/GraphicsDevice.cs | 75 +++++++++++------- .../Binary/video_fullscreen.vert.refresh | Bin 0 -> 1997 bytes .../Binary/video_yuv2rgba.frag.refresh | Bin 0 -> 3030 bytes .../Source}/video_fullscreen.vert | 0 .../StockShaders/Source}/video_yuv2rgba.frag | 0 8 files changed, 56 insertions(+), 33 deletions(-) create mode 100644 src/Graphics/StockShaders/Binary/video_fullscreen.vert.refresh create mode 100644 src/Graphics/StockShaders/Binary/video_yuv2rgba.frag.refresh rename src/{Video/Shaders => Graphics/StockShaders/Source}/video_fullscreen.vert (100%) rename src/{Video/Shaders => Graphics/StockShaders/Source}/video_yuv2rgba.frag (100%) diff --git a/MoonWorks.csproj b/MoonWorks.csproj index e09a901..baa210d 100644 --- a/MoonWorks.csproj +++ b/MoonWorks.csproj @@ -24,4 +24,13 @@ Never + + + + MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh + + + MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh + + diff --git a/lib/dav1dfile b/lib/dav1dfile index 3dcd69f..5065e2c 160000 --- a/lib/dav1dfile +++ b/lib/dav1dfile @@ -1 +1 @@ -Subproject commit 3dcd69ff85db80eea51481edd323b42c05993e1a +Subproject commit 5065e2cd4662dbe023b77a45ef967f975170dfff diff --git a/src/Game.cs b/src/Game.cs index 1b7286f..0ba395f 100644 --- a/src/Game.cs +++ b/src/Game.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using SDL2; +using SDL2; using MoonWorks.Audio; using MoonWorks.Graphics; using MoonWorks.Input; diff --git a/src/Graphics/GraphicsDevice.cs b/src/Graphics/GraphicsDevice.cs index 9920b09..191dbde 100644 --- a/src/Graphics/GraphicsDevice.cs +++ b/src/Graphics/GraphicsDevice.cs @@ -45,39 +45,54 @@ namespace MoonWorks.Graphics string basePath = System.AppContext.BaseDirectory; string videoVertPath = Path.Combine(basePath, "video_fullscreen.vert.refresh"); string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh"); + + ShaderModule videoVertShader; + ShaderModule videoFragShader; + if (File.Exists(videoVertPath) && File.Exists(videoFragPath)) { - ShaderModule videoVertShader = new ShaderModule(this, videoVertPath); - ShaderModule videoFragShader = new ShaderModule(this, videoFragPath); - - VideoPipeline = new GraphicsPipeline( - this, - new GraphicsPipelineCreateInfo - { - AttachmentInfo = new GraphicsPipelineAttachmentInfo( - new ColorAttachmentDescription( - TextureFormat.R8G8B8A8, - ColorAttachmentBlendState.None - ) - ), - DepthStencilState = DepthStencilState.Disable, - VertexShaderInfo = GraphicsShaderInfo.Create( - videoVertShader, - "main", - 0 - ), - FragmentShaderInfo = GraphicsShaderInfo.Create( - videoFragShader, - "main", - 3 - ), - VertexInputState = VertexInputState.Empty, - RasterizerState = RasterizerState.CCW_CullNone, - PrimitiveType = PrimitiveType.TriangleList, - MultisampleState = MultisampleState.None - } - ); + videoVertShader = new ShaderModule(this, videoVertPath); + videoFragShader = new ShaderModule(this, videoFragPath); } + else + { + // use defaults + var assembly = typeof(GraphicsDevice).Assembly; + + using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoFullscreen.vert.refresh"); + using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh"); + + videoVertShader = new ShaderModule(this, vertStream); + videoFragShader = new ShaderModule(this, fragStream); + } + + VideoPipeline = new GraphicsPipeline( + this, + new GraphicsPipelineCreateInfo + { + AttachmentInfo = new GraphicsPipelineAttachmentInfo( + new ColorAttachmentDescription( + TextureFormat.R8G8B8A8, + ColorAttachmentBlendState.None + ) + ), + DepthStencilState = DepthStencilState.Disable, + VertexShaderInfo = GraphicsShaderInfo.Create( + videoVertShader, + "main", + 0 + ), + FragmentShaderInfo = GraphicsShaderInfo.Create( + videoFragShader, + "main", + 3 + ), + VertexInputState = VertexInputState.Empty, + RasterizerState = RasterizerState.CCW_CullNone, + PrimitiveType = PrimitiveType.TriangleList, + MultisampleState = MultisampleState.None + } + ); FencePool = new FencePool(this); } diff --git a/src/Graphics/StockShaders/Binary/video_fullscreen.vert.refresh b/src/Graphics/StockShaders/Binary/video_fullscreen.vert.refresh new file mode 100644 index 0000000000000000000000000000000000000000..131f3a6723eca7e74fc34ae78601249f78245eb0 GIT binary patch literal 1997 zcmZ`(ZBNrs6uvMva5xYV@CETAiXGrG7BohfFBv#UmTWRY{ML}IkS4WVx|U20iHYC+ zQO3kS;J@$>_{GHMX?wQ|=p~1H&U2og*RK0-n!7_kM}$ZXFO3Q@B*sNftcv(diD4iD zSSGrwwPvl*4cdi`Cq*(QL@FYZGbzR*9ihbgBip5M1#%T~7m~u?Fz#<4kBF?O*34?X zUajqztxm`61|J;DbZpl$+qQ4D0{he=Z%pIaZp#VVIw1{)o-ZxZQBE%m4y<0q^ZYiD zOjN^(V;)+5VD)L_sxj;uFoV`Z4?FLQzBdX*x zv5)=1S8?plw%rYm+?Lh%aZ>b!j??$0F%!;)r;1KEuZ75pZO(2SvI5yf#i}l1JPM0< z#O)0*)A7wl`WnUDGBaI+rP&iFTRO9&62r+o{;c=NDt<0hZj z0yOrjV$ernUGunEGySZKcKp5$>QXyl9yJrtUMc%N3?V+hRQAKjj^pQ{Gd^=a#SYmI zd*C7d8%P>=87OCygK#e2fzqB#FwW*jf=wkj=i?@j(242K%)lnz zKwR!|7Q#5hU!XeflNe*te-A?JYro1pevtLrp;W%-7pM2+lnj|ipP2RSgzwP^m=Qu z2xToY8Rm>L*M}WXNJbs#c(eqGy`EU6&4mowyU;>eVnc?;ESx8(>a|HF{pk6UI^r;B l2^kZX4l%Ua14f`(|03&?DPMT=xM|-CLf=iA?B~RY{sM(xQ?&p9 literal 0 HcmV?d00001 diff --git a/src/Graphics/StockShaders/Binary/video_yuv2rgba.frag.refresh b/src/Graphics/StockShaders/Binary/video_yuv2rgba.frag.refresh new file mode 100644 index 0000000000000000000000000000000000000000..176e8b4cab11b1f354d8b8560720f4bdc97b806f GIT binary patch literal 3030 zcma)8*=`&~6fN8F!i32}HbMfl6P7WaG`-9k$4fGHOe9MNk1@+jYvgHLBTZ(ko*rX^ zB9VW{XYjxSuYknA@CQ8b5{YxF`*tcGTco1vy346^t8U$z&er4R7h}IoDK#;EcUq}2 zbzbGwno7oHH4cx0Z%WVW8_f-S5V!48*&*VBn$SREE~;sD4m0IxtJ7oPGQ@^l0*-zp zfHnbErT)UAb1J9SH-h!e&Gn77VE5o47{tfjFz9xAVbJbG;cnbH42hdDaGl<6ci1)o zlR%hxCz0bVS zTKkQDw@<=#WM_^$5Y|F$uH~T3d>?2lv6;)n(L7#g5_p;>iVRH>}j4@9cGda0WSyzMp%kA&~5W5ch*Jvi&?|smT--M8xy|9GLw26~Ex1h5RB+qT=W}f$9 z6GOk`;X9(AL-JVAnTI{ZJhX|EJa?e8pCr#+=w_aeU=u^XC_5Jd8qb^}$}Z--=61mi*LP_pK=0?+oHFD#Xs*LOz%NjyL0099n_kfha~8%4I&5@%?c2uY=GLkMf@f!uY!2fQ79%%k znmfUEE7}j^S3HOPPTShUS2o~(Ckk_~Z^F0maYsJx3YvXnEoS#_C)ZpUx!__}h&lVn zSbU7da$(tGaY&WVTKppx%Z+4<#qmFjt@*{Irheg`V>=bEyjQfKWfL!-N6hX?AGF<% z3pjL9*1EP^DLEK^$@e|4TK4NH7f8YA+SR&O$EbNV->+3X-zimU)$-olC_iiQ=KR>J z=W6PDz3lmJ#jBOvYF(2jC-IZ0=U1JIQ$8~-tN7+=J==9%eV|&oT&_Dc&+%PdJRbOz zQ~dyX7a^4M89Uu<@uWK~-DRdlc!uOqZSAJaT1v3XDSh^7Ya!WE42ye{I(H3;+NC literal 0 HcmV?d00001 diff --git a/src/Video/Shaders/video_fullscreen.vert b/src/Graphics/StockShaders/Source/video_fullscreen.vert similarity index 100% rename from src/Video/Shaders/video_fullscreen.vert rename to src/Graphics/StockShaders/Source/video_fullscreen.vert diff --git a/src/Video/Shaders/video_yuv2rgba.frag b/src/Graphics/StockShaders/Source/video_yuv2rgba.frag similarity index 100% rename from src/Video/Shaders/video_yuv2rgba.frag rename to src/Graphics/StockShaders/Source/video_yuv2rgba.frag -- 2.25.1 From 005dcb864a8a4b46fceb08cef8ee50c92e78f4a2 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Tue, 12 Dec 2023 19:29:45 -0800 Subject: [PATCH 09/17] audio disposal is friggin haunted man --- src/Audio/AudioDevice.cs | 20 +++++++++++++++++--- src/Game.cs | 6 +++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs index 17f617e..c6f2cda 100644 --- a/src/Audio/AudioDevice.cs +++ b/src/Audio/AudioDevice.cs @@ -292,15 +292,30 @@ namespace MoonWorks.Audio { Thread.Join(); - // dispose all voices first + // dispose all source voices first foreach (var resource in resources) { - if (resource.Target is Voice voice) + if (resource.Target is SourceVoice voice) { voice.Dispose(); } } + // dispose all submix voices except the faux mastering voice + foreach (var resource in resources) + { + if (resource.Target is SubmixVoice voice && voice != fauxMasteringVoice) + { + voice.Dispose(); + } + } + + // dispose the faux mastering voice + fauxMasteringVoice.Dispose(); + + // dispose the true mastering voice + FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice); + // destroy all other audio resources foreach (var resource in resources) { @@ -313,7 +328,6 @@ namespace MoonWorks.Audio resources.Clear(); } - FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice); FAudio.FAudio_Release(Handle); IsDisposed = true; diff --git a/src/Game.cs b/src/Game.cs index 0ba395f..38e2784 100644 --- a/src/Game.cs +++ b/src/Game.cs @@ -109,9 +109,6 @@ namespace MoonWorks Logger.LogInfo("Cleaning up game..."); Destroy(); - Logger.LogInfo("Closing audio thread..."); - AudioDevice.Dispose(); - Logger.LogInfo("Unclaiming window..."); GraphicsDevice.UnclaimWindow(MainWindow); @@ -121,6 +118,9 @@ namespace MoonWorks Logger.LogInfo("Disposing graphics device..."); GraphicsDevice.Dispose(); + Logger.LogInfo("Closing audio thread..."); + AudioDevice.Dispose(); + SDL.SDL_Quit(); } -- 2.25.1 From baef5d3ed958fdcb37be121a29606201c38401f0 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 13 Dec 2023 13:57:53 -0800 Subject: [PATCH 10/17] stop updating audio on dispose --- src/Audio/AudioDevice.cs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs index c6f2cda..de5e847 100644 --- a/src/Audio/AudioDevice.cs +++ b/src/Audio/AudioDevice.cs @@ -25,7 +25,7 @@ namespace MoonWorks.Audio public float DopplerScale = 1f; public float SpeedOfSound = 343.5f; - private readonly HashSet resources = new HashSet(); + private readonly HashSet resourceHandles = new HashSet(); private readonly HashSet updatingSourceVoices = new HashSet(); private AudioTweenManager AudioTweenManager; @@ -265,7 +265,7 @@ namespace MoonWorks.Audio { lock (StateLock) { - resources.Add(resourceReference); + resourceHandles.Add(resourceReference); if (resourceReference.Target is UpdatingSourceVoice updatableVoice) { @@ -278,7 +278,12 @@ namespace MoonWorks.Audio { lock (StateLock) { - resources.Remove(resourceReference); + resourceHandles.Remove(resourceReference); + + if (resourceReference.Target is UpdatingSourceVoice updatableVoice) + { + updatingSourceVoices.Remove(updatableVoice); + } } } @@ -293,18 +298,18 @@ namespace MoonWorks.Audio Thread.Join(); // dispose all source voices first - foreach (var resource in resources) + foreach (var handle in resourceHandles) { - if (resource.Target is SourceVoice voice) + if (handle.Target is SourceVoice voice) { voice.Dispose(); } } // dispose all submix voices except the faux mastering voice - foreach (var resource in resources) + foreach (var handle in resourceHandles) { - if (resource.Target is SubmixVoice voice && voice != fauxMasteringVoice) + if (handle.Target is SubmixVoice voice && voice != fauxMasteringVoice) { voice.Dispose(); } @@ -317,15 +322,15 @@ namespace MoonWorks.Audio FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice); // destroy all other audio resources - foreach (var resource in resources) + foreach (var handle in resourceHandles) { - if (resource.Target is IDisposable disposable) + if (handle.Target is AudioResource resource) { - disposable.Dispose(); + resource.Dispose(); } } - resources.Clear(); + resourceHandles.Clear(); } FAudio.FAudio_Release(Handle); -- 2.25.1 From 1cdcdab1488158323c3eb4d18e092099e594afd0 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 13 Dec 2023 13:58:55 -0800 Subject: [PATCH 11/17] avoid race condition on StreamingSound.Dispose --- src/Audio/StreamingVoice.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Audio/StreamingVoice.cs b/src/Audio/StreamingVoice.cs index 528e7ec..22c886c 100644 --- a/src/Audio/StreamingVoice.cs +++ b/src/Audio/StreamingVoice.cs @@ -150,13 +150,16 @@ namespace MoonWorks.Audio { if (!IsDisposed) { - Stop(); - - for (int i = 0; i < BUFFER_COUNT; i += 1) + lock (StateLock) { - if (buffers[i] != IntPtr.Zero) + Stop(); + + for (int i = 0; i < BUFFER_COUNT; i += 1) { - NativeMemory.Free((void*) buffers[i]); + if (buffers[i] != IntPtr.Zero) + { + NativeMemory.Free((void*) buffers[i]); + } } } } -- 2.25.1 From 6cd31b4938db6f54815edf86e9fedbb3075c90ba Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Wed, 13 Dec 2023 17:19:02 -0800 Subject: [PATCH 12/17] add some defaults to make text render setup easier --- MoonWorks.csproj | 6 +++ src/Graphics/Font/Structs.cs | 9 +++- src/Graphics/Font/TextBatch.cs | 18 +++++++ src/Graphics/GraphicsDevice.cs | 45 +++++++++++++++++- .../Binary/text_msdf.frag.refresh | Bin 0 -> 2697 bytes .../Binary/text_transform.vert.refresh | Bin 0 -> 1529 bytes .../StockShaders/Source/text_msdf.frag | 34 +++++++++++++ .../StockShaders/Source/text_transform.vert | 20 ++++++++ src/Video/VideoPlayer.cs | 4 -- 9 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 src/Graphics/StockShaders/Binary/text_msdf.frag.refresh create mode 100644 src/Graphics/StockShaders/Binary/text_transform.vert.refresh create mode 100644 src/Graphics/StockShaders/Source/text_msdf.frag create mode 100644 src/Graphics/StockShaders/Source/text_transform.vert diff --git a/MoonWorks.csproj b/MoonWorks.csproj index baa210d..2e0eb18 100644 --- a/MoonWorks.csproj +++ b/MoonWorks.csproj @@ -32,5 +32,11 @@ MoonWorks.Graphics.StockShaders.VideoYUV2RGBA.frag.refresh + + MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh + + + MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh + diff --git a/src/Graphics/Font/Structs.cs b/src/Graphics/Font/Structs.cs index b5b1e5a..f5f0eec 100644 --- a/src/Graphics/Font/Structs.cs +++ b/src/Graphics/Font/Structs.cs @@ -4,10 +4,17 @@ using MoonWorks.Math.Float; namespace MoonWorks.Graphics.Font { [StructLayout(LayoutKind.Sequential)] - public struct Vertex + public struct Vertex : IVertexType { public Vector3 Position; public Vector2 TexCoord; public Color Color; + + public static VertexElementFormat[] Formats { get; } = new VertexElementFormat[] + { + VertexElementFormat.Vector3, + VertexElementFormat.Vector2, + VertexElementFormat.Color + }; } } diff --git a/src/Graphics/Font/TextBatch.cs b/src/Graphics/Font/TextBatch.cs index a3b5e28..cccbd59 100644 --- a/src/Graphics/Font/TextBatch.cs +++ b/src/Graphics/Font/TextBatch.cs @@ -100,6 +100,24 @@ namespace MoonWorks.Graphics.Font PrimitiveCount = vertexCount / 2; // FIXME: is this jank? } + // Call this AFTER binding your text pipeline! + public void Render(CommandBuffer commandBuffer, Math.Float.Matrix4x4 transformMatrix) + { + commandBuffer.BindFragmentSamplers(new TextureSamplerBinding( + CurrentFont.Texture, + GraphicsDevice.LinearSampler + )); + commandBuffer.BindVertexBuffers(VertexBuffer); + commandBuffer.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo); + commandBuffer.DrawIndexedPrimitives( + 0, + 0, + PrimitiveCount, + commandBuffer.PushVertexShaderUniforms(transformMatrix), + commandBuffer.PushFragmentShaderUniforms(CurrentFont.DistanceRange) + ); + } + protected override void Dispose(bool disposing) { if (!IsDisposed) diff --git a/src/Graphics/GraphicsDevice.cs b/src/Graphics/GraphicsDevice.cs index 191dbde..7fc0737 100644 --- a/src/Graphics/GraphicsDevice.cs +++ b/src/Graphics/GraphicsDevice.cs @@ -4,6 +4,7 @@ using System.IO; using System.Runtime.InteropServices; using MoonWorks.Video; using RefreshCS; +using WellspringCS; namespace MoonWorks.Graphics { @@ -21,6 +22,15 @@ namespace MoonWorks.Graphics // Built-in video pipeline internal GraphicsPipeline VideoPipeline { get; } + // Built-in text shader info + public GraphicsShaderInfo TextVertexShaderInfo { get; } + public GraphicsShaderInfo TextFragmentShaderInfo { get; } + public VertexInputState TextVertexInputState { get; } + + // Built-in samplers + public Sampler PointSampler { get; } + public Sampler LinearSampler { get; } + public bool IsDisposed { get; private set; } private readonly HashSet resources = new HashSet(); @@ -41,14 +51,23 @@ namespace MoonWorks.Graphics Conversions.BoolToByte(debugMode) ); - // Check for optional video shaders + // TODO: check for CreateDevice fail + + // Check for replacement stock shaders string basePath = System.AppContext.BaseDirectory; + string videoVertPath = Path.Combine(basePath, "video_fullscreen.vert.refresh"); string videoFragPath = Path.Combine(basePath, "video_yuv2rgba.frag.refresh"); + string textVertPath = Path.Combine(basePath, "text_transform.vert.refresh"); + string textFragPath = Path.Combine(basePath, "text_msdf.frag.refresh"); + ShaderModule videoVertShader; ShaderModule videoFragShader; + ShaderModule textVertShader; + ShaderModule textFragShader; + if (File.Exists(videoVertPath) && File.Exists(videoFragPath)) { videoVertShader = new ShaderModule(this, videoVertPath); @@ -66,6 +85,23 @@ namespace MoonWorks.Graphics videoFragShader = new ShaderModule(this, fragStream); } + if (File.Exists(textVertPath) && File.Exists(textFragPath)) + { + textVertShader = new ShaderModule(this, textVertPath); + textFragShader = new ShaderModule(this, textFragPath); + } + else + { + // use defaults + var assembly = typeof(GraphicsDevice).Assembly; + + using var vertStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextTransform.vert.refresh"); + using var fragStream = assembly.GetManifestResourceStream("MoonWorks.Graphics.StockShaders.TextMSDF.frag.refresh"); + + textVertShader = new ShaderModule(this, vertStream); + textFragShader = new ShaderModule(this, fragStream); + } + VideoPipeline = new GraphicsPipeline( this, new GraphicsPipelineCreateInfo @@ -94,6 +130,13 @@ namespace MoonWorks.Graphics } ); + TextVertexShaderInfo = GraphicsShaderInfo.Create(textVertShader, "main", 0); + TextFragmentShaderInfo = GraphicsShaderInfo.Create(textFragShader, "main", 1); + TextVertexInputState = VertexInputState.CreateSingleBinding(); + + PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp); + LinearSampler = new Sampler(this, SamplerCreateInfo.LinearClamp); + FencePool = new FencePool(this); } diff --git a/src/Graphics/StockShaders/Binary/text_msdf.frag.refresh b/src/Graphics/StockShaders/Binary/text_msdf.frag.refresh new file mode 100644 index 0000000000000000000000000000000000000000..ba50a5aa2a2d33d398d20d8b5b57b6728ccf2cc0 GIT binary patch literal 2697 zcmZ9Ne^ZoI6vr>J3kZmssX3K_SYZ&UAxfl7f-V{bfo8wgWp|;cV3&cV(f+b&n&}1f zih2jVoBpZk^Lh5(dh8j$oO8bC*S+`LyRP5eSnhc?7((xn@%|8c!caICc0%_%8IHh& zpuCD43VpdH+qZwK+l|%TQ zCZ#YKDl65>>S|@>PPM+bS3T%F-Dy^LvR1R&$o8A{PWD67xPd}k)~fFuHi`jdB8t7s zbQ-3|ZnKfqS`%AW=DYu(LYe+Yk^52S81gmB520`7d|x<@JgDzCo2|7c>$TQ)bHdzm zI1hi=$~rlh$M%NTkPmOKa#H17#|(Sjti0bC_Tgq5y%Z+VcMlp{#+1qMjJtgvG@oo_ zKeAo;a(;eU3vRL9-sd1)-x5A@bN#+~IbRANqVLuAYrA=FDXjg^e(;>#A3n!Udt1s5 zI<;2aw$v?!Z;Zl`o{4_0#J_gY?_KiT{fuYZ&)Kd=zxk)=YTq%s*-!lpS|4&WM=8ID-bJ}F z{tiT4F2Vef$e*R>D^a)JDE6x{R{lJ(?zh{cl=m@CZl2gt&l7US(#N4K|qT(7qGMsA9g$b0F{DZ<<>IQyhr?d{0Taz6HS-G0{l6Q+!` z&mwXO^haCiTP@nY*P`uvE!w`A=HeQ^K=*e%)%+6On4j5~b-n^}-rHXy z_cgkC`mE{M%cUAsbh)2m{9|-EYYcO?*4+f2r!#pBykEW}>p469)^j%Bg6(LVn*n3i z*sK2UA}60b>jMwM+^F3K_5c*ZXZLY z{hvfPKG`pzn=>3|a|+!xy$+1kpJHD`FXo=cF6N%WHg^ITt3TzQMRz@^&r9g3k9qo& z?_Bg*$MqQdH~Ww;o`rYAZv?o{6G-2%zwQ2i;@KqoYP7wx7t!6{7#Ig>jc-JLjTqnI zn?Oz&a|t;GCP4$+^}QXrZE~FBJCU<)n;7d{j{L8&?iFln%R6h=DrXPluY+0eN3?H5 zF6GZ*&j5Mz-J4v>pGUW5vfoEH<~RC#hi(F6j4kPsjUkX%GcAkbT@BoI$55}sgI3k=iu~=2v=Mx*@ugEURuFDQ(w`5IOMZdE4UpiPd%ckA++r3`9 z`^q1V$NnV!76yK3qre~8Bp9alO8{?aj%TA`I33LsJTd0)_S7>SeGL76kQ@g|8k{N= zn&J55;)zXd90^@?d}eXDjnaYruA4cN<>*)PMH=(#iatYaH|?I-ak=2RPww;_D`A(mft`q@Zr3iI?s%Xpf;3r<^coQxcXwl=t9gK@X=&GZssxfZRl3^ zx_V$P{4IG4+H10Hou^M?=H1l)lJ=U6)y(5F^0{wU@v?UKV0-e=r>H>=tBH3`d!gmy zJm%eianGvbWp%lScumIIQH*xfrLR4mLPIcWU(n7hVALi?S1|Gyb00o4ZfmD!?k2yZ zy(9zYJTbb1(F@!wGGl%`gI|}&S^A+~Lpz+}zH9QygHw-~?*_*C8``N|0GGt9D^ zW7=le)g0sAg2!9qEIst42tDr0I1isZ+Wk~ray1!u!=XQBJ&^Gh= Date: Fri, 15 Dec 2023 09:23:46 -0800 Subject: [PATCH 13/17] make text batches use less memory by defualt --- src/Graphics/Font/TextBatch.cs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Graphics/Font/TextBatch.cs b/src/Graphics/Font/TextBatch.cs index cccbd59..7aef845 100644 --- a/src/Graphics/Font/TextBatch.cs +++ b/src/Graphics/Font/TextBatch.cs @@ -6,9 +6,9 @@ namespace MoonWorks.Graphics.Font { public unsafe class TextBatch : GraphicsResource { - public const int MAX_CHARS = 4096; - public const int MAX_VERTICES = MAX_CHARS * 4; - public const int MAX_INDICES = MAX_CHARS * 6; + public const int INITIAL_CHAR_COUNT = 64; + public const int INITIAL_VERTEX_COUNT = INITIAL_CHAR_COUNT * 4; + public const int INITIAL_INDEX_COUNT = INITIAL_CHAR_COUNT * 6; private GraphicsDevice GraphicsDevice { get; } public IntPtr Handle { get; } @@ -30,10 +30,11 @@ namespace MoonWorks.Graphics.Font StringBytesLength = 128; StringBytes = (byte*) NativeMemory.Alloc((nuint) StringBytesLength); - VertexBuffer = Buffer.Create(GraphicsDevice, BufferUsageFlags.Vertex, MAX_VERTICES); - IndexBuffer = Buffer.Create(GraphicsDevice, BufferUsageFlags.Index, MAX_INDICES); + VertexBuffer = Buffer.Create(GraphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT); + IndexBuffer = Buffer.Create(GraphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT); } + // Call this to initialize or reset the batch. public void Start(Font font) { Wellspring.Wellspring_StartTextBatch(Handle, font.Handle); @@ -41,6 +42,7 @@ namespace MoonWorks.Graphics.Font PrimitiveCount = 0; } + // Add text with size and color to the batch public unsafe bool Add( string text, int pixelSize, @@ -79,7 +81,7 @@ namespace MoonWorks.Graphics.Font return true; } - // Call this after you have made all the Draw calls you want. + // Call this after you have made all the Add calls you want, but before beginning a render pass. public unsafe void UploadBufferData(CommandBuffer commandBuffer) { Wellspring.Wellspring_GetBufferData( @@ -91,13 +93,25 @@ namespace MoonWorks.Graphics.Font out uint indexDataLengthInBytes ); + if (VertexBuffer.Size < vertexDataLengthInBytes) + { + VertexBuffer.Dispose(); + VertexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes); + } + + if (IndexBuffer.Size < indexDataLengthInBytes) + { + IndexBuffer.Dispose(); + IndexBuffer = new Buffer(GraphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes); + } + if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0) { commandBuffer.SetBufferData(VertexBuffer, vertexDataPointer, 0, vertexDataLengthInBytes); commandBuffer.SetBufferData(IndexBuffer, indexDataPointer, 0, indexDataLengthInBytes); } - PrimitiveCount = vertexCount / 2; // FIXME: is this jank? + PrimitiveCount = vertexCount / 2; } // Call this AFTER binding your text pipeline! -- 2.25.1 From 086267ae4c78329fb41f758b93da81999ab0a367 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 15 Dec 2023 10:02:40 -0800 Subject: [PATCH 14/17] only dispose video textures if they have been created --- src/Video/VideoPlayer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Video/VideoPlayer.cs b/src/Video/VideoPlayer.cs index efc2814..f2d079a 100644 --- a/src/Video/VideoPlayer.cs +++ b/src/Video/VideoPlayer.cs @@ -318,10 +318,10 @@ namespace MoonWorks.Video { Unload(); - RenderTexture.Dispose(); - yTexture.Dispose(); - uTexture.Dispose(); - vTexture.Dispose(); + RenderTexture?.Dispose(); + yTexture?.Dispose(); + uTexture?.Dispose(); + vTexture?.Dispose(); } } base.Dispose(disposing); -- 2.25.1 From e696d659d4f8d334098512806da40d59a7e56589 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 15 Dec 2023 10:03:51 -0800 Subject: [PATCH 15/17] add comment for Font.Load --- src/Graphics/Font/Font.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Graphics/Font/Font.cs b/src/Graphics/Font/Font.cs index b1af731..0dd6d1c 100644 --- a/src/Graphics/Font/Font.cs +++ b/src/Graphics/Font/Font.cs @@ -16,6 +16,11 @@ namespace MoonWorks.Graphics.Font private byte* StringBytes; private int StringBytesLength; + /// + /// Loads a TTF or OTF font from a path for use in MSDF rendering. + /// Note that there must be an msdf-atlas-gen JSON and image file alongside. + /// + /// public unsafe static Font Load( GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, -- 2.25.1 From 0d37d86cda552344659d5bd489f763493d28b76c Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 15 Dec 2023 10:37:34 -0800 Subject: [PATCH 16/17] more logging on program startup --- src/Audio/AudioDevice.cs | 1 - src/Game.cs | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs index de5e847..48ef387 100644 --- a/src/Audio/AudioDevice.cs +++ b/src/Audio/AudioDevice.cs @@ -123,7 +123,6 @@ namespace MoonWorks.Audio AudioTweenManager = new AudioTweenManager(); VoicePool = new SourceVoicePool(this); - Logger.LogInfo("Setting up audio thread..."); WakeSignal = new AutoResetEvent(true); Thread = new Thread(ThreadMain); diff --git a/src/Game.cs b/src/Game.cs index 38e2784..096afc7 100644 --- a/src/Game.cs +++ b/src/Game.cs @@ -57,6 +57,7 @@ namespace MoonWorks bool debugMode = false ) { + Logger.LogInfo("Initializing frame limiter..."); Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / targetTimestep); gameTimer = Stopwatch.StartNew(); @@ -67,6 +68,7 @@ namespace MoonWorks previousSleepTimes[i] = TimeSpan.FromMilliseconds(1); } + Logger.LogInfo("Initializing SDL..."); if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0) { Logger.LogError("Failed to initialize SDL!"); @@ -75,13 +77,16 @@ namespace MoonWorks Logger.Initialize(); + Logger.LogInfo("Initializing input..."); Inputs = new Inputs(); + Logger.LogInfo("Initializing graphics device..."); GraphicsDevice = new GraphicsDevice( Backend.Vulkan, debugMode ); + Logger.LogInfo("Initializing main window..."); MainWindow = new Window(windowCreateInfo, GraphicsDevice.WindowFlags | SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN); if (!GraphicsDevice.ClaimWindow(MainWindow, windowCreateInfo.PresentMode)) @@ -89,6 +94,7 @@ namespace MoonWorks throw new System.SystemException("Could not claim window!"); } + Logger.LogInfo("Initializing audio thread..."); AudioDevice = new AudioDevice(); } -- 2.25.1 From ffd8ada54365dcd42b8a28a85d72f9ab5e72e444 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 15 Dec 2023 10:38:35 -0800 Subject: [PATCH 17/17] bumping to Wellspring 1.0.0 --- MoonWorks.dll.config | 4 ++-- lib/WellspringCS | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MoonWorks.dll.config b/MoonWorks.dll.config index 67f28dc..167fa25 100644 --- a/MoonWorks.dll.config +++ b/MoonWorks.dll.config @@ -13,8 +13,8 @@ - - + + diff --git a/lib/WellspringCS b/lib/WellspringCS index 6e98795..074f2af 160000 --- a/lib/WellspringCS +++ b/lib/WellspringCS @@ -1 +1 @@ -Subproject commit 6e9879505978c6a7f05bc8ee1145587703ede0bf +Subproject commit 074f2afc833b221906bb2468735041ce78f2cb89 -- 2.25.1