[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 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 15:02:38 +08:00
co-authored by Claude Fable 5
parent b8ffd25148
commit f355080b6f
3 changed files with 63 additions and 7 deletions
@@ -3485,6 +3485,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false; return false;
} }
t_backendContextCurrent = true; 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; return true;
} }
@@ -3511,7 +3514,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
void DestroyEGLContext() { void DestroyEGLContext() {
BufferImpl::UnregisterBufferBackendOps(); BufferImpl::OnBackendContextDestroyed();
t_backendContextCurrent = false; t_backendContextCurrent = false;
if (g_Display != EGL_NO_DISPLAY) { if (g_Display != EGL_NO_DISPLAY) {
g_EGLFuncs.eglMakeCurrent(g_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); g_EGLFuncs.eglMakeCurrent(g_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+46 -5
View File
@@ -123,6 +123,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint g_boundArrayBufferId = 0; Uint g_boundArrayBufferId = 0;
Bool g_boundArrayBufferKnown = false; 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 // Resources whose owning BufferObject died; ids deleted at the next
// sync point with a current ES context. // sync point with a current ES context.
Vector<SharedPtr<BackendBufferResource>> g_deferredBufferReleases; Vector<SharedPtr<BackendBufferResource>> g_deferredBufferReleases;
@@ -133,7 +137,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
Bool CanTouchGLNow() { Bool CanTouchGLNow() {
return false && DirectGLES::IsBackendContextCurrentOnThisThread(); // BISECT-EXPERIMENT return DirectGLES::IsBackendContextCurrentOnThisThread();
} }
// (Re)specify backend storage from the shadow copy: glBufferData. // (Re)specify backend storage from the shadow copy: glBufferData.
@@ -151,6 +155,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
resource.storageInitialized = true; resource.storageInitialized = true;
resource.pendingRespecify = false; resource.pendingRespecify = false;
resource.pendingRanges.clear(); resource.pendingRanges.clear();
resource.syncedChangeSerial = bufferObject.GetChangeSerial();
} }
Bool StorageMatches(const GLESBufferResource& resource, const BufferObject& bufferObject) { Bool StorageMatches(const GLESBufferResource& resource, const BufferObject& bufferObject) {
@@ -171,7 +176,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
void Ops_Respecify(BufferObject& bufferObject) { void Ops_Respecify(BufferObject& bufferObject) {
auto* resource = ResourceOf(bufferObject); auto* resource = ResourceOf(bufferObject);
if (!resource) return; // lazy: EnsureBufferResource full-uploads on creation 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->pendingRespecify = true;
resource->pendingRanges.clear(); resource->pendingRanges.clear();
return; return;
@@ -190,11 +196,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto* resource = ResourceOf(bufferObject); auto* resource = ResourceOf(bufferObject);
if (!resource) return; if (!resource) return;
if (resource->pendingRespecify) return; // full re-upload pending anyway 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}); resource->pendingRanges.Add({offset, offset + size});
return; return;
} }
UploadRangeNow(*resource, bufferObject, offset, offset + size); UploadRangeNow(*resource, bufferObject, offset, offset + size);
resource->syncedChangeSerial = bufferObject.GetChangeSerial();
} }
void Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range, void Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range,
@@ -202,7 +211,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto* resource = ResourceOf(bufferObject); auto* resource = ResourceOf(bufferObject);
if (!resource) return; if (!resource) return;
if (resource->pendingRespecify) 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); resource->pendingRanges.Add(range);
return; return;
} }
@@ -227,17 +238,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
Memcpy(mappedData, bufferObject.GetDataReadOnly()->data() + range.start, Memcpy(mappedData, bufferObject.GetDataReadOnly()->data() + range.start,
range.end - range.start); range.end - range.start);
g_GLESFuncs.glUnmapBuffer(TempBufferTarget); g_GLESFuncs.glUnmapBuffer(TempBufferTarget);
resource->syncedChangeSerial = bufferObject.GetChangeSerial();
return; return;
} }
MGLOG_E("Failed to map buffer with ID: %u for flush, falling back to glBufferSubData", MGLOG_E("Failed to map buffer with ID: %u for flush, falling back to glBufferSubData",
resource->id); resource->id);
} }
UploadRangeNow(*resource, bufferObject, range.start, range.end); UploadRangeNow(*resource, bufferObject, range.start, range.end);
resource->syncedChangeSerial = bufferObject.GetChangeSerial();
} }
void Ops_OnDestroy(SharedPtr<BackendBufferResource>&& resource) { void Ops_OnDestroy(SharedPtr<BackendBufferResource>&& resource) {
if (!resource) return; if (!resource) return;
auto* glesResource = static_cast<GLESBufferResource*>(resource.get()); auto* glesResource = static_cast<GLESBufferResource*>(resource.get());
if (glesResource->contextGeneration != g_bufferContextGeneration) {
glesResource->id = 0; // id belonged to a destroyed context
return;
}
if (CanTouchGLNow()) { if (CanTouchGLNow()) {
if (glesResource->id != 0) { if (glesResource->id != 0) {
if (g_boundArrayBufferKnown && g_boundArrayBufferId == glesResource->id) { if (g_boundArrayBufferKnown && g_boundArrayBufferId == glesResource->id) {
@@ -274,6 +291,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_deferredBufferReleases.clear(); g_deferredBufferReleases.clear();
} }
void OnBackendContextDestroyed() {
UnregisterBufferBackendOps();
++g_bufferContextGeneration;
}
void ProcessDeferredBufferReleases() { void ProcessDeferredBufferReleases() {
if (!CanTouchGLNow()) return; if (!CanTouchGLNow()) return;
Vector<SharedPtr<BackendBufferResource>> releases; Vector<SharedPtr<BackendBufferResource>> releases;
@@ -283,6 +305,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
for (auto& resource : releases) { for (auto& resource : releases) {
auto* glesResource = static_cast<GLESBufferResource*>(resource.get()); auto* glesResource = static_cast<GLESBufferResource*>(resource.get());
if (glesResource->contextGeneration != g_bufferContextGeneration) {
glesResource->id = 0;
continue;
}
if (glesResource->id != 0) { if (glesResource->id != 0) {
if (g_boundArrayBufferKnown && g_boundArrayBufferId == glesResource->id) { if (g_boundArrayBufferKnown && g_boundArrayBufferId == glesResource->id) {
InvalidateArrayBufferBindingCache(); InvalidateArrayBufferBindingCache();
@@ -312,6 +338,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
bufferObject->SetBackendResource(std::move(newResource)); 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) { if (resource->id == 0) {
g_GLESFuncs.glGenBuffers(1, &resource->id); g_GLESFuncs.glGenBuffers(1, &resource->id);
if (resource->id == 0) { if (resource->id == 0) {
@@ -340,6 +376,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
UploadRangeNow(*resource, *bufferObject, std::min(range.start, end), end); UploadRangeNow(*resource, *bufferObject, std::min(range.start, end), end);
} }
resource->pendingRanges.clear(); 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; return resource;
} }
@@ -348,7 +389,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND); ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif #endif
if (false && target == GL_ARRAY_BUFFER) { // BISECT2: cache disabled if (target == GL_ARRAY_BUFFER) {
if (g_boundArrayBufferKnown && g_boundArrayBufferId == id) { if (g_boundArrayBufferKnown && g_boundArrayBufferId == id) {
return; return;
} }
+13 -1
View File
@@ -126,15 +126,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint id = 0; Uint id = 0;
SizeT storageSize = 0; SizeT storageSize = 0;
Bool storageInitialized = false; 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 // Ops that arrived while no ES context was current on the calling thread
// (or before storage existed); replayed by EnsureBufferResource. // (or before storage existed); replayed by EnsureBufferResource.
Bool pendingRespecify = false; Bool pendingRespecify = false;
VecRange1D pendingRanges; 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 RegisterBufferBackendOps();
void UnregisterBufferBackendOps(); 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 // Get-or-create the backend resource and bring its storage up to date
// (creates the GL buffer, replays pending ops, pushes persistent-mapped // (creates the GL buffer, replays pending ops, pushes persistent-mapped