Setup entry point + integrated moonworks stuff
This commit is contained in:
16
Nerfed.Runtime/Graphics/Font/Enums.cs
Normal file
16
Nerfed.Runtime/Graphics/Font/Enums.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
public enum HorizontalAlignment
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
}
|
||||
|
||||
public enum VerticalAlignment
|
||||
{
|
||||
Baseline,
|
||||
Top,
|
||||
Middle,
|
||||
Bottom
|
||||
}
|
123
Nerfed.Runtime/Graphics/Font/Font.cs
Normal file
123
Nerfed.Runtime/Graphics/Font/Font.cs
Normal file
@ -0,0 +1,123 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using WellspringCS;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
public unsafe class Font : GraphicsResource
|
||||
{
|
||||
public Texture Texture { get; }
|
||||
public float PixelsPerEm { get; }
|
||||
public float DistanceRange { get; }
|
||||
|
||||
internal IntPtr Handle { get; }
|
||||
|
||||
private byte* StringBytes;
|
||||
private int StringBytesLength;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Font Load(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, string fontPath)
|
||||
{
|
||||
FileStream fontFileStream = new FileStream(fontPath, FileMode.Open, FileAccess.Read);
|
||||
void* fontFileByteBuffer = NativeMemory.Alloc((nuint)fontFileStream.Length);
|
||||
Span<byte> fontFileByteSpan = new Span<byte>(fontFileByteBuffer, (int)fontFileStream.Length);
|
||||
fontFileStream.ReadExactly(fontFileByteSpan);
|
||||
fontFileStream.Close();
|
||||
|
||||
FileStream atlasFileStream = new FileStream(Path.ChangeExtension(fontPath, ".json"), FileMode.Open, FileAccess.Read);
|
||||
void* atlasFileByteBuffer = NativeMemory.Alloc((nuint)atlasFileStream.Length);
|
||||
Span<byte> atlasFileByteSpan = new Span<byte>(atlasFileByteBuffer, (int)atlasFileStream.Length);
|
||||
atlasFileStream.ReadExactly(atlasFileByteSpan);
|
||||
atlasFileStream.Close();
|
||||
|
||||
IntPtr handle = Wellspring.Wellspring_CreateFont(
|
||||
(IntPtr)fontFileByteBuffer,
|
||||
(uint)fontFileByteSpan.Length,
|
||||
(IntPtr)atlasFileByteBuffer,
|
||||
(uint)atlasFileByteSpan.Length,
|
||||
out float pixelsPerEm,
|
||||
out float distanceRange
|
||||
);
|
||||
|
||||
string imagePath = Path.ChangeExtension(fontPath, ".png");
|
||||
ImageUtils.ImageInfoFromFile(imagePath, out uint width, out uint height, out uint sizeInBytes);
|
||||
|
||||
ResourceUploader uploader = new ResourceUploader(graphicsDevice);
|
||||
Texture texture = uploader.CreateTexture2DFromCompressed(imagePath);
|
||||
uploader.Upload();
|
||||
uploader.Dispose();
|
||||
|
||||
NativeMemory.Free(fontFileByteBuffer);
|
||||
NativeMemory.Free(atlasFileByteBuffer);
|
||||
|
||||
return new Font(graphicsDevice, handle, texture, pixelsPerEm, distanceRange);
|
||||
}
|
||||
|
||||
private Font(GraphicsDevice device, IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange) : base(device)
|
||||
{
|
||||
Handle = handle;
|
||||
Texture = texture;
|
||||
PixelsPerEm = pixelsPerEm;
|
||||
DistanceRange = distanceRange;
|
||||
|
||||
StringBytesLength = 32;
|
||||
StringBytes = (byte*)NativeMemory.Alloc((nuint)StringBytesLength);
|
||||
}
|
||||
|
||||
public unsafe bool TextBounds(
|
||||
string text,
|
||||
int pixelSize,
|
||||
HorizontalAlignment horizontalAlignment,
|
||||
VerticalAlignment verticalAlignment,
|
||||
out Wellspring.Rectangle rectangle
|
||||
)
|
||||
{
|
||||
int 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);
|
||||
|
||||
byte result = Wellspring.Wellspring_TextBounds(
|
||||
Handle,
|
||||
pixelSize,
|
||||
(Wellspring.HorizontalAlignment)horizontalAlignment,
|
||||
(Wellspring.VerticalAlignment)verticalAlignment,
|
||||
(IntPtr)StringBytes,
|
||||
(uint)byteCount,
|
||||
out rectangle
|
||||
);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
Log.Warning("Could not decode string: " + text);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
Texture.Dispose();
|
||||
}
|
||||
|
||||
Wellspring.Wellspring_DestroyFont(Handle);
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
26
Nerfed.Runtime/Graphics/Font/Structs.cs
Normal file
26
Nerfed.Runtime/Graphics/Font/Structs.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct FontVertex : IVertexType
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Vector2 TexCoord;
|
||||
public Color Color;
|
||||
|
||||
public static VertexElementFormat[] Formats { get; } =
|
||||
[
|
||||
VertexElementFormat.Vector3,
|
||||
VertexElementFormat.Vector2,
|
||||
VertexElementFormat.Color
|
||||
];
|
||||
|
||||
public static uint[] Offsets { get; } =
|
||||
[
|
||||
0,
|
||||
12,
|
||||
20
|
||||
];
|
||||
}
|
191
Nerfed.Runtime/Graphics/Font/TextBatch.cs
Normal file
191
Nerfed.Runtime/Graphics/Font/TextBatch.cs
Normal file
@ -0,0 +1,191 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using WellspringCS;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
public unsafe class TextBatch : GraphicsResource
|
||||
{
|
||||
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;
|
||||
|
||||
public IntPtr Handle { get; }
|
||||
public Buffer VertexBuffer { get; protected set; } = null;
|
||||
public Buffer IndexBuffer { get; protected set; } = null;
|
||||
public uint PrimitiveCount { get; protected set; }
|
||||
public Font CurrentFont { get; private set; }
|
||||
|
||||
private readonly GraphicsDevice graphicsDevice;
|
||||
private readonly int stringBytesLength;
|
||||
private TransferBuffer TransferBuffer;
|
||||
private byte* stringBytes;
|
||||
|
||||
public TextBatch(GraphicsDevice device) : base(device)
|
||||
{
|
||||
graphicsDevice = device;
|
||||
Handle = Wellspring.Wellspring_CreateTextBatch();
|
||||
|
||||
stringBytesLength = 128;
|
||||
stringBytes = (byte*)NativeMemory.Alloc((nuint)stringBytesLength);
|
||||
|
||||
VertexBuffer = Buffer.Create<FontVertex>(graphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT);
|
||||
IndexBuffer = Buffer.Create<uint>(graphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT);
|
||||
|
||||
TransferBuffer = TransferBuffer.Create<byte>(
|
||||
graphicsDevice,
|
||||
TransferBufferUsage.Upload,
|
||||
VertexBuffer.Size + IndexBuffer.Size
|
||||
);
|
||||
}
|
||||
|
||||
// Call this to initialize or reset the batch.
|
||||
public void Start(Font font)
|
||||
{
|
||||
Wellspring.Wellspring_StartTextBatch(Handle, font.Handle);
|
||||
CurrentFont = font;
|
||||
PrimitiveCount = 0;
|
||||
}
|
||||
|
||||
// Add text with size and color to the batch
|
||||
public unsafe bool Add(
|
||||
string text,
|
||||
int pixelSize,
|
||||
Color color,
|
||||
HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
|
||||
)
|
||||
{
|
||||
int 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);
|
||||
|
||||
byte result = Wellspring.Wellspring_AddToTextBatch(
|
||||
Handle,
|
||||
pixelSize,
|
||||
new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
|
||||
(Wellspring.HorizontalAlignment)horizontalAlignment,
|
||||
(Wellspring.VerticalAlignment)verticalAlignment,
|
||||
(IntPtr)stringBytes,
|
||||
(uint)byteCount
|
||||
);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
Log.Warning("Could not decode string: " + text);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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(
|
||||
Handle,
|
||||
out uint vertexCount,
|
||||
out IntPtr vertexDataPointer,
|
||||
out uint vertexDataLengthInBytes,
|
||||
out IntPtr indexDataPointer,
|
||||
out uint indexDataLengthInBytes
|
||||
);
|
||||
|
||||
Span<byte> vertexSpan = new Span<byte>((void*)vertexDataPointer, (int)vertexDataLengthInBytes);
|
||||
Span<byte> indexSpan = new Span<byte>((void*)indexDataPointer, (int)indexDataLengthInBytes);
|
||||
|
||||
bool newTransferBufferNeeded = false;
|
||||
|
||||
if (VertexBuffer.Size < vertexDataLengthInBytes)
|
||||
{
|
||||
VertexBuffer.Dispose();
|
||||
VertexBuffer = new Buffer(graphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
|
||||
newTransferBufferNeeded = true;
|
||||
}
|
||||
|
||||
if (IndexBuffer.Size < indexDataLengthInBytes)
|
||||
{
|
||||
IndexBuffer.Dispose();
|
||||
IndexBuffer = new Buffer(graphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes);
|
||||
newTransferBufferNeeded = true;
|
||||
}
|
||||
|
||||
if (newTransferBufferNeeded)
|
||||
{
|
||||
TransferBuffer.Dispose();
|
||||
TransferBuffer = new TransferBuffer(
|
||||
graphicsDevice,
|
||||
TransferBufferUsage.Upload,
|
||||
VertexBuffer.Size + IndexBuffer.Size
|
||||
);
|
||||
}
|
||||
|
||||
if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
|
||||
{
|
||||
TransferBuffer.SetData(vertexSpan, true);
|
||||
TransferBuffer.SetData(indexSpan, (uint)vertexSpan.Length, false);
|
||||
|
||||
CopyPass copyPass = commandBuffer.BeginCopyPass();
|
||||
copyPass.UploadToBuffer(
|
||||
new TransferBufferLocation(TransferBuffer),
|
||||
new BufferRegion(VertexBuffer, 0, (uint)vertexSpan.Length),
|
||||
true
|
||||
);
|
||||
copyPass.UploadToBuffer(
|
||||
new TransferBufferLocation(TransferBuffer, (uint)vertexSpan.Length),
|
||||
new BufferRegion(IndexBuffer, 0, (uint)indexSpan.Length),
|
||||
true
|
||||
);
|
||||
commandBuffer.EndCopyPass(copyPass);
|
||||
}
|
||||
|
||||
PrimitiveCount = vertexCount / 2;
|
||||
}
|
||||
|
||||
// Call this AFTER binding your text pipeline!
|
||||
public void Render(CommandBuffer commandBuffer, RenderPass renderPass, Matrix4x4 transformMatrix)
|
||||
{
|
||||
commandBuffer.PushVertexUniformData(transformMatrix);
|
||||
commandBuffer.PushFragmentUniformData(CurrentFont.DistanceRange);
|
||||
|
||||
renderPass.BindFragmentSampler(
|
||||
new TextureSamplerBinding(
|
||||
CurrentFont.Texture,
|
||||
graphicsDevice.LinearSampler
|
||||
)
|
||||
);
|
||||
renderPass.BindVertexBuffer(VertexBuffer);
|
||||
renderPass.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo);
|
||||
|
||||
renderPass.DrawIndexedPrimitives(
|
||||
0,
|
||||
0,
|
||||
PrimitiveCount
|
||||
);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
VertexBuffer.Dispose();
|
||||
IndexBuffer.Dispose();
|
||||
}
|
||||
|
||||
NativeMemory.Free(stringBytes);
|
||||
Wellspring.Wellspring_DestroyTextBatch(Handle);
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user