feat(D.2b): Step-0 chrome sprites confirmed + direct-RenderSurface upload path

Step-0 prove-out result: retail UI chrome sprites are RenderSurface objects
(0x06xxxxxx) that must be decoded DIRECTLY, not via the Surface->SurfaceTexture
chain GetOrUpload uses for world materials (which produced 1x1 magenta/garbage).
Added TextureCache.GetOrUploadRenderSurface(id, out w, out h) — Portal/HighRes
TryGet<RenderSurface> -> DecodeRenderSurface(palette:null) -> upload, separately
cached. This is the path UI chrome + (later) dat fonts use.

Confirmed the universal floating-window bevel is an 8-piece border + center fill:
  center  0x06004CC2 (48x48)
  edges   0x060074BF/C1 (10x5 horiz)  0x060074C0/C2 (5x10 vert)
  corners 0x060074C3..C6 (5x5)
Recorded in RetailChromeSprites.cs (edge/corner->position mapping is a best
guess pending the LayoutDesc 0x21000040 parse; visually confirmed at panel
render). The memory-note ids were right; only the decode path was wrong.

Temporary prove-out harness (added to GameWindow.OnRender) removed. proveout*.log
gitignored.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-14 16:32:27 +02:00
parent 66888d2c8e
commit 8e91805206
3 changed files with 92 additions and 0 deletions

View file

@ -31,6 +31,12 @@ public sealed unsafe class TextureCache : Wb.ITextureCachePerInstance, IDisposab
private readonly Dictionary<(uint surfaceId, uint origTexOverride, ulong paletteHash), uint> _handlesByPalette = new();
private uint _magentaHandle;
// Direct-RenderSurface caches for UI sprites: 0x06xxxxxx RenderSurface ids
// decoded directly (Portal/HighRes → DecodeRenderSurface), bypassing the
// Surface→SurfaceTexture chain that GetOrUpload uses for world materials.
private readonly Dictionary<uint, uint> _handlesByRenderSurfaceId = new();
private readonly Dictionary<uint, (int w, int h)> _rsSizeById = new();
private readonly Wb.BindlessSupport? _bindless;
// Bindless / Texture2DArray parallel caches. Keys mirror the legacy three
@ -103,6 +109,43 @@ public sealed unsafe class TextureCache : Wb.ITextureCachePerInstance, IDisposab
return h;
}
/// <summary>
/// Upload a UI sprite by its RenderSurface DataId (0x06xxxxxx), decoded
/// DIRECTLY (Portal/HighRes → DecodeRenderSurface) rather than through the
/// Surface→SurfaceTexture chain that <see cref="GetOrUpload(uint)"/> uses
/// for world-geometry materials. This is the correct path for retail UI
/// chrome + font glyph sheets, which reference RenderSurface directly.
/// Palette is null for now (a paletted INDEX16/P8 UI sprite would return
/// Magenta — wire a UI palette when one is actually encountered). Returns a
/// 1x1 magenta handle on miss.
/// </summary>
public uint GetOrUploadRenderSurface(uint renderSurfaceId, out int width, out int height)
{
if (_handlesByRenderSurfaceId.TryGetValue(renderSurfaceId, out var existing)
&& _rsSizeById.TryGetValue(renderSurfaceId, out var sz))
{
width = sz.w; height = sz.h;
return existing;
}
DecodedTexture decoded;
if (_dats.Portal.TryGet<RenderSurface>(renderSurfaceId, out var rs)
|| _dats.HighRes.TryGet<RenderSurface>(renderSurfaceId, out rs))
{
decoded = SurfaceDecoder.DecodeRenderSurface(rs, palette: null);
}
else
{
decoded = DecodedTexture.Magenta;
}
uint h = UploadRgba8(decoded);
_handlesByRenderSurfaceId[renderSurfaceId] = h;
_rsSizeById[renderSurfaceId] = (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