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(graphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT); IndexBuffer = Buffer.Create(graphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT); TransferBuffer = TransferBuffer.Create( 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 vertexSpan = new Span((void*)vertexDataPointer, (int)vertexDataLengthInBytes); Span indexSpan = new Span((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); } }