fix(D.5.1): dispose IconComposer + RenderSurface GL handles (review)

Two GL texture leaks plugged, both found in code review of 6e82807:

1. _handlesByRenderSurfaceId (pre-existing gap): populated by
   GetOrUploadRenderSurface for UI sprites but absent from Dispose's
   Phase 3 sweep. Added foreach/_gl.DeleteTexture/Clear in Dispose.

2. _adhocHandles (new): the public UploadRgba8(byte[],int,int,bool)
   wrapper used by IconComposer stored composited icon handles nowhere,
   so they leaked. Added _adhocHandles list; wrapper now appends the
   returned GL name before returning. Dispose sweeps + clears the list.
   Tracking is intentionally in the PUBLIC wrapper only — the private
   UploadRgba8(DecodedTexture,bool) is shared by all keyed-cache paths
   and tracking there would cause double-deletes.

No behavior change to icon rendering. No GL-context unit test added
(no context in test projects); correctness is by-inspection + green
suite (2598 passing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-16 22:18:20 +02:00
parent 6e82807863
commit e9a5248972

View file

@ -37,6 +37,12 @@ public sealed unsafe class TextureCache : Wb.ITextureCachePerInstance, IDisposab
private readonly Dictionary<uint, uint> _handlesByRenderSurfaceId = new();
private readonly Dictionary<uint, (int w, int h)> _rsSizeById = new();
// Ad-hoc handles produced by the public UploadRgba8(byte[],int,int,bool) wrapper
// (used by IconComposer for composited item icons). These are NOT stored in any
// of the keyed caches above, so Dispose must sweep this list to avoid leaking
// GL texture objects until process exit.
private readonly List<uint> _adhocHandles = new();
private readonly Wb.BindlessSupport? _bindless;
// Bindless / Texture2DArray parallel caches. Keys mirror the legacy three
@ -543,9 +549,16 @@ public sealed unsafe class TextureCache : Wb.ITextureCachePerInstance, IDisposab
}
/// <summary>Uploads a raw RGBA8 byte array as a Texture2D. Used by
/// <see cref="AcDream.App.UI.IconComposer"/> to upload CPU-composited icon layers.</summary>
/// <see cref="AcDream.App.UI.IconComposer"/> to upload CPU-composited icon layers.
/// The returned handle is tracked in <see cref="_adhocHandles"/> and deleted by
/// <see cref="Dispose"/>. Callers must NOT also store the handle in any of the
/// keyed caches — that would cause a double-delete on Dispose.</summary>
public uint UploadRgba8(byte[] rgba, int width, int height, bool nearest = false)
=> UploadRgba8(new DecodedTexture(rgba, width, height), nearest);
{
uint h = UploadRgba8(new DecodedTexture(rgba, width, height), nearest);
_adhocHandles.Add(h);
return h;
}
private uint UploadRgba8(DecodedTexture decoded, bool nearest = false)
{
@ -656,5 +669,17 @@ public sealed unsafe class TextureCache : Wb.ITextureCachePerInstance, IDisposab
_gl.DeleteTexture(_magentaHandle);
_magentaHandle = 0;
}
// RenderSurface (UI sprite) handles — pre-existing gap: this dict was populated
// by GetOrUploadRenderSurface but was not swept here before this fix.
foreach (var h in _handlesByRenderSurfaceId.Values)
_gl.DeleteTexture(h);
_handlesByRenderSurfaceId.Clear();
// Ad-hoc handles from the public UploadRgba8(byte[],int,int,bool) wrapper
// (IconComposer composited icons). Not stored in any keyed cache.
foreach (var h in _adhocHandles)
_gl.DeleteTexture(h);
_adhocHandles.Clear();
}
}