Setup entry point + integrated moonworks stuff

This commit is contained in:
2024-07-05 14:32:58 +02:00
parent e7a4a862be
commit 8334a24fd1
116 changed files with 16988 additions and 3 deletions

View File

@ -0,0 +1,16 @@
namespace Nerfed.Runtime.Graphics;
public enum HorizontalAlignment
{
Left,
Center,
Right
}
public enum VerticalAlignment
{
Baseline,
Top,
Middle,
Bottom
}

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

View 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
];
}

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