mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-17 00:28:31 +09:00
[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:
@@ -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) {
|
||||
|
||||
@@ -1397,13 +1397,14 @@ TEST(ServerLoopEglTest, AReadPixelsReplyIsTheTightExtentWhateverDstSizeTheClient
|
||||
// production sequence, on the real op table and the real backend: A - glBufferData(64, data), drawn
|
||||
// (ensured), then deleted on the apply thread, so its id retires into the buffer pool; two presents
|
||||
// with a finish between them move the frame-completion watermark past the id's retire serial, which
|
||||
// is the pool's hand-out rule. B - glBufferData(64, NULL) then glBufferSubData(0, 16), the ordinary
|
||||
// streaming idiom, so its server shadow covers [0, 16) of 64. B's first ensure finds A's id in the
|
||||
// pool and would seed the driver's whole 64-byte store from that shadow, 48 bytes of which nothing
|
||||
// staged. The refusal fires BEFORE the glBufferSubData, so the forked child - whose only thread is a
|
||||
// copy of this one and holds no context - reaches it with no GL call that matters (a no-context call
|
||||
// dispatches to a no-op). Red once by deleting the pool-reuse MGL_SERVER_STAGED_REQUIRE: the child
|
||||
// seeds the store and does not die.
|
||||
// is the pool's hand-out rule. B - glBufferData(64, data2), whose 64 bytes cross as resource_subdata
|
||||
// behind the respecify (table 1 row 19) - and only 16 of them arrive, the MISSING-RECORD shape. B's
|
||||
// first ensure finds A's id in the pool and would seed the driver's whole 64-byte store from a shadow
|
||||
// 48 bytes of which nothing staged. The refusal fires BEFORE the glBufferSubData, so the forked
|
||||
// child - whose only thread is a copy of this one and holds no context - reaches it with no GL call
|
||||
// that matters (a no-context call dispatches to a no-op). Red once by deleting the pool-reuse
|
||||
// MGL_SERVER_STAGED_REQUIRE: the child seeds the store and does not die. (The ORPHANED shape of the
|
||||
// same sequence, glBufferData(64, NULL) + 16 bytes, must NOT die: TheStreamingIdiom... below.)
|
||||
TEST(StagedShadowProductionTest, ASparseShadowForcedThroughAPoolReuseUploadIsFatalByName) {
|
||||
EglServerFixture fixture;
|
||||
MGL_EGL_BRING_UP_OR_BAIL(fixture);
|
||||
@@ -1423,7 +1424,7 @@ TEST(StagedShadowProductionTest, ASparseShadowForcedThroughAPoolReuseUploadIsFat
|
||||
MOBILEGL_OK);
|
||||
ASSERT_TRUE(aEnsured) << "A's ensure minted no storage, so nothing retired into the pool";
|
||||
|
||||
const MG_Pipe::MGPipeHandle b = DeclareBuffer(BufferDesc(22, 64, false));
|
||||
const MG_Pipe::MGPipeHandle b = DeclareBuffer(BufferDesc(22, 64, true));
|
||||
StageBytes(b, 0, 16, 0x22);
|
||||
ASSERT_NE(Twin(b), nullptr) << "the subdata did not mint B's twin (ID-52 item 3)";
|
||||
ASSERT_EQ(Twin(b)->id, 0u) << "B already has a store; the pool-reuse arm cannot be reached";
|
||||
@@ -1606,6 +1607,103 @@ TEST(StagedShadowProductionTest, TheEnsurePathUploadsTheServerShadowNotTheClient
|
||||
|
||||
fixture.TearDown();
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::string ReadLogFrom(SizeT offset) {
|
||||
const std::string whole = ReadLog();
|
||||
return offset < whole.size() ? whole.substr(offset) : std::string();
|
||||
}
|
||||
|
||||
// THE STREAMING IDIOM, end to end, in a FORKED CHILD that brings the server up itself (the
|
||||
// integration harness's pre-flight shape): a Fatal on the apply thread ends the child, not the
|
||||
// case, so the case can name it. glBufferData(64, NULL) then glBufferSubData(0, 16) then a draw:
|
||||
// the frontend object ORPHANS its store and writes 16 bytes - which flips ITS HasDefinedContent
|
||||
// to true - while on the wire that is a resource_respecify with HasDefinedContent CLEAR and one
|
||||
// 16-byte resource_subdata, so the server shadow covers [0, 16) of 64 and the descriptor says
|
||||
// the rest is undefined by the application's own declaration. The draw's whole-store upload
|
||||
// (the respecify reader, or the pool-reuse reader when a 64-byte id is waiting in the pool)
|
||||
// must go through and leave 16 bytes of pattern and 48 of zero in the driver's store. Exit 0
|
||||
// when it did, 7 when the wrong bytes landed, 8 when the child's bring-up failed.
|
||||
enum class IdiomArm { Respecify, PoolReuse };
|
||||
|
||||
[[noreturn]] void RunTheStreamingIdiomAndExit(IdiomArm arm) {
|
||||
EglServerFixture fixture;
|
||||
if (!fixture.BringUp().empty() || !fixture.MakeCurrent()) ::_exit(8);
|
||||
Vector<Uint8> sixteen(16, Uint8{0x77});
|
||||
auto buffer = MakeShared<MG_State::GLState::BufferObject>(arm == IdiomArm::PoolReuse ? 33u : 32u);
|
||||
buffer->Respecify(64, nullptr);
|
||||
buffer->UploadSubData(DataPtr{sixteen.data(), sixteen.size()}, 0);
|
||||
Bool ok = false;
|
||||
(void)OnApply([&] {
|
||||
if (arm == IdiomArm::PoolReuse) {
|
||||
const MG_Pipe::MGPipeHandle a = DeclareBuffer(BufferDesc(29, 64, true));
|
||||
StageBytes(a, 0, 64, 0x11);
|
||||
if (Ensure(a) == nullptr) return;
|
||||
MG_Pipe::MGPipeGetResourceOps()->Destroy(a);
|
||||
for (int frame = 0; frame < 2; ++frame) {
|
||||
if (MG_Backend::DirectGLES::g_GLESFuncs.glFinish) MG_Backend::DirectGLES::g_GLESFuncs.glFinish();
|
||||
MG_Backend::DirectGLES::Present();
|
||||
}
|
||||
}
|
||||
const Uint32 slot = arm == IdiomArm::PoolReuse ? 30u : 28u;
|
||||
const MG_Pipe::MGPipeHandle b = DeclareBuffer(BufferDesc(slot, 64, false));
|
||||
StageBytes(b, 0, 16, 0x77);
|
||||
auto* twin = MG_Backend::DirectGLES::BufferImpl::EnsureBufferResourceForHandle(buffer, b); // the draw
|
||||
if (twin == nullptr || twin->id == 0) return;
|
||||
const auto& gl = MG_Backend::DirectGLES::g_GLESFuncs;
|
||||
gl.glBindBuffer(GL_ARRAY_BUFFER, twin->id);
|
||||
const auto* view = static_cast<const Uint8*>(gl.glMapBufferRange(GL_ARRAY_BUFFER, 0, 64, GL_MAP_READ_BIT));
|
||||
if (view == nullptr) return;
|
||||
ok = view[0] == 0x77 && view[15] == 0x77 && view[16] == 0 && view[63] == 0;
|
||||
gl.glUnmapBuffer(GL_ARRAY_BUFFER);
|
||||
});
|
||||
::_exit(ok ? 0 : 7);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// M-3, ROUND 3's RULE, positive direction (the round-2 refusal aborted six LargeArenaAdoption /
|
||||
// ResourceSubsystemControl entries on the joint by this exact name): the ordinary streaming idiom
|
||||
// through the RESPECIFY reader is uploaded, not refused. Red once by making the refusal
|
||||
// unconditional again (the descriptor's HasDefinedContent ignored): the child dies of
|
||||
// Fatal{StageSnapshotTooNarrow, "respecify_whole_store"} and the appended log names it.
|
||||
TEST(StagedShadowProductionTest, TheStreamingIdiomOrphanThenPartialSubDataIsUploadedNotRefused) {
|
||||
{
|
||||
EglServerFixture probe;
|
||||
MGL_EGL_BRING_UP_OR_BAIL(probe);
|
||||
probe.TearDown();
|
||||
}
|
||||
const SizeT mark = ReadLog().size();
|
||||
EXPECT_EXIT(RunTheStreamingIdiomAndExit(IdiomArm::Respecify), ::testing::ExitedWithCode(0), ".*")
|
||||
<< "the streaming idiom - glBufferData(64, NULL), glBufferSubData(0, 16), draw - did not put "
|
||||
"its 16 bytes and 48 undefined (zero) bytes into the driver's store through the respecify "
|
||||
"reader: exit 7 is the wrong bytes, a signal is the whole-store refusal firing on a store "
|
||||
"the application itself orphaned";
|
||||
const std::string appended = ReadLogFrom(mark);
|
||||
EXPECT_EQ(appended.find("Fatal{StageSnapshotTooNarrow"), std::string::npos)
|
||||
<< "the orphan-then-partial-subdata idiom was refused by name (M-3's whole-store refusal must "
|
||||
"key on the descriptor's HasDefinedContent, not on the frontend flag a partial subdata "
|
||||
"flips); the log says: " << appended;
|
||||
}
|
||||
|
||||
// The same idiom through the POOL-REUSE reader: a 64-byte id retired to the pool by a previous
|
||||
// buffer's delete, handed to the orphaned store's first draw, seeded from the sparse shadow.
|
||||
TEST(StagedShadowProductionTest, TheStreamingIdiomThroughAPoolReuseIsUploadedNotRefused) {
|
||||
{
|
||||
EglServerFixture probe;
|
||||
MGL_EGL_BRING_UP_OR_BAIL(probe);
|
||||
probe.TearDown();
|
||||
}
|
||||
const SizeT mark = ReadLog().size();
|
||||
EXPECT_EXIT(RunTheStreamingIdiomAndExit(IdiomArm::PoolReuse), ::testing::ExitedWithCode(0), ".*")
|
||||
<< "the streaming idiom through a recycled pool id did not put its 16 bytes and 48 undefined "
|
||||
"(zero) bytes into the driver's store: exit 7 is the wrong bytes, a signal is the "
|
||||
"whole-store refusal firing on a store the application itself orphaned";
|
||||
const std::string appended = ReadLogFrom(mark);
|
||||
EXPECT_EQ(appended.find("Fatal{StageSnapshotTooNarrow"), std::string::npos)
|
||||
<< "the orphan-then-partial-subdata idiom through a pool reuse was refused by name (M-3's "
|
||||
"whole-store refusal must key on the descriptor's HasDefinedContent); the log says: "
|
||||
<< appended;
|
||||
}
|
||||
#endif // !_WIN32
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
Reference in New Issue
Block a user