From f355080b6f668a92c74b8897315b15844a33fa77 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 8 Jul 2026 15:02:38 +0800 Subject: [PATCH] [Fix] (MG_Backend/DirectGLES): survive ES context recreation in buffer ops Track context generation + synced change serial per resource; re-register ops on MakeCurrent. Fixes frozen buffer contents after the trace replayer's probe context teardown. Co-Authored-By: Claude Fable 5 --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 5 +- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 51 +++++++++++++++++-- MobileGL/MG_Backend/DirectGLES/Managers.h | 14 ++++- 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 05f64ef5..5f7e27fc 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -3485,6 +3485,9 @@ namespace MobileGL::MG_Backend::DirectGLES { return false; } t_backendContextCurrent = true; + // The ops table may have been unregistered when a previous ES context was + // destroyed (e.g. a probe context); re-register now that GL is usable. + BufferImpl::RegisterBufferBackendOps(); return true; } @@ -3511,7 +3514,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } void DestroyEGLContext() { - BufferImpl::UnregisterBufferBackendOps(); + BufferImpl::OnBackendContextDestroyed(); t_backendContextCurrent = false; if (g_Display != EGL_NO_DISPLAY) { g_EGLFuncs.eglMakeCurrent(g_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 4bb02d43..3f041430 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -123,6 +123,10 @@ namespace MobileGL::MG_Backend::DirectGLES { Uint g_boundArrayBufferId = 0; Bool g_boundArrayBufferKnown = false; + // Bumped whenever the backend ES context is destroyed; resources with + // an older generation hold ids from a dead context. + Uint g_bufferContextGeneration = 1; + // Resources whose owning BufferObject died; ids deleted at the next // sync point with a current ES context. Vector> g_deferredBufferReleases; @@ -133,7 +137,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } Bool CanTouchGLNow() { - return false && DirectGLES::IsBackendContextCurrentOnThisThread(); // BISECT-EXPERIMENT + return DirectGLES::IsBackendContextCurrentOnThisThread(); } // (Re)specify backend storage from the shadow copy: glBufferData. @@ -151,6 +155,7 @@ namespace MobileGL::MG_Backend::DirectGLES { resource.storageInitialized = true; resource.pendingRespecify = false; resource.pendingRanges.clear(); + resource.syncedChangeSerial = bufferObject.GetChangeSerial(); } Bool StorageMatches(const GLESBufferResource& resource, const BufferObject& bufferObject) { @@ -171,7 +176,8 @@ namespace MobileGL::MG_Backend::DirectGLES { void Ops_Respecify(BufferObject& bufferObject) { auto* resource = ResourceOf(bufferObject); if (!resource) return; // lazy: EnsureBufferResource full-uploads on creation - if (!CanTouchGLNow() || resource->id == 0) { + if (!CanTouchGLNow() || resource->id == 0 || + resource->contextGeneration != g_bufferContextGeneration) { resource->pendingRespecify = true; resource->pendingRanges.clear(); return; @@ -190,11 +196,14 @@ namespace MobileGL::MG_Backend::DirectGLES { auto* resource = ResourceOf(bufferObject); if (!resource) return; if (resource->pendingRespecify) return; // full re-upload pending anyway - if (!CanTouchGLNow() || resource->id == 0 || !StorageMatches(*resource, bufferObject)) { + if (!CanTouchGLNow() || resource->id == 0 || + resource->contextGeneration != g_bufferContextGeneration || + !StorageMatches(*resource, bufferObject)) { resource->pendingRanges.Add({offset, offset + size}); return; } UploadRangeNow(*resource, bufferObject, offset, offset + size); + resource->syncedChangeSerial = bufferObject.GetChangeSerial(); } void Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range, @@ -202,7 +211,9 @@ namespace MobileGL::MG_Backend::DirectGLES { auto* resource = ResourceOf(bufferObject); if (!resource) return; if (resource->pendingRespecify) return; - if (!CanTouchGLNow() || resource->id == 0 || !StorageMatches(*resource, bufferObject)) { + if (!CanTouchGLNow() || resource->id == 0 || + resource->contextGeneration != g_bufferContextGeneration || + !StorageMatches(*resource, bufferObject)) { resource->pendingRanges.Add(range); return; } @@ -227,17 +238,23 @@ namespace MobileGL::MG_Backend::DirectGLES { Memcpy(mappedData, bufferObject.GetDataReadOnly()->data() + range.start, range.end - range.start); g_GLESFuncs.glUnmapBuffer(TempBufferTarget); + resource->syncedChangeSerial = bufferObject.GetChangeSerial(); return; } MGLOG_E("Failed to map buffer with ID: %u for flush, falling back to glBufferSubData", resource->id); } UploadRangeNow(*resource, bufferObject, range.start, range.end); + resource->syncedChangeSerial = bufferObject.GetChangeSerial(); } void Ops_OnDestroy(SharedPtr&& resource) { if (!resource) return; auto* glesResource = static_cast(resource.get()); + if (glesResource->contextGeneration != g_bufferContextGeneration) { + glesResource->id = 0; // id belonged to a destroyed context + return; + } if (CanTouchGLNow()) { if (glesResource->id != 0) { if (g_boundArrayBufferKnown && g_boundArrayBufferId == glesResource->id) { @@ -274,6 +291,11 @@ namespace MobileGL::MG_Backend::DirectGLES { g_deferredBufferReleases.clear(); } + void OnBackendContextDestroyed() { + UnregisterBufferBackendOps(); + ++g_bufferContextGeneration; + } + void ProcessDeferredBufferReleases() { if (!CanTouchGLNow()) return; Vector> releases; @@ -283,6 +305,10 @@ namespace MobileGL::MG_Backend::DirectGLES { } for (auto& resource : releases) { auto* glesResource = static_cast(resource.get()); + if (glesResource->contextGeneration != g_bufferContextGeneration) { + glesResource->id = 0; + continue; + } if (glesResource->id != 0) { if (g_boundArrayBufferKnown && g_boundArrayBufferId == glesResource->id) { InvalidateArrayBufferBindingCache(); @@ -312,6 +338,16 @@ namespace MobileGL::MG_Backend::DirectGLES { bufferObject->SetBackendResource(std::move(newResource)); } + if (resource->contextGeneration != g_bufferContextGeneration) { + // The id (if any) belonged to a destroyed ES context. + resource->id = 0; + resource->storageInitialized = false; + resource->storageSize = 0; + resource->pendingRespecify = true; + resource->pendingRanges.clear(); + resource->contextGeneration = g_bufferContextGeneration; + } + if (resource->id == 0) { g_GLESFuncs.glGenBuffers(1, &resource->id); if (resource->id == 0) { @@ -340,6 +376,11 @@ namespace MobileGL::MG_Backend::DirectGLES { UploadRangeNow(*resource, *bufferObject, std::min(range.start, end), end); } resource->pendingRanges.clear(); + resource->syncedChangeSerial = bufferObject->GetChangeSerial(); + } else if (resource->syncedChangeSerial != bufferObject->GetChangeSerial()) { + // Ops could not track some writes (e.g. the ops table was + // unregistered between contexts); re-upload everything. + RespecifyStorageNow(*resource, *bufferObject); } return resource; } @@ -348,7 +389,7 @@ namespace MobileGL::MG_Backend::DirectGLES { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif - if (false && target == GL_ARRAY_BUFFER) { // BISECT2: cache disabled + if (target == GL_ARRAY_BUFFER) { if (g_boundArrayBufferKnown && g_boundArrayBufferId == id) { return; } diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 736a2969..23235ba1 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -126,15 +126,27 @@ namespace MobileGL::MG_Backend::DirectGLES { Uint id = 0; SizeT storageSize = 0; Bool storageInitialized = false; + // ES context generation this resource's id belongs to; ids from a + // destroyed context are invalid and must not be deleted or reused. + Uint contextGeneration = 0; + // Frontend change serial the backend storage reflects. When immediate + // ops cannot run (ops unregistered, no current context), this lags and + // EnsureBufferResource falls back to a full re-upload. + Uint64 syncedChangeSerial = 0; // Ops that arrived while no ES context was current on the calling thread // (or before storage existed); replayed by EnsureBufferResource. Bool pendingRespecify = false; VecRange1D pendingRanges; }; - // Registered as the frontend's BufferBackendOps at backend init. + // Registered as the frontend's BufferBackendOps at backend init and on + // every MakeCurrent (the ES context can be destroyed and recreated, e.g. + // by the trace replayer's probe context). void RegisterBufferBackendOps(); void UnregisterBufferBackendOps(); + // The ES context died: unregister ops, invalidate all outstanding GL ids + // (they belonged to the dead context) and drop deferred deletes. + void OnBackendContextDestroyed(); // Get-or-create the backend resource and bring its storage up to date // (creates the GL buffer, replays pending ops, pushes persistent-mapped