mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
Compare commits
2
Commits
5f9c3d6947
...
e4aab8d0bc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4aab8d0bc | ||
|
|
745f59bc3f |
@@ -164,6 +164,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return VkBufferManager::SelfTestWireImport(why, whyBytes);
|
||||
},
|
||||
#endif
|
||||
// T0 in one process: the client gets the store's own mapping, as monolith's
|
||||
// donation does; the client seeds it from its shadow once this returns.
|
||||
.DonateInProcess = [](MG_Pipe::MGPipeHandle res, Uint64 size) -> void* {
|
||||
auto* m = WireManager("donate");
|
||||
return m != nullptr ? m->DonateWireBuffer(res, size, nullptr) : nullptr;
|
||||
},
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -1659,7 +1659,10 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// A split client's backend is the remote one, which forwards the shared-image verbs to
|
||||
// the server; the server's own apply thread and a monolith have no allocator behind them.
|
||||
return StreamGateActive() && MG_Backend::pActiveBackendObject != nullptr;
|
||||
// Nor has inproc: shared images carry memory between processes, and an inproc server is
|
||||
// this process (its server refuses them too - ServerVerbSink::OnSharedImage).
|
||||
return StreamGateActive() && MG_Config::Transport != MG_Config::TransportMode::InProcess &&
|
||||
MG_Backend::pActiveBackendObject != nullptr;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
|
||||
@@ -2574,8 +2574,8 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
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")
|
||||
# P11 B2 (CONTRACT-P11 B2): THE SHARED-SEGMENT ARMS WITH MOBILEGL_IPC_ADOPT_TIER=0, which A1 made
|
||||
# die at the handshake and B2 turned into an ASK. A host has no AHardwareBuffer, so the server
|
||||
# refuses T0 by name at its first native bind, publishes no kCapAdoptT0, and the client falls
|
||||
# die at the handshake and B2 turned into an ASK. A host has no AHardwareBuffer (and Espryt cannot
|
||||
# hand an in-process client its stores), so the server refuses T0 by name at its first native bind, publishes no kCapAdoptT0, and the client falls
|
||||
# back to T2 with one named line at its first map_persistent: every case green, the arena
|
||||
# emulated (SplitLogPaths declares it), ASessionThatCannotRunT0FallsBackToT2OnceByName reading
|
||||
# the two lines. `.AdoptT0Disallowed.` is the same on the spawn arm with the server's allow switch
|
||||
@@ -2583,6 +2583,13 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
foreach(mglT0Arm IN ITEMS Split Spawn)
|
||||
string(TOUPPER "${mglT0Arm}" mglT0ArmUpper)
|
||||
string(TOLOWER "${mglT0Arm}" mglT0ArmLower)
|
||||
# Split is one process: Espryt cannot hand its client its stores, and an in-process session
|
||||
# never takes the AHardwareBuffer arm, so the refusal names that rather than the platform.
|
||||
if(mglT0Arm STREQUAL "Split")
|
||||
set(mglT0Fallback "in process")
|
||||
else()
|
||||
set(mglT0Fallback "no AHardwareBuffer")
|
||||
endif()
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectGLES.${mglT0Arm}.AdoptTier0."
|
||||
TEST_LIST "MGL_SPLIT_ARM_TESTS_${MGL_SPLIT_ARM_INDEX}"
|
||||
@@ -2591,7 +2598,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
PROPERTIES
|
||||
LABELS "integration-gpu\;integration-${mglT0ArmLower}"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
ENVIRONMENT "${MGL_ITEST_GLES_${mglT0ArmUpper}_ENVIRONMENT}\;MOBILEGL_IPC_ADOPT_TIER=0\;MGITEST_ADOPT_T0_FALLBACK=no AHardwareBuffer"
|
||||
ENVIRONMENT "${MGL_ITEST_GLES_${mglT0ArmUpper}_ENVIRONMENT}\;MOBILEGL_IPC_ADOPT_TIER=0\;MGITEST_ADOPT_T0_FALLBACK=${mglT0Fallback}"
|
||||
)
|
||||
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")
|
||||
@@ -4090,6 +4097,14 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
mgl_itest_magma_arm_shape(${mglItestArm})
|
||||
mgl_itest_join_environment(mglItestMagmaBandEnvironment
|
||||
${MGL_ITEST_MAGMA_BAND_FLAVOUR_ENV} ${mglItestArmTransportEnv})
|
||||
# Where the >= 16 MiB arena lands on this arm (P11 A2). Split is one process: T0 (the default
|
||||
# tier) is granted in process and the arena is the store Magma hands its client - adopted.
|
||||
# Across processes a host has no shareable memory, so the session runs T2 - emulated.
|
||||
if("${mglItestArm}" STREQUAL "Split")
|
||||
set(mglItestArenaArm adopted)
|
||||
else()
|
||||
set(mglItestArenaArm emulated)
|
||||
endif()
|
||||
foreach(bandCase RenderbufferReadOfExactlyTwoMiBIsBandedNotRefused
|
||||
TwoMiBPlusOneRowIsBanded
|
||||
AReadLargerThanTheWholeReplySegmentIsBanded
|
||||
@@ -4137,9 +4152,8 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
ENVIRONMENT "${mglItestMagmaBandEnvironment}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p10-fence${mglItestArmLogTag}-${fenceCase}.log")
|
||||
endforeach()
|
||||
# P11 A2 (MG_Remote/CONTRACT-P11.md §2): the >= 16 MiB arena on Magma's three arms, in the
|
||||
# same flavour. Declared emulated: every split acquisition declines (the wire resource
|
||||
# table's MapPersistent returns null, VkBufferManager.cpp), so the tier case asserts T2
|
||||
# on every arm; DirectVulkan.ArenaArm. is its monolith (adopted) half.
|
||||
# same flavour, declared per arm (mglItestArenaArm above); DirectVulkan.ArenaArm. is its
|
||||
# monolith (adopted) half.
|
||||
foreach(arenaCase IN LISTS MGL_ITEST_ARENA_CASES)
|
||||
gtest_discover_tests(MobileGLIntegrationTest
|
||||
TEST_PREFIX "DirectVulkan.${mglItestArm}.Arena."
|
||||
@@ -4147,7 +4161,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestMagmaBandEnvironment}\;MGITEST_PERSISTENT_MAP_ARM=emulated\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p11-arena${mglItestArmLogTag}-${arenaCase}.log")
|
||||
ENVIRONMENT "${mglItestMagmaBandEnvironment}\;MGITEST_PERSISTENT_MAP_ARM=${mglItestArenaArm}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p11-arena${mglItestArmLogTag}-${arenaCase}.log")
|
||||
endforeach()
|
||||
# P11 B2 addendum: DeletedBoundBufferScenario on Magma's three arms, same flavour (the
|
||||
# Espryt arms come from mgl_itest_register_split_arms). Before the frontend fix Magma did
|
||||
@@ -4167,7 +4181,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
endforeach()
|
||||
# ...and the one-process arena pair (the Espryt half is beside mgl_itest_register_split_arms).
|
||||
string(REPLACE "\\;" ";" mglItestPairEnv "${mglItestMagmaBandEnvironment}")
|
||||
list(APPEND mglItestPairEnv "MGITEST_PERSISTENT_MAP_ARM=emulated"
|
||||
list(APPEND mglItestPairEnv "MGITEST_PERSISTENT_MAP_ARM=${mglItestArenaArm}"
|
||||
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p11-arena-pair${mglItestArmLogTag}.log")
|
||||
string(REPLACE "\\;" ";" mglItestPairLabels "${mglItestMagmaLabels}")
|
||||
set(mglItestPairName "DirectVulkan.${mglItestArm}.Arena.LargeArenaAdoptionScenario.GpuWriteThenTheTierCaseInOneProcess")
|
||||
@@ -4181,13 +4195,17 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
${mglItestArmProperties})
|
||||
# P11 A1 + B2 (CONTRACT-P11 §1, B2): MOBILEGL_IPC_ADOPT_TIER=0 on every arm, one case set.
|
||||
# On tcp a stream refuses the tier by name at the client's handshake (A1, the stream case
|
||||
# reads the line); over shared segments (split, spawn) the knob ASKS T0, a host has no
|
||||
# AHardwareBuffer, and the session falls back to T2 by name (B2, the fallback case reads
|
||||
# both lines). Register only the assertion applicable to each transport; the
|
||||
# parity check names these two transport-specific exceptions explicitly.
|
||||
# reads the line); on spawn the knob ASKS T0, a host has no AHardwareBuffer, and the session
|
||||
# falls back to T2 by name (B2, the fallback case reads both lines); on split the client is
|
||||
# the server's process and T0 is granted in process (the tier case reads `adopted`).
|
||||
# Register only the assertion applicable to each transport; the parity check names these
|
||||
# transport-specific exceptions explicitly.
|
||||
if(mglItestArm STREQUAL "Tcp")
|
||||
set(mglItestAdoptTier0Env "MGITEST_ADOPT_TIER_REFUSAL=T0")
|
||||
set(mglItestAdoptTier0Case AStreamSessionRefusesTheAdoptTierOnceByName)
|
||||
elseif(mglItestArm STREQUAL "Split")
|
||||
set(mglItestAdoptTier0Env "MOBILEGL_IPC_ALLOW_ADOPT_T0=1")
|
||||
set(mglItestAdoptTier0Case)
|
||||
else()
|
||||
set(mglItestAdoptTier0Env "MGITEST_ADOPT_T0_FALLBACK=no AHardwareBuffer")
|
||||
set(mglItestAdoptTier0Case ASessionThatCannotRunT0FallsBackToT2OnceByName)
|
||||
@@ -4199,7 +4217,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
DISCOVERY_TIMEOUT 30
|
||||
PROPERTIES LABELS "${mglItestMagmaLabels}" TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
${mglItestArmProperties}
|
||||
ENVIRONMENT "${mglItestMagmaBandEnvironment}\;MOBILEGL_IPC_ADOPT_TIER=0\;${mglItestAdoptTier0Env}\;MGITEST_PERSISTENT_MAP_ARM=emulated\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p11-adopt-tier0${mglItestArmLogTag}-${arenaCase}.log")
|
||||
ENVIRONMENT "${mglItestMagmaBandEnvironment}\;MOBILEGL_IPC_ADOPT_TIER=0\;${mglItestAdoptTier0Env}\;MGITEST_PERSISTENT_MAP_ARM=${mglItestArenaArm}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/p11-adopt-tier0${mglItestArmLogTag}-${arenaCase}.log")
|
||||
endforeach()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
@@ -224,7 +224,9 @@ def specialized_required():
|
||||
}
|
||||
for backend in ("DirectGLES", "DirectVulkan"):
|
||||
names.add(f"{backend}.Tcp.AdoptTier0.LargeArenaAdoptionScenario.AStreamSessionRefusesTheAdoptTierOnceByName")
|
||||
for arm in ("Split", "Spawn"):
|
||||
# Magma's split arm is granted T0 in process (its tier case reads `adopted`), so only Espryt,
|
||||
# which cannot hand an in-process client its stores, falls back there.
|
||||
for arm in (("Split", "Spawn") if backend == "DirectGLES" else ("Spawn",)):
|
||||
names.add(f"{backend}.{arm}.AdoptTier0.LargeArenaAdoptionScenario.ASessionThatCannotRunT0FallsBackToT2OnceByName")
|
||||
for arm in ("Split", "Spawn"):
|
||||
for case in ("Rgb16fDrawnBaseWithARefusedReadbackDeclinesByName",
|
||||
|
||||
@@ -100,7 +100,8 @@ void main() { word = 0xC0FFEEu; }
|
||||
// which also set MOBILEGL_IPC_ADOPT_TIER=0; a harness marker, never read by the library.
|
||||
constexpr const char* kAdoptTierRefusalMarker = "MGITEST_ADOPT_TIER_REFUSAL";
|
||||
// P11 B2: set by the shared-segment `.AdoptTier0.` / `.AdoptT0Disallowed.` entries to the reason word the
|
||||
// server gives for not granting T0 ("no AHardwareBuffer" on a host, "disallowed" with its allow switch off).
|
||||
// server gives for not granting T0 ("no AHardwareBuffer" on a host, "in process" where the backend cannot hand an
|
||||
// in-process client its stores, "disallowed" with its allow switch off).
|
||||
constexpr const char* kAdoptT0FallbackMarker = "MGITEST_ADOPT_T0_FALLBACK";
|
||||
// Draws issued against the arena inside the counted window. One definition, many draws:
|
||||
// "one per definition" (1) and "one per draw" (kDrawsInTheWindow) have to be different
|
||||
|
||||
@@ -157,6 +157,11 @@ namespace MobileGL::MG_Pipe {
|
||||
// CapsSnapshot Accept sends (no context exists yet); set in the one the first native
|
||||
// bind republishes. The client adopts through T0 only while it reads this bit.
|
||||
kCapAdoptT0 = 1ull << 11,
|
||||
// T0 IN ONE PROCESS. Set with kCapAdoptT0 when the client is this process (inproc) and the
|
||||
// backend can donate a store: map_persistent's OK answer carries the address of the
|
||||
// server's own mapped store, and the client allocates no AHardwareBuffer - the store is
|
||||
// the one memory both sides already share, as monolith's donation is.
|
||||
kCapAdoptInProcess = 1ull << 12,
|
||||
};
|
||||
|
||||
// Readiness is supplied independently by each backend's integration gate.
|
||||
|
||||
@@ -2860,6 +2860,23 @@ namespace MobileGL::MG_Pipe {
|
||||
if (g_resourceOps == nullptr || g_resourceOps->ImportExternal == nullptr) return false;
|
||||
return g_resourceOps->ImportExternal(handle.Handle, ahb, size);
|
||||
}
|
||||
|
||||
void* MGPipeApplyDonateInProcess(const MGPHandleOnly& handle) {
|
||||
if (static_cast<MGPipeKind>(handle.Kind) != MGPipeKind::Buffer) {
|
||||
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption")
|
||||
" donate_in_process {slot=%u, gen=%u}: a T0 store is the buffer family's "
|
||||
"and the handle names another kind (%u)",
|
||||
handle.Handle.Slot, handle.Handle.Gen, handle.Kind);
|
||||
return nullptr;
|
||||
}
|
||||
MGPipeResourceRecord* record = ResolveResource("donate_in_process", handle.Handle);
|
||||
if (record == nullptr || record->Desc.Width == 0) return nullptr;
|
||||
// adopt_external's pin, for adopt_external's reason: the client writes the donated store
|
||||
// directly, as a monolith client writes its adopted map.
|
||||
PinNoLiveHostWrites(*record, handle.Handle, "donate_in_process");
|
||||
if (g_resourceOps == nullptr || g_resourceOps->DonateInProcess == nullptr) return nullptr;
|
||||
return g_resourceOps->DonateInProcess(handle.Handle, record->Desc.Width);
|
||||
}
|
||||
#endif
|
||||
|
||||
// ================================================================================
|
||||
|
||||
@@ -142,6 +142,10 @@ namespace MobileGL::MG_Pipe {
|
||||
// pattern of its own, and read that back through the held pointer (and through the
|
||||
// backend's own host map of the import). False with the reason in `why`.
|
||||
Bool (*SelfTestExternal)(char* why, Uint64 whyBytes);
|
||||
// T0 for a client in this process: the store's own host-visible coherent mapping (the
|
||||
// pointer a monolith map_persistent donates), handed to the client as its persistent map.
|
||||
// The store takes T0's semantics from here on. Null = not donated: the store runs T2.
|
||||
void* (*DonateInProcess)(MGPipeHandle res, Uint64 size);
|
||||
};
|
||||
|
||||
// Install / read the table. A null argument uninstalls, which is what a backend does at
|
||||
@@ -1541,6 +1545,9 @@ namespace MobileGL::MG_Pipe {
|
||||
// without the member, declines (false); the codec answers DECLINED and the store runs T2.
|
||||
// MapPersistentRoundtrips is not moved: under split the server never counted map_persistent.
|
||||
Bool MGPipeApplyAdoptExternal(const MGPHandleOnly& handle, void* ahb, Uint64 size);
|
||||
// T0 in one process: buffer `handle`'s store, donated through the backend's DonateInProcess.
|
||||
// Null declines (not a live buffer, or a backend without the member); the store runs T2.
|
||||
void* MGPipeApplyDonateInProcess(const MGPHandleOnly& handle);
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// P3a: the five vertex-input entry points (D-G, D-H, D-I)
|
||||
|
||||
@@ -75,7 +75,10 @@ namespace MobileGL::MG_Pipe {
|
||||
// handled it and `status` is the answer (OK: the client's AHardwareBuffer is now the
|
||||
// store; DECLINED: the store runs T2); false = not a T0 session, and the codec gives
|
||||
// today's constant decline. `seq` is the record's, which is what the store's Offer names.
|
||||
virtual Bool OnMapPersistent(const MGPHandleOnly&, Uint64 /*seq*/, Int32& /*status*/) {
|
||||
// `inProcessBase`: a client in this process gets the donated store's address in the OK
|
||||
// answer (kCapAdoptInProcess); 0 = the answer carries no bytes (the AHardwareBuffer arm).
|
||||
virtual Bool OnMapPersistent(const MGPHandleOnly&, Uint64 /*seq*/, Int32& /*status*/,
|
||||
Uint64& /*inProcessBase*/) {
|
||||
return false;
|
||||
}
|
||||
virtual Bool OnFenceCreate(const MGPHandleOnly&) { return false; }
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
@@ -3011,11 +3012,25 @@ namespace MobileGL::MG_Remote::Client {
|
||||
Bool ClientSession::AdoptT0() {
|
||||
if (!m_adoptT0Asked) return false;
|
||||
if (m_adoptT0Decision >= 0) return m_adoptT0Decision == 1;
|
||||
if (Caps().HasCap(MG_Pipe::kCapAdoptT0)) {
|
||||
const Bool peerIsThisProcess = Transport::PeerIsThisProcess(m_transport);
|
||||
if (Caps().HasCap(MG_Pipe::kCapAdoptT0) && peerIsThisProcess &&
|
||||
!Caps().HasCap(MG_Pipe::kCapAdoptInProcess)) {
|
||||
// A server in this process offered the AHardwareBuffer arm. The stores are this
|
||||
// process's memory already; an in-process session never allocates one.
|
||||
m_adoptT0Decision = 0;
|
||||
++m_t0Fallbacks;
|
||||
MGLOG_W("MG_Remote client: Refuse{AdoptT0Unavailable, \"in process\"} - the server is this process "
|
||||
"and published kCapAdoptT0 without kCapAdoptInProcess; the session runs T2");
|
||||
} else if (Caps().HasCap(MG_Pipe::kCapAdoptT0)) {
|
||||
m_adoptT0Decision = 1;
|
||||
MGLOG_I("MG_Remote client: T0 - the server published kCapAdoptT0 (its POST self-test of the "
|
||||
"sustained-lock pattern passed), so this session adopts persistent stores as "
|
||||
"AHardwareBuffers the server imports");
|
||||
if (Caps().HasCap(MG_Pipe::kCapAdoptInProcess)) {
|
||||
MGLOG_I("MG_Remote client: T0 in process - the server is this process, so this session's "
|
||||
"persistent maps are the server's own store mappings (no AHardwareBuffer)");
|
||||
} else {
|
||||
MGLOG_I("MG_Remote client: T0 - the server published kCapAdoptT0 (its POST self-test of the "
|
||||
"sustained-lock pattern passed), so this session adopts persistent stores as "
|
||||
"AHardwareBuffers the server imports");
|
||||
}
|
||||
} else {
|
||||
m_adoptT0Decision = 0;
|
||||
++m_t0Fallbacks;
|
||||
@@ -3036,6 +3051,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
void* ClientSession::AdoptPersistentT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 size,
|
||||
const void* seed) {
|
||||
using namespace Transport::AdoptT0;
|
||||
if (Caps().HasCap(MG_Pipe::kCapAdoptInProcess)) return AdoptPersistentInProcess(handle, size, seed);
|
||||
// Everything that can fail runs BEFORE the record is encoded, so the record never waits on
|
||||
// a step that has not happened: either the Offer carries the handle or it says it does not.
|
||||
HeldStore store;
|
||||
@@ -3141,6 +3157,50 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// T0 IN ONE PROCESS (kCapAdoptInProcess): the OK answer carries the address of the server's own
|
||||
// mapping of the store, which becomes this buffer's persistent map - the memory both sides
|
||||
// already share, as monolith's donation is. The server has drained the GPU's use of the store
|
||||
// before answering and applies nothing of this client's until this call returns, so the
|
||||
// shadow is copied in here, by the side that owns it.
|
||||
void* ClientSession::AdoptPersistentInProcess(const MG_Pipe::MGPHandleOnly& handle, Uint64 size,
|
||||
const void* seed) {
|
||||
Uint64 base = 0;
|
||||
Uint64 returned = 0;
|
||||
Int32 status = Wire::ReplySink::kStatusError;
|
||||
EmitAndWait(MG_Pipe::MGPWireOp::MapPersistent, &handle, sizeof(handle), nullptr, 0, &base, sizeof(base),
|
||||
&status, &returned);
|
||||
if (status == Wire::ReplySink::kStatusError) {
|
||||
SessionFail(MGFatalFamily::ReplyError,
|
||||
"MGPipe: Fatal{ReplyError, \"map_persistent\"} - the in-process T0 row answered ERROR, "
|
||||
"which is not an acceptance answer");
|
||||
}
|
||||
if (status != Wire::ReplySink::kStatusOk) {
|
||||
++m_t0Declined;
|
||||
return nullptr;
|
||||
}
|
||||
if (returned != sizeof(base) || base == 0) {
|
||||
SessionFail(MGFatalFamily::UnexpectedMapAccept,
|
||||
"MGPipe: Fatal{UnexpectedMapAccept, \"map_persistent\"} - the server accepted an "
|
||||
"in-process T0 store without the store's address");
|
||||
}
|
||||
void* ptr = reinterpret_cast<void*>(static_cast<std::uintptr_t>(base));
|
||||
if (seed != nullptr) std::memcpy(ptr, seed, static_cast<SizeT>(size));
|
||||
++m_t0Imported;
|
||||
m_t0Bytes += size;
|
||||
// No AHardwareBuffer to hold: the entry only marks the handle T0 (its release is a no-op).
|
||||
const Uint64 key = T0Key(handle.Handle);
|
||||
if (auto it = m_t0Stores.find(key); it != m_t0Stores.end()) {
|
||||
Transport::AdoptT0::ReleaseHeld(it->second);
|
||||
++m_t0Released;
|
||||
}
|
||||
m_t0Stores[key] = Transport::AdoptT0::HeldStore{nullptr, ptr, size};
|
||||
MGLOG_D("MG_Remote client: T0 store {slot=%u, gen=%u} donated in process - %llu bytes; session total "
|
||||
"%llu stores / %llu bytes",
|
||||
handle.Handle.Slot, handle.Handle.Gen, static_cast<unsigned long long>(size),
|
||||
static_cast<unsigned long long>(m_t0Imported), static_cast<unsigned long long>(m_t0Bytes));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
Bool ClientSession::EmitSharedImage(const MG_Pipe::MGPSharedImageOp& op, int sendFd,
|
||||
MG_Pipe::MGPSharedImageReply* reply, int* receivedFd) {
|
||||
if (receivedFd != nullptr) *receivedFd = -1;
|
||||
|
||||
@@ -293,6 +293,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// server's inbox relies on), and wait for the answer. The held pointer on OK - the caller
|
||||
// adopts it as the store's mapping - or null on DECLINED (the store stays T2).
|
||||
void* AdoptPersistentT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 size, const void* seed);
|
||||
void* AdoptPersistentInProcess(const MG_Pipe::MGPHandleOnly& handle, Uint64 size, const void* seed);
|
||||
// shared_image (docs/Disaggregated/notes/anland/plan-ahb-dmabuf.md): emits `op` and waits
|
||||
// for its answer. `sendFd` >= 0 (an Import) is queued on the aux socket for the record's
|
||||
// seq BEFORE the record is published; `receivedFd` non-null (an Allocate) takes the
|
||||
|
||||
@@ -116,9 +116,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
Bool ServerVerbSink::OnMapPersistent(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status) {
|
||||
Bool ServerVerbSink::OnMapPersistent(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status,
|
||||
Uint64& inProcessBase) {
|
||||
ServerSession* session = ServerSession::Active();
|
||||
return session != nullptr && session->AdoptStoreT0(handle, seq, status);
|
||||
return session != nullptr && session->AdoptStoreT0(handle, seq, status, inProcessBase);
|
||||
}
|
||||
|
||||
Bool ServerVerbSink::OnFenceCreate(const MG_Pipe::MGPHandleOnly& desc) {
|
||||
@@ -389,6 +390,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
ServerSession* session = ServerSession::Active();
|
||||
if (session == nullptr) return false;
|
||||
SI::SessionHolder& holder = session->SharedImageHolder();
|
||||
// Shared images carry memory between processes. A client in this process has none to
|
||||
// carry, so it gets none - no platform buffer is ever allocated or imported for it.
|
||||
const Bool peerIsThisProcess = Transport::PeerIsThisProcess(session->ControlTransport());
|
||||
const auto fill = [&reply](const SI::Image& image) {
|
||||
reply.ImageId = image.Id;
|
||||
reply.Modifier = image.Modifier;
|
||||
@@ -402,6 +406,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
};
|
||||
switch (op.Op) {
|
||||
case MG_Pipe::kMGPSharedImageAllocate: {
|
||||
if (peerIsThisProcess) {
|
||||
MGLOG_W_ONCE("MG_Remote server: a shared-image allocation was refused: the client is this process");
|
||||
return false;
|
||||
}
|
||||
std::string why;
|
||||
SI::ImageRef image = SI::Allocate(op.Width, op.Height, op.Format, why);
|
||||
if (image == nullptr) {
|
||||
@@ -435,6 +443,13 @@ namespace MobileGL::MG_Remote::Server {
|
||||
static_cast<unsigned long long>(seq), why.c_str());
|
||||
return false;
|
||||
}
|
||||
if (peerIsThisProcess) {
|
||||
#if !defined(_WIN32)
|
||||
::close(fd);
|
||||
#endif
|
||||
MGLOG_W_ONCE("MG_Remote server: a dma-buf import was refused: the client is this process");
|
||||
return false;
|
||||
}
|
||||
SI::ImageRef image = SI::Identify(fd, why);
|
||||
// YUV: the planes and colour hints ride in Damage (MGPImportPlaneWord). A buffer this
|
||||
// server did not allocate is taken too, through the labelled CPU-copy fallback
|
||||
|
||||
@@ -156,7 +156,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// queries, present, the T0 map, the control records and shared images.
|
||||
|
||||
// P11 B2: T0's map_persistent - ServerSession::AdoptStoreT0 (it owns the aux socket).
|
||||
Bool OnMapPersistent(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status) override;
|
||||
Bool OnMapPersistent(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status,
|
||||
Uint64& inProcessBase) override;
|
||||
Bool OnFenceCreate(const MG_Pipe::MGPHandleOnly&) override;
|
||||
Bool OnFenceDestroy(const MG_Pipe::MGPHandleOnly&) override;
|
||||
Bool OnFenceStatus(const MG_Pipe::MGPHandleOnly&, Uint32&) override;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
@@ -726,6 +727,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
m_adoptT0Quiet = m_adoptAsk == Transport::kAdoptAskT0Default;
|
||||
m_adoptT0Settled = false;
|
||||
m_adoptT0 = false;
|
||||
m_adoptT0InProcess = false;
|
||||
m_t0Stores = m_t0Bytes = m_t0Declined = m_t0Refusals = 0;
|
||||
m_adoptInbox.Clear();
|
||||
m_adoptInbox.Attach(&transport);
|
||||
@@ -966,6 +968,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// P11 B2: this session's T0 grant, never part of the process-wide m_capBits - it is a
|
||||
// statement about THIS client's ask and this server's self-test.
|
||||
if (m_adoptT0) callMask |= static_cast<Uint64>(MG_Pipe::kCapAdoptT0);
|
||||
if (m_adoptT0InProcess) callMask |= static_cast<Uint64>(MG_Pipe::kCapAdoptInProcess);
|
||||
const auto timerSupported = m_backend->GetBackendFunctions().GL.IsTimerQuerySupported;
|
||||
if ((callMask & MG_Pipe::kCapTimerQuery) && (!timerSupported || !timerSupported()))
|
||||
callMask &= ~static_cast<Uint64>(MG_Pipe::kCapTimerQuery);
|
||||
@@ -1113,7 +1116,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
m_adoptInbox.Clear();
|
||||
m_adoptInbox.Attach(nullptr);
|
||||
m_sharedImages.Clear();
|
||||
m_adoptT0Asked = m_adoptT0Settled = m_adoptT0 = m_adoptT0Quiet = false;
|
||||
m_adoptT0Asked = m_adoptT0Settled = m_adoptT0 = m_adoptT0Quiet = m_adoptT0InProcess = false;
|
||||
m_adoptAsk = 2;
|
||||
m_consumer.Detach();
|
||||
*m_commands = Transport::RingConsumer();
|
||||
@@ -1153,7 +1156,26 @@ namespace MobileGL::MG_Remote::Server {
|
||||
const char* backendName = m_backend == nullptr ? "server"
|
||||
: m_backend->GetBackendType() == BackendType::DirectVulkan ? "Magma" : "Espryt";
|
||||
std::string why;
|
||||
if (MG_Config::Ipc.AllowAdoptT0 == 0) {
|
||||
// A CLIENT IN THIS PROCESS SHARES THE SERVER'S MEMORY ALREADY: its persistent maps are the
|
||||
// stores' own mappings, donated as monolith donates them, and no AHardwareBuffer, import
|
||||
// or POST is involved. A backend that cannot donate leaves the session on T2 - an
|
||||
// in-process session never takes the AHardwareBuffer arm below.
|
||||
const Bool inProcess = Transport::PeerIsThisProcess(m_transport);
|
||||
if (inProcess) {
|
||||
const MG_Pipe::MGPipeResourceOps* ops = MG_Pipe::MGPipeGetResourceOps();
|
||||
if (MG_Config::Ipc.AllowAdoptT0 == 0) {
|
||||
why = "this server's MOBILEGL_IPC_ALLOW_ADOPT_T0=0 disallows it";
|
||||
} else if (ops == nullptr || ops->DonateInProcess == nullptr) {
|
||||
why = std::string("the ") + backendName + " backend cannot hand its stores to a client in this process";
|
||||
} else {
|
||||
m_adoptT0 = true;
|
||||
m_adoptT0InProcess = true;
|
||||
MGLOG_I("MG_Remote server: T0 granted in process - the client is this process, so each "
|
||||
"map_persistent hands it the %s store's own mapping (no AHardwareBuffer)",
|
||||
backendName);
|
||||
return;
|
||||
}
|
||||
} else if (MG_Config::Ipc.AllowAdoptT0 == 0) {
|
||||
why = "this server's MOBILEGL_IPC_ALLOW_ADOPT_T0=0 disallows it";
|
||||
} else if (!Transport::AdoptT0::PlatformHasAhb()) {
|
||||
why = "no AHardwareBuffer on this platform";
|
||||
@@ -1182,7 +1204,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
if (!why.empty()) {
|
||||
++m_t0Refusals;
|
||||
const char* word = MG_Config::Ipc.AllowAdoptT0 == 0 ? "disallowed"
|
||||
: !Transport::AdoptT0::PlatformHasAhb() ? "no AHardwareBuffer" : "POST";
|
||||
: inProcess ? "in process"
|
||||
: !Transport::AdoptT0::PlatformHasAhb() ? "no AHardwareBuffer"
|
||||
: "POST";
|
||||
if (m_adoptT0Quiet) {
|
||||
// THE DEFAULT ASK (MOBILEGL_IPC_ADOPT_TIER unset) FALLS BACK QUIETLY: every host
|
||||
// session would otherwise carry this line. Counted (T0Refusals, the totals line).
|
||||
@@ -1202,9 +1226,29 @@ namespace MobileGL::MG_Remote::Server {
|
||||
backendName);
|
||||
}
|
||||
|
||||
Bool ServerSession::AdoptStoreT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status) {
|
||||
Bool ServerSession::AdoptStoreT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status,
|
||||
Uint64& inProcessBase) {
|
||||
if (!m_adoptT0) return false;
|
||||
status = Wire::ReplySink::kStatusDeclined;
|
||||
inProcessBase = 0;
|
||||
if (m_adoptT0InProcess) {
|
||||
void* base = MG_Pipe::MGPipeApplyDonateInProcess(handle);
|
||||
if (base == nullptr) {
|
||||
++m_t0Declined;
|
||||
MGLOG_W("MG_Remote server: T0 map_persistent {slot=%u, gen=%u} (record %llu) DECLINED - the "
|
||||
"backend did not donate the store (its log names why); the store runs T2",
|
||||
handle.Handle.Slot, handle.Handle.Gen, static_cast<unsigned long long>(seq));
|
||||
return true;
|
||||
}
|
||||
inProcessBase = static_cast<Uint64>(reinterpret_cast<std::uintptr_t>(base));
|
||||
++m_t0Stores;
|
||||
status = Wire::ReplySink::kStatusOk;
|
||||
MGLOG_D("MG_Remote server: T0 donated store {slot=%u, gen=%u} in process (record %llu); session "
|
||||
"total %llu stores",
|
||||
handle.Handle.Slot, handle.Handle.Gen, static_cast<unsigned long long>(seq),
|
||||
static_cast<unsigned long long>(m_t0Stores));
|
||||
return true;
|
||||
}
|
||||
int hop = -1;
|
||||
Transport::AdoptT0::Offer offer{};
|
||||
std::string why;
|
||||
|
||||
@@ -320,7 +320,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// hang), read the AHB off its hop, and import it as `handle`'s store. False = this session is
|
||||
// not T0 (the codec's T2 decline answers); true = handled and `status` is the answer (OK, or
|
||||
// DECLINED with a named line).
|
||||
Bool AdoptStoreT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status);
|
||||
// A client in this process (inproc) takes no AHardwareBuffer: the backend donates the
|
||||
// store's own mapping and `inProcessBase` carries its address in the OK answer.
|
||||
Bool AdoptStoreT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status,
|
||||
Uint64& inProcessBase);
|
||||
// T0 granted for a client in this process: map_persistent donates (kCapAdoptInProcess).
|
||||
Bool AdoptT0InProcess() const { return m_adoptT0InProcess; }
|
||||
// The arm's proof for this session (B2): stores imported and their bytes, declined.
|
||||
Uint64 T0StoresImported() const { return m_t0Stores; }
|
||||
Uint64 T0BytesImported() const { return m_t0Bytes; }
|
||||
@@ -426,6 +431,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Uint8 m_adoptAsk = 2;
|
||||
Bool m_adoptT0Settled = false;
|
||||
Bool m_adoptT0 = false;
|
||||
Bool m_adoptT0InProcess = false;
|
||||
AdoptInbox m_adoptInbox;
|
||||
SharedImages::SessionHolder m_sharedImages;
|
||||
Uint64 m_t0Stores = 0;
|
||||
|
||||
@@ -56,6 +56,10 @@ namespace MobileGL::MG_Remote::Transport::AdoptT0 {
|
||||
// True where AHardwareBuffer exists (Android). Everywhere else T0 is never granted.
|
||||
bool PlatformHasAhb();
|
||||
|
||||
// A session whose peer is this very process (Transport::PeerIsThisProcess) never takes this
|
||||
// header's arm: its stores are this process's memory already, so T0 there is the backend
|
||||
// handing over its own store (kCapAdoptInProcess) or nothing (T2).
|
||||
|
||||
// ---- client half ----------------------------------------------------------------------
|
||||
|
||||
// A store the client allocated. `ahb` is an AHardwareBuffer*, `ptr` its CPU lock, held until
|
||||
|
||||
@@ -127,4 +127,12 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
ITransport() = default;
|
||||
};
|
||||
|
||||
// True when the session on `transport` has this very process as its peer (an in-process
|
||||
// transport). Its memory is the peer's already, so what exists only to carry memory between
|
||||
// processes - T0's handle arm, shared images - is never offered on it.
|
||||
bool PeerIsThisProcess(const ITransport* transport);
|
||||
// Test seam: the protocol tests run over an in-process transport and stand for a peer in
|
||||
// another process while this is set.
|
||||
void SetPeerInAnotherProcessForTest(bool asIfCrossProcess);
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Transport
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
@@ -290,4 +291,17 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
|
||||
Doorbell& InProcessTransport::SelfDoorbell() { return m_channel->Bell(m_endpoint); }
|
||||
|
||||
namespace {
|
||||
std::atomic<bool> g_peerInAnotherProcessForTest{false};
|
||||
} // namespace
|
||||
|
||||
bool PeerIsThisProcess(const ITransport* transport) {
|
||||
return transport != nullptr && transport->Role() == TransportRole::InProcess &&
|
||||
!g_peerInAnotherProcessForTest.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void SetPeerInAnotherProcessForTest(bool asIfCrossProcess) {
|
||||
g_peerInAnotherProcessForTest.store(asIfCrossProcess, std::memory_order_release);
|
||||
}
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Transport
|
||||
|
||||
@@ -1852,9 +1852,11 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// it (Server/AdoptInbox.h says why that wait cannot hang the apply thread); it answers
|
||||
// for itself or says "not T0", and everything below is the T2 arm, unchanged.
|
||||
Int32 t0Status = ReplySink::kStatusDeclined;
|
||||
Uint64 inProcessBase = 0;
|
||||
if (m_verbs != nullptr &&
|
||||
m_verbs->OnMapPersistent(*static_cast<const MGPHandleOnly*>(payload), seq, t0Status)) {
|
||||
PostReply(op, seq, t0Status, nullptr, 0);
|
||||
m_verbs->OnMapPersistent(*static_cast<const MGPHandleOnly*>(payload), seq, t0Status, inProcessBase)) {
|
||||
if (inProcessBase != 0) PostReply(op, seq, t0Status, &inProcessBase, sizeof(inProcessBase));
|
||||
else PostReply(op, seq, t0Status, nullptr, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Impl/EGLImpl/EGLImpl.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
|
||||
using namespace MobileGL;
|
||||
using namespace P12;
|
||||
|
||||
@@ -332,6 +335,52 @@ namespace {
|
||||
(void)EGL::Terminate(dpy);
|
||||
}
|
||||
|
||||
struct InprocReport {
|
||||
Int32 initialized = 0;
|
||||
Int32 current = 0;
|
||||
Int32 dmabufImport = 0;
|
||||
Int32 nativeFence = 0;
|
||||
Int32 externalImage = 0;
|
||||
Int32 allocated = 0;
|
||||
Int32 eglError = 0;
|
||||
char note[128] = {};
|
||||
};
|
||||
|
||||
// An inproc client: its server is this process, so nothing that carries memory between
|
||||
// processes is offered, asked for or allocated.
|
||||
void RunInproc(InprocReport& r) {
|
||||
static int fakeGbmDevice[16] = {};
|
||||
const EGLDisplay dpy = EGL::GetPlatformDisplay(kPlatformGbm, fakeGbmDevice, nullptr);
|
||||
EGLint major = 0, minor = 0;
|
||||
r.initialized = dpy != EGL_NO_DISPLAY && EGL::Initialize(dpy, &major, &minor) == EGL_TRUE ? 1 : 0;
|
||||
if (!r.initialized) return;
|
||||
const char* exts = EGL::QueryString(dpy, EGL_EXTENSIONS);
|
||||
r.dmabufImport = HasToken(exts, "EGL_EXT_image_dma_buf_import") ? 1 : 0;
|
||||
r.nativeFence = HasToken(exts, "EGL_ANDROID_native_fence_sync") ? 1 : 0;
|
||||
(void)EGL::BindAPI(EGL_OPENGL_API);
|
||||
const EGLint core33[] = {EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
|
||||
EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, EGL_NONE};
|
||||
const EGLContext ctx = EGL::CreateContext(dpy, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, core33);
|
||||
r.current = ctx != EGL_NO_CONTEXT && EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) == EGL_TRUE;
|
||||
if (!r.current) {
|
||||
r.eglError = EGL::GetError();
|
||||
return;
|
||||
}
|
||||
// The GL extension list is where a split client asks the server whether it holds YUV images.
|
||||
r.externalImage = HasGlExtension("GL_OES_EGL_image_external") ? 1 : 0;
|
||||
// Asked anyway (a C-ABI or GLX caller does not look at the extensions first): refused.
|
||||
auto* backend = MG_Backend::pActiveBackendObject.get();
|
||||
MG_Backend::SharedImageExport image;
|
||||
if (backend != nullptr && backend->AllocateSharedImage(64, 32, kXrgb8888, &image)) {
|
||||
r.allocated = 1;
|
||||
if (image.Fd >= 0) ::close(image.Fd);
|
||||
(void)backend->ReleaseSharedImage(image.Id);
|
||||
}
|
||||
(void)EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
(void)EGL::DestroyContext(dpy, ctx);
|
||||
(void)EGL::Terminate(dpy);
|
||||
}
|
||||
|
||||
// The supervisor on an abstract unix socket, as the device's is: shared images travel as
|
||||
// descriptors on its aux socket, which a TCP endpoint does not have.
|
||||
bool LaunchUnixSupervisor(ServerProcess* server) {
|
||||
@@ -350,8 +399,9 @@ namespace {
|
||||
}
|
||||
|
||||
// One forked client running `run` against the server, its report back through a pipe.
|
||||
// `server == nullptr` runs the client inproc, its server in its own process.
|
||||
template <class Report>
|
||||
Report RunPeer(const ServerProcess& server, const std::string& clientLogBase, void (*run)(Report&)) {
|
||||
Report RunPeer(const ServerProcess* server, const std::string& clientLogBase, void (*run)(Report&)) {
|
||||
Report report{};
|
||||
int fds[2] = {-1, -1};
|
||||
if (::pipe(fds) != 0) return report;
|
||||
@@ -360,8 +410,8 @@ namespace {
|
||||
if (pid == 0) {
|
||||
::close(fds[0]);
|
||||
::setenv("MOBILEGL_LOG_FILE_PATH", clientLogBase.c_str(), 1);
|
||||
::setenv("MOBILEGL_TRANSPORT", "spawn", 1);
|
||||
::setenv("MOBILEGL_IPC_CONTROL", server.endpoint.c_str(), 1);
|
||||
::setenv("MOBILEGL_TRANSPORT", server != nullptr ? "spawn" : "inproc", 1);
|
||||
if (server != nullptr) ::setenv("MOBILEGL_IPC_CONTROL", server->endpoint.c_str(), 1);
|
||||
::setenv("MOBILEGL_BACKEND_TYPE", "DirectGLES", 1);
|
||||
::setenv("MOBILEGL_IPC_SURFACE", "offscreen", 1);
|
||||
Report r{};
|
||||
@@ -398,7 +448,7 @@ TEST(GlamorEglSequence, XwaylandFindsEverythingGlamorNeeds) {
|
||||
ASSERT_TRUE(LaunchUnixSupervisor(&server)) << server.Log();
|
||||
const std::string clientLog = "/tmp/mgl-glamor-client-" + std::to_string(::getpid()) + ".log";
|
||||
Debug::TruncateRoleLogs(clientLog.c_str());
|
||||
const GlamorReport r = RunPeer<GlamorReport>(server, clientLog, RunGlamor);
|
||||
const GlamorReport r = RunPeer<GlamorReport>(&server, clientLog, RunGlamor);
|
||||
ASSERT_EQ(r.display, 1) << r.note;
|
||||
EXPECT_EQ(r.platformGbmExtension, 1);
|
||||
ASSERT_EQ(r.initialized, 1);
|
||||
@@ -430,7 +480,7 @@ TEST(GlamorEglSequence, ChromesGpuProcessGetsANativeFenceOfItsDmaBufWrites) {
|
||||
ASSERT_TRUE(LaunchUnixSupervisor(&server)) << server.Log();
|
||||
const std::string clientLog = "/tmp/mgl-chrome-fence-client-" + std::to_string(::getpid()) + ".log";
|
||||
Debug::TruncateRoleLogs(clientLog.c_str());
|
||||
const ChromeFenceReport r = RunPeer<ChromeFenceReport>(server, clientLog, RunChromeGpuFence);
|
||||
const ChromeFenceReport r = RunPeer<ChromeFenceReport>(&server, clientLog, RunChromeGpuFence);
|
||||
ASSERT_EQ(r.initialized, 1) << r.note;
|
||||
// ANGLE offers Chrome fences only with all three, and loads the KHR names.
|
||||
EXPECT_EQ(r.fenceSyncExtension, 1);
|
||||
@@ -463,3 +513,30 @@ TEST(GlamorEglSequence, ChromesGpuProcessGetsANativeFenceOfItsDmaBufWrites) {
|
||||
std::filesystem::remove(clientLog, ec);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GlamorEglSequence, AnInprocClientIsOfferedNoSharedImagesAndAllocatesNone) {
|
||||
PinHeadlessEgl();
|
||||
const std::string clientLog = "/tmp/mgl-inproc-images-" + std::to_string(::getpid()) + ".log";
|
||||
Debug::TruncateRoleLogs(clientLog.c_str());
|
||||
const InprocReport r = RunPeer<InprocReport>(nullptr, clientLog, RunInproc);
|
||||
ASSERT_EQ(r.initialized, 1) << r.note;
|
||||
ASSERT_EQ(r.current, 1) << "error 0x" << std::hex << r.eglError;
|
||||
EXPECT_EQ(r.dmabufImport, 0) << "an inproc display offered dma-buf import";
|
||||
EXPECT_EQ(r.nativeFence, 0) << "an inproc display offered native fences";
|
||||
EXPECT_EQ(r.externalImage, 0) << "an inproc context offered external (YUV) images";
|
||||
EXPECT_EQ(r.allocated, 0) << "an inproc client was given a shared image";
|
||||
std::string log;
|
||||
for (const auto role : {Debug::LogRole::Client, Debug::LogRole::Server}) {
|
||||
std::ifstream in(Debug::RoleLogPath(clientLog.c_str(), role));
|
||||
log += std::string(std::istreambuf_iterator<char>(in), {});
|
||||
}
|
||||
EXPECT_EQ(log.find("Shared images: the server"), std::string::npos) << "the YUV probe allocated an image";
|
||||
EXPECT_NE(log.find("shared-image allocation was refused: the client is this process"), std::string::npos)
|
||||
<< "the server did not refuse the allocation by name";
|
||||
std::error_code ec;
|
||||
if (!::testing::Test::HasFailure()) {
|
||||
std::filesystem::remove(Debug::RoleLogPath(clientLog.c_str(), Debug::LogRole::Client), ec);
|
||||
std::filesystem::remove(Debug::RoleLogPath(clientLog.c_str(), Debug::LogRole::Server), ec);
|
||||
std::filesystem::remove(clientLog, ec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,9 @@ void FakeDrop(FakeAhb* f) {
|
||||
}
|
||||
}
|
||||
bool FakeHasAhb() { return true; }
|
||||
Uint64 g_fakeAllocations = 0;
|
||||
bool FakeAllocate(std::uint64_t size, const void* seed, T0::HeldStore& out, std::string& why) {
|
||||
++g_fakeAllocations;
|
||||
auto* f = new FakeAhb;
|
||||
f->fd = static_cast<int>(::syscall(SYS_memfd_create, "t0-fake", 1u /*MFD_CLOEXEC*/));
|
||||
if (f->fd < 0 || ::ftruncate(f->fd, static_cast<off_t>(size)) != 0) {
|
||||
@@ -144,6 +146,20 @@ MGPipeResourceOps MakeFakeOps() {
|
||||
}
|
||||
MGPipeResourceOps g_fakeOps = MakeFakeOps();
|
||||
|
||||
// ---- the stand-in donor: a backend that hands an in-process client its store's own mapping ---
|
||||
alignas(64) Uint8 g_donatedStore[kT0Bytes];
|
||||
Uint64 g_donations = 0;
|
||||
void* FakeDonateInProcess(MGPipeHandle res, Uint64 size) {
|
||||
++g_donations;
|
||||
return res.Slot == 5 && size == kT0Bytes ? g_donatedStore : nullptr;
|
||||
}
|
||||
MGPipeResourceOps MakeDonatingOps() {
|
||||
MGPipeResourceOps ops = MakeFakeOps();
|
||||
ops.DonateInProcess = &FakeDonateInProcess;
|
||||
return ops;
|
||||
}
|
||||
MGPipeResourceOps g_donatingOps = MakeDonatingOps();
|
||||
|
||||
// What the server's first native bind does for T0, on the apply thread like the real one: the
|
||||
// grant (and its POST), then the caps that bind republishes - which the client's make-current
|
||||
// adopts, as here.
|
||||
@@ -184,6 +200,7 @@ size_t CountOf(const std::string& log, const std::string& needle) {
|
||||
// The grant with the stand-ins, the store, and the map_persistent: returns the adopted pointer.
|
||||
void* AdoptOneStore(Uint8 salt, std::vector<Uint8>& seed) {
|
||||
MG_Config::Ipc.AdoptTier = 0;
|
||||
MobileGL::MG_Remote::Transport::SetPeerInAnotherProcessForTest(true);
|
||||
T0::SetPlatformForTest(&kFakePlatform);
|
||||
MG_Pipe::MGPipeSetResourceOps(&g_fakeOps);
|
||||
StartControlSession();
|
||||
@@ -223,6 +240,7 @@ TEST(RemoteClientAdoptT0, KnobZeroOverSharedSegmentsAsksT0AndFallsBackToT2ByName
|
||||
TEST(RemoteClientAdoptT0, KnobTwoIsTodaysT2WithNoT0Line) {
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Ipc.AdoptTier = 2;
|
||||
MobileGL::MG_Remote::Transport::SetPeerInAnotherProcessForTest(true);
|
||||
T0::SetPlatformForTest(&kFakePlatform);
|
||||
MG_Pipe::MGPipeSetResourceOps(&g_fakeOps);
|
||||
StartControlSession();
|
||||
@@ -251,6 +269,7 @@ TEST(RemoteClientAdoptT0, KnobTwoIsTodaysT2WithNoT0Line) {
|
||||
TEST(RemoteClientAdoptT0, AnUnsetKnobFallsBackToT2QuietlyAndCountsIt) {
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Ipc.AdoptTier = MG_Config::kAdoptTierUnset;
|
||||
MobileGL::MG_Remote::Transport::SetPeerInAnotherProcessForTest(true);
|
||||
MG_Pipe::MGPipeSetResourceOps(&g_fakeOps); // a backend WITH the members: the platform decides
|
||||
StartControlSession();
|
||||
if (!Srv::ServerSessionInstance().AdoptT0Asked()) ::_exit(210); // the Hello asked T0 by default
|
||||
@@ -292,6 +311,7 @@ TEST(RemoteClientAdoptT0, KnobOneIsRefusedByNameAndAsksT2) {
|
||||
TEST(RemoteClientAdoptT0, NoAhardwareBufferOnThisPlatformIsT2AndTwoNamedLines) {
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Ipc.AdoptTier = 0;
|
||||
MobileGL::MG_Remote::Transport::SetPeerInAnotherProcessForTest(true);
|
||||
MG_Pipe::MGPipeSetResourceOps(&g_fakeOps); // a backend WITH the members: the platform decides
|
||||
StartControlSession();
|
||||
GrantAtFirstBind();
|
||||
@@ -383,4 +403,60 @@ TEST(RemoteClientAdoptT0, AnOfferThatNeverArrivesIsANamedDeclineNotAHang) {
|
||||
EXPECT_EQ(CountOf(child.Log, "Refuse{AdoptT0NoStore, \"record"), 1u) << child.Log;
|
||||
EXPECT_EQ(child.Log.find("Fatal{"), std::string::npos) << child.Log;
|
||||
}
|
||||
// T0 IN ONE PROCESS: the client is this process, so a backend that can donate hands it the store's
|
||||
// own mapping - no AHardwareBuffer is allocated, no Offer, no import, no POST - and the client's
|
||||
// pointer is the server's store, seeded with the shadow.
|
||||
// RED-ONCE (host): grant the AHardwareBuffer arm for an in-process client (drop the in-process
|
||||
// branch of ServerSession::SettleAdoptT0AtBind) -> the stand-in platform allocates, 223.
|
||||
TEST(RemoteClientAdoptT0, AnInProcessClientGetsTheServersStoreAndNoAhardwareBuffer) {
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Ipc.AdoptTier = 0;
|
||||
T0::SetPlatformForTest(&kFakePlatform);
|
||||
MG_Pipe::MGPipeSetResourceOps(&g_donatingOps);
|
||||
StartControlSession();
|
||||
GrantAtFirstBind();
|
||||
if (!Srv::ServerSessionInstance().AdoptT0InProcess()) ::_exit(220);
|
||||
const auto seed = Pattern(kT0Bytes, 0x77);
|
||||
const MGPHandleOnly handle = MakeBufferStore(5, kT0Bytes);
|
||||
void* adopted = MGPipeRouteMapPersistent(handle, kT0Bytes, seed.data());
|
||||
if (adopted != g_donatedStore) ::_exit(221);
|
||||
if (std::memcmp(g_donatedStore, seed.data(), kT0Bytes) != 0) ::_exit(222);
|
||||
if (g_fakeAllocations != 0 || g_fakeImports.count != 0) ::_exit(223);
|
||||
if (g_donations != 1) ::_exit(224);
|
||||
if (Srv::ServerSessionInstance().T0StoresImported() != 1 || ClientSessionInstance().T0StoresImported() != 1)
|
||||
::_exit(225);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
EXPECT_NE(child.Log.find("T0 granted in process"), std::string::npos) << child.Log;
|
||||
EXPECT_EQ(child.Log.find("T0 POST"), std::string::npos) << child.Log;
|
||||
EXPECT_EQ(child.Log.find("Fatal{"), std::string::npos) << child.Log;
|
||||
}
|
||||
// T0 IN ONE PROCESS NEVER TAKES THE AHardwareBuffer ARM: a backend that cannot hand an in-process
|
||||
// client its store leaves the session on T2 by name - no allocation, no Offer, no import, no POST -
|
||||
// even where the transfer arm would work (the stand-in platform and importer are installed).
|
||||
// RED-ONCE (host): let SettleAdoptT0AtBind fall through to the AHardwareBuffer arm for an in-process
|
||||
// client -> the stand-in POST passes and the session is granted T0, 230.
|
||||
TEST(RemoteClientAdoptT0, AnInProcessClientWhoseBackendCannotDonateRunsT2AndNeverTheTransferArm) {
|
||||
const auto child = RunInChild([] {
|
||||
MG_Config::Ipc.AdoptTier = 0;
|
||||
T0::SetPlatformForTest(&kFakePlatform);
|
||||
MG_Pipe::MGPipeSetResourceOps(&g_fakeOps); // the transfer arm's members, no DonateInProcess
|
||||
StartControlSession();
|
||||
GrantAtFirstBind();
|
||||
if (Srv::ServerSessionInstance().AdoptT0()) ::_exit(230);
|
||||
if (Srv::ServerSessionInstance().T0Refusals() != 1) ::_exit(231);
|
||||
const auto seed = Pattern(kT0Bytes, 0x88);
|
||||
const MGPHandleOnly handle = MakeBufferStore(5, kT0Bytes);
|
||||
if (MGPipeRouteMapPersistent(handle, kT0Bytes, seed.data()) != nullptr) ::_exit(232);
|
||||
if (g_fakeAllocations != 0 || g_fakeImports.count != 0) ::_exit(233);
|
||||
if (ClientSessionInstance().AdoptT0()) ::_exit(234);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
EXPECT_EQ(CountOf(child.Log, "Refuse{AdoptT0Unavailable, \"in process\"}"), 1u) << child.Log;
|
||||
EXPECT_EQ(child.Log.find("T0 POST"), std::string::npos) << child.Log;
|
||||
EXPECT_EQ(child.Log.find("T0 granted"), std::string::npos) << child.Log;
|
||||
EXPECT_EQ(child.Log.find("Fatal{"), std::string::npos) << child.Log;
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user