mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
Pipe: fire-and-forget is async - mipmap, copies, XFB span and storage-block rows to kWaitNone, inproc creates and PBO readbacks stop waiting, and the default dual block lets a barriered verb cost one wait (FCL BSL: ~40 sync points/frame).
This commit is contained in:
+3
-2
@@ -740,8 +740,9 @@ namespace MobileGL::MG_Config {
|
||||
// selection folds to the single shared block) and forced off by MOBILEGL_PIPE_VERIFY
|
||||
// (the comparator owns the one fill block it compares against). 0 is not merely the
|
||||
// default, it is the negative control: the lane's distinctness case must go red
|
||||
// without it (P5F §6).
|
||||
Bool RoleSplitState = false;
|
||||
// without it (P5F §6). P15: the default is ON (unset = on, 0 = off); the negative control
|
||||
// sets 0 explicitly.
|
||||
Bool RoleSplitState = true;
|
||||
// MOBILEGL_IPC_AUDIT: after a record retires, the server fills the SEG_STAGE bytes
|
||||
// it referenced with 0xDD (R-2.5). This is the ONLY mechanical control that an
|
||||
// inproc implementation did not quietly keep using a pointer past its lifetime.
|
||||
|
||||
@@ -624,7 +624,11 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
// verify harness: the comparator's entry compare and compare-at-read hook are built on
|
||||
// there being ONE filled block (the hook pins itself to &gPipeInputs,
|
||||
// PipeFill.cpp's MGPipeVerifyReadHook), which is exactly what the rehearsal splits.
|
||||
ipc.RoleSplitState = QueryEnvFlag("MOBILEGL_IPC_ROLE_SPLIT_STATE");
|
||||
// P15: ON BY DEFAULT. Every split lane and the on-device retrace gate already ran with it,
|
||||
// and the dual-block census is empty (no apply reads a client-filled field), so the
|
||||
// shipped arm now matches what is tested - and with two blocks the client's fill is its
|
||||
// own and needs no applier quiesce. 0 still turns it off (the negative control does).
|
||||
ipc.RoleSplitState = QueryEnvQuirkOverride("MOBILEGL_IPC_ROLE_SPLIT_STATE") != MG_Config::QuirkOverride::ForceOff;
|
||||
if (MG_Config::Features.PipeVerify && ipc.RoleSplitState) {
|
||||
MGLOG_W("Config: MOBILEGL_IPC_ROLE_SPLIT_STATE=1 is incompatible with "
|
||||
"MOBILEGL_PIPE_VERIFY (the comparator owns the single fill block); "
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include <Config.h>
|
||||
#if MOBILEGL_PIPE_VERIFY
|
||||
#include <MG_Impl/Pipe/Verb/VerbPort.h>
|
||||
#include <MG_Impl/Pipe/Verb/GpuWriteSet.h>
|
||||
#endif
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
@@ -400,8 +401,31 @@ namespace MobileGL::MG_Pipe {
|
||||
// `ctx` MAY BE NULL: the validate point asks this question BEFORE it has decided
|
||||
// whether there is anything to fill. A null context has no open transform-feedback
|
||||
// span, so clause 2 is false for it - and the verb is a no-op below either way.
|
||||
// MGPipeValidateForVerbBarriered's switch: the next ClientVerbIsBarriered answers true.
|
||||
Bool g_forceBarrieredFill = false;
|
||||
|
||||
// P15: A PACK-BUFFER READBACK IS FIRE-AND-FORGET, decided per call. With a pack buffer
|
||||
// bound the readback emitters publish read_pixels_to_buffer / get_texture_image_to_buffer
|
||||
// (kWaitNone) instead of the reply form, and nothing is owed to the client at the call;
|
||||
// the predicate mirrors PackBufferReadbackTarget's conditions. The one condition it
|
||||
// cannot see (the byte range past 2 GiB) sends the emitter to the reply form, which then
|
||||
// re-validates barriered (MGPipeValidateForVerbBarriered) before it publishes.
|
||||
Bool PackReadbackGoesToBuffer(GLContext* ctx) {
|
||||
if (ctx == nullptr) return false;
|
||||
const auto& pbo = ctx->GetBufferBindingSlot(::MobileGL::BufferTarget::PixelPack).GetBoundObject();
|
||||
if (!pbo) return false;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Ipc.PboReadbackSync != 0) return false;
|
||||
#endif
|
||||
if (pbo->IsMapped() || !MG_Record::BufferWritebackIsReachable(*pbo)) return false;
|
||||
return !MGPipeHandleIsNull(MGPipeSlots().FindByLifetimeId(MGPipeKind::Buffer, pbo->GetLifetimeId()));
|
||||
}
|
||||
|
||||
Bool ClientVerbIsBarriered(MGPipeVerb verb, GLContext* ctx) {
|
||||
const MGPWireOp op = MGPipeRecordOpForVerb(verb);
|
||||
if (g_forceBarrieredFill) return true;
|
||||
MGPWireOp op = MGPipeRecordOpForVerb(verb);
|
||||
if (op == MGPWireOp::ReadPixels && PackReadbackGoesToBuffer(ctx)) op = MGPWireOp::ReadPixelsToBuffer;
|
||||
if (op == MGPWireOp::GetTextureImage && PackReadbackGoesToBuffer(ctx)) op = MGPWireOp::GetTextureImageToBuffer;
|
||||
// No record (answered from the caps mirror): no apply can read a fill for it.
|
||||
if (op == kMGPipeNoRecord) return false;
|
||||
if (MGPipeWaitClassFor(op) != kWaitNone) return true;
|
||||
@@ -441,6 +465,12 @@ namespace MobileGL::MG_Pipe {
|
||||
// call that returns, and the lockstep client's behaviour is unchanged byte for byte
|
||||
// (G1) - under lockstep appliedSeq is already at LastPublishedSeq by construction.
|
||||
void QuiesceApplierBeforeFill(const char* surface) {
|
||||
// P15: WITH TWO BLOCKS THE FILL IS THE CLIENT'S OWN. The applier reads the server
|
||||
// block, so a GL-thread write to the client block races nothing and needs no wait -
|
||||
// which is what makes one barriered verb cost one wait (its record's) instead of a
|
||||
// quiesce before the fill, another before the residual half and one per frontend
|
||||
// mutation after it (FCL BSL: 22 of them per frame).
|
||||
if (MGPipeBlocksAreDistinct()) return;
|
||||
MG_Remote::Client::ClientSession* session = MG_Remote::Client::ClientSession::Active();
|
||||
if (session == nullptr) return; // no session: this process has no applier to outrun
|
||||
session->WaitForApplyToCatchUp(surface);
|
||||
@@ -3397,6 +3427,12 @@ namespace MobileGL::MG_Pipe {
|
||||
field);
|
||||
}
|
||||
|
||||
void MGPipeValidateForVerbBarriered(MGPipeVerb verb) {
|
||||
g_forceBarrieredFill = true;
|
||||
MGPipeValidateForVerb(verb);
|
||||
g_forceBarrieredFill = false;
|
||||
}
|
||||
|
||||
// ---- the validate point (P2 brief D1) ----
|
||||
void MGPipeValidateForVerb(MGPipeVerb verb) {
|
||||
// P5f (f1): the fill side's one new spelling. With MOBILEGL_IPC_ROLE_SPLIT_STATE=1 this
|
||||
|
||||
@@ -33,6 +33,11 @@ namespace MobileGL::MG_Pipe {
|
||||
// entry points with one call site per verb instead of nine.
|
||||
void MGPipeValidateForVerb(MGPipeVerb verb);
|
||||
|
||||
// P15: the validate point again for a verb whose first validate answered UNBARRIERED for a
|
||||
// per-call reason that did not hold at emission (a pack-buffer readback that falls back to
|
||||
// the reply form). Runs the barriered fill: quiesce, then fill, exactly as a barriered verb.
|
||||
void MGPipeValidateForVerbBarriered(MGPipeVerb verb);
|
||||
|
||||
// Ends the verb in flight without starting another: bumps the serial, so every field the
|
||||
// verb stamped goes stale, and puts the current verb back to "none", so a read made after
|
||||
// it aborts as Fatal{UnmigratedPipeInput, "<Field>@<none>"} - which is what such a read
|
||||
|
||||
@@ -133,6 +133,9 @@
|
||||
sizeof(record), nullptr, 0, nullptr, 0, nullptr);
|
||||
return;
|
||||
}
|
||||
// P15: the validate point answered "pack buffer, fire-and-forget" and the reply
|
||||
// form is taken after all, so the barriered fill that form needs runs now.
|
||||
MG_Pipe::MGPipeValidateForVerbBarriered(MG_Pipe::MGPipeVerb::GetTextureImage);
|
||||
}
|
||||
Vector<Uint8> bytes(static_cast<SizeT>(tight));
|
||||
const Uint64 capacity = session.MaxReplyBytes();
|
||||
|
||||
@@ -1199,6 +1199,9 @@ namespace MobileGL::MG_Record {
|
||||
});
|
||||
return;
|
||||
}
|
||||
// P15: the validate point answered "pack buffer, fire-and-forget" and the reply
|
||||
// form is taken after all, so the barriered fill that form needs runs now.
|
||||
MG_Pipe::MGPipeValidateForVerbBarriered(MG_Pipe::MGPipeVerb::ReadPixels);
|
||||
}
|
||||
|
||||
// THE COMMON CASE KEEPS THE ZERO-COPY. A neutral pack state means the destination
|
||||
|
||||
@@ -292,6 +292,9 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; }
|
||||
glBindTexture(GL_TEXTURE_2D, m_texture);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
ASSERT_EQ(FirstGLError(), 0u) << "glGenerateMipmap";
|
||||
// generate_mipmap is fire-and-forget (P15): the server applies it after this
|
||||
// returns, so the server log this case reads is complete only after a finish.
|
||||
glFinish();
|
||||
}
|
||||
|
||||
// Under the knob the SERVER must say it filtered on the CPU; without that line the entry
|
||||
|
||||
@@ -466,6 +466,10 @@ void main() { oColor = vec4(vColor, 1.0); }
|
||||
}
|
||||
|
||||
Gl().EndFrame(); // close the setup window; SetUp's own store and map go in it
|
||||
// The window is emitted where the SERVER applies that present, and the client runs ahead
|
||||
// of it (a buffer create no longer waits since P15): without this the counted window's
|
||||
// map can be tallied before the setup window closes and land in it.
|
||||
glFinish();
|
||||
unsigned long long acquisitionsBefore = 0, pushedBefore = 0;
|
||||
const bool separateClientStats = PeekSeparateClientMapStats(&acquisitionsBefore, &pushedBefore);
|
||||
|
||||
|
||||
@@ -265,8 +265,8 @@
|
||||
/* (kBlitOrCopy), same shape, a wait that is already carried in this lane. THE COST IS */ \
|
||||
/* MEASURED, NOT ASSUMED: glCopyImageSubData is zero per frame in the Minecraft 26.3-rc-3 */ \
|
||||
/* scene (~849 draws/frame), so the wait is not on any per-frame path. */ \
|
||||
X(ResourceCopyRegion, MGPCopyRegion, kCtxObject, kNone, kWaitApplied) \
|
||||
X(GenerateMipmap, MGPMipPlan, kCtxObject, kNone, kWaitApplied) \
|
||||
X(ResourceCopyRegion, MGPCopyRegion, kCtxObject, kNone, kWaitNone) \
|
||||
X(GenerateMipmap, MGPMipPlan, kCtxObject, kNone, kWaitNone) \
|
||||
X(GetTextureImage, MGPReadbackInfo, kCtxObject, kReplySlot, kWaitReply) \
|
||||
/* ---- context: transfer calls that read whole-context state, and the commands ---- */ \
|
||||
X(Blit, MGPBlit, kCtxVerb, kNone, kWaitNone) \
|
||||
@@ -275,10 +275,10 @@
|
||||
X(DrawVbo, MGPDrawInfo, kCtxVerb, kHostSpan|kVarTail, kWaitNone) \
|
||||
X(LaunchGrid, MGPGridInfo, kCtxVerb, kNone, kWaitNone) \
|
||||
X(MemoryBarrier, MGPMemoryBarrier, kCtxVerb, kNone, kWaitNone) \
|
||||
X(BeginStreamOutput, MGPStreamOutputBegin, kCtxVerb, kNone, kWaitApplied) \
|
||||
X(EndStreamOutput, MGPXfbAccounting, kCtxVerb, kNone, kWaitApplied) \
|
||||
X(PauseStreamOutput, MGPStreamOutputControl, kCtxVerb, kNone, kWaitApplied) \
|
||||
X(ResumeStreamOutput, MGPStreamOutputControl, kCtxVerb, kNone, kWaitApplied) \
|
||||
X(BeginStreamOutput, MGPStreamOutputBegin, kCtxVerb, kNone, kWaitNone) \
|
||||
X(EndStreamOutput, MGPXfbAccounting, kCtxVerb, kNone, kWaitNone) \
|
||||
X(PauseStreamOutput, MGPStreamOutputControl, kCtxVerb, kNone, kWaitNone) \
|
||||
X(ResumeStreamOutput, MGPStreamOutputControl, kCtxVerb, kNone, kWaitNone) \
|
||||
X(Flush, MGPFlush, kCtxVerb, kNone, kWaitNone) \
|
||||
X(Present, MGPPresent, kCtxVerb, kNone, kWaitPresent) \
|
||||
X(SetSwapInterval, MGPSwapInterval, kCtxVerb, kOptional, kWaitNone) \
|
||||
@@ -305,13 +305,13 @@
|
||||
X(PatchParameter, MGPPatchParameter, kCtxVerb, kNone, kWaitNone) \
|
||||
/* glBindTransformFeedback - the driver object rebind and the capture-binding shadow */ \
|
||||
/* invalidation happen at the call; the XFB namespace has no wire lifetime. */ \
|
||||
X(BindStreamOutput, MGPStreamOutputBind, kCtxVerb, kNone, kWaitApplied) \
|
||||
X(BindStreamOutput, MGPStreamOutputBind, kCtxVerb, kNone, kWaitNone) \
|
||||
/* glShaderStorageBlockBinding - the block NAME is the coordinate all three sides share */ \
|
||||
/* (BackendObject.h), and a name is bytes: kHasBlob, staged whole in SEG_STAGE, NUL included. */ \
|
||||
X(SetStorageBlockBinding, MGPStorageBlockBinding, kCtxVerb, kHasBlob, kWaitApplied) \
|
||||
X(SetStorageBlockBinding, MGPStorageBlockBinding, kCtxVerb, kHasBlob, kWaitNone) \
|
||||
/* glCopyTexImage2D / glCopyTexSubImage2D - a copy whose SOURCE is the read framebuffer, */ \
|
||||
/* which resource_copy_region (resource -> resource) cannot express. */ \
|
||||
X(CopyFramebufferToTexture, MGPCopyFromFramebuffer, kCtxVerb, kNone, kWaitApplied) \
|
||||
X(CopyFramebufferToTexture, MGPCopyFromFramebuffer, kCtxVerb, kNone, kWaitNone) \
|
||||
/* ---- APPENDED BY P5c (MG_Remote/CONTRACT-P5C.md §5): the two control records. Opcodes ---- */ \
|
||||
/* ---- 77..78. Neither has an MGPipeApply* entry point and neither gains one: both reach ---- */ \
|
||||
/* ---- WireVerbSink, and under monolith neither has a producer - the reset is a direct ---- */ \
|
||||
|
||||
@@ -443,8 +443,8 @@ inline constexpr Uint8 kMGPipeWaitClasses[static_cast<SizeT>(MGPWireOp::kOpCount
|
||||
/* 50 ResourceSubDataComplete */ static_cast<Uint8>(kWaitNone),
|
||||
/* 51 ResourceFlushRange */ static_cast<Uint8>(kWaitNone),
|
||||
/* 52 ResourceReadback */ static_cast<Uint8>(kWaitReply),
|
||||
/* 53 ResourceCopyRegion */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 54 GenerateMipmap */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 53 ResourceCopyRegion */ static_cast<Uint8>(kWaitNone),
|
||||
/* 54 GenerateMipmap */ static_cast<Uint8>(kWaitNone),
|
||||
/* 55 GetTextureImage */ static_cast<Uint8>(kWaitReply),
|
||||
/* 56 Blit */ static_cast<Uint8>(kWaitNone),
|
||||
/* 57 Clear */ static_cast<Uint8>(kWaitNone),
|
||||
@@ -452,10 +452,10 @@ inline constexpr Uint8 kMGPipeWaitClasses[static_cast<SizeT>(MGPWireOp::kOpCount
|
||||
/* 59 DrawVbo */ static_cast<Uint8>(kWaitNone),
|
||||
/* 60 LaunchGrid */ static_cast<Uint8>(kWaitNone),
|
||||
/* 61 MemoryBarrier */ static_cast<Uint8>(kWaitNone),
|
||||
/* 62 BeginStreamOutput */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 63 EndStreamOutput */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 64 PauseStreamOutput */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 65 ResumeStreamOutput */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 62 BeginStreamOutput */ static_cast<Uint8>(kWaitNone),
|
||||
/* 63 EndStreamOutput */ static_cast<Uint8>(kWaitNone),
|
||||
/* 64 PauseStreamOutput */ static_cast<Uint8>(kWaitNone),
|
||||
/* 65 ResumeStreamOutput */ static_cast<Uint8>(kWaitNone),
|
||||
/* 66 Flush */ static_cast<Uint8>(kWaitNone),
|
||||
/* 67 Present */ static_cast<Uint8>(kWaitPresent),
|
||||
/* 68 SetSwapInterval */ static_cast<Uint8>(kWaitNone),
|
||||
@@ -464,9 +464,9 @@ inline constexpr Uint8 kMGPipeWaitClasses[static_cast<SizeT>(MGPWireOp::kOpCount
|
||||
/* 71 FenceWaitServer */ static_cast<Uint8>(kWaitNone),
|
||||
/* 72 BindShaderImage */ static_cast<Uint8>(kWaitNone),
|
||||
/* 73 PatchParameter */ static_cast<Uint8>(kWaitNone),
|
||||
/* 74 BindStreamOutput */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 75 SetStorageBlockBinding */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 76 CopyFramebufferToTexture*/ static_cast<Uint8>(kWaitApplied),
|
||||
/* 74 BindStreamOutput */ static_cast<Uint8>(kWaitNone),
|
||||
/* 75 SetStorageBlockBinding */ static_cast<Uint8>(kWaitNone),
|
||||
/* 76 CopyFramebufferToTexture*/ static_cast<Uint8>(kWaitNone),
|
||||
/* 77 ApplierReset */ static_cast<Uint8>(kWaitApplied),
|
||||
/* 78 ObjectDeath */ static_cast<Uint8>(kWaitNone),
|
||||
/* 79 SetContextValues */ static_cast<Uint8>(kWaitNone),
|
||||
|
||||
@@ -743,8 +743,8 @@ inline constexpr WireCallLayout kMGPipeWireCallLayouts[] = {
|
||||
{"ResourceSubDataComplete", "MGPSubDataComplete", static_cast<Uint64>(MGPWireOp::ResourceSubDataComplete), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"ResourceFlushRange", "MGPFlushRange", static_cast<Uint64>(MGPWireOp::ResourceFlushRange), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"ResourceReadback", "MGPReadback", static_cast<Uint64>(MGPWireOp::ResourceReadback), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"ResourceCopyRegion", "MGPCopyRegion", static_cast<Uint64>(MGPWireOp::ResourceCopyRegion), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"GenerateMipmap", "MGPMipPlan", static_cast<Uint64>(MGPWireOp::GenerateMipmap), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"ResourceCopyRegion", "MGPCopyRegion", static_cast<Uint64>(MGPWireOp::ResourceCopyRegion), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"GenerateMipmap", "MGPMipPlan", static_cast<Uint64>(MGPWireOp::GenerateMipmap), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"GetTextureImage", "MGPReadbackInfo", static_cast<Uint64>(MGPWireOp::GetTextureImage), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"Blit", "MGPBlit", static_cast<Uint64>(MGPWireOp::Blit), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"Clear", "MGPClear", static_cast<Uint64>(MGPWireOp::Clear), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
@@ -752,10 +752,10 @@ inline constexpr WireCallLayout kMGPipeWireCallLayouts[] = {
|
||||
{"DrawVbo", "MGPDrawInfo", static_cast<Uint64>(MGPWireOp::DrawVbo), static_cast<Uint64>(kHostSpan | kVarTail), static_cast<Uint64>(kWaitNone)},
|
||||
{"LaunchGrid", "MGPGridInfo", static_cast<Uint64>(MGPWireOp::LaunchGrid), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"MemoryBarrier", "MGPMemoryBarrier", static_cast<Uint64>(MGPWireOp::MemoryBarrier), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"BeginStreamOutput", "MGPStreamOutputBegin", static_cast<Uint64>(MGPWireOp::BeginStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"EndStreamOutput", "MGPXfbAccounting", static_cast<Uint64>(MGPWireOp::EndStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"PauseStreamOutput", "MGPStreamOutputControl", static_cast<Uint64>(MGPWireOp::PauseStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"ResumeStreamOutput", "MGPStreamOutputControl", static_cast<Uint64>(MGPWireOp::ResumeStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"BeginStreamOutput", "MGPStreamOutputBegin", static_cast<Uint64>(MGPWireOp::BeginStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"EndStreamOutput", "MGPXfbAccounting", static_cast<Uint64>(MGPWireOp::EndStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"PauseStreamOutput", "MGPStreamOutputControl", static_cast<Uint64>(MGPWireOp::PauseStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"ResumeStreamOutput", "MGPStreamOutputControl", static_cast<Uint64>(MGPWireOp::ResumeStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"Flush", "MGPFlush", static_cast<Uint64>(MGPWireOp::Flush), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"Present", "MGPPresent", static_cast<Uint64>(MGPWireOp::Present), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitPresent)},
|
||||
{"SetSwapInterval", "MGPSwapInterval", static_cast<Uint64>(MGPWireOp::SetSwapInterval), static_cast<Uint64>(kOptional), static_cast<Uint64>(kWaitNone)},
|
||||
@@ -764,9 +764,9 @@ inline constexpr WireCallLayout kMGPipeWireCallLayouts[] = {
|
||||
{"FenceWaitServer", "MGPFenceWait", static_cast<Uint64>(MGPWireOp::FenceWaitServer), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"BindShaderImage", "MGPImageBind", static_cast<Uint64>(MGPWireOp::BindShaderImage), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"PatchParameter", "MGPPatchParameter", static_cast<Uint64>(MGPWireOp::PatchParameter), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"BindStreamOutput", "MGPStreamOutputBind", static_cast<Uint64>(MGPWireOp::BindStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"SetStorageBlockBinding", "MGPStorageBlockBinding", static_cast<Uint64>(MGPWireOp::SetStorageBlockBinding), static_cast<Uint64>(kHasBlob), static_cast<Uint64>(kWaitApplied)},
|
||||
{"CopyFramebufferToTexture", "MGPCopyFromFramebuffer", static_cast<Uint64>(MGPWireOp::CopyFramebufferToTexture), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"BindStreamOutput", "MGPStreamOutputBind", static_cast<Uint64>(MGPWireOp::BindStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetStorageBlockBinding", "MGPStorageBlockBinding", static_cast<Uint64>(MGPWireOp::SetStorageBlockBinding), static_cast<Uint64>(kHasBlob), static_cast<Uint64>(kWaitNone)},
|
||||
{"CopyFramebufferToTexture", "MGPCopyFromFramebuffer", static_cast<Uint64>(MGPWireOp::CopyFramebufferToTexture), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"ApplierReset", "MGPApplierReset", static_cast<Uint64>(MGPWireOp::ApplierReset), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"ObjectDeath", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::ObjectDeath), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetContextValues", "MGPContextValues", static_cast<Uint64>(MGPWireOp::SetContextValues), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
|
||||
@@ -2408,6 +2408,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return seq;
|
||||
}
|
||||
|
||||
++m_recordWaits;
|
||||
if (MG_Config::Ipc.WaitStats && RunAheadArmed()) ++g_waitCensus.recordWaits[static_cast<SizeT>(op)];
|
||||
const BarrierWaitScope waiting;
|
||||
const Uint64 waitBudgetMs = AppliedWaitBudgetMs(op, payload);
|
||||
@@ -2793,6 +2794,9 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// is exactly "no record is being applied" and not merely "not decoding".
|
||||
if (isBarrieredFill && !ApplyThreadIsInsideApplier()) return;
|
||||
if (Server::ServerLoop::OnApplyThread()) return; // the applier owns the block
|
||||
// P15: with two blocks the GL thread's fill writes the client block, which the
|
||||
// applier never reads (the dual-block census is empty), so there is nothing to race.
|
||||
if (isBarrieredFill && MG_Pipe::MGPipeBlocksAreDistinct()) return;
|
||||
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"gPipeInputs\"} - the GL thread touched "
|
||||
"gPipeInputs (%s) on a RUN-AHEAD session %s. With the client running ahead "
|
||||
"the block is the server's to read for as long as ANY record is in flight "
|
||||
|
||||
@@ -400,6 +400,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// is never the frame's critical path; a number close to the frame count means the
|
||||
// credit is what paces this workload, which is what the device exit measures.
|
||||
Uint64 PresentCreditWaits() const;
|
||||
// P15: how many records this session waited on (applied or reply) after publishing.
|
||||
Uint64 RecordWaits() const { return m_recordWaits; }
|
||||
|
||||
// §2.4, called by EmitPresent immediately before it encodes: pay the credit (parking
|
||||
// if this client already has `MOBILEGL_IPC_PRESENT_CREDIT` presents in flight), then
|
||||
@@ -630,6 +632,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
Uint32 m_pendingDamageCount = 0;
|
||||
Int32 m_pendingDamage[MG_Pipe::kMGPMaxDamageRects * 4] = {};
|
||||
Uint64 m_presentCreditWaits = 0;
|
||||
Uint64 m_recordWaits = 0;
|
||||
|
||||
// sm: the spawn client owns ONE end and two bells. Held here rather
|
||||
// than in the transport because which bell is "mine" is the session's
|
||||
|
||||
@@ -643,6 +643,28 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// (PipeApply.h:298-304), so nobody re-sends those texels. So it takes the blocking
|
||||
// path instead, which is the pre-window shape exactly.
|
||||
const Uint32 window = session.LinkRetainsReplies() ? CreateWindowEffective() : 1u;
|
||||
// P15: UNDER RUN-AHEAD A CREATE IS FIRE-AND-FORGET. The handle is the client's own
|
||||
// allocation, and the server's accept is derivable: it declines only a reserved or
|
||||
// out-of-table slot or a target with no table (all protocol corruption, trip-wired on
|
||||
// the server) or a texture family with no consumer, which the client already knows
|
||||
// and never emits for (P4aFamilyHasItsConsumer). Waiting for that answer was a
|
||||
// blocking round trip per new object (FCL + BSL: 2-3 per frame).
|
||||
// A link that retains answers (the stream arm) keeps its create window, which already
|
||||
// defers the answer and reads it later; this is the arm without one (the shared pool).
|
||||
if (window <= 1 && session.RunAheadArmed() && !CreateIsSuspect(payload->Resource)) {
|
||||
Int32 status = Wire::ReplySink::kStatusError;
|
||||
const Uint64 seq = session.EmitAndWaitTails(
|
||||
MGPWireOp::ResourceCreate, payload, sizeof(*payload), nullptr, 0, nullptr, 0,
|
||||
&status, nullptr, /*wantReply=*/false, /*willReadReply=*/false);
|
||||
if (seq != Wire::kInvalidSeq) reply->Id = seq;
|
||||
MG_Pipe::MGPipePostReply(*reply,
|
||||
seq == Wire::kInvalidSeq ? Wire::ReplySink::kStatusDeclined
|
||||
: Wire::ReplySink::kStatusOk,
|
||||
seq == Wire::kInvalidSeq ? 0u : 1u);
|
||||
++g_emitted;
|
||||
if (seq == Wire::kInvalidSeq) ++g_declined;
|
||||
return;
|
||||
}
|
||||
// THE WINDOW OFF RESTORES THE OLD SHAPE EXACTLY, and is the negative control for it.
|
||||
// THE WINDOW OFF, THE SUSPECT LIST FULL, OR AN OBJECT THAT HAS ALREADY BEEN REFUSED:
|
||||
// all four take the blocking branch, which is the pre-window shape - and on the device
|
||||
|
||||
@@ -769,28 +769,16 @@ TEST(PipeCatalogue, EveryRowCarriesTheWaitClassTheContractGivesIt) {
|
||||
<< WireOpNameForDiag(op);
|
||||
}
|
||||
|
||||
// kWaitApplied: the rows whose apply STILL reads a BARRIER_PULLED row or probes the client
|
||||
// allocator, so the client parks in its own wait and CONTRACT-P5C's semantics hold for them
|
||||
// unchanged - their pulls count into rsp and their probes are legal inside a scope. Two of
|
||||
// them are trailing items and are expected to leave this list in a later package:
|
||||
// generate_mipmap the moment tx2 resolves the texture from VerbMipRes instead of the active
|
||||
// unit, and set_storage_block_binding the moment pg resolves it through
|
||||
// MGPStorageBlockBinding::ShaderCso. When they do, THIS list is what has to be edited.
|
||||
// kWaitApplied: only applier_reset is left. P15 (user decision: everything fire-and-forget
|
||||
// is async) moved generate_mipmap, resource_copy_region, copy_framebuffer_to_texture,
|
||||
// set_storage_block_binding and the five stream-output rows to kWaitNone: their applies
|
||||
// resolve everything from the record's own handles (VerbMipRes, VerbCopyTexDst, the copy's
|
||||
// endpoints, the storage-block program CSO, the P5f server-side span), and the dual-block
|
||||
// census - where any client-filled read is a named Fatal - is empty. applier_reset keeps its
|
||||
// wait: it is once per make-current and its apply probes the client allocator, which is only
|
||||
// legal inside a parked scope.
|
||||
const MGPWireOp appliedRows[] = {
|
||||
MGPWireOp::ApplierReset, MGPWireOp::GenerateMipmap,
|
||||
MGPWireOp::SetStorageBlockBinding, MGPWireOp::BeginStreamOutput,
|
||||
MGPWireOp::EndStreamOutput, MGPWireOp::PauseStreamOutput,
|
||||
MGPWireOp::ResumeStreamOutput, MGPWireOp::BindStreamOutput,
|
||||
MGPWireOp::CopyFramebufferToTexture,
|
||||
// P5e (gl), ID-118. resource_copy_region JOINED THIS SET, and it is the retiring phase
|
||||
// of a field that put it here: its verb is CopyImageSubData, whose apply reads
|
||||
// GetTextureObject, and that row retires in P7. An unbarriered record reading client
|
||||
// memory is an unconditional Fatal, so leaving it kWaitNone would have meant a real
|
||||
// defect standing for two phases - or an exception hand-written into a DERIVED
|
||||
// allowlist, which is the same thing with a comment on it. It sits beside
|
||||
// copy_framebuffer_to_texture here for the reason it took that class: same verb class,
|
||||
// no reply slot, and zero calls per frame in the measured scene.
|
||||
MGPWireOp::ResourceCopyRegion,
|
||||
MGPWireOp::ApplierReset,
|
||||
};
|
||||
for (const MGPWireOp op : appliedRows) {
|
||||
EXPECT_EQ(MGPipeWaitClassFor(op), kWaitApplied) << WireOpNameForDiag(op);
|
||||
@@ -812,12 +800,17 @@ TEST(PipeCatalogue, EveryRowCarriesTheWaitClassTheContractGivesIt) {
|
||||
MGPWireOp::CreateShaderState, MGPWireOp::SetDrawProgram,
|
||||
// P9: the pack-buffer readbacks - the point of the two rows is that nothing waits.
|
||||
MGPWireOp::ReadPixelsToBuffer, MGPWireOp::GetTextureImageToBuffer,
|
||||
// P15: fire-and-forget.
|
||||
MGPWireOp::GenerateMipmap, MGPWireOp::ResourceCopyRegion, MGPWireOp::CopyFramebufferToTexture,
|
||||
MGPWireOp::SetStorageBlockBinding, MGPWireOp::BeginStreamOutput, MGPWireOp::EndStreamOutput,
|
||||
MGPWireOp::PauseStreamOutput, MGPWireOp::ResumeStreamOutput, MGPWireOp::BindStreamOutput,
|
||||
};
|
||||
for (const MGPWireOp op : noneRows) {
|
||||
EXPECT_EQ(MGPipeWaitClassFor(op), kWaitNone) << WireOpNameForDiag(op);
|
||||
}
|
||||
|
||||
// The partition, by count. 15 + 1 + 10 = 26 rows wait (shared_image made the reply rows 15); every other row of the catalogue does
|
||||
// The partition, by count. 15 + 1 + 1 = 17 rows wait (shared_image made the reply rows 15; P15
|
||||
// left one kWaitApplied row); every other row of the catalogue does
|
||||
// not. A row that changed class moves two of these numbers at once - which is why the
|
||||
// kWaitApplied count went 9 -> 10 and the kWaitNone offset 24 -> 25 in the SAME commit that
|
||||
// moved resource_copy_region (P5e gl, ID-118).
|
||||
@@ -832,9 +825,9 @@ TEST(PipeCatalogue, EveryRowCarriesTheWaitClassTheContractGivesIt) {
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(reply, 15u);
|
||||
EXPECT_EQ(applied, 10u);
|
||||
EXPECT_EQ(applied, 1u);
|
||||
EXPECT_EQ(present, 1u);
|
||||
EXPECT_EQ(none, static_cast<SizeT>(kMGPipeCallCount) - 26u);
|
||||
EXPECT_EQ(none, static_cast<SizeT>(kMGPipeCallCount) - 17u);
|
||||
EXPECT_EQ(other, 0u) << "a row carries the kWaitClassCount terminator as its class";
|
||||
|
||||
// And the reply half of the partition BOTH WAYS, over the whole catalogue: exactly the rows
|
||||
|
||||
@@ -16,7 +16,7 @@ struct E1BoundaryPeer : Codec::WireVerbSink {
|
||||
while (!bell.entered.load() && !clientReturned.load()) std::this_thread::yield();
|
||||
return true;
|
||||
}
|
||||
Bool OnGenerateMipmap(const MGPMipPlan&) override { return Apply(MGPWireOp::GenerateMipmap); }
|
||||
Bool OnApplierReset(const MGPApplierReset&) override { return Apply(MGPWireOp::ApplierReset); }
|
||||
Bool OnClear(const MGPClear&) override { return Apply(MGPWireOp::Clear); }
|
||||
void Install() {
|
||||
if (Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting([](void* self) {
|
||||
@@ -49,10 +49,11 @@ void CheckE1Boundary(Bool serverCap, MGPWireOp op, Bool expectWait, const char*
|
||||
peer.Install();
|
||||
const Uint64 before = session.Control()->submittedSeq.load();
|
||||
producer.*PeerMember(ProducerBellTag{}) = &bell;
|
||||
const MGPMipPlan mip{};
|
||||
const MGPApplierReset reset{};
|
||||
const MGPClear clear{};
|
||||
const Uint64 seq = op == MGPWireOp::GenerateMipmap
|
||||
? session.EmitAndWait(op, &mip, sizeof(mip), nullptr, 0, nullptr, 0, nullptr)
|
||||
// P15: applier_reset is the one kWaitApplied row left (generate_mipmap went async).
|
||||
const Uint64 seq = op == MGPWireOp::ApplierReset
|
||||
? session.EmitAndWait(op, &reset, sizeof(reset), nullptr, 0, nullptr, 0, nullptr)
|
||||
: session.EmitAndWait(op, &clear, sizeof(clear), nullptr, 0, nullptr, 0, nullptr);
|
||||
// Observe BEFORE allowing the peer to complete a non-waiting emission.
|
||||
const Bool parked = bell.entered.load();
|
||||
@@ -63,7 +64,7 @@ void CheckE1Boundary(Bool serverCap, MGPWireOp op, Bool expectWait, const char*
|
||||
if (seq <= before || peer.seen.load() != 1 || session.Control()->Progress.appliedSeq.load() < seq) ::_exit(86);
|
||||
const Bool correct = expectWait ? parked && appliedBeforeReturn : !parked && !appliedBeforeReturn;
|
||||
std::printf("E1 observation: op=%s cap=%u barrier=%u parked=%u applied_before_return=%u emitted=1 peer_seen=1\n",
|
||||
op == MGPWireOp::GenerateMipmap ? "GenerateMipmap" : "Clear",
|
||||
op == MGPWireOp::ApplierReset ? "ApplierReset" : "Clear",
|
||||
static_cast<unsigned>(serverCap), MG_Config::Ipc.VerbBarrier,
|
||||
static_cast<unsigned>(parked), static_cast<unsigned>(appliedBeforeReturn));
|
||||
std::fflush(stdout);
|
||||
@@ -80,9 +81,9 @@ void CheckE1Boundary(Bool serverCap, MGPWireOp op, Bool expectWait, const char*
|
||||
|
||||
TEST(RemoteWaitBoundaryControl, AppliedClassWaitsWithRunAheadCap) {
|
||||
#if MGTEST_HAVE_FORK
|
||||
ASSERT_EQ(MGPipeWaitClassFor(MGPWireOp::GenerateMipmap), kWaitApplied);
|
||||
CheckE1Boundary(true, MGPWireOp::GenerateMipmap, true,
|
||||
"E1 wait boundary: GenerateMipmap returned before apply");
|
||||
ASSERT_EQ(MGPipeWaitClassFor(MGPWireOp::ApplierReset), kWaitApplied);
|
||||
CheckE1Boundary(true, MGPWireOp::ApplierReset, true,
|
||||
"E1 wait boundary: ApplierReset returned before apply");
|
||||
#else
|
||||
GTEST_SKIP() << "E1 requires POSIX child isolation";
|
||||
#endif
|
||||
|
||||
@@ -2587,6 +2587,9 @@ void StartRunAheadSession() {
|
||||
::alarm(15);
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
MG_Config::Ipc.RunAhead = 1;
|
||||
// The run-ahead guards below are the SHARED-block rules; the dual block (the default since
|
||||
// P15) makes a barriered fill the client's own and is covered by DualBlockScenario.
|
||||
MG_Config::Ipc.RoleSplitState = false;
|
||||
Srv::ServerSessionInstance().SetCapabilityBits(
|
||||
static_cast<Uint64>(MG_Pipe::kCapRunAheadApply));
|
||||
Srv::ServerSessionInstance().SetConsumedSubsystems(kMGPipeSubsystemsMigratedAtP4a);
|
||||
@@ -2828,6 +2831,91 @@ TEST(RemoteRunAhead, AnIndexedDrawVerbIsUnbarrieredAndTouchesPipeInputsNotAtAll)
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
// P15: FIRE-AND-FORGET IS THE DEFAULT. The texture-op copies, mip generation and the transform
|
||||
// feedback span calls owe the client no answer, so under run-ahead each publishes and returns.
|
||||
// The wait classes are pinned at compile time (the red: put any of them back to kWaitApplied and
|
||||
// this file stops compiling); the case below proves the client really does not wait on them.
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::GenerateMipmap) == kWaitNone);
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::ResourceCopyRegion) == kWaitNone);
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::CopyFramebufferToTexture) == kWaitNone);
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::BeginStreamOutput) == kWaitNone);
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::EndStreamOutput) == kWaitNone);
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::PauseStreamOutput) == kWaitNone);
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::ResumeStreamOutput) == kWaitNone);
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::BindStreamOutput) == kWaitNone);
|
||||
static_assert(MGPipeWaitClassFor(MGPWireOp::SetStorageBlockBinding) == kWaitNone);
|
||||
|
||||
struct AcceptAllPeer : Codec::WireVerbSink {
|
||||
Bool OnGenerateMipmap(const MGPMipPlan&) override { return true; }
|
||||
Bool OnResourceCopyRegion(const MGPCopyRegion&) override { return true; }
|
||||
Bool OnCopyFramebufferToTexture(const MGPCopyFromFramebuffer&) override { return true; }
|
||||
Bool OnBeginStreamOutput(const MGPStreamOutputBegin&) override { return true; }
|
||||
Bool OnEndStreamOutput(const MGPXfbAccounting&) override { return true; }
|
||||
Bool OnPauseStreamOutput(const MGPStreamOutputControl&) override { return true; }
|
||||
Bool OnResumeStreamOutput(const MGPStreamOutputControl&) override { return true; }
|
||||
Bool OnBindStreamOutput(const MGPStreamOutputBind&) override { return true; }
|
||||
void Install() {
|
||||
if (Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting(
|
||||
[](void* self) {
|
||||
auto& decoder = Srv::ServerSessionInstance().Applier().*PeerMember(DecoderTag{});
|
||||
decoder.SetVerbSink(static_cast<AcceptAllPeer*>(self));
|
||||
return MOBILEGL_OK;
|
||||
},
|
||||
this) != MOBILEGL_OK) {
|
||||
::_exit(82);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST(RemoteRunAhead, FireAndForgetRowsDoNotWaitForTheirApply) {
|
||||
const auto child = RunInChild([] {
|
||||
StartRunAheadSession();
|
||||
AcceptAllPeer peer;
|
||||
peer.Install();
|
||||
ClientSession& session = ClientSessionInstance();
|
||||
const Uint64 before = session.RecordWaits();
|
||||
const auto emit = [&](MGPWireOp op, const auto& payload) {
|
||||
session.EmitAndWait(op, &payload, sizeof(payload), nullptr, 0, nullptr, 0, nullptr);
|
||||
};
|
||||
emit(MGPWireOp::GenerateMipmap, MGPMipPlan{});
|
||||
emit(MGPWireOp::ResourceCopyRegion, MGPCopyRegion{});
|
||||
emit(MGPWireOp::CopyFramebufferToTexture, MGPCopyFromFramebuffer{});
|
||||
emit(MGPWireOp::BindStreamOutput, MGPStreamOutputBind{});
|
||||
emit(MGPWireOp::BeginStreamOutput, MGPStreamOutputBegin{});
|
||||
emit(MGPWireOp::PauseStreamOutput, MGPStreamOutputControl{});
|
||||
emit(MGPWireOp::ResumeStreamOutput, MGPStreamOutputControl{});
|
||||
emit(MGPWireOp::EndStreamOutput, MGPXfbAccounting{});
|
||||
if (session.RecordWaits() != before) ::_exit(121);
|
||||
session.Stop();
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
// P15: a create is fire-and-forget under run-ahead. The handle is the client's own allocation
|
||||
// and the server's accept is derivable (it declines only protocol corruption or a family with no
|
||||
// consumer, which the client never emits for), so the call publishes and returns with a
|
||||
// provisional accept instead of waiting for the answer.
|
||||
//
|
||||
// THE RED (done once while writing it): drop Wire_ResourceCreate's run-ahead fast path and the
|
||||
// call takes the blocking path, so RecordWaits() moves and the child exits 112.
|
||||
TEST(RemoteRunAhead, AResourceCreateDoesNotWaitForItsAnswer) {
|
||||
const auto child = RunInChild([] {
|
||||
StartRunAheadSession();
|
||||
ClientSession& session = ClientSessionInstance();
|
||||
const Uint64 before = session.RecordWaits();
|
||||
MG_Pipe::MGPResourceDesc desc{};
|
||||
desc.Resource = MG_Pipe::MGPipeHandle{MG_Pipe::kMGPipeFirstAllocatableSlot, 1};
|
||||
desc.Target = MG_Pipe::kMGPipeResourceTargetBuffer;
|
||||
desc.Width = 64;
|
||||
MG_Pipe::MGPReplySlot reply = MG_Pipe::MGPipeMintReplySlot();
|
||||
MG_Pipe::gMGPipeScreen.ResourceCreate(&desc, &reply);
|
||||
if (!MG_Pipe::MGPipeTakeReplyBool(reply, "resource_create")) ::_exit(111);
|
||||
if (session.RecordWaits() != before) ::_exit(112);
|
||||
session.Stop();
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
// P15: the blit family. Every blit reaches the validate point as BlitNamedFramebuffer and
|
||||
// publishes one `blit` record (kWaitNone, stamped as BlitFramebuffer), so it is unbarriered: no
|
||||
// fill and no applier quiesce. A barriered answer here was a forced wait once per frame on FCL
|
||||
|
||||
@@ -60,7 +60,7 @@ CMake:`MOBILEGL_BUILD_DISAGGREGATED`(OFF)、`MOBILEGL_BUILD_DISAGGREGATED_
|
||||
| `MOBILEGL_IPC_PERSISTENT_BLOCK_KB` / `_PERSISTENT_HASH_SUPPRESS` | 64 / 1 | persistent-map 推送粒度 / 只推变化的块;`0` 是对照 |
|
||||
| `MOBILEGL_IPC_ADOPT_TIER` | 未设 = T0 | 未设(用户 2026-09-29 定为缺省)= 能用就 T0,不能用(stream、无 AHB、POST 失败、server 不允许)安静回退 T2:只 MGLOG_D、照样计数;`0` = 点名要 T0,回退有具名行(A1 的 `Refuse{AdoptTierOnStream}` 等);`1` = T1 已关,具名拒绝后 T2;`2` = T2(P11 前的缺省)。`CONTRACT-P11.md` B2 |
|
||||
| `MOBILEGL_IPC_ALLOW_ADOPT_T0` | 1 | server 自有:`0` = 永不批准 T0(会话走 T2),经 `ServerSpawn` 保留 |
|
||||
| `MOBILEGL_IPC_STRICT_ERRORS` / `MOBILEGL_IPC_AUDIT` / `MOBILEGL_IPC_ROLE_SPLIT_STATE` | 0 / 0 / 0 | 残余输入读升级为 Fatal / 退休 staging 填 `0xDD` / 双块演练(P5f) |
|
||||
| `MOBILEGL_IPC_STRICT_ERRORS` / `MOBILEGL_IPC_AUDIT` / `MOBILEGL_IPC_ROLE_SPLIT_STATE` | 0 / 0 / 1 | 残余输入读升级为 Fatal / 退休 staging 填 `0xDD` / 双块(P5f;P15 起默认开,`0` 关闭,阴性对照显式设 0;双块下 barriered fill 写 client 块,不再 quiesce applier) |
|
||||
| `MOBILEGL_IPC_SPIN_US` / `MOBILEGL_IPC_SERVER_AFFINITY` | 50 / `auto` | park 前自旋预算 / apply 线程亲和(Redmi 内核忽略) |
|
||||
| `MOBILEGL_IPC_RECOVER` | 1 | session 因 device loss 结束后,应用下一次 `eglCreateContext` / `eglCreate*Surface` / `eglInitialize` 重新拨号得到新 session(旧 context 保持丢失,surface 在新 session 重建);`0` = 丢失对整个进程永久 |
|
||||
| `MOBILEGL_IPC_RESPAWN` | — | 具名拒绝:没有阶段实现 server 重启后的全量重推 |
|
||||
|
||||
Reference in New Issue
Block a user