diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 1bd52dd8..1270df39 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -22,6 +22,7 @@ #include "../Sync/GL_Sync.h" #include "../Debug/GL_Debug.h" #include +#include #include #define DECLARE_GL_FUNCTION_STUB_HEAD(type, name, ...) MOBILEGL_GL_API type gl##name(__VA_ARGS__) { @@ -127,6 +128,11 @@ MOBILEGL_GL_API void glFinish() { MobileGL::MG_Impl::EGLImpl::FlushSharedImageAccesses(); MobileGL::MG_Pipe::MGPipeClientFinish(); #endif + // Everything issued has completed, so a persistent read map now has to show the GPU's + // writes (GL 4.6 core 7.13). + if (MobileGL::MG_State::pGLContext != nullptr) { + MobileGL::MG_State::GLState::SyncPersistentReadMapsFromGpu(); + } } MOBILEGL_GL_API void glFlush() { MGLOG_D("Implementing function: %s(...)", __FUNCTION__); diff --git a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp index 97199d96..75cbe127 100644 --- a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp +++ b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp @@ -9,6 +9,7 @@ #include "GL_Sync.h" #include #include +#include #include // CONTRACT-P5.md ยง7 / ID-14: a null check on a GLFunctionsTable slot may not survive into the // client under split - it becomes a caps-mirror read. SlotCaps.h carries the rule and the test @@ -101,11 +102,18 @@ namespace MobileGL::MG_Impl::GLImpl { return GL_WAIT_FAILED; } const auto backendClientWaitSync = MG_Backend::gBackendFunctionsTable.GL.ClientWaitSync; - if (!backendClientWaitSync || !syncObject->backendHandle) { - return GL_ALREADY_SIGNALED; // legacy always-signaled fallback + GLenum result = GL_ALREADY_SIGNALED; // legacy always-signaled fallback + if (backendClientWaitSync && syncObject->backendHandle) { + MGP_FILL(ClientWaitSync); + result = backendClientWaitSync(syncObject->backendHandle, flags, timeout); } - MGP_FILL(ClientWaitSync); - return backendClientWaitSync(syncObject->backendHandle, flags, timeout); + // A satisfied wait is the point at which GPU writes issued before the fence become + // visible through a persistent read map (GL 4.6 core 7.13), and its pointer is the + // frontend shadow, which only an explicit pull refreshes. + if (result == GL_ALREADY_SIGNALED || result == GL_CONDITION_SATISFIED) { + MG_State::GLState::SyncPersistentReadMapsFromGpu(); + } + return result; } void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) { diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp index ef6d0367..b055286b 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #if MOBILEGL_BUILD_DISAGGREGATED // MG_State reaching into MG_Remote/Client is the layering ARCHITECTURE.md:575 names - the CLIENT @@ -64,6 +65,33 @@ namespace MobileGL::MG_State::GLState { } } + namespace { + // The live persistent READ maps, keyed on the never-reused lifetime id. Recursive + // because SyncGpuWrites runs under it and must stay free to reach map-state code. + std::recursive_mutex g_persistentReadMapsMutex; + UnorderedMap g_livePersistentReadMaps; + } // namespace + + void BufferObject::NotePersistentReadMapStateChanged() { + const Bool live = m_isMapped && (m_mappingAccess & BufferMappingAccessBit::Persistent) && + (m_mappingAccess & BufferMappingAccessBit::Read); + const std::lock_guard lock(g_persistentReadMapsMutex); + if (live) { + g_livePersistentReadMaps[m_lifetimeId] = this; + } else { + g_livePersistentReadMaps.erase(m_lifetimeId); + } + } + + void SyncPersistentReadMapsFromGpu() { + const std::lock_guard lock(g_persistentReadMapsMutex); + if (g_livePersistentReadMaps.empty()) return; + for (const auto& [lifetimeId, buffer] : g_livePersistentReadMaps) { + (void)lifetimeId; + buffer->SyncGpuWrites(); + } + } + Uint64 BufferObject::AllocateLifetimeId() { return g_nextBufferLifetimeId.fetch_add(1, std::memory_order_relaxed); } @@ -86,6 +114,10 @@ namespace MobileGL::MG_State::GLState { // outlives its object is the one failure this set must not have. Forget is a no-op // for a buffer that was never a member. MG_Record::PersistentMapTracker::Instance().Forget(*this); + { + const std::lock_guard lock(g_persistentReadMapsMutex); + g_livePersistentReadMaps.erase(m_lifetimeId); + } // P3a D-L: the buffer's death crosses as resource_destroy, which is the catalogue // call for it - no seventh NotifyStateObjectDestroyed raiser is added, because that // header exists for kinds that have no such call. The emit-then-free ORDER is fixed @@ -426,6 +458,7 @@ namespace MobileGL::MG_State::GLState { } void BufferObject::NotePersistentMapStateChanged() { + NotePersistentReadMapStateChanged(); if (!MG_Record::PersistentMapTracker::PushIsArmed()) return; MG_Record::PersistentMapTracker::Instance().NoteMapStateChanged(*this); // Teardown must retire membership, but only the client publishes live-host-write state. diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.h b/MobileGL/MG_State/GLState/BufferState/BufferObject.h index 57214eef..118d2267 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.h +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.h @@ -168,6 +168,10 @@ namespace MobileGL { // call, no serial and no epoch, and the applier's IsBufferDrawCleanByHandle can // no longer ask this object (there is no object on that side of a spawn). void NotePersistentMapStateChanged(); + // Keeps this buffer's membership in the live persistent READ map set current (see + // SyncPersistentReadMapsFromGpu). Called from NotePersistentMapStateChanged, so it + // rides every event that can move the predicate, on every transport. + void NotePersistentReadMapStateChanged(); // What MGPipeEmitResourceSubData and MGPipeEmitBufferSubDataResident write into // MGPSubData::HasLiveHostWrites. The PUBLISHED value, not the live predicate: the @@ -294,5 +298,14 @@ namespace MobileGL { SizeT m_stagingBias = 0; Bool m_ownsStagingData; }; + + // GL 4.6 core 6.2 / 7.13: GPU writes to a buffer become visible through a persistent + // READ map once the client has waited for them - glFinish, or a glClientWaitSync on a + // fence issued after them (coherent maps; a non-coherent map adds a + // CLIENT_MAPPED_BUFFER_BARRIER_BIT barrier before that fence). The pointer such a map + // hands out is the frontend shadow, which nothing else pulls the GPU's bytes into, so + // these two waits refresh every live persistent read map that has a GPU write + // outstanding. + void SyncPersistentReadMapsFromGpu(); } // namespace MG_State::GLState } // namespace MobileGL