From b19297d5e5ae29e2e8f2f497b540a23883857472 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 23 Sep 2026 03:52:59 -0400 Subject: [PATCH] [MG_Backend, MG_Test] (P7): prove aggregate waits with a single-fence control --- .../DirectVulkan/Renderer/SubmitFencePrefix.h | 41 ++++++++++++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 9 +-- MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp | 2 +- MobileGL/MG_Test/Pipe/CMakeLists.txt | 6 ++ .../MG_Test/Pipe/MagmaAggregateWaitTest.cpp | 66 +++++++++++++++++++ 5 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/SubmitFencePrefix.h create mode 100644 MobileGL/MG_Test/Pipe/MagmaAggregateWaitTest.cpp diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SubmitFencePrefix.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/SubmitFencePrefix.h new file mode 100644 index 00000000..1154390c --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SubmitFencePrefix.h @@ -0,0 +1,41 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/SubmitFencePrefix.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once + +#include +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + +// Collect every fence in the ordered submission prefix. Waiting on only the last matching +// frame-serial record is insufficient: the same serial may have an earlier in-flight submit. +template +bool CollectSubmitFencePrefix(const Records& records, std::uint64_t submitIndex, + Fence nullFence, std::vector& fences) { + fences.clear(); + bool foundTarget = false; + for (const auto& record : records) { + if (record.submitIndex > submitIndex) { + break; + } + if (record.fence == nullFence) { + fences.clear(); + return false; + } + fences.push_back(record.fence); + foundTarget = foundTarget || record.submitIndex == submitIndex; + } + if (!foundTarget) { + fences.clear(); + return false; + } + return true; +} + +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index d22e58d1..682f1d57 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -7,6 +7,7 @@ // End of Source File Header #include "VulkanRenderer.h" +#include "SubmitFencePrefix.h" #include "MG_Backend/DirectVulkan/SubgroupSupportPolicy.h" #include "MG_Backend/DirectGLES/Utils.h" @@ -13679,12 +13680,8 @@ void main() { if (submitIndex <= m_completedSubmitCounter) return true; if (submitIndex > m_submitCounter || m_device == VK_NULL_HANDLE) return false; Vector fences; - for (const auto& record : m_inFlightSubmits) { - if (record.submitIndex > submitIndex) break; - if (record.fence == VK_NULL_HANDLE) return false; - fences.push_back(record.fence); - } - if (fences.empty()) return false; + if (!CollectSubmitFencePrefix(m_inFlightSubmits, submitIndex, VkFence{}, fences)) + return false; const VkResult result = vkWaitForFences(m_device, static_cast(fences.size()), fences.data(), VK_TRUE, timeoutNs); if (result == VK_SUCCESS) { diff --git a/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp index c7f73ab8..02037555 100644 --- a/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp +++ b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp @@ -174,7 +174,7 @@ namespace MobileGL::MG_Impl::GLXImpl { } struct ContextObject { - Display* XDisplay = nullptr; + ::Display* XDisplay = nullptr; EGLDisplay Display = EGL_NO_DISPLAY; EGLConfig Config = nullptr; EGLContext Context = EGL_NO_CONTEXT; diff --git a/MobileGL/MG_Test/Pipe/CMakeLists.txt b/MobileGL/MG_Test/Pipe/CMakeLists.txt index 41038a27..3bfa550c 100644 --- a/MobileGL/MG_Test/Pipe/CMakeLists.txt +++ b/MobileGL/MG_Test/Pipe/CMakeLists.txt @@ -24,6 +24,12 @@ if (MSVC) target_compile_options(PipeCatalogueTest PRIVATE /Zc:preprocessor) endif() +# P7 B4 aggregate-wait regression control shares the production selector; no device is needed. +add_executable(MagmaAggregateWaitTest MagmaAggregateWaitTest.cpp) +target_include_directories(MagmaAggregateWaitTest PRIVATE ${MGL_ROOT}/include ${MGL_ROOT}/MobileGL) +target_link_libraries(MagmaAggregateWaitTest PRIVATE GTest::gtest_main ${LINK_LIBRARIES}) +gtest_discover_tests(MagmaAggregateWaitTest DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit) + # The P1 poison and verify shapes at the block level: a fake GLContext, the real filler and # accessors out of the static library. Links gtest (not gtest_main): the suite needs its own # main() to point MOBILEGL_LOG_FILE_PATH at a temp file before anything logs, because its diff --git a/MobileGL/MG_Test/Pipe/MagmaAggregateWaitTest.cpp b/MobileGL/MG_Test/Pipe/MagmaAggregateWaitTest.cpp new file mode 100644 index 00000000..67d20382 --- /dev/null +++ b/MobileGL/MG_Test/Pipe/MagmaAggregateWaitTest.cpp @@ -0,0 +1,66 @@ +// MobileGL - MobileGL/MG_Test/Pipe/MagmaAggregateWaitTest.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +// Deterministic, device-free control for P7 B4's aggregate submit wait. Production and this +// test share CollectSubmitFencePrefix: two submissions can carry one frame serial, so the +// targeted serial stays incomplete when the old single-fence control retires only one. +#include + +#include + +#include +#include +#include + +namespace { +using MobileGL::MG_Backend::DirectVulkan::CollectSubmitFencePrefix; + +struct Submit { + std::uint64_t submitIndex; + std::uint64_t frameSerial; + int fence; +}; + +bool SerialComplete(std::uint64_t serial, const std::vector& pending) { + return std::none_of(pending.begin(), pending.end(), [serial](const Submit& submit) { + return submit.frameSerial == serial; + }); +} + +TEST(MagmaAggregateWaitTest, MissingTargetAfterAnEarlierPrefixIsRejected) { + const std::vector gap{{10, 42, 101}, {12, 43, 103}}; + std::vector fences; + + EXPECT_FALSE(CollectSubmitFencePrefix(gap, 11, 0, fences)); + EXPECT_TRUE(fences.empty()); +} + +TEST(MagmaAggregateWaitTest, SingleFenceControlLeavesTheTargetFrameInFlight) { + const std::vector pending{{10, 42, 101}, {11, 42, 102}, {12, 43, 103}}; + + // The old control waits only the first matching fence. The second submit still carries 42. + auto singleFenceControl = pending; + singleFenceControl.erase(singleFenceControl.begin()); + EXPECT_FALSE(SerialComplete(42, singleFenceControl)); + + // The production selector includes both submissions through index 11, but not frame 43. + std::vector fences; + ASSERT_TRUE(CollectSubmitFencePrefix(pending, 11, 0, fences)); + ASSERT_EQ(fences, (std::vector{101, 102})); + + auto aggregateWait = pending; + aggregateWait.erase( + std::remove_if(aggregateWait.begin(), aggregateWait.end(), [&fences](const Submit& submit) { + return std::find(fences.begin(), fences.end(), submit.fence) != fences.end(); + }), + aggregateWait.end()); + EXPECT_TRUE(SerialComplete(42, aggregateWait)); + ASSERT_EQ(aggregateWait.size(), 1u); + EXPECT_EQ(aggregateWait.front().frameSerial, 43u); +} +} // namespace