diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index d144cd84..ab4f3bdf 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -2788,6 +2788,18 @@ if (MOBILEGL_BUILD_DISAGGREGATED) endforeach() endforeach() + # ---- P11 M: DEV'S TWO GPU-CONTENT SCENARIOS ON THE SPLIT ARMS ---------------------------- + # + # Merged from dev@c60639db with their monolith entries. ImageBindableRemintScenario is the + # monolith twin of TextureRemintPullScenario above (568090f0 made Espryt's frontend re-mint + # read the GPU level back as the server's already did); PartialUploadOverGpuContentScenario is + # 8966d5c2's sub-rect upload over converted/reshaped levels (the server's handle arm cuts the + # region from the conversion's buffer too) plus b1d60523's Magma ordering cases, which are + # plain GL and run here on Espryt as well. The Magma arms are laid by hand in the + # `DirectVulkan..GpuContent.` block, where the gated Magma flavour lives. + mgl_itest_register_split_arms("ImageBindableRemintScenario.*" "") + mgl_itest_register_split_arms("PartialUploadOverGpuContentScenario.*" "") + # ---- P5b t2: the transform-feedback spans, the XFB object bind, the patch parameter ------- # # MG_Remote/CONTRACT-P5B.md §2 t2. The three P5 lanes above are the REDUCED PATH's targets @@ -3778,6 +3790,31 @@ if (MOBILEGL_BUILD_DISAGGREGATED) endforeach() endif() +# P11 M: DEV'S TWO GPU-CONTENT SCENARIOS ON MAGMA, all three arms, GATED, in the band flavour - +# ImageBindableRemintScenario (Magma is its control backend: a storage upgrade copies image to +# image) and PartialUploadOverGpuContentScenario (8966d5c2's RGB->RGBA region cut and b1d60523's +# upload-after-clear/draw ordering, both of which run in the server's renderer on these arms). +# One entry per scenario and arm; every entry gets a private log through MGL_SPLIT_ARM_TEST_LISTS. +if (MOBILEGL_BUILD_DISAGGREGATED) + foreach(mglItestArm IN LISTS MGL_ITEST_MAGMA_ARMS) + mgl_itest_magma_arm_shape(${mglItestArm}) + mgl_itest_join_environment(mglItestMagmaGpuContentEnvironment + ${MGL_ITEST_MAGMA_BAND_FLAVOUR_ENV} ${mglItestArmTransportEnv}) + foreach(mglItestGpuContentScenario ImageBindableRemintScenario PartialUploadOverGpuContentScenario) + gtest_discover_tests(MobileGLIntegrationTest + TEST_PREFIX "DirectVulkan.${mglItestArm}.GpuContent." + TEST_LIST "MGL_SPLIT_ARM_TESTS_${MGL_SPLIT_ARM_INDEX}" + TEST_FILTER "${mglItestGpuContentScenario}.*" + DISCOVERY_TIMEOUT 30 + PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT} + ${mglItestArmProperties} + ENVIRONMENT "${mglItestMagmaGpuContentEnvironment}") + list(APPEND MGL_SPLIT_ARM_TEST_LISTS "MGL_SPLIT_ARM_TESTS_${MGL_SPLIT_ARM_INDEX}") + math(EXPR MGL_SPLIT_ARM_INDEX "${MGL_SPLIT_ARM_INDEX} + 1") + endforeach() + endforeach() +endif() + # P10 B: ASYNC COMPILATION, THE XFB QUERY PAIR AND GL_PRIMITIVES_GENERATED ON MAGMA, all three # arms, GATED, in the band flavour (role-split state, strict errors, run-ahead). The DirectGLES # half of the first two is registered beside the other Espryt arms; the third is Magma's alone. diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PartialUploadOverGpuContentScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PartialUploadOverGpuContentScenario.cpp index 617ccf3a..eea82a47 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PartialUploadOverGpuContentScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PartialUploadOverGpuContentScenario.cpp @@ -259,6 +259,67 @@ void main() { ExpectClearAroundBox(rgb, kEdge, kEdge, kBoxAt, kBoxAt, kBoxEdge, kBoxEdge); } + // P11 M: TWO SCATTERED WRITES INTO ONE LEVEL, so the level carries a rect list (their + // area is well under 3/4 of their union box) and not just a box. Under a transport + // (and on the push builds' monolith arm) the list crosses in the client's record, + // whose regions state their source pitch and offset in the CLIENT's layout; a level + // whose upload is converted is cut from the conversion's wider buffer, so those + // numbers must not address it (Managers.cpp, carriedLayoutApplies). The whole level + // is uploaded green first, so every texel the union box spans outside the two boxes + // is green in the shadow too: this case is about WHERE the boxes land, not about GPU + // content between them. Green everywhere but the two blue boxes. + void Run2DScatter(const Format& f) { + constexpr int kFirstAt = 1, kSecondAt = 5; + const GLuint t = NewTexture(GL_TEXTURE_2D); + const bool integer = f.encoding == Encoding::Uint8; + const char* sampler = integer ? "usampler2D" : "sampler2D"; + const auto green = Fill(f, kEdge * kEdge, 0.0f, 1.0f, 0.0f); + glBindTexture(GL_TEXTURE_2D, t); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, static_cast(f.internalFormat), kEdge, kEdge, 0, f.clientFormat, + f.clientType, green.data()); + glBindTexture(GL_TEXTURE_2D, 0); + ASSERT_EQ(FirstGLError(), 0u) << "definition"; + SyncBySampling(GL_TEXTURE_2D, t, sampler, "ivec2(0)"); + if (!Attach(t, -1)) { + Detach(); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + GTEST_SKIP() << "the format does not attach on backend " << Gl().BackendName(); + } + Detach(); + const auto blue = Fill(f, kBoxEdge * kBoxEdge, 0.0f, 0.0f, 1.0f); + glBindTexture(GL_TEXTURE_2D, t); + glTexSubImage2D(GL_TEXTURE_2D, 0, kFirstAt, kFirstAt, kBoxEdge, kBoxEdge, f.clientFormat, f.clientType, + blue.data()); + glTexSubImage2D(GL_TEXTURE_2D, 0, kSecondAt, kSecondAt, kBoxEdge, kBoxEdge, f.clientFormat, + f.clientType, blue.data()); + glBindTexture(GL_TEXTURE_2D, 0); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + SyncBySampling(GL_TEXTURE_2D, t, sampler, "ivec2(0)"); + ASSERT_TRUE(Attach(t, -1)); + const auto rgb = ReadRgb(f, kEdge, kEdge); + Detach(); + int bad = 0; + std::ostringstream first; + for (int y = 0; y < kEdge; ++y) + for (int x = 0; x < kEdge; ++x) { + const auto inBox = [&](int at) { + return x >= at && x < at + kBoxEdge && y >= at && y < at + kBoxEdge; + }; + const bool blueHere = inBox(kFirstAt) || inBox(kSecondAt); + const float want[3] = {0.0f, blueHere ? 0.0f : 1.0f, blueHere ? 1.0f : 0.0f}; + const float* got = &rgb[static_cast(y * kEdge + x) * 3u]; + bool texelBad = false; + for (int c = 0; c < 3; ++c) texelBad |= std::fabs(got[c] - want[c]) > 0.02f; + if (texelBad && bad++ == 0) { + first << "(" << x << "," << y << ") got " << got[0] << "," << got[1] << "," << got[2] + << " want " << want[0] << "," << want[1] << "," << want[2]; + } + } + EXPECT_EQ(bad, 0) << bad << " of " << kEdge * kEdge << " texels wrong; first " << first.str() + << " (a box read at the wrong pitch or offset lands the wrong bytes)"; + } + // Building blocks of the RGBA8 ordering cases: the same texels, issued in the orders // a backend that defers work may get wrong. static Format Rgba8() { return {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, 4, Encoding::Unorm8}; } @@ -348,6 +409,24 @@ void main() { Run2D({GL_RGB8UI, GL_RGB_INTEGER, GL_UNSIGNED_BYTE, 3, Encoding::Uint8}); } + // P11 M: the rect-list shape of the same upload (Run2DScatter). RGBA8 is the control whose + // record pitch does address the uploaded bytes; the other two are stored four-channel on + // llvmpipe and on every ES driver, so their boxes are cut from the conversion's buffer. + TEST_F(PartialUploadOverGpuContentScenario, Rgba8ScatteredSubImagesLandWhereWritten) { + if (!Ready()) return; + Run2DScatter({GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, 4, Encoding::Unorm8}); + } + + TEST_F(PartialUploadOverGpuContentScenario, Srgb8ScatteredSubImagesLandWhereWritten) { + if (!Ready()) return; + Run2DScatter({GL_SRGB8, GL_RGB, GL_UNSIGNED_BYTE, 3, Encoding::Unorm8}); + } + + TEST_F(PartialUploadOverGpuContentScenario, Rgb8uiScatteredSubImagesLandWhereWritten) { + if (!Ready()) return; + Run2DScatter({GL_RGB8UI, GL_RGB_INTEGER, GL_UNSIGNED_BYTE, 3, Encoding::Uint8}); + } + // A 1D array's ES image is a 2D array one texel high with the layers in depth, so the // upload is shaped differently from the shadow (layers as rows) even with no conversion. TEST_F(PartialUploadOverGpuContentScenario, OneDimensionalArraySubImageKeepsTheGpuClearedLayer) { diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TextureRemintPullScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TextureRemintPullScenario.cpp index 5f640a60..3c1c216a 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TextureRemintPullScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TextureRemintPullScenario.cpp @@ -44,10 +44,12 @@ // G and G3 were Fatal{ResourceUnavailable, "image-promotion-readback"} on Espryt's split arms // until W2-a: the promotion read a level the driver never allocated. // -// ARMS. Both backends on the monolith arm and on Split / Spawn / Tcp. ONE NAMED SKIP: the -// monolith DirectGLES arm replays the client's shadow over texels the GPU wrote (B, C, D, E, F, -// F2, H) - a known dev defect, not P9's (W2-REMINT.md §6), whose fix moves the pull build's .text -// (G1). Its other cases run and pass there. +// ARMS. Both backends on the monolith arm and on Split / Spawn / Tcp, every case. The monolith +// DirectGLES arm used to be skipped for B, C, D, E, F, F2 and H: its re-mint replayed the client's +// shadow over texels the GPU wrote (W2-REMINT.md §6, the half of ID-P9-8 left to dev). Dev's +// 568090f0 made it read the GPU level back first (AdoptDriverLevelIntoShadow), P11 M merged it, +// and the skip is retired; E is also what holds the push builds' monolith arm to merging the +// applier's pending boxes, which the frontend's dirty model no longer names there. // // W2-d's COUNTER. `trp=` (TextureRemintPulls) counts a re-mint of storage the backend already // held on BOTH Espryt arms now. The peek is process-wide, so it sees the server's count only @@ -181,16 +183,6 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } FirstGLError(); } - // The one named skip (see the header): the monolith DirectGLES re-mint replays the - // client's shadow, which never saw what the GPU wrote. Empty when the arm is a subject. - std::string MonolithEsprytShadowReplaySkip() const { - if (!m_espryt || m_wire) return {}; - return "the monolith DirectGLES re-mint (RequireImageBindableStorage) replays the client's " - "shadow over texels the GPU wrote - a known dev defect filed separately " - "(notes/p9/W2-REMINT.md §6), whose fix moves the pull build's .text (G1). The split " - "arms and both Magma arms are this case's subject."; - } - GLuint NewTexture() { GLuint texture = 0; glGenTextures(1, &texture); @@ -310,8 +302,13 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } << Gl().BackendName() << ", " << (m_wire ? "split" : "monolith") << ")"; } - // W2-d: where the peek can see the server's count (monolith, inproc). - bool RemintCountVisible() const { return m_espryt && (!m_wire || m_inproc); } + // W2-d: where the peek can see the server's count (monolith, inproc) - and only on a + // push build, which is where PipeStats exists; the pull build's peek answers false (P11 + // M: the skip retired above now runs H there too, and A always did). + bool RemintCountVisible() const { + unsigned long long ignored = 0; + return m_espryt && (!m_wire || m_inproc) && PeekPipeStatsTextureRemintPulls(&ignored); + } unsigned long long RemintCount() { unsigned long long count = 0; EXPECT_TRUE(PeekPipeStatsTextureRemintPulls(&count)); @@ -360,7 +357,6 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } TEST_F(TextureRemintPullScenario, AGpuClearAfterTheUploadSurvivesTheImageBind) { if (!Ready()) return; - if (const std::string skip = MonolithEsprytShadowReplaySkip(); !skip.empty()) GTEST_SKIP() << skip; const GLuint texture = NewTexture(); const auto pattern = Pattern(kEdge, 0x40); glBindTexture(GL_TEXTURE_2D, texture); @@ -375,7 +371,6 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } TEST_F(TextureRemintPullScenario, AnImmutableTexturesGpuClearSurvivesTheImageBind) { if (!Ready()) return; - if (const std::string skip = MonolithEsprytShadowReplaySkip(); !skip.empty()) GTEST_SKIP() << skip; const bool counted = RemintCountVisible() && m_inproc; const unsigned long long before = counted ? RemintCount() : 0; const GLuint texture = NewTexture(); @@ -398,7 +393,6 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } TEST_F(TextureRemintPullScenario, ANullDefinedTextureWrittenOnlyByTheGpuSurvivesTheImageBind) { if (!Ready()) return; - if (const std::string skip = MonolithEsprytShadowReplaySkip(); !skip.empty()) GTEST_SKIP() << skip; const GLuint texture = NewTexture(); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kEdge, kEdge, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); @@ -412,7 +406,6 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } TEST_F(TextureRemintPullScenario, APendingPartialUploadMergesOverGpuWrittenTexels) { if (!Ready()) return; - if (const std::string skip = MonolithEsprytShadowReplaySkip(); !skip.empty()) GTEST_SKIP() << skip; const GLuint texture = NewTexture(); const auto pattern = Pattern(kEdge, 0x40); glBindTexture(GL_TEXTURE_2D, texture); @@ -440,7 +433,6 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } TEST_F(TextureRemintPullScenario, AGeneratedMipLevelSurvivesTheImageBind) { if (!Ready()) return; - if (const std::string skip = MonolithEsprytShadowReplaySkip(); !skip.empty()) GTEST_SKIP() << skip; const GLuint texture = NewTexture(); const auto solid = Solid(kEdge, 200, 100, 50, 255); glBindTexture(GL_TEXTURE_2D, texture); @@ -456,7 +448,6 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } TEST_F(TextureRemintPullScenario, AGpuClearedMipLevelSurvivesTheImageBind) { if (!Ready()) return; - if (const std::string skip = MonolithEsprytShadowReplaySkip(); !skip.empty()) GTEST_SKIP() << skip; const GLuint texture = NewTexture(); const auto level0 = Solid(kEdge, 200, 100, 50, 255); const auto level1 = Solid(kEdge / 2, 10, 20, 30, 255); @@ -529,7 +520,6 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } TEST_F(TextureRemintPullScenario, AWidenedFormatsGpuClearSurvivesTheCarrierRemint) { if (!Ready()) return; - if (const std::string skip = MonolithEsprytShadowReplaySkip(); !skip.empty()) GTEST_SKIP() << skip; const bool counted = RemintCountVisible(); const unsigned long long before = counted ? RemintCount() : 0; const GLuint texture = NewTexture();