From 149e26a79ae4e16a70f8ceb50d38f99a773fc230 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 6 Sep 2026 08:23:57 -0400 Subject: [PATCH] [Fix] (Espryt): do not return a reference through a null slot pointer, and stop a comment claiming a memo that is no longer there - SyncTextureObjectToBackend re-resolves its slot after the nested glTextureView sync. The assert that it is still there is right - the caller holds the frontend object, so nothing can reclaim its slot - but the function returns a REFERENCE, and in a release build the assert is gone and the deref is not. It now falls back to GetOrCreate and puts the twin the caller is about to use back. - ResolveVaoTwin documents why its raw twin pointer survives the whole draw on both arms instead of pointing at TwinLookupMemo, which the handle arm does not compile. --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 02e6401f..dc1e7c37 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1218,8 +1218,10 @@ namespace MobileGL::MG_Backend::DirectGLES { // the result to the buffer sync (resolved-buffers memo host), the VAO sync and // the draw-time bind, which each used to run their own registry Find. The raw // pointer stays valid for the whole draw: the frontend VAO is pinned by the - // context binding, and a live object's registry entry is never erased nor its - // twin replaced (see TwinLookupMemo's contract). + // context binding, and a live object's twin is never erased nor replaced - on the + // legacy arm that is TwinLookupMemo's contract, and on the {slot, gen} arm it is + // simply that nothing but the sweep frees a slot and the sweep only takes slots + // whose frontend object is already gone. BackendVertexArrayObject* ResolveVaoTwin(const SharedPtr& vao) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); @@ -1390,7 +1392,17 @@ namespace MobileGL::MG_Backend::DirectGLES { auto* slot = g_backendTextureObjects.Find(textureObject.get()); MOBILEGL_ASSERT(slot != nullptr && *slot != nullptr, "the texture twin resolved at entry is gone after its own sync"); - return *slot; + if (slot != nullptr && *slot != nullptr) { + return *slot; + } + // Cannot happen - the caller holds the frontend object, so its slot cannot be + // reclaimed underneath this call - but the return is a reference, and a null + // deref in a release build is a worse way to learn that than a re-created twin. + auto& repaired = g_backendTextureObjects.GetOrCreate(textureObject); + if (!repaired) { + repaired = backendObj; + } + return repaired; } #endif auto* refreshedSlot = g_backendTextureObjects.Find(textureObject.get());