[Refactor] (Espryt): answer a readback through the reverse channel and bump the mutation epoch after it, never before

This commit is contained in:
2026-09-08 04:52:15 -04:00
parent 4e44650199
commit ce24e2a734
3 changed files with 65 additions and 0 deletions
@@ -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<MG_State::GLState::TextureObjectBuffer*>(imageBinding.Texture.get());
const auto& bufferObject = textureBuffer->GetBufferBindingSlot().GetBoundObject();
#if MOBILEGL_PIPE_PUSH
BufferImpl::MarkBufferGpuWritten(bufferObject);
#else
if (bufferObject) bufferObject->MarkGpuWritten();
#endif
}
}
@@ -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<MG_State::GLState::BufferObject>& 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
+12
View File
@@ -720,6 +720,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool IsBufferDrawCleanByHandle(MG_Pipe::MGPipeHandle res, const GLESBufferResource* resource);
GLESBufferResource* EnsureBufferResourceForHandle(
const SharedPtr<MG_State::GLState::BufferObject>& 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<MG_State::GLState::BufferObject>& bufferObject);
#endif
// Registered as the frontend's BufferBackendOps at backend init and on