diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index a47b1987..97873c0b 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -2576,3 +2576,28 @@ if (MOBILEGL_BUILD_DISAGGREGATED) ENVIRONMENT "${MGL_ITEST_GLES_DUALBLOCK_ENVIRONMENT}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/split-logs/DirectGLES.Split.DualBlock.TheTwoRolesHaveDistinctBlocks.log" ) endif() + +# P5f exit: observe every one of three real drawing frames on both backend roles. +if (MOBILEGL_BUILD_DISAGGREGATED) + foreach(backend DirectGLES DirectVulkan) + if (backend STREQUAL "DirectGLES") + set(p5f_rsp_label "integration-split") + else() + set(p5f_rsp_label "integration-magma-split") + endif() + mgl_itest_join_environment(p5f_rsp_environment + "MOBILEGL_BACKEND_TYPE=${backend}" "MOBILEGL_TRANSPORT=inproc" + "MOBILEGL_IPC_SERVER_PATH=${MGL_ITEST_SPLIT_SERVER_PATH}" "MGITEST_SPLIT_LANE=1" + "MGITEST_P5F_RSP_LANE=1" "MOBILEGL_PIPE_STATS=1" "MOBILEGL_PIPE_STATS_PERIOD=1" + "MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p5f-rsp-${backend}.log" + ${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV}) + gtest_discover_tests(MobileGLIntegrationTest + TEST_PREFIX "${backend}.Split.P5fRsp." + TEST_FILTER "F1WireScenario.EachWireFrameHasZeroResidualPulls" + DISCOVERY_TIMEOUT 30 + PROPERTIES LABELS "${p5f_rsp_label}\;integration-p5f-rsp" + TIMEOUT ${MGL_ITEST_TIMEOUT} + RESOURCE_LOCK "p5f-rsp-${backend}.log" + ENVIRONMENT "${p5f_rsp_environment}") + endforeach() +endif() diff --git a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp index f2d32cfd..115fb79b 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp @@ -1,6 +1,8 @@ // f1 split-only pixel controls: every result depends on the migrated verb. #include "../Harness/ScenarioFixture.h" #include "../Harness/SplitRuntimePeek.h" +#include "../Harness/PipeStatsWindow.h" +#include "../Harness/SplitLane.h" #include #include #include @@ -93,6 +95,61 @@ protected: }; } +// P5f exit: each published frame must have a real draw and server stamp, and +// zero residual reads. Missing instrumentation is a failure rather than a false zero. +TEST_F(F1WireScenario, EachWireFrameHasZeroResidualPulls) { + if (!Ready()) return; + if (!SplitLane::MarkerIsOne("MGITEST_P5F_RSP_LANE")) + GTEST_SKIP() << "requires the dedicated per-frame stats lane"; + ASSERT_FALSE(PipeStatsWindow::LibraryLogPath().empty()); + Attach(GL_RGBA8); + const char* fragment = R"(#version 430 core +uniform float value; +layout(location=0) out vec4 color; +void main() { color = vec4(value, 0.25, 0.75, 1.0); } +)"; + const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, + {GL_FRAGMENT_SHADER, fragment}}); + ASSERT_NE(program, 0u); + GLuint vao = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glUseProgram(program); + const GLint location = glGetUniformLocation(program, "value"); + ASSERT_GE(location, 0); + glViewport(0, 0, 8, 8); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_RASTERIZER_DISCARD); + Gl().EndFrame(); // close setup's counter window + for (int frame = 0; frame < 3; ++frame) { + const float value = float(frame + 1) / 4.0f; + glUniform1f(location, value); + glDrawArrays(GL_TRIANGLES, 0, 3); + std::array pixel{}; + glReadPixels(3, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + EXPECT_NEAR(pixel[0], value * 255.0f, 1) << "frame " << frame; + EXPECT_NEAR(pixel[1], 64, 1); + EXPECT_NEAR(pixel[2], 191, 1); + EXPECT_EQ(pixel[3], 255); + Gl().EndFrame(); + const auto window = PipeStatsWindow::LastFromLaneLog(); + ASSERT_TRUE(window.found) << "P5f rsp window missing on frame " << frame; + EXPECT_GT(PipeStatsWindow::CounterOrAbsent(window, "draws"), 0) << window.line; + EXPECT_GT(PipeStatsWindow::CounterOrAbsent(window, "vbs"), 0) << window.line; + EXPECT_EQ(PipeStatsWindow::CounterOrAbsent(window, "rsp"), 0) + << "P5f residual pull on frame " << frame << ": " << window.line; + RecordProperty("p5f_frame_" + std::to_string(frame), window.line); + } + glUseProgram(0); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(program); +} + TEST_F(F1WireScenario, ClearBufferfvPixels) { // Red once (executed, reverted): zero the clear record values; F1.ClearBufferfv.pixels fails. if (!Ready()) return; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp index a50e0c50..76cfa0c0 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp @@ -381,12 +381,8 @@ void main() { oColor = vec4(vColor, 1.0); } "a green strict lane containing only entries like this one would mean the knob " "was never armed rather than that the debt was paid (ID-115): " << window.line; - EXPECT_TRUE(residual == 0 || residual >= draws) - << "rsp neither scales with draws nor is zero (" << residual << " vs " << draws - << "). ID-119: while this phase's draw-path debt is unretired every draw pulls at " - "least one BARRIER-PULLED row, and the retirement is visible as rsp ceasing to " - "scale with draws - a value strictly between is neither state: " - << window.line; + EXPECT_EQ(residual, 0) + << "P5f exit: a drawing frame still reads client residual state: " << window.line; } } // namespace MGITest