mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-18 00:58:30 +09:00
[Feat] (MG_Remote, P5b sync): migrate fence lifecycle and wait replies to the apply thread
This commit is contained in:
@@ -61,11 +61,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
auto* syncObject = new SyncObject;
|
auto* syncObject = new SyncObject;
|
||||||
syncObject->condition = condition;
|
syncObject->condition = condition;
|
||||||
syncObject->flags = flags;
|
syncObject->flags = flags;
|
||||||
// The family's ONE gate. FenceSync is class C under split, and "absent" is the
|
// P5b: FenceSync is now a class-B emitter under split. Its server sink keeps the
|
||||||
// answer the whole fallback chain below is written against: every later site already
|
// backend's optional/null-native fallback; the client must reach the wire first.
|
||||||
// checks syncObject->backendHandle, which stays null from here. The POINTER-valued
|
// This is the same pointer expression MGL_BACKEND_SLOT_PTR_LOCAL had in a pull build.
|
||||||
// macro keeps the init-statement byte-identical in a pull build (G1).
|
if (const auto backendFenceSync = MG_Backend::gBackendFunctionsTable.GL.FenceSync) {
|
||||||
if (const auto backendFenceSync = MGL_BACKEND_SLOT_PTR_LOCAL(FenceSync)) {
|
|
||||||
MGP_FILL(FenceSync);
|
MGP_FILL(FenceSync);
|
||||||
syncObject->backendHandle = backendFenceSync();
|
syncObject->backendHandle = backendFenceSync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ add_executable(MobileGLIntegrationTest
|
|||||||
Scenarios/ResidentIndexScenario.cpp
|
Scenarios/ResidentIndexScenario.cpp
|
||||||
Scenarios/MultiDrawScenario.cpp
|
Scenarios/MultiDrawScenario.cpp
|
||||||
Scenarios/IndexedDrawFamilyScenario.cpp
|
Scenarios/IndexedDrawFamilyScenario.cpp
|
||||||
|
Scenarios/SyncWireScenario.cpp
|
||||||
Scenarios/DrawParametersScenario.cpp
|
Scenarios/DrawParametersScenario.cpp
|
||||||
Scenarios/AsyncCompileScenario.cpp
|
Scenarios/AsyncCompileScenario.cpp
|
||||||
Scenarios/XfbAfterClipDistanceScenario.cpp
|
Scenarios/XfbAfterClipDistanceScenario.cpp
|
||||||
@@ -2068,6 +2069,17 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
|||||||
ENVIRONMENT "${MGL_ITEST_GLES_SPLIT_ENVIRONMENT}"
|
ENVIRONMENT "${MGL_ITEST_GLES_SPLIT_ENVIRONMENT}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
gtest_discover_tests(MobileGLIntegrationTest
|
||||||
|
TEST_PREFIX "DirectGLES.Split."
|
||||||
|
TEST_LIST MGL_SPLIT_SYNC_TESTS
|
||||||
|
TEST_FILTER "SyncWireScenario.*"
|
||||||
|
DISCOVERY_TIMEOUT 30
|
||||||
|
PROPERTIES
|
||||||
|
LABELS "integration-gpu\;integration-split"
|
||||||
|
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||||
|
ENVIRONMENT "${MGL_ITEST_GLES_SPLIT_ENVIRONMENT}"
|
||||||
|
)
|
||||||
|
|
||||||
# The split half of the counting pair. MGITEST_PERSISTENT_MAP_ARM=emulated is R-6: under split
|
# The split half of the counting pair. MGITEST_PERSISTENT_MAP_ARM=emulated is R-6: under split
|
||||||
# the adopt tier is pinned at T2, the resource owner declines every acquisition and the client
|
# the adopt tier is pinned at T2, the resource owner declines every acquisition and the client
|
||||||
# pushes the mapping's dirty blocks - so pmap must be non-zero and mpr must be the monolith
|
# pushes the mapping's dirty blocks - so pmap must be non-zero and mpr must be the monolith
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ file(MAKE_DIRECTORY "@CMAKE_CURRENT_BINARY_DIR@/split-logs")
|
|||||||
# P5b t2's two lanes ride the same rule: one private log path per entry, or
|
# P5b t2's two lanes ride the same rule: one private log path per entry, or
|
||||||
# SplitLogPaths.PrivateAndDistinct is red for them (ID-53).
|
# SplitLogPaths.PrivateAndDistinct is red for them (ID-53).
|
||||||
foreach(entry IN LISTS MGL_SPLIT_CLEAR_TESTS MGL_SPLIT_TRIANGLE_TESTS MGL_SPLIT_PMAP_TESTS
|
foreach(entry IN LISTS MGL_SPLIT_CLEAR_TESTS MGL_SPLIT_TRIANGLE_TESTS MGL_SPLIT_PMAP_TESTS
|
||||||
MGL_SPLIT_T2_TESS_TESTS MGL_SPLIT_T2_XFB_TESTS)
|
MGL_SPLIT_T2_TESS_TESTS MGL_SPLIT_T2_XFB_TESTS MGL_SPLIT_SYNC_TESTS)
|
||||||
set_tests_properties("${entry}" PROPERTIES ENVIRONMENT
|
set_tests_properties("${entry}" PROPERTIES ENVIRONMENT
|
||||||
"MOBILEGL_LOG_FILE_PATH=@CMAKE_CURRENT_BINARY_DIR@/split-logs/${entry}.log")
|
"MOBILEGL_LOG_FILE_PATH=@CMAKE_CURRENT_BINARY_DIR@/split-logs/${entry}.log")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// P5b sync migration: a real fence and reply cross the apply thread.
|
||||||
|
#include "../Harness/ScenarioFixture.h"
|
||||||
|
#include "../Harness/SplitRuntimePeek.h"
|
||||||
|
#ifdef GLAPI
|
||||||
|
#undef GLAPI
|
||||||
|
#endif
|
||||||
|
#define GL_GLEXT_PROTOTYPES
|
||||||
|
#include <GL/gl.h>
|
||||||
|
#include <GL/glcorearb.h>
|
||||||
|
#undef GL_GLEXT_PROTOTYPES
|
||||||
|
|
||||||
|
namespace MGITest {
|
||||||
|
class SyncWireScenario : public ScenarioTest {};
|
||||||
|
|
||||||
|
TEST_F(SyncWireScenario, FenceWaitStatusAndDeletionCrossAndPreserveRenderedPixels) {
|
||||||
|
if (!Ready()) return;
|
||||||
|
const auto why = SplitRuntimeSkipReason();
|
||||||
|
if (!why.empty()) GTEST_SKIP() << why;
|
||||||
|
glClearColor(0.25f, 0.5f, 0.75f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
const auto before = PeekSplitRuntime().emitSeq;
|
||||||
|
const auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
|
ASSERT_NE(sync, nullptr);
|
||||||
|
EXPECT_TRUE(glIsSync(sync));
|
||||||
|
GLint status = 0;
|
||||||
|
glGetSynciv(sync, GL_SYNC_STATUS, 1, nullptr, &status);
|
||||||
|
EXPECT_TRUE(status == GL_SIGNALED || status == GL_UNSIGNALED);
|
||||||
|
const auto wait = glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 5000000000ull);
|
||||||
|
EXPECT_TRUE(wait == GL_ALREADY_SIGNALED || wait == GL_CONDITION_SATISFIED) << wait;
|
||||||
|
glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
|
||||||
|
glDeleteSync(sync);
|
||||||
|
EXPECT_FALSE(glIsSync(sync));
|
||||||
|
EXPECT_GE(PeekSplitRuntime().emitSeq, before + 5);
|
||||||
|
GLubyte pixel[4]{};
|
||||||
|
glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
|
||||||
|
const int expected[4] = {64, 128, 191, 255};
|
||||||
|
for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1);
|
||||||
|
EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
|
||||||
|
// This one remains live until full session teardown, exercising server orphan cleanup.
|
||||||
|
ASSERT_NE(glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0), nullptr);
|
||||||
|
}
|
||||||
|
} // namespace MGITest
|
||||||
@@ -287,7 +287,12 @@
|
|||||||
X(PatchParameter, PatchParameteri) \
|
X(PatchParameter, PatchParameteri) \
|
||||||
X(BindStreamOutput, BindTransformFeedback) \
|
X(BindStreamOutput, BindTransformFeedback) \
|
||||||
X(SetStorageBlockBinding, ShaderStorageBlockBinding) \
|
X(SetStorageBlockBinding, ShaderStorageBlockBinding) \
|
||||||
X(CopyFramebufferToTexture, CopyTexImage2D)
|
X(CopyFramebufferToTexture, CopyTexImage2D) \
|
||||||
|
X(FenceCreate, FenceSync) \
|
||||||
|
X(FenceStatus, GetSyncStatus) \
|
||||||
|
X(FenceWait, ClientWaitSync) \
|
||||||
|
X(FenceDestroy, DeleteSync) \
|
||||||
|
X(FenceWaitServer, WaitSync)
|
||||||
|
|
||||||
// X(Op, Why) - verb-shaped calls that are deliberately NOT stamp points.
|
// X(Op, Why) - verb-shaped calls that are deliberately NOT stamp points.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -408,8 +408,10 @@ namespace MobileGL::MG_Pipe {
|
|||||||
struct MGPFenceWait {
|
struct MGPFenceWait {
|
||||||
MGPipeHandle Fence;
|
MGPipeHandle Fence;
|
||||||
Uint64 TimeoutNs;
|
Uint64 TimeoutNs;
|
||||||
|
Uint32 Flags;
|
||||||
|
Uint32 Pad0;
|
||||||
};
|
};
|
||||||
MGP_ASSERT_POD(MGPFenceWait, 16);
|
MGP_ASSERT_POD(MGPFenceWait, 24);
|
||||||
|
|
||||||
struct MGPQueryDesc {
|
struct MGPQueryDesc {
|
||||||
MGPipeHandle Query;
|
MGPipeHandle Query;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
F(BufOffset) F(BufSize)
|
F(BufOffset) F(BufSize)
|
||||||
|
|
||||||
#define MGP_FIELDS_MGPFenceWait(F) \
|
#define MGP_FIELDS_MGPFenceWait(F) \
|
||||||
F(Fence) F(TimeoutNs)
|
F(Fence) F(TimeoutNs) F(Flags)
|
||||||
|
|
||||||
#define MGP_FIELDS_MGPQueryDesc(F) \
|
#define MGP_FIELDS_MGPQueryDesc(F) \
|
||||||
F(Query) F(Kind) F(Stream)
|
F(Query) F(Kind) F(Stream)
|
||||||
|
|||||||
@@ -287,12 +287,17 @@ constexpr MGPipeVerb MGPipeVerbForWireOp(MGPWireOp op) {
|
|||||||
case MGPWireOp::BindStreamOutput: return MGPipeVerb::BindTransformFeedback;
|
case MGPWireOp::BindStreamOutput: return MGPipeVerb::BindTransformFeedback;
|
||||||
case MGPWireOp::SetStorageBlockBinding: return MGPipeVerb::ShaderStorageBlockBinding;
|
case MGPWireOp::SetStorageBlockBinding: return MGPipeVerb::ShaderStorageBlockBinding;
|
||||||
case MGPWireOp::CopyFramebufferToTexture: return MGPipeVerb::CopyTexImage2D;
|
case MGPWireOp::CopyFramebufferToTexture: return MGPipeVerb::CopyTexImage2D;
|
||||||
|
case MGPWireOp::FenceCreate: return MGPipeVerb::FenceSync;
|
||||||
|
case MGPWireOp::FenceStatus: return MGPipeVerb::GetSyncStatus;
|
||||||
|
case MGPWireOp::FenceWait: return MGPipeVerb::ClientWaitSync;
|
||||||
|
case MGPWireOp::FenceDestroy: return MGPipeVerb::DeleteSync;
|
||||||
|
case MGPWireOp::FenceWaitServer: return MGPipeVerb::WaitSync;
|
||||||
default:
|
default:
|
||||||
return MGPipeVerb::kVerbCount;
|
return MGPipeVerb::kVerbCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline constexpr SizeT kMGPipeVerbBoundaryOpCount = 18;
|
inline constexpr SizeT kMGPipeVerbBoundaryOpCount = 23;
|
||||||
inline constexpr SizeT kMGPipeVerbBoundaryExemptCount = 3;
|
inline constexpr SizeT kMGPipeVerbBoundaryExemptCount = 3;
|
||||||
|
|
||||||
// The class sizes, as constants a test can pin without recounting the table.
|
// The class sizes, as constants a test can pin without recounting the table.
|
||||||
|
|||||||
@@ -451,3 +451,32 @@ The wave-3 tail no P5b package owns, by name, so nobody discovers it by grep: `B
|
|||||||
- The backends (`MG_Backend/DirectGLES`, `DirectVulkan`): untouched by c0b; a package that
|
- The backends (`MG_Backend/DirectGLES`, `DirectVulkan`): untouched by c0b; a package that
|
||||||
must touch one does so behind `#if MOBILEGL_BUILD_DISAGGREGATED` and names the region in
|
must touch one does so behind `#if MOBILEGL_BUILD_DISAGGREGATED` and names the region in
|
||||||
its report (G1 admits no pull-build symbol motion; G5's untouched regions are pinned).
|
its report (G1 admits no pull-build symbol motion; G5's untouched regions are pinned).
|
||||||
|
|
||||||
|
|
||||||
|
## §9 Wave 3: fence sync (integrator-approved, 2026-09-16)
|
||||||
|
|
||||||
|
The dynamic Minecraft census exposes `FenceSync`. All five sync slots migrate together on
|
||||||
|
existing opcodes: FenceCreate/FenceStatus/FenceWait/FenceDestroy/FenceWaitServer. No opcode
|
||||||
|
moves and no creation reply is introduced. The client allocates a Fence-kind `{slot, gen}`;
|
||||||
|
a local opaque proxy satisfies the frontend's `BackendSyncHandle` API. Only the handle crosses
|
||||||
|
SEG_CMD. The server owns a generation-checked table of native backend sync handles, creates,
|
||||||
|
waits, queries and deletes them exclusively on the apply thread, and releases remaining native
|
||||||
|
objects before the backend is detached. Client orphan deletion after session shutdown only
|
||||||
|
releases its proxy. Duplicate creation and missing, destroyed or stale wire handles are protocol
|
||||||
|
corruption, since GL argument errors were already handled by the frontend.
|
||||||
|
|
||||||
|
`MGPFenceWait` grows 16 → 24 bytes: append `Uint32 Flags; Uint32 Pad0;` after TimeoutNs.
|
||||||
|
Flags preserves `GL_SYNC_FLUSH_COMMANDS_BIT` for ClientWaitSync; server WaitSync accepts only
|
||||||
|
zero flags and GL_TIMEOUT_IGNORED. Existing ABI fingerprinting rejects mixed layouts.
|
||||||
|
FenceStatus and FenceWait retain their existing reply slots: an OK reply is exactly one Uint32,
|
||||||
|
respectively 0/1 or the backend's GL wait enum. Missing backend declines and never manufactures
|
||||||
|
an OK result. A present backend with no FenceSync slot or a FenceSync call returning null uses
|
||||||
|
exactly GL_Sync.cpp's existing always-signaled fallback; a real native fence's failed/timeout
|
||||||
|
wait answer is returned unchanged. A missing native wait/status slot uses the same frontend
|
||||||
|
fallback. This is compatibility with existing monolith behavior, not an unconditional success.
|
||||||
|
|
||||||
|
All five opcodes stamp their matching MGPipeVerb (`FenceSync`, `GetSyncStatus`,
|
||||||
|
`ClientWaitSync`, `DeleteSync`, `WaitSync`). Every record keeps the verb barrier. The sole `GL_Sync.cpp` FenceSync guard changes from the class-C LOCAL macro to its original
|
||||||
|
table pointer expression: in a pull build these are identical. Under split it reaches the
|
||||||
|
class-B emitter, and the server preserves the optional-native fallback. No backend changes. The wave-3 tail's five slots move C → B; d1/i1/t2/f1 ownership
|
||||||
|
counts remain unchanged.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
//
|
//
|
||||||
// THE PARTITION IS CONTRACT-P5.md §7's AND IS NOT RE-DERIVED HERE (R-15, ID-12):
|
// THE PARTITION IS CONTRACT-P5.md §7's AND IS NOT RE-DERIVED HERE (R-15, ID-12):
|
||||||
// class A 2 slots answered locally from the caps mirror, never emitted, never Fatal
|
// class A 2 slots answered locally from the caps mirror, never emitted, never Fatal
|
||||||
// class B 49 slots emitted; class C 20 slots name their unmigrated verb.
|
// class B 54 slots emitted; class C 15 slots name their unmigrated verb.
|
||||||
// The three counts are static_asserted to sum to kRemoteEmitSlotCount below, so a slot that
|
// The three counts are static_asserted to sum to kRemoteEmitSlotCount below, so a slot that
|
||||||
// changes class without changing the arithmetic is a build break rather than a behaviour
|
// changes class without changing the arithmetic is a build break rather than a behaviour
|
||||||
// change nobody reviewed.
|
// change nobody reviewed.
|
||||||
@@ -1441,7 +1441,84 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// CLASS C - 21 slots remain after d1/i1/t2/f1; each names its first blocker.
|
// The frontend sees a local opaque token; neither this address nor the driver's
|
||||||
|
// BackendSyncHandle is serialized. The Fence-kind slot allocator supplies wire identity.
|
||||||
|
struct RemoteFenceProxy { MG_Pipe::MGPipeHandle Handle; };
|
||||||
|
std::mutex g_fenceMutex;
|
||||||
|
UnorderedMap<MG_Backend::BackendSyncHandle, UniquePtr<RemoteFenceProxy>> g_fenceProxies;
|
||||||
|
|
||||||
|
MG_Pipe::MGPipeHandle FenceHandle(MG_Backend::BackendSyncHandle proxy) {
|
||||||
|
const auto it = g_fenceProxies.find(proxy);
|
||||||
|
if (it == g_fenceProxies.end())
|
||||||
|
Wire::WireProtocolFatal("Fence.proxy", "unknown client fence proxy");
|
||||||
|
return it->second->Handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
MG_Backend::BackendSyncHandle EmitFenceSync() {
|
||||||
|
ClientSession& session = RequireSession("FenceSync");
|
||||||
|
const std::lock_guard<std::mutex> lock(g_fenceMutex);
|
||||||
|
BeforeReadOnlyVerb();
|
||||||
|
auto proxy = MakeUnique<RemoteFenceProxy>();
|
||||||
|
proxy->Handle = MG_Pipe::MGPipeSlots().Allocate(MG_Pipe::MGPipeKind::Fence);
|
||||||
|
const MG_Pipe::MGPHandleOnly desc{proxy->Handle, static_cast<Uint32>(MG_Pipe::MGPipeKind::Fence), 0};
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::FenceCreate, &desc, sizeof(desc),
|
||||||
|
nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
auto* local = proxy.get();
|
||||||
|
g_fenceProxies.emplace(local, std::move(proxy));
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 ReadFenceReply(ClientSession& session, MG_Pipe::MGPWireOp op,
|
||||||
|
const void* payload, Uint64 bytes) {
|
||||||
|
Uint32 result = 0;
|
||||||
|
Int32 status = Wire::ReplySink::kStatusError;
|
||||||
|
Uint64 replyBytes = 0;
|
||||||
|
session.EmitAndWait(op, payload, bytes, nullptr, 0, &result, sizeof(result),
|
||||||
|
&status, &replyBytes);
|
||||||
|
if (status != Wire::ReplySink::kStatusOk || replyBytes != sizeof(result))
|
||||||
|
Wire::WireProtocolFatal("Fence.reply", "missing or malformed sync result");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum EmitClientWaitSync(MG_Backend::BackendSyncHandle proxy, GLbitfield flags, GLuint64 timeout) {
|
||||||
|
ClientSession& session = RequireSession("ClientWaitSync");
|
||||||
|
const std::lock_guard<std::mutex> lock(g_fenceMutex);
|
||||||
|
const MG_Pipe::MGPFenceWait request{FenceHandle(proxy), timeout, flags, 0};
|
||||||
|
return ReadFenceReply(session, MG_Pipe::MGPWireOp::FenceWait, &request, sizeof(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool EmitGetSyncStatus(MG_Backend::BackendSyncHandle proxy) {
|
||||||
|
ClientSession& session = RequireSession("GetSyncStatus");
|
||||||
|
const std::lock_guard<std::mutex> lock(g_fenceMutex);
|
||||||
|
const MG_Pipe::MGPHandleOnly desc{FenceHandle(proxy), static_cast<Uint32>(MG_Pipe::MGPipeKind::Fence), 0};
|
||||||
|
const Uint32 result = ReadFenceReply(session, MG_Pipe::MGPWireOp::FenceStatus, &desc, sizeof(desc));
|
||||||
|
if (result > 1) Wire::WireProtocolFatal("FenceStatus.reply", "status must be boolean");
|
||||||
|
return result != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitWaitSync(MG_Backend::BackendSyncHandle proxy, GLbitfield flags, GLuint64 timeout) {
|
||||||
|
ClientSession& session = RequireSession("WaitSync");
|
||||||
|
const std::lock_guard<std::mutex> lock(g_fenceMutex);
|
||||||
|
const MG_Pipe::MGPFenceWait request{FenceHandle(proxy), timeout, flags, 0};
|
||||||
|
session.EmitAndWait(MG_Pipe::MGPWireOp::FenceWaitServer, &request, sizeof(request),
|
||||||
|
nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmitDeleteSync(MG_Backend::BackendSyncHandle proxy) {
|
||||||
|
const std::lock_guard<std::mutex> lock(g_fenceMutex);
|
||||||
|
const auto handle = FenceHandle(proxy);
|
||||||
|
// MobileGL::Destroy stops the server BEFORE DestroyAllSyncObjects. Detach already
|
||||||
|
// released those native objects on the apply thread; only the local proxy remains.
|
||||||
|
if (auto* session = ClientSession::Active(); session != nullptr && session->Started()) {
|
||||||
|
const MG_Pipe::MGPHandleOnly desc{handle, static_cast<Uint32>(MG_Pipe::MGPipeKind::Fence), 0};
|
||||||
|
session->EmitAndWait(MG_Pipe::MGPWireOp::FenceDestroy, &desc, sizeof(desc),
|
||||||
|
nullptr, 0, nullptr, 0, nullptr);
|
||||||
|
}
|
||||||
|
MG_Pipe::MGPipeSlots().Free(MG_Pipe::MGPipeKind::Fence, handle);
|
||||||
|
g_fenceProxies.erase(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLASS C - 16 slots remain after d1/i1/t2/f1; each names its first blocker.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
//
|
//
|
||||||
// PARTITIONED BY THE P5b PACKAGE THAT OWNS THE FLIP (MG_Remote/CONTRACT-P5B.md,
|
// PARTITIONED BY THE P5b PACKAGE THAT OWNS THE FLIP (MG_Remote/CONTRACT-P5B.md,
|
||||||
@@ -1501,8 +1578,6 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
X(GetTextureImage, void, \
|
X(GetTextureImage, void, \
|
||||||
(const SharedPtr<MG_State::GLState::ITextureObject>&, TextureUploadTarget, GLint, GLenum, \
|
(const SharedPtr<MG_State::GLState::ITextureObject>&, TextureUploadTarget, GLint, GLenum, \
|
||||||
GLenum, GLsizei, GLvoid*)) \
|
GLenum, GLsizei, GLvoid*)) \
|
||||||
X(WaitSync, void, (MG_Backend::BackendSyncHandle, GLbitfield, GLuint64)) \
|
|
||||||
X(DeleteSync, void, (MG_Backend::BackendSyncHandle)) \
|
|
||||||
X(EndTimeElapsedQuery, void, (MG_Backend::BackendQueryHandle)) \
|
X(EndTimeElapsedQuery, void, (MG_Backend::BackendQueryHandle)) \
|
||||||
X(DeleteBackendQuery, void, (MG_Backend::BackendQueryHandle)) \
|
X(DeleteBackendQuery, void, (MG_Backend::BackendQueryHandle)) \
|
||||||
X(EndOcclusionQuery, void, (MG_Backend::BackendQueryHandle)) \
|
X(EndOcclusionQuery, void, (MG_Backend::BackendQueryHandle)) \
|
||||||
@@ -1514,9 +1589,6 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
// warn on the second. It does see it; they are split for readability. All ten are the
|
// warn on the second. It does see it; they are split for readability. All ten are the
|
||||||
// wave-3 tail.
|
// wave-3 tail.
|
||||||
#define MGR_UNMIGRATED_TAIL_VALUE_SLOTS(X) \
|
#define MGR_UNMIGRATED_TAIL_VALUE_SLOTS(X) \
|
||||||
X(FenceSync, MG_Backend::BackendSyncHandle, ()) \
|
|
||||||
X(ClientWaitSync, GLenum, (MG_Backend::BackendSyncHandle, GLbitfield, GLuint64)) \
|
|
||||||
X(GetSyncStatus, Bool, (MG_Backend::BackendSyncHandle)) \
|
|
||||||
X(BeginTimeElapsedQuery, MG_Backend::BackendQueryHandle, ()) \
|
X(BeginTimeElapsedQuery, MG_Backend::BackendQueryHandle, ()) \
|
||||||
X(QueryCounterTimestamp, MG_Backend::BackendQueryHandle, ()) \
|
X(QueryCounterTimestamp, MG_Backend::BackendQueryHandle, ()) \
|
||||||
X(IsQueryResultAvailable, Bool, (MG_Backend::BackendQueryHandle)) \
|
X(IsQueryResultAvailable, Bool, (MG_Backend::BackendQueryHandle)) \
|
||||||
@@ -1570,9 +1642,10 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
constexpr Uint32 kEmittedSlotsT2 = 6;
|
constexpr Uint32 kEmittedSlotsT2 = 6;
|
||||||
constexpr Uint32 kEmittedSlotsF1 = 11;
|
constexpr Uint32 kEmittedSlotsF1 = 11;
|
||||||
constexpr Uint32 kEmittedSlotsTail = 1; // BlitNamedFramebuffer
|
constexpr Uint32 kEmittedSlotsTail = 1; // BlitNamedFramebuffer
|
||||||
|
constexpr Uint32 kEmittedSlotsSync = 5;
|
||||||
constexpr Uint32 kEmittedSlots =
|
constexpr Uint32 kEmittedSlots =
|
||||||
kEmittedSlotsP5 + kEmittedSlotsD1 + kEmittedSlotsI1 + kEmittedSlotsT2 + kEmittedSlotsF1 +
|
kEmittedSlotsP5 + kEmittedSlotsD1 + kEmittedSlotsI1 + kEmittedSlotsT2 + kEmittedSlotsF1 +
|
||||||
kEmittedSlotsTail;
|
kEmittedSlotsTail + kEmittedSlotsSync;
|
||||||
constexpr Uint32 kLocallyAnsweredSlots = 2; // GetIntegeri_v, IsTimerQuerySupported
|
constexpr Uint32 kLocallyAnsweredSlots = 2; // GetIntegeri_v, IsTimerQuerySupported
|
||||||
|
|
||||||
// EACH PACKAGE'S OWNERSHIP, PINNED. A package that flips a slot removes one row and
|
// EACH PACKAGE'S OWNERSHIP, PINNED. A package that flips a slot removes one row and
|
||||||
@@ -1583,7 +1656,8 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
static_assert(kUnmigratedI1 + kEmittedSlotsI1 == 7, "i1 owns the 7 image/compute/barrier/copy/SSBO slots");
|
static_assert(kUnmigratedI1 + kEmittedSlotsI1 == 7, "i1 owns the 7 image/compute/barrier/copy/SSBO slots");
|
||||||
static_assert(kUnmigratedT2 + kEmittedSlotsT2 == 7, "t2 owns the 7 XFB/tessellation slots");
|
static_assert(kUnmigratedT2 + kEmittedSlotsT2 == 7, "t2 owns the 7 XFB/tessellation slots");
|
||||||
static_assert(kUnmigratedF1 + kEmittedSlotsF1 == 11, "f1 owns the 11 clear/copy/mip slots");
|
static_assert(kUnmigratedF1 + kEmittedSlotsF1 == 11, "f1 owns the 11 clear/copy/mip slots");
|
||||||
static_assert(kUnmigratedTail + kEmittedSlotsTail == 20, "the original wave-3 tail owns 20 slots");
|
static_assert(kUnmigratedTail + kEmittedSlotsTail + kEmittedSlotsSync == 20,
|
||||||
|
"the original wave-3 tail owns 20 slots");
|
||||||
static_assert(kUnmigratedSlots + kEmittedSlots == 69, "class B and C own 69 slots");
|
static_assert(kUnmigratedSlots + kEmittedSlots == 69, "class B and C own 69 slots");
|
||||||
static_assert(kLocallyAnsweredSlots + kEmittedSlots + kUnmigratedSlots == kRemoteEmitSlotCount,
|
static_assert(kLocallyAnsweredSlots + kEmittedSlots + kUnmigratedSlots == kRemoteEmitSlotCount,
|
||||||
"the three classes no longer partition the 71 slots");
|
"the three classes no longer partition the 71 slots");
|
||||||
@@ -1603,6 +1677,12 @@ namespace MobileGL::MG_Remote::Client {
|
|||||||
#undef MGR_ASSIGN_UNMIGRATED
|
#undef MGR_ASSIGN_UNMIGRATED
|
||||||
table.SetSwapInterval = &SetSwapInterval_Unmigrated;
|
table.SetSwapInterval = &SetSwapInterval_Unmigrated;
|
||||||
|
|
||||||
|
table.GL.FenceSync = &EmitFenceSync;
|
||||||
|
table.GL.ClientWaitSync = &EmitClientWaitSync;
|
||||||
|
table.GL.GetSyncStatus = &EmitGetSyncStatus;
|
||||||
|
table.GL.WaitSync = &EmitWaitSync;
|
||||||
|
table.GL.DeleteSync = &EmitDeleteSync;
|
||||||
|
|
||||||
// ---- class A
|
// ---- class A
|
||||||
table.GL.GetIntegeri_v = &AnswerGetIntegeri_v;
|
table.GL.GetIntegeri_v = &AnswerGetIntegeri_v;
|
||||||
table.GL.IsTimerQuerySupported = &AnswerIsTimerQuerySupported;
|
table.GL.IsTimerQuerySupported = &AnswerIsTimerQuerySupported;
|
||||||
|
|||||||
@@ -52,11 +52,8 @@
|
|||||||
// class-C slot Fatals by name instead.
|
// class-C slot Fatals by name instead.
|
||||||
//
|
//
|
||||||
// Converted, because the fallback is right:
|
// Converted, because the fallback is right:
|
||||||
// GL_Sync.cpp:59 FenceSync -> always-signaled syncs, which the table's own
|
// FenceSync used this fallback through P5. P5b §9 moved it to class B: the frontend
|
||||||
// header documents as the fallback and which GL
|
// now calls the emitter and the server preserves the optional/native-null fallback.
|
||||||
// permits; every other sync site is already
|
|
||||||
// guarded on syncObject->backendHandle, so this
|
|
||||||
// one gate carries the whole family.
|
|
||||||
// GL_Texture.cpp:6537 GetTextureImage -> the frontend's own CPU readback, which is exact
|
// GL_Texture.cpp:6537 GetTextureImage -> the frontend's own CPU readback, which is exact
|
||||||
// GL_Texture.cpp:6799 GetTexImage -> the same
|
// GL_Texture.cpp:6799 GetTexImage -> the same
|
||||||
// GL_Getter.cpp x2 GetGpuTimestampNs -> 0, which BackendObject.h:192 already names as
|
// GL_Getter.cpp x2 GetGpuTimestampNs -> 0, which BackendObject.h:192 already names as
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
// ServerVerbSink - the five class-B verbs
|
// ServerVerbSink - the five class-B verbs
|
||||||
// -----------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------
|
||||||
|
|
||||||
void ServerVerbSink::SetBackend(MG_Backend::BackendObject* backend) { m_backend = backend; }
|
void ServerVerbSink::SetBackend(MG_Backend::BackendObject* backend) {
|
||||||
|
if (m_backend != backend) ReleaseFences();
|
||||||
|
m_backend = backend;
|
||||||
|
}
|
||||||
|
|
||||||
const MG_Backend::GlobalBackendFunctionsTable* ServerVerbSink::Table(const char* verb) const {
|
const MG_Backend::GlobalBackendFunctionsTable* ServerVerbSink::Table(const char* verb) const {
|
||||||
if (m_backend == nullptr) {
|
if (m_backend == nullptr) {
|
||||||
@@ -68,6 +71,96 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
return &m_backend->GetBackendFunctions();
|
return &m_backend->GetBackendFunctions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ServerVerbSink::FenceEntry& ServerVerbSink::FindFence(MG_Pipe::MGPipeHandle handle) {
|
||||||
|
const auto it = m_fences.find(handle.Slot);
|
||||||
|
if (handle.Slot == 0 || it == m_fences.end() ||
|
||||||
|
!it->second.Live || it->second.Gen != handle.Gen) {
|
||||||
|
Wire::WireProtocolFatal("Fence.handle", "missing, destroyed or stale fence handle");
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnFenceCreate(const MG_Pipe::MGPHandleOnly& desc) {
|
||||||
|
if (desc.Kind != static_cast<Uint32>(MG_Pipe::MGPipeKind::Fence))
|
||||||
|
Wire::WireProtocolFatal("Fence.Kind", "expected Fence namespace");
|
||||||
|
const auto* table = Table("FenceCreate");
|
||||||
|
if (table == nullptr) return false;
|
||||||
|
const auto handle = desc.Handle;
|
||||||
|
if (handle.Slot == 0) {
|
||||||
|
Wire::WireProtocolFatal("FenceCreate.handle", "reserved fence handle");
|
||||||
|
}
|
||||||
|
const Bool seen = m_fences.find(handle.Slot) != m_fences.end();
|
||||||
|
auto& entry = m_fences[handle.Slot];
|
||||||
|
if (entry.Live || (seen && handle.Gen <= entry.Gen)) {
|
||||||
|
Wire::WireProtocolFatal("FenceCreate.handle", "duplicate or stale fence generation");
|
||||||
|
}
|
||||||
|
entry.Gen = handle.Gen;
|
||||||
|
entry.Live = true;
|
||||||
|
// GL_Sync.cpp treats an absent slot or a null creation result as always signaled.
|
||||||
|
entry.Native = table->GL.FenceSync == nullptr ? nullptr : table->GL.FenceSync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnFenceDestroy(const MG_Pipe::MGPHandleOnly& desc) {
|
||||||
|
if (desc.Kind != static_cast<Uint32>(MG_Pipe::MGPipeKind::Fence))
|
||||||
|
Wire::WireProtocolFatal("Fence.Kind", "expected Fence namespace");
|
||||||
|
auto& entry = FindFence(desc.Handle);
|
||||||
|
const auto* table = Table("FenceDestroy");
|
||||||
|
if (table == nullptr) return false;
|
||||||
|
if (entry.Native != nullptr && table->GL.DeleteSync != nullptr) table->GL.DeleteSync(entry.Native);
|
||||||
|
entry.Native = nullptr;
|
||||||
|
entry.Live = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnFenceStatus(const MG_Pipe::MGPHandleOnly& desc, Uint32& result) {
|
||||||
|
if (desc.Kind != static_cast<Uint32>(MG_Pipe::MGPipeKind::Fence))
|
||||||
|
Wire::WireProtocolFatal("Fence.Kind", "expected Fence namespace");
|
||||||
|
auto& entry = FindFence(desc.Handle);
|
||||||
|
const auto* table = Table("FenceStatus");
|
||||||
|
if (table == nullptr) return false;
|
||||||
|
result = entry.Native == nullptr || table->GL.GetSyncStatus == nullptr ||
|
||||||
|
table->GL.GetSyncStatus(entry.Native);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnFenceWait(const MG_Pipe::MGPFenceWait& request, Uint32& result) {
|
||||||
|
if ((request.Flags & ~static_cast<Uint32>(GL_SYNC_FLUSH_COMMANDS_BIT)) != 0) {
|
||||||
|
Wire::WireProtocolFatal("FenceWait.Flags", "unknown client-wait flag");
|
||||||
|
}
|
||||||
|
auto& entry = FindFence(request.Fence);
|
||||||
|
const auto* table = Table("FenceWait");
|
||||||
|
if (table == nullptr) return false;
|
||||||
|
result = entry.Native == nullptr || table->GL.ClientWaitSync == nullptr
|
||||||
|
? GL_ALREADY_SIGNALED
|
||||||
|
: table->GL.ClientWaitSync(entry.Native, request.Flags, request.TimeoutNs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ServerVerbSink::OnFenceWaitServer(const MG_Pipe::MGPFenceWait& request) {
|
||||||
|
if (request.Flags != 0 || request.TimeoutNs != GL_TIMEOUT_IGNORED) {
|
||||||
|
Wire::WireProtocolFatal("FenceWaitServer.arguments", "invalid server wait arguments");
|
||||||
|
}
|
||||||
|
auto& entry = FindFence(request.Fence);
|
||||||
|
const auto* table = Table("FenceWaitServer");
|
||||||
|
if (table == nullptr) return false;
|
||||||
|
if (entry.Native != nullptr && table->GL.WaitSync != nullptr)
|
||||||
|
table->GL.WaitSync(entry.Native, request.Flags, request.TimeoutNs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerVerbSink::ReleaseFences() {
|
||||||
|
// Detach runs on the apply thread before its private backend/context is destroyed.
|
||||||
|
if (m_backend != nullptr) {
|
||||||
|
const auto destroy = m_backend->GetBackendFunctions().GL.DeleteSync;
|
||||||
|
if (destroy != nullptr) {
|
||||||
|
for (auto& [slot, entry] : m_fences)
|
||||||
|
if (entry.Live && entry.Native != nullptr) destroy(entry.Native);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_fences.clear();
|
||||||
|
}
|
||||||
|
|
||||||
Bool ServerVerbSink::OnClear(const MG_Pipe::MGPClear& clear) {
|
Bool ServerVerbSink::OnClear(const MG_Pipe::MGPClear& clear) {
|
||||||
const MG_Backend::GlobalBackendFunctionsTable* table = Table("clear");
|
const MG_Backend::GlobalBackendFunctionsTable* table = Table("clear");
|
||||||
if (table == nullptr) return false;
|
if (table == nullptr) return false;
|
||||||
|
|||||||
@@ -102,6 +102,12 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
// verb that arrives before then declines by name rather than dereferencing.
|
// verb that arrives before then declines by name rather than dereferencing.
|
||||||
void SetBackend(MG_Backend::BackendObject* backend);
|
void SetBackend(MG_Backend::BackendObject* backend);
|
||||||
|
|
||||||
|
Bool OnFenceCreate(const MG_Pipe::MGPHandleOnly&) override;
|
||||||
|
Bool OnFenceDestroy(const MG_Pipe::MGPHandleOnly&) override;
|
||||||
|
Bool OnFenceStatus(const MG_Pipe::MGPHandleOnly&, Uint32&) override;
|
||||||
|
Bool OnFenceWait(const MG_Pipe::MGPFenceWait&, Uint32&) override;
|
||||||
|
Bool OnFenceWaitServer(const MG_Pipe::MGPFenceWait&) override;
|
||||||
|
void ReleaseFences();
|
||||||
Bool OnClear(const MG_Pipe::MGPClear& clear) override;
|
Bool OnClear(const MG_Pipe::MGPClear& clear) override;
|
||||||
Bool OnBlit(const MG_Pipe::MGPBlit& blit) override;
|
Bool OnBlit(const MG_Pipe::MGPBlit& blit) override;
|
||||||
Bool OnPresent(const MG_Pipe::MGPPresent& present) override;
|
Bool OnPresent(const MG_Pipe::MGPPresent& present) override;
|
||||||
@@ -201,6 +207,13 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
private:
|
private:
|
||||||
const MG_Backend::GlobalBackendFunctionsTable* Table(const char* verb) const;
|
const MG_Backend::GlobalBackendFunctionsTable* Table(const char* verb) const;
|
||||||
|
|
||||||
|
struct FenceEntry {
|
||||||
|
Uint32 Gen = 0;
|
||||||
|
Bool Live = false;
|
||||||
|
MG_Backend::BackendSyncHandle Native = nullptr;
|
||||||
|
};
|
||||||
|
FenceEntry& FindFence(MG_Pipe::MGPipeHandle handle);
|
||||||
|
UnorderedMap<Uint32, FenceEntry> m_fences;
|
||||||
MG_Backend::BackendObject* m_backend = nullptr;
|
MG_Backend::BackendObject* m_backend = nullptr;
|
||||||
Uint64 m_clears = 0;
|
Uint64 m_clears = 0;
|
||||||
Uint64 m_draws = 0;
|
Uint64 m_draws = 0;
|
||||||
|
|||||||
@@ -1537,12 +1537,24 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
MGPipeApplyUnmapPersistent(*static_cast<const MGPHandleOnly*>(payload));
|
MGPipeApplyUnmapPersistent(*static_cast<const MGPHandleOnly*>(payload));
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// ---- fences and queries: off the reduced path (BRIEF §4) -------------------------
|
|
||||||
case MGPWireOp::FenceCreate:
|
case MGPWireOp::FenceCreate:
|
||||||
case MGPWireOp::FenceStatus:
|
return m_verbs != nullptr && m_verbs->OnFenceCreate(*static_cast<const MGPHandleOnly*>(payload));
|
||||||
case MGPWireOp::FenceWait:
|
|
||||||
case MGPWireOp::FenceDestroy:
|
case MGPWireOp::FenceDestroy:
|
||||||
|
return m_verbs != nullptr && m_verbs->OnFenceDestroy(*static_cast<const MGPHandleOnly*>(payload));
|
||||||
case MGPWireOp::FenceWaitServer:
|
case MGPWireOp::FenceWaitServer:
|
||||||
|
return m_verbs != nullptr && m_verbs->OnFenceWaitServer(*static_cast<const MGPFenceWait*>(payload));
|
||||||
|
case MGPWireOp::FenceStatus:
|
||||||
|
case MGPWireOp::FenceWait: {
|
||||||
|
Uint32 result = 0;
|
||||||
|
const Bool ok = m_verbs != nullptr &&
|
||||||
|
(op == MGPWireOp::FenceStatus
|
||||||
|
? m_verbs->OnFenceStatus(*static_cast<const MGPHandleOnly*>(payload), result)
|
||||||
|
: m_verbs->OnFenceWait(*static_cast<const MGPFenceWait*>(payload), result));
|
||||||
|
PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined,
|
||||||
|
ok ? &result : nullptr, ok ? sizeof(result) : 0);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
// Query migration follows the measured first blockers.
|
||||||
case MGPWireOp::QueryCreate:
|
case MGPWireOp::QueryCreate:
|
||||||
case MGPWireOp::QueryBegin:
|
case MGPWireOp::QueryBegin:
|
||||||
case MGPWireOp::QueryEnd:
|
case MGPWireOp::QueryEnd:
|
||||||
|
|||||||
@@ -414,6 +414,11 @@ namespace MobileGL::MG_Remote::Wire {
|
|||||||
class WireVerbSink {
|
class WireVerbSink {
|
||||||
public:
|
public:
|
||||||
virtual ~WireVerbSink() = default;
|
virtual ~WireVerbSink() = default;
|
||||||
|
virtual Bool OnFenceCreate(const MG_Pipe::MGPHandleOnly&) { return false; }
|
||||||
|
virtual Bool OnFenceDestroy(const MG_Pipe::MGPHandleOnly&) { return false; }
|
||||||
|
virtual Bool OnFenceStatus(const MG_Pipe::MGPHandleOnly&, Uint32&) { return false; }
|
||||||
|
virtual Bool OnFenceWait(const MG_Pipe::MGPFenceWait&, Uint32&) { return false; }
|
||||||
|
virtual Bool OnFenceWaitServer(const MG_Pipe::MGPFenceWait&) { return false; }
|
||||||
virtual Bool OnClear(const MG_Pipe::MGPClear& clear) {
|
virtual Bool OnClear(const MG_Pipe::MGPClear& clear) {
|
||||||
(void)clear;
|
(void)clear;
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ TEST_F(FieldOwnershipTest, VerbBoundaryOpsCoverEveryVerbShapedCall) {
|
|||||||
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::SetStorageBlockBinding),
|
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::SetStorageBlockBinding),
|
||||||
MGPipeVerb::ShaderStorageBlockBinding);
|
MGPipeVerb::ShaderStorageBlockBinding);
|
||||||
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::CopyFramebufferToTexture), MGPipeVerb::CopyTexImage2D);
|
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::CopyFramebufferToTexture), MGPipeVerb::CopyTexImage2D);
|
||||||
EXPECT_EQ(kMGPipeVerbBoundaryOpCount, SizeT{18});
|
EXPECT_EQ(kMGPipeVerbBoundaryOpCount, SizeT{23});
|
||||||
EXPECT_EQ(kMGPipeVerbBoundaryExemptCount, SizeT{3});
|
EXPECT_EQ(kMGPipeVerbBoundaryExemptCount, SizeT{3});
|
||||||
|
|
||||||
// Present is class B (it is emitted in P5) and is STILL not a verb boundary:
|
// Present is class B (it is emitted in P5) and is STILL not a verb boundary:
|
||||||
|
|||||||
@@ -36,6 +36,8 @@
|
|||||||
|
|
||||||
// MG_Config::Transport and MG_Config::Ipc.AdoptTier: the two knobs R-6's tier gate reads.
|
// MG_Config::Transport and MG_Config::Ipc.AdoptTier: the two knobs R-6's tier gate reads.
|
||||||
#include <Config.h>
|
#include <Config.h>
|
||||||
|
#include <MG_Remote/Server/PipeApplier.h>
|
||||||
|
#include <MG_Backend/DirectGLES/BackendObject_DirectGLES.h>
|
||||||
#include <MG_Pipe/MGPipe.h>
|
#include <MG_Pipe/MGPipe.h>
|
||||||
#include <MG_Pipe/MGPipeRenderStateSpans.h>
|
#include <MG_Pipe/MGPipeRenderStateSpans.h>
|
||||||
#include <MG_Pipe/PipeApply.h>
|
#include <MG_Pipe/PipeApply.h>
|
||||||
@@ -2636,3 +2638,140 @@ int main(int argc, char** argv) {
|
|||||||
fs::remove(path, ec);
|
fs::remove(path, ec);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct FenceProbeBackend final : MG_Backend::DirectGLES::BackendObject_DirectGLES {
|
||||||
|
MG_Backend::GlobalBackendFunctionsTable Table{};
|
||||||
|
const MG_Backend::GlobalBackendFunctionsTable& GetBackendFunctions() const override { return Table; }
|
||||||
|
};
|
||||||
|
Uint32 fenceDeletes = 0;
|
||||||
|
Uint32 fenceServerWaits = 0;
|
||||||
|
Uint32 fenceFlags = 0;
|
||||||
|
Uint64 fenceTimeout = 0;
|
||||||
|
int nativeFenceToken = 0;
|
||||||
|
|
||||||
|
void InstallFenceProbe(FenceProbeBackend& backend) {
|
||||||
|
fenceDeletes = fenceServerWaits = fenceFlags = 0;
|
||||||
|
fenceTimeout = 0;
|
||||||
|
backend.Table.GL.FenceSync = +[]() -> MG_Backend::BackendSyncHandle { return &nativeFenceToken; };
|
||||||
|
backend.Table.GL.ClientWaitSync = +[](MG_Backend::BackendSyncHandle native, GLbitfield flags,
|
||||||
|
GLuint64 timeout) -> GLenum {
|
||||||
|
EXPECT_EQ(native, &nativeFenceToken);
|
||||||
|
fenceFlags = flags;
|
||||||
|
fenceTimeout = timeout;
|
||||||
|
return GL_TIMEOUT_EXPIRED;
|
||||||
|
};
|
||||||
|
backend.Table.GL.GetSyncStatus = +[](MG_Backend::BackendSyncHandle native) -> Bool {
|
||||||
|
EXPECT_EQ(native, &nativeFenceToken);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
backend.Table.GL.WaitSync = +[](MG_Backend::BackendSyncHandle native, GLbitfield flags, GLuint64 timeout) {
|
||||||
|
EXPECT_EQ(native, &nativeFenceToken);
|
||||||
|
EXPECT_EQ(flags, 0u);
|
||||||
|
EXPECT_EQ(timeout, GL_TIMEOUT_IGNORED);
|
||||||
|
++fenceServerWaits;
|
||||||
|
};
|
||||||
|
backend.Table.GL.DeleteSync = +[](MG_Backend::BackendSyncHandle native) {
|
||||||
|
EXPECT_EQ(native, &nativeFenceToken);
|
||||||
|
++fenceDeletes;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FenceWireRoundTrip, PreservesWaitFlagsTimeoutAnswersAndNativeLifetime) {
|
||||||
|
Wire2 wire;
|
||||||
|
FenceProbeBackend backend;
|
||||||
|
InstallFenceProbe(backend);
|
||||||
|
Server::ServerVerbSink sink;
|
||||||
|
sink.SetBackend(&backend);
|
||||||
|
wire.Decoder().SetVerbSink(&sink);
|
||||||
|
const MGPHandleOnly fence{{41, 0}, static_cast<Uint32>(MGPipeKind::Fence), 0};
|
||||||
|
auto send = [&](MGPWireOp op, const auto& payload) {
|
||||||
|
const auto seq = wire.Encoder().EncodeRecord(op, &payload, sizeof(payload));
|
||||||
|
EXPECT_NE(seq, kInvalidSeq);
|
||||||
|
bool applied = false;
|
||||||
|
EXPECT_TRUE(wire.PumpOne(&applied));
|
||||||
|
EXPECT_TRUE(applied);
|
||||||
|
return seq;
|
||||||
|
};
|
||||||
|
send(MGPWireOp::FenceCreate, fence);
|
||||||
|
EXPECT_TRUE(wire.Answers().All.empty());
|
||||||
|
const MGPFenceWait wait{fence.Handle, 0x123456789ull, GL_SYNC_FLUSH_COMMANDS_BIT, 0};
|
||||||
|
const auto waitSeq = send(MGPWireOp::FenceWait, wait);
|
||||||
|
ASSERT_EQ(wire.Answers().All.size(), 1u);
|
||||||
|
const auto& answer = wire.Answers().All.back();
|
||||||
|
EXPECT_EQ(answer.Seq, waitSeq);
|
||||||
|
EXPECT_EQ(answer.Status, ReplySink::kStatusOk);
|
||||||
|
ASSERT_EQ(answer.Bytes.size(), sizeof(Uint32));
|
||||||
|
Uint32 value = 0;
|
||||||
|
std::memcpy(&value, answer.Bytes.data(), sizeof(value));
|
||||||
|
EXPECT_EQ(value, GL_TIMEOUT_EXPIRED); // a real timeout must never become signaled
|
||||||
|
EXPECT_EQ(fenceFlags, GL_SYNC_FLUSH_COMMANDS_BIT);
|
||||||
|
EXPECT_EQ(fenceTimeout, wait.TimeoutNs);
|
||||||
|
send(MGPWireOp::FenceStatus, fence);
|
||||||
|
std::memcpy(&value, wire.Answers().All.back().Bytes.data(), sizeof(value));
|
||||||
|
EXPECT_EQ(value, 0u);
|
||||||
|
send(MGPWireOp::FenceWaitServer, MGPFenceWait{fence.Handle, GL_TIMEOUT_IGNORED, 0, 0});
|
||||||
|
EXPECT_EQ(fenceServerWaits, 1u);
|
||||||
|
send(MGPWireOp::FenceDestroy, fence);
|
||||||
|
EXPECT_EQ(fenceDeletes, 1u);
|
||||||
|
const MGPHandleOnly replacement{{41, 1}, static_cast<Uint32>(MGPipeKind::Fence), 0};
|
||||||
|
send(MGPWireOp::FenceCreate, replacement);
|
||||||
|
sink.SetBackend(nullptr); // orphan cleanup occurs before backend destruction
|
||||||
|
EXPECT_EQ(fenceDeletes, 2u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FenceWireRoundTrip, NullNativeFenceUsesOnlyTheExistingMonolithFallback) {
|
||||||
|
Wire2 wire;
|
||||||
|
FenceProbeBackend backend;
|
||||||
|
InstallFenceProbe(backend);
|
||||||
|
backend.Table.GL.FenceSync = +[]() -> MG_Backend::BackendSyncHandle { return nullptr; };
|
||||||
|
Server::ServerVerbSink sink;
|
||||||
|
sink.SetBackend(&backend);
|
||||||
|
wire.Decoder().SetVerbSink(&sink);
|
||||||
|
const MGPHandleOnly fence{{1, 1}, static_cast<Uint32>(MGPipeKind::Fence), 0};
|
||||||
|
ASSERT_TRUE(sink.OnFenceCreate(fence));
|
||||||
|
Uint32 result = 0;
|
||||||
|
EXPECT_TRUE(sink.OnFenceWait({fence.Handle, 0, 0, 0}, result));
|
||||||
|
EXPECT_EQ(result, GL_ALREADY_SIGNALED);
|
||||||
|
EXPECT_TRUE(sink.OnFenceStatus(fence, result));
|
||||||
|
EXPECT_EQ(result, 1u);
|
||||||
|
EXPECT_TRUE(sink.OnFenceDestroy(fence));
|
||||||
|
EXPECT_EQ(fenceDeletes, 0u);
|
||||||
|
sink.SetBackend(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MGTEST_HAVE_FORK
|
||||||
|
TEST(FenceWireRoundTrip, DestroyedAndRecycledWireHandlesNeverReachTheBackend) {
|
||||||
|
const auto r = RunInChild([] {
|
||||||
|
FenceProbeBackend backend;
|
||||||
|
InstallFenceProbe(backend);
|
||||||
|
Server::ServerVerbSink sink;
|
||||||
|
sink.SetBackend(&backend);
|
||||||
|
const MGPHandleOnly old{{9, 0}, static_cast<Uint32>(MGPipeKind::Fence), 0};
|
||||||
|
sink.OnFenceCreate(old);
|
||||||
|
sink.OnFenceDestroy(old);
|
||||||
|
sink.OnFenceCreate({{9, 1}, static_cast<Uint32>(MGPipeKind::Fence), 0});
|
||||||
|
Uint32 result = 0;
|
||||||
|
sink.OnFenceWait({old.Handle, 0, 0, 0}, result);
|
||||||
|
});
|
||||||
|
ASSERT_TRUE(DiedOfAbort(r));
|
||||||
|
EXPECT_NE(r.Log.find("Fence.handle"), std::string::npos) << r.Log;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
TEST(FenceWireRoundTrip, MissingConsumerDeclinesWithoutInventingASignaledAnswer) {
|
||||||
|
Wire2 wire;
|
||||||
|
const MGPFenceWait wait{{3, 0}, 0, 0, 0};
|
||||||
|
const auto seq = wire.Encoder().EncodeRecord(MGPWireOp::FenceWait, &wait, sizeof(wait));
|
||||||
|
ASSERT_NE(seq, kInvalidSeq);
|
||||||
|
bool applied = true;
|
||||||
|
ASSERT_TRUE(wire.PumpOne(&applied));
|
||||||
|
EXPECT_FALSE(applied);
|
||||||
|
ASSERT_EQ(wire.Answers().All.size(), 1u);
|
||||||
|
EXPECT_EQ(wire.Answers().All[0].Seq, seq);
|
||||||
|
EXPECT_EQ(wire.Answers().All[0].Status, ReplySink::kStatusDeclined);
|
||||||
|
EXPECT_TRUE(wire.Answers().All[0].Bytes.empty());
|
||||||
|
}
|
||||||
|
|||||||
@@ -177,8 +177,8 @@ namespace {
|
|||||||
TEST(RemoteEmitTable, TheThreeClassesPartitionAllSeventyOneSlots) {
|
TEST(RemoteEmitTable, TheThreeClassesPartitionAllSeventyOneSlots) {
|
||||||
// P5 baseline five + f1 eleven + i1 seven + t2 six emitted slots.
|
// P5 baseline five + f1 eleven + i1 seven + t2 six emitted slots.
|
||||||
EXPECT_EQ(LocallyAnsweredSlotCount(), 2u);
|
EXPECT_EQ(LocallyAnsweredSlotCount(), 2u);
|
||||||
EXPECT_EQ(ImplementedVerbCount(), 49u);
|
EXPECT_EQ(ImplementedVerbCount(), 54u);
|
||||||
EXPECT_EQ(UnmigratedSlotCount(), 20u);
|
EXPECT_EQ(UnmigratedSlotCount(), 15u);
|
||||||
EXPECT_EQ(LocallyAnsweredSlotCount() + ImplementedVerbCount() + UnmigratedSlotCount(),
|
EXPECT_EQ(LocallyAnsweredSlotCount() + ImplementedVerbCount() + UnmigratedSlotCount(),
|
||||||
kRemoteEmitSlotCount);
|
kRemoteEmitSlotCount);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user