[Fix, Test] (DirectGLES/Managers, MG_Test/Wire): M-3's two whole-store refusals key on the DESCRIPTOR's HasDefinedContent - content the application supplied (glBufferData(size, data)) must be fully staged by the draw, a gap is a missing record and Fatal{StageSnapshotTooNarrow} by name; a store the application orphaned (glBufferData(size, NULL)) has undefined bytes by its own declaration, so the streaming idiom (orphan, partial glBufferSubData, draw) uploads the staged bytes plus zero-fill exactly as the monolith arm uploads MappedData(); round 2 refused both shapes and aborted six LargeArenaAdoption / ResourceSubsystemControl joint entries by name (hidden under c1 round 2's BlobMissing abort until c1 round 3); two positive gates run the idiom end to end in a forked child through each reader and red on their own string when the refusal is made unconditional again

This commit is contained in:
2026-09-16 10:31:29 -04:00
parent 13043e3ff4
commit 1646049ac9
2 changed files with 148 additions and 18 deletions
+42 -10
View File
@@ -2003,6 +2003,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto* record = ResourceRecordOf(res);
return record != nullptr ? static_cast<SizeT>(record->Desc.Width) : 0;
}
#if MOBILEGL_BUILD_DISAGGREGATED
// M-3's rule for the two WHOLE-STORE readers (v1 round 3). The descriptor is the
// application's own statement about the store: HasDefinedContent set means it SUPPLIED
// the content (glBufferData(size, data)), which under split arrives as resource_subdata
// records behind the respecify (table 1 row 19) - so a coverage gap at the draw is a
// MISSING RECORD and the zero-fill past the coverage is not the application's bytes.
// Clear means it ORPHANED the store (glBufferData(size, NULL), glBufferStorage(NULL)):
// every byte it has not staged since is UNDEFINED by its own declaration, the streaming
// idiom (orphan, partial glBufferSubData, draw) is the ordinary case, and uploading the
// shadow's zero-fill for the rest is exactly what the monolith arm uploads from
// MappedData(). Round 2 refused both shapes and aborted six LargeArenaAdoption /
// ResourceSubsystemControl entries on the joint by name (v1-v3.md 6).
Bool ResourceContentIsDeclared(MG_Pipe::MGPipeHandle res) {
const auto* record = ResourceRecordOf(res);
return record != nullptr && record->Desc.HasDefinedContent != 0;
}
#endif
void Ops_H_Create(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc) {
(void)res;
@@ -3028,13 +3045,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
resource->pendingRespecify = false;
BindBufferId(TempBufferTarget, reused);
// M-3 / codex 4: this is a WHOLE-STORE upload from the base, and under split
// the base is the server shadow (M-2), whose zero-filled bytes past the staged
// coverage are not the application's - uploading them is the silent data loss
// the M-6 ruling forbids. RequireCoverage is a no-op for the legacy arm's
// MappedData() and for a non-copying store; under split it Fatals by name on a
// sparse shadow rather than seeding the driver with zeroes.
MGL_SERVER_STAGED_REQUIRE(*resource, liveHostBase(), 0, poolSize,
"pool_reuse_whole_store");
// the base is the server shadow (M-2). For a store whose content the
// application SUPPLIED, zero-filled bytes past the staged coverage are not the
// application's - uploading them is the silent data loss the M-6 ruling
// forbids, and a gap is a missing record: Fatal by name. For a store the
// application ORPHANED the gap is its own undefined content and the upload is
// legal (ResourceContentIsDeclared, above). RequireCoverage is a no-op for the
// legacy arm's MappedData() and for a non-copying store.
#if MOBILEGL_BUILD_DISAGGREGATED
if (ResourceContentIsDeclared(res)) {
MGL_SERVER_STAGED_REQUIRE(*resource, liveHostBase(), 0, poolSize,
"pool_reuse_whole_store");
}
#endif
g_GLESFuncs.glBufferSubData(TempBufferTarget, 0, (GLsizeiptr)poolSize, liveHostBase());
if (MG_Util::PipeStats::Enabled()) {
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer,
@@ -3112,13 +3135,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
// [0, size) upload from the base, so it owes the same coverage the pending-range drain
// below owes - the two respecify arms were the readers M-6's "never widened" rule did
// not reach. No-op for the legacy arm's MappedData() and for a non-copying store; a
// Fatal{StageSnapshotTooNarrow, "respecify_whole_store"} under split when the shadow's
// coverage does not span the store, instead of uploading its zero-fill as content.
// Fatal{StageSnapshotTooNarrow, "respecify_whole_store"} under split when the
// DESCRIPTOR says the application supplied the content and the shadow's coverage does
// not span the store (a missing record), instead of uploading its zero-fill as content.
// NOT for a store the application orphaned: there `shadowHasContent` is the frontend
// object's flag, which the streaming idiom's partial glBufferSubData flips to true, and
// the bytes it did not write are undefined by its own glBufferData(NULL) - the rule at
// ResourceContentIsDeclared. Round 2 refused that idiom and aborted six joint entries.
const auto requireWholeStoreCoverage = [&]() {
if (initialData != nullptr) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (initialData != nullptr && record->Desc.HasDefinedContent != 0) {
MGL_SERVER_STAGED_REQUIRE(*resource, static_cast<const Uint8*>(initialData), 0,
size, "respecify_whole_store");
}
#else
(void)initialData;
#endif
};
if (resource->pendingRespecify || !resource->storageInitialized || resource->storageSize != size) {