fix(D.2b): draw UI sprites in submission order so stamina/mana numbers render
TextRenderer batched sprites per-texture and drew each texture's whole buffer at its FIRST-insertion point. The dat-font glyph atlas is one shared texture used by all three vital numbers; it first appeared at the health bar, so all three numbers were emitted right after the health bars — then the stamina + mana bar sprites painted over their own numbers (only health survived). Replaced the per-texture dictionary with submission-ordered segments (consecutive same-texture quads still batch); each meter's number now draws after its own bars. The renderer's own comment had predicted this break once bars became sprites (importer did that). Removed the temporary UiMeter label diagnostic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8aa643f3e0
commit
43064bab09
2 changed files with 38 additions and 16 deletions
|
|
@ -29,7 +29,15 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
|
||||
private readonly List<float> _textBuf = new(8192);
|
||||
private readonly List<float> _rectBuf = new(1024);
|
||||
private readonly Dictionary<uint, List<float>> _spriteBufs = new();
|
||||
// Submission-ordered sprite segments: consecutive DrawSprite calls with the
|
||||
// SAME texture batch into one segment; a texture change starts a new segment.
|
||||
// Drawing segments in submission order preserves painter z-order for
|
||||
// sprite-on-sprite UI. (The old per-texture dictionary drew a REUSED texture
|
||||
// at its FIRST-insertion point, so later bar sprites covered glyphs emitted
|
||||
// earlier via the shared dat-font atlas — the stamina/mana numbers vanished.)
|
||||
private sealed class SpriteSeg { public uint Texture; public readonly List<float> Verts = new(256); }
|
||||
private readonly List<SpriteSeg> _spriteSegs = new();
|
||||
private int _segUsed;
|
||||
private int _textVerts;
|
||||
private int _rectVerts;
|
||||
private Vector2 _screenSize;
|
||||
|
|
@ -65,7 +73,7 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
_screenSize = screenSize;
|
||||
_textBuf.Clear();
|
||||
_rectBuf.Clear();
|
||||
foreach (var b in _spriteBufs.Values) b.Clear();
|
||||
_segUsed = 0; // pool the SpriteSeg objects across frames
|
||||
_textVerts = 0;
|
||||
_rectVerts = 0;
|
||||
}
|
||||
|
|
@ -139,12 +147,24 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
public void DrawSprite(uint texture, float x, float y, float w, float h,
|
||||
float u0, float v0, float u1, float v1, Vector4 tint)
|
||||
{
|
||||
if (!_spriteBufs.TryGetValue(texture, out var buf))
|
||||
SpriteSeg seg;
|
||||
if (_segUsed > 0 && _spriteSegs[_segUsed - 1].Texture == texture)
|
||||
{
|
||||
buf = new List<float>(256);
|
||||
_spriteBufs[texture] = buf;
|
||||
seg = _spriteSegs[_segUsed - 1]; // extend the current same-texture run
|
||||
}
|
||||
AppendQuad(buf, x, y, w, h, u0, v0, u1, v1, tint);
|
||||
else if (_segUsed < _spriteSegs.Count)
|
||||
{
|
||||
seg = _spriteSegs[_segUsed++]; // reuse a pooled segment
|
||||
seg.Texture = texture;
|
||||
seg.Verts.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
seg = new SpriteSeg { Texture = texture };
|
||||
_spriteSegs.Add(seg);
|
||||
_segUsed++;
|
||||
}
|
||||
AppendQuad(seg.Verts, x, y, w, h, u0, v0, u1, v1, tint);
|
||||
}
|
||||
|
||||
private static void AppendQuad(List<float> buf,
|
||||
|
|
@ -177,8 +197,7 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
/// <summary>Upload + draw accumulated rects + text. font may be null if only DrawRect was used.</summary>
|
||||
public void Flush(BitmapFont? font)
|
||||
{
|
||||
bool hasSprites = false;
|
||||
foreach (var b in _spriteBufs.Values) if (b.Count > 0) { hasSprites = true; break; }
|
||||
bool hasSprites = _segUsed > 0;
|
||||
if (_textVerts == 0 && _rectVerts == 0 && !hasSprites) return;
|
||||
|
||||
_shader.Use();
|
||||
|
|
@ -201,9 +220,10 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
// 1. RGBA dat sprites — window chrome / panel backgrounds (behind)
|
||||
// 2. Untextured rects — widget fills (e.g. vital bars) on the chrome
|
||||
// 3. Text glyphs — on top
|
||||
// NOTE: this type-bucketed order is correct while bars are solid rects.
|
||||
// When bars become gradient SPRITES, this must move to true submission
|
||||
// (painter) order so sprite-on-sprite z is preserved (D.2b follow-up).
|
||||
// Bucket 1 (sprites) draws in SUBMISSION (painter) order via _spriteSegs,
|
||||
// so sprite-on-sprite z is preserved — each meter's dat-font number draws
|
||||
// after its own bar sprites. Buckets 2 (rects) + 3 (debug text) composite
|
||||
// on top, in that order.
|
||||
|
||||
// 1. RGBA dat sprites first — one draw call per distinct GL texture.
|
||||
if (hasSprites)
|
||||
|
|
@ -211,12 +231,13 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
_shader.SetInt("uUseTexture", 2);
|
||||
_gl.ActiveTexture(TextureUnit.Texture0);
|
||||
_shader.SetInt("uTex", 0);
|
||||
foreach (var kv in _spriteBufs)
|
||||
for (int i = 0; i < _segUsed; i++)
|
||||
{
|
||||
if (kv.Value.Count == 0) continue;
|
||||
_gl.BindTexture(TextureTarget.Texture2D, kv.Key);
|
||||
UploadBuffer(kv.Value);
|
||||
_gl.DrawArrays(PrimitiveType.Triangles, 0, (uint)(kv.Value.Count / FloatsPerVertex));
|
||||
var seg = _spriteSegs[i];
|
||||
if (seg.Verts.Count == 0) continue;
|
||||
_gl.BindTexture(TextureTarget.Texture2D, seg.Texture);
|
||||
UploadBuffer(seg.Verts);
|
||||
_gl.DrawArrays(PrimitiveType.Triangles, 0, (uint)(seg.Verts.Count / FloatsPerVertex));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ namespace AcDream.App.UI;
|
|||
/// </summary>
|
||||
public sealed class UiMeter : UiElement
|
||||
{
|
||||
|
||||
/// <summary>Fill fraction provider; a null result draws an empty bar.</summary>
|
||||
public Func<float?> Fill { get; set; } = () => 0f;
|
||||
/// <summary>Centered overlay text provider (e.g. "291/291"); null = none.</summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue