diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 1946bd41..6c312f5a 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -497,7 +497,14 @@ namespace MobileGL::MG_Backend::DirectGLES { for (SizeT i = 0; i < bindingPointCnt; ++i) { const auto& obj = MGB_CTX->GetBufferBindingPoint(BufferTarget::ShaderStorage, i).GetBoundObject(); +#if MOBILEGL_PIPE_PUSH + // P3a (D-D): announced on the reverse channel on the handle arm, poked into + // the object on the legacy one. MarkBufferGpuWritten is the one place that + // decides, so the three announcement sites stay one line each. + MarkBufferGpuWritten(obj); +#else if (obj) obj->MarkGpuWritten(); +#endif } } @@ -541,7 +548,11 @@ namespace MobileGL::MG_Backend::DirectGLES { // conformance case reads the result back with glMapBufferRange or // glGetBufferSubData - which serve the frontend's CPU shadow until the buffer is // flagged (BufferObject::SyncGpuWrites), exactly as for a storage buffer. +#if MOBILEGL_PIPE_PUSH + MarkBufferGpuWritten(obj); +#else obj->MarkGpuWritten(); +#endif } } @@ -2009,7 +2020,11 @@ namespace MobileGL::MG_Backend::DirectGLES { auto* textureBuffer = static_cast(imageBinding.Texture.get()); const auto& bufferObject = textureBuffer->GetBufferBindingSlot().GetBoundObject(); +#if MOBILEGL_PIPE_PUSH + BufferImpl::MarkBufferGpuWritten(bufferObject); +#else if (bufferObject) bufferObject->MarkGpuWritten(); +#endif } } diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 3a80a4f8..e245373b 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -2002,6 +2002,17 @@ namespace MobileGL::MG_Backend::DirectGLES { // no longer reaches into the frontend's address space, it ANSWERS. In monolith // the client's implementation is one call away, so SyncGpuWrites' caller still // sees the reconciled shadow on return, exactly as before. + // + // THE ORDER FROM HERE IS A CORRECTNESS RULE, NOT A PREFERENCE + // (ARCHITECTURE.md 8.2: "写回的 epoch bump 必须在任何后续读该 handle 的命令之前被 + // server 应用;反向通道需要与正向通道相同的有序保证"): + // 1. writeback - the client's shadow takes the bytes, + // 2. unmap - the read mapping goes away, + // 3. serial stamp- syncedChangeSerial catches up with the record, so the next + // draw does not re-upload the readback over itself, + // 4. epoch bump - in Ops_H_ReadbackTracked, i.e. strictly AFTER 1-3, so a + // draw-clean memo re-probed by the bump can never observe a + // half-reconciled resource. The bump must NEVER move earlier. if (MG_Pipe::gMGPipeCallbacks.OnBufferWriteback != nullptr) { MG_Pipe::gMGPipeCallbacks.OnBufferWriteback( res, record.Offset, @@ -2230,6 +2241,33 @@ namespace MobileGL::MG_Backend::DirectGLES { return MG_Pipe::MGPipeSlots().FindByLifetimeId(MG_Pipe::MGPipeKind::Buffer, bufferObject->GetLifetimeId()); } + + void MarkBufferGpuWritten(const SharedPtr& bufferObject) { + if (!bufferObject) return; + if (!ResourceSubsystemEnabled()) { + bufferObject->MarkGpuWritten(); + return; + } + const MG_Pipe::MGPipeHandle res = HandleOfBuffer(bufferObject.get()); + if (MG_Pipe::MGPipeHandleIsNull(res) || MG_Pipe::gMGPipeCallbacks.OnGpuWritten == nullptr) { + // Deliberately NOT a fall-back to MarkGpuWritten: on this arm the resource + // family is switched over, and quietly reaching into the frontend object again + // would hide a missing handle or a missing reverse channel behind a picture + // that still looks right - which is what the subsystem A/B exists to expose. + MGLOG_E_ONCE("MGPipe: no reverse channel for the GPU-write announcement of buffer %u " + "(handle %s, OnGpuWritten %s)", + bufferObject->GetExternalIndex(), + MG_Pipe::MGPipeHandleIsNull(res) ? "missing" : "present", + MG_Pipe::gMGPipeCallbacks.OnGpuWritten == nullptr ? "unset" : "set"); + return; + } + // WHOLE RESOURCE, stated rather than implied: the extent is spelled as one range of + // kMGPipeWholeBuffer rather than as "zero ranges", because a zero count is the + // shape a fully NARROWED announcement will legitimately have once P8/P9 build the + // client's conservative set, and the two must not be the same record. + const MG_Pipe::MGPRange whole{0, MG_Pipe::kMGPipeWholeBuffer}; + MG_Pipe::gMGPipeCallbacks.OnGpuWritten(res, 1, &whole); + } #endif // See the declaration: re-mints of a live resource's driver id. Written only on diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 79d17ddc..5e473dca 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -720,6 +720,18 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool IsBufferDrawCleanByHandle(MG_Pipe::MGPipeHandle res, const GLESBufferResource* resource); GLESBufferResource* EnsureBufferResourceForHandle( const SharedPtr& bufferObject, MG_Pipe::MGPipeHandle res); + + // P3a (D-D): "the GPU wrote through this resource", announced on the reverse channel + // instead of poked into the frontend object. ARCHITECTURE.md calls OnGpuWritten a + // NARROWING channel - the client builds its pending set conservatively at each + // draw/dispatch emission point and this callback only ever takes entries out of it - + // so in P3a, where the client's conservative set is exactly what the three + // MarkGpuWritten sites marked, the announced set is the whole resource and the + // observable behaviour is identical. P8/P9 narrow it; the channel is what they need. + // + // The legacy arm keeps calling BufferObject::MarkGpuWritten directly, and the pull + // build never sees this function at all (G1). + void MarkBufferGpuWritten(const SharedPtr& bufferObject); #endif // Registered as the frontend's BufferBackendOps at backend init and on