From e9a5248972774414651bc907bc537d0eb0972eab Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 16 Jun 2026 22:18:20 +0200 Subject: [PATCH] fix(D.5.1): dispose IconComposer + RenderSurface GL handles (review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/AcDream.App/Rendering/TextureCache.cs | 29 +++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/AcDream.App/Rendering/TextureCache.cs b/src/AcDream.App/Rendering/TextureCache.cs index 0e7ebcea..250a69e4 100644 --- a/src/AcDream.App/Rendering/TextureCache.cs +++ b/src/AcDream.App/Rendering/TextureCache.cs @@ -37,6 +37,12 @@ public sealed unsafe class TextureCache : Wb.ITextureCachePerInstance, IDisposab private readonly Dictionary _handlesByRenderSurfaceId = new(); private readonly Dictionary _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 _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 } /// Uploads a raw RGBA8 byte array as a Texture2D. Used by - /// to upload CPU-composited icon layers. + /// to upload CPU-composited icon layers. + /// The returned handle is tracked in and deleted by + /// . Callers must NOT also store the handle in any of the + /// keyed caches — that would cause a double-delete on Dispose. 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(); } }