diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 29380647..7b3157e2 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -612,7 +612,10 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool allClean = true; for (Uint i = 0; i < memo->count; ++i) { auto& entry = memo->entries[i]; - if (IsBufferDrawCleanByHandle(entry.handle, entry.resource)) continue; + // entry.frontend is the same object the legacy arm probes and is kept + // alive by the VAO attribute's SharedPtr for as long as this memo is + // valid; it answers the live-map question no record carries in P3a. + if (IsBufferDrawCleanByHandle(entry.handle, entry.resource, entry.frontend)) continue; allClean = false; entry.resource = EnsureBufferResource(currentVAOObject->GetAttribute(entry.attribIndex).Buffer); @@ -787,7 +790,8 @@ namespace MobileGL::MG_Backend::DirectGLES { if (memo && memo->iboHandle == iboHandle && memo->iboCleanEpoch == bufferEpoch) { // probed fully clean at this epoch; nothing can have dirtied it } else if (memo && memo->iboHandle == iboHandle && - IsBufferDrawCleanByHandle(iboHandle, memo->iboResource)) { + IsBufferDrawCleanByHandle(iboHandle, memo->iboResource, + possibleIBO.get())) { memo->iboCleanEpoch = bufferEpoch; } else { auto* resource = EnsureBufferResource(possibleIBO); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 9672bd53..3a7c1e67 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -2426,13 +2426,25 @@ namespace MobileGL::MG_Backend::DirectGLES { // identity the slot table's twin at this handle, not GetBackendResource() // size record.Desc.Width, not GetSize() // freshness record.Serial vs syncedChangeSerial, not GetChangeSerial() - // map state record.HasLiveHostWrites, not IsMapped() + // map state record.HasLiveHostWrites AND the frontend's IsMapped() - see below // the rest unchanged, and server-side to begin with // // HasLiveHostWrites is ALWAYS FALSE in P3a and is written by nobody; it is here so the // phase that pushes persistent-mapped host writes can set it with no new record kind, // and the assertion below is what stops that phase landing a silent semantic change. - Bool IsBufferDrawCleanByHandle(MG_Pipe::MGPipeHandle res, const GLESBufferResource* resource) { + // + // BECAUSE it is pinned false, it CANNOT stand in for the frontend's IsMapped() yet, and + // the probe still asks the object: an EMULATED (non-adopted) persistent map - under the + // 16 MiB adoption threshold, or with DisableLargeBufferAdoption, or with no + // EXT_buffer_storage - mutates the client's shadow with no call, no serial and no epoch, + // which is the entire reason the legacy probe asks a map question instead of a serial + // one. Answering only HasLiveHostWrites made such a buffer read draw-clean forever, so + // SyncPersistentMappedRange (D-N keeps it on the ensure path for all of P3a) was never + // reached again and the frame drew the last uploaded bytes with no diagnostic anywhere. + // The frontend read retires the moment P5 gives HasLiveHostWrites a producer, and it is + // the last frontend read in this function. + Bool IsBufferDrawCleanByHandle(MG_Pipe::MGPipeHandle res, const GLESBufferResource* resource, + const MG_State::GLState::BufferObject* frontend) { if (!resource) return false; const auto* twin = g_backendBufferResources.FindByHandle(res); if (twin == nullptr || twin->get() != resource) return false; @@ -2448,6 +2460,11 @@ namespace MobileGL::MG_Backend::DirectGLES { "MGPipeResourceRecord::HasLiveHostWrites is set, but P3a has no producer for it"); #endif if (record->HasLiveHostWrites) return false; + // The same question the legacy arm asks at this exact point in the order, for the + // reason written at the top of this function. A null object is the "no frontend to + // ask" case (nothing reaches this probe without one today) and is treated as "not + // mapped", which is what the record already says. + if (frontend != nullptr && frontend->IsMapped()) return false; if (resource->pendingRespecify || !resource->storageInitialized) return false; if (!resource->pendingRanges.empty()) return false; if (resource->storageSize != static_cast(record->Desc.Width)) return false; @@ -2458,7 +2475,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool IsBufferDrawClean(const MG_State::GLState::BufferObject* frontend, const GLESBufferResource* resource) { #if MOBILEGL_PIPE_PUSH if (ResourceSubsystemEnabled()) { - return IsBufferDrawCleanByHandle(HandleOfBuffer(frontend), resource); + return IsBufferDrawCleanByHandle(HandleOfBuffer(frontend), resource, frontend); } #endif // Identity first: a respecify path can hand the frontend a NEW resource; the diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index d7d90422..dd0562b9 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -721,7 +721,14 @@ namespace MobileGL::MG_Backend::DirectGLES { // asks the applier the same five questions IsBufferDrawClean asks the frontend object, // with identical semantics (D-A4); EnsureBufferResourceForHandle is the ensure path // driven by the applier's descriptor and the shadow base the call carried. - Bool IsBufferDrawCleanByHandle(MG_Pipe::MGPipeHandle res, const GLESBufferResource* resource); + // + // `frontend` supplies the ONE question the applier's record cannot answer in P3a: an + // emulated (non-adopted) persistent map is written through its pointer with no call, so + // MGPipeResourceRecord::HasLiveHostWrites - the field that will carry it - is pinned + // false and the probe still has to ask the object. It retires with P5. See the long note + // at the definition; passing null means "no live map", not "unknown". + Bool IsBufferDrawCleanByHandle(MG_Pipe::MGPipeHandle res, const GLESBufferResource* resource, + const MG_State::GLState::BufferObject* frontend); GLESBufferResource* EnsureBufferResourceForHandle( const SharedPtr& bufferObject, MG_Pipe::MGPipeHandle res);