feat(D.2b): textured-sprite path in TextRenderer + UV-rect DrawSprite
Add uUseTexture==2 (RGBA modulate) branch to ui_text.frag so dat sprites can be drawn through the existing 2D batcher without touching the font path. TextRenderer gains _spriteBufs (per-GL-handle List<float>), DrawSprite(), and a Flush block that issues one draw call per distinct texture with uUseTexture=2. Also adds DepthMask(false) in the state-save block (restored to true after) to prevent the transparent-quad pass from writing depth and corrupting the 3D scene if the UI is flushed mid-frame. TextureCache gains GetOrUpload(surfaceId, out width, out height) — caches pixel dimensions alongside the GL handle so UI 9-slice geometry can compute slice UVs from the source image size without a second decode. UiRenderContext gains a DrawSprite forwarder that applies the current 2D translate stack, matching the DrawRect / DrawRectOutline pattern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
626d06ebc1
commit
c9eef1d7cd
4 changed files with 70 additions and 2 deletions
|
|
@ -7,10 +7,13 @@ uniform sampler2D uTex;
|
|||
uniform int uUseTexture;
|
||||
|
||||
void main() {
|
||||
if (uUseTexture != 0) {
|
||||
if (uUseTexture == 1) {
|
||||
// Font atlas is a single-channel R8 texture; red = coverage alpha.
|
||||
float coverage = texture(uTex, vUv).r;
|
||||
FragColor = vec4(vColor.rgb, vColor.a * coverage);
|
||||
} else if (uUseTexture == 2) {
|
||||
// RGBA dat sprite (decoded to RGBA8); modulate by tint/alpha.
|
||||
FragColor = texture(uTex, vUv) * vColor;
|
||||
} else {
|
||||
FragColor = vColor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ 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();
|
||||
private int _textVerts;
|
||||
private int _rectVerts;
|
||||
private Vector2 _screenSize;
|
||||
|
|
@ -64,6 +65,7 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
_screenSize = screenSize;
|
||||
_textBuf.Clear();
|
||||
_rectBuf.Clear();
|
||||
foreach (var b in _spriteBufs.Values) b.Clear();
|
||||
_textVerts = 0;
|
||||
_rectVerts = 0;
|
||||
}
|
||||
|
|
@ -129,6 +131,22 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw a textured sprite quad in screen pixel space with an explicit
|
||||
/// source-UV rectangle (for 9-slice / atlas sub-regions). Batched per
|
||||
/// GL texture handle; flushed with uUseTexture=2 (RGBA modulate).
|
||||
/// </summary>
|
||||
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))
|
||||
{
|
||||
buf = new List<float>(256);
|
||||
_spriteBufs[texture] = buf;
|
||||
}
|
||||
AppendQuad(buf, x, y, w, h, u0, v0, u1, v1, tint);
|
||||
}
|
||||
|
||||
private static void AppendQuad(List<float> buf,
|
||||
float x, float y, float w, float h,
|
||||
float u0, float v0, float u1, float v1, Vector4 color)
|
||||
|
|
@ -159,7 +177,9 @@ 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)
|
||||
{
|
||||
if (_textVerts == 0 && _rectVerts == 0) return;
|
||||
bool hasSprites = false;
|
||||
foreach (var b in _spriteBufs.Values) if (b.Count > 0) { hasSprites = true; break; }
|
||||
if (_textVerts == 0 && _rectVerts == 0 && !hasSprites) return;
|
||||
|
||||
_shader.Use();
|
||||
_shader.SetVec2("uScreenSize", _screenSize);
|
||||
|
|
@ -173,6 +193,7 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
bool wasCull = _gl.IsEnabled(EnableCap.CullFace);
|
||||
_gl.Disable(EnableCap.DepthTest);
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
_gl.DepthMask(false);
|
||||
_gl.Enable(EnableCap.Blend);
|
||||
_gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
|
||||
|
|
@ -195,7 +216,23 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
_gl.DrawArrays(PrimitiveType.Triangles, 0, (uint)_textVerts);
|
||||
}
|
||||
|
||||
// RGBA dat sprites — one draw call per distinct GL texture.
|
||||
if (hasSprites)
|
||||
{
|
||||
_shader.SetInt("uUseTexture", 2);
|
||||
_gl.ActiveTexture(TextureUnit.Texture0);
|
||||
_shader.SetInt("uTex", 0);
|
||||
foreach (var kv in _spriteBufs)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// Restore GL state.
|
||||
_gl.DepthMask(true);
|
||||
if (!wasBlend) _gl.Disable(EnableCap.Blend);
|
||||
if (wasCull) _gl.Enable(EnableCap.CullFace);
|
||||
if (wasDepth) _gl.Enable(EnableCap.DepthTest);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public sealed unsafe class TextureCache : Wb.ITextureCachePerInstance, IDisposab
|
|||
private readonly GL _gl;
|
||||
private readonly DatCollection _dats;
|
||||
private readonly Dictionary<uint, uint> _handlesBySurfaceId = new();
|
||||
private readonly Dictionary<uint, (int w, int h)> _sizeBySurfaceId = new();
|
||||
/// <summary>
|
||||
/// Composite cache for surface-with-override-origtex entries (Phase 5
|
||||
/// TextureChanges). Key = (baseSurfaceId, overrideOrigTextureId),
|
||||
|
|
@ -80,6 +81,28 @@ public sealed unsafe class TextureCache : Wb.ITextureCachePerInstance, IDisposab
|
|||
return h;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="GetOrUpload(uint)"/> but also returns the decoded
|
||||
/// pixel dimensions. UI 9-slice geometry needs the source size to
|
||||
/// compute slice UVs. Cached alongside the handle.
|
||||
/// </summary>
|
||||
public uint GetOrUpload(uint surfaceId, out int width, out int height)
|
||||
{
|
||||
if (_handlesBySurfaceId.TryGetValue(surfaceId, out var existing)
|
||||
&& _sizeBySurfaceId.TryGetValue(surfaceId, out var sz))
|
||||
{
|
||||
width = sz.w; height = sz.h;
|
||||
return existing;
|
||||
}
|
||||
|
||||
var decoded = DecodeFromDats(surfaceId, origTextureOverride: null, paletteOverride: null);
|
||||
uint h = UploadRgba8(decoded);
|
||||
_handlesBySurfaceId[surfaceId] = h;
|
||||
_sizeBySurfaceId[surfaceId] = (decoded.Width, decoded.Height);
|
||||
width = decoded.Width; height = decoded.Height;
|
||||
return h;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alpha-channel histogram for one decoded texture. Used to diagnose
|
||||
/// "why are clouds not transparent" — if cloud textures come out with
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue