Files
2026-07-13 13:15:05 +08:00

327 lines
12 KiB
C#

using Lib.Utils;
using T3.Core.Utils;
namespace Lib.render.@_;
[Guid("c5707b79-859b-4d53-92e0-cbed53aae648")]
internal sealed class _RenderFontBuffer : Instance<_RenderFontBuffer>
{
[Output(Guid = "3D2F53A3-F1F0-489B-B20B-BADB09CDAEBE")]
public readonly Slot<Buffer> Buffer = new();
[Output(Guid = "A0ECA9CE-35AA-497D-B5C9-CDE52A7C8D58")]
public readonly Slot<int> VertexCount = new();
public _RenderFontBuffer()
{
_fontResource = new Resource<BmFontDescription>(Filepath, OnFileChanged);
_fontResource.AddDependentSlots(Buffer);
Buffer.UpdateAction += UpdateMesh;
}
private bool OnFileChanged(FileResource file, BmFontDescription currentValue, out BmFontDescription newValue, out string failureReason)
{
if (BmFontDescription.TryInitializeFromFile(file.AbsolutePath, out newValue))
{
failureReason = null;
return true;
}
failureReason = "Failed to load font from file";
return false;
}
// Cached state (hot path avoids work if none changed)
private string _lastText = string.Empty;
private float _lastSize, _lastSpacing, _lastLineHeight;
private Vector2 _lastPosition;
private Vector4 _lastColor;
private int _lastHAlign, _lastVAlign;
private BmFontDescription _lastFontRef;
private bool _bufferDirty = true;
private BufferLayout[] _bufferContent = Array.Empty<BufferLayout>();
private int _lastVertexCount;
private static readonly int HorizontalAlignCount = Enum.GetValues(typeof(BmFontDescription.HorizontalAligns)).Length;
private static readonly int VerticalAlignCount = Enum.GetValues(typeof(BmFontDescription.VerticalAligns)).Length;
private void UpdateMesh(EvaluationContext context)
{
if (!_fontResource.TryGetValue(context, out var font))
return;
var text = Text.GetValue(context);
if (string.IsNullOrEmpty(text))
text = " ";
// Inputs
var rawSize = Size.GetValue(context);
var spacing = Spacing.GetValue(context);
var lineHeight = LineHeight.GetValue(context);
var position = Position.GetValue(context);
var color = Color.GetValue(context);
var hAlignInt = HorizontalAlign.GetValue(context).Clamp(0, HorizontalAlignCount - 1);
var vAlignInt = VerticalAlign.GetValue(context).Clamp(0, VerticalAlignCount - 1);
// Change detection
bool fontChanged = !ReferenceEquals(font, _lastFontRef);
bool textChanged = !string.Equals(text, _lastText, StringComparison.Ordinal);
bool formattingChanged =
_lastSize != rawSize ||
_lastSpacing != spacing ||
_lastLineHeight != lineHeight ||
_lastPosition != position ||
_lastColor != color ||
_lastHAlign != hAlignInt ||
_lastVAlign != vAlignInt;
if (fontChanged || textChanged || formattingChanged)
{
_lastFontRef = font;
_lastText = text;
_lastSize = rawSize;
_lastSpacing = spacing;
_lastLineHeight= lineHeight;
_lastPosition = position;
_lastColor = color;
_lastHAlign = hAlignInt;
_lastVAlign = vAlignInt;
// Ensure capacity (grow only)
if (_bufferContent.Length < text.Length)
_bufferContent = new BufferLayout[text.Length];
// Rebuild CPU buffer
_lastVertexCount = RebuildBuffer(
font,
text,
rawSize,
spacing,
lineHeight,
position,
color,
(BmFontDescription.HorizontalAligns)hAlignInt,
(BmFontDescription.VerticalAligns)vAlignInt);
_bufferDirty = true;
}
// Upload only when dirty or buffer missing
if (_bufferDirty || Buffer.Value == null)
{
ResourceManager.SetupStructuredBuffer(_bufferContent, ref Buffer.Value);
if (Buffer.Value != null)
Buffer.Value.DebugName = nameof(_RenderFontBuffer);
_bufferDirty = false;
}
VertexCount.Value = _lastVertexCount;
}
private int RebuildBuffer(BmFontDescription font,
string text,
float rawSize,
float characterSpacing,
float lineHeight,
Vector2 position,
Vector4 color,
BmFontDescription.HorizontalAligns horizontalAlign,
BmFontDescription.VerticalAligns verticalAlign)
{
// Line count without allocations
int numLinesInText = 1;
for (int i = 0; i < text.Length; i++)
if (text[i] == '\n') numLinesInText++;
float textureWidth = font.BmFont.Common.ScaleW;
float textureHeight = font.BmFont.Common.ScaleH;
// Scale to match 1080p 72DPI pt sizes (as in original)
var scaleFactor = 1.0 / font.BmFont.Info.Size * 0.00185;
var size = (float)(rawSize * scaleFactor);
float cursorX = 0;
float cursorY = 0;
const float sdfWidth = 5f; // heuristic
switch (verticalAlign)
{
case BmFontDescription.VerticalAligns.Top:
cursorY = font.BmFont.Common.Base * (1 + sdfWidth / font.BmFont.Info.Size);
break;
case BmFontDescription.VerticalAligns.Middle:
cursorY = font.BmFont.Common.LineHeight * lineHeight * (numLinesInText - 1) / 2
+ font.BmFont.Common.LineHeight / 2f
+ font.BmFont.Common.Base * (sdfWidth / font.BmFont.Info.Size);
break;
case BmFontDescription.VerticalAligns.Bottom:
cursorY = font.BmFont.Common.LineHeight * lineHeight * numLinesInText;
break;
}
int outputIndex = 0;
int currentLineCharacterCount = 0;
int lineNumber = 0;
int lastChar = 0;
for (int idx = 0; idx < text.Length; idx++)
{
char c = text[idx];
if (c == '\n')
{
AdjustLineAlignment(horizontalAlign, cursorX, characterSpacing, size, currentLineCharacterCount, outputIndex);
cursorY -= font.BmFont.Common.LineHeight * lineHeight;
cursorX = 0;
currentLineCharacterCount = 0;
lastChar = 0;
lineNumber++;
continue;
}
if (!font.InfoForCharacter.TryGetValue(c, out var charInfo))
{
lastChar = 0;
continue;
}
if (lastChar != 0)
{
int key = lastChar | c; // kept as in original
if (font.KerningForPairs.TryGetValue(key, out var kerning))
cursorX += kerning;
}
float sizeWidth = charInfo.Width * size;
float sizeHeight = charInfo.Height * size;
float x = position.X + (cursorX + charInfo.XOffset) * size;
float y = position.Y + ((cursorY - charInfo.YOffset)) * size;
if (charInfo.Width != 1 || charInfo.Height != 1)
{
_bufferContent[outputIndex] = new BufferLayout
{
Position = new Vector3(x, y, 0),
CharHeight = sizeHeight,
Orientation = Quaternion.Identity,
AspectRatio = sizeWidth / sizeHeight,
Color = color,
UvMinMax = new Vector4(
charInfo.X / textureWidth,
charInfo.Y / textureHeight,
(charInfo.X + charInfo.Width) / textureWidth,
(charInfo.Y + charInfo.Height) / textureHeight),
Id = (uint)outputIndex,
LineNumber = (uint)lineNumber,
Offset = new Vector2(charInfo.XOffset, charInfo.YOffset),
};
outputIndex++;
}
currentLineCharacterCount++;
cursorX += charInfo.XAdvance + characterSpacing;
lastChar = c;
}
// Align last line
AdjustLineAlignment(horizontalAlign, cursorX, characterSpacing, size, currentLineCharacterCount, outputIndex);
// Return vertex count (6 per quad)
return outputIndex * 6;
}
private void AdjustLineAlignment(BmFontDescription.HorizontalAligns horizontalAlign,
float cursorX,
float characterSpacing,
float size,
int currentLineCharacterCount,
int outputIndex)
{
switch (horizontalAlign)
{
case BmFontDescription.HorizontalAligns.Center:
OffsetLineCharacters((cursorX / 2 - characterSpacing / 2) * size, currentLineCharacterCount, outputIndex);
break;
case BmFontDescription.HorizontalAligns.Right:
OffsetLineCharacters(cursorX * size, currentLineCharacterCount, outputIndex);
break;
}
}
private void OffsetLineCharacters(float offset, int currentLineCharacterCount, int outputIndex)
{
for (int backIndex = 0; backIndex <= currentLineCharacterCount; backIndex++)
{
int index0 = outputIndex - backIndex;
if (index0 < 0 || index0 >= _bufferContent.Length)
continue;
_bufferContent[index0].Position.X -= offset;
}
}
private Resource<BmFontDescription> _fontResource;
[StructLayout(LayoutKind.Explicit, Size = StructSize)]
public struct BufferLayout
{
[FieldOffset(0)]
public Vector3 Position;
[FieldOffset(3 * 4)]
public float CharHeight;
[FieldOffset(4 * 4)]
public float AspectRatio;
[FieldOffset(5 * 4)]
public Quaternion Orientation;
[FieldOffset(9 * 4)]
public Vector4 Color;
[FieldOffset(13 * 4)]
public Vector4 UvMinMax;
[FieldOffset(17 * 4)]
public uint Id;
[FieldOffset(18 * 4)]
public uint LineNumber;
[FieldOffset(19 * 4)]
public Vector2 Offset;
private const int StructSize = 21 * 4;
}
// Inputs ----------------------------------------------------
[Input(Guid = "F2DD87B1-7F37-4B02-871B-B2E35972F246")]
public readonly InputSlot<string> Text = new();
[Input(Guid = "E827FDD1-20CA-473C-99EE-B839563690E9")]
public readonly InputSlot<string> Filepath = new();
[Input(Guid = "1CDE902D-5EAA-4144-B579-85F54717356B")]
public readonly InputSlot<Vector4> Color = new();
[Input(Guid = "5008E9B4-083A-4494-8F7C-50FE5D80FC35")]
public readonly InputSlot<float> Size = new();
[Input(Guid = "E05E143E-8D4C-4DE7-8C9C-7FA7755009D3")]
public readonly InputSlot<float> Spacing = new();
[Input(Guid = "9EB4E13F-0FE3-4ED9-9DF1-814F075A05DA")]
public readonly InputSlot<float> LineHeight = new();
[Input(Guid = "C4F03392-FF7E-4B4A-8740-F93A581B2B6B")]
public readonly InputSlot<Vector2> Position = new();
[Input(Guid = "14829EAC-BA59-4D31-90DC-53C7FC56CC30")]
public readonly InputSlot<int> VerticalAlign = new();
[Input(Guid = "E43BC887-D425-4F9C-8A86-A32C761DE0CC")]
public readonly InputSlot<int> HorizontalAlign = new();
}