mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Remote] (Disaggregated): P5e vi - kDrawClientArrays on the wire, and the run-ahead refusal
- ReadDrawBindings answers ClientVertexArrays in the SAME single read of the bindings the
rest of the draw plan is made from: an enabled attribute with no buffer behind it, which is
exactly what EmitVertexBuffers publishes as Res == kMGPipeNullHandle.
- PlanDrawInfo sets kDrawClientArrays for every draw shape, not only the array ones - a
glDrawElements can fetch its VERTICES from client memory too - because MGPipeBarriered is
computed from the record on both sides and a null Res inside the window is also what a
disabled attribute publishes.
- EmitDrawRecord and the reduced DrawArrays path refuse such a draw under run-ahead with
Fatal{UnmigratedVerb, "DrawArrays+CLIENT_ARRAYS"} on the GL thread, before the hooks
(ID-82 / ruling 2). LOCKSTEP IS UNCHANGED: the client is parked in WaitForApplied for that
record, so the server's upload reads memory that is not moving (ruling 4). Staging the
bytes as a per-attribute {BindingIndex, MGHostSpan} tail is P8's.
- The armed predicate is spelled here from the three inputs MGPipeTypes.h names for
RunAheadArmed() rather than called, because ClientSession::RunAheadArmed is package ra's;
it is inert until the integration commit publishes the caps bit, and ra collapses this
body into that latch.
This commit is contained in:
@@ -382,8 +382,39 @@ namespace MobileGL::MG_Remote::Client {
|
||||
nullptr, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
// P5e (vi), ID-82: declared here because the REDUCED DrawArrays path below predates the
|
||||
// nineteen-slot block that defines them, and it needs the same two answers. One
|
||||
// definition each, further down, beside the rest of the draw plan.
|
||||
[[noreturn]] void RefuseDrawByName(const char* slot, const char* qualifier);
|
||||
Bool RunAheadWouldSkipTheWait(ClientSession& session);
|
||||
|
||||
// Does the bound VAO fetch any ENABLED attribute out of the application's own memory.
|
||||
// The test is EmitVertexBuffers' own (`attrib.Enabled && !attrib.Buffer` is exactly what
|
||||
// it publishes as Res == kMGPipeNullHandle), so the flag on the wire and the record the
|
||||
// server applies agree by construction.
|
||||
Bool BoundVaoHasClientVertexArrays(MG_State::GLState::GLContext* ctx) {
|
||||
if (ctx == nullptr) return false;
|
||||
const auto& vao = ctx->GetBoundVertexArray();
|
||||
if (!vao) return false;
|
||||
const auto& attributes = vao->GetAllAttributes();
|
||||
for (SizeT i = 0; i < attributes.size(); ++i) {
|
||||
if (attributes[i].Enabled && !attributes[i].Buffer) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EmitDrawArrays(GLenum mode, GLint first, GLsizei count) {
|
||||
ClientSession& session = RequireSession("DrawArrays");
|
||||
|
||||
// P5e (vi): the same refusal the nineteen-slot block raises in EmitDrawRecord, and
|
||||
// this is the entry point the contract names by name - "DrawArrays+CLIENT_ARRAYS".
|
||||
// The probe runs only on this path's own draw, before the hooks, and it is inert
|
||||
// under lockstep (see RunAheadWouldSkipTheWait).
|
||||
const Bool clientArrays = BoundVaoHasClientVertexArrays(MG_State::pGLContext.get());
|
||||
if (clientArrays && RunAheadWouldSkipTheWait(session)) {
|
||||
RefuseDrawByName("DrawArrays", "CLIENT_ARRAYS");
|
||||
}
|
||||
|
||||
BeforeDrawVerb();
|
||||
|
||||
if (g_dropDrawEmission) {
|
||||
@@ -399,7 +430,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
MG_Pipe::MGPDrawInfo info{};
|
||||
info.Mode = static_cast<Uint32>(mode);
|
||||
info.IndexSize = 0; // arrays
|
||||
info.Flags = 0; // NO kDrawHasUserIndices: the reduced path draws from a VBO
|
||||
// NO kDrawHasUserIndices: the reduced path draws from a VBO. kDrawClientArrays IS
|
||||
// set when one is present, because the wait rule is computed from the record on
|
||||
// both sides and this path publishes its own record rather than PlanDrawInfo's.
|
||||
info.Flags = clientArrays ? static_cast<Uint8>(MG_Pipe::kDrawClientArrays) : 0;
|
||||
info.InstanceCount = 1;
|
||||
info.StartInstance = 0;
|
||||
info.RestartIndex = 0;
|
||||
@@ -446,6 +480,27 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// indices (or past 2^32 of them): the record spells Start in
|
||||
// indices, and rounding would draw from the wrong element.
|
||||
|
||||
// P5e (vi), ID-82 / CONTRACT-P5E §5.1: "would this session publish a draw and move on".
|
||||
//
|
||||
// IT IS THE CONJUNCTION MGPipeTypes.h STATES FOR RunAheadArmed(), spelled here rather
|
||||
// than called, and that is a SEAM, not a duplication: ClientSession::RunAheadArmed() is
|
||||
// package ra's to land (it latches the same three at the first caps adoption after Start
|
||||
// and may afterwards only turn OFF). Until it exists this is the same answer computed
|
||||
// from the same three inputs, and it is inert for the whole phase because the third one
|
||||
// - the server's kCapRunAheadApply - is not published until the integration commit
|
||||
// (kMGPipeP5eRunAheadReady). WHEN ra LANDS, THIS BODY BECOMES `session.RunAheadArmed()`:
|
||||
// one latch, one answer, and no way for the refusal to disagree with the wait rule about
|
||||
// which arm the session is on.
|
||||
Bool RunAheadWouldSkipTheWait(ClientSession& session) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
return session.BarrierArmed() && MG_Config::Ipc.RunAhead != 0 &&
|
||||
session.Caps().HasCap(MG_Pipe::kCapRunAheadApply);
|
||||
#else
|
||||
(void)session;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
[[noreturn]] void RefuseDrawByName(const char* slot, const char* qualifier) {
|
||||
char name[96];
|
||||
std::snprintf(name, sizeof(name), "%s+%s", slot, qualifier);
|
||||
@@ -465,6 +520,19 @@ namespace MobileGL::MG_Remote::Client {
|
||||
b.ElementBufferBound = true;
|
||||
b.ElementBuffer = MG_Pipe::MGPipeResourceTrackerInstance().Find(*bound);
|
||||
}
|
||||
// P5e (vi): the client-array probe, in the SAME single read of the bindings the
|
||||
// rest of the plan is made from rather than in a second walk at the refusal -
|
||||
// this runs on the GL thread once per draw and the refusal must not add its own
|
||||
// frontend pass. The test is the emitter's own: EmitVertexBuffers publishes
|
||||
// Res == kMGPipeNullHandle for exactly `attrib.Enabled && !attrib.Buffer`, so
|
||||
// the flag and the record agree by construction instead of by inspection.
|
||||
const auto& attributes = vao->GetAllAttributes();
|
||||
for (SizeT i = 0; i < attributes.size(); ++i) {
|
||||
if (attributes[i].Enabled && !attributes[i].Buffer) {
|
||||
b.ClientVertexArrays = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const auto& di = ctx->GetBufferBindingSlot(::MobileGL::BufferTarget::DrawIndirect).GetBoundObject()) {
|
||||
b.DrawIndirectBuffer = MG_Pipe::MGPipeResourceTrackerInstance().Find(*di);
|
||||
@@ -486,6 +554,24 @@ namespace MobileGL::MG_Remote::Client {
|
||||
const void* clientIndices, Uint64 clientIndexBytes,
|
||||
const MG_Pipe::MGPDrawIndirect* indirect) {
|
||||
ClientSession& session = RequireSession(slot);
|
||||
|
||||
// P5e (vi), ID-82 / ruling 2, BEFORE the pre-verb hooks and before the record: a
|
||||
// draw that fetches vertices out of the application's own memory has no wire form,
|
||||
// and under run-ahead the server would dereference a client pointer into memory
|
||||
// this thread is already past. It is refused BY NAME on the GL thread, in the
|
||||
// shape ID-57 gives every unmigrated draw shape, rather than rendered wrong (R-4).
|
||||
//
|
||||
// Under LOCKSTEP it is not refused and nothing here changes: the client is parked
|
||||
// in WaitForApplied for exactly this record, so the server's upload reads memory
|
||||
// that is not moving - which is ruling 4's "barriered records keep P5C's
|
||||
// semantics", and is why this is a run-ahead gate and not a transport gate.
|
||||
// Staging the bytes as a per-attribute {BindingIndex, MGHostSpan} tail, shaped like
|
||||
// kDrawHasUserIndices, is P8's and retires the refusal with it.
|
||||
if ((info.Flags & static_cast<Uint8>(MG_Pipe::kDrawClientArrays)) != 0 &&
|
||||
RunAheadWouldSkipTheWait(session)) {
|
||||
RefuseDrawByName(slot, "CLIENT_ARRAYS");
|
||||
}
|
||||
|
||||
BeforeDrawVerb();
|
||||
|
||||
if (g_dropDrawEmission) {
|
||||
@@ -1853,6 +1939,13 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// on its side). kDrawHasUserIndices / kDrawIsIndirect are added by the emission itself,
|
||||
// kDrawHasIndexRange by the two DrawRangeElements* callers.
|
||||
info.Flags = bindings.PrimitiveRestart ? static_cast<Uint8>(MG_Pipe::kDrawPrimitiveRestart) : 0;
|
||||
// P5e (vi), CONTRACT-P5E §2.1 (ii) / §5.1. On the wire BECAUSE the server needs it: the
|
||||
// barriered predicate is computed identically by both roles from the record alone, and
|
||||
// "does this draw fetch from client memory" is not derivable from anything else in it -
|
||||
// a null Res inside the vertex-buffer window is also what a DISABLED attribute below
|
||||
// the high-water mark publishes. Set for every draw shape, not just the array ones: a
|
||||
// glDrawElements can fetch its VERTICES from client memory too.
|
||||
if (bindings.ClientVertexArrays) info.Flags |= static_cast<Uint8>(MG_Pipe::kDrawClientArrays);
|
||||
// A negative count has already been refused by the frontend (INVALID_VALUE); 0 crosses
|
||||
// as 0 and draws nothing, which is what the driver does with it.
|
||||
info.InstanceCount = instanceCount > 0 ? static_cast<Uint32>(instanceCount) : 0u;
|
||||
|
||||
@@ -187,6 +187,16 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// carried verbatim (informational in P5b: the backend reads its own barrier-pulled copy).
|
||||
Bool PrimitiveRestart = false;
|
||||
Uint32 RestartIndex = 0;
|
||||
// P5e (vi), ID-82 / CONTRACT-P5E §5.1: TRUE when the bound VAO has at least one ENABLED
|
||||
// attribute with no buffer object behind it, i.e. an array whose vertices live in the
|
||||
// application's own memory. The server has no such memory: today it dereferences
|
||||
// `attrib.Offset` as a raw client pointer from the apply thread
|
||||
// (Managers.cpp's SyncClientSideAttributesForDrawArrays), which is legal only while the
|
||||
// client is parked behind the record. It rides in MGPDrawInfo::Flags as kDrawClientArrays
|
||||
// so BOTH roles can decide from the wire - the client refuses such a draw under
|
||||
// run-ahead, and MGPipeBarriered's escalation (ii) keeps it barriered if one ever
|
||||
// arrives anyway. Staging the bytes is P8's.
|
||||
Bool ClientVertexArrays = false;
|
||||
};
|
||||
|
||||
// 1 / 2 / 4 for the three GL index types, 0 for anything else (the frontend has already
|
||||
|
||||
Reference in New Issue
Block a user