mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[WIP] (MG_Remote): implement P6.5 TCP and stream transport
This commit is contained in:
@@ -592,6 +592,9 @@ endif()
|
||||
if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
message(STATUS "MobileGL: disaggregated transport ON, appending MG_Remote sources")
|
||||
list(APPEND SOURCE_FILES
|
||||
MobileGL/MG_Remote/Transport/ShmLink.cpp
|
||||
MobileGL/MG_Remote/Transport/LinkMetrics.cpp
|
||||
MobileGL/MG_Remote/Transport/StreamLink.cpp
|
||||
MobileGL/MG_Remote/Transport/Ring.cpp
|
||||
MobileGL/MG_Remote/Transport/Doorbell.cpp
|
||||
MobileGL/MG_Remote/Transport/ShmSegment.cpp
|
||||
@@ -603,6 +606,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
MobileGL/MG_Remote/Transport/FdPassing.cpp
|
||||
MobileGL/MG_Remote/Transport/InProcessTransport.cpp
|
||||
MobileGL/MG_Remote/Transport/SocketTransport.cpp
|
||||
MobileGL/MG_Remote/Transport/ControlInbox.cpp
|
||||
# Keeps MG_Util/Debug/Log.h - and through it the GL frontend's
|
||||
# umbrella header - out of the header-only wire code (WireLog.h).
|
||||
MobileGL/MG_Remote/Transport/WireLog.cpp
|
||||
|
||||
@@ -471,6 +471,9 @@ namespace MobileGL::MG_Config {
|
||||
// SPLIT variant already carry it, and an environment variable that nothing parses is
|
||||
// indistinguishable from one that is parsed and ignored.
|
||||
String ServerPath;
|
||||
// Control endpoint and data-plane selection are independent of topology.
|
||||
String Control = "fork"; // fork | unix:<path> | tcp://host:port
|
||||
String Data = "auto"; // auto | shm | stream
|
||||
// MOBILEGL_IPC_RING_MB: SEG_CMD size. A RECORD MAY BE AT MOST HALF OF THIS
|
||||
// (RingProducer::MaxRecordBytes), so 8 MiB caps one record at 4 MiB; R-10 makes the
|
||||
// codec publish a max-record-bytes counter rather than assume that is enough.
|
||||
|
||||
@@ -378,6 +378,8 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
inline void InitIpc() {
|
||||
auto& ipc = MG_Config::Ipc;
|
||||
QueryEnvVariable("MOBILEGL_IPC_SERVER_PATH", ipc.ServerPath, "");
|
||||
QueryEnvVariable("MOBILEGL_IPC_CONTROL", ipc.Control, "fork");
|
||||
QueryEnvVariable("MOBILEGL_IPC_DATA", ipc.Data, "auto");
|
||||
// Both ring floors are 1 MiB, not 0: a ring caps ONE record at half its size, and
|
||||
// the catalogue's largest fixed payload (MGPFramebufferState, 304 bytes) plus a
|
||||
// create_shader_state archive already needs far more than a toy ring. The ceilings
|
||||
|
||||
@@ -332,13 +332,13 @@ namespace MobileGL {
|
||||
};
|
||||
|
||||
struct DynamicBackendParameters {
|
||||
SizeT UniformBufferOffsetAlignment = 256;
|
||||
Uint64 UniformBufferOffsetAlignment = 256;
|
||||
// GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, which is a SEPARATE limit from the
|
||||
// uniform one and is routinely larger: Adreno 830 reports 32 for uniform buffers and
|
||||
// 64 for storage buffers. Answering the storage query with the uniform value let an
|
||||
// application bind a storage range at an offset the driver cannot address, which it
|
||||
// accepted without error and then wrote somewhere else entirely.
|
||||
SizeT ShaderStorageBufferOffsetAlignment = 256;
|
||||
Uint64 ShaderStorageBufferOffsetAlignment = 256;
|
||||
// GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT. 1.0 means the backend cannot filter anisotropically,
|
||||
// which is also why the extension is not advertised in that case.
|
||||
Float MaxTextureMaxAnisotropy = 1.0f;
|
||||
@@ -460,8 +460,8 @@ namespace MobileGL {
|
||||
// VulkanRenderer::SelectProvokingVertexMode out of VK_EXT_provoking_vertex,
|
||||
// provokingVertexModePerPipeline and the topology, so no single convention is true
|
||||
// of the backend.
|
||||
GLenum LayerProvokingVertex = GL_UNDEFINED_VERTEX;
|
||||
GLenum ViewportIndexProvokingVertex = GL_UNDEFINED_VERTEX;
|
||||
Uint32 LayerProvokingVertex = GL_UNDEFINED_VERTEX;
|
||||
Uint32 ViewportIndexProvokingVertex = GL_UNDEFINED_VERTEX;
|
||||
Int MaxViewportWidth = 16384;
|
||||
Int MaxViewportHeight = 16384;
|
||||
Float ViewportBoundsRangeMin = 0.0f;
|
||||
@@ -558,7 +558,7 @@ namespace MobileGL {
|
||||
Bool SupportsTessellationPointSize = true;
|
||||
// The geometry-stage twin (OpCapability GeometryPointSize).
|
||||
Bool SupportsGeometryPointSize = true;
|
||||
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||
Uint64 MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||
Uint32 SubgroupSize = 0;
|
||||
Uint32 SubgroupSupportedStages = 0;
|
||||
Uint32 SubgroupSupportedFeatures = 0;
|
||||
|
||||
@@ -600,11 +600,11 @@ namespace MobileGL::MG_Pipe {
|
||||
// so only a non-empty run goes through the bounds-checked resolve.
|
||||
if (bytes.Size != 0) {
|
||||
auto* session = MG_Remote::Client::ClientSession::Active();
|
||||
const void* resolved =
|
||||
session == nullptr
|
||||
? nullptr
|
||||
: session->Segments().Resolve(MG_Remote::Wire::kSegEvent, bytes.Offset,
|
||||
bytes.Size);
|
||||
const void* resolved = nullptr;
|
||||
if (session && session->DataLink()) {
|
||||
session->DataLink()->ResolveSpan(MG_Remote::Transport::LinkSegment::Event,
|
||||
{bytes.Offset, bytes.Size}, &resolved);
|
||||
}
|
||||
if (resolved == nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"OnBufferWriteback.Offset\"} - "
|
||||
"the writeback blobref's {%llu + %llu} does not resolve inside the "
|
||||
|
||||
@@ -2003,6 +2003,38 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
"MOBILEGL_IPC_SERVER_PATH=${MGL_ITEST_SPLIT_SERVER_PATH}" "MGITEST_SPLIT_LANE=1"
|
||||
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV})
|
||||
|
||||
# A single supervisor serves one session at a time. Fixture teardown also reaps a
|
||||
# child left behind by a failed client; the resource lock serializes this lane.
|
||||
set(MOBILEGL_ITEST_TCP_ENDPOINT "tcp://127.0.0.1:40613" CACHE STRING "Loopback TCP integration endpoint")
|
||||
set(MOBILEGL_ITEST_TCP_DEVICE_ENDPOINT "" CACHE STRING "Optional device TCP integration endpoint")
|
||||
set(MOBILEGL_ITEST_TCP_DEVICE_TOKEN "" CACHE STRING "Token for the device TCP integration endpoint")
|
||||
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||
set(MGL_TCP_FIXTURE "${CMAKE_SOURCE_DIR}/scripts/ci/tcp_server_fixture.py")
|
||||
set(MGL_TCP_STATE "${CMAKE_CURRENT_BINARY_DIR}/tcp-server.json")
|
||||
add_test(NAME TcpServer.Start COMMAND "${Python3_EXECUTABLE}" "${MGL_TCP_FIXTURE}"
|
||||
start --server "${MGL_ITEST_SPLIT_SERVER_PATH}" --endpoint "${MOBILEGL_ITEST_TCP_ENDPOINT}"
|
||||
--state "${MGL_TCP_STATE}")
|
||||
add_test(NAME TcpServer.Stop COMMAND "${Python3_EXECUTABLE}" "${MGL_TCP_FIXTURE}"
|
||||
stop --state "${MGL_TCP_STATE}")
|
||||
set_tests_properties(TcpServer.Start PROPERTIES FIXTURES_SETUP mobilegl-tcp TIMEOUT 20)
|
||||
set_tests_properties(TcpServer.Stop PROPERTIES FIXTURES_CLEANUP mobilegl-tcp TIMEOUT 20)
|
||||
add_test(NAME TcpLane.AccountingControls COMMAND "${Python3_EXECUTABLE}"
|
||||
"${CMAKE_SOURCE_DIR}/scripts/ci/tcp_lane_tools_test.py")
|
||||
set_tests_properties(TcpLane.AccountingControls PROPERTIES LABELS unit TIMEOUT 20)
|
||||
mgl_itest_join_environment(MGL_ITEST_GLES_TCP_ENVIRONMENT
|
||||
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_TRANSPORT=spawn"
|
||||
"MOBILEGL_IPC_CONTROL=${MOBILEGL_ITEST_TCP_ENDPOINT}" "MOBILEGL_IPC_DATA=stream"
|
||||
"MOBILEGL_IPC_REQUIRE_SAME_BUILD=1" "MOBILEGL_IPC_LOG_FORWARD=1" "MOBILEGL_IPC_TOKEN="
|
||||
"MGITEST_SPLIT_LANE=1" "MGITEST_TCP_LANE=1" "MOBILEGL_ITEST_REQUIRE_GPU=1"
|
||||
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV})
|
||||
mgl_itest_join_environment(MGL_ITEST_GLES_TCPDEVICE_ENVIRONMENT
|
||||
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_TRANSPORT=spawn"
|
||||
"MOBILEGL_IPC_CONTROL=${MOBILEGL_ITEST_TCP_DEVICE_ENDPOINT}" "MOBILEGL_IPC_DATA=stream"
|
||||
"MOBILEGL_IPC_REQUIRE_SAME_BUILD=1" "MOBILEGL_IPC_LOG_FORWARD=1"
|
||||
"MOBILEGL_IPC_TOKEN=${MOBILEGL_ITEST_TCP_DEVICE_TOKEN}"
|
||||
"MGITEST_SPLIT_LANE=1" "MGITEST_TCP_LANE=1" "MOBILEGL_ITEST_REQUIRE_GPU=1"
|
||||
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_COMMON_ENV})
|
||||
|
||||
# scenarioFilter, a PREFIX TAIL (empty for most; "F1." and "Ct." for the two per-slot loops,
|
||||
# which name their sub-lane in the test name), then zero or more EXTRA LABEL STEMS. A stem is
|
||||
# suffixed with the arm's own name (`integration-clientarrays` becomes `-split` on one arm and
|
||||
@@ -2017,9 +2049,20 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
set(MGL_SPLIT_ARM_TEST_LISTS "")
|
||||
set(MGL_SPLIT_ARM_INDEX 0)
|
||||
macro(mgl_itest_register_split_arms scenarioFilter prefixTail)
|
||||
foreach(mglItestArm IN ITEMS Split Spawn)
|
||||
set(mglItestArms Split Spawn Tcp)
|
||||
if(MOBILEGL_ITEST_TCP_DEVICE_ENDPOINT)
|
||||
list(APPEND mglItestArms TcpDevice)
|
||||
endif()
|
||||
foreach(mglItestArm IN LISTS mglItestArms)
|
||||
string(TOLOWER "${mglItestArm}" mglItestArmLower)
|
||||
string(TOUPPER "${mglItestArm}" mglItestArmUpper)
|
||||
set(mglItestArmProperties)
|
||||
if(mglItestArm STREQUAL "Tcp")
|
||||
set(mglItestArmProperties FIXTURES_REQUIRED mobilegl-tcp RESOURCE_LOCK mobilegl-tcp)
|
||||
elseif(mglItestArm STREQUAL "TcpDevice")
|
||||
set(mglItestArmLower tcp-device)
|
||||
set(mglItestArmProperties RESOURCE_LOCK mobilegl-tcp-device)
|
||||
endif()
|
||||
set(mglItestArmLabels "integration-gpu\\;integration-${mglItestArmLower}")
|
||||
foreach(mglItestExtra IN ITEMS ${ARGN})
|
||||
set(mglItestArmLabels "${mglItestArmLabels}\\;${mglItestExtra}-${mglItestArmLower}")
|
||||
@@ -2042,6 +2085,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
LABELS "${mglItestArmLabels}"
|
||||
TIMEOUT ${MGL_ITEST_TIMEOUT}
|
||||
ENVIRONMENT "${MGL_ITEST_GLES_${mglItestArmUpper}_ENVIRONMENT}"
|
||||
${mglItestArmProperties}
|
||||
)
|
||||
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")
|
||||
|
||||
@@ -327,7 +327,18 @@ namespace MGITest {
|
||||
// has thought of. Only then does the parent do the real bring-up.
|
||||
//
|
||||
// Returns an empty string when the platform survived a full bring-up.
|
||||
bool RemoteConnectRequested() {
|
||||
const char* control = std::getenv("MOBILEGL_IPC_CONTROL");
|
||||
return control != nullptr && std::strncmp(control, "tcp://", 6) == 0;
|
||||
}
|
||||
|
||||
std::string PreflightBringUp() {
|
||||
// Connect already isolates driver setup in the server's session
|
||||
// child. A local pre-flight would consume a remote session, then
|
||||
// race its teardown and make the actual test receive Refuse{Busy}.
|
||||
if (RemoteConnectRequested()) {
|
||||
return {};
|
||||
}
|
||||
#if !MGITEST_HAVE_FORK_PREFLIGHT
|
||||
// No fork(): let the in-process bring-up speak for itself, which is
|
||||
// what this module did before. Windows/macOS are not CI targets for
|
||||
@@ -585,7 +596,9 @@ namespace MGITest {
|
||||
// That is a real result, not a machine without a GPU, so say so: it
|
||||
// means something is different between the two attempts (a leaked
|
||||
// exclusive device, an environment the child did not have).
|
||||
m_skipReason = reason + " - although an identical bring-up in a forked pre-flight child succeeded";
|
||||
m_skipReason = reason + (RemoteConnectRequested()
|
||||
? " - remote TCP bring-up failed"
|
||||
: " - although an identical bring-up in a forked pre-flight child succeeded");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
extern "C" void MGPipeSyncPeerLog();
|
||||
#endif
|
||||
|
||||
namespace MGITest::PipeStatsWindow {
|
||||
|
||||
// P6: MOBILEGL_LOG_FILE_PATH IS A BASE NAME, NOT A FILE. The library writes one log per
|
||||
@@ -121,6 +125,9 @@ namespace MGITest::PipeStatsWindow {
|
||||
}
|
||||
|
||||
inline LogMark MarkLaneLog() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MGPipeSyncPeerLog();
|
||||
#endif
|
||||
return LogMark{FileSizeOrZero(LibraryLogPath()), FileSizeOrZero(ServerLibraryLogPath())};
|
||||
}
|
||||
|
||||
@@ -131,6 +138,9 @@ namespace MGITest::PipeStatsWindow {
|
||||
// which is the most expensive possible way to be wrong: it accuses the product of a defect
|
||||
// that the reader itself invented.
|
||||
inline std::string ReadLaneLogSince(const LogMark& mark) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MGPipeSyncPeerLog();
|
||||
#endif
|
||||
return ReadFileSince(LibraryLogPath(), mark.client)
|
||||
+ ReadFileSince(ServerLibraryLogPath(), mark.server);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#if defined(__linux__)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -152,6 +156,18 @@ namespace MGITest {
|
||||
RecordProperty("split_transport", state.transportName);
|
||||
RecordProperty("split_implemented_verbs", static_cast<int>(state.implementedVerbs));
|
||||
RecordProperty("split_emit_seq_at_setup", static_cast<int>(state.emitSeq));
|
||||
#if defined(__linux__)
|
||||
if (std::getenv("MGITEST_TCP_LANE")) {
|
||||
// Connect must never silently launch a local server. The
|
||||
// companion log gate proves the remote Welcome pid/endpoint.
|
||||
std::ifstream children("/proc/self/task/" + std::to_string(::getpid()) + "/children");
|
||||
ASSERT_TRUE(children.good()) << "TCP topology proof cannot read /proc children";
|
||||
int child = 0;
|
||||
ASSERT_FALSE(static_cast<bool>(children >> child))
|
||||
<< "Dial=Connect created a local server child: " << child;
|
||||
RecordProperty("tcp_local_children", 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// A scenario starts from a clean slate but shares the context (and so
|
||||
// the renderer's memos) with every other scenario in this process -
|
||||
|
||||
@@ -48,7 +48,8 @@ def paths(document):
|
||||
props = {p["name"]: p["value"] for p in test.get("properties", [])}
|
||||
values = [v.split("=", 1)[1] for v in props.get("ENVIRONMENT", [])
|
||||
if v.startswith("MOBILEGL_LOG_FILE_PATH=")]
|
||||
is_split = test["name"].startswith(("DirectGLES.Split.", "DirectVulkan.Split."))
|
||||
is_split = test["name"].startswith(("DirectGLES.Split.", "DirectVulkan.Split.",
|
||||
"DirectGLES.Spawn.", "DirectGLES.Tcp.", "DirectGLES.TcpDevice."))
|
||||
if is_split and (len(values) != 1 or not values[0]):
|
||||
raise ValueError(f"{test['name']}: requires exactly one nonempty MOBILEGL_LOG_FILE_PATH")
|
||||
for value in values:
|
||||
|
||||
@@ -175,10 +175,8 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPBlobRef RendererInfo;
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<MGPCaps>, "MGPCaps must be trivially copyable");
|
||||
// Stated as a COMPOSITION rather than a literal: DynamicBackendParameters still carries
|
||||
// SizeT fields, so its literal size is ABI-dependent until P0.5 moves the caps block
|
||||
// into MGPipeValueTypes.h with fixed-width members. The assertion still fires on any
|
||||
// padding introduced between the members below.
|
||||
// Stated as a composition to catch padding between the wire members.
|
||||
// P6.5: all dynamic scalar widths are explicit; member offsets are in the wire digest.
|
||||
static_assert(sizeof(MGPCaps) == sizeof(DynamicBackendParameters) + 8 + 24 + 24,
|
||||
"MGPCaps gained padding or a member; update the wire format");
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// MobileGL - P6.5 wire compatibility facts. Generated member coverage is checked by gen_pipe.py.
|
||||
#pragma once
|
||||
#include "MGPipe.h"
|
||||
#include "MGPipeRenderStateSpans.h"
|
||||
|
||||
namespace MobileGL::MG_Pipe {
|
||||
#include "generated/PipeWireLayout.inc"
|
||||
|
||||
static_assert(sizeof(Bool) == 1 && sizeof(Float) == 4 && sizeof(Int32) == 4,
|
||||
"wire scalar representation is unsupported");
|
||||
static_assert(sizeof(RenderStateParameters) == 1168, "render-state wire blob changed");
|
||||
|
||||
constexpr void WireMixNumber(Uint64& hash, Uint64 value) {
|
||||
for (unsigned i = 0; i < 8; ++i) {
|
||||
hash ^= (value >> (i * 8)) & 255;
|
||||
hash *= 1099511628211ull;
|
||||
}
|
||||
}
|
||||
constexpr void WireMixName(Uint64& hash, const char* name) {
|
||||
do {
|
||||
hash ^= static_cast<unsigned char>(*name);
|
||||
hash *= 1099511628211ull;
|
||||
} while (*name++ != '\0');
|
||||
}
|
||||
constexpr Uint64 WireMemberLayoutDigest(const WireLayoutMember* fields, SizeT count) {
|
||||
Uint64 hash = 1469598103934665603ull;
|
||||
for (SizeT i = 0; i < count; ++i) {
|
||||
WireMixName(hash, fields[i].Name);
|
||||
WireMixNumber(hash, fields[i].Offset);
|
||||
WireMixNumber(hash, fields[i].Size);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
inline constexpr Uint64 kMGPipeWireMemberLayoutDigest = WireMemberLayoutDigest(
|
||||
kMGPipeWireLayoutMembers, sizeof(kMGPipeWireLayoutMembers) / sizeof(WireLayoutMember));
|
||||
|
||||
constexpr Uint64 WireCatalogueDigest() {
|
||||
Uint64 hash = 1469598103934665603ull;
|
||||
for (const auto& call : kMGPipeWireCallLayouts) {
|
||||
WireMixName(hash, call.Name);
|
||||
WireMixName(hash, call.Payload);
|
||||
WireMixNumber(hash, call.Op);
|
||||
WireMixNumber(hash, call.Flags);
|
||||
WireMixNumber(hash, call.Wait);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
inline constexpr Uint64 kMGPipeWireCatalogueDigest = WireCatalogueDigest();
|
||||
constexpr Uint64 WireRenderStateDigest() {
|
||||
Uint64 hash = 1469598103934665603ull;
|
||||
WireMixNumber(hash, kMGPipeRenderStateChunkTableVersion);
|
||||
for (SizeT boundary : kMGPipeRenderStateChunkBoundaries) WireMixNumber(hash, boundary);
|
||||
return hash;
|
||||
}
|
||||
} // namespace MobileGL::MG_Pipe
|
||||
@@ -0,0 +1,756 @@
|
||||
// MobileGL - MobileGL/MG_Pipe/generated/PipeWireLayout.inc
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// P6.5: wire layout and ordered catalogue.
|
||||
//
|
||||
// GENERATED by scripts/gen_pipe.py from PipeFields.def and PipeCalls.def - DO NOT EDIT.
|
||||
// Regenerate with `python3 scripts/gen_pipe.py`; CI runs it and diffs the result.
|
||||
// This file is included from MG_Pipe/MGPipe.h inside namespace MobileGL::MG_Pipe.
|
||||
|
||||
struct WireLayoutMember { const char* Name; Uint64 Offset; Uint64 Size; };
|
||||
inline constexpr WireLayoutMember kMGPipeWireLayoutMembers[] = {
|
||||
{"MGPBlobRef", 0, sizeof(MGPBlobRef)},
|
||||
{"MGPBlobRef.Offset", offsetof(MGPBlobRef, Offset), sizeof(((MGPBlobRef*)nullptr)->Offset)},
|
||||
{"MGPBlobRef.Size", offsetof(MGPBlobRef, Size), sizeof(((MGPBlobRef*)nullptr)->Size)},
|
||||
{"MGPBlobRef.Seg", offsetof(MGPBlobRef, Seg), sizeof(((MGPBlobRef*)nullptr)->Seg)},
|
||||
{"MGPRange", 0, sizeof(MGPRange)},
|
||||
{"MGPRange.Offset", offsetof(MGPRange, Offset), sizeof(((MGPRange*)nullptr)->Offset)},
|
||||
{"MGPRange.Size", offsetof(MGPRange, Size), sizeof(((MGPRange*)nullptr)->Size)},
|
||||
{"MGPBox", 0, sizeof(MGPBox)},
|
||||
{"MGPBox.X", offsetof(MGPBox, X), sizeof(((MGPBox*)nullptr)->X)},
|
||||
{"MGPBox.Y", offsetof(MGPBox, Y), sizeof(((MGPBox*)nullptr)->Y)},
|
||||
{"MGPBox.Z", offsetof(MGPBox, Z), sizeof(((MGPBox*)nullptr)->Z)},
|
||||
{"MGPBox.W", offsetof(MGPBox, W), sizeof(((MGPBox*)nullptr)->W)},
|
||||
{"MGPBox.H", offsetof(MGPBox, H), sizeof(((MGPBox*)nullptr)->H)},
|
||||
{"MGPBox.D", offsetof(MGPBox, D), sizeof(((MGPBox*)nullptr)->D)},
|
||||
{"MGPReplySlot", 0, sizeof(MGPReplySlot)},
|
||||
{"MGPReplySlot.Id", offsetof(MGPReplySlot, Id), sizeof(((MGPReplySlot*)nullptr)->Id)},
|
||||
{"MGPStateChunk", 0, sizeof(MGPStateChunk)},
|
||||
{"MGPStateChunk.Offset", offsetof(MGPStateChunk, Offset), sizeof(((MGPStateChunk*)nullptr)->Offset)},
|
||||
{"MGPStateChunk.Length", offsetof(MGPStateChunk, Length), sizeof(((MGPStateChunk*)nullptr)->Length)},
|
||||
{"MGPHandleOnly", 0, sizeof(MGPHandleOnly)},
|
||||
{"MGPHandleOnly.Handle", offsetof(MGPHandleOnly, Handle), sizeof(((MGPHandleOnly*)nullptr)->Handle)},
|
||||
{"MGPHandleOnly.Kind", offsetof(MGPHandleOnly, Kind), sizeof(((MGPHandleOnly*)nullptr)->Kind)},
|
||||
{"MGPCaps", 0, sizeof(MGPCaps)},
|
||||
{"MGPCaps.Dynamic", offsetof(MGPCaps, Dynamic), sizeof(((MGPCaps*)nullptr)->Dynamic)},
|
||||
{"MGPCaps.CallMask", offsetof(MGPCaps, CallMask), sizeof(((MGPCaps*)nullptr)->CallMask)},
|
||||
{"MGPCaps.FormatCapabilities", offsetof(MGPCaps, FormatCapabilities), sizeof(((MGPCaps*)nullptr)->FormatCapabilities)},
|
||||
{"MGPCaps.RendererInfo", offsetof(MGPCaps, RendererInfo), sizeof(((MGPCaps*)nullptr)->RendererInfo)},
|
||||
{"MGPResourceDesc", 0, sizeof(MGPResourceDesc)},
|
||||
{"MGPResourceDesc.Resource", offsetof(MGPResourceDesc, Resource), sizeof(((MGPResourceDesc*)nullptr)->Resource)},
|
||||
{"MGPResourceDesc.Target", offsetof(MGPResourceDesc, Target), sizeof(((MGPResourceDesc*)nullptr)->Target)},
|
||||
{"MGPResourceDesc.StorageKind", offsetof(MGPResourceDesc, StorageKind), sizeof(((MGPResourceDesc*)nullptr)->StorageKind)},
|
||||
{"MGPResourceDesc.BindMask", offsetof(MGPResourceDesc, BindMask), sizeof(((MGPResourceDesc*)nullptr)->BindMask)},
|
||||
{"MGPResourceDesc.InternalFormat", offsetof(MGPResourceDesc, InternalFormat), sizeof(((MGPResourceDesc*)nullptr)->InternalFormat)},
|
||||
{"MGPResourceDesc.Width", offsetof(MGPResourceDesc, Width), sizeof(((MGPResourceDesc*)nullptr)->Width)},
|
||||
{"MGPResourceDesc.Height", offsetof(MGPResourceDesc, Height), sizeof(((MGPResourceDesc*)nullptr)->Height)},
|
||||
{"MGPResourceDesc.Depth", offsetof(MGPResourceDesc, Depth), sizeof(((MGPResourceDesc*)nullptr)->Depth)},
|
||||
{"MGPResourceDesc.ArrayLayers", offsetof(MGPResourceDesc, ArrayLayers), sizeof(((MGPResourceDesc*)nullptr)->ArrayLayers)},
|
||||
{"MGPResourceDesc.Levels", offsetof(MGPResourceDesc, Levels), sizeof(((MGPResourceDesc*)nullptr)->Levels)},
|
||||
{"MGPResourceDesc.Samples", offsetof(MGPResourceDesc, Samples), sizeof(((MGPResourceDesc*)nullptr)->Samples)},
|
||||
{"MGPResourceDesc.FixedSampleLocations", offsetof(MGPResourceDesc, FixedSampleLocations), sizeof(((MGPResourceDesc*)nullptr)->FixedSampleLocations)},
|
||||
{"MGPResourceDesc.Immutable", offsetof(MGPResourceDesc, Immutable), sizeof(((MGPResourceDesc*)nullptr)->Immutable)},
|
||||
{"MGPResourceDesc.Usage", offsetof(MGPResourceDesc, Usage), sizeof(((MGPResourceDesc*)nullptr)->Usage)},
|
||||
{"MGPResourceDesc.StorageFlags", offsetof(MGPResourceDesc, StorageFlags), sizeof(((MGPResourceDesc*)nullptr)->StorageFlags)},
|
||||
{"MGPResourceDesc.HasDefinedContent", offsetof(MGPResourceDesc, HasDefinedContent), sizeof(((MGPResourceDesc*)nullptr)->HasDefinedContent)},
|
||||
{"MGPResourceDesc.ImageBindableHint", offsetof(MGPResourceDesc, ImageBindableHint), sizeof(((MGPResourceDesc*)nullptr)->ImageBindableHint)},
|
||||
{"MGPResourceDesc.HasRespecifiedLevel", offsetof(MGPResourceDesc, HasRespecifiedLevel), sizeof(((MGPResourceDesc*)nullptr)->HasRespecifiedLevel)},
|
||||
{"MGPResourceDesc.GlNameForDiag", offsetof(MGPResourceDesc, GlNameForDiag), sizeof(((MGPResourceDesc*)nullptr)->GlNameForDiag)},
|
||||
{"MGPResourceDesc.RespecifiedUploadTarget", offsetof(MGPResourceDesc, RespecifiedUploadTarget), sizeof(((MGPResourceDesc*)nullptr)->RespecifiedUploadTarget)},
|
||||
{"MGPResourceDesc.RespecifiedLevel", offsetof(MGPResourceDesc, RespecifiedLevel), sizeof(((MGPResourceDesc*)nullptr)->RespecifiedLevel)},
|
||||
{"MGPResourceDesc.ViewOf", offsetof(MGPResourceDesc, ViewOf), sizeof(((MGPResourceDesc*)nullptr)->ViewOf)},
|
||||
{"MGPResourceDesc.BufferForTexBuffer", offsetof(MGPResourceDesc, BufferForTexBuffer), sizeof(((MGPResourceDesc*)nullptr)->BufferForTexBuffer)},
|
||||
{"MGPResourceDesc.BufOffset", offsetof(MGPResourceDesc, BufOffset), sizeof(((MGPResourceDesc*)nullptr)->BufOffset)},
|
||||
{"MGPResourceDesc.BufSize", offsetof(MGPResourceDesc, BufSize), sizeof(((MGPResourceDesc*)nullptr)->BufSize)},
|
||||
{"MGPFenceWait", 0, sizeof(MGPFenceWait)},
|
||||
{"MGPFenceWait.Fence", offsetof(MGPFenceWait, Fence), sizeof(((MGPFenceWait*)nullptr)->Fence)},
|
||||
{"MGPFenceWait.TimeoutNs", offsetof(MGPFenceWait, TimeoutNs), sizeof(((MGPFenceWait*)nullptr)->TimeoutNs)},
|
||||
{"MGPFenceWait.Flags", offsetof(MGPFenceWait, Flags), sizeof(((MGPFenceWait*)nullptr)->Flags)},
|
||||
{"MGPQueryDesc", 0, sizeof(MGPQueryDesc)},
|
||||
{"MGPQueryDesc.Query", offsetof(MGPQueryDesc, Query), sizeof(((MGPQueryDesc*)nullptr)->Query)},
|
||||
{"MGPQueryDesc.Kind", offsetof(MGPQueryDesc, Kind), sizeof(((MGPQueryDesc*)nullptr)->Kind)},
|
||||
{"MGPQueryDesc.Stream", offsetof(MGPQueryDesc, Stream), sizeof(((MGPQueryDesc*)nullptr)->Stream)},
|
||||
{"MGPQueryResultRequest", 0, sizeof(MGPQueryResultRequest)},
|
||||
{"MGPQueryResultRequest.Query", offsetof(MGPQueryResultRequest, Query), sizeof(((MGPQueryResultRequest*)nullptr)->Query)},
|
||||
{"MGPQueryResultRequest.Wait", offsetof(MGPQueryResultRequest, Wait), sizeof(((MGPQueryResultRequest*)nullptr)->Wait)},
|
||||
{"MGPTimestampRequest", 0, sizeof(MGPTimestampRequest)},
|
||||
{"MGPTimestampRequest.Reserved", offsetof(MGPTimestampRequest, Reserved), sizeof(((MGPTimestampRequest*)nullptr)->Reserved)},
|
||||
{"MGPRenderStateDesc", 0, sizeof(MGPRenderStateDesc)},
|
||||
{"MGPRenderStateDesc.Cso", offsetof(MGPRenderStateDesc, Cso), sizeof(((MGPRenderStateDesc*)nullptr)->Cso)},
|
||||
{"MGPRenderStateDesc.BaseCso", offsetof(MGPRenderStateDesc, BaseCso), sizeof(((MGPRenderStateDesc*)nullptr)->BaseCso)},
|
||||
{"MGPRenderStateDesc.ChunkMask", offsetof(MGPRenderStateDesc, ChunkMask), sizeof(((MGPRenderStateDesc*)nullptr)->ChunkMask)},
|
||||
{"MGPRenderStateDesc.Blob", offsetof(MGPRenderStateDesc, Blob), sizeof(((MGPRenderStateDesc*)nullptr)->Blob)},
|
||||
{"MGPBindRenderState", 0, sizeof(MGPBindRenderState)},
|
||||
{"MGPBindRenderState.Cso", offsetof(MGPBindRenderState, Cso), sizeof(((MGPBindRenderState*)nullptr)->Cso)},
|
||||
{"MGPBindRenderState.Version", offsetof(MGPBindRenderState, Version), sizeof(((MGPBindRenderState*)nullptr)->Version)},
|
||||
{"MGPBindRenderState.PipelineVersion", offsetof(MGPBindRenderState, PipelineVersion), sizeof(((MGPBindRenderState*)nullptr)->PipelineVersion)},
|
||||
{"MGPDynamicState", 0, sizeof(MGPDynamicState)},
|
||||
{"MGPDynamicState.ChunkMask", offsetof(MGPDynamicState, ChunkMask), sizeof(((MGPDynamicState*)nullptr)->ChunkMask)},
|
||||
{"MGPDynamicState.Version", offsetof(MGPDynamicState, Version), sizeof(((MGPDynamicState*)nullptr)->Version)},
|
||||
{"MGPDynamicState.Blob", offsetof(MGPDynamicState, Blob), sizeof(((MGPDynamicState*)nullptr)->Blob)},
|
||||
{"MGPVertexElements", 0, sizeof(MGPVertexElements)},
|
||||
{"MGPVertexElements.Cso", offsetof(MGPVertexElements, Cso), sizeof(((MGPVertexElements*)nullptr)->Cso)},
|
||||
{"MGPVertexElements.AttributeCount", offsetof(MGPVertexElements, AttributeCount), sizeof(((MGPVertexElements*)nullptr)->AttributeCount)},
|
||||
{"MGPVertexElements.BindingPointCount", offsetof(MGPVertexElements, BindingPointCount), sizeof(((MGPVertexElements*)nullptr)->BindingPointCount)},
|
||||
{"MGPVertexElements.Blob", offsetof(MGPVertexElements, Blob), sizeof(((MGPVertexElements*)nullptr)->Blob)},
|
||||
{"MGPSamplerDesc", 0, sizeof(MGPSamplerDesc)},
|
||||
{"MGPSamplerDesc.Cso", offsetof(MGPSamplerDesc, Cso), sizeof(((MGPSamplerDesc*)nullptr)->Cso)},
|
||||
{"MGPSamplerDesc.Parameters", offsetof(MGPSamplerDesc, Parameters), sizeof(((MGPSamplerDesc*)nullptr)->Parameters)},
|
||||
{"MGPSamplerView", 0, sizeof(MGPSamplerView)},
|
||||
{"MGPSamplerView.Cso", offsetof(MGPSamplerView, Cso), sizeof(((MGPSamplerView*)nullptr)->Cso)},
|
||||
{"MGPSamplerView.Texture", offsetof(MGPSamplerView, Texture), sizeof(((MGPSamplerView*)nullptr)->Texture)},
|
||||
{"MGPSamplerView.InternalFormat", offsetof(MGPSamplerView, InternalFormat), sizeof(((MGPSamplerView*)nullptr)->InternalFormat)},
|
||||
{"MGPSamplerView.Target", offsetof(MGPSamplerView, Target), sizeof(((MGPSamplerView*)nullptr)->Target)},
|
||||
{"MGPSamplerView.MinLevel", offsetof(MGPSamplerView, MinLevel), sizeof(((MGPSamplerView*)nullptr)->MinLevel)},
|
||||
{"MGPSamplerView.NumLevels", offsetof(MGPSamplerView, NumLevels), sizeof(((MGPSamplerView*)nullptr)->NumLevels)},
|
||||
{"MGPSamplerView.MinLayer", offsetof(MGPSamplerView, MinLayer), sizeof(((MGPSamplerView*)nullptr)->MinLayer)},
|
||||
{"MGPSamplerView.NumLayers", offsetof(MGPSamplerView, NumLayers), sizeof(((MGPSamplerView*)nullptr)->NumLayers)},
|
||||
{"MGPSamplerView.Samples", offsetof(MGPSamplerView, Samples), sizeof(((MGPSamplerView*)nullptr)->Samples)},
|
||||
{"MGPSamplerView.FixedSampleLocations", offsetof(MGPSamplerView, FixedSampleLocations), sizeof(((MGPSamplerView*)nullptr)->FixedSampleLocations)},
|
||||
{"MGPTextureParams", 0, sizeof(MGPTextureParams)},
|
||||
{"MGPTextureParams.Res", offsetof(MGPTextureParams, Res), sizeof(((MGPTextureParams*)nullptr)->Res)},
|
||||
{"MGPTextureParams.BuiltinSampler", offsetof(MGPTextureParams, BuiltinSampler), sizeof(((MGPTextureParams*)nullptr)->BuiltinSampler)},
|
||||
{"MGPTextureParams.BaseLevel", offsetof(MGPTextureParams, BaseLevel), sizeof(((MGPTextureParams*)nullptr)->BaseLevel)},
|
||||
{"MGPTextureParams.MaxLevel", offsetof(MGPTextureParams, MaxLevel), sizeof(((MGPTextureParams*)nullptr)->MaxLevel)},
|
||||
{"MGPTextureParams.Swizzle", offsetof(MGPTextureParams, Swizzle), sizeof(((MGPTextureParams*)nullptr)->Swizzle)},
|
||||
{"MGPTextureParams.DepthStencilMode", offsetof(MGPTextureParams, DepthStencilMode), sizeof(((MGPTextureParams*)nullptr)->DepthStencilMode)},
|
||||
{"MGPTextureParams.ForceResync", offsetof(MGPTextureParams, ForceResync), sizeof(((MGPTextureParams*)nullptr)->ForceResync)},
|
||||
{"MGPTextureParams.SamplerResync", offsetof(MGPTextureParams, SamplerResync), sizeof(((MGPTextureParams*)nullptr)->SamplerResync)},
|
||||
{"MGPTextureParams.MinLod", offsetof(MGPTextureParams, MinLod), sizeof(((MGPTextureParams*)nullptr)->MinLod)},
|
||||
{"MGPTextureParams.MaxLod", offsetof(MGPTextureParams, MaxLod), sizeof(((MGPTextureParams*)nullptr)->MaxLod)},
|
||||
{"MGPTextureParams.LodBias", offsetof(MGPTextureParams, LodBias), sizeof(((MGPTextureParams*)nullptr)->LodBias)},
|
||||
{"MGPProgramDesc", 0, sizeof(MGPProgramDesc)},
|
||||
{"MGPProgramDesc.Cso", offsetof(MGPProgramDesc, Cso), sizeof(((MGPProgramDesc*)nullptr)->Cso)},
|
||||
{"MGPProgramDesc.StageMask", offsetof(MGPProgramDesc, StageMask), sizeof(((MGPProgramDesc*)nullptr)->StageMask)},
|
||||
{"MGPProgramDesc.GlobalUboSize", offsetof(MGPProgramDesc, GlobalUboSize), sizeof(((MGPProgramDesc*)nullptr)->GlobalUboSize)},
|
||||
{"MGPProgramDesc.ReservedNumSamplesOffset", offsetof(MGPProgramDesc, ReservedNumSamplesOffset), sizeof(((MGPProgramDesc*)nullptr)->ReservedNumSamplesOffset)},
|
||||
{"MGPProgramDesc.SpirvStatus", offsetof(MGPProgramDesc, SpirvStatus), sizeof(((MGPProgramDesc*)nullptr)->SpirvStatus)},
|
||||
{"MGPProgramDesc.NativeFloat64", offsetof(MGPProgramDesc, NativeFloat64), sizeof(((MGPProgramDesc*)nullptr)->NativeFloat64)},
|
||||
{"MGPProgramDesc.PointSizeDemoted", offsetof(MGPProgramDesc, PointSizeDemoted), sizeof(((MGPProgramDesc*)nullptr)->PointSizeDemoted)},
|
||||
{"MGPProgramDesc.EnableSpirvValidation", offsetof(MGPProgramDesc, EnableSpirvValidation), sizeof(((MGPProgramDesc*)nullptr)->EnableSpirvValidation)},
|
||||
{"MGPProgramDesc.LinkStatus", offsetof(MGPProgramDesc, LinkStatus), sizeof(((MGPProgramDesc*)nullptr)->LinkStatus)},
|
||||
{"MGPProgramDesc.Spirv", offsetof(MGPProgramDesc, Spirv), sizeof(((MGPProgramDesc*)nullptr)->Spirv)},
|
||||
{"MGPProgramDesc.Reflection", offsetof(MGPProgramDesc, Reflection), sizeof(((MGPProgramDesc*)nullptr)->Reflection)},
|
||||
{"MGPSurface", 0, sizeof(MGPSurface)},
|
||||
{"MGPSurface.Res", offsetof(MGPSurface, Res), sizeof(((MGPSurface*)nullptr)->Res)},
|
||||
{"MGPSurface.InternalFormat", offsetof(MGPSurface, InternalFormat), sizeof(((MGPSurface*)nullptr)->InternalFormat)},
|
||||
{"MGPSurface.Kind", offsetof(MGPSurface, Kind), sizeof(((MGPSurface*)nullptr)->Kind)},
|
||||
{"MGPSurface.Layered", offsetof(MGPSurface, Layered), sizeof(((MGPSurface*)nullptr)->Layered)},
|
||||
{"MGPSurface.Level", offsetof(MGPSurface, Level), sizeof(((MGPSurface*)nullptr)->Level)},
|
||||
{"MGPSurface.Layer", offsetof(MGPSurface, Layer), sizeof(((MGPSurface*)nullptr)->Layer)},
|
||||
{"MGPSurface.UploadTarget", offsetof(MGPSurface, UploadTarget), sizeof(((MGPSurface*)nullptr)->UploadTarget)},
|
||||
{"MGPSurface.TextureTarget", offsetof(MGPSurface, TextureTarget), sizeof(((MGPSurface*)nullptr)->TextureTarget)},
|
||||
{"MGPFramebufferState", 0, sizeof(MGPFramebufferState)},
|
||||
{"MGPFramebufferState.Fbo", offsetof(MGPFramebufferState, Fbo), sizeof(((MGPFramebufferState*)nullptr)->Fbo)},
|
||||
{"MGPFramebufferState.Color", offsetof(MGPFramebufferState, Color), sizeof(((MGPFramebufferState*)nullptr)->Color)},
|
||||
{"MGPFramebufferState.Depth", offsetof(MGPFramebufferState, Depth), sizeof(((MGPFramebufferState*)nullptr)->Depth)},
|
||||
{"MGPFramebufferState.Stencil", offsetof(MGPFramebufferState, Stencil), sizeof(((MGPFramebufferState*)nullptr)->Stencil)},
|
||||
{"MGPFramebufferState.ReadSurface", offsetof(MGPFramebufferState, ReadSurface), sizeof(((MGPFramebufferState*)nullptr)->ReadSurface)},
|
||||
{"MGPFramebufferState.DrawBuffers", offsetof(MGPFramebufferState, DrawBuffers), sizeof(((MGPFramebufferState*)nullptr)->DrawBuffers)},
|
||||
{"MGPFramebufferState.Width", offsetof(MGPFramebufferState, Width), sizeof(((MGPFramebufferState*)nullptr)->Width)},
|
||||
{"MGPFramebufferState.Height", offsetof(MGPFramebufferState, Height), sizeof(((MGPFramebufferState*)nullptr)->Height)},
|
||||
{"MGPFramebufferState.Layers", offsetof(MGPFramebufferState, Layers), sizeof(((MGPFramebufferState*)nullptr)->Layers)},
|
||||
{"MGPFramebufferState.Samples", offsetof(MGPFramebufferState, Samples), sizeof(((MGPFramebufferState*)nullptr)->Samples)},
|
||||
{"MGPFramebufferState.FixedSampleLocations", offsetof(MGPFramebufferState, FixedSampleLocations), sizeof(((MGPFramebufferState*)nullptr)->FixedSampleLocations)},
|
||||
{"MGPFramebufferState.IsDefault", offsetof(MGPFramebufferState, IsDefault), sizeof(((MGPFramebufferState*)nullptr)->IsDefault)},
|
||||
{"MGPFramebufferState.Complete", offsetof(MGPFramebufferState, Complete), sizeof(((MGPFramebufferState*)nullptr)->Complete)},
|
||||
{"MGPFramebufferState.Target", offsetof(MGPFramebufferState, Target), sizeof(((MGPFramebufferState*)nullptr)->Target)},
|
||||
{"MGPFramebufferState.ContentHash", offsetof(MGPFramebufferState, ContentHash), sizeof(((MGPFramebufferState*)nullptr)->ContentHash)},
|
||||
{"MGPVertexBuffer", 0, sizeof(MGPVertexBuffer)},
|
||||
{"MGPVertexBuffer.Res", offsetof(MGPVertexBuffer, Res), sizeof(((MGPVertexBuffer*)nullptr)->Res)},
|
||||
{"MGPVertexBuffer.Offset", offsetof(MGPVertexBuffer, Offset), sizeof(((MGPVertexBuffer*)nullptr)->Offset)},
|
||||
{"MGPVertexBuffer.Stride", offsetof(MGPVertexBuffer, Stride), sizeof(((MGPVertexBuffer*)nullptr)->Stride)},
|
||||
{"MGPVertexBuffer.Divisor", offsetof(MGPVertexBuffer, Divisor), sizeof(((MGPVertexBuffer*)nullptr)->Divisor)},
|
||||
{"MGPVertexBuffer.BindingIndex", offsetof(MGPVertexBuffer, BindingIndex), sizeof(((MGPVertexBuffer*)nullptr)->BindingIndex)},
|
||||
{"MGPVertexBuffers", 0, sizeof(MGPVertexBuffers)},
|
||||
{"MGPVertexBuffers.Start", offsetof(MGPVertexBuffers, Start), sizeof(((MGPVertexBuffers*)nullptr)->Start)},
|
||||
{"MGPVertexBuffers.Count", offsetof(MGPVertexBuffers, Count), sizeof(((MGPVertexBuffers*)nullptr)->Count)},
|
||||
{"MGPVertexBuffers.BaseInstance", offsetof(MGPVertexBuffers, BaseInstance), sizeof(((MGPVertexBuffers*)nullptr)->BaseInstance)},
|
||||
{"MGPVertexBuffers.ContentHash", offsetof(MGPVertexBuffers, ContentHash), sizeof(((MGPVertexBuffers*)nullptr)->ContentHash)},
|
||||
{"MGPIndexBuffer", 0, sizeof(MGPIndexBuffer)},
|
||||
{"MGPIndexBuffer.Res", offsetof(MGPIndexBuffer, Res), sizeof(((MGPIndexBuffer*)nullptr)->Res)},
|
||||
{"MGPIndexBuffer.Offset", offsetof(MGPIndexBuffer, Offset), sizeof(((MGPIndexBuffer*)nullptr)->Offset)},
|
||||
{"MGPIndexBuffer.IndexSize", offsetof(MGPIndexBuffer, IndexSize), sizeof(((MGPIndexBuffer*)nullptr)->IndexSize)},
|
||||
{"MGPIndirectBuffers", 0, sizeof(MGPIndirectBuffers)},
|
||||
{"MGPIndirectBuffers.DrawIndirect", offsetof(MGPIndirectBuffers, DrawIndirect), sizeof(((MGPIndirectBuffers*)nullptr)->DrawIndirect)},
|
||||
{"MGPIndirectBuffers.Parameter", offsetof(MGPIndirectBuffers, Parameter), sizeof(((MGPIndirectBuffers*)nullptr)->Parameter)},
|
||||
{"MGPBoundView", 0, sizeof(MGPBoundView)},
|
||||
{"MGPBoundView.View", offsetof(MGPBoundView, View), sizeof(((MGPBoundView*)nullptr)->View)},
|
||||
{"MGPBoundView.Texture", offsetof(MGPBoundView, Texture), sizeof(((MGPBoundView*)nullptr)->Texture)},
|
||||
{"MGPBoundView.Unit", offsetof(MGPBoundView, Unit), sizeof(((MGPBoundView*)nullptr)->Unit)},
|
||||
{"MGPSamplerViews", 0, sizeof(MGPSamplerViews)},
|
||||
{"MGPSamplerViews.Start", offsetof(MGPSamplerViews, Start), sizeof(((MGPSamplerViews*)nullptr)->Start)},
|
||||
{"MGPSamplerViews.Count", offsetof(MGPSamplerViews, Count), sizeof(((MGPSamplerViews*)nullptr)->Count)},
|
||||
{"MGPSamplerViews.ContentHash", offsetof(MGPSamplerViews, ContentHash), sizeof(((MGPSamplerViews*)nullptr)->ContentHash)},
|
||||
{"MGPSamplerStates", 0, sizeof(MGPSamplerStates)},
|
||||
{"MGPSamplerStates.Start", offsetof(MGPSamplerStates, Start), sizeof(((MGPSamplerStates*)nullptr)->Start)},
|
||||
{"MGPSamplerStates.Count", offsetof(MGPSamplerStates, Count), sizeof(((MGPSamplerStates*)nullptr)->Count)},
|
||||
{"MGPSamplerStates.ContentHash", offsetof(MGPSamplerStates, ContentHash), sizeof(((MGPSamplerStates*)nullptr)->ContentHash)},
|
||||
{"MGPImageView", 0, sizeof(MGPImageView)},
|
||||
{"MGPImageView.Res", offsetof(MGPImageView, Res), sizeof(((MGPImageView*)nullptr)->Res)},
|
||||
{"MGPImageView.Unit", offsetof(MGPImageView, Unit), sizeof(((MGPImageView*)nullptr)->Unit)},
|
||||
{"MGPImageView.InternalFormat", offsetof(MGPImageView, InternalFormat), sizeof(((MGPImageView*)nullptr)->InternalFormat)},
|
||||
{"MGPImageView.Layer", offsetof(MGPImageView, Layer), sizeof(((MGPImageView*)nullptr)->Layer)},
|
||||
{"MGPImageView.Level", offsetof(MGPImageView, Level), sizeof(((MGPImageView*)nullptr)->Level)},
|
||||
{"MGPImageView.Layered", offsetof(MGPImageView, Layered), sizeof(((MGPImageView*)nullptr)->Layered)},
|
||||
{"MGPImageView.Access", offsetof(MGPImageView, Access), sizeof(((MGPImageView*)nullptr)->Access)},
|
||||
{"MGPShaderImages", 0, sizeof(MGPShaderImages)},
|
||||
{"MGPShaderImages.Start", offsetof(MGPShaderImages, Start), sizeof(((MGPShaderImages*)nullptr)->Start)},
|
||||
{"MGPShaderImages.Count", offsetof(MGPShaderImages, Count), sizeof(((MGPShaderImages*)nullptr)->Count)},
|
||||
{"MGPShaderImages.ContentHash", offsetof(MGPShaderImages, ContentHash), sizeof(((MGPShaderImages*)nullptr)->ContentHash)},
|
||||
{"MGPBufferRange", 0, sizeof(MGPBufferRange)},
|
||||
{"MGPBufferRange.Res", offsetof(MGPBufferRange, Res), sizeof(((MGPBufferRange*)nullptr)->Res)},
|
||||
{"MGPBufferRange.Offset", offsetof(MGPBufferRange, Offset), sizeof(((MGPBufferRange*)nullptr)->Offset)},
|
||||
{"MGPBufferRange.Size", offsetof(MGPBufferRange, Size), sizeof(((MGPBufferRange*)nullptr)->Size)},
|
||||
{"MGPShaderBuffers", 0, sizeof(MGPShaderBuffers)},
|
||||
{"MGPShaderBuffers.Class", offsetof(MGPShaderBuffers, Class), sizeof(((MGPShaderBuffers*)nullptr)->Class)},
|
||||
{"MGPShaderBuffers.Start", offsetof(MGPShaderBuffers, Start), sizeof(((MGPShaderBuffers*)nullptr)->Start)},
|
||||
{"MGPShaderBuffers.Count", offsetof(MGPShaderBuffers, Count), sizeof(((MGPShaderBuffers*)nullptr)->Count)},
|
||||
{"MGPShaderBuffers.WritableMask", offsetof(MGPShaderBuffers, WritableMask), sizeof(((MGPShaderBuffers*)nullptr)->WritableMask)},
|
||||
{"MGPShaderBuffers.HostSpanCount", offsetof(MGPShaderBuffers, HostSpanCount), sizeof(((MGPShaderBuffers*)nullptr)->HostSpanCount)},
|
||||
{"MGPShaderBuffers.ContentHash", offsetof(MGPShaderBuffers, ContentHash), sizeof(((MGPShaderBuffers*)nullptr)->ContentHash)},
|
||||
{"MGPStreamOutputTargets", 0, sizeof(MGPStreamOutputTargets)},
|
||||
{"MGPStreamOutputTargets.Count", offsetof(MGPStreamOutputTargets, Count), sizeof(((MGPStreamOutputTargets*)nullptr)->Count)},
|
||||
{"MGPStreamOutputTargets.Generation", offsetof(MGPStreamOutputTargets, Generation), sizeof(((MGPStreamOutputTargets*)nullptr)->Generation)},
|
||||
{"MGPStreamOutputTargets.ContentHash", offsetof(MGPStreamOutputTargets, ContentHash), sizeof(((MGPStreamOutputTargets*)nullptr)->ContentHash)},
|
||||
{"MGPGlobalConstants", 0, sizeof(MGPGlobalConstants)},
|
||||
{"MGPGlobalConstants.ShaderCso", offsetof(MGPGlobalConstants, ShaderCso), sizeof(((MGPGlobalConstants*)nullptr)->ShaderCso)},
|
||||
{"MGPGlobalConstants.Version", offsetof(MGPGlobalConstants, Version), sizeof(((MGPGlobalConstants*)nullptr)->Version)},
|
||||
{"MGPGlobalConstants.Blob", offsetof(MGPGlobalConstants, Blob), sizeof(((MGPGlobalConstants*)nullptr)->Blob)},
|
||||
{"MGPAttribValue", 0, sizeof(MGPAttribValue)},
|
||||
{"MGPAttribValue.Location", offsetof(MGPAttribValue, Location), sizeof(((MGPAttribValue*)nullptr)->Location)},
|
||||
{"MGPAttribValue.ValueClass", offsetof(MGPAttribValue, ValueClass), sizeof(((MGPAttribValue*)nullptr)->ValueClass)},
|
||||
{"MGPAttribValue.FloatView", offsetof(MGPAttribValue, FloatView), sizeof(((MGPAttribValue*)nullptr)->FloatView)},
|
||||
{"MGPAttribValue.IntView", offsetof(MGPAttribValue, IntView), sizeof(((MGPAttribValue*)nullptr)->IntView)},
|
||||
{"MGPAttribValue.UintView", offsetof(MGPAttribValue, UintView), sizeof(((MGPAttribValue*)nullptr)->UintView)},
|
||||
{"MGPVertexAttribDefaults", 0, sizeof(MGPVertexAttribDefaults)},
|
||||
{"MGPVertexAttribDefaults.Mask", offsetof(MGPVertexAttribDefaults, Mask), sizeof(((MGPVertexAttribDefaults*)nullptr)->Mask)},
|
||||
{"MGPVertexAttribDefaults.Count", offsetof(MGPVertexAttribDefaults, Count), sizeof(((MGPVertexAttribDefaults*)nullptr)->Count)},
|
||||
{"MGPPixelPackState", 0, sizeof(MGPPixelPackState)},
|
||||
{"MGPPixelPackState.Pack", offsetof(MGPPixelPackState, Pack), sizeof(((MGPPixelPackState*)nullptr)->Pack)},
|
||||
{"MGPPatchState", 0, sizeof(MGPPatchState)},
|
||||
{"MGPPatchState.Vertices", offsetof(MGPPatchState, Vertices), sizeof(((MGPPatchState*)nullptr)->Vertices)},
|
||||
{"MGPPatchState.Outer", offsetof(MGPPatchState, Outer), sizeof(((MGPPatchState*)nullptr)->Outer)},
|
||||
{"MGPPatchState.Inner", offsetof(MGPPatchState, Inner), sizeof(((MGPPatchState*)nullptr)->Inner)},
|
||||
{"ResidualValueBlock", 0, sizeof(ResidualValueBlock)},
|
||||
{"ResidualValueBlock.CapabilityBits", offsetof(ResidualValueBlock, CapabilityBits), sizeof(((ResidualValueBlock*)nullptr)->CapabilityBits)},
|
||||
{"MGPResidualValueState", 0, sizeof(MGPResidualValueState)},
|
||||
{"MGPResidualValueState.Version", offsetof(MGPResidualValueState, Version), sizeof(((MGPResidualValueState*)nullptr)->Version)},
|
||||
{"MGPResidualValueState.Blob", offsetof(MGPResidualValueState, Blob), sizeof(((MGPResidualValueState*)nullptr)->Blob)},
|
||||
{"MGPSubRegion", 0, sizeof(MGPSubRegion)},
|
||||
{"MGPSubRegion.X", offsetof(MGPSubRegion, X), sizeof(((MGPSubRegion*)nullptr)->X)},
|
||||
{"MGPSubRegion.Y", offsetof(MGPSubRegion, Y), sizeof(((MGPSubRegion*)nullptr)->Y)},
|
||||
{"MGPSubRegion.Z", offsetof(MGPSubRegion, Z), sizeof(((MGPSubRegion*)nullptr)->Z)},
|
||||
{"MGPSubRegion.W", offsetof(MGPSubRegion, W), sizeof(((MGPSubRegion*)nullptr)->W)},
|
||||
{"MGPSubRegion.H", offsetof(MGPSubRegion, H), sizeof(((MGPSubRegion*)nullptr)->H)},
|
||||
{"MGPSubRegion.D", offsetof(MGPSubRegion, D), sizeof(((MGPSubRegion*)nullptr)->D)},
|
||||
{"MGPSubRegion.SrcOffset", offsetof(MGPSubRegion, SrcOffset), sizeof(((MGPSubRegion*)nullptr)->SrcOffset)},
|
||||
{"MGPSubRegion.SrcRowStride", offsetof(MGPSubRegion, SrcRowStride), sizeof(((MGPSubRegion*)nullptr)->SrcRowStride)},
|
||||
{"MGPSubRegion.SrcSliceStride", offsetof(MGPSubRegion, SrcSliceStride), sizeof(((MGPSubRegion*)nullptr)->SrcSliceStride)},
|
||||
{"MGPSubData", 0, sizeof(MGPSubData)},
|
||||
{"MGPSubData.Res", offsetof(MGPSubData, Res), sizeof(((MGPSubData*)nullptr)->Res)},
|
||||
{"MGPSubData.Target", offsetof(MGPSubData, Target), sizeof(((MGPSubData*)nullptr)->Target)},
|
||||
{"MGPSubData.Level", offsetof(MGPSubData, Level), sizeof(((MGPSubData*)nullptr)->Level)},
|
||||
{"MGPSubData.SourceIsVerbatimLevelShadow", offsetof(MGPSubData, SourceIsVerbatimLevelShadow), sizeof(((MGPSubData*)nullptr)->SourceIsVerbatimLevelShadow)},
|
||||
{"MGPSubData.HasLiveHostWrites", offsetof(MGPSubData, HasLiveHostWrites), sizeof(((MGPSubData*)nullptr)->HasLiveHostWrites)},
|
||||
{"MGPSubData.UnionBox", offsetof(MGPSubData, UnionBox), sizeof(((MGPSubData*)nullptr)->UnionBox)},
|
||||
{"MGPSubData.RegionCount", offsetof(MGPSubData, RegionCount), sizeof(((MGPSubData*)nullptr)->RegionCount)},
|
||||
{"MGPSubData.Blob", offsetof(MGPSubData, Blob), sizeof(((MGPSubData*)nullptr)->Blob)},
|
||||
{"MGPSubData.LevelWidth", offsetof(MGPSubData, LevelWidth), sizeof(((MGPSubData*)nullptr)->LevelWidth)},
|
||||
{"MGPSubData.LevelHeight", offsetof(MGPSubData, LevelHeight), sizeof(((MGPSubData*)nullptr)->LevelHeight)},
|
||||
{"MGPSubData.LevelDepth", offsetof(MGPSubData, LevelDepth), sizeof(((MGPSubData*)nullptr)->LevelDepth)},
|
||||
{"MGPSubData.LevelPad", offsetof(MGPSubData, LevelPad), sizeof(((MGPSubData*)nullptr)->LevelPad)},
|
||||
{"MGPSubDataComplete", 0, sizeof(MGPSubDataComplete)},
|
||||
{"MGPSubDataComplete.Res", offsetof(MGPSubDataComplete, Res), sizeof(((MGPSubDataComplete*)nullptr)->Res)},
|
||||
{"MGPSubDataComplete.Target", offsetof(MGPSubDataComplete, Target), sizeof(((MGPSubDataComplete*)nullptr)->Target)},
|
||||
{"MGPSubDataComplete.FirstLevel", offsetof(MGPSubDataComplete, FirstLevel), sizeof(((MGPSubDataComplete*)nullptr)->FirstLevel)},
|
||||
{"MGPSubDataComplete.LevelCount", offsetof(MGPSubDataComplete, LevelCount), sizeof(((MGPSubDataComplete*)nullptr)->LevelCount)},
|
||||
{"MGPSubDataComplete.PullSerial", offsetof(MGPSubDataComplete, PullSerial), sizeof(((MGPSubDataComplete*)nullptr)->PullSerial)},
|
||||
{"MGPFlushRange", 0, sizeof(MGPFlushRange)},
|
||||
{"MGPFlushRange.Res", offsetof(MGPFlushRange, Res), sizeof(((MGPFlushRange*)nullptr)->Res)},
|
||||
{"MGPFlushRange.Offset", offsetof(MGPFlushRange, Offset), sizeof(((MGPFlushRange*)nullptr)->Offset)},
|
||||
{"MGPFlushRange.Size", offsetof(MGPFlushRange, Size), sizeof(((MGPFlushRange*)nullptr)->Size)},
|
||||
{"MGPFlushRange.AccessFlags", offsetof(MGPFlushRange, AccessFlags), sizeof(((MGPFlushRange*)nullptr)->AccessFlags)},
|
||||
{"MGPReadback", 0, sizeof(MGPReadback)},
|
||||
{"MGPReadback.Res", offsetof(MGPReadback, Res), sizeof(((MGPReadback*)nullptr)->Res)},
|
||||
{"MGPReadback.Offset", offsetof(MGPReadback, Offset), sizeof(((MGPReadback*)nullptr)->Offset)},
|
||||
{"MGPReadback.Size", offsetof(MGPReadback, Size), sizeof(((MGPReadback*)nullptr)->Size)},
|
||||
{"MGPCopyRegion", 0, sizeof(MGPCopyRegion)},
|
||||
{"MGPCopyRegion.Src", offsetof(MGPCopyRegion, Src), sizeof(((MGPCopyRegion*)nullptr)->Src)},
|
||||
{"MGPCopyRegion.Dst", offsetof(MGPCopyRegion, Dst), sizeof(((MGPCopyRegion*)nullptr)->Dst)},
|
||||
{"MGPCopyRegion.SrcBox", offsetof(MGPCopyRegion, SrcBox), sizeof(((MGPCopyRegion*)nullptr)->SrcBox)},
|
||||
{"MGPCopyRegion.DstX", offsetof(MGPCopyRegion, DstX), sizeof(((MGPCopyRegion*)nullptr)->DstX)},
|
||||
{"MGPCopyRegion.DstY", offsetof(MGPCopyRegion, DstY), sizeof(((MGPCopyRegion*)nullptr)->DstY)},
|
||||
{"MGPCopyRegion.DstZ", offsetof(MGPCopyRegion, DstZ), sizeof(((MGPCopyRegion*)nullptr)->DstZ)},
|
||||
{"MGPCopyRegion.SrcTarget", offsetof(MGPCopyRegion, SrcTarget), sizeof(((MGPCopyRegion*)nullptr)->SrcTarget)},
|
||||
{"MGPCopyRegion.DstTarget", offsetof(MGPCopyRegion, DstTarget), sizeof(((MGPCopyRegion*)nullptr)->DstTarget)},
|
||||
{"MGPCopyRegion.SrcLevel", offsetof(MGPCopyRegion, SrcLevel), sizeof(((MGPCopyRegion*)nullptr)->SrcLevel)},
|
||||
{"MGPCopyRegion.DstLevel", offsetof(MGPCopyRegion, DstLevel), sizeof(((MGPCopyRegion*)nullptr)->DstLevel)},
|
||||
{"MGPCopyRegion.SrcGlName", offsetof(MGPCopyRegion, SrcGlName), sizeof(((MGPCopyRegion*)nullptr)->SrcGlName)},
|
||||
{"MGPCopyRegion.DstGlName", offsetof(MGPCopyRegion, DstGlName), sizeof(((MGPCopyRegion*)nullptr)->DstGlName)},
|
||||
{"MGPBlit", 0, sizeof(MGPBlit)},
|
||||
{"MGPBlit.ReadFbo", offsetof(MGPBlit, ReadFbo), sizeof(((MGPBlit*)nullptr)->ReadFbo)},
|
||||
{"MGPBlit.DrawFbo", offsetof(MGPBlit, DrawFbo), sizeof(((MGPBlit*)nullptr)->DrawFbo)},
|
||||
{"MGPBlit.SrcX0", offsetof(MGPBlit, SrcX0), sizeof(((MGPBlit*)nullptr)->SrcX0)},
|
||||
{"MGPBlit.SrcY0", offsetof(MGPBlit, SrcY0), sizeof(((MGPBlit*)nullptr)->SrcY0)},
|
||||
{"MGPBlit.SrcX1", offsetof(MGPBlit, SrcX1), sizeof(((MGPBlit*)nullptr)->SrcX1)},
|
||||
{"MGPBlit.SrcY1", offsetof(MGPBlit, SrcY1), sizeof(((MGPBlit*)nullptr)->SrcY1)},
|
||||
{"MGPBlit.DstX0", offsetof(MGPBlit, DstX0), sizeof(((MGPBlit*)nullptr)->DstX0)},
|
||||
{"MGPBlit.DstY0", offsetof(MGPBlit, DstY0), sizeof(((MGPBlit*)nullptr)->DstY0)},
|
||||
{"MGPBlit.DstX1", offsetof(MGPBlit, DstX1), sizeof(((MGPBlit*)nullptr)->DstX1)},
|
||||
{"MGPBlit.DstY1", offsetof(MGPBlit, DstY1), sizeof(((MGPBlit*)nullptr)->DstY1)},
|
||||
{"MGPBlit.Mask", offsetof(MGPBlit, Mask), sizeof(((MGPBlit*)nullptr)->Mask)},
|
||||
{"MGPBlit.Filter", offsetof(MGPBlit, Filter), sizeof(((MGPBlit*)nullptr)->Filter)},
|
||||
{"MGPClear", 0, sizeof(MGPClear)},
|
||||
{"MGPClear.Fbo", offsetof(MGPClear, Fbo), sizeof(((MGPClear*)nullptr)->Fbo)},
|
||||
{"MGPClear.Kind", offsetof(MGPClear, Kind), sizeof(((MGPClear*)nullptr)->Kind)},
|
||||
{"MGPClear.DrawBufferIndex", offsetof(MGPClear, DrawBufferIndex), sizeof(((MGPClear*)nullptr)->DrawBufferIndex)},
|
||||
{"MGPClear.BufferMask", offsetof(MGPClear, BufferMask), sizeof(((MGPClear*)nullptr)->BufferMask)},
|
||||
{"MGPClear.ValueClass", offsetof(MGPClear, ValueClass), sizeof(((MGPClear*)nullptr)->ValueClass)},
|
||||
{"MGPClear.ColorValue", offsetof(MGPClear, ColorValue), sizeof(((MGPClear*)nullptr)->ColorValue)},
|
||||
{"MGPClear.DepthValue", offsetof(MGPClear, DepthValue), sizeof(((MGPClear*)nullptr)->DepthValue)},
|
||||
{"MGPClear.StencilValue", offsetof(MGPClear, StencilValue), sizeof(((MGPClear*)nullptr)->StencilValue)},
|
||||
{"MGPMipPlan", 0, sizeof(MGPMipPlan)},
|
||||
{"MGPMipPlan.Res", offsetof(MGPMipPlan, Res), sizeof(((MGPMipPlan*)nullptr)->Res)},
|
||||
{"MGPMipPlan.Target", offsetof(MGPMipPlan, Target), sizeof(((MGPMipPlan*)nullptr)->Target)},
|
||||
{"MGPMipPlan.BaseLevel", offsetof(MGPMipPlan, BaseLevel), sizeof(((MGPMipPlan*)nullptr)->BaseLevel)},
|
||||
{"MGPMipPlan.LevelCount", offsetof(MGPMipPlan, LevelCount), sizeof(((MGPMipPlan*)nullptr)->LevelCount)},
|
||||
{"MGPReadbackInfo", 0, sizeof(MGPReadbackInfo)},
|
||||
{"MGPReadbackInfo.Res", offsetof(MGPReadbackInfo, Res), sizeof(((MGPReadbackInfo*)nullptr)->Res)},
|
||||
{"MGPReadbackInfo.Box", offsetof(MGPReadbackInfo, Box), sizeof(((MGPReadbackInfo*)nullptr)->Box)},
|
||||
{"MGPReadbackInfo.Format", offsetof(MGPReadbackInfo, Format), sizeof(((MGPReadbackInfo*)nullptr)->Format)},
|
||||
{"MGPReadbackInfo.Type", offsetof(MGPReadbackInfo, Type), sizeof(((MGPReadbackInfo*)nullptr)->Type)},
|
||||
{"MGPReadbackInfo.Target", offsetof(MGPReadbackInfo, Target), sizeof(((MGPReadbackInfo*)nullptr)->Target)},
|
||||
{"MGPReadbackInfo.Level", offsetof(MGPReadbackInfo, Level), sizeof(((MGPReadbackInfo*)nullptr)->Level)},
|
||||
{"MGPReadbackInfo.DstOffset", offsetof(MGPReadbackInfo, DstOffset), sizeof(((MGPReadbackInfo*)nullptr)->DstOffset)},
|
||||
{"MGPReadbackInfo.DstSize", offsetof(MGPReadbackInfo, DstSize), sizeof(((MGPReadbackInfo*)nullptr)->DstSize)},
|
||||
{"MGPDrawInfo", 0, sizeof(MGPDrawInfo)},
|
||||
{"MGPDrawInfo.Mode", offsetof(MGPDrawInfo, Mode), sizeof(((MGPDrawInfo*)nullptr)->Mode)},
|
||||
{"MGPDrawInfo.IndexSize", offsetof(MGPDrawInfo, IndexSize), sizeof(((MGPDrawInfo*)nullptr)->IndexSize)},
|
||||
{"MGPDrawInfo.Flags", offsetof(MGPDrawInfo, Flags), sizeof(((MGPDrawInfo*)nullptr)->Flags)},
|
||||
{"MGPDrawInfo.InstanceCount", offsetof(MGPDrawInfo, InstanceCount), sizeof(((MGPDrawInfo*)nullptr)->InstanceCount)},
|
||||
{"MGPDrawInfo.StartInstance", offsetof(MGPDrawInfo, StartInstance), sizeof(((MGPDrawInfo*)nullptr)->StartInstance)},
|
||||
{"MGPDrawInfo.RestartIndex", offsetof(MGPDrawInfo, RestartIndex), sizeof(((MGPDrawInfo*)nullptr)->RestartIndex)},
|
||||
{"MGPDrawInfo.DrawIdOffset", offsetof(MGPDrawInfo, DrawIdOffset), sizeof(((MGPDrawInfo*)nullptr)->DrawIdOffset)},
|
||||
{"MGPDrawInfo.IndexResource", offsetof(MGPDrawInfo, IndexResource), sizeof(((MGPDrawInfo*)nullptr)->IndexResource)},
|
||||
{"MGPDrawInfo.MinIndex", offsetof(MGPDrawInfo, MinIndex), sizeof(((MGPDrawInfo*)nullptr)->MinIndex)},
|
||||
{"MGPDrawInfo.MaxIndex", offsetof(MGPDrawInfo, MaxIndex), sizeof(((MGPDrawInfo*)nullptr)->MaxIndex)},
|
||||
{"MGPDrawInfo.XfbCpuCapturedVertices", offsetof(MGPDrawInfo, XfbCpuCapturedVertices), sizeof(((MGPDrawInfo*)nullptr)->XfbCpuCapturedVertices)},
|
||||
{"MGPDrawInfo.NumDraws", offsetof(MGPDrawInfo, NumDraws), sizeof(((MGPDrawInfo*)nullptr)->NumDraws)},
|
||||
{"MGPDrawRange", 0, sizeof(MGPDrawRange)},
|
||||
{"MGPDrawRange.Start", offsetof(MGPDrawRange, Start), sizeof(((MGPDrawRange*)nullptr)->Start)},
|
||||
{"MGPDrawRange.Count", offsetof(MGPDrawRange, Count), sizeof(((MGPDrawRange*)nullptr)->Count)},
|
||||
{"MGPDrawRange.IndexBias", offsetof(MGPDrawRange, IndexBias), sizeof(((MGPDrawRange*)nullptr)->IndexBias)},
|
||||
{"MGPDrawIndirect", 0, sizeof(MGPDrawIndirect)},
|
||||
{"MGPDrawIndirect.Buffer", offsetof(MGPDrawIndirect, Buffer), sizeof(((MGPDrawIndirect*)nullptr)->Buffer)},
|
||||
{"MGPDrawIndirect.ParameterBuffer", offsetof(MGPDrawIndirect, ParameterBuffer), sizeof(((MGPDrawIndirect*)nullptr)->ParameterBuffer)},
|
||||
{"MGPDrawIndirect.Offset", offsetof(MGPDrawIndirect, Offset), sizeof(((MGPDrawIndirect*)nullptr)->Offset)},
|
||||
{"MGPDrawIndirect.ParameterOffset", offsetof(MGPDrawIndirect, ParameterOffset), sizeof(((MGPDrawIndirect*)nullptr)->ParameterOffset)},
|
||||
{"MGPDrawIndirect.Stride", offsetof(MGPDrawIndirect, Stride), sizeof(((MGPDrawIndirect*)nullptr)->Stride)},
|
||||
{"MGPDrawIndirect.DrawCount", offsetof(MGPDrawIndirect, DrawCount), sizeof(((MGPDrawIndirect*)nullptr)->DrawCount)},
|
||||
{"MGPGridInfo", 0, sizeof(MGPGridInfo)},
|
||||
{"MGPGridInfo.GridX", offsetof(MGPGridInfo, GridX), sizeof(((MGPGridInfo*)nullptr)->GridX)},
|
||||
{"MGPGridInfo.GridY", offsetof(MGPGridInfo, GridY), sizeof(((MGPGridInfo*)nullptr)->GridY)},
|
||||
{"MGPGridInfo.GridZ", offsetof(MGPGridInfo, GridZ), sizeof(((MGPGridInfo*)nullptr)->GridZ)},
|
||||
{"MGPGridInfo.BlockX", offsetof(MGPGridInfo, BlockX), sizeof(((MGPGridInfo*)nullptr)->BlockX)},
|
||||
{"MGPGridInfo.BlockY", offsetof(MGPGridInfo, BlockY), sizeof(((MGPGridInfo*)nullptr)->BlockY)},
|
||||
{"MGPGridInfo.BlockZ", offsetof(MGPGridInfo, BlockZ), sizeof(((MGPGridInfo*)nullptr)->BlockZ)},
|
||||
{"MGPGridInfo.IndirectBuffer", offsetof(MGPGridInfo, IndirectBuffer), sizeof(((MGPGridInfo*)nullptr)->IndirectBuffer)},
|
||||
{"MGPGridInfo.IndirectOffset", offsetof(MGPGridInfo, IndirectOffset), sizeof(((MGPGridInfo*)nullptr)->IndirectOffset)},
|
||||
{"MGPGridInfo.IsIndirect", offsetof(MGPGridInfo, IsIndirect), sizeof(((MGPGridInfo*)nullptr)->IsIndirect)},
|
||||
{"MGPMemoryBarrier", 0, sizeof(MGPMemoryBarrier)},
|
||||
{"MGPMemoryBarrier.Bits", offsetof(MGPMemoryBarrier, Bits), sizeof(((MGPMemoryBarrier*)nullptr)->Bits)},
|
||||
{"MGPMemoryBarrier.ByRegion", offsetof(MGPMemoryBarrier, ByRegion), sizeof(((MGPMemoryBarrier*)nullptr)->ByRegion)},
|
||||
{"MGPStreamOutputBegin", 0, sizeof(MGPStreamOutputBegin)},
|
||||
{"MGPStreamOutputBegin.PrimitiveMode", offsetof(MGPStreamOutputBegin, PrimitiveMode), sizeof(((MGPStreamOutputBegin*)nullptr)->PrimitiveMode)},
|
||||
{"MGPStreamOutputBegin.CaptureProgram", offsetof(MGPStreamOutputBegin, CaptureProgram), sizeof(((MGPStreamOutputBegin*)nullptr)->CaptureProgram)},
|
||||
{"MGPStreamOutputBegin.LifetimeId", offsetof(MGPStreamOutputBegin, LifetimeId), sizeof(((MGPStreamOutputBegin*)nullptr)->LifetimeId)},
|
||||
{"MGPStreamOutputBegin.Targets", offsetof(MGPStreamOutputBegin, Targets), sizeof(((MGPStreamOutputBegin*)nullptr)->Targets)},
|
||||
{"MGPXfbAccounting", 0, sizeof(MGPXfbAccounting)},
|
||||
{"MGPXfbAccounting.CapturedVertices", offsetof(MGPXfbAccounting, CapturedVertices), sizeof(((MGPXfbAccounting*)nullptr)->CapturedVertices)},
|
||||
{"MGPXfbAccounting.PrimitivesWritten", offsetof(MGPXfbAccounting, PrimitivesWritten), sizeof(((MGPXfbAccounting*)nullptr)->PrimitivesWritten)},
|
||||
{"MGPXfbAccounting.PrimitiveMode", offsetof(MGPXfbAccounting, PrimitiveMode), sizeof(((MGPXfbAccounting*)nullptr)->PrimitiveMode)},
|
||||
{"MGPStreamOutputControl", 0, sizeof(MGPStreamOutputControl)},
|
||||
{"MGPStreamOutputControl.Reserved", offsetof(MGPStreamOutputControl, Reserved), sizeof(((MGPStreamOutputControl*)nullptr)->Reserved)},
|
||||
{"MGPFlush", 0, sizeof(MGPFlush)},
|
||||
{"MGPFlush.Flags", offsetof(MGPFlush, Flags), sizeof(((MGPFlush*)nullptr)->Flags)},
|
||||
{"MGPPresent", 0, sizeof(MGPPresent)},
|
||||
{"MGPPresent.FrameSerial", offsetof(MGPPresent, FrameSerial), sizeof(((MGPPresent*)nullptr)->FrameSerial)},
|
||||
{"MGPSwapInterval", 0, sizeof(MGPSwapInterval)},
|
||||
{"MGPSwapInterval.Interval", offsetof(MGPSwapInterval, Interval), sizeof(((MGPSwapInterval*)nullptr)->Interval)},
|
||||
{"MGPSurfaceInfo", 0, sizeof(MGPSurfaceInfo)},
|
||||
{"MGPSurfaceInfo.Width", offsetof(MGPSurfaceInfo, Width), sizeof(((MGPSurfaceInfo*)nullptr)->Width)},
|
||||
{"MGPSurfaceInfo.Height", offsetof(MGPSurfaceInfo, Height), sizeof(((MGPSurfaceInfo*)nullptr)->Height)},
|
||||
{"MGPSurfaceInfo.InternalFormat", offsetof(MGPSurfaceInfo, InternalFormat), sizeof(((MGPSurfaceInfo*)nullptr)->InternalFormat)},
|
||||
{"MGPSurfaceInfo.Samples", offsetof(MGPSurfaceInfo, Samples), sizeof(((MGPSurfaceInfo*)nullptr)->Samples)},
|
||||
{"MGPSurfaceInfo.Layers", offsetof(MGPSurfaceInfo, Layers), sizeof(((MGPSurfaceInfo*)nullptr)->Layers)},
|
||||
{"MGPSurfaceInfo.IsDefault", offsetof(MGPSurfaceInfo, IsDefault), sizeof(((MGPSurfaceInfo*)nullptr)->IsDefault)},
|
||||
{"MGPImageBind", 0, sizeof(MGPImageBind)},
|
||||
{"MGPImageBind.Res", offsetof(MGPImageBind, Res), sizeof(((MGPImageBind*)nullptr)->Res)},
|
||||
{"MGPImageBind.Unit", offsetof(MGPImageBind, Unit), sizeof(((MGPImageBind*)nullptr)->Unit)},
|
||||
{"MGPImageBind.GlName", offsetof(MGPImageBind, GlName), sizeof(((MGPImageBind*)nullptr)->GlName)},
|
||||
{"MGPImageBind.Level", offsetof(MGPImageBind, Level), sizeof(((MGPImageBind*)nullptr)->Level)},
|
||||
{"MGPImageBind.Layer", offsetof(MGPImageBind, Layer), sizeof(((MGPImageBind*)nullptr)->Layer)},
|
||||
{"MGPImageBind.Access", offsetof(MGPImageBind, Access), sizeof(((MGPImageBind*)nullptr)->Access)},
|
||||
{"MGPImageBind.Format", offsetof(MGPImageBind, Format), sizeof(((MGPImageBind*)nullptr)->Format)},
|
||||
{"MGPImageBind.Layered", offsetof(MGPImageBind, Layered), sizeof(((MGPImageBind*)nullptr)->Layered)},
|
||||
{"MGPPatchParameter", 0, sizeof(MGPPatchParameter)},
|
||||
{"MGPPatchParameter.Pname", offsetof(MGPPatchParameter, Pname), sizeof(((MGPPatchParameter*)nullptr)->Pname)},
|
||||
{"MGPPatchParameter.Value", offsetof(MGPPatchParameter, Value), sizeof(((MGPPatchParameter*)nullptr)->Value)},
|
||||
{"MGPStreamOutputBind", 0, sizeof(MGPStreamOutputBind)},
|
||||
{"MGPStreamOutputBind.GlName", offsetof(MGPStreamOutputBind, GlName), sizeof(((MGPStreamOutputBind*)nullptr)->GlName)},
|
||||
{"MGPStreamOutputBind.LifetimeId", offsetof(MGPStreamOutputBind, LifetimeId), sizeof(((MGPStreamOutputBind*)nullptr)->LifetimeId)},
|
||||
{"MGPStorageBlockBinding", 0, sizeof(MGPStorageBlockBinding)},
|
||||
{"MGPStorageBlockBinding.ShaderCso", offsetof(MGPStorageBlockBinding, ShaderCso), sizeof(((MGPStorageBlockBinding*)nullptr)->ShaderCso)},
|
||||
{"MGPStorageBlockBinding.GlName", offsetof(MGPStorageBlockBinding, GlName), sizeof(((MGPStorageBlockBinding*)nullptr)->GlName)},
|
||||
{"MGPStorageBlockBinding.Binding", offsetof(MGPStorageBlockBinding, Binding), sizeof(((MGPStorageBlockBinding*)nullptr)->Binding)},
|
||||
{"MGPStorageBlockBinding.Name", offsetof(MGPStorageBlockBinding, Name), sizeof(((MGPStorageBlockBinding*)nullptr)->Name)},
|
||||
{"MGPCopyFromFramebuffer", 0, sizeof(MGPCopyFromFramebuffer)},
|
||||
{"MGPCopyFromFramebuffer.Dst", offsetof(MGPCopyFromFramebuffer, Dst), sizeof(((MGPCopyFromFramebuffer*)nullptr)->Dst)},
|
||||
{"MGPCopyFromFramebuffer.Target", offsetof(MGPCopyFromFramebuffer, Target), sizeof(((MGPCopyFromFramebuffer*)nullptr)->Target)},
|
||||
{"MGPCopyFromFramebuffer.Level", offsetof(MGPCopyFromFramebuffer, Level), sizeof(((MGPCopyFromFramebuffer*)nullptr)->Level)},
|
||||
{"MGPCopyFromFramebuffer.InternalFormat", offsetof(MGPCopyFromFramebuffer, InternalFormat), sizeof(((MGPCopyFromFramebuffer*)nullptr)->InternalFormat)},
|
||||
{"MGPCopyFromFramebuffer.X", offsetof(MGPCopyFromFramebuffer, X), sizeof(((MGPCopyFromFramebuffer*)nullptr)->X)},
|
||||
{"MGPCopyFromFramebuffer.Y", offsetof(MGPCopyFromFramebuffer, Y), sizeof(((MGPCopyFromFramebuffer*)nullptr)->Y)},
|
||||
{"MGPCopyFromFramebuffer.Width", offsetof(MGPCopyFromFramebuffer, Width), sizeof(((MGPCopyFromFramebuffer*)nullptr)->Width)},
|
||||
{"MGPCopyFromFramebuffer.Height", offsetof(MGPCopyFromFramebuffer, Height), sizeof(((MGPCopyFromFramebuffer*)nullptr)->Height)},
|
||||
{"MGPCopyFromFramebuffer.XOffset", offsetof(MGPCopyFromFramebuffer, XOffset), sizeof(((MGPCopyFromFramebuffer*)nullptr)->XOffset)},
|
||||
{"MGPCopyFromFramebuffer.YOffset", offsetof(MGPCopyFromFramebuffer, YOffset), sizeof(((MGPCopyFromFramebuffer*)nullptr)->YOffset)},
|
||||
{"MGPCopyFromFramebuffer.SubImage", offsetof(MGPCopyFromFramebuffer, SubImage), sizeof(((MGPCopyFromFramebuffer*)nullptr)->SubImage)},
|
||||
{"MGPApplierReset", 0, sizeof(MGPApplierReset)},
|
||||
{"MGPApplierReset.ContextSerial", offsetof(MGPApplierReset, ContextSerial), sizeof(((MGPApplierReset*)nullptr)->ContextSerial)},
|
||||
{"MGPContextValues", 0, sizeof(MGPContextValues)},
|
||||
{"MGPContextValues.ActiveTextureUnit", offsetof(MGPContextValues, ActiveTextureUnit), sizeof(((MGPContextValues*)nullptr)->ActiveTextureUnit)},
|
||||
{"MGPContextValues.MaxTouchedTextureUnit", offsetof(MGPContextValues, MaxTouchedTextureUnit), sizeof(((MGPContextValues*)nullptr)->MaxTouchedTextureUnit)},
|
||||
{"MGPContextValues.TouchedBufferBindingPointCount", offsetof(MGPContextValues, TouchedBufferBindingPointCount), sizeof(((MGPContextValues*)nullptr)->TouchedBufferBindingPointCount)},
|
||||
{"MGPContextValues.IsTransformFeedbackActive", offsetof(MGPContextValues, IsTransformFeedbackActive), sizeof(((MGPContextValues*)nullptr)->IsTransformFeedbackActive)},
|
||||
{"MGPContextValues.IsTransformFeedbackPaused", offsetof(MGPContextValues, IsTransformFeedbackPaused), sizeof(((MGPContextValues*)nullptr)->IsTransformFeedbackPaused)},
|
||||
{"MGPContextValues.TransformFeedbackGeneration", offsetof(MGPContextValues, TransformFeedbackGeneration), sizeof(((MGPContextValues*)nullptr)->TransformFeedbackGeneration)},
|
||||
{"MGPContextValues.BoundTransformFeedbackLifetimeId", offsetof(MGPContextValues, BoundTransformFeedbackLifetimeId), sizeof(((MGPContextValues*)nullptr)->BoundTransformFeedbackLifetimeId)},
|
||||
{"MGPContextValues.TransformFeedbackCapturedVertices", offsetof(MGPContextValues, TransformFeedbackCapturedVertices), sizeof(((MGPContextValues*)nullptr)->TransformFeedbackCapturedVertices)},
|
||||
{"MGPProgramBindings", 0, sizeof(MGPProgramBindings)},
|
||||
{"MGPProgramBindings.Cso", offsetof(MGPProgramBindings, Cso), sizeof(((MGPProgramBindings*)nullptr)->Cso)},
|
||||
{"MGPProgramBindings.Signature", offsetof(MGPProgramBindings, Signature), sizeof(((MGPProgramBindings*)nullptr)->Signature)},
|
||||
{"MGPProgramBindings.BlockBindingCount", offsetof(MGPProgramBindings, BlockBindingCount), sizeof(((MGPProgramBindings*)nullptr)->BlockBindingCount)},
|
||||
{"MGPProgramBindings.SamplerUnitCount", offsetof(MGPProgramBindings, SamplerUnitCount), sizeof(((MGPProgramBindings*)nullptr)->SamplerUnitCount)},
|
||||
{"MGPProgramBindings.StorageOverrideCount", offsetof(MGPProgramBindings, StorageOverrideCount), sizeof(((MGPProgramBindings*)nullptr)->StorageOverrideCount)},
|
||||
{"MGPProgramSamplerUnit", 0, sizeof(MGPProgramSamplerUnit)},
|
||||
{"MGPProgramSamplerUnit.Location", offsetof(MGPProgramSamplerUnit, Location), sizeof(((MGPProgramSamplerUnit*)nullptr)->Location)},
|
||||
{"MGPProgramSamplerUnit.Unit", offsetof(MGPProgramSamplerUnit, Unit), sizeof(((MGPProgramSamplerUnit*)nullptr)->Unit)},
|
||||
{"MGPProgramStorageOverride", 0, sizeof(MGPProgramStorageOverride)},
|
||||
{"MGPProgramStorageOverride.Name", offsetof(MGPProgramStorageOverride, Name), sizeof(((MGPProgramStorageOverride*)nullptr)->Name)},
|
||||
{"MGPProgramStorageOverride.Binding", offsetof(MGPProgramStorageOverride, Binding), sizeof(((MGPProgramStorageOverride*)nullptr)->Binding)},
|
||||
{"RenderStateParameters", 0, sizeof(RenderStateParameters)},
|
||||
{"RenderStateParameters.Viewports", offsetof(RenderStateParameters, Viewports), sizeof(((RenderStateParameters*)nullptr)->Viewports)},
|
||||
{"RenderStateParameters.LineWidth", offsetof(RenderStateParameters, LineWidth), sizeof(((RenderStateParameters*)nullptr)->LineWidth)},
|
||||
{"RenderStateParameters.PointSize", offsetof(RenderStateParameters, PointSize), sizeof(((RenderStateParameters*)nullptr)->PointSize)},
|
||||
{"RenderStateParameters.PatchVertices", offsetof(RenderStateParameters, PatchVertices), sizeof(((RenderStateParameters*)nullptr)->PatchVertices)},
|
||||
{"RenderStateParameters.PatchDefaultOuterLevel", offsetof(RenderStateParameters, PatchDefaultOuterLevel), sizeof(((RenderStateParameters*)nullptr)->PatchDefaultOuterLevel)},
|
||||
{"RenderStateParameters.PatchDefaultInnerLevel", offsetof(RenderStateParameters, PatchDefaultInnerLevel), sizeof(((RenderStateParameters*)nullptr)->PatchDefaultInnerLevel)},
|
||||
{"RenderStateParameters.PolygonOffsetFactor", offsetof(RenderStateParameters, PolygonOffsetFactor), sizeof(((RenderStateParameters*)nullptr)->PolygonOffsetFactor)},
|
||||
{"RenderStateParameters.PolygonOffsetUnits", offsetof(RenderStateParameters, PolygonOffsetUnits), sizeof(((RenderStateParameters*)nullptr)->PolygonOffsetUnits)},
|
||||
{"RenderStateParameters.PolygonOffsetClamp", offsetof(RenderStateParameters, PolygonOffsetClamp), sizeof(((RenderStateParameters*)nullptr)->PolygonOffsetClamp)},
|
||||
{"RenderStateParameters.ClipOrigin", offsetof(RenderStateParameters, ClipOrigin), sizeof(((RenderStateParameters*)nullptr)->ClipOrigin)},
|
||||
{"RenderStateParameters.ClipDepthMode", offsetof(RenderStateParameters, ClipDepthMode), sizeof(((RenderStateParameters*)nullptr)->ClipDepthMode)},
|
||||
{"RenderStateParameters.BlendStates", offsetof(RenderStateParameters, BlendStates), sizeof(((RenderStateParameters*)nullptr)->BlendStates)},
|
||||
{"RenderStateParameters.LogicOp", offsetof(RenderStateParameters, LogicOp), sizeof(((RenderStateParameters*)nullptr)->LogicOp)},
|
||||
{"RenderStateParameters.DepthTestEnabled", offsetof(RenderStateParameters, DepthTestEnabled), sizeof(((RenderStateParameters*)nullptr)->DepthTestEnabled)},
|
||||
{"RenderStateParameters.DepthFunc", offsetof(RenderStateParameters, DepthFunc), sizeof(((RenderStateParameters*)nullptr)->DepthFunc)},
|
||||
{"RenderStateParameters.DepthMask", offsetof(RenderStateParameters, DepthMask), sizeof(((RenderStateParameters*)nullptr)->DepthMask)},
|
||||
{"RenderStateParameters.ColorMasks", offsetof(RenderStateParameters, ColorMasks), sizeof(((RenderStateParameters*)nullptr)->ColorMasks)},
|
||||
{"RenderStateParameters.FramebufferSrgbEnabled", offsetof(RenderStateParameters, FramebufferSrgbEnabled), sizeof(((RenderStateParameters*)nullptr)->FramebufferSrgbEnabled)},
|
||||
{"RenderStateParameters.DepthClampEnabled", offsetof(RenderStateParameters, DepthClampEnabled), sizeof(((RenderStateParameters*)nullptr)->DepthClampEnabled)},
|
||||
{"RenderStateParameters.TextureCubeMapSeamlessEnabled", offsetof(RenderStateParameters, TextureCubeMapSeamlessEnabled), sizeof(((RenderStateParameters*)nullptr)->TextureCubeMapSeamlessEnabled)},
|
||||
{"RenderStateParameters.ClearColor", offsetof(RenderStateParameters, ClearColor), sizeof(((RenderStateParameters*)nullptr)->ClearColor)},
|
||||
{"RenderStateParameters.ClearDepth", offsetof(RenderStateParameters, ClearDepth), sizeof(((RenderStateParameters*)nullptr)->ClearDepth)},
|
||||
{"RenderStateParameters.ClearStencil", offsetof(RenderStateParameters, ClearStencil), sizeof(((RenderStateParameters*)nullptr)->ClearStencil)},
|
||||
{"RenderStateParameters.BlendColor", offsetof(RenderStateParameters, BlendColor), sizeof(((RenderStateParameters*)nullptr)->BlendColor)},
|
||||
{"RenderStateParameters.DepthRanges", offsetof(RenderStateParameters, DepthRanges), sizeof(((RenderStateParameters*)nullptr)->DepthRanges)},
|
||||
{"RenderStateParameters.SampleCoverageValue", offsetof(RenderStateParameters, SampleCoverageValue), sizeof(((RenderStateParameters*)nullptr)->SampleCoverageValue)},
|
||||
{"RenderStateParameters.SampleCoverageInvert", offsetof(RenderStateParameters, SampleCoverageInvert), sizeof(((RenderStateParameters*)nullptr)->SampleCoverageInvert)},
|
||||
{"RenderStateParameters.SampleMaskValue", offsetof(RenderStateParameters, SampleMaskValue), sizeof(((RenderStateParameters*)nullptr)->SampleMaskValue)},
|
||||
{"RenderStateParameters.MinSampleShadingValue", offsetof(RenderStateParameters, MinSampleShadingValue), sizeof(((RenderStateParameters*)nullptr)->MinSampleShadingValue)},
|
||||
{"RenderStateParameters.StencilStates", offsetof(RenderStateParameters, StencilStates), sizeof(((RenderStateParameters*)nullptr)->StencilStates)},
|
||||
{"RenderStateParameters.CullFaceEnabled", offsetof(RenderStateParameters, CullFaceEnabled), sizeof(((RenderStateParameters*)nullptr)->CullFaceEnabled)},
|
||||
{"RenderStateParameters.CullFaceModeSetting", offsetof(RenderStateParameters, CullFaceModeSetting), sizeof(((RenderStateParameters*)nullptr)->CullFaceModeSetting)},
|
||||
{"RenderStateParameters.FrontFaceModeSetting", offsetof(RenderStateParameters, FrontFaceModeSetting), sizeof(((RenderStateParameters*)nullptr)->FrontFaceModeSetting)},
|
||||
{"RenderStateParameters.ProvokingVertexModeSetting", offsetof(RenderStateParameters, ProvokingVertexModeSetting), sizeof(((RenderStateParameters*)nullptr)->ProvokingVertexModeSetting)},
|
||||
{"RenderStateParameters.LineSmoothHint", offsetof(RenderStateParameters, LineSmoothHint), sizeof(((RenderStateParameters*)nullptr)->LineSmoothHint)},
|
||||
{"RenderStateParameters.PolygonSmoothHint", offsetof(RenderStateParameters, PolygonSmoothHint), sizeof(((RenderStateParameters*)nullptr)->PolygonSmoothHint)},
|
||||
{"RenderStateParameters.TextureCompressionHint", offsetof(RenderStateParameters, TextureCompressionHint), sizeof(((RenderStateParameters*)nullptr)->TextureCompressionHint)},
|
||||
{"RenderStateParameters.FragmentShaderDerivativeHint", offsetof(RenderStateParameters, FragmentShaderDerivativeHint), sizeof(((RenderStateParameters*)nullptr)->FragmentShaderDerivativeHint)},
|
||||
{"RenderStateParameters.PointFadeThresholdSize", offsetof(RenderStateParameters, PointFadeThresholdSize), sizeof(((RenderStateParameters*)nullptr)->PointFadeThresholdSize)},
|
||||
{"RenderStateParameters.PointSpriteCoordOrigin", offsetof(RenderStateParameters, PointSpriteCoordOrigin), sizeof(((RenderStateParameters*)nullptr)->PointSpriteCoordOrigin)},
|
||||
{"RenderStateParameters.ClampReadColor", offsetof(RenderStateParameters, ClampReadColor), sizeof(((RenderStateParameters*)nullptr)->ClampReadColor)},
|
||||
{"RenderStateParameters.PolygonModeFront", offsetof(RenderStateParameters, PolygonModeFront), sizeof(((RenderStateParameters*)nullptr)->PolygonModeFront)},
|
||||
{"RenderStateParameters.PolygonModeBack", offsetof(RenderStateParameters, PolygonModeBack), sizeof(((RenderStateParameters*)nullptr)->PolygonModeBack)},
|
||||
{"RenderStateParameters.PrimitiveRestartIndex", offsetof(RenderStateParameters, PrimitiveRestartIndex), sizeof(((RenderStateParameters*)nullptr)->PrimitiveRestartIndex)},
|
||||
{"RenderStateParameters.ColorLogicOpEnabled", offsetof(RenderStateParameters, ColorLogicOpEnabled), sizeof(((RenderStateParameters*)nullptr)->ColorLogicOpEnabled)},
|
||||
{"RenderStateParameters.DebugOutputEnabled", offsetof(RenderStateParameters, DebugOutputEnabled), sizeof(((RenderStateParameters*)nullptr)->DebugOutputEnabled)},
|
||||
{"RenderStateParameters.DebugOutputSynchronousEnabled", offsetof(RenderStateParameters, DebugOutputSynchronousEnabled), sizeof(((RenderStateParameters*)nullptr)->DebugOutputSynchronousEnabled)},
|
||||
{"RenderStateParameters.DitherEnabled", offsetof(RenderStateParameters, DitherEnabled), sizeof(((RenderStateParameters*)nullptr)->DitherEnabled)},
|
||||
{"RenderStateParameters.LineSmoothEnabled", offsetof(RenderStateParameters, LineSmoothEnabled), sizeof(((RenderStateParameters*)nullptr)->LineSmoothEnabled)},
|
||||
{"RenderStateParameters.MultisampleEnabled", offsetof(RenderStateParameters, MultisampleEnabled), sizeof(((RenderStateParameters*)nullptr)->MultisampleEnabled)},
|
||||
{"RenderStateParameters.PolygonOffsetFillEnabled", offsetof(RenderStateParameters, PolygonOffsetFillEnabled), sizeof(((RenderStateParameters*)nullptr)->PolygonOffsetFillEnabled)},
|
||||
{"RenderStateParameters.PolygonOffsetLineEnabled", offsetof(RenderStateParameters, PolygonOffsetLineEnabled), sizeof(((RenderStateParameters*)nullptr)->PolygonOffsetLineEnabled)},
|
||||
{"RenderStateParameters.PolygonOffsetPointEnabled", offsetof(RenderStateParameters, PolygonOffsetPointEnabled), sizeof(((RenderStateParameters*)nullptr)->PolygonOffsetPointEnabled)},
|
||||
{"RenderStateParameters.PolygonSmoothEnabled", offsetof(RenderStateParameters, PolygonSmoothEnabled), sizeof(((RenderStateParameters*)nullptr)->PolygonSmoothEnabled)},
|
||||
{"RenderStateParameters.PrimitiveRestartEnabled", offsetof(RenderStateParameters, PrimitiveRestartEnabled), sizeof(((RenderStateParameters*)nullptr)->PrimitiveRestartEnabled)},
|
||||
{"RenderStateParameters.PrimitiveRestartFixedIndexEnabled", offsetof(RenderStateParameters, PrimitiveRestartFixedIndexEnabled), sizeof(((RenderStateParameters*)nullptr)->PrimitiveRestartFixedIndexEnabled)},
|
||||
{"RenderStateParameters.RasterizerDiscardEnabled", offsetof(RenderStateParameters, RasterizerDiscardEnabled), sizeof(((RenderStateParameters*)nullptr)->RasterizerDiscardEnabled)},
|
||||
{"RenderStateParameters.SampleAlphaToCoverageEnabled", offsetof(RenderStateParameters, SampleAlphaToCoverageEnabled), sizeof(((RenderStateParameters*)nullptr)->SampleAlphaToCoverageEnabled)},
|
||||
{"RenderStateParameters.SampleAlphaToOneEnabled", offsetof(RenderStateParameters, SampleAlphaToOneEnabled), sizeof(((RenderStateParameters*)nullptr)->SampleAlphaToOneEnabled)},
|
||||
{"RenderStateParameters.SampleCoverageEnabled", offsetof(RenderStateParameters, SampleCoverageEnabled), sizeof(((RenderStateParameters*)nullptr)->SampleCoverageEnabled)},
|
||||
{"RenderStateParameters.SampleMaskEnabled", offsetof(RenderStateParameters, SampleMaskEnabled), sizeof(((RenderStateParameters*)nullptr)->SampleMaskEnabled)},
|
||||
{"RenderStateParameters.SampleShadingEnabled", offsetof(RenderStateParameters, SampleShadingEnabled), sizeof(((RenderStateParameters*)nullptr)->SampleShadingEnabled)},
|
||||
{"RenderStateParameters.StencilTestEnabled", offsetof(RenderStateParameters, StencilTestEnabled), sizeof(((RenderStateParameters*)nullptr)->StencilTestEnabled)},
|
||||
{"RenderStateParameters.ProgramPointSizeEnabled", offsetof(RenderStateParameters, ProgramPointSizeEnabled), sizeof(((RenderStateParameters*)nullptr)->ProgramPointSizeEnabled)},
|
||||
{"RenderStateParameters.ScissorTestEnabledMask", offsetof(RenderStateParameters, ScissorTestEnabledMask), sizeof(((RenderStateParameters*)nullptr)->ScissorTestEnabledMask)},
|
||||
{"RenderStateParameters.ScissorBoxes", offsetof(RenderStateParameters, ScissorBoxes), sizeof(((RenderStateParameters*)nullptr)->ScissorBoxes)},
|
||||
{"RenderStateParameters.ScissorBoxWrittenMask", offsetof(RenderStateParameters, ScissorBoxWrittenMask), sizeof(((RenderStateParameters*)nullptr)->ScissorBoxWrittenMask)},
|
||||
{"RenderStateParameters.ClipDistanceEnabledMask", offsetof(RenderStateParameters, ClipDistanceEnabledMask), sizeof(((RenderStateParameters*)nullptr)->ClipDistanceEnabledMask)},
|
||||
{"PixelStoreParameters", 0, sizeof(PixelStoreParameters)},
|
||||
{"PixelStoreParameters.SwapBytes", offsetof(PixelStoreParameters, SwapBytes), sizeof(((PixelStoreParameters*)nullptr)->SwapBytes)},
|
||||
{"PixelStoreParameters.LSBFirst", offsetof(PixelStoreParameters, LSBFirst), sizeof(((PixelStoreParameters*)nullptr)->LSBFirst)},
|
||||
{"PixelStoreParameters.RowLength", offsetof(PixelStoreParameters, RowLength), sizeof(((PixelStoreParameters*)nullptr)->RowLength)},
|
||||
{"PixelStoreParameters.ImageHeight", offsetof(PixelStoreParameters, ImageHeight), sizeof(((PixelStoreParameters*)nullptr)->ImageHeight)},
|
||||
{"PixelStoreParameters.SkipPixels", offsetof(PixelStoreParameters, SkipPixels), sizeof(((PixelStoreParameters*)nullptr)->SkipPixels)},
|
||||
{"PixelStoreParameters.SkipRows", offsetof(PixelStoreParameters, SkipRows), sizeof(((PixelStoreParameters*)nullptr)->SkipRows)},
|
||||
{"PixelStoreParameters.SkipImages", offsetof(PixelStoreParameters, SkipImages), sizeof(((PixelStoreParameters*)nullptr)->SkipImages)},
|
||||
{"PixelStoreParameters.Alignment", offsetof(PixelStoreParameters, Alignment), sizeof(((PixelStoreParameters*)nullptr)->Alignment)},
|
||||
{"SamplerParameters", 0, sizeof(SamplerParameters)},
|
||||
{"SamplerParameters.wrapS", offsetof(SamplerParameters, wrapS), sizeof(((SamplerParameters*)nullptr)->wrapS)},
|
||||
{"SamplerParameters.wrapT", offsetof(SamplerParameters, wrapT), sizeof(((SamplerParameters*)nullptr)->wrapT)},
|
||||
{"SamplerParameters.wrapR", offsetof(SamplerParameters, wrapR), sizeof(((SamplerParameters*)nullptr)->wrapR)},
|
||||
{"SamplerParameters.minFilter", offsetof(SamplerParameters, minFilter), sizeof(((SamplerParameters*)nullptr)->minFilter)},
|
||||
{"SamplerParameters.magFilter", offsetof(SamplerParameters, magFilter), sizeof(((SamplerParameters*)nullptr)->magFilter)},
|
||||
{"SamplerParameters.mipmapMode", offsetof(SamplerParameters, mipmapMode), sizeof(((SamplerParameters*)nullptr)->mipmapMode)},
|
||||
{"SamplerParameters.minLod", offsetof(SamplerParameters, minLod), sizeof(((SamplerParameters*)nullptr)->minLod)},
|
||||
{"SamplerParameters.maxLod", offsetof(SamplerParameters, maxLod), sizeof(((SamplerParameters*)nullptr)->maxLod)},
|
||||
{"SamplerParameters.lodBias", offsetof(SamplerParameters, lodBias), sizeof(((SamplerParameters*)nullptr)->lodBias)},
|
||||
{"SamplerParameters.maxAnisotropy", offsetof(SamplerParameters, maxAnisotropy), sizeof(((SamplerParameters*)nullptr)->maxAnisotropy)},
|
||||
{"SamplerParameters.compareFunc", offsetof(SamplerParameters, compareFunc), sizeof(((SamplerParameters*)nullptr)->compareFunc)},
|
||||
{"SamplerParameters.compareMode", offsetof(SamplerParameters, compareMode), sizeof(((SamplerParameters*)nullptr)->compareMode)},
|
||||
{"SamplerParameters.borderColor", offsetof(SamplerParameters, borderColor), sizeof(((SamplerParameters*)nullptr)->borderColor)},
|
||||
{"SamplerParameters.borderColorI", offsetof(SamplerParameters, borderColorI), sizeof(((SamplerParameters*)nullptr)->borderColorI)},
|
||||
{"SamplerParameters.borderColorUI", offsetof(SamplerParameters, borderColorUI), sizeof(((SamplerParameters*)nullptr)->borderColorUI)},
|
||||
{"SamplerParameters.borderColorForm", offsetof(SamplerParameters, borderColorForm), sizeof(((SamplerParameters*)nullptr)->borderColorForm)},
|
||||
{"PerBufferBlendState", 0, sizeof(PerBufferBlendState)},
|
||||
{"PerBufferBlendState.Enabled", offsetof(PerBufferBlendState, Enabled), sizeof(((PerBufferBlendState*)nullptr)->Enabled)},
|
||||
{"PerBufferBlendState.SrcFactorRGB", offsetof(PerBufferBlendState, SrcFactorRGB), sizeof(((PerBufferBlendState*)nullptr)->SrcFactorRGB)},
|
||||
{"PerBufferBlendState.DstFactorRGB", offsetof(PerBufferBlendState, DstFactorRGB), sizeof(((PerBufferBlendState*)nullptr)->DstFactorRGB)},
|
||||
{"PerBufferBlendState.SrcFactorAlpha", offsetof(PerBufferBlendState, SrcFactorAlpha), sizeof(((PerBufferBlendState*)nullptr)->SrcFactorAlpha)},
|
||||
{"PerBufferBlendState.DstFactorAlpha", offsetof(PerBufferBlendState, DstFactorAlpha), sizeof(((PerBufferBlendState*)nullptr)->DstFactorAlpha)},
|
||||
{"PerBufferBlendState.ColorEquation", offsetof(PerBufferBlendState, ColorEquation), sizeof(((PerBufferBlendState*)nullptr)->ColorEquation)},
|
||||
{"PerBufferBlendState.AlphaEquation", offsetof(PerBufferBlendState, AlphaEquation), sizeof(((PerBufferBlendState*)nullptr)->AlphaEquation)},
|
||||
{"StencilFaceState", 0, sizeof(StencilFaceState)},
|
||||
{"StencilFaceState.Func", offsetof(StencilFaceState, Func), sizeof(((StencilFaceState*)nullptr)->Func)},
|
||||
{"StencilFaceState.Ref", offsetof(StencilFaceState, Ref), sizeof(((StencilFaceState*)nullptr)->Ref)},
|
||||
{"StencilFaceState.ValueMask", offsetof(StencilFaceState, ValueMask), sizeof(((StencilFaceState*)nullptr)->ValueMask)},
|
||||
{"StencilFaceState.WriteMask", offsetof(StencilFaceState, WriteMask), sizeof(((StencilFaceState*)nullptr)->WriteMask)},
|
||||
{"StencilFaceState.FailOp", offsetof(StencilFaceState, FailOp), sizeof(((StencilFaceState*)nullptr)->FailOp)},
|
||||
{"StencilFaceState.PassDepthFailOp", offsetof(StencilFaceState, PassDepthFailOp), sizeof(((StencilFaceState*)nullptr)->PassDepthFailOp)},
|
||||
{"StencilFaceState.PassDepthPassOp", offsetof(StencilFaceState, PassDepthPassOp), sizeof(((StencilFaceState*)nullptr)->PassDepthPassOp)},
|
||||
{"DynamicBackendParameters", 0, sizeof(DynamicBackendParameters)},
|
||||
{"DynamicBackendParameters.UniformBufferOffsetAlignment", offsetof(DynamicBackendParameters, UniformBufferOffsetAlignment), sizeof(((DynamicBackendParameters*)nullptr)->UniformBufferOffsetAlignment)},
|
||||
{"DynamicBackendParameters.ShaderStorageBufferOffsetAlignment", offsetof(DynamicBackendParameters, ShaderStorageBufferOffsetAlignment), sizeof(((DynamicBackendParameters*)nullptr)->ShaderStorageBufferOffsetAlignment)},
|
||||
{"DynamicBackendParameters.MaxTextureMaxAnisotropy", offsetof(DynamicBackendParameters, MaxTextureMaxAnisotropy), sizeof(((DynamicBackendParameters*)nullptr)->MaxTextureMaxAnisotropy)},
|
||||
{"DynamicBackendParameters.AliasedLineWidthRangeMin", offsetof(DynamicBackendParameters, AliasedLineWidthRangeMin), sizeof(((DynamicBackendParameters*)nullptr)->AliasedLineWidthRangeMin)},
|
||||
{"DynamicBackendParameters.AliasedLineWidthRangeMax", offsetof(DynamicBackendParameters, AliasedLineWidthRangeMax), sizeof(((DynamicBackendParameters*)nullptr)->AliasedLineWidthRangeMax)},
|
||||
{"DynamicBackendParameters.SmoothLineWidthRangeMin", offsetof(DynamicBackendParameters, SmoothLineWidthRangeMin), sizeof(((DynamicBackendParameters*)nullptr)->SmoothLineWidthRangeMin)},
|
||||
{"DynamicBackendParameters.SmoothLineWidthRangeMax", offsetof(DynamicBackendParameters, SmoothLineWidthRangeMax), sizeof(((DynamicBackendParameters*)nullptr)->SmoothLineWidthRangeMax)},
|
||||
{"DynamicBackendParameters.SmoothLineWidthGranularity", offsetof(DynamicBackendParameters, SmoothLineWidthGranularity), sizeof(((DynamicBackendParameters*)nullptr)->SmoothLineWidthGranularity)},
|
||||
{"DynamicBackendParameters.PointSizeRangeMin", offsetof(DynamicBackendParameters, PointSizeRangeMin), sizeof(((DynamicBackendParameters*)nullptr)->PointSizeRangeMin)},
|
||||
{"DynamicBackendParameters.PointSizeRangeMax", offsetof(DynamicBackendParameters, PointSizeRangeMax), sizeof(((DynamicBackendParameters*)nullptr)->PointSizeRangeMax)},
|
||||
{"DynamicBackendParameters.PointSizeGranularity", offsetof(DynamicBackendParameters, PointSizeGranularity), sizeof(((DynamicBackendParameters*)nullptr)->PointSizeGranularity)},
|
||||
{"DynamicBackendParameters.Max3DTextureSize", offsetof(DynamicBackendParameters, Max3DTextureSize), sizeof(((DynamicBackendParameters*)nullptr)->Max3DTextureSize)},
|
||||
{"DynamicBackendParameters.MaxArrayTextureLayers", offsetof(DynamicBackendParameters, MaxArrayTextureLayers), sizeof(((DynamicBackendParameters*)nullptr)->MaxArrayTextureLayers)},
|
||||
{"DynamicBackendParameters.MaxCubeMapTextureSize", offsetof(DynamicBackendParameters, MaxCubeMapTextureSize), sizeof(((DynamicBackendParameters*)nullptr)->MaxCubeMapTextureSize)},
|
||||
{"DynamicBackendParameters.MaxFramebufferWidth", offsetof(DynamicBackendParameters, MaxFramebufferWidth), sizeof(((DynamicBackendParameters*)nullptr)->MaxFramebufferWidth)},
|
||||
{"DynamicBackendParameters.MaxFramebufferHeight", offsetof(DynamicBackendParameters, MaxFramebufferHeight), sizeof(((DynamicBackendParameters*)nullptr)->MaxFramebufferHeight)},
|
||||
{"DynamicBackendParameters.MaxFramebufferLayers", offsetof(DynamicBackendParameters, MaxFramebufferLayers), sizeof(((DynamicBackendParameters*)nullptr)->MaxFramebufferLayers)},
|
||||
{"DynamicBackendParameters.MaxRenderbufferSize", offsetof(DynamicBackendParameters, MaxRenderbufferSize), sizeof(((DynamicBackendParameters*)nullptr)->MaxRenderbufferSize)},
|
||||
{"DynamicBackendParameters.MaxTextureSize", offsetof(DynamicBackendParameters, MaxTextureSize), sizeof(((DynamicBackendParameters*)nullptr)->MaxTextureSize)},
|
||||
{"DynamicBackendParameters.MaxColorTextureSamples", offsetof(DynamicBackendParameters, MaxColorTextureSamples), sizeof(((DynamicBackendParameters*)nullptr)->MaxColorTextureSamples)},
|
||||
{"DynamicBackendParameters.MaxDepthTextureSamples", offsetof(DynamicBackendParameters, MaxDepthTextureSamples), sizeof(((DynamicBackendParameters*)nullptr)->MaxDepthTextureSamples)},
|
||||
{"DynamicBackendParameters.MaxFramebufferSamples", offsetof(DynamicBackendParameters, MaxFramebufferSamples), sizeof(((DynamicBackendParameters*)nullptr)->MaxFramebufferSamples)},
|
||||
{"DynamicBackendParameters.MaxIntegerSamples", offsetof(DynamicBackendParameters, MaxIntegerSamples), sizeof(((DynamicBackendParameters*)nullptr)->MaxIntegerSamples)},
|
||||
{"DynamicBackendParameters.MaxSamples", offsetof(DynamicBackendParameters, MaxSamples), sizeof(((DynamicBackendParameters*)nullptr)->MaxSamples)},
|
||||
{"DynamicBackendParameters.MaxSampleMaskWords", offsetof(DynamicBackendParameters, MaxSampleMaskWords), sizeof(((DynamicBackendParameters*)nullptr)->MaxSampleMaskWords)},
|
||||
{"DynamicBackendParameters.MaxPatchVertices", offsetof(DynamicBackendParameters, MaxPatchVertices), sizeof(((DynamicBackendParameters*)nullptr)->MaxPatchVertices)},
|
||||
{"DynamicBackendParameters.MaxTessGenLevel", offsetof(DynamicBackendParameters, MaxTessGenLevel), sizeof(((DynamicBackendParameters*)nullptr)->MaxTessGenLevel)},
|
||||
{"DynamicBackendParameters.MinProgramTextureGatherOffset", offsetof(DynamicBackendParameters, MinProgramTextureGatherOffset), sizeof(((DynamicBackendParameters*)nullptr)->MinProgramTextureGatherOffset)},
|
||||
{"DynamicBackendParameters.MaxProgramTextureGatherOffset", offsetof(DynamicBackendParameters, MaxProgramTextureGatherOffset), sizeof(((DynamicBackendParameters*)nullptr)->MaxProgramTextureGatherOffset)},
|
||||
{"DynamicBackendParameters.MaxTextureImageUnits", offsetof(DynamicBackendParameters, MaxTextureImageUnits), sizeof(((DynamicBackendParameters*)nullptr)->MaxTextureImageUnits)},
|
||||
{"DynamicBackendParameters.MaxVertexTextureImageUnits", offsetof(DynamicBackendParameters, MaxVertexTextureImageUnits), sizeof(((DynamicBackendParameters*)nullptr)->MaxVertexTextureImageUnits)},
|
||||
{"DynamicBackendParameters.MaxComputeTextureImageUnits", offsetof(DynamicBackendParameters, MaxComputeTextureImageUnits), sizeof(((DynamicBackendParameters*)nullptr)->MaxComputeTextureImageUnits)},
|
||||
{"DynamicBackendParameters.MaxCombinedTextureImageUnits", offsetof(DynamicBackendParameters, MaxCombinedTextureImageUnits), sizeof(((DynamicBackendParameters*)nullptr)->MaxCombinedTextureImageUnits)},
|
||||
{"DynamicBackendParameters.MaxVertexAttribs", offsetof(DynamicBackendParameters, MaxVertexAttribs), sizeof(((DynamicBackendParameters*)nullptr)->MaxVertexAttribs)},
|
||||
{"DynamicBackendParameters.MaxComputeShaderStorageBlocks", offsetof(DynamicBackendParameters, MaxComputeShaderStorageBlocks), sizeof(((DynamicBackendParameters*)nullptr)->MaxComputeShaderStorageBlocks)},
|
||||
{"DynamicBackendParameters.MaxCombinedShaderStorageBlocks", offsetof(DynamicBackendParameters, MaxCombinedShaderStorageBlocks), sizeof(((DynamicBackendParameters*)nullptr)->MaxCombinedShaderStorageBlocks)},
|
||||
{"DynamicBackendParameters.MaxVertexShaderStorageBlocks", offsetof(DynamicBackendParameters, MaxVertexShaderStorageBlocks), sizeof(((DynamicBackendParameters*)nullptr)->MaxVertexShaderStorageBlocks)},
|
||||
{"DynamicBackendParameters.MaxTessControlShaderStorageBlocks", offsetof(DynamicBackendParameters, MaxTessControlShaderStorageBlocks), sizeof(((DynamicBackendParameters*)nullptr)->MaxTessControlShaderStorageBlocks)},
|
||||
{"DynamicBackendParameters.MaxTessEvaluationShaderStorageBlocks", offsetof(DynamicBackendParameters, MaxTessEvaluationShaderStorageBlocks), sizeof(((DynamicBackendParameters*)nullptr)->MaxTessEvaluationShaderStorageBlocks)},
|
||||
{"DynamicBackendParameters.MaxGeometryShaderStorageBlocks", offsetof(DynamicBackendParameters, MaxGeometryShaderStorageBlocks), sizeof(((DynamicBackendParameters*)nullptr)->MaxGeometryShaderStorageBlocks)},
|
||||
{"DynamicBackendParameters.MaxFragmentShaderStorageBlocks", offsetof(DynamicBackendParameters, MaxFragmentShaderStorageBlocks), sizeof(((DynamicBackendParameters*)nullptr)->MaxFragmentShaderStorageBlocks)},
|
||||
{"DynamicBackendParameters.MaxComputeUniformBlocks", offsetof(DynamicBackendParameters, MaxComputeUniformBlocks), sizeof(((DynamicBackendParameters*)nullptr)->MaxComputeUniformBlocks)},
|
||||
{"DynamicBackendParameters.MaxComputeWorkGroupInvocations", offsetof(DynamicBackendParameters, MaxComputeWorkGroupInvocations), sizeof(((DynamicBackendParameters*)nullptr)->MaxComputeWorkGroupInvocations)},
|
||||
{"DynamicBackendParameters.MaxComputeWorkGroupCount", offsetof(DynamicBackendParameters, MaxComputeWorkGroupCount), sizeof(((DynamicBackendParameters*)nullptr)->MaxComputeWorkGroupCount)},
|
||||
{"DynamicBackendParameters.MaxComputeWorkGroupSize", offsetof(DynamicBackendParameters, MaxComputeWorkGroupSize), sizeof(((DynamicBackendParameters*)nullptr)->MaxComputeWorkGroupSize)},
|
||||
{"DynamicBackendParameters.MaxShaderStorageBufferBindings", offsetof(DynamicBackendParameters, MaxShaderStorageBufferBindings), sizeof(((DynamicBackendParameters*)nullptr)->MaxShaderStorageBufferBindings)},
|
||||
{"DynamicBackendParameters.MaxTextureBufferSize", offsetof(DynamicBackendParameters, MaxTextureBufferSize), sizeof(((DynamicBackendParameters*)nullptr)->MaxTextureBufferSize)},
|
||||
{"DynamicBackendParameters.TextureBufferOffsetAlignment", offsetof(DynamicBackendParameters, TextureBufferOffsetAlignment), sizeof(((DynamicBackendParameters*)nullptr)->TextureBufferOffsetAlignment)},
|
||||
{"DynamicBackendParameters.MaxUniformBufferBindings", offsetof(DynamicBackendParameters, MaxUniformBufferBindings), sizeof(((DynamicBackendParameters*)nullptr)->MaxUniformBufferBindings)},
|
||||
{"DynamicBackendParameters.MaxUniformBlockSize", offsetof(DynamicBackendParameters, MaxUniformBlockSize), sizeof(((DynamicBackendParameters*)nullptr)->MaxUniformBlockSize)},
|
||||
{"DynamicBackendParameters.MaxImageUnits", offsetof(DynamicBackendParameters, MaxImageUnits), sizeof(((DynamicBackendParameters*)nullptr)->MaxImageUnits)},
|
||||
{"DynamicBackendParameters.MaxCombinedImageUniforms", offsetof(DynamicBackendParameters, MaxCombinedImageUniforms), sizeof(((DynamicBackendParameters*)nullptr)->MaxCombinedImageUniforms)},
|
||||
{"DynamicBackendParameters.MaxVertexImageUniforms", offsetof(DynamicBackendParameters, MaxVertexImageUniforms), sizeof(((DynamicBackendParameters*)nullptr)->MaxVertexImageUniforms)},
|
||||
{"DynamicBackendParameters.MaxGeometryImageUniforms", offsetof(DynamicBackendParameters, MaxGeometryImageUniforms), sizeof(((DynamicBackendParameters*)nullptr)->MaxGeometryImageUniforms)},
|
||||
{"DynamicBackendParameters.MaxFragmentImageUniforms", offsetof(DynamicBackendParameters, MaxFragmentImageUniforms), sizeof(((DynamicBackendParameters*)nullptr)->MaxFragmentImageUniforms)},
|
||||
{"DynamicBackendParameters.MaxComputeImageUniforms", offsetof(DynamicBackendParameters, MaxComputeImageUniforms), sizeof(((DynamicBackendParameters*)nullptr)->MaxComputeImageUniforms)},
|
||||
{"DynamicBackendParameters.MaxDrawBuffers", offsetof(DynamicBackendParameters, MaxDrawBuffers), sizeof(((DynamicBackendParameters*)nullptr)->MaxDrawBuffers)},
|
||||
{"DynamicBackendParameters.MaxColorAttachments", offsetof(DynamicBackendParameters, MaxColorAttachments), sizeof(((DynamicBackendParameters*)nullptr)->MaxColorAttachments)},
|
||||
{"DynamicBackendParameters.MaxClipDistances", offsetof(DynamicBackendParameters, MaxClipDistances), sizeof(((DynamicBackendParameters*)nullptr)->MaxClipDistances)},
|
||||
{"DynamicBackendParameters.MaxCullDistances", offsetof(DynamicBackendParameters, MaxCullDistances), sizeof(((DynamicBackendParameters*)nullptr)->MaxCullDistances)},
|
||||
{"DynamicBackendParameters.MaxCombinedClipAndCullDistances", offsetof(DynamicBackendParameters, MaxCombinedClipAndCullDistances), sizeof(((DynamicBackendParameters*)nullptr)->MaxCombinedClipAndCullDistances)},
|
||||
{"DynamicBackendParameters.MaxViewports", offsetof(DynamicBackendParameters, MaxViewports), sizeof(((DynamicBackendParameters*)nullptr)->MaxViewports)},
|
||||
{"DynamicBackendParameters.LayerProvokingVertex", offsetof(DynamicBackendParameters, LayerProvokingVertex), sizeof(((DynamicBackendParameters*)nullptr)->LayerProvokingVertex)},
|
||||
{"DynamicBackendParameters.ViewportIndexProvokingVertex", offsetof(DynamicBackendParameters, ViewportIndexProvokingVertex), sizeof(((DynamicBackendParameters*)nullptr)->ViewportIndexProvokingVertex)},
|
||||
{"DynamicBackendParameters.MaxViewportWidth", offsetof(DynamicBackendParameters, MaxViewportWidth), sizeof(((DynamicBackendParameters*)nullptr)->MaxViewportWidth)},
|
||||
{"DynamicBackendParameters.MaxViewportHeight", offsetof(DynamicBackendParameters, MaxViewportHeight), sizeof(((DynamicBackendParameters*)nullptr)->MaxViewportHeight)},
|
||||
{"DynamicBackendParameters.ViewportBoundsRangeMin", offsetof(DynamicBackendParameters, ViewportBoundsRangeMin), sizeof(((DynamicBackendParameters*)nullptr)->ViewportBoundsRangeMin)},
|
||||
{"DynamicBackendParameters.ViewportBoundsRangeMax", offsetof(DynamicBackendParameters, ViewportBoundsRangeMax), sizeof(((DynamicBackendParameters*)nullptr)->ViewportBoundsRangeMax)},
|
||||
{"DynamicBackendParameters.ViewportSubpixelBits", offsetof(DynamicBackendParameters, ViewportSubpixelBits), sizeof(((DynamicBackendParameters*)nullptr)->ViewportSubpixelBits)},
|
||||
{"DynamicBackendParameters.MinFragmentInterpolationOffset", offsetof(DynamicBackendParameters, MinFragmentInterpolationOffset), sizeof(((DynamicBackendParameters*)nullptr)->MinFragmentInterpolationOffset)},
|
||||
{"DynamicBackendParameters.MaxFragmentInterpolationOffset", offsetof(DynamicBackendParameters, MaxFragmentInterpolationOffset), sizeof(((DynamicBackendParameters*)nullptr)->MaxFragmentInterpolationOffset)},
|
||||
{"DynamicBackendParameters.FragmentInterpolationOffsetBits", offsetof(DynamicBackendParameters, FragmentInterpolationOffsetBits), sizeof(((DynamicBackendParameters*)nullptr)->FragmentInterpolationOffsetBits)},
|
||||
{"DynamicBackendParameters.SupportsWideLines", offsetof(DynamicBackendParameters, SupportsWideLines), sizeof(((DynamicBackendParameters*)nullptr)->SupportsWideLines)},
|
||||
{"DynamicBackendParameters.SupportsDistinctDepthStencilAttachments", offsetof(DynamicBackendParameters, SupportsDistinctDepthStencilAttachments), sizeof(((DynamicBackendParameters*)nullptr)->SupportsDistinctDepthStencilAttachments)},
|
||||
{"DynamicBackendParameters.PerLayerFramebufferAttachmentTargets", offsetof(DynamicBackendParameters, PerLayerFramebufferAttachmentTargets), sizeof(((DynamicBackendParameters*)nullptr)->PerLayerFramebufferAttachmentTargets)},
|
||||
{"DynamicBackendParameters.SupportsShaderFloat64", offsetof(DynamicBackendParameters, SupportsShaderFloat64), sizeof(((DynamicBackendParameters*)nullptr)->SupportsShaderFloat64)},
|
||||
{"DynamicBackendParameters.SupportsFloat64VertexAttributes", offsetof(DynamicBackendParameters, SupportsFloat64VertexAttributes), sizeof(((DynamicBackendParameters*)nullptr)->SupportsFloat64VertexAttributes)},
|
||||
{"DynamicBackendParameters.SupportsTessellationPointSize", offsetof(DynamicBackendParameters, SupportsTessellationPointSize), sizeof(((DynamicBackendParameters*)nullptr)->SupportsTessellationPointSize)},
|
||||
{"DynamicBackendParameters.SupportsGeometryPointSize", offsetof(DynamicBackendParameters, SupportsGeometryPointSize), sizeof(((DynamicBackendParameters*)nullptr)->SupportsGeometryPointSize)},
|
||||
{"DynamicBackendParameters.MaxShaderStorageBlockSize", offsetof(DynamicBackendParameters, MaxShaderStorageBlockSize), sizeof(((DynamicBackendParameters*)nullptr)->MaxShaderStorageBlockSize)},
|
||||
{"DynamicBackendParameters.SubgroupSize", offsetof(DynamicBackendParameters, SubgroupSize), sizeof(((DynamicBackendParameters*)nullptr)->SubgroupSize)},
|
||||
{"DynamicBackendParameters.SubgroupSupportedStages", offsetof(DynamicBackendParameters, SubgroupSupportedStages), sizeof(((DynamicBackendParameters*)nullptr)->SubgroupSupportedStages)},
|
||||
{"DynamicBackendParameters.SubgroupSupportedFeatures", offsetof(DynamicBackendParameters, SubgroupSupportedFeatures), sizeof(((DynamicBackendParameters*)nullptr)->SubgroupSupportedFeatures)},
|
||||
{"DynamicBackendParameters.SubgroupQuadOperationsInAllStages", offsetof(DynamicBackendParameters, SubgroupQuadOperationsInAllStages), sizeof(((DynamicBackendParameters*)nullptr)->SubgroupQuadOperationsInAllStages)},
|
||||
{"DynamicBackendParameters.GpuVendor", offsetof(DynamicBackendParameters, GpuVendor), sizeof(((DynamicBackendParameters*)nullptr)->GpuVendor)},
|
||||
{"MGHostSpan", 0, sizeof(MGHostSpan)},
|
||||
{"MGHostSpan.Ptr", offsetof(MGHostSpan, Ptr), sizeof(((MGHostSpan*)nullptr)->Ptr)},
|
||||
{"MGHostSpan.Seg", offsetof(MGHostSpan, Seg), sizeof(((MGHostSpan*)nullptr)->Seg)},
|
||||
{"MGHostSpan.Size", offsetof(MGHostSpan, Size), sizeof(((MGHostSpan*)nullptr)->Size)},
|
||||
{"MGHostSpan.Offset", offsetof(MGHostSpan, Offset), sizeof(((MGHostSpan*)nullptr)->Offset)},
|
||||
{"MGPVertexAttribWire", 0, sizeof(MGPVertexAttribWire)},
|
||||
{"MGPVertexAttribWire.Offset", offsetof(MGPVertexAttribWire, Offset), sizeof(((MGPVertexAttribWire*)nullptr)->Offset)},
|
||||
{"MGPVertexAttribWire.Stride", offsetof(MGPVertexAttribWire, Stride), sizeof(((MGPVertexAttribWire*)nullptr)->Stride)},
|
||||
{"MGPVertexAttribWire.Type", offsetof(MGPVertexAttribWire, Type), sizeof(((MGPVertexAttribWire*)nullptr)->Type)},
|
||||
{"MGPVertexAttribWire.Size", offsetof(MGPVertexAttribWire, Size), sizeof(((MGPVertexAttribWire*)nullptr)->Size)},
|
||||
{"MGPVertexAttribWire.Enabled", offsetof(MGPVertexAttribWire, Enabled), sizeof(((MGPVertexAttribWire*)nullptr)->Enabled)},
|
||||
{"MGPVertexAttribWire.Normalized", offsetof(MGPVertexAttribWire, Normalized), sizeof(((MGPVertexAttribWire*)nullptr)->Normalized)},
|
||||
{"MGPVertexAttribWire.IsInteger", offsetof(MGPVertexAttribWire, IsInteger), sizeof(((MGPVertexAttribWire*)nullptr)->IsInteger)},
|
||||
{"MGPVertexAttribWire.IsLong", offsetof(MGPVertexAttribWire, IsLong), sizeof(((MGPVertexAttribWire*)nullptr)->IsLong)},
|
||||
{"MGPVertexAttribWire.IsBgra", offsetof(MGPVertexAttribWire, IsBgra), sizeof(((MGPVertexAttribWire*)nullptr)->IsBgra)},
|
||||
{"MGPVertexAttribWire.BindingIndex", offsetof(MGPVertexAttribWire, BindingIndex), sizeof(((MGPVertexAttribWire*)nullptr)->BindingIndex)},
|
||||
{"MGPVertexBindingPointWire", 0, sizeof(MGPVertexBindingPointWire)},
|
||||
{"MGPVertexBindingPointWire.Offset", offsetof(MGPVertexBindingPointWire, Offset), sizeof(((MGPVertexBindingPointWire*)nullptr)->Offset)},
|
||||
{"MGPVertexBindingPointWire.Stride", offsetof(MGPVertexBindingPointWire, Stride), sizeof(((MGPVertexBindingPointWire*)nullptr)->Stride)},
|
||||
{"MGPVertexBindingPointWire.Divisor", offsetof(MGPVertexBindingPointWire, Divisor), sizeof(((MGPVertexBindingPointWire*)nullptr)->Divisor)},
|
||||
{"MGPipeHandle", 0, sizeof(MGPipeHandle)},
|
||||
{"MGPipeHandle.Slot", offsetof(MGPipeHandle, Slot), sizeof(((MGPipeHandle*)nullptr)->Slot)},
|
||||
{"MGPipeHandle.Gen", offsetof(MGPipeHandle, Gen), sizeof(((MGPipeHandle*)nullptr)->Gen)},
|
||||
{"MGPWireRecHeader", 0, sizeof(MGPWireRecHeader)},
|
||||
{"MGPWireRecHeader.Op", offsetof(MGPWireRecHeader, Op), sizeof(((MGPWireRecHeader*)nullptr)->Op)},
|
||||
{"MGPWireRecHeader.Flags", offsetof(MGPWireRecHeader, Flags), sizeof(((MGPWireRecHeader*)nullptr)->Flags)},
|
||||
{"MGPWireRecHeader.Size", offsetof(MGPWireRecHeader, Size), sizeof(((MGPWireRecHeader*)nullptr)->Size)},
|
||||
};
|
||||
struct WireCallLayout { const char* Name; const char* Payload; Uint64 Op; Uint64 Flags; Uint64 Wait; };
|
||||
inline constexpr WireCallLayout kMGPipeWireCallLayouts[] = {
|
||||
{"GetCaps", "MGPCaps", static_cast<Uint64>(MGPWireOp::GetCaps), static_cast<Uint64>(kReplySlot | kHasBlob), static_cast<Uint64>(kWaitReply)},
|
||||
{"ResourceCreate", "MGPResourceDesc", static_cast<Uint64>(MGPWireOp::ResourceCreate), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"ResourceRespecify", "MGPResourceDesc", static_cast<Uint64>(MGPWireOp::ResourceRespecify), static_cast<Uint64>(kNeedsAck | kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"ResourceDestroy", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::ResourceDestroy), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"MapPersistent", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::MapPersistent), static_cast<Uint64>(kReplySlot | kOptional), static_cast<Uint64>(kWaitReply)},
|
||||
{"UnmapPersistent", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::UnmapPersistent), static_cast<Uint64>(kOptional), static_cast<Uint64>(kWaitNone)},
|
||||
{"FenceCreate", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::FenceCreate), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"FenceStatus", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::FenceStatus), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"FenceWait", "MGPFenceWait", static_cast<Uint64>(MGPWireOp::FenceWait), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"FenceDestroy", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::FenceDestroy), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"QueryCreate", "MGPQueryDesc", static_cast<Uint64>(MGPWireOp::QueryCreate), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"QueryBegin", "MGPQueryDesc", static_cast<Uint64>(MGPWireOp::QueryBegin), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"QueryEnd", "MGPQueryDesc", static_cast<Uint64>(MGPWireOp::QueryEnd), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"QueryAvailable", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::QueryAvailable), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"QueryResult", "MGPQueryResultRequest", static_cast<Uint64>(MGPWireOp::QueryResult), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"QueryDestroy", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::QueryDestroy), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"CreateRenderState", "MGPRenderStateDesc", static_cast<Uint64>(MGPWireOp::CreateRenderState), static_cast<Uint64>(kHasBlob), static_cast<Uint64>(kWaitNone)},
|
||||
{"BindRenderState", "MGPBindRenderState", static_cast<Uint64>(MGPWireOp::BindRenderState), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"DeleteRenderState", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::DeleteRenderState), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"CreateVertexElements", "MGPVertexElements", static_cast<Uint64>(MGPWireOp::CreateVertexElements), static_cast<Uint64>(kHasBlob), static_cast<Uint64>(kWaitNone)},
|
||||
{"BindVertexElements", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::BindVertexElements), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"DeleteVertexElements", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::DeleteVertexElements), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"CreateSamplerState", "MGPSamplerDesc", static_cast<Uint64>(MGPWireOp::CreateSamplerState), static_cast<Uint64>(kHasBlob), static_cast<Uint64>(kWaitNone)},
|
||||
{"DeleteSamplerState", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::DeleteSamplerState), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"CreateSamplerView", "MGPSamplerView", static_cast<Uint64>(MGPWireOp::CreateSamplerView), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"DeleteSamplerView", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::DeleteSamplerView), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"CreateShaderState", "MGPProgramDesc", static_cast<Uint64>(MGPWireOp::CreateShaderState), static_cast<Uint64>(kHasBlob), static_cast<Uint64>(kWaitNone)},
|
||||
{"BindShaderState", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::BindShaderState), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"DeleteShaderState", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::DeleteShaderState), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetDynamicState", "MGPDynamicState", static_cast<Uint64>(MGPWireOp::SetDynamicState), static_cast<Uint64>(kHasBlob), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetFramebufferState", "MGPFramebufferState", static_cast<Uint64>(MGPWireOp::SetFramebufferState), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetVertexBuffers", "MGPVertexBuffers", static_cast<Uint64>(MGPWireOp::SetVertexBuffers), static_cast<Uint64>(kVarTail), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetIndexBuffer", "MGPIndexBuffer", static_cast<Uint64>(MGPWireOp::SetIndexBuffer), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetIndirectBuffers", "MGPIndirectBuffers", static_cast<Uint64>(MGPWireOp::SetIndirectBuffers), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetSamplerViews", "MGPSamplerViews", static_cast<Uint64>(MGPWireOp::SetSamplerViews), static_cast<Uint64>(kVarTail), static_cast<Uint64>(kWaitNone)},
|
||||
{"BindSamplerStates", "MGPSamplerStates", static_cast<Uint64>(MGPWireOp::BindSamplerStates), static_cast<Uint64>(kVarTail), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetShaderImages", "MGPShaderImages", static_cast<Uint64>(MGPWireOp::SetShaderImages), static_cast<Uint64>(kVarTail), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetShaderBuffers", "MGPShaderBuffers", static_cast<Uint64>(MGPWireOp::SetShaderBuffers), static_cast<Uint64>(kVarTail | kHostSpan), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetStreamOutputTargets", "MGPStreamOutputTargets", static_cast<Uint64>(MGPWireOp::SetStreamOutputTargets), static_cast<Uint64>(kVarTail), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetGlobalConstants", "MGPGlobalConstants", static_cast<Uint64>(MGPWireOp::SetGlobalConstants), static_cast<Uint64>(kHasBlob), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetVertexAttribDefaults", "MGPVertexAttribDefaults", static_cast<Uint64>(MGPWireOp::SetVertexAttribDefaults), static_cast<Uint64>(kVarTail), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetPixelPackState", "MGPPixelPackState", static_cast<Uint64>(MGPWireOp::SetPixelPackState), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetPatchState", "MGPPatchState", static_cast<Uint64>(MGPWireOp::SetPatchState), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetDrawProgram", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::SetDrawProgram), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetDispatchProgram", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::SetDispatchProgram), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetResidualValueState", "MGPResidualValueState", static_cast<Uint64>(MGPWireOp::SetResidualValueState), static_cast<Uint64>(kHasBlob), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetTextureParams", "MGPTextureParams", static_cast<Uint64>(MGPWireOp::SetTextureParams), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"ResourceSubData", "MGPSubData", static_cast<Uint64>(MGPWireOp::ResourceSubData), static_cast<Uint64>(kHasBlob | kVarTail | kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"BufferSubDataResident", "MGPSubData", static_cast<Uint64>(MGPWireOp::BufferSubDataResident), static_cast<Uint64>(kHasBlob | kOptional), static_cast<Uint64>(kWaitNone)},
|
||||
{"ResourceSubDataComplete", "MGPSubDataComplete", static_cast<Uint64>(MGPWireOp::ResourceSubDataComplete), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"ResourceFlushRange", "MGPFlushRange", static_cast<Uint64>(MGPWireOp::ResourceFlushRange), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"ResourceReadback", "MGPReadback", static_cast<Uint64>(MGPWireOp::ResourceReadback), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"ResourceCopyRegion", "MGPCopyRegion", static_cast<Uint64>(MGPWireOp::ResourceCopyRegion), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"GenerateMipmap", "MGPMipPlan", static_cast<Uint64>(MGPWireOp::GenerateMipmap), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"GetTextureImage", "MGPReadbackInfo", static_cast<Uint64>(MGPWireOp::GetTextureImage), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"Blit", "MGPBlit", static_cast<Uint64>(MGPWireOp::Blit), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"Clear", "MGPClear", static_cast<Uint64>(MGPWireOp::Clear), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"ReadPixels", "MGPReadbackInfo", static_cast<Uint64>(MGPWireOp::ReadPixels), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"DrawVbo", "MGPDrawInfo", static_cast<Uint64>(MGPWireOp::DrawVbo), static_cast<Uint64>(kHostSpan | kVarTail), static_cast<Uint64>(kWaitNone)},
|
||||
{"LaunchGrid", "MGPGridInfo", static_cast<Uint64>(MGPWireOp::LaunchGrid), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"MemoryBarrier", "MGPMemoryBarrier", static_cast<Uint64>(MGPWireOp::MemoryBarrier), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"BeginStreamOutput", "MGPStreamOutputBegin", static_cast<Uint64>(MGPWireOp::BeginStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"EndStreamOutput", "MGPXfbAccounting", static_cast<Uint64>(MGPWireOp::EndStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"PauseStreamOutput", "MGPStreamOutputControl", static_cast<Uint64>(MGPWireOp::PauseStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"ResumeStreamOutput", "MGPStreamOutputControl", static_cast<Uint64>(MGPWireOp::ResumeStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"Flush", "MGPFlush", static_cast<Uint64>(MGPWireOp::Flush), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"Present", "MGPPresent", static_cast<Uint64>(MGPWireOp::Present), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitPresent)},
|
||||
{"SetSwapInterval", "MGPSwapInterval", static_cast<Uint64>(MGPWireOp::SetSwapInterval), static_cast<Uint64>(kOptional), static_cast<Uint64>(kWaitNone)},
|
||||
{"QueryTimestamp", "MGPTimestampRequest", static_cast<Uint64>(MGPWireOp::QueryTimestamp), static_cast<Uint64>(kReplySlot), static_cast<Uint64>(kWaitReply)},
|
||||
{"QueryCounter", "MGPQueryDesc", static_cast<Uint64>(MGPWireOp::QueryCounter), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"FenceWaitServer", "MGPFenceWait", static_cast<Uint64>(MGPWireOp::FenceWaitServer), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"BindShaderImage", "MGPImageBind", static_cast<Uint64>(MGPWireOp::BindShaderImage), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"PatchParameter", "MGPPatchParameter", static_cast<Uint64>(MGPWireOp::PatchParameter), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"BindStreamOutput", "MGPStreamOutputBind", static_cast<Uint64>(MGPWireOp::BindStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"SetStorageBlockBinding", "MGPStorageBlockBinding", static_cast<Uint64>(MGPWireOp::SetStorageBlockBinding), static_cast<Uint64>(kHasBlob), static_cast<Uint64>(kWaitApplied)},
|
||||
{"CopyFramebufferToTexture", "MGPCopyFromFramebuffer", static_cast<Uint64>(MGPWireOp::CopyFramebufferToTexture), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"ApplierReset", "MGPApplierReset", static_cast<Uint64>(MGPWireOp::ApplierReset), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitApplied)},
|
||||
{"ObjectDeath", "MGPHandleOnly", static_cast<Uint64>(MGPWireOp::ObjectDeath), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetContextValues", "MGPContextValues", static_cast<Uint64>(MGPWireOp::SetContextValues), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
{"SetProgramBindings", "MGPProgramBindings", static_cast<Uint64>(MGPWireOp::SetProgramBindings), static_cast<Uint64>(kVarTail | kHostSpan), static_cast<Uint64>(kWaitNone)},
|
||||
{"DeleteStreamOutput", "MGPStreamOutputBind", static_cast<Uint64>(MGPWireOp::DeleteStreamOutput), static_cast<Uint64>(kNone), static_cast<Uint64>(kWaitNone)},
|
||||
};
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "CapsCodec.h"
|
||||
|
||||
#include "Protocol/mg_protocol_base.h"
|
||||
#include <MG_Pipe/PipeWireLayout.h>
|
||||
|
||||
#include <MGGitHash.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
@@ -461,43 +462,31 @@ namespace MobileGL::MG_Remote {
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
Transport::AbiFingerprintInputs CapsAbiFingerprintInputs() {
|
||||
// MGPCaps has only a COMPOSITIONAL size assertion (MGPipeTypes.h:145-146) because
|
||||
// DynamicBackendParameters still carries SizeT and GLenum members - P0.5's fixed-width
|
||||
// rewrite did not happen and P5 does not do it either (table 0's ABI row; the rewrite
|
||||
// is P7's account). So the caps block's literal size IS ABI-dependent, and this
|
||||
// fingerprint is what turns that from a latent hazard into a named refusal.
|
||||
//
|
||||
// The git stamp is in it because two builds of the same sizes can still disagree about
|
||||
// a FIELD ORDER, which no sizeof can see; P6's spawn is same-machine and same-binary,
|
||||
// so it inherits this unchanged rather than needing a looser rule.
|
||||
Transport::AbiFingerprintInputs inputs;
|
||||
inputs.DynamicParamsSize = sizeof(MG_Backend::DynamicBackendParameters);
|
||||
inputs.CapsSize = sizeof(MG_Pipe::MGPCaps);
|
||||
inputs.FunctionTableSize = sizeof(MG_Backend::GLFunctionsTable);
|
||||
inputs.FormatCapabilityTargets = static_cast<Uint64>(MG_Backend::kFormatCapabilityTargetCount);
|
||||
inputs.FormatCapabilityFormats = static_cast<Uint64>(MG_Backend::kFormatCapabilityFormatCount);
|
||||
inputs.MemberLayout = MG_Pipe::kMGPipeWireMemberLayoutDigest;
|
||||
inputs.CatalogueLayout = MG_Pipe::kMGPipeWireCatalogueDigest;
|
||||
inputs.RenderStateLayout = MG_Pipe::WireRenderStateDigest();
|
||||
inputs.FormatCapabilityTargets = MG_Backend::kFormatCapabilityTargetCount;
|
||||
inputs.FormatCapabilityFormats = MG_Backend::kFormatCapabilityFormatCount;
|
||||
inputs.FormatCapabilitiesCodecVersion = kFormatCapabilitiesCodecVersion;
|
||||
inputs.RendererInfoCodecVersion = kRendererInfoCodecVersion;
|
||||
inputs.OpCount = static_cast<Uint64>(MG_Pipe::MGPWireOp::kOpCount);
|
||||
// The declared protocol ABI, carried over from s1's version of this function at
|
||||
// integration (ID-33). The sizeofs above catch a struct that changed shape; this
|
||||
// catches a peer that changed the PROTOCOL while every struct stayed the same size,
|
||||
// which is the one break the rest of the mix is blind to.
|
||||
inputs.AbiVersion =
|
||||
static_cast<Uint32>(MOBILEGL_ABI_VERSION(MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR));
|
||||
// MOBILEGL_BUILD_STAMP_VALUE, NOT GIT_COMMIT_HASH_SHORT (CONTRACT-P6 4.2). The old
|
||||
// macro is application-visible through glGetString(GL_VERSION) and may not move, and it
|
||||
// came from an execute_process with no RESULT_VARIABLE - so a tree git could not read
|
||||
// produced "" and every such build agreed with every other. The new pair separates the
|
||||
// VALUE from whether the build could name a commit at all.
|
||||
inputs.BuildStamp = MOBILEGL_BUILD_STAMP_VALUE;
|
||||
inputs.BuildStampPresent = MOBILEGL_BUILD_STAMP_PRESENT;
|
||||
inputs.AbiVersion = MOBILEGL_ABI_VERSION(MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR);
|
||||
inputs.PointerBits = sizeof(void*) * 8;
|
||||
const Uint32 endian = 1;
|
||||
inputs.LittleEndian = *reinterpret_cast<const Uint8*>(&endian) == 1 ? 1u : 0u;
|
||||
return inputs;
|
||||
}
|
||||
|
||||
// ONE implementation, deliberately a one-liner: the wave-1 review (ID-46 finding 6) found a
|
||||
// second hand-rolled FNV loop here while the mixer under Transport/ had no production caller,
|
||||
// so the sensitivity test could not see this function change. Now it starts from here.
|
||||
Uint64 CapsAbiFingerprint() { return Transport::MixAbiFingerprint(CapsAbiFingerprintInputs()); }
|
||||
Uint64 WireFingerprint() {
|
||||
static const Uint64 fingerprint = Transport::MixAbiFingerprint(CapsAbiFingerprintInputs());
|
||||
return fingerprint;
|
||||
}
|
||||
// Compatibility spelling for existing wire tests and helper peers.
|
||||
Uint64 CapsAbiFingerprint() { return WireFingerprint(); }
|
||||
const char* BuildFingerprint() { return MOBILEGL_BUILD_STAMP_VALUE; }
|
||||
Bool BuildFingerprintPresent() { return MOBILEGL_BUILD_STAMP_PRESENT != 0; }
|
||||
|
||||
} // namespace MobileGL::MG_Remote
|
||||
|
||||
@@ -20,13 +20,7 @@
|
||||
// RendererInfo -> RendererInfo (MG_Util/Types.h:317): three Strings, an
|
||||
// Optional<String>, and a Vector<GLExtension> inside GLInfo.
|
||||
//
|
||||
// THE ABI RULING (table 0). MGPCaps has only a COMPOSITIONAL size assertion
|
||||
// (MGPipeTypes.h:145-146) because DynamicBackendParameters still carries SizeT and GLenum
|
||||
// members - P0.5's fixed-width rewrite did not happen. P5 does NOT rewrite it. Instead the
|
||||
// handshake asserts that both peers agree on sizeof(DynamicBackendParameters),
|
||||
// sizeof(MGPCaps) and the build fingerprint, and Fatal{AbiMismatch} otherwise. P6's spawn is
|
||||
// same-machine and same-binary, so it inherits this unchanged; the fixed-width rewrite is on
|
||||
// P7's account.
|
||||
// P6.5: wire layout and build identity are independent handshake facts.
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
@@ -90,19 +84,11 @@ namespace MobileGL::MG_Remote {
|
||||
Bool EncodeRendererInfo(const RendererInfo& info, Vector<Uint8>& out);
|
||||
Bool DecodeRendererInfo(const void* bytes, Uint64 size, RendererInfo& out);
|
||||
|
||||
// ---- the ABI assertion the handshake carries ----------------------------------------
|
||||
//
|
||||
// The inputs, as this build sees them: sizeof(DynamicBackendParameters), sizeof(MGPCaps),
|
||||
// sizeof(GLFunctionsTable), the format-capability table's extents, the two caps-blob codec
|
||||
// versions, MGPWireOp::kOpCount, the protocol ABI version and the compile-time git stamp.
|
||||
// Public so that the sensitivity control can pin every one of them to the real value AND
|
||||
// perturb them one at a time through the same mixer the handshake uses.
|
||||
// Wire compatibility never includes a git stamp or local function-pointer tables.
|
||||
Transport::AbiFingerprintInputs CapsAbiFingerprintInputs();
|
||||
|
||||
// What Hello/Welcome carry and compare: EXACTLY Transport::MixAbiFingerprint(
|
||||
// CapsAbiFingerprintInputs()) - one implementation, no second hash (ID-46 finding 6). A
|
||||
// mismatch is Fatal{AbiMismatch} and never a downgrade, because every alternative silently
|
||||
// reads one struct as another.
|
||||
Uint64 CapsAbiFingerprint();
|
||||
Uint64 WireFingerprint();
|
||||
Uint64 CapsAbiFingerprint(); // legacy spelling of WireFingerprint
|
||||
const char* BuildFingerprint();
|
||||
Bool BuildFingerprintPresent();
|
||||
|
||||
} // namespace MobileGL::MG_Remote
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#include "ClientSession.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
#include <MG_Remote/Handshake.h>
|
||||
#include <MG_Remote/Transport/LinkMetrics.h>
|
||||
|
||||
#include "../CapsCodec.h"
|
||||
#include "../Protocol/generated/protocol_generated.h"
|
||||
@@ -109,39 +111,6 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return ::MobileGL::Wire::GetCtrlEnvelope(bytes.data());
|
||||
}
|
||||
|
||||
// CONTRACT-P6 4.3: THE DECLARED PROTOCOL VERSION, WHICH THIS SIDE NEVER READ.
|
||||
//
|
||||
// Hello has carried abiMajor/abiMinor since P5 and the SERVER checks them, but the
|
||||
// client only ever compared the fingerprint - so the asymmetry was silent in the one
|
||||
// direction that matters for spawn, where the peer is a SEPARATE IMAGE the operator
|
||||
// chose with MOBILEGL_IPC_SERVER_PATH.
|
||||
//
|
||||
// The fingerprint mixes AbiVersion and would catch a mismatch anyway, but it catches it
|
||||
// as "struct shapes" - which sends the reader looking for a layout change that is not
|
||||
// there. Checking the version FIRST turns the same failure into the sentence that
|
||||
// explains it. Both handshake sites call this; there are two, and only one of them used
|
||||
// to be reachable, which is how the gap survived review.
|
||||
[[noreturn]] void FatalProtocolVersionMismatch(unsigned theirMajor, unsigned theirMinor,
|
||||
const char* theirStamp) {
|
||||
SessionFail(MGFatalFamily::AbiMismatch, "MGPipe: Fatal{AbiMismatch, \"protocol version\"} ours=%u.%u theirs=%u.%u "
|
||||
"ourBuild=%s theirBuild=%s - the peer speaks a different MGPipe protocol. "
|
||||
"Under spawn this is most often a stale MOBILEGL_IPC_SERVER_PATH pointing at "
|
||||
"a libMobileGLServer.so built from another tree.",
|
||||
static_cast<unsigned>(MOBILEGL_PROTOCOL_ABI_MAJOR),
|
||||
static_cast<unsigned>(MOBILEGL_PROTOCOL_ABI_MINOR), theirMajor, theirMinor,
|
||||
GIT_COMMIT_HASH_SHORT, theirStamp == nullptr ? "?" : theirStamp);
|
||||
}
|
||||
|
||||
[[noreturn]] void FatalAbiMismatch(const char* what, Uint64 ours, Uint64 theirs,
|
||||
const char* theirStamp) {
|
||||
SessionFail(MGFatalFamily::AbiMismatch, "MGPipe: Fatal{AbiMismatch, \"%s\"} ours=%llu theirs=%llu ourBuild=%s "
|
||||
"theirBuild=%s - never a downgrade: the caps block's size is ABI-dependent "
|
||||
"and every field past the first difference would be read at the wrong offset",
|
||||
what, static_cast<unsigned long long>(ours),
|
||||
static_cast<unsigned long long>(theirs), GIT_COMMIT_HASH_SHORT,
|
||||
theirStamp == nullptr ? "?" : theirStamp);
|
||||
}
|
||||
|
||||
// Guarded the same way ServerSession's helpers are: MG_Config::Ipc only exists
|
||||
// behind MOBILEGL_BUILD_DISAGGREGATED (Config.h), and this file is only compiled
|
||||
// there today - but the guard is what keeps that true if the source list ever
|
||||
@@ -531,6 +500,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return *instance;
|
||||
}
|
||||
|
||||
ClientSession::ClientSession() {
|
||||
m_link = Transport::CreateSharedLink(Transport::TransportRoleTag::ClientProducer);
|
||||
m_cmd = &m_link->CommandsOut(); m_events = &m_link->EventsIn();
|
||||
}
|
||||
|
||||
ClientSession::~ClientSession() {
|
||||
if (m_started) {
|
||||
Stop();
|
||||
@@ -580,13 +554,19 @@ namespace MobileGL::MG_Remote::Client {
|
||||
|
||||
// ---- 2. Hello. Sent before the server accepts: InProcessTransport queues whole
|
||||
// messages, so one thread can drive both halves of the handshake in order.
|
||||
const Uint64 fingerprint = CapsAbiFingerprint();
|
||||
const auto dial = ::MobileGL::Wire::DialMode::No;
|
||||
const auto dataPlane = ::MobileGL::Wire::DataPlane::SharedSegments;
|
||||
const Uint64 fingerprint = WireFingerprint();
|
||||
{
|
||||
::flatbuffers::FlatBufferBuilder builder(512);
|
||||
auto stamp = builder.CreateString(GIT_COMMIT_HASH_SHORT);
|
||||
auto stamp = builder.CreateString(BuildFingerprint());
|
||||
auto terms = ::MobileGL::Wire::CreateLinkTerms(builder, dataPlane);
|
||||
const char* tokenText = std::getenv("MOBILEGL_IPC_TOKEN");
|
||||
auto token = builder.CreateString(tokenText == nullptr ? "" : tokenText);
|
||||
auto hello = ::MobileGL::Wire::CreateHello(
|
||||
builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, stamp,
|
||||
/*backendType=*/0u, /*pid=*/SelfProcessId(), /*configBlob=*/0, fingerprint);
|
||||
static_cast<Uint32>(MG_Config::ActiveBackendType), SelfProcessId(), 0,
|
||||
fingerprint, fingerprint, terms, token, dial);
|
||||
auto root = ::MobileGL::Wire::CreateCtrlEnvelope(
|
||||
builder, ::MobileGL::Wire::CtrlMsg::Hello, hello.Union());
|
||||
::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root);
|
||||
@@ -617,6 +597,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return received;
|
||||
}
|
||||
const ::MobileGL::Wire::CtrlEnvelope* envelope = ParseEnvelope(frame);
|
||||
if (LogPeerRefusal(envelope)) {
|
||||
Stop();
|
||||
return MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
}
|
||||
// msg_as_Welcome() IS PART OF THE GUARD - flatbuffers' Verifier::VerifyTable is
|
||||
// `return !table || table->Verify(*this)`, so a NULL union member verifies while
|
||||
// msg_type() still reports Welcome. See ServerSession::Accept for the same guard.
|
||||
@@ -634,16 +618,32 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// 4.3: the pid the SERVER stated, taken before any check can abort, so a Fatal line
|
||||
// can name it too.
|
||||
m_peerServerPid = static_cast<std::uint32_t>(welcome->serverPid());
|
||||
// The VERSION before the fingerprint: both would fire, and only one names the cause.
|
||||
if (welcome->abiMajor() != MOBILEGL_PROTOCOL_ABI_MAJOR ||
|
||||
welcome->abiMinor() != MOBILEGL_PROTOCOL_ABI_MINOR) {
|
||||
FatalProtocolVersionMismatch(static_cast<unsigned>(welcome->abiMajor()),
|
||||
static_cast<unsigned>(welcome->abiMinor()),
|
||||
theirStamp);
|
||||
const MobileGLResult compatible = ValidatePeerHandshake(*m_transport,
|
||||
welcome->abiMajor(), welcome->abiMinor(), welcome->wireFingerprint(), theirStamp, dial);
|
||||
if (compatible != MOBILEGL_OK) {
|
||||
Stop();
|
||||
return compatible;
|
||||
}
|
||||
if (welcome->abiFingerprint() != fingerprint) {
|
||||
FatalAbiMismatch("struct shapes", fingerprint, welcome->abiFingerprint(),
|
||||
theirStamp);
|
||||
const auto* terms = welcome->linkTerms();
|
||||
if (terms == nullptr || terms->dataPlane() != dataPlane ||
|
||||
terms->wireForm() != ::MobileGL::Wire::WireForm::StructImage ||
|
||||
terms->cmdWindowBytes() < Transport::kMinRingCapacity ||
|
||||
(terms->cmdWindowBytes() & (terms->cmdWindowBytes() - 1)) != 0 ||
|
||||
terms->stageWindowBytes() == 0 || terms->maxReplyBytes() == 0 ||
|
||||
terms->maxReplyBytes() > 0xFFFFFFFFull - sizeof(Transport::ReplySlotHeader) ||
|
||||
terms->eventWindowBytes() < Transport::kMinRingCapacity ||
|
||||
(terms->eventWindowBytes() & (terms->eventWindowBytes() - 1)) != 0) {
|
||||
const auto result = RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::LinkTerms,
|
||||
"invalid server link terms");
|
||||
Stop();
|
||||
return result;
|
||||
}
|
||||
if (welcome->backendType() != static_cast<Uint32>(MG_Config::ActiveBackendType)) {
|
||||
const auto result = RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::Backend,
|
||||
"server backend differs from request", static_cast<Uint32>(MG_Config::ActiveBackendType),
|
||||
welcome->backendType());
|
||||
Stop();
|
||||
return result;
|
||||
}
|
||||
// The four SegmentRefs are what a spawn client MAPS (P6). Under inproc the mapping
|
||||
// already exists, so what they are good for here is the cross-check that the two
|
||||
@@ -675,7 +675,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// under the client role; under `spawn` it becomes ShmSegment::Adopt of the fds the
|
||||
// server passed by SCM_RIGHTS, which is why nothing below this line knows which it was.
|
||||
const MobileGLResult attached =
|
||||
m_shm.AttachInProcess(server.Shm(), Transport::MemoryRole::Client);
|
||||
m_link->Memory().AttachInProcess(server.Shm(), Transport::MemoryRole::Client);
|
||||
if (attached != MOBILEGL_OK) {
|
||||
Stop();
|
||||
return attached;
|
||||
@@ -690,6 +690,35 @@ namespace MobileGL::MG_Remote::Client {
|
||||
if (m_started) {
|
||||
return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
const std::string configured(MG_Config::Ipc.Control.c_str());
|
||||
if (configured != "fork") {
|
||||
const bool tcp = configured.compare(0, 6, "tcp://") == 0;
|
||||
const bool unixSocket = configured.compare(0, 5, "unix:") == 0;
|
||||
const std::string data(MG_Config::Ipc.Data.c_str());
|
||||
if ((!tcp && !unixSocket) ||
|
||||
(data != "auto" && data != (tcp ? "stream" : "shm"))) {
|
||||
MGLOG_E("MG_Remote: Refuse{ProtocolMismatch} unsupported control/data pair %s/%s",
|
||||
configured.c_str(), data.c_str());
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
m_connectDial = true;
|
||||
const auto endpoint = tcp ? configured : configured.substr(5);
|
||||
std::unique_ptr<Transport::SocketTransport> socket;
|
||||
const auto connected = Transport::SocketTransport::ConnectTo(endpoint, kSpawnConnectTimeoutMs, socket);
|
||||
if (connected != MOBILEGL_OK) return connected;
|
||||
const auto started = StartOverSocket(std::move(socket));
|
||||
if (started == MOBILEGL_OK) {
|
||||
MGLOG_I("MG_Remote client: control=%s data=%s server=%s pid=%u dial=connect",
|
||||
tcp ? "tcp" : "unix", tcp ? "stream" : "shm",
|
||||
tcp ? endpoint.c_str() + 6 : endpoint.c_str(), m_peerServerPid);
|
||||
}
|
||||
return started;
|
||||
}
|
||||
if (MG_Config::Ipc.Data != "auto" && MG_Config::Ipc.Data != "shm") {
|
||||
MGLOG_E("MG_Remote: Refuse{ProtocolMismatch} fork requires shm data in P6.5 wave one");
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
m_connectDial = false;
|
||||
// A rendezvous nobody else can collide with. Independent processes need
|
||||
// a NAME; making it per-process-per-instant is what stops two runs on
|
||||
// one machine - which `ctest -j` produces by construction - finding each
|
||||
@@ -755,13 +784,20 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// ---- Hello. The same bytes as inproc; the fingerprint is still compared
|
||||
// for equality, because under Dial == Fork the child IS the same binary and
|
||||
// comparing it catches a stale MOBILEGL_IPC_SERVER_PATH (CONTRACT-P6 4.1).
|
||||
const Uint64 fingerprint = CapsAbiFingerprint();
|
||||
const auto dial = m_connectDial ? ::MobileGL::Wire::DialMode::Connect : ::MobileGL::Wire::DialMode::Fork;
|
||||
const auto dataPlane = m_socketTransport->IsTcp() ? ::MobileGL::Wire::DataPlane::Stream
|
||||
: ::MobileGL::Wire::DataPlane::SharedSegments;
|
||||
const Uint64 fingerprint = WireFingerprint();
|
||||
{
|
||||
::flatbuffers::FlatBufferBuilder builder(512);
|
||||
auto stamp = builder.CreateString(GIT_COMMIT_HASH_SHORT);
|
||||
auto stamp = builder.CreateString(BuildFingerprint());
|
||||
auto terms = ::MobileGL::Wire::CreateLinkTerms(builder, dataPlane);
|
||||
const char* tokenText = std::getenv("MOBILEGL_IPC_TOKEN");
|
||||
auto token = builder.CreateString(tokenText == nullptr ? "" : tokenText);
|
||||
auto hello = ::MobileGL::Wire::CreateHello(
|
||||
builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, stamp,
|
||||
/*backendType=*/0u, /*pid=*/SelfProcessId(), /*configBlob=*/0, fingerprint);
|
||||
static_cast<Uint32>(MG_Config::ActiveBackendType), SelfProcessId(), 0,
|
||||
fingerprint, fingerprint, terms, token, dial);
|
||||
auto root = ::MobileGL::Wire::CreateCtrlEnvelope(
|
||||
builder, ::MobileGL::Wire::CtrlMsg::Hello, hello.Union());
|
||||
::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root);
|
||||
@@ -778,6 +814,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// own mapping, which a6 showed is vacuous against a real peer. Here the wire
|
||||
// values are the only values, which is what that check has to become on
|
||||
// every lane (CONTRACT-P6 4.4).
|
||||
Transport::SessionSegmentSizes negotiatedSizes;
|
||||
bool stream = false;
|
||||
Uint64 announced[4] = {0, 0, 0, 0};
|
||||
const Uint32 replySlotCount = Transport::kDefaultReplySlotCount;
|
||||
{
|
||||
@@ -791,6 +829,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return received;
|
||||
}
|
||||
const ::MobileGL::Wire::CtrlEnvelope* envelope = ParseEnvelope(frame);
|
||||
if (LogPeerRefusal(envelope)) {
|
||||
Stop();
|
||||
return MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
}
|
||||
if (envelope == nullptr || envelope->msg_type() != ::MobileGL::Wire::CtrlMsg::Welcome ||
|
||||
envelope->msg_as_Welcome() == nullptr) {
|
||||
MGLOG_E("MG_Remote client: the spawned server first frame is not a verifiable "
|
||||
@@ -805,17 +847,39 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// 4.3: the pid the SERVER stated, taken before any check can abort, so a Fatal line
|
||||
// can name it too.
|
||||
m_peerServerPid = static_cast<std::uint32_t>(welcome->serverPid());
|
||||
// The VERSION before the fingerprint: both would fire, and only one names the cause.
|
||||
if (welcome->abiMajor() != MOBILEGL_PROTOCOL_ABI_MAJOR ||
|
||||
welcome->abiMinor() != MOBILEGL_PROTOCOL_ABI_MINOR) {
|
||||
FatalProtocolVersionMismatch(static_cast<unsigned>(welcome->abiMajor()),
|
||||
static_cast<unsigned>(welcome->abiMinor()),
|
||||
theirStamp);
|
||||
const MobileGLResult compatible = ValidatePeerHandshake(*m_transport,
|
||||
welcome->abiMajor(), welcome->abiMinor(), welcome->wireFingerprint(), theirStamp, dial);
|
||||
if (compatible != MOBILEGL_OK) {
|
||||
Stop();
|
||||
return compatible;
|
||||
}
|
||||
if (welcome->abiFingerprint() != fingerprint) {
|
||||
FatalAbiMismatch("struct shapes", fingerprint, welcome->abiFingerprint(),
|
||||
theirStamp);
|
||||
const auto* terms = welcome->linkTerms();
|
||||
if (terms == nullptr || terms->dataPlane() != dataPlane ||
|
||||
terms->wireForm() != ::MobileGL::Wire::WireForm::StructImage ||
|
||||
terms->cmdWindowBytes() < Transport::kMinRingCapacity ||
|
||||
(terms->cmdWindowBytes() & (terms->cmdWindowBytes() - 1)) != 0 ||
|
||||
terms->stageWindowBytes() == 0 || terms->maxReplyBytes() == 0 ||
|
||||
terms->maxReplyBytes() > 0xFFFFFFFFull - sizeof(Transport::ReplySlotHeader) ||
|
||||
terms->eventWindowBytes() < Transport::kMinRingCapacity ||
|
||||
(terms->eventWindowBytes() & (terms->eventWindowBytes() - 1)) != 0) {
|
||||
const auto result = RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::LinkTerms,
|
||||
"invalid server link terms");
|
||||
Stop();
|
||||
return result;
|
||||
}
|
||||
if (welcome->backendType() != static_cast<Uint32>(MG_Config::ActiveBackendType)) {
|
||||
const auto result = RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::Backend,
|
||||
"server backend differs from request", static_cast<Uint32>(MG_Config::ActiveBackendType),
|
||||
welcome->backendType());
|
||||
Stop();
|
||||
return result;
|
||||
}
|
||||
stream = terms->dataPlane() == ::MobileGL::Wire::DataPlane::Stream;
|
||||
negotiatedSizes.CmdRingBytes = terms->cmdWindowBytes();
|
||||
negotiatedSizes.StageBytes = terms->stageWindowBytes();
|
||||
negotiatedSizes.EventRingBytes = terms->eventWindowBytes();
|
||||
negotiatedSizes.ReplyBytes = (terms->maxReplyBytes() + sizeof(Transport::ReplySlotHeader)) *
|
||||
negotiatedSizes.ReplySlotCount;
|
||||
const ::MobileGL::Wire::SegmentRef* refs[4] = {welcome->cmdRing(), welcome->stageRing(),
|
||||
welcome->replyPool(),
|
||||
welcome->eventRing()};
|
||||
@@ -830,6 +894,14 @@ namespace MobileGL::MG_Remote::Client {
|
||||
}
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
const auto attached = AttachStreamLink(m_socketTransport->TakeDataFd(), negotiatedSizes);
|
||||
if (attached != MOBILEGL_OK) { Stop(); return attached; }
|
||||
m_controlInbox = std::make_unique<Transport::ControlInbox>(*m_transport);
|
||||
Server::ServerLoopInstance().SetRemoteControlSink(&RemoteControlSinkThunk, this);
|
||||
return FinishStartup(&m_link->ConsumerBell(), &m_link->ProducerBell(), false);
|
||||
}
|
||||
|
||||
// ---- the four descriptors. THIS is the step inproc does not have, and the
|
||||
// reason SCM_RIGHTS is in this design at all: the segments are anonymous -
|
||||
// ASharedMemory_create has no filesystem name and memfd / shm_open+unlink
|
||||
@@ -894,14 +966,14 @@ namespace MobileGL::MG_Remote::Client {
|
||||
|
||||
// ---- adopt and map. The same Adopt + Map every inproc run has
|
||||
// exercised since P5; only the origin of the descriptor is new.
|
||||
const MobileGLResult adopted = m_shm.AdoptFromDescriptors(
|
||||
const MobileGLResult adopted = m_link->Memory().AdoptFromDescriptors(
|
||||
fds, announced, replySlotCount, Transport::MemoryRole::Client);
|
||||
if (adopted != MOBILEGL_OK) {
|
||||
closeAll();
|
||||
Stop();
|
||||
return adopted;
|
||||
}
|
||||
// The four segment descriptors now belong to m_shm. The two bells
|
||||
// The four segment descriptors now belong to m_link->Memory(). The two bells
|
||||
// belong to the doorbells, which own and close them.
|
||||
//
|
||||
// WHICH BELL IS WHICH IS THE SESSION'S KNOWLEDGE, exactly as under
|
||||
@@ -947,6 +1019,46 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return static_cast<ClientSession*>(user)->RunRemoteSurfaceControlFrame(frame);
|
||||
}
|
||||
|
||||
MobileGLResult ClientSession::ReceiveControlFrame(std::vector<Uint8>& frame, Uint32 timeoutMs) {
|
||||
return m_controlInbox ? m_controlInbox->Receive(frame, timeoutMs)
|
||||
: ReceiveEnvelope(*m_transport, frame, timeoutMs);
|
||||
}
|
||||
|
||||
void ClientSession::SyncPeerLog() {
|
||||
if (!m_started || !m_controlInbox || !m_transport || DeviceLost()) return;
|
||||
WaitForApplyToCatchUp("SyncPeerLog");
|
||||
const std::lock_guard<std::mutex> lock(m_remoteControlMutex);
|
||||
const auto seq = ++m_logFlushSeq;
|
||||
flatbuffers::FlatBufferBuilder builder(64);
|
||||
auto flush = ::MobileGL::Wire::CreateLogFlush(builder, seq, false);
|
||||
auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::LogFlush, flush.Union());
|
||||
::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope);
|
||||
if (m_transport->SendFrame({builder.GetBufferPointer(), builder.GetSize()}) != MOBILEGL_OK) {
|
||||
LatchDeviceLost("control stream closed during LogFlush");
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
std::vector<Uint8> bytes;
|
||||
const auto received = ReceiveControlFrame(bytes, kRemoteControlTimeoutMs);
|
||||
if (received == MOBILEGL_ERR_TRANSPORT_CLOSED) {
|
||||
LatchDeviceLost("control stream closed during LogFlush");
|
||||
return;
|
||||
}
|
||||
if (received != MOBILEGL_OK)
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, LogFlush} no acknowledgement rc=%d", received);
|
||||
const auto* reply = ParseEnvelope(bytes);
|
||||
if (!reply)
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, LogFlush} malformed acknowledgement");
|
||||
if (const auto* ack = reply->msg_as_LogFlush()) {
|
||||
if (!ack->ack() || ack->seq() != seq) {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, LogFlush} reply sequence mismatch");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (const auto* caps = reply->msg_as_CapsSnapshot()) AdoptCapsSnapshot(caps);
|
||||
}
|
||||
}
|
||||
|
||||
MobileGLResult ClientSession::RunRemoteSurfaceControlFrame(Server::SurfaceControlFrame& frame) {
|
||||
if (m_transport == nullptr) {
|
||||
return MOBILEGL_ERR_NOT_INITIALIZED;
|
||||
@@ -974,6 +1086,15 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// caller-serialised on the server's m_callerMutex under inproc; the same
|
||||
// discipline has to hold here or two replies race for one correlation.
|
||||
const std::lock_guard<std::mutex> lock(m_remoteControlMutex);
|
||||
// Control and data are independent ordered streams. Finish the old
|
||||
// command prefix before a surface operation can change its EGL tuple.
|
||||
if (m_started && m_link && !m_link->Capabilities().PublishIsDelivery) {
|
||||
const auto submitted = m_producer.LastPublishedSeq();
|
||||
m_producer.PublishAndNotify(submitted);
|
||||
if (WaitForAppliedBudget(m_producer, *m_events, submitted, kRemoteControlTimeoutMs) !=
|
||||
Transport::SessionWait::Reached) return MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
DrainEventRing(*m_events);
|
||||
}
|
||||
const MobileGLResult sent = m_transport->SendFrame(
|
||||
MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()});
|
||||
if (sent != MOBILEGL_OK) {
|
||||
@@ -986,7 +1107,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
for (;;) {
|
||||
std::vector<Uint8> reply;
|
||||
const MobileGLResult received =
|
||||
ReceiveEnvelope(*m_transport, reply, kRemoteControlTimeoutMs);
|
||||
ReceiveControlFrame(reply, kRemoteControlTimeoutMs);
|
||||
if (received != MOBILEGL_OK) {
|
||||
// `dl` (D5b): THE EXPIRY IS NOT FATAL AND IT IS NOT ONE ANSWER. The transport
|
||||
// already distinguishes the two states this wait can end in, and the contract
|
||||
@@ -1063,6 +1184,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
}
|
||||
MG_Remote::DecodeWireSurfaceReply(*wire, &frame);
|
||||
if (m_link) {
|
||||
const auto delivered = m_link->WaitForEventDelivery(frame.eventHead, kRemoteControlTimeoutMs);
|
||||
if (delivered != MOBILEGL_OK) return delivered;
|
||||
}
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
}
|
||||
@@ -1081,9 +1206,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
MobileGLResult ClientSession::FinishStartup(Transport::Doorbell* peerBell,
|
||||
Transport::Doorbell* selfBell,
|
||||
bool startApplyThreadHere) {
|
||||
Transport::RingControl* control = m_shm.CmdControl();
|
||||
m_cmd = Transport::RingProducer(control, m_shm.CmdRingBase(), m_shm.CmdRingCapacity(),
|
||||
Transport::RingCursorSet::Cmd);
|
||||
m_link->InitializeEndpoints();
|
||||
// NO STAGE RING. SEG_STAGE is package w1's encoder-local LINEAR ALLOCATOR:
|
||||
// a staged byte run carries no RingRecordHeader, nothing consumes SEG_STAGE,
|
||||
// and the allocator reclaims on retiredSeq. A RingProducer over
|
||||
@@ -1091,23 +1214,22 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// two tails, so FreeBytes() would fall to zero the first time the head
|
||||
// lapped the capacity and never recover - a guaranteed hang. See
|
||||
// RingControl's stage triple in Ring.h.
|
||||
if (!m_cmd.Valid()) {
|
||||
if (!m_cmd->Valid()) {
|
||||
Stop();
|
||||
return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
// PeerDoorbell() is the bell the SERVER parks on and this side rings; SelfDoorbell() is
|
||||
// this side's own. Which is which is the session's knowledge, not the transport's.
|
||||
m_producer.Attach(control, &m_cmd, peerBell, selfBell, SpinUsFromConfig());
|
||||
m_link->BindDoorbells(peerBell, selfBell);
|
||||
peerBell = &m_link->ConsumerBell(); selfBell = &m_link->ProducerBell();
|
||||
m_producer.Attach(*m_link, SpinUsFromConfig());
|
||||
|
||||
|
||||
m_replies = Transport::ReplySlotPool(m_shm.ReplyBase(), m_shm.ReplyBytes(),
|
||||
m_shm.ReplySlotCount());
|
||||
m_events = Transport::EventRingConsumer(m_shm.EventControl(), control,
|
||||
m_shm.EventRingBase(), m_shm.EventRingCapacity(),
|
||||
m_shm.EventSegmentBase());
|
||||
// P5e (ra, §2.6): the drain's other half. A server parked on a full SEG_EVENT is woken
|
||||
// by the drain that emptied it, not by the next thing this client happens to publish.
|
||||
m_events.SetServerDoorbell(peerBell);
|
||||
if (!m_replies.Valid() || !m_events.Valid()) {
|
||||
m_events->SetServerDoorbell(peerBell);
|
||||
m_events->SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get());
|
||||
if (MaxReplyBytes() == 0 || !m_events->Valid()) {
|
||||
Stop();
|
||||
return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
@@ -1128,21 +1250,9 @@ namespace MobileGL::MG_Remote::Client {
|
||||
//
|
||||
// The Install calls themselves are package w1's and are named-Fatal stubs until w1
|
||||
// lands; see ServerSession::Accept for why they are called anyway.
|
||||
m_segments.Install(Wire::kSegCmd,
|
||||
Wire::SegmentView{m_shm.CmdRingBase(), m_shm.CmdRingCapacity()});
|
||||
m_segments.Install(Wire::kSegStage,
|
||||
Wire::SegmentView{m_shm.StageBase(), m_shm.StageBytes()});
|
||||
m_segments.Install(Wire::kSegReply,
|
||||
Wire::SegmentView{m_shm.ReplyBase(), m_shm.ReplyBytes()});
|
||||
m_segments.Install(Wire::kSegEvent,
|
||||
Wire::SegmentView{m_shm.EventSegmentBase(),
|
||||
m_shm.AnnouncedSize(Transport::SessionSegmentSlot::Event)});
|
||||
// nullptr for the stage producer, and that is the honest value: c0's
|
||||
// signature predates w1's ruling that SEG_STAGE is a linear allocator, and
|
||||
// the encoder reaches its bytes through the SegmentTable above. Handing it
|
||||
// a live RingProducer over a cursor triple nobody consumes would be the
|
||||
// half-wired shape this session exists not to have.
|
||||
m_encoder = Wire::PipeWireEncoder(control, &m_cmd, nullptr, &m_segments);
|
||||
m_segments.AttachLink(m_link.get());
|
||||
m_encoder = Wire::PipeWireEncoder(m_link.get(), &m_segments);
|
||||
m_encoder.SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get());
|
||||
m_encoder.SetStageRetirementDoorbell(m_producer.SelfDoorbell());
|
||||
// P5e (ra, §2.6): the stage bell is a client wait and every client wait drains. The
|
||||
// encoder owns the park; the session owns SEG_EVENT, so the session lends it a drain.
|
||||
@@ -1171,6 +1281,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// to be true before the pointer that grants access to it is published. `Stop()` takes
|
||||
// them down in the mirror order (m_started = false, then g_active = nullptr).
|
||||
m_started = true;
|
||||
Transport::LinkMetricsBegin();
|
||||
g_active = this;
|
||||
// D1c: THE SESSION-LIVE AND APPLY-THREAD FACTS, stated where they become true.
|
||||
//
|
||||
@@ -1239,6 +1350,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// monolith adapter runs is a defined no-op rather than a use-after-free.
|
||||
UninstallClientWireTables();
|
||||
if (!m_started) {
|
||||
m_controlInbox.reset();
|
||||
if (m_socketTransport) m_socketTransport->Shutdown();
|
||||
// Start's own failure paths land here with a half-built session. FIVE of them are
|
||||
// reached AFTER ServerSession::Accept has already returned OK, so tearing down
|
||||
// only the client half is not enough and gets three things wrong at once: the
|
||||
@@ -1249,7 +1362,9 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// still point at. The server closes FIRST, in the same order the started path
|
||||
// gets right, and only then do the transports go.
|
||||
m_producer.Detach();
|
||||
m_shm.Close();
|
||||
m_link = Transport::CreateSharedLink(Transport::TransportRoleTag::ClientProducer);
|
||||
m_cmd = &m_link->CommandsOut(); m_events = &m_link->EventsIn();
|
||||
m_link->Memory().Close();
|
||||
Server::ServerSessionInstance().Close();
|
||||
m_clientTransport.reset();
|
||||
m_serverTransport.reset();
|
||||
@@ -1264,7 +1379,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
//
|
||||
// 1. publish and let the server drain. Bounded: a lost record must be a red lane, not
|
||||
// a hung exit.
|
||||
Transport::RingControl* control = m_shm.CmdControl();
|
||||
Transport::RingControl* control = m_link->Memory().CmdControl();
|
||||
if (control != nullptr) {
|
||||
// The PRODUCER's own last-published seq, not RingControl::submittedSeq. Ring.h:72-77
|
||||
// permits submittedSeq to be published lazily and Ring.h:243 encourages batching
|
||||
@@ -1291,6 +1406,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
if (m_transport != nullptr) {
|
||||
m_transport->Shutdown();
|
||||
}
|
||||
m_controlInbox.reset();
|
||||
|
||||
// 3. JOIN, bounded - package v1's ServerLoop::Stop, which also destroys the server's
|
||||
// private BackendObject on that thread before it exits.
|
||||
@@ -1300,12 +1416,14 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// referenced by an unapplied record is a use-after-free the join is what prevents.
|
||||
LogMemory("teardown");
|
||||
LogWireLedger();
|
||||
Transport::LinkMetricsEnd();
|
||||
m_producer.Detach();
|
||||
m_encoder = Wire::PipeWireEncoder();
|
||||
m_events = Transport::EventRingConsumer();
|
||||
m_replies = Transport::ReplySlotPool();
|
||||
m_cmd = Transport::RingProducer();
|
||||
m_shm.Close();
|
||||
*m_events = Transport::EventRingConsumer();
|
||||
*m_cmd = Transport::RingProducer();
|
||||
m_link = Transport::CreateSharedLink(Transport::TransportRoleTag::ClientProducer);
|
||||
m_cmd = &m_link->CommandsOut(); m_events = &m_link->EventsIn();
|
||||
m_link->Memory().Close();
|
||||
Server::ServerSessionInstance().Close();
|
||||
m_clientTransport.reset();
|
||||
m_serverTransport.reset();
|
||||
@@ -1412,6 +1530,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
Wire::WireOpName(op));
|
||||
}
|
||||
|
||||
const Bool rowCarriesReplySlot =
|
||||
(MG_Pipe::MGPipeCallFlagsFor(op) & static_cast<Uint32>(MG_Pipe::kReplySlot)) != 0;
|
||||
const Bool ownsReplySlot = rowCarriesReplySlot && wantReply;
|
||||
const auto replyMetricStart = Transport::LinkMetricsBeginReply(ownsReplySlot);
|
||||
Uint64 seq = m_encoder.EncodeRecord(op, payload, payloadBytes, tails, tailCount);
|
||||
if (seq == Wire::kInvalidSeq) {
|
||||
// EncodeRecord already refused individually oversized records. This one fits,
|
||||
@@ -1419,7 +1541,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// including the wrap pad. Prior EmitAndWait calls have published every record.
|
||||
Wire::WireRecordLayout layout{};
|
||||
Wire::MGPipeWireRecordLayout(op, payload, layout);
|
||||
const Uint64 toEnd = m_cmd.Capacity() - m_cmd.LocalHead() % m_cmd.Capacity();
|
||||
const Uint64 toEnd = m_cmd->Capacity() - m_cmd->LocalHead() % m_cmd->Capacity();
|
||||
const Uint64 needed = layout.TotalBytes + (toEnd < layout.TotalBytes ? toEnd : 0);
|
||||
const BarrierWaitScope waiting;
|
||||
const auto wait = m_producer.WaitForCmdSpace(needed, kBarrierTimeoutMs);
|
||||
@@ -1434,7 +1556,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// here without draining is the other half of the flow-control deadlock: the server
|
||||
// stops applying on a full event ring, so retiredSeq stops moving, so this wait
|
||||
// never completes. One drain on the way out closes it.
|
||||
DrainEventRing(m_events);
|
||||
DrainEventRing(*m_events);
|
||||
seq = m_encoder.EncodeRecord(op, payload, payloadBytes, tails, tailCount);
|
||||
if (seq == Wire::kInvalidSeq) {
|
||||
SessionFail(MGFatalFamily::RingOverrun, "MGPipe: Fatal{RingOverrun, \"SEG_CMD\"} - %s refused after sufficient "
|
||||
@@ -1446,6 +1568,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// SessionProducer's one job and RingTest.cpp:446's pin. Notify-then-publish loses the
|
||||
// wakeup.
|
||||
m_producer.PublishAndNotify(seq);
|
||||
if (op == MG_Pipe::MGPWireOp::Present) Transport::LinkMetricsPresent();
|
||||
|
||||
// Does this row own a reply slot? kMGPipeCallFlags IS THE SINGLE SOURCE OF TRUTH
|
||||
// (R-16 / ID-31) - fourteen rows now, because the four Bool acceptance entry points
|
||||
@@ -1456,9 +1579,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// The catalogue still says which rows CARRY a slot; a caller that passes false says it
|
||||
// will not read this one (resource_subdata's buffer half, §2.5 - the only such caller).
|
||||
// The server posts either way, so the wire is unchanged and the row keeps its flag.
|
||||
const Bool rowCarriesReplySlot =
|
||||
(MG_Pipe::MGPipeCallFlagsFor(op) & static_cast<Uint32>(MG_Pipe::kReplySlot)) != 0;
|
||||
const Bool ownsReplySlot = rowCarriesReplySlot && wantReply;
|
||||
|
||||
|
||||
// ---- THE P5e WAIT RULE (CONTRACT-P5E §2.3) ---------------------------------------
|
||||
//
|
||||
@@ -1564,7 +1685,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
const BarrierWaitScope waiting;
|
||||
const Uint64 waitBudgetMs = AppliedWaitBudgetMs(op, payload);
|
||||
const Transport::SessionWait wait =
|
||||
WaitForAppliedBudget(m_producer, m_events, seq, waitBudgetMs);
|
||||
WaitForAppliedBudget(m_producer, *m_events, seq, waitBudgetMs);
|
||||
if (wait == Transport::SessionWait::ShutDown) {
|
||||
// `dl`: SHUTDOWN IS TWO DIFFERENT EVENTS AND ONLY ONE IS A DEVICE LOSS. Stop() kills
|
||||
// this same bell to wake a parked applier, so an orderly exit arrives here too - and
|
||||
@@ -1599,12 +1720,14 @@ namespace MobileGL::MG_Remote::Client {
|
||||
static_cast<unsigned long long>(waitBudgetMs));
|
||||
}
|
||||
|
||||
Transport::LinkMetricsReplyApplied(replyMetricStart);
|
||||
|
||||
// THE REVERSE CHANNEL IS DRAINED HERE, and here is the only place it can be: under the
|
||||
// barrier this is the one instant at which the apply thread is known not to be inside
|
||||
// the applier, and MGPipeClientOnGpuWritten / OnBufferWriteback write frontend objects.
|
||||
// It is what gives AwaitBufferWriteback something to have waited FOR: b1's third state
|
||||
// clears when the writeback lands, and the writeback lands on this ring.
|
||||
DrainEventRing(m_events);
|
||||
DrainEventRing(*m_events);
|
||||
|
||||
if (!ownsReplySlot) {
|
||||
// P5e (ra, §2.3). A barriered record's apply has returned and this thread is the
|
||||
@@ -1716,7 +1839,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
if (target == 0) return;
|
||||
const BarrierWaitScope waiting;
|
||||
const Transport::SessionWait wait =
|
||||
WaitForAppliedBudget(m_producer, m_events, target, kBarrierTimeoutMs);
|
||||
WaitForAppliedBudget(m_producer, *m_events, target, kBarrierTimeoutMs);
|
||||
if (wait == Transport::SessionWait::ShutDown) {
|
||||
// `dl`: the same two-events distinction the verb barrier makes. Only a peer that
|
||||
// HUNG UP is a device loss; Stop() reaches this path too.
|
||||
@@ -1741,7 +1864,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// on: the server blocks only on a full event ring, the client blocks only on
|
||||
// watermarks the server advances, and every client wait drains - so at most one side
|
||||
// is parked at any instant.
|
||||
DrainEventRing(m_events);
|
||||
DrainEventRing(*m_events);
|
||||
ReleaseFillPins();
|
||||
#else
|
||||
(void)why;
|
||||
@@ -1791,7 +1914,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
Transport::Watermark::Reached(control->Progress.presentAckSerial, awaited)) {
|
||||
break;
|
||||
}
|
||||
DrainEventRing(m_events);
|
||||
DrainEventRing(*m_events);
|
||||
}
|
||||
if (wait == Transport::SessionWait::TimedOut) {
|
||||
SessionFail(MGFatalFamily::PresentCreditTimeout, "MGPipe: Fatal{PresentCreditTimeout} - present %llu waited %u ms for "
|
||||
@@ -1803,7 +1926,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
}
|
||||
// ShutDown is teardown and returns: the present did not happen, the same answer
|
||||
// the barrier gives on a dead doorbell.
|
||||
if (wait == Transport::SessionWait::Reached) DrainEventRing(m_events);
|
||||
if (wait == Transport::SessionWait::Reached) DrainEventRing(*m_events);
|
||||
}
|
||||
m_presentsSent = serial;
|
||||
return serial;
|
||||
@@ -1935,9 +2058,9 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// drains them would otherwise hold the GL thread here for ever, and a frame backlog
|
||||
// deeper than this is a finding rather than a steady state.
|
||||
for (Uint32 guard = 0; guard < kMaxControlFramesPerPump; ++guard) {
|
||||
if (m_transport->PeekFrameSize() == 0) break;
|
||||
if ((m_controlInbox ? m_controlInbox->Peek() : m_transport->PeekFrameSize()) == 0) break;
|
||||
std::vector<Uint8> frame;
|
||||
if (ReceiveEnvelope(*m_transport, frame, 0) != MOBILEGL_OK) break;
|
||||
if (ReceiveControlFrame(frame, 0) != MOBILEGL_OK) break;
|
||||
const ::MobileGL::Wire::CtrlEnvelope* envelope = ParseEnvelope(frame);
|
||||
if (envelope == nullptr) {
|
||||
MGLOG_E("MG_Remote client: an unverifiable control frame (%llu bytes) was dropped",
|
||||
@@ -1965,6 +2088,22 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return adopted;
|
||||
}
|
||||
|
||||
MobileGLResult ClientSession::AttachStreamLink(int fd, const Transport::SessionSegmentSizes& sizes) {
|
||||
std::unique_ptr<Transport::ILink> link;
|
||||
const auto attached = Transport::CreateStreamLink(fd, sizes, Transport::TransportRoleTag::ClientProducer, link);
|
||||
if (attached != MOBILEGL_OK) return attached;
|
||||
AttachDataLink(std::move(link));
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
|
||||
void ClientSession::AttachDataLink(std::unique_ptr<Transport::ILink> link) {
|
||||
m_link = std::move(link);
|
||||
m_cmd = &m_link->CommandsOut(); m_events = &m_link->EventsIn();
|
||||
m_producer.SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get());
|
||||
m_encoder.SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get());
|
||||
m_events->SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get());
|
||||
}
|
||||
|
||||
Transport::SessionProducer& ClientSession::Producer() { return m_producer; }
|
||||
|
||||
Transport::SessionWait ClientSession::WaitForApplied(Uint64 seq, Uint32 timeoutMs) {
|
||||
@@ -1973,38 +2112,48 @@ namespace MobileGL::MG_Remote::Client {
|
||||
|
||||
Bool ClientSession::ReadReply(Uint64 seq, void* outBytes, Uint64 outCapacity, Int32* outStatus,
|
||||
Uint64* outSize) {
|
||||
return m_replies.Read(seq, outBytes, outCapacity, outStatus, outSize);
|
||||
const void* bytes = nullptr; Uint64 size = 0; Int32 status = 0;
|
||||
if (m_link->ReadReply(seq, &status, &bytes, &size) != MOBILEGL_OK) return false;
|
||||
if (outStatus) *outStatus = status;
|
||||
if (outSize) *outSize = size;
|
||||
if (size > outCapacity || (size && !outBytes)) return false;
|
||||
if (size) std::memcpy(outBytes, bytes, static_cast<SizeT>(size));
|
||||
return true;
|
||||
}
|
||||
|
||||
Uint32 ClientSession::MaxReplyBytes() const { return m_replies.MaxReplyBytes(); }
|
||||
Uint32 ClientSession::MaxReplyBytes() const { return static_cast<Uint32>(m_link->Capabilities().MaxReplyBytes); }
|
||||
|
||||
Bool ClientSession::ReplyCanHold(Uint64 bytes) const { return m_replies.CanHold(bytes); }
|
||||
Bool ClientSession::ReplyCanHold(Uint64 bytes) const { return bytes <= MaxReplyBytes(); }
|
||||
|
||||
// ID-47. Forwarded verbatim so that the message, the boundary and the abort are the pool's
|
||||
// and are pinned once, in SessionTest, rather than re-derived per caller.
|
||||
void ClientSession::RequireReadPixelsReplyFits(Uint32 width, Uint32 height, Uint32 format,
|
||||
Uint32 type, Uint64 bytes) const {
|
||||
m_replies.RequireReadPixelsFits(width, height, format, type, bytes);
|
||||
if (!ReplyCanHold(bytes)) {
|
||||
SessionFail(MGFatalFamily::ReplyTooLarge,
|
||||
"MGPipe: Fatal{ReplyTooLarge, \"ReadPixels %ux%u 0x%04X/0x%04X %llu > %u\"}",
|
||||
width, height, format, type, static_cast<unsigned long long>(bytes), MaxReplyBytes());
|
||||
}
|
||||
}
|
||||
|
||||
Transport::EventRingConsumer& ClientSession::Events() { return m_events; }
|
||||
Transport::EventRingConsumer& ClientSession::Events() { return *m_events; }
|
||||
|
||||
Uint32 ClientSession::DrainPublishedEvents() {
|
||||
// Not gated on m_started on purpose: PumpControlPlane's own pre-start call during
|
||||
// Start() has its ring-consumer twin here, and DrainEventRing's Valid() check is
|
||||
// the whole guard either case needs.
|
||||
return DrainEventRing(m_events);
|
||||
return DrainEventRing(*m_events);
|
||||
}
|
||||
|
||||
Uint64 ClientSession::EventRingCapacityBytes() const { return m_shm.EventRingCapacity(); }
|
||||
Uint64 ClientSession::EventRingCapacityBytes() const { return m_link->Memory().EventRingCapacity(); }
|
||||
|
||||
Uint64 ClientSession::StageCapacityBytes() const { return m_shm.StageBytes(); }
|
||||
Uint64 ClientSession::StageCapacityBytes() const { return m_link->Memory().StageBytes(); }
|
||||
|
||||
Wire::SegmentTable& ClientSession::Segments() { return m_segments; }
|
||||
|
||||
Transport::RingControl* ClientSession::Control() { return m_shm.CmdControl(); }
|
||||
Transport::RingControl* ClientSession::Control() { return m_link->Memory().CmdControl(); }
|
||||
|
||||
Transport::SessionSegments& ClientSession::Shm() { return m_shm; }
|
||||
Transport::SessionSegments& ClientSession::Shm() { return m_link->Memory(); }
|
||||
|
||||
Transport::ITransport* ClientSession::Control_Plane() { return m_transport; }
|
||||
|
||||
@@ -2071,3 +2220,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
#undef MGP5_C0_STUB
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Client
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) void MGPipeSyncPeerLog() {
|
||||
if (auto* session = MobileGL::MG_Remote::Client::ClientSession::Active()) session->SyncPeerLog();
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
// defect P4a paid two contract corrections for, and "always accept" is ID-39's 66 lost uploads.
|
||||
|
||||
#pragma once
|
||||
#include "../Transport/ILink.h"
|
||||
#include "../Transport/ControlInbox.h"
|
||||
#include <Includes.h>
|
||||
|
||||
#include <atomic> // the device-lost latch is read off the GL thread
|
||||
@@ -96,7 +98,13 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// the server states its own pid, which IS ours, and that is the honest answer there.
|
||||
std::uint32_t PeerServerPid() const { return m_peerServerPid; }
|
||||
|
||||
ClientSession();
|
||||
~ClientSession();
|
||||
MobileGLResult AttachStreamLink(int dataFd, const Transport::SessionSegmentSizes& sizes);
|
||||
void AttachDataLink(std::unique_ptr<Transport::ILink> link);
|
||||
Transport::ILink* DataLink() const { return m_link.get(); }
|
||||
bool ConnectDial() const { return m_connectDial; }
|
||||
void SyncPeerLog();
|
||||
|
||||
// Builds the four segments, performs Hello/Welcome, takes the first CapsSnapshot, and
|
||||
// - for TransportMode::InProcess - starts the server role's apply thread. Returns a
|
||||
@@ -407,6 +415,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
void LogWireLedger() const;
|
||||
|
||||
private:
|
||||
MobileGLResult ReceiveControlFrame(std::vector<Uint8>& frame, Uint32 timeoutMs);
|
||||
std::unique_ptr<Transport::ControlInbox> m_controlInbox;
|
||||
std::unique_ptr<Transport::ILink> m_link;
|
||||
bool m_connectDial = false;
|
||||
std::uint64_t m_logFlushSeq = 0;
|
||||
// P5e (ra): the latch, taken from PumpControlPlane after every adoption. Private
|
||||
// because "when may run-ahead start" is this class's own rule and not a caller's.
|
||||
void LatchRunAheadFromCaps();
|
||||
@@ -439,11 +452,9 @@ namespace MobileGL::MG_Remote::Client {
|
||||
|
||||
std::unique_ptr<Transport::InProcessTransport> m_clientTransport;
|
||||
std::unique_ptr<Transport::InProcessTransport> m_serverTransport;
|
||||
Transport::SessionSegments m_shm;
|
||||
Transport::RingProducer m_cmd;
|
||||
Transport::RingProducer* m_cmd = nullptr;
|
||||
Transport::SessionProducer m_producer;
|
||||
Transport::EventRingConsumer m_events;
|
||||
Transport::ReplySlotPool m_replies;
|
||||
Transport::EventRingConsumer* m_events = nullptr;
|
||||
Transport::ITransport* m_transport = nullptr;
|
||||
Bool m_started = false;
|
||||
// Written by whichever thread first notices the hangup - the GL thread in the barrier,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// P6.5 handshake policy, shared by both roles. Rejections are returned, never aborted.
|
||||
#pragma once
|
||||
#include "CapsCodec.h"
|
||||
#include "Protocol/generated/protocol_generated.h"
|
||||
#include "Transport/ITransport.h"
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace MobileGL::MG_Remote {
|
||||
inline MobileGLResult RefuseHandshake(Transport::ITransport& transport,
|
||||
::MobileGL::Wire::RefuseCode code,
|
||||
const char* detail, Uint64 expected = 0,
|
||||
Uint64 actual = 0, const char* peerValue = nullptr) {
|
||||
MGLOG_E("MGPipe: Refuse{%s} %s expected=%llu actual=%llu peer=%s",
|
||||
::MobileGL::Wire::EnumNameRefuseCode(code), detail,
|
||||
static_cast<unsigned long long>(expected), static_cast<unsigned long long>(actual),
|
||||
peerValue == nullptr ? "" : peerValue);
|
||||
::flatbuffers::FlatBufferBuilder builder(256);
|
||||
auto refusal = ::MobileGL::Wire::CreateRefuseDirect(builder, code, detail, expected, actual, peerValue);
|
||||
auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Refuse,
|
||||
refusal.Union());
|
||||
::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope);
|
||||
transport.SendFrame({builder.GetBufferPointer(), builder.GetSize()});
|
||||
return MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
}
|
||||
|
||||
inline bool LogPeerRefusal(const ::MobileGL::Wire::CtrlEnvelope* envelope) {
|
||||
if (envelope == nullptr || envelope->msg_type() != ::MobileGL::Wire::CtrlMsg::Refuse) return false;
|
||||
const auto* refusal = envelope->msg_as_Refuse();
|
||||
if (refusal == nullptr) return false;
|
||||
MGLOG_E("MGPipe: peer Refuse{%s} %s expected=%llu actual=%llu",
|
||||
::MobileGL::Wire::EnumNameRefuseCode(refusal->code()),
|
||||
refusal->detail() == nullptr ? "" : refusal->detail()->c_str(),
|
||||
static_cast<unsigned long long>(refusal->expected()),
|
||||
static_cast<unsigned long long>(refusal->actual()));
|
||||
return true;
|
||||
}
|
||||
|
||||
inline MobileGLResult ValidatePeerHandshake(Transport::ITransport& transport,
|
||||
Uint32 major, Uint32 minor, Uint64 fingerprint,
|
||||
const char* stamp, ::MobileGL::Wire::DialMode dial) {
|
||||
using ::MobileGL::Wire::RefuseCode;
|
||||
if (major != MOBILEGL_PROTOCOL_ABI_MAJOR || minor != MOBILEGL_PROTOCOL_ABI_MINOR)
|
||||
return RefuseHandshake(transport, RefuseCode::ProtocolVersion, "protocol version",
|
||||
MOBILEGL_ABI_VERSION(MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR),
|
||||
MOBILEGL_ABI_VERSION(major, minor));
|
||||
if (fingerprint != WireFingerprint())
|
||||
return RefuseHandshake(transport, RefuseCode::WireFingerprint, "wire layout",
|
||||
WireFingerprint(), fingerprint);
|
||||
const bool sameBuild = BuildFingerprintPresent() && stamp != nullptr && stamp[0] != '\0' &&
|
||||
std::strcmp(stamp, BuildFingerprint()) == 0;
|
||||
const char* required = std::getenv("MOBILEGL_IPC_REQUIRE_SAME_BUILD");
|
||||
const bool requireSame = dial == ::MobileGL::Wire::DialMode::Fork ||
|
||||
(required != nullptr && std::strcmp(required, "1") == 0);
|
||||
if (!sameBuild && requireSame)
|
||||
return RefuseHandshake(transport, RefuseCode::BuildFingerprint, "build identity", 0, 0,
|
||||
stamp == nullptr ? "<missing>" : stamp);
|
||||
if (!sameBuild && dial == ::MobileGL::Wire::DialMode::Connect)
|
||||
MGLOG_W("MGPipe: compatible wire with different build: local=%s peer=%s",
|
||||
BuildFingerprint(), stamp == nullptr ? "<missing>" : stamp);
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
} // namespace MobileGL::MG_Remote
|
||||
@@ -190,7 +190,7 @@ namespace MobileGL::MG_Remote {
|
||||
void EncodeSurfaceReplyFrame(const SurfaceControlFrame& frame,
|
||||
flatbuffers::FlatBufferBuilder* builder) {
|
||||
const auto reply = ::MobileGL::Wire::CreateSurfaceReply(*builder, frame.seq, frame.ok, frame.eglMajor,
|
||||
frame.eglMinor, /*defaultFb=*/0);
|
||||
frame.eglMinor, /*defaultFb=*/0, frame.eventHead);
|
||||
const auto envelope =
|
||||
::MobileGL::Wire::CreateCtrlEnvelope(*builder, ::MobileGL::Wire::CtrlMsg::SurfaceReply, reply.Union());
|
||||
::MobileGL::Wire::FinishCtrlEnvelopeBuffer(*builder, envelope);
|
||||
@@ -201,6 +201,7 @@ namespace MobileGL::MG_Remote {
|
||||
frame->ok = reply.ok();
|
||||
frame->eglMajor = reply.eglMajor();
|
||||
frame->eglMinor = reply.eglMinor();
|
||||
frame->eventHead = reply.eventHead();
|
||||
}
|
||||
|
||||
MobileGLResult ServerApplyWireSurfaceOp(const ::MobileGL::Wire::SurfaceOp& op, SurfaceControlFrame* replyOut) {
|
||||
|
||||
@@ -32,6 +32,15 @@ namespace Wire {
|
||||
struct SegmentRef;
|
||||
struct SegmentRefBuilder;
|
||||
|
||||
struct LinkTerms;
|
||||
struct LinkTermsBuilder;
|
||||
|
||||
struct Refuse;
|
||||
struct RefuseBuilder;
|
||||
|
||||
struct LogFlush;
|
||||
struct LogFlushBuilder;
|
||||
|
||||
struct Hello;
|
||||
struct HelloBuilder;
|
||||
|
||||
@@ -113,6 +122,147 @@ inline const char *EnumNameSegmentKind(SegmentKind e) {
|
||||
return EnumNamesSegmentKind()[index];
|
||||
}
|
||||
|
||||
enum class DataPlane : uint8_t {
|
||||
SharedSegments = 0,
|
||||
Stream = 1,
|
||||
MIN = SharedSegments,
|
||||
MAX = Stream
|
||||
};
|
||||
|
||||
inline const DataPlane (&EnumValuesDataPlane())[2] {
|
||||
static const DataPlane values[] = {
|
||||
DataPlane::SharedSegments,
|
||||
DataPlane::Stream
|
||||
};
|
||||
return values;
|
||||
}
|
||||
|
||||
inline const char * const *EnumNamesDataPlane() {
|
||||
static const char * const names[3] = {
|
||||
"SharedSegments",
|
||||
"Stream",
|
||||
nullptr
|
||||
};
|
||||
return names;
|
||||
}
|
||||
|
||||
inline const char *EnumNameDataPlane(DataPlane e) {
|
||||
if (::flatbuffers::IsOutRange(e, DataPlane::SharedSegments, DataPlane::Stream)) return "";
|
||||
const size_t index = static_cast<size_t>(e);
|
||||
return EnumNamesDataPlane()[index];
|
||||
}
|
||||
|
||||
enum class WireForm : uint8_t {
|
||||
StructImage = 0,
|
||||
MIN = StructImage,
|
||||
MAX = StructImage
|
||||
};
|
||||
|
||||
inline const WireForm (&EnumValuesWireForm())[1] {
|
||||
static const WireForm values[] = {
|
||||
WireForm::StructImage
|
||||
};
|
||||
return values;
|
||||
}
|
||||
|
||||
inline const char * const *EnumNamesWireForm() {
|
||||
static const char * const names[2] = {
|
||||
"StructImage",
|
||||
nullptr
|
||||
};
|
||||
return names;
|
||||
}
|
||||
|
||||
inline const char *EnumNameWireForm(WireForm e) {
|
||||
if (::flatbuffers::IsOutRange(e, WireForm::StructImage, WireForm::StructImage)) return "";
|
||||
const size_t index = static_cast<size_t>(e);
|
||||
return EnumNamesWireForm()[index];
|
||||
}
|
||||
|
||||
enum class DialMode : uint8_t {
|
||||
No = 0,
|
||||
Fork = 1,
|
||||
Connect = 2,
|
||||
MIN = No,
|
||||
MAX = Connect
|
||||
};
|
||||
|
||||
inline const DialMode (&EnumValuesDialMode())[3] {
|
||||
static const DialMode values[] = {
|
||||
DialMode::No,
|
||||
DialMode::Fork,
|
||||
DialMode::Connect
|
||||
};
|
||||
return values;
|
||||
}
|
||||
|
||||
inline const char * const *EnumNamesDialMode() {
|
||||
static const char * const names[4] = {
|
||||
"No",
|
||||
"Fork",
|
||||
"Connect",
|
||||
nullptr
|
||||
};
|
||||
return names;
|
||||
}
|
||||
|
||||
inline const char *EnumNameDialMode(DialMode e) {
|
||||
if (::flatbuffers::IsOutRange(e, DialMode::No, DialMode::Connect)) return "";
|
||||
const size_t index = static_cast<size_t>(e);
|
||||
return EnumNamesDialMode()[index];
|
||||
}
|
||||
|
||||
enum class RefuseCode : uint32_t {
|
||||
None = 0,
|
||||
ProtocolVersion = 1,
|
||||
WireFingerprint = 2,
|
||||
BuildFingerprint = 3,
|
||||
Authentication = 4,
|
||||
LinkTerms = 5,
|
||||
Backend = 6,
|
||||
Busy = 7,
|
||||
MalformedHello = 8,
|
||||
MIN = None,
|
||||
MAX = MalformedHello
|
||||
};
|
||||
|
||||
inline const RefuseCode (&EnumValuesRefuseCode())[9] {
|
||||
static const RefuseCode values[] = {
|
||||
RefuseCode::None,
|
||||
RefuseCode::ProtocolVersion,
|
||||
RefuseCode::WireFingerprint,
|
||||
RefuseCode::BuildFingerprint,
|
||||
RefuseCode::Authentication,
|
||||
RefuseCode::LinkTerms,
|
||||
RefuseCode::Backend,
|
||||
RefuseCode::Busy,
|
||||
RefuseCode::MalformedHello
|
||||
};
|
||||
return values;
|
||||
}
|
||||
|
||||
inline const char * const *EnumNamesRefuseCode() {
|
||||
static const char * const names[10] = {
|
||||
"None",
|
||||
"ProtocolVersion",
|
||||
"WireFingerprint",
|
||||
"BuildFingerprint",
|
||||
"Authentication",
|
||||
"LinkTerms",
|
||||
"Backend",
|
||||
"Busy",
|
||||
"MalformedHello",
|
||||
nullptr
|
||||
};
|
||||
return names;
|
||||
}
|
||||
|
||||
inline const char *EnumNameRefuseCode(RefuseCode e) {
|
||||
if (::flatbuffers::IsOutRange(e, RefuseCode::None, RefuseCode::MalformedHello)) return "";
|
||||
const size_t index = static_cast<size_t>(e);
|
||||
return EnumNamesRefuseCode()[index];
|
||||
}
|
||||
|
||||
enum class SurfaceOpKind : uint8_t {
|
||||
None = 0,
|
||||
InitializeDisplay = 1,
|
||||
@@ -350,11 +500,13 @@ enum class CtrlMsg : uint8_t {
|
||||
AuxRequest = 8,
|
||||
Fatal = 9,
|
||||
LogLine = 10,
|
||||
Refuse = 11,
|
||||
LogFlush = 12,
|
||||
MIN = NONE,
|
||||
MAX = LogLine
|
||||
MAX = LogFlush
|
||||
};
|
||||
|
||||
inline const CtrlMsg (&EnumValuesCtrlMsg())[11] {
|
||||
inline const CtrlMsg (&EnumValuesCtrlMsg())[13] {
|
||||
static const CtrlMsg values[] = {
|
||||
CtrlMsg::NONE,
|
||||
CtrlMsg::Hello,
|
||||
@@ -366,13 +518,15 @@ inline const CtrlMsg (&EnumValuesCtrlMsg())[11] {
|
||||
CtrlMsg::ResyncDone,
|
||||
CtrlMsg::AuxRequest,
|
||||
CtrlMsg::Fatal,
|
||||
CtrlMsg::LogLine
|
||||
CtrlMsg::LogLine,
|
||||
CtrlMsg::Refuse,
|
||||
CtrlMsg::LogFlush
|
||||
};
|
||||
return values;
|
||||
}
|
||||
|
||||
inline const char * const *EnumNamesCtrlMsg() {
|
||||
static const char * const names[12] = {
|
||||
static const char * const names[14] = {
|
||||
"NONE",
|
||||
"Hello",
|
||||
"Welcome",
|
||||
@@ -384,13 +538,15 @@ inline const char * const *EnumNamesCtrlMsg() {
|
||||
"AuxRequest",
|
||||
"Fatal",
|
||||
"LogLine",
|
||||
"Refuse",
|
||||
"LogFlush",
|
||||
nullptr
|
||||
};
|
||||
return names;
|
||||
}
|
||||
|
||||
inline const char *EnumNameCtrlMsg(CtrlMsg e) {
|
||||
if (::flatbuffers::IsOutRange(e, CtrlMsg::NONE, CtrlMsg::LogLine)) return "";
|
||||
if (::flatbuffers::IsOutRange(e, CtrlMsg::NONE, CtrlMsg::LogFlush)) return "";
|
||||
const size_t index = static_cast<size_t>(e);
|
||||
return EnumNamesCtrlMsg()[index];
|
||||
}
|
||||
@@ -439,6 +595,14 @@ template<> struct CtrlMsgTraits<MobileGL::Wire::LogLine> {
|
||||
static const CtrlMsg enum_value = CtrlMsg::LogLine;
|
||||
};
|
||||
|
||||
template<> struct CtrlMsgTraits<MobileGL::Wire::Refuse> {
|
||||
static const CtrlMsg enum_value = CtrlMsg::Refuse;
|
||||
};
|
||||
|
||||
template<> struct CtrlMsgTraits<MobileGL::Wire::LogFlush> {
|
||||
static const CtrlMsg enum_value = CtrlMsg::LogFlush;
|
||||
};
|
||||
|
||||
template <bool B = false>
|
||||
bool VerifyCtrlMsg(::flatbuffers::VerifierTemplate<B> &verifier, const void *obj, CtrlMsg type);
|
||||
template <bool B = false>
|
||||
@@ -538,6 +702,270 @@ inline ::flatbuffers::Offset<SegmentRef> CreateSegmentRefDirect(
|
||||
name__);
|
||||
}
|
||||
|
||||
struct LinkTerms FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
typedef LinkTermsBuilder Builder;
|
||||
struct Traits;
|
||||
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
|
||||
VT_DATAPLANE = 4,
|
||||
VT_WIREFORM = 6,
|
||||
VT_MAXREPLYBYTES = 8,
|
||||
VT_CMDWINDOWBYTES = 10,
|
||||
VT_STAGEWINDOWBYTES = 12,
|
||||
VT_EVENTWINDOWBYTES = 14
|
||||
};
|
||||
MobileGL::Wire::DataPlane dataPlane() const {
|
||||
return static_cast<MobileGL::Wire::DataPlane>(GetField<uint8_t>(VT_DATAPLANE, 0));
|
||||
}
|
||||
MobileGL::Wire::WireForm wireForm() const {
|
||||
return static_cast<MobileGL::Wire::WireForm>(GetField<uint8_t>(VT_WIREFORM, 0));
|
||||
}
|
||||
uint64_t maxReplyBytes() const {
|
||||
return GetField<uint64_t>(VT_MAXREPLYBYTES, 0);
|
||||
}
|
||||
uint64_t cmdWindowBytes() const {
|
||||
return GetField<uint64_t>(VT_CMDWINDOWBYTES, 0);
|
||||
}
|
||||
uint64_t stageWindowBytes() const {
|
||||
return GetField<uint64_t>(VT_STAGEWINDOWBYTES, 0);
|
||||
}
|
||||
uint64_t eventWindowBytes() const {
|
||||
return GetField<uint64_t>(VT_EVENTWINDOWBYTES, 0);
|
||||
}
|
||||
template <bool B = false>
|
||||
bool Verify(::flatbuffers::VerifierTemplate<B> &verifier) const {
|
||||
return VerifyTableStart(verifier) &&
|
||||
VerifyField<uint8_t>(verifier, VT_DATAPLANE, 1) &&
|
||||
VerifyField<uint8_t>(verifier, VT_WIREFORM, 1) &&
|
||||
VerifyField<uint64_t>(verifier, VT_MAXREPLYBYTES, 8) &&
|
||||
VerifyField<uint64_t>(verifier, VT_CMDWINDOWBYTES, 8) &&
|
||||
VerifyField<uint64_t>(verifier, VT_STAGEWINDOWBYTES, 8) &&
|
||||
VerifyField<uint64_t>(verifier, VT_EVENTWINDOWBYTES, 8) &&
|
||||
verifier.EndTable();
|
||||
}
|
||||
};
|
||||
|
||||
struct LinkTermsBuilder {
|
||||
typedef LinkTerms Table;
|
||||
::flatbuffers::FlatBufferBuilder &fbb_;
|
||||
::flatbuffers::uoffset_t start_;
|
||||
void add_dataPlane(MobileGL::Wire::DataPlane dataPlane) {
|
||||
fbb_.AddElement<uint8_t>(LinkTerms::VT_DATAPLANE, static_cast<uint8_t>(dataPlane), 0);
|
||||
}
|
||||
void add_wireForm(MobileGL::Wire::WireForm wireForm) {
|
||||
fbb_.AddElement<uint8_t>(LinkTerms::VT_WIREFORM, static_cast<uint8_t>(wireForm), 0);
|
||||
}
|
||||
void add_maxReplyBytes(uint64_t maxReplyBytes) {
|
||||
fbb_.AddElement<uint64_t>(LinkTerms::VT_MAXREPLYBYTES, maxReplyBytes, 0);
|
||||
}
|
||||
void add_cmdWindowBytes(uint64_t cmdWindowBytes) {
|
||||
fbb_.AddElement<uint64_t>(LinkTerms::VT_CMDWINDOWBYTES, cmdWindowBytes, 0);
|
||||
}
|
||||
void add_stageWindowBytes(uint64_t stageWindowBytes) {
|
||||
fbb_.AddElement<uint64_t>(LinkTerms::VT_STAGEWINDOWBYTES, stageWindowBytes, 0);
|
||||
}
|
||||
void add_eventWindowBytes(uint64_t eventWindowBytes) {
|
||||
fbb_.AddElement<uint64_t>(LinkTerms::VT_EVENTWINDOWBYTES, eventWindowBytes, 0);
|
||||
}
|
||||
explicit LinkTermsBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
|
||||
: fbb_(_fbb) {
|
||||
start_ = fbb_.StartTable();
|
||||
}
|
||||
::flatbuffers::Offset<LinkTerms> Finish() {
|
||||
const auto end = fbb_.EndTable(start_);
|
||||
auto o = ::flatbuffers::Offset<LinkTerms>(end);
|
||||
return o;
|
||||
}
|
||||
};
|
||||
|
||||
inline ::flatbuffers::Offset<LinkTerms> CreateLinkTerms(
|
||||
::flatbuffers::FlatBufferBuilder &_fbb,
|
||||
MobileGL::Wire::DataPlane dataPlane = MobileGL::Wire::DataPlane::SharedSegments,
|
||||
MobileGL::Wire::WireForm wireForm = MobileGL::Wire::WireForm::StructImage,
|
||||
uint64_t maxReplyBytes = 0,
|
||||
uint64_t cmdWindowBytes = 0,
|
||||
uint64_t stageWindowBytes = 0,
|
||||
uint64_t eventWindowBytes = 0) {
|
||||
LinkTermsBuilder builder_(_fbb);
|
||||
builder_.add_eventWindowBytes(eventWindowBytes);
|
||||
builder_.add_stageWindowBytes(stageWindowBytes);
|
||||
builder_.add_cmdWindowBytes(cmdWindowBytes);
|
||||
builder_.add_maxReplyBytes(maxReplyBytes);
|
||||
builder_.add_wireForm(wireForm);
|
||||
builder_.add_dataPlane(dataPlane);
|
||||
return builder_.Finish();
|
||||
}
|
||||
|
||||
struct LinkTerms::Traits {
|
||||
using type = LinkTerms;
|
||||
static auto constexpr Create = CreateLinkTerms;
|
||||
};
|
||||
|
||||
struct Refuse FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
typedef RefuseBuilder Builder;
|
||||
struct Traits;
|
||||
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
|
||||
VT_CODE = 4,
|
||||
VT_DETAIL = 6,
|
||||
VT_EXPECTED = 8,
|
||||
VT_ACTUAL = 10,
|
||||
VT_PEERVALUE = 12
|
||||
};
|
||||
MobileGL::Wire::RefuseCode code() const {
|
||||
return static_cast<MobileGL::Wire::RefuseCode>(GetField<uint32_t>(VT_CODE, 0));
|
||||
}
|
||||
const ::flatbuffers::String *detail() const {
|
||||
return GetPointer<const ::flatbuffers::String *>(VT_DETAIL);
|
||||
}
|
||||
uint64_t expected() const {
|
||||
return GetField<uint64_t>(VT_EXPECTED, 0);
|
||||
}
|
||||
uint64_t actual() const {
|
||||
return GetField<uint64_t>(VT_ACTUAL, 0);
|
||||
}
|
||||
const ::flatbuffers::String *peerValue() const {
|
||||
return GetPointer<const ::flatbuffers::String *>(VT_PEERVALUE);
|
||||
}
|
||||
template <bool B = false>
|
||||
bool Verify(::flatbuffers::VerifierTemplate<B> &verifier) const {
|
||||
return VerifyTableStart(verifier) &&
|
||||
VerifyField<uint32_t>(verifier, VT_CODE, 4) &&
|
||||
VerifyOffset(verifier, VT_DETAIL) &&
|
||||
verifier.VerifyString(detail()) &&
|
||||
VerifyField<uint64_t>(verifier, VT_EXPECTED, 8) &&
|
||||
VerifyField<uint64_t>(verifier, VT_ACTUAL, 8) &&
|
||||
VerifyOffset(verifier, VT_PEERVALUE) &&
|
||||
verifier.VerifyString(peerValue()) &&
|
||||
verifier.EndTable();
|
||||
}
|
||||
};
|
||||
|
||||
struct RefuseBuilder {
|
||||
typedef Refuse Table;
|
||||
::flatbuffers::FlatBufferBuilder &fbb_;
|
||||
::flatbuffers::uoffset_t start_;
|
||||
void add_code(MobileGL::Wire::RefuseCode code) {
|
||||
fbb_.AddElement<uint32_t>(Refuse::VT_CODE, static_cast<uint32_t>(code), 0);
|
||||
}
|
||||
void add_detail(::flatbuffers::Offset<::flatbuffers::String> detail) {
|
||||
fbb_.AddOffset(Refuse::VT_DETAIL, detail);
|
||||
}
|
||||
void add_expected(uint64_t expected) {
|
||||
fbb_.AddElement<uint64_t>(Refuse::VT_EXPECTED, expected, 0);
|
||||
}
|
||||
void add_actual(uint64_t actual) {
|
||||
fbb_.AddElement<uint64_t>(Refuse::VT_ACTUAL, actual, 0);
|
||||
}
|
||||
void add_peerValue(::flatbuffers::Offset<::flatbuffers::String> peerValue) {
|
||||
fbb_.AddOffset(Refuse::VT_PEERVALUE, peerValue);
|
||||
}
|
||||
explicit RefuseBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
|
||||
: fbb_(_fbb) {
|
||||
start_ = fbb_.StartTable();
|
||||
}
|
||||
::flatbuffers::Offset<Refuse> Finish() {
|
||||
const auto end = fbb_.EndTable(start_);
|
||||
auto o = ::flatbuffers::Offset<Refuse>(end);
|
||||
return o;
|
||||
}
|
||||
};
|
||||
|
||||
inline ::flatbuffers::Offset<Refuse> CreateRefuse(
|
||||
::flatbuffers::FlatBufferBuilder &_fbb,
|
||||
MobileGL::Wire::RefuseCode code = MobileGL::Wire::RefuseCode::None,
|
||||
::flatbuffers::Offset<::flatbuffers::String> detail = 0,
|
||||
uint64_t expected = 0,
|
||||
uint64_t actual = 0,
|
||||
::flatbuffers::Offset<::flatbuffers::String> peerValue = 0) {
|
||||
RefuseBuilder builder_(_fbb);
|
||||
builder_.add_actual(actual);
|
||||
builder_.add_expected(expected);
|
||||
builder_.add_peerValue(peerValue);
|
||||
builder_.add_detail(detail);
|
||||
builder_.add_code(code);
|
||||
return builder_.Finish();
|
||||
}
|
||||
|
||||
struct Refuse::Traits {
|
||||
using type = Refuse;
|
||||
static auto constexpr Create = CreateRefuse;
|
||||
};
|
||||
|
||||
inline ::flatbuffers::Offset<Refuse> CreateRefuseDirect(
|
||||
::flatbuffers::FlatBufferBuilder &_fbb,
|
||||
MobileGL::Wire::RefuseCode code = MobileGL::Wire::RefuseCode::None,
|
||||
const char *detail = nullptr,
|
||||
uint64_t expected = 0,
|
||||
uint64_t actual = 0,
|
||||
const char *peerValue = nullptr) {
|
||||
auto detail__ = detail ? _fbb.CreateString(detail) : 0;
|
||||
auto peerValue__ = peerValue ? _fbb.CreateString(peerValue) : 0;
|
||||
return MobileGL::Wire::CreateRefuse(
|
||||
_fbb,
|
||||
code,
|
||||
detail__,
|
||||
expected,
|
||||
actual,
|
||||
peerValue__);
|
||||
}
|
||||
|
||||
struct LogFlush FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
typedef LogFlushBuilder Builder;
|
||||
struct Traits;
|
||||
enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
|
||||
VT_SEQ = 4,
|
||||
VT_ACK = 6
|
||||
};
|
||||
uint64_t seq() const {
|
||||
return GetField<uint64_t>(VT_SEQ, 0);
|
||||
}
|
||||
bool ack() const {
|
||||
return GetField<uint8_t>(VT_ACK, 0) != 0;
|
||||
}
|
||||
template <bool B = false>
|
||||
bool Verify(::flatbuffers::VerifierTemplate<B> &verifier) const {
|
||||
return VerifyTableStart(verifier) &&
|
||||
VerifyField<uint64_t>(verifier, VT_SEQ, 8) &&
|
||||
VerifyField<uint8_t>(verifier, VT_ACK, 1) &&
|
||||
verifier.EndTable();
|
||||
}
|
||||
};
|
||||
|
||||
struct LogFlushBuilder {
|
||||
typedef LogFlush Table;
|
||||
::flatbuffers::FlatBufferBuilder &fbb_;
|
||||
::flatbuffers::uoffset_t start_;
|
||||
void add_seq(uint64_t seq) {
|
||||
fbb_.AddElement<uint64_t>(LogFlush::VT_SEQ, seq, 0);
|
||||
}
|
||||
void add_ack(bool ack) {
|
||||
fbb_.AddElement<uint8_t>(LogFlush::VT_ACK, static_cast<uint8_t>(ack), 0);
|
||||
}
|
||||
explicit LogFlushBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
|
||||
: fbb_(_fbb) {
|
||||
start_ = fbb_.StartTable();
|
||||
}
|
||||
::flatbuffers::Offset<LogFlush> Finish() {
|
||||
const auto end = fbb_.EndTable(start_);
|
||||
auto o = ::flatbuffers::Offset<LogFlush>(end);
|
||||
return o;
|
||||
}
|
||||
};
|
||||
|
||||
inline ::flatbuffers::Offset<LogFlush> CreateLogFlush(
|
||||
::flatbuffers::FlatBufferBuilder &_fbb,
|
||||
uint64_t seq = 0,
|
||||
bool ack = false) {
|
||||
LogFlushBuilder builder_(_fbb);
|
||||
builder_.add_seq(seq);
|
||||
builder_.add_ack(ack);
|
||||
return builder_.Finish();
|
||||
}
|
||||
|
||||
struct LogFlush::Traits {
|
||||
using type = LogFlush;
|
||||
static auto constexpr Create = CreateLogFlush;
|
||||
};
|
||||
|
||||
struct Hello FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
typedef HelloBuilder Builder;
|
||||
struct Traits;
|
||||
@@ -548,7 +976,11 @@ struct Hello FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
VT_BACKENDTYPE = 10,
|
||||
VT_PID = 12,
|
||||
VT_CONFIGBLOB = 14,
|
||||
VT_ABIFINGERPRINT = 16
|
||||
VT_ABIFINGERPRINT = 16,
|
||||
VT_WIREFINGERPRINT = 18,
|
||||
VT_LINKTERMS = 20,
|
||||
VT_TOKEN = 22,
|
||||
VT_DIALMODE = 24
|
||||
};
|
||||
uint32_t abiMajor() const {
|
||||
return GetField<uint32_t>(VT_ABIMAJOR, 0);
|
||||
@@ -571,6 +1003,18 @@ struct Hello FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
uint64_t abiFingerprint() const {
|
||||
return GetField<uint64_t>(VT_ABIFINGERPRINT, 0);
|
||||
}
|
||||
uint64_t wireFingerprint() const {
|
||||
return GetField<uint64_t>(VT_WIREFINGERPRINT, 0);
|
||||
}
|
||||
const MobileGL::Wire::LinkTerms *linkTerms() const {
|
||||
return GetPointer<const MobileGL::Wire::LinkTerms *>(VT_LINKTERMS);
|
||||
}
|
||||
const ::flatbuffers::String *token() const {
|
||||
return GetPointer<const ::flatbuffers::String *>(VT_TOKEN);
|
||||
}
|
||||
MobileGL::Wire::DialMode dialMode() const {
|
||||
return static_cast<MobileGL::Wire::DialMode>(GetField<uint8_t>(VT_DIALMODE, 0));
|
||||
}
|
||||
template <bool B = false>
|
||||
bool Verify(::flatbuffers::VerifierTemplate<B> &verifier) const {
|
||||
return VerifyTableStart(verifier) &&
|
||||
@@ -583,6 +1027,12 @@ struct Hello FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
VerifyOffset(verifier, VT_CONFIGBLOB) &&
|
||||
verifier.VerifyVector(configBlob()) &&
|
||||
VerifyField<uint64_t>(verifier, VT_ABIFINGERPRINT, 8) &&
|
||||
VerifyField<uint64_t>(verifier, VT_WIREFINGERPRINT, 8) &&
|
||||
VerifyOffset(verifier, VT_LINKTERMS) &&
|
||||
verifier.VerifyTable(linkTerms()) &&
|
||||
VerifyOffset(verifier, VT_TOKEN) &&
|
||||
verifier.VerifyString(token()) &&
|
||||
VerifyField<uint8_t>(verifier, VT_DIALMODE, 1) &&
|
||||
verifier.EndTable();
|
||||
}
|
||||
};
|
||||
@@ -612,6 +1062,18 @@ struct HelloBuilder {
|
||||
void add_abiFingerprint(uint64_t abiFingerprint) {
|
||||
fbb_.AddElement<uint64_t>(Hello::VT_ABIFINGERPRINT, abiFingerprint, 0);
|
||||
}
|
||||
void add_wireFingerprint(uint64_t wireFingerprint) {
|
||||
fbb_.AddElement<uint64_t>(Hello::VT_WIREFINGERPRINT, wireFingerprint, 0);
|
||||
}
|
||||
void add_linkTerms(::flatbuffers::Offset<MobileGL::Wire::LinkTerms> linkTerms) {
|
||||
fbb_.AddOffset(Hello::VT_LINKTERMS, linkTerms);
|
||||
}
|
||||
void add_token(::flatbuffers::Offset<::flatbuffers::String> token) {
|
||||
fbb_.AddOffset(Hello::VT_TOKEN, token);
|
||||
}
|
||||
void add_dialMode(MobileGL::Wire::DialMode dialMode) {
|
||||
fbb_.AddElement<uint8_t>(Hello::VT_DIALMODE, static_cast<uint8_t>(dialMode), 0);
|
||||
}
|
||||
explicit HelloBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
|
||||
: fbb_(_fbb) {
|
||||
start_ = fbb_.StartTable();
|
||||
@@ -631,15 +1093,23 @@ inline ::flatbuffers::Offset<Hello> CreateHello(
|
||||
uint32_t backendType = 0,
|
||||
uint32_t pid = 0,
|
||||
::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> configBlob = 0,
|
||||
uint64_t abiFingerprint = 0) {
|
||||
uint64_t abiFingerprint = 0,
|
||||
uint64_t wireFingerprint = 0,
|
||||
::flatbuffers::Offset<MobileGL::Wire::LinkTerms> linkTerms = 0,
|
||||
::flatbuffers::Offset<::flatbuffers::String> token = 0,
|
||||
MobileGL::Wire::DialMode dialMode = MobileGL::Wire::DialMode::No) {
|
||||
HelloBuilder builder_(_fbb);
|
||||
builder_.add_wireFingerprint(wireFingerprint);
|
||||
builder_.add_abiFingerprint(abiFingerprint);
|
||||
builder_.add_token(token);
|
||||
builder_.add_linkTerms(linkTerms);
|
||||
builder_.add_configBlob(configBlob);
|
||||
builder_.add_pid(pid);
|
||||
builder_.add_backendType(backendType);
|
||||
builder_.add_buildFingerprint(buildFingerprint);
|
||||
builder_.add_abiMinor(abiMinor);
|
||||
builder_.add_abiMajor(abiMajor);
|
||||
builder_.add_dialMode(dialMode);
|
||||
return builder_.Finish();
|
||||
}
|
||||
|
||||
@@ -656,9 +1126,14 @@ inline ::flatbuffers::Offset<Hello> CreateHelloDirect(
|
||||
uint32_t backendType = 0,
|
||||
uint32_t pid = 0,
|
||||
const std::vector<uint8_t> *configBlob = nullptr,
|
||||
uint64_t abiFingerprint = 0) {
|
||||
uint64_t abiFingerprint = 0,
|
||||
uint64_t wireFingerprint = 0,
|
||||
::flatbuffers::Offset<MobileGL::Wire::LinkTerms> linkTerms = 0,
|
||||
const char *token = nullptr,
|
||||
MobileGL::Wire::DialMode dialMode = MobileGL::Wire::DialMode::No) {
|
||||
auto buildFingerprint__ = buildFingerprint ? _fbb.CreateString(buildFingerprint) : 0;
|
||||
auto configBlob__ = configBlob ? _fbb.CreateVector<uint8_t>(*configBlob) : 0;
|
||||
auto token__ = token ? _fbb.CreateString(token) : 0;
|
||||
return MobileGL::Wire::CreateHello(
|
||||
_fbb,
|
||||
abiMajor,
|
||||
@@ -667,7 +1142,11 @@ inline ::flatbuffers::Offset<Hello> CreateHelloDirect(
|
||||
backendType,
|
||||
pid,
|
||||
configBlob__,
|
||||
abiFingerprint);
|
||||
abiFingerprint,
|
||||
wireFingerprint,
|
||||
linkTerms,
|
||||
token__,
|
||||
dialMode);
|
||||
}
|
||||
|
||||
struct Welcome FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
@@ -682,7 +1161,10 @@ struct Welcome FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
VT_REPLYPOOL = 14,
|
||||
VT_EVENTRING = 16,
|
||||
VT_BUILDFINGERPRINT = 18,
|
||||
VT_ABIFINGERPRINT = 20
|
||||
VT_ABIFINGERPRINT = 20,
|
||||
VT_WIREFINGERPRINT = 22,
|
||||
VT_LINKTERMS = 24,
|
||||
VT_BACKENDTYPE = 26
|
||||
};
|
||||
uint32_t abiMajor() const {
|
||||
return GetField<uint32_t>(VT_ABIMAJOR, 0);
|
||||
@@ -711,6 +1193,15 @@ struct Welcome FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
uint64_t abiFingerprint() const {
|
||||
return GetField<uint64_t>(VT_ABIFINGERPRINT, 0);
|
||||
}
|
||||
uint64_t wireFingerprint() const {
|
||||
return GetField<uint64_t>(VT_WIREFINGERPRINT, 0);
|
||||
}
|
||||
const MobileGL::Wire::LinkTerms *linkTerms() const {
|
||||
return GetPointer<const MobileGL::Wire::LinkTerms *>(VT_LINKTERMS);
|
||||
}
|
||||
uint32_t backendType() const {
|
||||
return GetField<uint32_t>(VT_BACKENDTYPE, 0);
|
||||
}
|
||||
template <bool B = false>
|
||||
bool Verify(::flatbuffers::VerifierTemplate<B> &verifier) const {
|
||||
return VerifyTableStart(verifier) &&
|
||||
@@ -728,6 +1219,10 @@ struct Welcome FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
VerifyOffset(verifier, VT_BUILDFINGERPRINT) &&
|
||||
verifier.VerifyString(buildFingerprint()) &&
|
||||
VerifyField<uint64_t>(verifier, VT_ABIFINGERPRINT, 8) &&
|
||||
VerifyField<uint64_t>(verifier, VT_WIREFINGERPRINT, 8) &&
|
||||
VerifyOffset(verifier, VT_LINKTERMS) &&
|
||||
verifier.VerifyTable(linkTerms()) &&
|
||||
VerifyField<uint32_t>(verifier, VT_BACKENDTYPE, 4) &&
|
||||
verifier.EndTable();
|
||||
}
|
||||
};
|
||||
@@ -763,6 +1258,15 @@ struct WelcomeBuilder {
|
||||
void add_abiFingerprint(uint64_t abiFingerprint) {
|
||||
fbb_.AddElement<uint64_t>(Welcome::VT_ABIFINGERPRINT, abiFingerprint, 0);
|
||||
}
|
||||
void add_wireFingerprint(uint64_t wireFingerprint) {
|
||||
fbb_.AddElement<uint64_t>(Welcome::VT_WIREFINGERPRINT, wireFingerprint, 0);
|
||||
}
|
||||
void add_linkTerms(::flatbuffers::Offset<MobileGL::Wire::LinkTerms> linkTerms) {
|
||||
fbb_.AddOffset(Welcome::VT_LINKTERMS, linkTerms);
|
||||
}
|
||||
void add_backendType(uint32_t backendType) {
|
||||
fbb_.AddElement<uint32_t>(Welcome::VT_BACKENDTYPE, backendType, 0);
|
||||
}
|
||||
explicit WelcomeBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
|
||||
: fbb_(_fbb) {
|
||||
start_ = fbb_.StartTable();
|
||||
@@ -784,9 +1288,15 @@ inline ::flatbuffers::Offset<Welcome> CreateWelcome(
|
||||
::flatbuffers::Offset<MobileGL::Wire::SegmentRef> replyPool = 0,
|
||||
::flatbuffers::Offset<MobileGL::Wire::SegmentRef> eventRing = 0,
|
||||
::flatbuffers::Offset<::flatbuffers::String> buildFingerprint = 0,
|
||||
uint64_t abiFingerprint = 0) {
|
||||
uint64_t abiFingerprint = 0,
|
||||
uint64_t wireFingerprint = 0,
|
||||
::flatbuffers::Offset<MobileGL::Wire::LinkTerms> linkTerms = 0,
|
||||
uint32_t backendType = 0) {
|
||||
WelcomeBuilder builder_(_fbb);
|
||||
builder_.add_wireFingerprint(wireFingerprint);
|
||||
builder_.add_abiFingerprint(abiFingerprint);
|
||||
builder_.add_backendType(backendType);
|
||||
builder_.add_linkTerms(linkTerms);
|
||||
builder_.add_buildFingerprint(buildFingerprint);
|
||||
builder_.add_eventRing(eventRing);
|
||||
builder_.add_replyPool(replyPool);
|
||||
@@ -813,7 +1323,10 @@ inline ::flatbuffers::Offset<Welcome> CreateWelcomeDirect(
|
||||
::flatbuffers::Offset<MobileGL::Wire::SegmentRef> replyPool = 0,
|
||||
::flatbuffers::Offset<MobileGL::Wire::SegmentRef> eventRing = 0,
|
||||
const char *buildFingerprint = nullptr,
|
||||
uint64_t abiFingerprint = 0) {
|
||||
uint64_t abiFingerprint = 0,
|
||||
uint64_t wireFingerprint = 0,
|
||||
::flatbuffers::Offset<MobileGL::Wire::LinkTerms> linkTerms = 0,
|
||||
uint32_t backendType = 0) {
|
||||
auto buildFingerprint__ = buildFingerprint ? _fbb.CreateString(buildFingerprint) : 0;
|
||||
return MobileGL::Wire::CreateWelcome(
|
||||
_fbb,
|
||||
@@ -825,7 +1338,10 @@ inline ::flatbuffers::Offset<Welcome> CreateWelcomeDirect(
|
||||
replyPool,
|
||||
eventRing,
|
||||
buildFingerprint__,
|
||||
abiFingerprint);
|
||||
abiFingerprint,
|
||||
wireFingerprint,
|
||||
linkTerms,
|
||||
backendType);
|
||||
}
|
||||
|
||||
struct CapsSnapshot FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
@@ -1211,7 +1727,8 @@ struct SurfaceReply FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
VT_OK = 6,
|
||||
VT_EGLMAJOR = 8,
|
||||
VT_EGLMINOR = 10,
|
||||
VT_DEFAULTFB = 12
|
||||
VT_DEFAULTFB = 12,
|
||||
VT_EVENTHEAD = 14
|
||||
};
|
||||
uint64_t seq() const {
|
||||
return GetField<uint64_t>(VT_SEQ, 0);
|
||||
@@ -1228,6 +1745,9 @@ struct SurfaceReply FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
const MobileGL::Wire::DefaultFramebufferInfo *defaultFb() const {
|
||||
return GetPointer<const MobileGL::Wire::DefaultFramebufferInfo *>(VT_DEFAULTFB);
|
||||
}
|
||||
uint64_t eventHead() const {
|
||||
return GetField<uint64_t>(VT_EVENTHEAD, 0);
|
||||
}
|
||||
template <bool B = false>
|
||||
bool Verify(::flatbuffers::VerifierTemplate<B> &verifier) const {
|
||||
return VerifyTableStart(verifier) &&
|
||||
@@ -1237,6 +1757,7 @@ struct SurfaceReply FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
VerifyField<int32_t>(verifier, VT_EGLMINOR, 4) &&
|
||||
VerifyOffset(verifier, VT_DEFAULTFB) &&
|
||||
verifier.VerifyTable(defaultFb()) &&
|
||||
VerifyField<uint64_t>(verifier, VT_EVENTHEAD, 8) &&
|
||||
verifier.EndTable();
|
||||
}
|
||||
};
|
||||
@@ -1260,6 +1781,9 @@ struct SurfaceReplyBuilder {
|
||||
void add_defaultFb(::flatbuffers::Offset<MobileGL::Wire::DefaultFramebufferInfo> defaultFb) {
|
||||
fbb_.AddOffset(SurfaceReply::VT_DEFAULTFB, defaultFb);
|
||||
}
|
||||
void add_eventHead(uint64_t eventHead) {
|
||||
fbb_.AddElement<uint64_t>(SurfaceReply::VT_EVENTHEAD, eventHead, 0);
|
||||
}
|
||||
explicit SurfaceReplyBuilder(::flatbuffers::FlatBufferBuilder &_fbb)
|
||||
: fbb_(_fbb) {
|
||||
start_ = fbb_.StartTable();
|
||||
@@ -1277,8 +1801,10 @@ inline ::flatbuffers::Offset<SurfaceReply> CreateSurfaceReply(
|
||||
bool ok = false,
|
||||
int32_t eglMajor = 0,
|
||||
int32_t eglMinor = 0,
|
||||
::flatbuffers::Offset<MobileGL::Wire::DefaultFramebufferInfo> defaultFb = 0) {
|
||||
::flatbuffers::Offset<MobileGL::Wire::DefaultFramebufferInfo> defaultFb = 0,
|
||||
uint64_t eventHead = 0) {
|
||||
SurfaceReplyBuilder builder_(_fbb);
|
||||
builder_.add_eventHead(eventHead);
|
||||
builder_.add_seq(seq);
|
||||
builder_.add_defaultFb(defaultFb);
|
||||
builder_.add_eglMinor(eglMinor);
|
||||
@@ -1656,6 +2182,12 @@ struct CtrlEnvelope FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table {
|
||||
const MobileGL::Wire::LogLine *msg_as_LogLine() const {
|
||||
return msg_type() == MobileGL::Wire::CtrlMsg::LogLine ? static_cast<const MobileGL::Wire::LogLine *>(msg()) : nullptr;
|
||||
}
|
||||
const MobileGL::Wire::Refuse *msg_as_Refuse() const {
|
||||
return msg_type() == MobileGL::Wire::CtrlMsg::Refuse ? static_cast<const MobileGL::Wire::Refuse *>(msg()) : nullptr;
|
||||
}
|
||||
const MobileGL::Wire::LogFlush *msg_as_LogFlush() const {
|
||||
return msg_type() == MobileGL::Wire::CtrlMsg::LogFlush ? static_cast<const MobileGL::Wire::LogFlush *>(msg()) : nullptr;
|
||||
}
|
||||
template <bool B = false>
|
||||
bool Verify(::flatbuffers::VerifierTemplate<B> &verifier) const {
|
||||
return VerifyTableStart(verifier) &&
|
||||
@@ -1706,6 +2238,14 @@ template<> inline const MobileGL::Wire::LogLine *CtrlEnvelope::msg_as<MobileGL::
|
||||
return msg_as_LogLine();
|
||||
}
|
||||
|
||||
template<> inline const MobileGL::Wire::Refuse *CtrlEnvelope::msg_as<MobileGL::Wire::Refuse>() const {
|
||||
return msg_as_Refuse();
|
||||
}
|
||||
|
||||
template<> inline const MobileGL::Wire::LogFlush *CtrlEnvelope::msg_as<MobileGL::Wire::LogFlush>() const {
|
||||
return msg_as_LogFlush();
|
||||
}
|
||||
|
||||
struct CtrlEnvelopeBuilder {
|
||||
typedef CtrlEnvelope Table;
|
||||
::flatbuffers::FlatBufferBuilder &fbb_;
|
||||
@@ -1788,6 +2328,14 @@ inline bool VerifyCtrlMsg(::flatbuffers::VerifierTemplate<B> &verifier, const vo
|
||||
auto ptr = reinterpret_cast<const MobileGL::Wire::LogLine *>(obj);
|
||||
return verifier.VerifyTable(ptr);
|
||||
}
|
||||
case CtrlMsg::Refuse: {
|
||||
auto ptr = reinterpret_cast<const MobileGL::Wire::Refuse *>(obj);
|
||||
return verifier.VerifyTable(ptr);
|
||||
}
|
||||
case CtrlMsg::LogFlush: {
|
||||
auto ptr = reinterpret_cast<const MobileGL::Wire::LogFlush *>(obj);
|
||||
return verifier.VerifyTable(ptr);
|
||||
}
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,47 @@ table SegmentRef {
|
||||
// Handshake
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// P6.5: independent control and data-plane choices. Values are append-only.
|
||||
enum DataPlane : ubyte { SharedSegments = 0, Stream = 1 }
|
||||
enum WireForm : ubyte { StructImage = 0 }
|
||||
enum DialMode : ubyte { No = 0, Fork = 1, Connect = 2 }
|
||||
|
||||
// Window sizes are stated by the server; the client's values are proposals only.
|
||||
table LinkTerms {
|
||||
dataPlane: DataPlane;
|
||||
wireForm: WireForm;
|
||||
maxReplyBytes: ulong;
|
||||
cmdWindowBytes: ulong;
|
||||
stageWindowBytes: ulong;
|
||||
eventWindowBytes: ulong;
|
||||
}
|
||||
|
||||
enum RefuseCode : uint {
|
||||
None = 0,
|
||||
ProtocolVersion = 1,
|
||||
WireFingerprint = 2,
|
||||
BuildFingerprint = 3,
|
||||
Authentication = 4,
|
||||
LinkTerms = 5,
|
||||
Backend = 6,
|
||||
Busy = 7,
|
||||
MalformedHello = 8,
|
||||
}
|
||||
|
||||
// A handshake rejection is a normal session outcome, never a process abort.
|
||||
table Refuse {
|
||||
code: RefuseCode;
|
||||
detail: string;
|
||||
expected: ulong;
|
||||
actual: ulong;
|
||||
peerValue: string;
|
||||
}
|
||||
|
||||
table LogFlush {
|
||||
seq: ulong;
|
||||
ack: bool;
|
||||
}
|
||||
|
||||
table Hello {
|
||||
abiMajor: uint;
|
||||
abiMinor: uint;
|
||||
@@ -63,15 +104,12 @@ table Hello {
|
||||
backendType: uint;
|
||||
pid: uint;
|
||||
configBlob: [ubyte];
|
||||
// CONTRACT-P5 table 0, "ABI agreement": MGPCaps has only a COMPOSITIONAL size
|
||||
// assertion, because DynamicBackendParameters still carries SizeT and GLenum.
|
||||
// So the two peers assert they were built from the same struct shapes instead
|
||||
// of rewriting them fixed-width (that is P7's account). This is
|
||||
// MG_Remote::CapsAbiFingerprint(): sizeof(DynamicBackendParameters),
|
||||
// sizeof(MGPCaps), sizeof(GLFunctionsTable), the protocol ABI version and the
|
||||
// build's git stamp, mixed. A mismatch is Fatal{AbiMismatch} and NEVER a
|
||||
// downgrade - every alternative silently reads one struct as another.
|
||||
// Retained legacy slot. P6.5 peers compare wireFingerprint instead.
|
||||
abiFingerprint: ulong;
|
||||
wireFingerprint: ulong;
|
||||
linkTerms: LinkTerms;
|
||||
token: string;
|
||||
dialMode: DialMode;
|
||||
}
|
||||
|
||||
table Welcome {
|
||||
@@ -82,12 +120,12 @@ table Welcome {
|
||||
stageRing: SegmentRef;
|
||||
replyPool: SegmentRef;
|
||||
eventRing: SegmentRef;
|
||||
// The server's half of the assertion above. The string is carried beside the
|
||||
// mixed value only so a mismatch can name both builds in the Fatal line; the
|
||||
// COMPARISON is on abiFingerprint, which also covers the three sizeofs the
|
||||
// string cannot.
|
||||
buildFingerprint: string;
|
||||
// Retained legacy slot; do not reuse or renumber it.
|
||||
abiFingerprint: ulong;
|
||||
wireFingerprint: ulong;
|
||||
linkTerms: LinkTerms;
|
||||
backendType: uint;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -221,6 +259,8 @@ table SurfaceReply {
|
||||
eglMajor: int;
|
||||
eglMinor: int;
|
||||
defaultFb: DefaultFramebufferInfo;
|
||||
// P6.5: all preceding data-plane events must arrive before this reply is observed.
|
||||
eventHead: ulong;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -306,6 +346,8 @@ union CtrlMsg {
|
||||
AuxRequest,
|
||||
Fatal,
|
||||
LogLine,
|
||||
Refuse,
|
||||
LogFlush,
|
||||
}
|
||||
|
||||
table CtrlEnvelope {
|
||||
|
||||
@@ -62,6 +62,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// The view is rebuilt per call rather than stored, so that this body does not change
|
||||
// ReplyPool's four members and therefore does not touch v1's header at all.
|
||||
void ReplyPool::PostReply(Uint64 seq, Int32 status, const void* bytes, Uint64 size) {
|
||||
if (m_link) {
|
||||
const auto result = m_link->PostReply(seq, status, bytes, size);
|
||||
if (result == MOBILEGL_ERR_BUFFER_TOO_SMALL)
|
||||
SessionFail(MGFatalFamily::ReplyTooLarge, "MGPipe: Fatal{ReplyTooLarge, stream reply}");
|
||||
return;
|
||||
}
|
||||
Transport::ReplySlotPool pool(m_base, m_size, m_slots);
|
||||
// Fatal inside Post when the answer does not fit a slot: P5 does not chunk replies,
|
||||
// and the client knows an answer's size before it emits the record.
|
||||
@@ -1192,6 +1198,15 @@ namespace MobileGL::MG_Remote::Server {
|
||||
PipeApplier::PipeApplier(Wire::SegmentTable* segments, ReplyPool* replies)
|
||||
: m_segments(segments), m_replies(replies) {}
|
||||
|
||||
void PipeApplier::Attach(Transport::ILink* link, MG_Backend::BackendObject* backend) {
|
||||
if (!link || !link->Attached() || !m_segments)
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, PipeApplier::Attach missing link}");
|
||||
m_verbs.SetBackend(backend);
|
||||
m_decoder = Wire::PipeWireDecoder(link, m_segments, m_replies);
|
||||
m_decoder.SetVerbSink(&m_verbs);
|
||||
MG_Pipe::MGPipeServerBlockNoteIdentity(); m_attached = true;
|
||||
}
|
||||
|
||||
void PipeApplier::Attach(Transport::RingControl* control, MG_Backend::BackendObject* backend) {
|
||||
if (control == nullptr || m_segments == nullptr) {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"PipeApplier::Attach\"} - no control "
|
||||
|
||||
@@ -89,6 +89,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
ReplyPool() = default;
|
||||
ReplyPool(void* base, Uint64 sizeBytes, Uint32 slotCount, Uint32 slotBytes);
|
||||
|
||||
void SetLink(Transport::ILink* link) { m_link = link; }
|
||||
void PostReply(Uint64 seq, Int32 status, const void* bytes, Uint64 size) override;
|
||||
|
||||
// A reply larger than one slot is Fatal rather than chunked: P5's only large answer is
|
||||
@@ -97,6 +98,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Uint32 SlotBytes() const;
|
||||
|
||||
private:
|
||||
Transport::ILink* m_link = nullptr;
|
||||
Uint8* m_base = nullptr;
|
||||
Uint64 m_size = 0;
|
||||
Uint32 m_slots = 0;
|
||||
@@ -350,6 +352,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// CALLED ON THE APPLY THREAD, ONCE, BEFORE THE FIRST RECORD. PipeWireDecoder is "not
|
||||
// thread safe: one decoder on the apply thread, by construction", and its constructor
|
||||
// installs the process-wide apply hook.
|
||||
void Attach(Transport::ILink* link, MG_Backend::BackendObject* backend);
|
||||
void Attach(Transport::RingControl* control, MG_Backend::BackendObject* backend);
|
||||
void Detach();
|
||||
Bool Attached() const;
|
||||
|
||||
@@ -372,10 +372,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// The decoder is built HERE, on this thread, because PipeWireDecoder is "not thread
|
||||
// safe: one decoder on the apply thread, by construction" and its constructor installs
|
||||
// the process-wide apply hook.
|
||||
session.Applier().Attach(&session.Control(), m_backend.get());
|
||||
session.Applier().Attach(session.DataLink(), m_backend.get());
|
||||
|
||||
Transport::RingControl& control = session.Control();
|
||||
Transport::Doorbell& bell = session.ConsumerDoorbell();
|
||||
const auto signals = session.DataLink()->Signals();
|
||||
Transport::Doorbell& bell = session.DataLink()->ConsumerBell();
|
||||
Transport::RingConsumer& ring = session.CommandRing();
|
||||
const Uint32 spinUs = SpinUsFromConfig();
|
||||
|
||||
@@ -400,16 +400,16 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// this bell (that pairing is the whole of the flow-control protocol; a cleared flag
|
||||
// with no bell is the lost wakeup the forward direction's publish-then-ring order
|
||||
// exists to prevent).
|
||||
const auto ready = [this, &control, &ring] {
|
||||
const auto ready = [this, signals, &ring] {
|
||||
if (m_stopRequested.load(std::memory_order_acquire) || ControlIsPending()) return true;
|
||||
if (control.eventRingFull.load(std::memory_order_acquire) != 0) return false;
|
||||
return control.cmdHead.load(std::memory_order_acquire) != ring.LocalTail();
|
||||
if (signals.EventRingFull->load(std::memory_order_acquire) != 0) return false;
|
||||
return signals.CmdHead->load(std::memory_order_acquire) != ring.LocalTail();
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
PumpControlRequest();
|
||||
if (m_stopRequested.load(std::memory_order_acquire)) break;
|
||||
if (control.eventRingFull.load(std::memory_order_acquire) == 0) DrainRing();
|
||||
if (signals.EventRingFull->load(std::memory_order_acquire) == 0) DrainRing();
|
||||
if (m_stopRequested.load(std::memory_order_acquire)) break;
|
||||
if (ready()) continue;
|
||||
|
||||
@@ -436,7 +436,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
MG_Util::PipeStats::PublishGauge(Gauge::ServerWaits, waits);
|
||||
MG_Util::PipeStats::PublishGauge(Gauge::ServerParks, ParkBlockCount());
|
||||
}
|
||||
const bool woke = bell.Wait(control.consumerParked, ready, spinUs,
|
||||
// Flush-on-idle publishes the last reply record even below the batch threshold.
|
||||
session.FlushDataProgress();
|
||||
const bool woke = bell.Wait(*signals.ConsumerParked, ready, spinUs,
|
||||
Transport::kWaitForever, &m_parkBlocks);
|
||||
if (!woke) {
|
||||
// Wait returns false only on a dead bell or an expired deadline, and this park
|
||||
@@ -735,7 +737,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// it atomically, so the apply thread cannot observe the request until this side is
|
||||
// waiting, and the doorbell remembers the notify regardless.
|
||||
if (m_session != nullptr) {
|
||||
m_session->ConsumerDoorbell().Notify();
|
||||
m_session->DataLink()->ConsumerBell().Notify();
|
||||
}
|
||||
m_controlDone.wait(lock, [this] { return m_controlFinished; });
|
||||
frame = m_controlFrame; // the reply half, written by the dispatch
|
||||
@@ -786,7 +788,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// The bell may already be dead - ClientSession::Stop calls transport->Shutdown()
|
||||
// first, which is what table 3's step 2 requires - and Notify on a dead bell is
|
||||
// harmless. Ringing anyway covers the paths that Stop without a Kill.
|
||||
m_session->ConsumerDoorbell().Notify();
|
||||
m_session->DataLink()->ConsumerBell().Notify();
|
||||
}
|
||||
|
||||
// THE JOIN IS BOUNDED. std::thread::join has no deadline, so a lost wakeup would wedge
|
||||
|
||||
@@ -1,316 +1,117 @@
|
||||
// MobileGL - MobileGL/MG_Remote/Server/ServerMain.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// MobileGL - separate-process server and TCP session supervisor.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// The server process's entry point. Package `sm` (CONTRACT-P6.md §3).
|
||||
//
|
||||
// ARCHITECTURE.md:488-496 has described this symbol for three phases and a6
|
||||
// found it existed nowhere in the tree. This is the real one.
|
||||
//
|
||||
// THE TWO PROCESSES ARE INDEPENDENT. This process is started by whoever starts
|
||||
// it - a shell, a test, an Android Service - and it LISTENS. The client is
|
||||
// started separately and CONNECTS. Nothing is inherited: no fds, no memory, no
|
||||
// parent.
|
||||
//
|
||||
// That is a deliberate change from this package's first shape, which forked the
|
||||
// server and handed it fds 3..6. Inheritance works and is less code, but it can
|
||||
// only ever produce a server that is a CHILD OF ITS CLIENT - the one arrangement
|
||||
// the end state cannot use, because there the server is a standing application
|
||||
// and the client is somewhere else entirely, possibly on another kernel.
|
||||
// CONTRACT-P6 §3.1 calls this Dial == Connect and had it as a later shape. It is
|
||||
// the primary one.
|
||||
//
|
||||
// WHAT THIS PACKAGE OWNS: the process, the rendezvous, the backend, the session,
|
||||
// the descriptor hand-off and the exit discipline. NOT what a control frame
|
||||
// means - that is `cp` - and not the data-plane seam, which is `lk`.
|
||||
//
|
||||
// _exit, NOT exit (CONTRACT-P6 §12.4). It decides whether g_processTeardown is
|
||||
// ever set, and therefore whether seven twin destructors take their early-out or
|
||||
// call into a driver whose context is already gone. The log is flushed first,
|
||||
// because _exit runs no atexit handler and a server that died without saying why
|
||||
// is the diagnostic this project refuses.
|
||||
|
||||
#include "../Transport/Doorbell.h" // kWaitForever, SocketDoorbell
|
||||
#include "../Transport/FdPassing.h"
|
||||
#include "../Transport/Doorbell.h"
|
||||
#include "../Transport/SocketTransport.h"
|
||||
#include "../Transport/WireLog.h"
|
||||
#include "../Protocol/SurfaceOpCodec.h"
|
||||
#include "ServerLoop.h"
|
||||
#include "ServerSession.h"
|
||||
#include "SurfaceControlFrame.h"
|
||||
|
||||
#include <Config.h> // MG_Config::Transport - D1 pins it to Spawn in this process
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h> // D1c: MGPipeSetServerProcessRole
|
||||
#include <Config.h>
|
||||
#include <Init.h>
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h>
|
||||
#include <MG_Backend/ServerRole.h>
|
||||
#include <MG_Util/Metrics/PipeStats.h> // step 1.6: the counters' latch, server-process half
|
||||
#include <Init.h> // MG_ConfigLoader::Init - the child loads its own config (step 1.5)
|
||||
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
#include <MG_Util/Metrics/PipeStats.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__unix__) || defined(__APPLE__) || defined(__ANDROID__)
|
||||
# include <sys/socket.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
#include <sys/socket.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace {
|
||||
using namespace MobileGL::MG_Remote;
|
||||
using namespace MobileGL::MG_Remote::Transport;
|
||||
namespace Protocol = ::MobileGL::Wire;
|
||||
|
||||
// The server must not be able to become a client. CONTRACT-P6 §3.1: this is
|
||||
// catch (a), STRUCTURAL and separate from role selection, because forcing
|
||||
// MG_Config::Transport to Monolith - what ARCHITECTURE.md:488 says - would
|
||||
// flip 148 MG_Backend lines to the frontend arm in the one process with no
|
||||
// frontend. Catch (b) is the launcher's envp scrub, and the two report
|
||||
// through DIFFERENT exit codes so they are distinguishable: two safeties
|
||||
// that cannot be told apart are one safety.
|
||||
bool DialingIsForbidden() {
|
||||
const char* dial = std::getenv("MOBILEGL_IPC_DIAL");
|
||||
return dial != nullptr && std::strcmp(dial, "no") == 0;
|
||||
}
|
||||
void Refuse(SocketTransport& transport, Protocol::RefuseCode code, const char* detail) {
|
||||
flatbuffers::FlatBufferBuilder builder(256);
|
||||
auto refusal = Protocol::CreateRefuseDirect(builder, code, detail);
|
||||
auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::Refuse, refusal.Union());
|
||||
Protocol::FinishCtrlEnvelopeBuffer(builder, envelope);
|
||||
transport.SendFrame({builder.GetBufferPointer(), builder.GetSize()});
|
||||
WireLogError("MG_Remote server: Refuse{%s} %s", Protocol::EnumNameRefuseCode(code), detail);
|
||||
}
|
||||
|
||||
bool LooksLikeAClientEnvironment() {
|
||||
static constexpr const char* kNames[] = {"MOBILEGL_TRANSPORT", "MOBILEGL_IPC_SERVER_PATH",
|
||||
"MOBILEGL_IPC_RING_MB", "MOBILEGL_IPC_STAGE_MB"};
|
||||
for (const char* name : kNames) {
|
||||
if (std::getenv(name) != nullptr) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void ForwardLog(void* user, const char* text) {
|
||||
auto& transport = *static_cast<ITransport*>(user);
|
||||
flatbuffers::FlatBufferBuilder builder(512);
|
||||
auto log = Protocol::CreateLogLine(builder, Protocol::LogLevel::Info, builder.CreateString(text));
|
||||
auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::LogLine, log.Union());
|
||||
Protocol::FinishCtrlEnvelopeBuffer(builder, envelope);
|
||||
(void)transport.SendFrame({builder.GetBufferPointer(), builder.GetSize()});
|
||||
}
|
||||
|
||||
// The rendezvous. A NAME, because independent processes have no other way to
|
||||
// find each other. No default: a default rendezvous is one that two unrelated
|
||||
// runs can collide on.
|
||||
std::string Endpoint(int argc, char** argv) {
|
||||
if (argc > 1 && argv[1] != nullptr && argv[1][0] != '\0') {
|
||||
return argv[1];
|
||||
}
|
||||
if (const char* fromEnv = std::getenv("MOBILEGL_IPC_ENDPOINT")) {
|
||||
return fromEnv;
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) int mobilegl_server_main(int argc, char** argv) {
|
||||
using namespace MobileGL::MG_Remote;
|
||||
using namespace MobileGL::MG_Remote::Transport;
|
||||
struct FlushAck { ITransport* transport; std::uint64_t seq; };
|
||||
void SendLogAck(void* pointer) {
|
||||
auto& ack = *static_cast<FlushAck*>(pointer);
|
||||
flatbuffers::FlatBufferBuilder builder(64);
|
||||
auto flush = Protocol::CreateLogFlush(builder, ack.seq, true);
|
||||
auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::LogFlush, flush.Union());
|
||||
Protocol::FinishCtrlEnvelopeBuffer(builder, envelope);
|
||||
(void)ack.transport->SendFrame({builder.GetBufferPointer(), builder.GetSize()});
|
||||
}
|
||||
|
||||
// Called only after fork: the supervisor never creates a backend or EGL context.
|
||||
[[noreturn]] void RunSession(std::unique_ptr<SocketTransport> control) {
|
||||
const int selfPid = static_cast<int>(::getpid());
|
||||
|
||||
// The arm-proof line (CONTRACT-P6 §9.5). ID-124 proved a lane can run
|
||||
// monolith and pass every other gate, so each entry leaves its own evidence.
|
||||
WireLogError("MG_Remote server: pid=%d transport=spawn role=server", selfPid);
|
||||
|
||||
if (!DialingIsForbidden()) {
|
||||
std::fprintf(stderr,
|
||||
"MG_Remote server: pid=%d refusing to start - MOBILEGL_IPC_DIAL=no was not "
|
||||
"set; anti-recursion catch (a) is missing\n",
|
||||
selfPid);
|
||||
std::fflush(nullptr);
|
||||
::_exit(64);
|
||||
const bool tcp = control->IsTcp();
|
||||
std::vector<std::uint8_t> helloFrame;
|
||||
std::uint64_t bytes = 0;
|
||||
auto received = control->ReceiveFrame({nullptr, 0}, &bytes, 10000);
|
||||
if (received != MOBILEGL_ERR_BUFFER_TOO_SMALL || bytes > 1024 * 1024) ::_exit(67);
|
||||
helloFrame.resize(static_cast<std::size_t>(bytes));
|
||||
if (control->ReceiveFrame({helloFrame.data(), helloFrame.size()}, &bytes, 0) != MOBILEGL_OK) ::_exit(67);
|
||||
flatbuffers::Verifier verifier(helloFrame.data(), helloFrame.size());
|
||||
if (!Protocol::VerifyCtrlEnvelopeBuffer(verifier)) ::_exit(67);
|
||||
const auto* hello = Protocol::GetCtrlEnvelope(helloFrame.data())->msg_as_Hello();
|
||||
if (!hello) ::_exit(67);
|
||||
if (tcp) {
|
||||
const char* expected = std::getenv("MOBILEGL_IPC_TOKEN");
|
||||
const std::string token = hello->token() ? hello->token()->str() : std::string();
|
||||
if (token != (expected ? expected : "")) {
|
||||
Refuse(*control, Protocol::RefuseCode::Authentication, "TCP token mismatch");
|
||||
::_exit(0);
|
||||
}
|
||||
}
|
||||
if (LooksLikeAClientEnvironment()) {
|
||||
std::fprintf(stderr,
|
||||
"MG_Remote server: pid=%d the envp scrub FAILED - a client knob survived "
|
||||
"into this process; catch (a) held, catch (b) did not\n",
|
||||
selfPid);
|
||||
std::fflush(nullptr);
|
||||
::_exit(65);
|
||||
}
|
||||
|
||||
const std::string endpoint = Endpoint(argc, argv);
|
||||
if (endpoint.empty()) {
|
||||
std::fprintf(stderr,
|
||||
"MG_Remote server: pid=%d no endpoint - pass one as argv[1] or set "
|
||||
"MOBILEGL_IPC_ENDPOINT\n",
|
||||
selfPid);
|
||||
std::fflush(nullptr);
|
||||
::_exit(71);
|
||||
}
|
||||
|
||||
// ---- 1. listen, BEFORE the backend. The client's bounded connect retry is
|
||||
// what tolerates the backend taking a while; a client that connected and then
|
||||
// waited for a Welcome a failed bring-up will never send would have to learn
|
||||
// about death from a timeout, which §5.4 forbids.
|
||||
int listenFd = -1;
|
||||
if (SocketTransport::Listen(endpoint, &listenFd) != MOBILEGL_OK) {
|
||||
std::fprintf(stderr, "MG_Remote server: pid=%d could not listen on \"%s\"\n", selfPid,
|
||||
endpoint.c_str());
|
||||
std::fflush(nullptr);
|
||||
::_exit(72);
|
||||
}
|
||||
WireLogError("MG_Remote server: pid=%d listening on \"%s\"", selfPid, endpoint.c_str());
|
||||
|
||||
// ---- 1.5. CONFIG, AND THEN D1.
|
||||
//
|
||||
// TWO SEPARATE DEFECTS LIVED HERE, and both were silent.
|
||||
//
|
||||
// (a) NOTHING LOADED THE CONFIG AT ALL. mobilegl_server_main calls
|
||||
// InitServerRoleForSpawn directly rather than MobileGL::Initialize, and
|
||||
// MG_ConfigLoader::Init is a step of the latter - so every MG_Config
|
||||
// value in this process was its STATIC DEFAULT. MOBILEGL_BACKEND_TYPE
|
||||
// survives the launcher's scrub on purpose, and it was being read by
|
||||
// nobody: a DirectVulkan session would have brought up a DirectGLES
|
||||
// server and then disagreed with its client about which backend the
|
||||
// capability mask describes.
|
||||
//
|
||||
// (b) THE CHILD RESOLVED Transport == Monolith. CONTRACT-P6 §3.1 (D1) says
|
||||
// it must read as Spawn, and a6 measured the cost of getting it wrong:
|
||||
// 226 live lines test `Transport != Monolith` to select the SERVER arm,
|
||||
// 148 of them in MG_Backend. Monolith flips all of them to frontend glue
|
||||
// in the one process that has no frontend. Observed, exactly as
|
||||
// predicted: TextureImpl::UnitTexturesByHandle() answered false, Clear
|
||||
// took the legacy arm that dereferences borrowed FRONTEND slot pointers,
|
||||
// and the apply thread took a SIGSEGV inside SyncNeccessaryTextures on
|
||||
// the first Clear record of the OpenRA retrace.
|
||||
//
|
||||
// IT IS SET HERE AND NOT THROUGH THE ENVIRONMENT because the environment is
|
||||
// where it must NOT appear: LooksLikeAClientEnvironment above exits 65 if
|
||||
// MOBILEGL_TRANSPORT survived into this process, and that check is
|
||||
// anti-recursion catch (b). D1's own answer is that Dial - not Transport -
|
||||
// is the anti-recursion axis, so Transport is free to say what is true.
|
||||
MobileGL::MG_ConfigLoader::Init();
|
||||
MobileGL::MG_Config::Transport = MobileGL::MG_Config::TransportMode::Spawn;
|
||||
// D1c/D10: THIS PROCESS IS THE SERVER, stated once and before any GL work.
|
||||
//
|
||||
// Four guards used to ask "am I on the apply thread" or "is a ClientSession active" - both
|
||||
// questions a server PROCESS answers wrongly: it has no ClientSession at all, and every one
|
||||
// of its threads is a server thread, not just the applier's. Saying it here is what arms
|
||||
// them; without it they are compiled in and permanently false, which is worse than absent
|
||||
// because it reads as coverage.
|
||||
MobileGL::MG_Pipe::MGPipeSetServerProcessRole(true);
|
||||
// ---- 1.6. THE MGPIPE COUNTERS, and this is the ONLY call site outside
|
||||
// MobileGL::Initialize().
|
||||
//
|
||||
// GATE 8'S SERVER HALF WAS STRUCTURALLY ZERO WITHOUT THIS, and the shape of the
|
||||
// defect is worth stating: the child never runs MobileGL::Initialize, so
|
||||
// PipeStats::Init() - a step of it - never ran here, and g_pipeStatsEnabled
|
||||
// stayed false. Every one of the ~80 `if (Enabled())` sites in the backends and
|
||||
// in MG_Remote (ServerLoop's own ServerWaits/ServerParks publish included)
|
||||
// compiled in and was permanently false in this process. The contract's
|
||||
// mandatory "socket doorbell vs inproc condvar" number therefore had no server
|
||||
// half under `spawn` at all, and its absence read exactly like a real zero:
|
||||
// `wait[srv=0 srvpark=0]` is what "the server never waited" looks like too.
|
||||
//
|
||||
// IT GOES AFTER THE CONFIG LOAD AND AFTER THE ROLE IS STATED, and both halves
|
||||
// of that order are load-bearing:
|
||||
//
|
||||
// * AFTER MG_ConfigLoader::Init, because Init() LATCHES the flag out of
|
||||
// MG_Config::Features.PipeStats, and the parser does not exist until the
|
||||
// line above has run. Calling it earlier would latch the static default
|
||||
// (false) and the MOBILEGL_PIPE_STATS the launcher passed would be read by
|
||||
// nobody - the same class of defect the config load itself used to have.
|
||||
// * AFTER MGPipeSetServerProcessRole, because that is what makes the LOG SINK
|
||||
// know this process is the server, and PipeStats' summary line is emitted
|
||||
// through MGLOG_I. The sink resolves its role from MOBILEGL_IPC_ROLE (which
|
||||
// the launcher sets and its envp scrub preserves) and would in fact be
|
||||
// right either way, but ordering it this way means the line cannot be filed
|
||||
// under the client by a future change to that resolution. What the contract
|
||||
// requires - that the summary lands in `<base>.server.log` - is asserted by
|
||||
// the lane, not assumed from this comment.
|
||||
//
|
||||
// MOBILEGL_PIPE_STATS / _PERIOD / _FILE MEAN EXACTLY WHAT THEY MEAN IN THE
|
||||
// CLIENT PROCESS, and that is the whole point of routing through Init(): all
|
||||
// three are read from the child's own environment by the same parser and
|
||||
// latched by the same function. Nothing about the stats channel is spawn-shaped.
|
||||
// One asymmetry is real and intended: this process has no backend Present of
|
||||
// its own, so its line cadence rides the present RECORDS it applies
|
||||
// (PipeApplier -> DirectGLES/DirectVulkan Present -> OnPresent) rather than a
|
||||
// swap the server does not perform. A spawn session that presents N times
|
||||
// produces N windows here, which is the same N the client's own line counts.
|
||||
// The child bypasses MobileGL::Initialize: arm its own role counters.
|
||||
MobileGL::MG_Util::PipeStats::Init();
|
||||
WireLogError("MG_Remote server: pid=%d config loaded, backend=%d, Transport pinned to Spawn "
|
||||
"(D1: this process IS the server arm)",
|
||||
selfPid, static_cast<int>(MobileGL::MG_Config::ActiveBackendType));
|
||||
|
||||
// ---- 2. the backend, through the SAME entry point the inproc server role
|
||||
// uses (MG_Backend/ServerRole.h). No EGL yet: the context is created on the
|
||||
// apply thread when the client's first eglMakeCurrent crosses.
|
||||
if (!MobileGL::MG_Backend::InitServerRoleForSpawn()) {
|
||||
std::fprintf(stderr,
|
||||
"MG_Remote server: pid=%d could not create a backend; refusing to accept a "
|
||||
"session it could never apply\n",
|
||||
selfPid);
|
||||
::close(listenFd);
|
||||
::unlink(endpoint.c_str());
|
||||
std::fflush(nullptr);
|
||||
::_exit(66);
|
||||
const auto backend = static_cast<MobileGL::BackendType>(hello->backendType());
|
||||
if (backend != MobileGL::BackendType::DirectGLES && backend != MobileGL::BackendType::DirectVulkan) {
|
||||
Refuse(*control, Protocol::RefuseCode::Backend, "unsupported Hello.backendType");
|
||||
::_exit(0);
|
||||
}
|
||||
|
||||
// ---- 3. accept the client's TWO connections: control, then aux.
|
||||
std::unique_ptr<SocketTransport> control;
|
||||
if (SocketTransport::AcceptPair(listenFd, 30000, control) != MOBILEGL_OK) {
|
||||
std::fprintf(stderr, "MG_Remote server: pid=%d no client within 30 s\n", selfPid);
|
||||
::close(listenFd);
|
||||
::unlink(endpoint.c_str());
|
||||
std::fflush(nullptr);
|
||||
::_exit(73);
|
||||
const char* pinned = std::getenv("MOBILEGL_BACKEND_TYPE");
|
||||
if (pinned && *pinned && backend != MobileGL::MG_Config::ActiveBackendType) {
|
||||
Refuse(*control, Protocol::RefuseCode::Backend, "Hello.backendType disagrees with pinned backend");
|
||||
::_exit(0);
|
||||
}
|
||||
// One session per process (CONTRACT-P6 §12.3), so the door closes behind the
|
||||
// one client that came through it. A second would collide on four process
|
||||
// globals and on DirectGLES's single per-process EGL tuple.
|
||||
::close(listenFd);
|
||||
::unlink(endpoint.c_str());
|
||||
|
||||
// ---- 4. the two bells, created HERE and handed over. NOT inherited: there
|
||||
// is no parent to inherit from. One socketpair per DIRECTION, because
|
||||
// SocketDoorbell is "write a byte to wake the peer, poll to be woken" and a
|
||||
// single pair would have each side reading the byte it wrote to the other.
|
||||
int serverBell[2] = {-1, -1};
|
||||
int clientBell[2] = {-1, -1};
|
||||
if (::socketpair(AF_UNIX, SOCK_STREAM, 0, serverBell) != 0 ||
|
||||
::socketpair(AF_UNIX, SOCK_STREAM, 0, clientBell) != 0) {
|
||||
std::fprintf(stderr, "MG_Remote server: pid=%d bell socketpair failed\n", selfPid);
|
||||
std::fflush(nullptr);
|
||||
::_exit(74);
|
||||
MobileGL::MG_Config::ActiveBackendType = backend;
|
||||
if (!MobileGL::MG_Backend::InitServerRoleForSpawn()) ::_exit(66);
|
||||
auto& session = Server::ServerSessionInstance();
|
||||
int serverBell[2] = {-1, -1}, clientBell[2] = {-1, -1};
|
||||
std::unique_ptr<SocketDoorbell> selfBell, peerBell;
|
||||
if (!tcp) {
|
||||
if (::socketpair(AF_UNIX, SOCK_STREAM, 0, serverBell) != 0 ||
|
||||
::socketpair(AF_UNIX, SOCK_STREAM, 0, clientBell) != 0) ::_exit(74);
|
||||
selfBell = std::make_unique<SocketDoorbell>(serverBell[0], serverBell[1], 1, true);
|
||||
peerBell = std::make_unique<SocketDoorbell>(-1, clientBell[1], 1, true);
|
||||
session.SetExternalDoorbells(selfBell.get(), peerBell.get());
|
||||
}
|
||||
// PARK AND NOTIFY ON DIFFERENT DESCRIPTORS, and this is not a refinement -
|
||||
// the single-fd form CANNOT WAKE ITSELF. send() on one end of a socketpair
|
||||
// is delivered to the OTHER end, so a one-fd bell built on serverBell[0]
|
||||
// writes to serverBell[1] and polls serverBell[0]: correct for waking a
|
||||
// PEER, useless for waking a thread in this process. The server needs
|
||||
// exactly that, because the control pump posts into the apply thread's
|
||||
// mailbox and then has to ring the bell that thread is parked on. Measured
|
||||
// symptom when it was wrong: every eglInitialize crossed, landed in the
|
||||
// mailbox, and timed out at 5 s because nothing ever woke mgl-srv-apply.
|
||||
SocketDoorbell selfBell(/*parkFd=*/serverBell[0], /*notifyFd=*/serverBell[1], /*code=*/1,
|
||||
/*ownsFds=*/true);
|
||||
// RINGS ONLY. The client parks on clientBell[0]; writing clientBell[1] is
|
||||
// what reaches it. parkFd = -1 says in the type that this side never waits.
|
||||
SocketDoorbell peerBell(/*parkFd=*/-1, /*notifyFd=*/clientBell[1], /*code=*/1,
|
||||
/*ownsFds=*/true);
|
||||
|
||||
Server::ServerSession& session = Server::ServerSessionInstance();
|
||||
session.SetExternalDoorbells(&selfBell, &peerBell);
|
||||
|
||||
// ---- 5. Accept: the ABI assertion, the four segments, and Welcome.
|
||||
const MobileGLResult accepted = session.Accept(*control);
|
||||
const auto accepted = session.Accept(*control, &helloFrame);
|
||||
if (accepted != MOBILEGL_OK) {
|
||||
std::fprintf(stderr, "MG_Remote server: pid=%d Accept failed (rc=%d)\n", selfPid,
|
||||
static_cast<int>(accepted));
|
||||
WireLogError("MG_Remote server: pid=%d Accept failed (rc=%d)", selfPid, static_cast<int>(accepted));
|
||||
std::fflush(nullptr);
|
||||
::_exit(67);
|
||||
::_exit(accepted == MOBILEGL_ERR_PROTOCOL_MISMATCH ? 0 : 67);
|
||||
}
|
||||
|
||||
// ---- 6. hand SIX descriptors over. THIS IS WHY SCM_RIGHTS IS IN THIS
|
||||
// DESIGN: the segments are anonymous - ASharedMemory_create has no filesystem
|
||||
// name at all, and memfd / shm_open+unlink are not openable either
|
||||
// (ShmSegmentPosix.cpp says so) - so the descriptor IS the only key, and this
|
||||
// process is the side that created them.
|
||||
//
|
||||
// The slot travels in the sideband rather than being inferred from arrival
|
||||
// order, so a reordered or dropped offer is a NAMED mismatch on arrival
|
||||
// instead of two segments quietly swapped.
|
||||
if (!tcp)
|
||||
{
|
||||
struct Sideband {
|
||||
std::uint32_t slot;
|
||||
@@ -359,87 +160,108 @@ extern "C" __attribute__((visibility("default"))) int mobilegl_server_main(int a
|
||||
::close(clientBell[0]);
|
||||
}
|
||||
|
||||
// ---- 7. the apply thread. It holds the native context for its whole life,
|
||||
// which is what turns MakeCurrent's cache-invalidation storm into a one-off.
|
||||
Server::ServerLoop& loop = Server::ServerLoopInstance();
|
||||
if (loop.Start(session) != MOBILEGL_OK) {
|
||||
std::fprintf(stderr, "MG_Remote server: pid=%d the apply thread did not start\n", selfPid);
|
||||
std::fflush(nullptr);
|
||||
::_exit(70);
|
||||
}
|
||||
|
||||
WireLogError("MG_Remote server: pid=%d transport=spawn role=server ready "
|
||||
"(backend up, seven descriptors handed over, apply thread running)",
|
||||
selfPid);
|
||||
|
||||
// ---- 8. the control pump. EOF is the whole exit condition: no timeout may
|
||||
// ever stand in for the death fact (§5.4), because a server one frame behind
|
||||
// is the intended steady state and a server that is gone is a different thing.
|
||||
const char* forwarding = std::getenv("MOBILEGL_IPC_LOG_FORWARD");
|
||||
if (tcp && (!forwarding || std::strcmp(forwarding, "0") != 0))
|
||||
MobileGL::MG_Util::Debug::SetLogForwarder(&ForwardLog, control.get());
|
||||
auto& loop = Server::ServerLoopInstance();
|
||||
if (loop.Start(session) != MOBILEGL_OK) ::_exit(70);
|
||||
WireLogError("MG_Remote server: pid=%d transport=spawn role=server ready control=%s data=%s",
|
||||
selfPid, tcp ? "tcp" : "unix", tcp ? "stream" : "shm");
|
||||
std::vector<std::uint8_t> buffer(64 * 1024);
|
||||
for (;;) {
|
||||
std::uint64_t size = 0;
|
||||
MobileGLMutableByteSpan span{buffer.data(), buffer.size()};
|
||||
const MobileGLResult result = control->ReceiveFrame(span, &size, kWaitForever);
|
||||
if (result == MOBILEGL_ERR_TRANSPORT_CLOSED) {
|
||||
WireLogError("MG_Remote server: pid=%d peer closed the control stream; exiting",
|
||||
selfPid);
|
||||
break;
|
||||
const auto result = control->ReceiveFrame({buffer.data(), buffer.size()}, &size, kWaitForever);
|
||||
if (result == MOBILEGL_ERR_BUFFER_TOO_SMALL) { buffer.resize(static_cast<std::size_t>(size)); continue; }
|
||||
if (result != MOBILEGL_OK) break;
|
||||
flatbuffers::Verifier check(buffer.data(), static_cast<std::size_t>(size));
|
||||
if (!Protocol::VerifyCtrlEnvelopeBuffer(check)) break;
|
||||
const auto* envelope = Protocol::GetCtrlEnvelope(buffer.data());
|
||||
if (const auto* flush = envelope->msg_as_LogFlush()) {
|
||||
if (flush->ack()) break;
|
||||
FlushAck ack{control.get(), flush->seq()};
|
||||
MobileGL::MG_Util::Debug::WithLogBarrier(&SendLogAck, &ack);
|
||||
continue;
|
||||
}
|
||||
if (result == MOBILEGL_ERR_BUFFER_TOO_SMALL) {
|
||||
buffer.resize(static_cast<std::size_t>(size));
|
||||
continue; // still queued: ITransport's contract
|
||||
}
|
||||
if (result != MOBILEGL_OK) {
|
||||
WireLogError("MG_Remote server: pid=%d control stream failed (rc=%d); exiting", selfPid,
|
||||
static_cast<int>(result));
|
||||
break;
|
||||
}
|
||||
// `cp`: decode, apply, reply. THIS THREAD IS NOT THE APPLY THREAD, which
|
||||
// is what CONTRACT-P6 §6.2 requires - a control arrival must not wake the
|
||||
// applier, and frame decode must not happen on the thread holding the
|
||||
// native context. ServerApplyWireSurfaceOp posts through ServerLoop's
|
||||
// existing one-slot mailbox, unchanged.
|
||||
const ::MobileGL::Wire::CtrlEnvelope* envelope = nullptr;
|
||||
{
|
||||
::flatbuffers::Verifier verifier(buffer.data(), static_cast<std::size_t>(size));
|
||||
if (::MobileGL::Wire::VerifyCtrlEnvelopeBuffer(verifier)) {
|
||||
envelope = ::MobileGL::Wire::GetCtrlEnvelope(buffer.data());
|
||||
}
|
||||
}
|
||||
if (envelope == nullptr || envelope->msg_type() != ::MobileGL::Wire::CtrlMsg::SurfaceOp ||
|
||||
envelope->msg_as_SurfaceOp() == nullptr) {
|
||||
WireLogError("MG_Remote server: pid=%d a control frame that is not a verifiable "
|
||||
"SurfaceOp arrived; ignoring it would be the silent drop this project "
|
||||
"refuses, so the session ends here",
|
||||
selfPid);
|
||||
break;
|
||||
}
|
||||
|
||||
const auto* operation = envelope->msg_as_SurfaceOp();
|
||||
if (!operation) break;
|
||||
Server::SurfaceControlFrame reply{};
|
||||
const MobileGLResult applied =
|
||||
ServerApplyWireSurfaceOp(*envelope->msg_as_SurfaceOp(), &reply);
|
||||
(void)applied; // carried in the reply's ok/result, not thrown away here
|
||||
|
||||
::flatbuffers::FlatBufferBuilder builder(256);
|
||||
(void)ServerApplyWireSurfaceOp(*operation, &reply);
|
||||
session.FlushDataProgress();
|
||||
if (session.DataLink()) reply.eventHead = session.DataLink()->EventPublishedHead();
|
||||
flatbuffers::FlatBufferBuilder builder(256);
|
||||
EncodeSurfaceReplyFrame(reply, &builder);
|
||||
if (control->SendFrame(
|
||||
MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}) != MOBILEGL_OK) {
|
||||
break;
|
||||
}
|
||||
if (control->SendFrame({builder.GetBufferPointer(), builder.GetSize()}) != MOBILEGL_OK) break;
|
||||
}
|
||||
|
||||
loop.Stop();
|
||||
session.Close();
|
||||
control->Shutdown();
|
||||
// Step 1.6's other half, and its position is the same argument MobileGL::Destroy
|
||||
// makes: AFTER loop.Stop(), because the apply thread is what publishes
|
||||
// ServerWaits / ServerParks and a dump taken while it still runs is a race; and
|
||||
// BEFORE _exit, because _exit runs no atexit handler and this is the only place
|
||||
// the server's run totals and its JSON dump can be written at all. Without it
|
||||
// the server printed whatever windows its last period boundary happened to
|
||||
// reach and lost the final one - the same "the last frame's numbers can be
|
||||
// lost" case Init.cpp's Shutdown call exists to avoid on the client side.
|
||||
// Publish the final server window while log forwarding is still attached.
|
||||
MobileGL::MG_Util::PipeStats::Shutdown();
|
||||
session.Close();
|
||||
MobileGL::MG_Util::Debug::SetLogForwarder(nullptr, nullptr);
|
||||
control->Shutdown();
|
||||
std::fflush(nullptr);
|
||||
::_exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" __attribute__((visibility("default"))) int mobilegl_server_main(int argc, char** argv) {
|
||||
const char* dial = std::getenv("MOBILEGL_IPC_DIAL");
|
||||
if (!dial || std::strcmp(dial, "no") != 0) {
|
||||
std::fprintf(stderr, "MG_Remote server: MOBILEGL_IPC_DIAL=no anti-recursion catch (a) missing\n");
|
||||
return 64;
|
||||
}
|
||||
for (const char* name : {"MOBILEGL_TRANSPORT", "MOBILEGL_IPC_SERVER_PATH", "MOBILEGL_IPC_RING_MB", "MOBILEGL_IPC_STAGE_MB"}) {
|
||||
if (std::getenv(name)) {
|
||||
std::fprintf(stderr, "MG_Remote server: envp scrub FAILED, catch (b): %s\n", name);
|
||||
return 65;
|
||||
}
|
||||
}
|
||||
std::string endpoint;
|
||||
bool serve = false;
|
||||
unsigned maxSessions = 0;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg(argv[i]);
|
||||
if (arg == "--serve") serve = true;
|
||||
else if (arg == "--max-sessions" && i + 1 < argc) {
|
||||
char* end = nullptr;
|
||||
const auto count = std::strtoul(argv[++i], &end, 10);
|
||||
if (*end || count == 0 || count > 1000000) return 71;
|
||||
maxSessions = static_cast<unsigned>(count);
|
||||
} else if (endpoint.empty() && arg.compare(0, 2, "--") != 0) endpoint = arg;
|
||||
else return 71;
|
||||
}
|
||||
if (endpoint.empty()) if (const char* value = std::getenv("MOBILEGL_IPC_ENDPOINT")) endpoint = value;
|
||||
if (endpoint.empty()) return 71;
|
||||
int listener = -1;
|
||||
if (SocketTransport::Listen(endpoint, &listener) != MOBILEGL_OK) return 72;
|
||||
WireLogError("MG_Remote server: pid=%d listening on %s", static_cast<int>(::getpid()), endpoint.c_str());
|
||||
pid_t active = -1;
|
||||
unsigned sessions = 0;
|
||||
for (;;) {
|
||||
if (active > 0 && ::waitpid(active, nullptr, WNOHANG) == active) active = -1;
|
||||
if (maxSessions && sessions >= maxSessions && active < 0) break;
|
||||
std::unique_ptr<SocketTransport> control;
|
||||
const auto accepted = SocketTransport::AcceptPair(listener, serve ? 250 : 30000, control);
|
||||
if (accepted == MOBILEGL_ERR_TIMEOUT && serve) continue;
|
||||
if (accepted != MOBILEGL_OK) { ::close(listener); return 73; }
|
||||
if (!serve) {
|
||||
::close(listener);
|
||||
if (endpoint[0] != '@' && endpoint.compare(0, 6, "tcp://") != 0) ::unlink(endpoint.c_str());
|
||||
RunSession(std::move(control));
|
||||
}
|
||||
if (active > 0 && ::waitpid(active, nullptr, WNOHANG) == active) active = -1;
|
||||
if (active > 0 || (maxSessions && sessions >= maxSessions)) {
|
||||
Refuse(*control, Protocol::RefuseCode::Busy, "one session is already active");
|
||||
continue;
|
||||
}
|
||||
std::fflush(nullptr);
|
||||
const pid_t child = ::fork();
|
||||
if (child == 0) { ::close(listener); RunSession(std::move(control)); }
|
||||
if (child < 0) { Refuse(*control, Protocol::RefuseCode::Busy, "could not create session child"); continue; }
|
||||
active = child;
|
||||
++sessions;
|
||||
control->CloseLocalCopy();
|
||||
}
|
||||
::close(listener);
|
||||
if (endpoint[0] != '@' && endpoint.compare(0, 6, "tcp://") != 0) ::unlink(endpoint.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
|
||||
// P5 package s1: the server half of a session.
|
||||
|
||||
#include "../Transport/SocketTransport.h"
|
||||
#include "ServerSession.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#include "../CapsCodec.h"
|
||||
#include "../Handshake.h"
|
||||
#include "../Protocol/generated/protocol_generated.h"
|
||||
#include "../Transport/InProcessTransport.h"
|
||||
|
||||
@@ -109,23 +111,6 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return ::MobileGL::Wire::GetCtrlEnvelope(bytes.data());
|
||||
}
|
||||
|
||||
[[noreturn]] void FatalAbiMismatch(const char* what, Uint64 ours, Uint64 theirs,
|
||||
const char* ourStamp, const char* theirStamp) {
|
||||
// NEVER a downgrade. Every alternative to aborting here reads one struct as
|
||||
// another - MGPCaps has only a compositional size assertion because
|
||||
// DynamicBackendParameters still carries SizeT, so a peer built from a different
|
||||
// tree hands over a caps block whose members are at different offsets and whose
|
||||
// bytes are all individually plausible.
|
||||
SessionFail(MGFatalFamily::AbiMismatch, "MGPipe: Fatal{AbiMismatch, \"%s\"} ours=%llu theirs=%llu ourBuild=%s "
|
||||
"theirBuild=%s - the two peers were not built from the same struct shapes, "
|
||||
"and there is no downgrade path: the caps block's size is ABI-dependent "
|
||||
"(MGPipeTypes.h:145-146) and every field past the first difference would be "
|
||||
"read at the wrong offset",
|
||||
what, static_cast<unsigned long long>(ours),
|
||||
static_cast<unsigned long long>(theirs),
|
||||
ourStamp == nullptr ? "?" : ourStamp, theirStamp == nullptr ? "?" : theirStamp);
|
||||
}
|
||||
|
||||
// MOBILEGL_IPC_RING_MB / MOBILEGL_IPC_STAGE_MB, actually applied.
|
||||
//
|
||||
// The first version of this file called this only `if (m_sizes.CmdBytes == 0)`, and
|
||||
@@ -246,17 +231,17 @@ namespace MobileGL::MG_Remote::Server {
|
||||
static_cast<unsigned long long>(cap));
|
||||
}
|
||||
|
||||
Transport::RingControl& control = session.Control();
|
||||
Transport::Doorbell& bell = session.ConsumerDoorbell();
|
||||
const auto drained = [&control] {
|
||||
return control.eventRingFull.load(std::memory_order_acquire) == 0;
|
||||
const auto signals = session.DataLink()->Signals();
|
||||
Transport::Doorbell& bell = session.DataLink()->ConsumerBell();
|
||||
const auto drained = [signals] {
|
||||
return signals.EventRingFull->load(std::memory_order_acquire) == 0;
|
||||
};
|
||||
for (Uint32 round = 0; round < 2; ++round) {
|
||||
// PUBLISH AND RING FIRST. The client cannot drain a ring whose head it has not
|
||||
// been shown, and the latch Reserve just set is only useful to a client that
|
||||
// gets a bell with it.
|
||||
session.PublishEvents();
|
||||
if (!drained() && !bell.Wait(control.consumerParked, drained, /*spinUs=*/0,
|
||||
if (!drained() && !bell.Wait(*signals.ConsumerParked, drained, /*spinUs=*/0,
|
||||
kEventBacklogWaitMs)) {
|
||||
SessionFail(MGFatalFamily::EventRingOverflow, "MGPipe: Fatal{EventRingOverflow} - %s waited %u ms for the client "
|
||||
"to drain SEG_EVENT and it never did. Under run-ahead the apply "
|
||||
@@ -394,6 +379,11 @@ namespace MobileGL::MG_Remote::Server {
|
||||
|
||||
ServerSession* ServerSession::Active() { return g_active.load(std::memory_order_acquire); }
|
||||
|
||||
ServerSession::ServerSession() {
|
||||
m_link = Transport::CreateSharedLink(Transport::TransportRoleTag::ServerConsumer);
|
||||
m_commands = &m_link->CommandsIn(); m_events = &m_link->EventsOut();
|
||||
}
|
||||
|
||||
ServerSession::~ServerSession() { Close(); }
|
||||
|
||||
void ServerSession::SetSegmentSizes(const Transport::SessionSegmentSizes& sizes) {
|
||||
@@ -429,7 +419,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
|
||||
Bool ServerSession::Accepted() const { return m_accepted; }
|
||||
|
||||
MobileGLResult ServerSession::Accept(Transport::ITransport& transport) {
|
||||
MobileGLResult ServerSession::Accept(Transport::ITransport& transport,
|
||||
const std::vector<Uint8>* firstFrame) {
|
||||
ServerSession* expectedOwner = nullptr;
|
||||
if (!g_sessionOwner.compare_exchange_strong(expectedOwner, this,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire)) {
|
||||
@@ -457,7 +448,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// ---- 1/2. the ABI assertion, BEFORE a single record is decoded and before a byte of
|
||||
// shared memory exists. Its whole purpose is to refuse to interpret the peer's bytes.
|
||||
std::vector<Uint8> frame;
|
||||
const MobileGLResult received = ReceiveEnvelope(transport, frame, kHandshakeTimeoutMs);
|
||||
if (firstFrame != nullptr) frame = *firstFrame;
|
||||
const MobileGLResult received = firstFrame != nullptr ? MOBILEGL_OK
|
||||
: ReceiveEnvelope(transport, frame, kHandshakeTimeoutMs);
|
||||
if (received != MOBILEGL_OK) {
|
||||
MGLOG_E("MG_Remote server: no Hello within %u ms (rc=%d)", kHandshakeTimeoutMs,
|
||||
static_cast<int>(received));
|
||||
@@ -472,57 +465,69 @@ namespace MobileGL::MG_Remote::Server {
|
||||
if (envelope == nullptr || envelope->msg_type() != ::MobileGL::Wire::CtrlMsg::Hello ||
|
||||
envelope->msg_as_Hello() == nullptr) {
|
||||
MGLOG_E("MG_Remote server: the first control frame is not a verifiable Hello");
|
||||
return MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::MalformedHello,
|
||||
"first control frame is not a verifiable Hello");
|
||||
}
|
||||
const ::MobileGL::Wire::Hello* hello = envelope->msg_as_Hello();
|
||||
const char* theirStamp =
|
||||
hello->buildFingerprint() == nullptr ? nullptr : hello->buildFingerprint()->c_str();
|
||||
|
||||
if (hello->abiMajor() != static_cast<Uint32>(MOBILEGL_PROTOCOL_ABI_MAJOR) ||
|
||||
hello->abiMinor() != static_cast<Uint32>(MOBILEGL_PROTOCOL_ABI_MINOR)) {
|
||||
FatalAbiMismatch("protocol version",
|
||||
MOBILEGL_ABI_VERSION(MOBILEGL_PROTOCOL_ABI_MAJOR,
|
||||
MOBILEGL_PROTOCOL_ABI_MINOR),
|
||||
MOBILEGL_ABI_VERSION(hello->abiMajor(), hello->abiMinor()),
|
||||
GIT_COMMIT_HASH_SHORT, theirStamp);
|
||||
}
|
||||
const Uint64 ourFingerprint = CapsAbiFingerprint();
|
||||
if (hello->abiFingerprint() != ourFingerprint) {
|
||||
FatalAbiMismatch("struct shapes", ourFingerprint, hello->abiFingerprint(),
|
||||
GIT_COMMIT_HASH_SHORT, theirStamp);
|
||||
}
|
||||
if (hello->dialMode() != ::MobileGL::Wire::DialMode::No &&
|
||||
hello->dialMode() != ::MobileGL::Wire::DialMode::Fork &&
|
||||
hello->dialMode() != ::MobileGL::Wire::DialMode::Connect)
|
||||
return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::MalformedHello,
|
||||
"unknown dial mode");
|
||||
const MobileGLResult compatible = ValidatePeerHandshake(transport, hello->abiMajor(),
|
||||
hello->abiMinor(), hello->wireFingerprint(), theirStamp, hello->dialMode());
|
||||
if (compatible != MOBILEGL_OK) return compatible;
|
||||
const Uint64 ourFingerprint = WireFingerprint();
|
||||
const auto* terms = hello->linkTerms();
|
||||
if (terms == nullptr || terms->wireForm() != ::MobileGL::Wire::WireForm::StructImage ||
|
||||
(terms->dataPlane() != ::MobileGL::Wire::DataPlane::SharedSegments &&
|
||||
terms->dataPlane() != ::MobileGL::Wire::DataPlane::Stream))
|
||||
return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::LinkTerms,
|
||||
"unsupported or missing link terms");
|
||||
const bool stream = terms->dataPlane() == ::MobileGL::Wire::DataPlane::Stream;
|
||||
if (stream && transport.Role() == Transport::TransportRole::InProcess)
|
||||
return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::LinkTerms,
|
||||
"stream requires a data connection");
|
||||
const char* expectedToken = std::getenv("MOBILEGL_IPC_TOKEN");
|
||||
if (transport.Role() != Transport::TransportRole::InProcess && expectedToken != nullptr &&
|
||||
expectedToken[0] != '\0' && (hello->token() == nullptr ||
|
||||
std::strcmp(hello->token()->c_str(), expectedToken) != 0))
|
||||
return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::Authentication,
|
||||
"token mismatch");
|
||||
if (hello->backendType() >= static_cast<Uint32>(BackendType::BackendTypeCount) ||
|
||||
(m_backend != nullptr && hello->backendType() != static_cast<Uint32>(m_backend->GetBackendType())))
|
||||
return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::Backend,
|
||||
"backend request differs from server", m_backend == nullptr ? 0 :
|
||||
static_cast<Uint32>(m_backend->GetBackendType()), hello->backendType());
|
||||
|
||||
// ---- 3. the four segments, both control pages, the rings.
|
||||
const MobileGLResult created = m_shm.Create(m_sizes, Transport::MemoryRole::Server);
|
||||
MobileGLResult created = MOBILEGL_ERR_UNSUPPORTED;
|
||||
if (stream) {
|
||||
auto* socket = dynamic_cast<Transport::SocketTransport*>(&transport);
|
||||
if (socket && socket->IsTcp()) created = AttachStreamLink(socket->TakeDataFd(), m_sizes);
|
||||
} else {
|
||||
created = m_link->Memory().Create(m_sizes, Transport::MemoryRole::Server);
|
||||
}
|
||||
if (created != MOBILEGL_OK) {
|
||||
return created;
|
||||
}
|
||||
|
||||
Transport::RingControl* control = m_shm.CmdControl();
|
||||
m_commands = Transport::RingConsumer(control, m_shm.CmdRingBase(), m_shm.CmdRingCapacity(),
|
||||
Transport::RingCursorSet::Cmd);
|
||||
if (!m_commands.Valid()) {
|
||||
m_link->InitializeEndpoints();
|
||||
if (!m_commands->Valid()) {
|
||||
Close();
|
||||
return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
m_consumer.Attach(control, &m_commands, &ProducerDoorbell(), &ConsumerDoorbell(),
|
||||
SpinUsFromConfig());
|
||||
m_link->BindDoorbells(&ConsumerDoorbell(), &ProducerDoorbell());
|
||||
m_consumer.Attach(*m_link, SpinUsFromConfig());
|
||||
m_replies = ReplyPool();
|
||||
m_replies.SetLink(m_link.get());
|
||||
|
||||
{
|
||||
Transport::ReplySlotPool pool(m_shm.ReplyBase(), m_shm.ReplyBytes(),
|
||||
m_shm.ReplySlotCount());
|
||||
if (!pool.Valid()) {
|
||||
Close();
|
||||
return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
// A stale stamp from a previous session must never read as this session's answer.
|
||||
pool.Clear();
|
||||
m_replies = ReplyPool(m_shm.ReplyBase(), m_shm.ReplyBytes(), m_shm.ReplySlotCount(),
|
||||
pool.SlotBytes());
|
||||
}
|
||||
|
||||
m_events = Transport::EventRingProducer(m_shm.EventControl(), control, m_shm.EventRingBase(),
|
||||
m_shm.EventRingCapacity());
|
||||
m_consumer.SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get());
|
||||
m_replies.SetLink(m_link.get());
|
||||
m_applier = PipeApplier(&m_segments, &m_replies);
|
||||
|
||||
// ---- 4. Welcome: the four SegmentRefs, plus this side's half of the ABI statement.
|
||||
@@ -531,14 +536,18 @@ namespace MobileGL::MG_Remote::Server {
|
||||
using Slot = Transport::SessionSegmentSlot;
|
||||
const auto segmentRef = [&](Uint32 id, ::MobileGL::Wire::SegmentKind kind, Slot slot) {
|
||||
return ::MobileGL::Wire::CreateSegmentRefDirect(builder, id, kind,
|
||||
m_shm.AnnouncedSize(slot),
|
||||
m_shm.AnnouncedName(slot));
|
||||
m_link->Memory().AnnouncedSize(slot),
|
||||
m_link->Memory().AnnouncedName(slot));
|
||||
};
|
||||
auto cmd = segmentRef(1, ::MobileGL::Wire::SegmentKind::Cmd, Slot::Cmd);
|
||||
auto stage = segmentRef(2, ::MobileGL::Wire::SegmentKind::Stage, Slot::Stage);
|
||||
auto reply = segmentRef(3, ::MobileGL::Wire::SegmentKind::Reply, Slot::Reply);
|
||||
auto event = segmentRef(4, ::MobileGL::Wire::SegmentKind::Event, Slot::Event);
|
||||
auto stamp = builder.CreateString(GIT_COMMIT_HASH_SHORT);
|
||||
auto stamp = builder.CreateString(BuildFingerprint());
|
||||
const Uint64 maxReply = m_sizes.ReplyBytes / m_sizes.ReplySlotCount - sizeof(Transport::ReplySlotHeader);
|
||||
auto negotiated = ::MobileGL::Wire::CreateLinkTerms(builder, terms->dataPlane(),
|
||||
::MobileGL::Wire::WireForm::StructImage, maxReply, m_link->Memory().CmdRingCapacity(),
|
||||
m_link->Memory().StageBytes(), m_link->Memory().EventRingCapacity());
|
||||
auto welcome = ::MobileGL::Wire::CreateWelcome(
|
||||
builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR,
|
||||
// CONTRACT-P6 4.3: THE SERVER'S OWN PID, not an echo of the client's.
|
||||
@@ -549,7 +558,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// that a same-process session cannot produce, because there the two pids are
|
||||
// equal by construction.
|
||||
static_cast<Uint32>(SelfProcessId()),
|
||||
cmd, stage, reply, event, stamp, ourFingerprint);
|
||||
cmd, stage, reply, event, stamp, ourFingerprint, ourFingerprint, negotiated,
|
||||
m_backend == nullptr ? hello->backendType() : static_cast<Uint32>(m_backend->GetBackendType()));
|
||||
auto root = ::MobileGL::Wire::CreateCtrlEnvelope(
|
||||
builder, ::MobileGL::Wire::CtrlMsg::Welcome, welcome.Union());
|
||||
::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root);
|
||||
@@ -571,15 +581,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// w1 lands. That is deliberate and is where the bring-up currently stops: a session
|
||||
// that skipped them and carried on would be a decoder with no segments, which is the
|
||||
// "split lane ran monolith and went green" shape.
|
||||
m_segments.Install(Wire::kSegCmd,
|
||||
Wire::SegmentView{m_shm.CmdRingBase(), m_shm.CmdRingCapacity()});
|
||||
m_segments.Install(Wire::kSegStage,
|
||||
Wire::SegmentView{m_shm.StageBase(), m_shm.StageBytes()});
|
||||
m_segments.Install(Wire::kSegReply,
|
||||
Wire::SegmentView{m_shm.ReplyBase(), m_shm.ReplyBytes()});
|
||||
m_segments.Install(Wire::kSegEvent, Wire::SegmentView{m_shm.EventSegmentBase(),
|
||||
m_shm.AnnouncedSize(
|
||||
Transport::SessionSegmentSlot::Event)});
|
||||
m_segments.AttachLink(m_link.get());
|
||||
m_segments.InstallProcessResolver();
|
||||
|
||||
m_accepted = true;
|
||||
@@ -671,6 +673,26 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return SendEnvelope(*m_transport, builder);
|
||||
}
|
||||
|
||||
MobileGLResult ServerSession::AttachStreamLink(int fd, const Transport::SessionSegmentSizes& sizes) {
|
||||
std::unique_ptr<Transport::ILink> link;
|
||||
const auto attached = Transport::CreateStreamLink(fd, sizes, Transport::TransportRoleTag::ServerConsumer, link);
|
||||
if (attached != MOBILEGL_OK) return attached;
|
||||
AttachDataLink(std::move(link));
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
|
||||
void ServerSession::AttachDataLink(std::unique_ptr<Transport::ILink> link) {
|
||||
m_link = std::move(link);
|
||||
m_commands = &m_link->CommandsIn(); m_events = &m_link->EventsOut();
|
||||
m_consumer.SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get());
|
||||
m_replies.SetLink(m_link.get());
|
||||
if (m_link) SetExternalDoorbells(&m_link->ConsumerBell(), &m_link->ProducerBell());
|
||||
}
|
||||
|
||||
void ServerSession::FlushDataProgress() {
|
||||
if (m_link) m_link->FlushProgress();
|
||||
}
|
||||
|
||||
void ServerSession::Close() {
|
||||
ServerSession* expectedActive = this;
|
||||
g_active.compare_exchange_strong(expectedActive, nullptr,
|
||||
@@ -695,10 +717,13 @@ namespace MobileGL::MG_Remote::Server {
|
||||
}
|
||||
}
|
||||
m_consumer.Detach();
|
||||
m_commands = Transport::RingConsumer();
|
||||
m_events = Transport::EventRingProducer();
|
||||
*m_commands = Transport::RingConsumer();
|
||||
*m_events = Transport::EventRingProducer();
|
||||
m_replies = ReplyPool();
|
||||
m_shm.Close();
|
||||
m_link = Transport::CreateSharedLink(Transport::TransportRoleTag::ServerConsumer);
|
||||
m_commands = &m_link->CommandsIn(); m_events = &m_link->EventsOut();
|
||||
m_externalConsumerBell = nullptr; m_externalProducerBell = nullptr;
|
||||
m_link->Memory().Close();
|
||||
m_transport = nullptr;
|
||||
m_accepted = false;
|
||||
ServerSession* expectedOwner = this;
|
||||
@@ -706,10 +731,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
std::memory_order_release, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
Transport::RingConsumer& ServerSession::CommandRing() { return m_commands; }
|
||||
Transport::RingConsumer& ServerSession::CommandRing() { return *m_commands; }
|
||||
|
||||
Transport::RingControl& ServerSession::Control() {
|
||||
Transport::RingControl* control = m_shm.CmdControl();
|
||||
Transport::RingControl* control = m_link->Memory().CmdControl();
|
||||
if (control == nullptr) {
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ServerSession::Control\"} - the control "
|
||||
"page does not exist until Accept() has mapped SEG_CMD");
|
||||
@@ -766,9 +791,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
static_cast<unsigned>(m_transport->Role()));
|
||||
}
|
||||
|
||||
Transport::SessionSegments& ServerSession::Shm() { return m_shm; }
|
||||
Transport::SessionSegments& ServerSession::Shm() { return m_link->Memory(); }
|
||||
Transport::SessionConsumer& ServerSession::Consumer() { return m_consumer; }
|
||||
Transport::EventRingProducer& ServerSession::Events() { return m_events; }
|
||||
Transport::EventRingProducer& ServerSession::Events() { return *m_events; }
|
||||
Transport::ITransport* ServerSession::Control_Plane() { return m_transport; }
|
||||
|
||||
void ServerSession::PublishEvents() {
|
||||
@@ -777,8 +802,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
}
|
||||
// Publish, THEN ring - the same order as the forward direction, and the session picks
|
||||
// the bell so that no caller can pair the right ring with the wrong flag.
|
||||
m_events.Ring().Publish();
|
||||
m_events->Ring().Publish();
|
||||
m_consumer.NotifyClient();
|
||||
FlushDataProgress();
|
||||
}
|
||||
|
||||
void ServerSession::PostGlError(Uint32 code, const char* message) {
|
||||
@@ -820,6 +846,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return;
|
||||
}
|
||||
m_consumer.CompleteFrame(serial);
|
||||
FlushDataProgress();
|
||||
}
|
||||
|
||||
void ServerSession::ReturnPresentCredit(Uint64 serial) {
|
||||
@@ -827,6 +854,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return;
|
||||
}
|
||||
m_consumer.ReturnPresentCredit(serial);
|
||||
FlushDataProgress();
|
||||
}
|
||||
|
||||
Transport::RoleMemorySample ServerSession::SampleMemory() const {
|
||||
|
||||
@@ -40,7 +40,9 @@
|
||||
// refused to exist until both landed would block every other package's bring-up.
|
||||
|
||||
#pragma once
|
||||
#include "../Transport/ILink.h"
|
||||
#include <Includes.h>
|
||||
#include <vector>
|
||||
|
||||
#include <MG_Pipe/MGPipe.h>
|
||||
|
||||
@@ -60,13 +62,19 @@ namespace MobileGL::MG_Remote::Server {
|
||||
public:
|
||||
static ServerSession* Active();
|
||||
|
||||
ServerSession();
|
||||
~ServerSession();
|
||||
MobileGLResult AttachStreamLink(int dataFd, const Transport::SessionSegmentSizes& sizes);
|
||||
void AttachDataLink(std::unique_ptr<Transport::ILink> link);
|
||||
Transport::ILink* DataLink() const { return m_link.get(); }
|
||||
void FlushDataProgress();
|
||||
|
||||
// Maps the four segments, answers Hello with Welcome, and publishes the first
|
||||
// CapsSnapshot. The ABI assertion (CapsCodec.h) happens HERE, before a single record is
|
||||
// decoded: sizeof(DynamicBackendParameters), sizeof(MGPCaps), sizeof(GLFunctionsTable)
|
||||
// and the build fingerprint must match, and a mismatch is Fatal{AbiMismatch}.
|
||||
MobileGLResult Accept(Transport::ITransport& transport);
|
||||
MobileGLResult Accept(Transport::ITransport& transport,
|
||||
const std::vector<Uint8>* firstFrame = nullptr);
|
||||
|
||||
// Re-publishes the whole snapshot. R-12: a SECOND arrival IS the invalidation signal,
|
||||
// which is how DirectGLES - which has no OnCapsInvalidated producer - tells the client
|
||||
@@ -199,17 +207,17 @@ namespace MobileGL::MG_Remote::Server {
|
||||
void LogMemory(const char* phase) const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Transport::ILink> m_link;
|
||||
// sm: non-owning; the spawn entry point owns the SocketDoorbells.
|
||||
Transport::Doorbell* m_externalConsumerBell = nullptr;
|
||||
Transport::Doorbell* m_externalProducerBell = nullptr;
|
||||
Transport::RingConsumer m_commands;
|
||||
Transport::RingConsumer* m_commands = nullptr;
|
||||
Wire::SegmentTable m_segments;
|
||||
PipeApplier m_applier;
|
||||
ReplyPool m_replies;
|
||||
|
||||
Transport::SessionSegments m_shm;
|
||||
Transport::SessionConsumer m_consumer;
|
||||
Transport::EventRingProducer m_events;
|
||||
Transport::EventRingProducer* m_events = nullptr;
|
||||
Transport::ITransport* m_transport = nullptr;
|
||||
MG_Backend::BackendObject* m_backend = nullptr;
|
||||
Transport::SessionSegmentSizes m_sizes;
|
||||
|
||||
@@ -96,6 +96,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Bool ok = false;
|
||||
Int eglMajor = 0;
|
||||
Int eglMinor = 0;
|
||||
Uint64 eventHead = 0; // data-plane delivery fence carried by SurfaceReply
|
||||
};
|
||||
|
||||
namespace Detail {
|
||||
@@ -110,14 +111,15 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// Exhaustive by the language's aggregate decomposition rule: adding ANY member
|
||||
// without extending this binding is a compile error, so new fields cannot evade it.
|
||||
const auto& [kind, seq, display, surface, readSurface, context, windowBackend,
|
||||
nativeToken, width, height, swapInterval, ok, eglMajor, eglMinor] = frame;
|
||||
nativeToken, width, height, swapInterval, ok, eglMajor, eglMinor, eventHead] = frame;
|
||||
return IsSurfaceControlValue<decltype(kind)> && IsSurfaceControlValue<decltype(seq)> &&
|
||||
IsSurfaceControlValue<decltype(display)> && IsSurfaceControlValue<decltype(surface)> &&
|
||||
IsSurfaceControlValue<decltype(readSurface)> && IsSurfaceControlValue<decltype(context)> &&
|
||||
IsSurfaceControlValue<decltype(windowBackend)> && IsSurfaceControlValue<decltype(nativeToken)> &&
|
||||
IsSurfaceControlValue<decltype(width)> && IsSurfaceControlValue<decltype(height)> &&
|
||||
IsSurfaceControlValue<decltype(swapInterval)> && IsSurfaceControlValue<decltype(ok)> &&
|
||||
IsSurfaceControlValue<decltype(eglMajor)> && IsSurfaceControlValue<decltype(eglMinor)>;
|
||||
IsSurfaceControlValue<decltype(eglMajor)> && IsSurfaceControlValue<decltype(eglMinor)> &&
|
||||
IsSurfaceControlValue<decltype(eventHead)>;
|
||||
}
|
||||
} // namespace Detail
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#include "ControlInbox.h"
|
||||
#include "Doorbell.h"
|
||||
#include "../Protocol/SurfaceOpCodec.h"
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
#include <chrono>
|
||||
|
||||
namespace MobileGL::MG_Remote::Transport {
|
||||
ControlInbox::ControlInbox(ITransport& transport) : m_transport(transport), m_reader([this] { Read(); }) {}
|
||||
ControlInbox::~ControlInbox() { Stop(); }
|
||||
|
||||
void ControlInbox::Stop() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_stop = true;
|
||||
m_cv.notify_all();
|
||||
}
|
||||
// Receive uses a short bounded wait, so no shutdown of the data plane is needed.
|
||||
if (m_reader.joinable()) m_reader.join();
|
||||
}
|
||||
|
||||
void ControlInbox::Read() {
|
||||
for (;;) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_stop) return;
|
||||
}
|
||||
std::uint64_t size = 0;
|
||||
auto result = m_transport.ReceiveFrame({nullptr, 0}, &size, 100);
|
||||
if (result == MOBILEGL_ERR_TIMEOUT) continue;
|
||||
std::vector<std::uint8_t> frame;
|
||||
if (result == MOBILEGL_ERR_BUFFER_TOO_SMALL && size <= 64ull * 1024 * 1024) {
|
||||
frame.resize(static_cast<std::size_t>(size));
|
||||
result = m_transport.ReceiveFrame({frame.data(), frame.size()}, &size, 0);
|
||||
}
|
||||
if (result == MOBILEGL_OK) {
|
||||
flatbuffers::Verifier verifier(frame.data(), frame.size());
|
||||
if (!::MobileGL::Wire::VerifyCtrlEnvelopeBuffer(verifier)) result = MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
else {
|
||||
const auto* envelope = ::MobileGL::Wire::GetCtrlEnvelope(frame.data());
|
||||
if (const auto* log = envelope->msg_as_LogLine()) {
|
||||
if (log->text()) MG_Util::Debug::WritePeerLog(log->text()->c_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
if (result != MOBILEGL_OK) {
|
||||
m_result = result;
|
||||
m_cv.notify_all();
|
||||
return;
|
||||
}
|
||||
m_cv.wait(lock, [&] { return m_stop || (m_frames.size() < 256 && m_bytes < 64ull * 1024 * 1024); });
|
||||
if (m_stop) return;
|
||||
m_bytes += frame.size();
|
||||
m_frames.push_back(std::move(frame));
|
||||
m_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
MobileGLResult ControlInbox::Receive(std::vector<std::uint8_t>& frame, std::uint32_t timeoutMs) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
const auto ready = [&] { return m_stop || !m_frames.empty() || m_result != MOBILEGL_OK; };
|
||||
if (timeoutMs == kWaitForever) m_cv.wait(lock, ready);
|
||||
else if (!m_cv.wait_for(lock, std::chrono::milliseconds(timeoutMs), ready)) return MOBILEGL_ERR_TIMEOUT;
|
||||
if (m_frames.empty()) return m_result == MOBILEGL_OK ? MOBILEGL_ERR_TRANSPORT_CLOSED : m_result;
|
||||
frame = std::move(m_frames.front());
|
||||
m_frames.pop_front();
|
||||
m_bytes -= frame.size();
|
||||
m_cv.notify_all();
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
|
||||
std::uint64_t ControlInbox::Peek() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_frames.empty() ? 0 : m_frames.front().size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "ITransport.h"
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace MobileGL::MG_Remote::Transport {
|
||||
// The control reader remains runnable while the GL thread waits on data.
|
||||
// Log forwarding can therefore never fill a socket and block the applier.
|
||||
class ControlInbox {
|
||||
public:
|
||||
explicit ControlInbox(ITransport& transport);
|
||||
~ControlInbox();
|
||||
void Stop();
|
||||
MobileGLResult Receive(std::vector<std::uint8_t>& frame, std::uint32_t timeoutMs);
|
||||
std::uint64_t Peek();
|
||||
private:
|
||||
void Read();
|
||||
ITransport& m_transport;
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
std::deque<std::vector<std::uint8_t>> m_frames;
|
||||
std::uint64_t m_bytes = 0;
|
||||
bool m_stop = false;
|
||||
MobileGLResult m_result = MOBILEGL_OK;
|
||||
std::thread m_reader;
|
||||
};
|
||||
}
|
||||
@@ -186,6 +186,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
// finished with every payload pointer it popped: a writeback's bytes live
|
||||
// in the ring itself, so retiring early is the R-11 violation one level
|
||||
// down.
|
||||
void SetLink(ILink* link) { m_link = link; }
|
||||
void Drained() {
|
||||
m_consumer.PublishRetired();
|
||||
if (m_cmdControl != nullptr) {
|
||||
@@ -202,6 +203,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
NotifyIfParked(*m_serverBell, m_cmdControl->consumerParked);
|
||||
}
|
||||
}
|
||||
if (m_link) m_link->Flush();
|
||||
}
|
||||
|
||||
// The bell the SERVER parks on, lent by the session so that Drained() can ring it.
|
||||
@@ -231,6 +233,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
|
||||
private:
|
||||
RingControl* m_cmdControl = nullptr;
|
||||
ILink* m_link = nullptr;
|
||||
RingConsumer m_consumer;
|
||||
const std::uint8_t* m_segmentBase = nullptr;
|
||||
// P5e (ra): the server's bell, rung by Drained() when it cleared a full latch.
|
||||
|
||||
@@ -72,10 +72,17 @@
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace MobileGL::MG_Remote::Transport {
|
||||
|
||||
class Doorbell;
|
||||
class SessionSegments;
|
||||
struct SessionSegmentSizes;
|
||||
class RingProducer;
|
||||
class RingConsumer;
|
||||
class EventRingProducer;
|
||||
class EventRingConsumer;
|
||||
|
||||
// Which end of the link this instance is. Deliberately NOT ITransport's
|
||||
// TransportRole: that enum's InProcess value names a control-plane
|
||||
@@ -194,6 +201,16 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
std::uint64_t MaxReplyBytes = 0;
|
||||
};
|
||||
|
||||
// Non-owning synchronization addresses, acquired once at attachment. They
|
||||
// denote process-local atomics on a stream and shared atomics on a mapping.
|
||||
struct LinkSignals {
|
||||
std::atomic<std::uint64_t>* CmdHead = nullptr;
|
||||
std::atomic<std::uint64_t>* SubmittedSeq = nullptr;
|
||||
std::atomic<std::uint32_t>* ConsumerParked = nullptr;
|
||||
std::atomic<std::uint32_t>* ProducerParked = nullptr;
|
||||
std::atomic<std::uint32_t>* EventRingFull = nullptr;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// The seam itself.
|
||||
//
|
||||
@@ -211,6 +228,19 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
|
||||
virtual LinkCapabilities Capabilities() const = 0;
|
||||
|
||||
// Setup views. Ownership remains in the link; callers cache the ring
|
||||
// endpoint once so reserve/pop and spin predicates remain non-virtual.
|
||||
virtual SessionSegments& Memory() = 0;
|
||||
virtual void InitializeEndpoints() = 0;
|
||||
LinkSignals Signals();
|
||||
std::uint64_t SegmentSize(LinkSegment segment);
|
||||
virtual RingProducer& CommandsOut() = 0;
|
||||
virtual RingConsumer& CommandsIn() = 0;
|
||||
virtual EventRingProducer& EventsOut() = 0;
|
||||
virtual EventRingConsumer& EventsIn() = 0;
|
||||
virtual void BindDoorbells(Doorbell*, Doorbell*) {}
|
||||
|
||||
|
||||
// The watermark block. Taken once; every wait predicate reads through
|
||||
// the returned pointer with no further virtual dispatch. Never null on
|
||||
// an attached link.
|
||||
@@ -259,6 +289,8 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
|
||||
virtual LinkArena* EventArena() = 0;
|
||||
virtual LinkCursor* EventCursor() = 0;
|
||||
virtual std::uint64_t EventPublishedHead() const = 0;
|
||||
virtual MobileGLResult WaitForEventDelivery(std::uint64_t head, std::uint32_t timeoutMs) = 0;
|
||||
|
||||
// ---- wakeups --------------------------------------------------------
|
||||
//
|
||||
@@ -289,6 +321,14 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
// waiting on. The second method writing StreamLink revealed.
|
||||
virtual MobileGLResult FlushProgress() = 0;
|
||||
|
||||
// The encoder keeps its established stage allocator; the link tracks only the
|
||||
// newly written spans that must precede command delivery.
|
||||
virtual void NoteStage(LinkSpan) {}
|
||||
// Called after local progress advances. Stream links coalesce transmission;
|
||||
// the explicit FlushProgress at every park remains mandatory.
|
||||
virtual void ProgressChanged() {}
|
||||
virtual bool PeerHungUp() const { return false; }
|
||||
|
||||
// ---- lifecycle -------------------------------------------------------
|
||||
|
||||
virtual bool Attached() const = 0;
|
||||
@@ -299,4 +339,9 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
ILink() = default;
|
||||
};
|
||||
|
||||
// Setup factories keep concrete link selection out of codec/session code.
|
||||
std::unique_ptr<ILink> CreateSharedLink(TransportRoleTag role);
|
||||
MobileGLResult CreateStreamLink(int fd, const SessionSegmentSizes& sizes,
|
||||
TransportRoleTag role, std::unique_ptr<ILink>& out);
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Transport
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
#include "LinkMetrics.h"
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#if !defined(_WIN32)
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Remote::Transport {
|
||||
namespace {
|
||||
struct Window {
|
||||
std::uint64_t waitReplies = 0, samples = 0, replyNs = 0, stageBytes = 0;
|
||||
std::array<std::uint64_t, 32> histogram{};
|
||||
};
|
||||
// The client already has exactly one record producer. The server never writes these.
|
||||
struct Metrics {
|
||||
bool active = false;
|
||||
std::uint64_t frame = 0, started = 0, lastFrame = 0, cpuStarted = 0, cpuLast = 0;
|
||||
Window total{}, current{};
|
||||
} metrics;
|
||||
std::uint64_t ClockNs() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
std::uint64_t ThreadCpuNs() {
|
||||
#if defined(CLOCK_THREAD_CPUTIME_ID)
|
||||
timespec value{};
|
||||
if (::clock_gettime(CLOCK_THREAD_CPUTIME_ID, &value) == 0)
|
||||
return std::uint64_t(value.tv_sec) * 1000000000ull + value.tv_nsec;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
std::uint64_t QuantileUpperUs(const Window& w, unsigned percentile) {
|
||||
if (w.samples == 0) return 0;
|
||||
const auto target = (w.samples * percentile + 99) / 100;
|
||||
std::uint64_t count = 0;
|
||||
for (unsigned i = 0; i < w.histogram.size(); ++i) {
|
||||
count += w.histogram[i];
|
||||
if (count >= target) return 1ull << i;
|
||||
}
|
||||
return 1ull << 31;
|
||||
}
|
||||
void Emit(const char* kind, const Window& w, std::uint64_t wallNs, std::uint64_t cpuNs) {
|
||||
char histogram[768]{};
|
||||
std::size_t at = 0;
|
||||
for (unsigned i = 0; i < w.histogram.size(); ++i) {
|
||||
const int n = std::snprintf(histogram + at, sizeof(histogram) - at, "%s%llu",
|
||||
i == 0 ? "" : ",", static_cast<unsigned long long>(w.histogram[i]));
|
||||
if (n < 0 || static_cast<std::size_t>(n) >= sizeof(histogram) - at) break;
|
||||
at += static_cast<std::size_t>(n);
|
||||
}
|
||||
MGLOG_I("P65LinkMetrics kind=%s frame=%llu wait_replies=%llu rtt_samples=%llu "
|
||||
"rtt_mean_us=%.3f rtt_p50_upper_us=%llu rtt_p99_upper_us=%llu "
|
||||
"stage_bytes=%llu wall_ns=%llu client_thread_cpu_ns=%llu rtt_hist_us_pow2=%s",
|
||||
kind, static_cast<unsigned long long>(metrics.frame),
|
||||
static_cast<unsigned long long>(w.waitReplies), static_cast<unsigned long long>(w.samples),
|
||||
w.samples == 0 ? 0.0 : double(w.replyNs) / double(w.samples) / 1000.0,
|
||||
static_cast<unsigned long long>(QuantileUpperUs(w, 50)),
|
||||
static_cast<unsigned long long>(QuantileUpperUs(w, 99)),
|
||||
static_cast<unsigned long long>(w.stageBytes), static_cast<unsigned long long>(wallNs),
|
||||
static_cast<unsigned long long>(cpuNs), histogram);
|
||||
}
|
||||
}
|
||||
void LinkMetricsBegin() {
|
||||
metrics = {};
|
||||
const char* enabled = std::getenv("MOBILEGL_PIPE_STATS");
|
||||
metrics.active = enabled != nullptr && std::strcmp(enabled, "1") == 0;
|
||||
if (!metrics.active) return;
|
||||
metrics.started = metrics.lastFrame = ClockNs();
|
||||
metrics.cpuStarted = metrics.cpuLast = ThreadCpuNs();
|
||||
}
|
||||
std::uint64_t LinkMetricsBeginReply(bool wantsReply) {
|
||||
if (!metrics.active || !wantsReply) return 0;
|
||||
++metrics.current.waitReplies; ++metrics.total.waitReplies;
|
||||
return ClockNs();
|
||||
}
|
||||
void LinkMetricsReplyApplied(std::uint64_t startedNs) {
|
||||
if (!metrics.active || startedNs == 0) return;
|
||||
const auto ns = ClockNs() - startedNs;
|
||||
const auto us = (ns + 999) / 1000;
|
||||
unsigned bucket = 0;
|
||||
while (bucket < 31 && us > (1ull << bucket)) ++bucket;
|
||||
for (Window* w : {&metrics.current, &metrics.total}) {
|
||||
++w->samples; w->replyNs += ns; ++w->histogram[bucket];
|
||||
}
|
||||
}
|
||||
void LinkMetricsStageBytes(std::uint64_t bytes) {
|
||||
if (!metrics.active) return;
|
||||
metrics.current.stageBytes += bytes; metrics.total.stageBytes += bytes;
|
||||
}
|
||||
void LinkMetricsPresent() {
|
||||
if (!metrics.active) return;
|
||||
++metrics.frame;
|
||||
const auto now = ClockNs(), cpu = ThreadCpuNs();
|
||||
Emit("frame", metrics.current, now - metrics.lastFrame, cpu - metrics.cpuLast);
|
||||
metrics.current = {}; metrics.lastFrame = now; metrics.cpuLast = cpu;
|
||||
}
|
||||
void LinkMetricsEnd() {
|
||||
if (!metrics.active) return;
|
||||
Emit("summary", metrics.total, ClockNs() - metrics.started, ThreadCpuNs() - metrics.cpuStarted);
|
||||
metrics.active = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// P6.5 transport measurements. Called only by the single client producer.
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace MobileGL::MG_Remote::Transport {
|
||||
// Disabled sessions never read a clock. Samples are a fixed 32-bucket histogram.
|
||||
void LinkMetricsBegin();
|
||||
void LinkMetricsEnd();
|
||||
std::uint64_t LinkMetricsBeginReply(bool wantsReply);
|
||||
void LinkMetricsReplyApplied(std::uint64_t startedNs);
|
||||
void LinkMetricsStageBytes(std::uint64_t bytes);
|
||||
void LinkMetricsPresent();
|
||||
}
|
||||
@@ -257,6 +257,19 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
std::memcpy(slot, &header, sizeof(header));
|
||||
}
|
||||
|
||||
// The link can lend the immutable payload after appliedSeq without a
|
||||
// second copy. The same seq and size honesty checks guard both readers.
|
||||
bool ReadView(std::uint64_t seq, std::int32_t* status, const void** payload,
|
||||
std::uint64_t* size) const {
|
||||
if (!m_base || !seq || !status || !payload || !size) return false;
|
||||
const auto* slot = SlotAt(seq);
|
||||
ReplySlotHeader header{}; std::memcpy(&header, slot, sizeof header);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
if (header.Seq != seq || header.Size > MaxReplyBytes()) return false;
|
||||
*status = header.Status; *size = header.Size;
|
||||
*payload = slot + sizeof header; return true;
|
||||
}
|
||||
|
||||
// Client side. Returns false when the slot does not carry THIS seq - the
|
||||
// self-check the stamp exists for. `outBytes` may be null for an answer
|
||||
// with no payload (every DECLINE, and the four Bool acceptances).
|
||||
|
||||
@@ -417,6 +417,11 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
// SessionProducer
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void SessionProducer::Attach(ILink& link, std::uint32_t spinUs) {
|
||||
Attach(link.Memory().CmdControl(), &link.CommandsOut(), &link.ConsumerBell(), &link.ProducerBell(), spinUs);
|
||||
SetLink(link.Capabilities().PublishIsDelivery ? nullptr : &link);
|
||||
}
|
||||
|
||||
void SessionProducer::Attach(RingControl* control, RingProducer* cmd, Doorbell* peerBell,
|
||||
Doorbell* selfBell, std::uint32_t spinUs) {
|
||||
m_control = control;
|
||||
@@ -432,6 +437,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
m_peerBell = nullptr;
|
||||
m_selfBell = nullptr;
|
||||
m_lastPublishedSeq = 0;
|
||||
m_link = nullptr;
|
||||
}
|
||||
|
||||
void SessionProducer::PublishAndNotify(std::uint64_t submittedSeq) {
|
||||
@@ -469,6 +475,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
// COUNTED BEFORE THE WAIT AND ONLY WHEN ONE REALLY HAPPENS - after the two refusals
|
||||
// above, which answer TimedOut without ever reaching the bell. See Waits()/Parks() in
|
||||
// the header for why the pair is the reading and either number alone is not.
|
||||
if (m_link && m_link->Flush() != MOBILEGL_OK) return SessionWait::ShutDown;
|
||||
m_waits.fetch_add(1, std::memory_order_relaxed);
|
||||
// &m_parks is the OTHER half of the pair, and it is passed rather than read off the
|
||||
// bell: the bell is this endpoint's and the encoder waits on it too, so its own
|
||||
@@ -547,6 +554,11 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
// SessionConsumer
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void SessionConsumer::Attach(ILink& link, std::uint32_t spinUs) {
|
||||
Attach(link.Memory().CmdControl(), &link.CommandsIn(), &link.ProducerBell(), &link.ConsumerBell(), spinUs);
|
||||
SetLink(link.Capabilities().PublishIsDelivery ? nullptr : &link);
|
||||
}
|
||||
|
||||
void SessionConsumer::Attach(RingControl* control, RingConsumer* cmd, Doorbell* peerBell,
|
||||
Doorbell* selfBell, std::uint32_t spinUs) {
|
||||
m_control = control;
|
||||
@@ -566,6 +578,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
m_selfBell = nullptr;
|
||||
m_retirableCursor = 0;
|
||||
m_borrowHeld = false;
|
||||
m_link = nullptr;
|
||||
}
|
||||
|
||||
SessionWait SessionConsumer::WaitForWork(std::uint32_t timeoutMs) {
|
||||
@@ -574,6 +587,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
}
|
||||
RingControl* control = m_control;
|
||||
RingConsumer* cmd = m_cmd;
|
||||
if (m_link && m_link->FlushProgress() != MOBILEGL_OK) return SessionWait::ShutDown;
|
||||
const bool woke = m_selfBell->Wait(
|
||||
control->consumerParked,
|
||||
[control, cmd] {
|
||||
@@ -647,6 +661,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
}
|
||||
|
||||
void SessionConsumer::NotifyClient() {
|
||||
if (m_link) m_link->ProgressChanged();
|
||||
if (m_control != nullptr && m_peerBell != nullptr) {
|
||||
NotifyIfParked(*m_peerBell, m_control->producerParked);
|
||||
}
|
||||
@@ -671,29 +686,17 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
};
|
||||
mix(inputs.DynamicParamsSize);
|
||||
mix(inputs.CapsSize);
|
||||
mix(inputs.FunctionTableSize);
|
||||
mix(inputs.MemberLayout);
|
||||
mix(inputs.CatalogueLayout);
|
||||
mix(inputs.RenderStateLayout);
|
||||
mix(inputs.FormatCapabilityTargets);
|
||||
mix(inputs.FormatCapabilityFormats);
|
||||
mix(inputs.FormatCapabilitiesCodecVersion);
|
||||
mix(inputs.RendererInfoCodecVersion);
|
||||
mix(inputs.OpCount);
|
||||
mix(inputs.AbiVersion);
|
||||
// CONTRACT-P6 4.2: THE BUILD SYSTEM'S OWN ANSWER, mixed before the bytes and separately
|
||||
// from them. `BuildStampPresent == 0` means the build could not determine a commit at
|
||||
// all; that is a different claim from "the commit is the empty string", and before this
|
||||
// field existed the two collapsed - so every stampless build agreed with every other,
|
||||
// which is the exact agreement the stamp is there to refuse.
|
||||
mix(inputs.BuildStampPresent);
|
||||
// A presence marker before the bytes, so that "no stamp" (nullptr) and
|
||||
// "an empty stamp" ("") are different inputs rather than the same
|
||||
// absence of bytes.
|
||||
mix(inputs.BuildStamp != nullptr ? 1u : 0u);
|
||||
if (inputs.BuildStamp != nullptr) {
|
||||
for (const char* c = inputs.BuildStamp; *c != '\0'; ++c) {
|
||||
hash ^= static_cast<std::uint64_t>(static_cast<unsigned char>(*c));
|
||||
hash *= 1099511628211ull;
|
||||
}
|
||||
}
|
||||
mix(inputs.PointerBits);
|
||||
mix(inputs.LittleEndian);
|
||||
// 0 is reserved for "not stated": a peer that forgot to fill the field
|
||||
// must not accidentally agree with one that did.
|
||||
return hash == 0 ? 1ull : hash;
|
||||
|
||||
@@ -138,6 +138,8 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
// Creates and maps all four, initialises BOTH control pages (SEG_CMD's
|
||||
// and SEG_EVENT's), and books the mapping in `role`'s ledger.
|
||||
MobileGLResult Create(const SessionSegmentSizes& sizes, MemoryRole role);
|
||||
// Private, identically addressed mirrors for the stream data plane.
|
||||
MobileGLResult CreatePrivate(const SessionSegmentSizes& sizes, MemoryRole role);
|
||||
|
||||
// The inproc peer's view of the owner's four segments, booked under the
|
||||
// OTHER role. It does NOT re-init the control pages - there is one shared
|
||||
@@ -210,6 +212,8 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
private:
|
||||
void DeriveViews();
|
||||
|
||||
void* m_private[4] = {};
|
||||
std::uint64_t m_privateSizes[4] = {};
|
||||
ShmSegment m_owned[4]; // empty on an attached (peer) view
|
||||
ShmSegment* m_segments[4] = {nullptr, nullptr, nullptr, nullptr};
|
||||
|
||||
@@ -314,9 +318,11 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
// stageHead with nothing advancing the two tails, so FreeBytes() would
|
||||
// fall to zero the first time the head lapped the capacity and never
|
||||
// recover: a guaranteed hang, not a slow path.
|
||||
void Attach(ILink& link, std::uint32_t spinUs);
|
||||
void Attach(RingControl* control, RingProducer* cmd, Doorbell* peerBell, Doorbell* selfBell,
|
||||
std::uint32_t spinUs);
|
||||
void Detach();
|
||||
void SetLink(ILink* link) { m_link = link; }
|
||||
bool Valid() const { return m_control != nullptr && m_cmd != nullptr; }
|
||||
|
||||
// Publish the command ring's head, record submittedSeq, THEN ring - in
|
||||
@@ -365,6 +371,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
// stalls forever.
|
||||
SessionWait WaitForCmdSpace(std::uint64_t bytes, std::uint32_t timeoutMs);
|
||||
|
||||
LinkProgress* Progress() const { return m_control ? &m_control->Progress : nullptr; }
|
||||
RingControl* Control() const { return m_control; }
|
||||
RingProducer* Cmd() const { return m_cmd; }
|
||||
Doorbell* PeerDoorbell() const { return m_peerBell; }
|
||||
@@ -398,6 +405,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
template <class Ready>
|
||||
SessionWait Park(Ready&& ready, std::uint32_t timeoutMs);
|
||||
|
||||
ILink* m_link = nullptr;
|
||||
RingControl* m_control = nullptr;
|
||||
RingProducer* m_cmd = nullptr;
|
||||
Doorbell* m_peerBell = nullptr;
|
||||
@@ -420,9 +428,11 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
public:
|
||||
SessionConsumer() = default;
|
||||
|
||||
void Attach(ILink& link, std::uint32_t spinUs);
|
||||
void Attach(RingControl* control, RingConsumer* cmd, Doorbell* peerBell, Doorbell* selfBell,
|
||||
std::uint32_t spinUs);
|
||||
void Detach();
|
||||
void SetLink(ILink* link) { m_link = link; }
|
||||
bool Valid() const { return m_control != nullptr && m_cmd != nullptr; }
|
||||
|
||||
// Park until a record is waiting, the session is shut down, or the
|
||||
@@ -520,6 +530,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
// How many kRecBorrowSlot records this consumer has seen. Non-zero in P5
|
||||
// is a finding, not a statistic.
|
||||
std::uint64_t BorrowedRecordsSeen() const { return m_borrowedSeen; }
|
||||
LinkProgress* Progress() const { return m_control ? &m_control->Progress : nullptr; }
|
||||
RingControl* Control() const { return m_control; }
|
||||
RingConsumer* Cmd() const { return m_cmd; }
|
||||
Doorbell* PeerDoorbell() const { return m_peerBell; }
|
||||
@@ -527,6 +538,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
std::uint32_t SpinUs() const { return m_spinUs; }
|
||||
|
||||
private:
|
||||
ILink* m_link = nullptr;
|
||||
RingControl* m_control = nullptr;
|
||||
RingConsumer* m_cmd = nullptr;
|
||||
Doorbell* m_peerBell = nullptr;
|
||||
@@ -543,58 +555,25 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// The ABI fingerprint's mixer - THE ONE IMPLEMENTATION.
|
||||
//
|
||||
// CapsCodec.cpp's CapsAbiFingerprint(), the value both handshakes compare,
|
||||
// is exactly MixAbiFingerprint(CapsAbiFingerprintInputs()). It lives under
|
||||
// Transport/ so that it can be tested without the GL frontend's umbrella
|
||||
// header, and it takes its inputs as a struct so that the SAME function the
|
||||
// handshake calls can be driven with one field perturbed at a time.
|
||||
//
|
||||
// The wave-1 review (ID-46 finding 6) found the previous shape - a
|
||||
// five-argument mixer here and a SEPARATE hand-rolled FNV loop in
|
||||
// CapsCodec.cpp - had exactly one caller of this function: the sensitivity
|
||||
// test. Production never called it, so replacing CapsAbiFingerprint() with
|
||||
// `return 1;` left every fingerprint test green and both peers agreeing on
|
||||
// nothing. Now there is one mixer, the sensitivity case starts from the
|
||||
// production entry point, and that perturbation turns it red.
|
||||
// P6.5 wire fingerprint: one mixer, driven by compiler-derived layout facts.
|
||||
// CapsCodec supplies generated member/catalogue digests; build identity and
|
||||
// negotiated window sizes do not participate in wire compatibility.
|
||||
// -----------------------------------------------------------------------
|
||||
// Wire facts only. Segment geometry and build identity are negotiated separately.
|
||||
struct AbiFingerprintInputs {
|
||||
// The three struct shapes table 0's ABI-agreement row names.
|
||||
std::uint64_t DynamicParamsSize = 0;
|
||||
std::uint64_t CapsSize = 0;
|
||||
std::uint64_t FunctionTableSize = 0;
|
||||
// The caps blob's own geometry and the two blob codecs' versions: the
|
||||
// format-capability table's extents and the codec version stamps.
|
||||
std::uint64_t MemberLayout = 0;
|
||||
std::uint64_t CatalogueLayout = 0;
|
||||
std::uint64_t RenderStateLayout = 0;
|
||||
std::uint64_t FormatCapabilityTargets = 0;
|
||||
std::uint64_t FormatCapabilityFormats = 0;
|
||||
std::uint64_t FormatCapabilitiesCodecVersion = 0;
|
||||
std::uint64_t RendererInfoCodecVersion = 0;
|
||||
// The catalogue's length (ID-33): a peer with one more opcode is a
|
||||
// different wire even if every struct kept its size.
|
||||
std::uint64_t OpCount = 0;
|
||||
// MOBILEGL_ABI_VERSION(major, minor): a protocol change that left every
|
||||
// struct the same size, which nothing above can see.
|
||||
std::uint32_t AbiVersion = 0;
|
||||
// MOBILEGL_BUILD_STAMP_VALUE: two builds of the same sizes can still
|
||||
// disagree about a FIELD ORDER, which no sizeof can see. nullptr and ""
|
||||
// are distinct inputs and neither equals a real stamp.
|
||||
const char* BuildStamp = nullptr;
|
||||
// CONTRACT-P6 4.2. WHETHER THE BUILD COULD NAME ITS COMMIT AT ALL, which
|
||||
// is not the same question as whether BuildStamp is empty - and telling
|
||||
// them apart is the entire fix.
|
||||
//
|
||||
// The old mix took GIT_COMMIT_HASH_SHORT, and a `git rev-parse` that
|
||||
// FAILED yielded "" with nothing reporting it (no RESULT_VARIABLE).
|
||||
// Every stampless build in the world then mixed the same "" and agreed
|
||||
// with every other, which is precisely the agreement the stamp exists to
|
||||
// refuse. Measured in this worktree: WSL cannot resolve the Windows path
|
||||
// in its `.git` file, so every WSL build was stampless and silent.
|
||||
//
|
||||
// Mixed as its own field so that 0-with-"" and 1-with-"" are different
|
||||
// fingerprints: a build that says "I have no stamp" must not collide
|
||||
// with one whose stamp genuinely is the empty string.
|
||||
std::uint32_t BuildStampPresent = 0;
|
||||
std::uint32_t PointerBits = 0;
|
||||
std::uint32_t LittleEndian = 0;
|
||||
};
|
||||
|
||||
// FNV-1a over every field above, in declaration order. Never 0: that value is
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
#include "ShmLink.h"
|
||||
namespace MobileGL::MG_Remote::Transport {
|
||||
LinkSignals ILink::Signals() {
|
||||
auto* c = Memory().CmdControl();
|
||||
return {&c->cmdHead, &c->submittedSeq, &c->consumerParked, &c->producerParked, &c->eventRingFull};
|
||||
}
|
||||
std::uint64_t ILink::SegmentSize(LinkSegment segment) {
|
||||
auto& m = Memory();
|
||||
switch (segment) {
|
||||
case LinkSegment::Cmd:
|
||||
return m.CmdRingCapacity();
|
||||
case LinkSegment::Stage:
|
||||
return m.StageBytes();
|
||||
case LinkSegment::Reply:
|
||||
return m.ReplyBytes();
|
||||
case LinkSegment::Event:
|
||||
return m.AnnouncedSize(SessionSegmentSlot::Event);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
std::unique_ptr<ILink> CreateSharedLink(TransportRoleTag role) {
|
||||
auto link = std::make_unique<ShmLink>();
|
||||
link->SetRole(role);
|
||||
return link;
|
||||
}
|
||||
void ShmLink::InitializeEndpoints() {
|
||||
auto* c = m_memory.CmdControl();
|
||||
m_commandsOut = RingProducer(c, m_memory.CmdRingBase(), m_memory.CmdRingCapacity(), RingCursorSet::Cmd);
|
||||
m_commandsIn = RingConsumer(c, m_memory.CmdRingBase(), m_memory.CmdRingCapacity(), RingCursorSet::Cmd);
|
||||
m_eventsOut =
|
||||
EventRingProducer(m_memory.EventControl(), c, m_memory.EventRingBase(), m_memory.EventRingCapacity());
|
||||
m_eventsIn = EventRingConsumer(m_memory.EventControl(), c, m_memory.EventRingBase(),
|
||||
m_memory.EventRingCapacity(), m_memory.EventSegmentBase());
|
||||
if (m_role == TransportRoleTag::ServerConsumer) {
|
||||
ReplySlotPool pool(m_memory.ReplyBase(), m_memory.ReplyBytes(), m_memory.ReplySlotCount());
|
||||
pool.Clear();
|
||||
}
|
||||
}
|
||||
void ShmLink::BindDoorbells(Doorbell* c, Doorbell* p) {
|
||||
m_consumer = c;
|
||||
m_producer = p;
|
||||
}
|
||||
LinkCapabilities ShmLink::Capabilities() const {
|
||||
const ReplySlotPool replies(m_memory.ReplyBase(), m_memory.ReplyBytes(), m_memory.ReplySlotCount());
|
||||
return {true, true, true, m_memory.CmdRingCapacity() / 2, replies.MaxReplyBytes()};
|
||||
}
|
||||
LinkProgress* ShmLink::Progress() {
|
||||
return m_memory.Valid() ? &m_memory.CmdControl()->Progress : nullptr;
|
||||
}
|
||||
LinkEventFlags* ShmLink::EventFlags() {
|
||||
return m_memory.Valid() ? reinterpret_cast<LinkEventFlags*>(&m_memory.CmdControl()->eventRingFull) : nullptr;
|
||||
}
|
||||
LinkArena* ShmLink::RecordArena() {
|
||||
m_recordArena = {m_memory.CmdRingBase(), m_memory.CmdRingCapacity(), m_memory.CmdRingCapacity() - 1,
|
||||
m_commandsOut.LocalHead()};
|
||||
return &m_recordArena;
|
||||
}
|
||||
LinkCursor* ShmLink::RecordCursor() {
|
||||
m_recordCursor = {m_memory.CmdRingBase(), m_memory.CmdRingCapacity(), m_memory.CmdRingCapacity() - 1,
|
||||
m_commandsIn.LocalTail()};
|
||||
return &m_recordCursor;
|
||||
}
|
||||
LinkArena* ShmLink::EventArena() {
|
||||
m_eventArena = {m_memory.EventRingBase(), m_memory.EventRingCapacity(), m_memory.EventRingCapacity() - 1,
|
||||
m_eventsOut.Ring().LocalHead()};
|
||||
return &m_eventArena;
|
||||
}
|
||||
LinkCursor* ShmLink::EventCursor() {
|
||||
m_eventCursor = {m_memory.EventRingBase(), m_memory.EventRingCapacity(), m_memory.EventRingCapacity() - 1,
|
||||
m_eventsIn.Ring().LocalTail()};
|
||||
return &m_eventCursor;
|
||||
}
|
||||
MobileGLResult ShmLink::StageBytes(std::uint64_t, LinkSpan*, void**) {
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
MobileGLResult ShmLink::ResolveSpan(LinkSegment segment, LinkSpan span, const void** out) {
|
||||
if (!out || !Attached()) return MOBILEGL_ERR_NOT_INITIALIZED;
|
||||
const void* base = nullptr;
|
||||
std::uint64_t size = 0;
|
||||
switch (segment) {
|
||||
case LinkSegment::Cmd:
|
||||
base = m_memory.CmdRingBase();
|
||||
size = m_memory.CmdRingCapacity();
|
||||
break;
|
||||
case LinkSegment::Stage:
|
||||
base = m_memory.StageBase();
|
||||
size = m_memory.StageBytes();
|
||||
break;
|
||||
case LinkSegment::Reply:
|
||||
base = m_memory.ReplyBase();
|
||||
size = m_memory.ReplyBytes();
|
||||
break;
|
||||
case LinkSegment::Event:
|
||||
base = m_memory.EventSegmentBase();
|
||||
size = m_memory.AnnouncedSize(SessionSegmentSlot::Event);
|
||||
break;
|
||||
default:
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
if (span.Offset > size || span.Size > size - span.Offset) return MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
*out = static_cast<const std::uint8_t*>(base) + span.Offset;
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
MobileGLResult ShmLink::PostReply(std::uint64_t seq, std::int32_t status, const void* payload, std::uint64_t size) {
|
||||
ReplySlotPool replies(m_memory.ReplyBase(), m_memory.ReplyBytes(), m_memory.ReplySlotCount());
|
||||
replies.Post(seq, status, payload, size);
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
MobileGLResult ShmLink::ReadReply(std::uint64_t seq, std::int32_t* status, const void** payload,
|
||||
std::uint64_t* size) {
|
||||
if (!status || !payload || !size) return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
ReplySlotPool replies(m_memory.ReplyBase(), m_memory.ReplyBytes(), m_memory.ReplySlotCount());
|
||||
return replies.ReadView(seq, status, payload, size) ? MOBILEGL_OK : MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
}
|
||||
std::uint64_t ShmLink::EventPublishedHead() const {
|
||||
return m_memory.Valid() ? m_memory.EventControl()->cmdHead.load(std::memory_order_acquire) : 0;
|
||||
}
|
||||
MobileGLResult ShmLink::WaitForEventDelivery(std::uint64_t head, std::uint32_t) {
|
||||
return EventPublishedHead() >= head ? MOBILEGL_OK : MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
}
|
||||
Doorbell& ShmLink::ConsumerBell() {
|
||||
return *m_consumer;
|
||||
}
|
||||
Doorbell& ShmLink::ProducerBell() {
|
||||
return *m_producer;
|
||||
}
|
||||
void ShmLink::Detach() {
|
||||
m_commandsOut = {};
|
||||
m_commandsIn = {};
|
||||
m_eventsOut = {};
|
||||
m_eventsIn = {};
|
||||
m_memory.Close();
|
||||
}
|
||||
} // namespace MobileGL::MG_Remote::Transport
|
||||
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
#pragma once
|
||||
#include "ILink.h"
|
||||
#include "SessionRings.h"
|
||||
#include <vector>
|
||||
namespace MobileGL::MG_Remote::Transport {
|
||||
// Owns the mappings and every physical endpoint. Shared and private mirrored
|
||||
// storage use identical ring algorithms; only delivery belongs to the link.
|
||||
class ShmLink final : public ILink {
|
||||
public:
|
||||
ShmLink() = default;
|
||||
~ShmLink() override = default;
|
||||
SessionSegments& Memory() override { return m_memory; }
|
||||
RingProducer& CommandsOut() override { return m_commandsOut; }
|
||||
RingConsumer& CommandsIn() override { return m_commandsIn; }
|
||||
EventRingProducer& EventsOut() override { return m_eventsOut; }
|
||||
EventRingConsumer& EventsIn() override { return m_eventsIn; }
|
||||
void InitializeEndpoints() override;
|
||||
void BindDoorbells(Doorbell* consumer, Doorbell* producer) override;
|
||||
void SetRole(TransportRoleTag role) { m_role = role; }
|
||||
LinkCapabilities Capabilities() const override;
|
||||
LinkProgress* Progress() override;
|
||||
LinkEventFlags* EventFlags() override;
|
||||
LinkArena* RecordArena() override;
|
||||
LinkCursor* RecordCursor() override;
|
||||
MobileGLResult StageBytes(std::uint64_t, LinkSpan*, void**) override;
|
||||
MobileGLResult ResolveSpan(LinkSegment, LinkSpan, const void**) override;
|
||||
MobileGLResult PostReply(std::uint64_t, std::int32_t, const void*, std::uint64_t) override;
|
||||
MobileGLResult ReadReply(std::uint64_t, std::int32_t*, const void**, std::uint64_t*) override;
|
||||
LinkArena* EventArena() override;
|
||||
LinkCursor* EventCursor() override;
|
||||
std::uint64_t EventPublishedHead() const override;
|
||||
MobileGLResult WaitForEventDelivery(std::uint64_t head, std::uint32_t timeoutMs) override;
|
||||
Doorbell& ConsumerBell() override;
|
||||
Doorbell& ProducerBell() override;
|
||||
MobileGLResult Flush() override { return MOBILEGL_OK; }
|
||||
MobileGLResult FlushProgress() override { return MOBILEGL_OK; }
|
||||
bool Attached() const override { return m_memory.Valid(); }
|
||||
void Detach() override;
|
||||
TransportRoleTag Role() const override { return m_role; }
|
||||
|
||||
private:
|
||||
SessionSegments m_memory;
|
||||
RingProducer m_commandsOut;
|
||||
RingConsumer m_commandsIn;
|
||||
EventRingProducer m_eventsOut;
|
||||
EventRingConsumer m_eventsIn;
|
||||
Doorbell* m_consumer = nullptr;
|
||||
Doorbell* m_producer = nullptr;
|
||||
TransportRoleTag m_role = TransportRoleTag::ClientProducer;
|
||||
LinkArena m_recordArena{}, m_eventArena{};
|
||||
LinkCursor m_recordCursor{}, m_eventCursor{};
|
||||
std::vector<std::uint8_t> m_reply;
|
||||
};
|
||||
} // namespace MobileGL::MG_Remote::Transport
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "RoleMemory.h"
|
||||
#include "SessionRings.h"
|
||||
#include <new>
|
||||
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
@@ -308,6 +309,36 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
|
||||
MobileGLResult SessionSegments::CreatePrivate(const SessionSegmentSizes& sizes, MemoryRole role) {
|
||||
Close();
|
||||
const std::uint64_t bytes[4] = {SegmentBytesForRing(sizes.CmdRingBytes), sizes.StageBytes,
|
||||
sizes.ReplyBytes, SegmentBytesForRing(sizes.EventRingBytes)};
|
||||
for (unsigned i = 0; i != 4; ++i) {
|
||||
if (!bytes[i] || bytes[i] > static_cast<std::uint64_t>(SIZE_MAX)) {
|
||||
Close(); return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
m_private[i] = ::operator new(static_cast<std::size_t>(bytes[i]),
|
||||
std::align_val_t{alignof(RingControl)}, std::nothrow);
|
||||
if (!m_private[i]) { Close(); return MOBILEGL_ERR_INVALID_ARGUMENT; }
|
||||
m_privateSizes[i] = bytes[i];
|
||||
std::memset(m_private[i], 0, static_cast<std::size_t>(bytes[i]));
|
||||
m_mappedBytes += bytes[i];
|
||||
}
|
||||
m_cmdControl = static_cast<RingControl*>(m_private[0]);
|
||||
m_cmdRingBase = static_cast<std::uint8_t*>(m_private[0]) + sizeof(RingControl);
|
||||
m_cmdRingCapacity = RingCapacityForSegment(bytes[0]);
|
||||
m_stageBase = m_private[1]; m_stageBytes = bytes[1];
|
||||
m_replyBase = m_private[2]; m_replyBytes = bytes[2]; m_replySlotCount = sizes.ReplySlotCount;
|
||||
m_eventControl = static_cast<RingControl*>(m_private[3]);
|
||||
m_eventSegmentBase = m_private[3];
|
||||
m_eventRingBase = static_cast<std::uint8_t*>(m_private[3]) + sizeof(RingControl);
|
||||
m_eventRingCapacity = RingCapacityForSegment(bytes[3]);
|
||||
InitRingControl(*m_cmdControl); InitRingControl(*m_eventControl);
|
||||
m_role = role; m_valid = true;
|
||||
LedgerAddSegment(role, m_mappedBytes); m_booked = true;
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
|
||||
MobileGLResult SessionSegments::AttachInProcess(SessionSegments& owner, MemoryRole role) {
|
||||
Close();
|
||||
if (!owner.Valid()) {
|
||||
@@ -497,6 +528,8 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
}
|
||||
for (std::size_t index = 0; index < kSlotCount; ++index) {
|
||||
m_segments[index] = nullptr;
|
||||
if (m_private[index]) ::operator delete(m_private[index], std::align_val_t{alignof(RingControl)});
|
||||
m_private[index] = nullptr; m_privateSizes[index] = 0;
|
||||
}
|
||||
m_cmdControl = nullptr;
|
||||
m_cmdRingBase = nullptr;
|
||||
@@ -516,7 +549,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
|
||||
std::uint64_t SessionSegments::AnnouncedSize(SessionSegmentSlot slot) const {
|
||||
const ShmSegment* segment = m_segments[SlotIndex(slot)];
|
||||
return segment == nullptr ? 0 : segment->Size();
|
||||
return segment == nullptr ? m_privateSizes[SlotIndex(slot)] : segment->Size();
|
||||
}
|
||||
|
||||
const char* SessionSegments::AnnouncedName(SessionSegmentSlot slot) const {
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
#if defined(__unix__) || defined(__APPLE__) || defined(__ANDROID__)
|
||||
# define MOBILEGL_SOCKET_TRANSPORT_POSIX 1
|
||||
@@ -25,6 +29,10 @@
|
||||
# include <sys/socket.h>
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
# include <fcntl.h>
|
||||
# include <netdb.h>
|
||||
# include <netinet/in.h>
|
||||
# include <netinet/tcp.h>
|
||||
#else
|
||||
# define MOBILEGL_SOCKET_TRANSPORT_POSIX 0
|
||||
#endif
|
||||
@@ -36,6 +44,140 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
namespace {
|
||||
constexpr std::uint64_t kPumpChunkBytes = 64ull * 1024;
|
||||
|
||||
bool TcpName(const std::string& name) { return name.compare(0, 6, "tcp://") == 0; }
|
||||
|
||||
bool TcpAddress(const std::string& name, addrinfo** result) {
|
||||
*result = nullptr;
|
||||
const std::string address = name.substr(6);
|
||||
std::string host, port;
|
||||
if (!address.empty() && address[0] == '[') {
|
||||
const auto end = address.find(']');
|
||||
if (end == std::string::npos || end + 1 >= address.size() || address[end + 1] != ':')
|
||||
return false;
|
||||
host = address.substr(1, end - 1);
|
||||
port = address.substr(end + 2);
|
||||
} else {
|
||||
const auto colon = address.find(':');
|
||||
if (colon == std::string::npos || address.find(':', colon + 1) != std::string::npos)
|
||||
return false;
|
||||
host = address.substr(0, colon);
|
||||
port = address.substr(colon + 1);
|
||||
}
|
||||
if (host.empty() || port.empty() || port.find_first_not_of("0123456789") != std::string::npos)
|
||||
return false;
|
||||
char* end = nullptr;
|
||||
const auto number = std::strtoul(port.c_str(), &end, 10);
|
||||
if (*end || number == 0 || number > 65535) return false;
|
||||
addrinfo hints{};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_flags = AI_NUMERICSERV;
|
||||
return ::getaddrinfo(host.c_str(), port.c_str(), &hints, result) == 0;
|
||||
}
|
||||
|
||||
bool Loopback(const sockaddr* address) {
|
||||
if (address->sa_family == AF_INET)
|
||||
return (ntohl(reinterpret_cast<const sockaddr_in*>(address)->sin_addr.s_addr) >> 24) == 127;
|
||||
if (address->sa_family == AF_INET6)
|
||||
return IN6_IS_ADDR_LOOPBACK(&reinterpret_cast<const sockaddr_in6*>(address)->sin6_addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TcpSocket(int fd) {
|
||||
sockaddr_storage address{};
|
||||
socklen_t size = sizeof(address);
|
||||
return ::getsockname(fd, reinterpret_cast<sockaddr*>(&address), &size) == 0 &&
|
||||
(address.ss_family == AF_INET || address.ss_family == AF_INET6);
|
||||
}
|
||||
|
||||
bool ConfigureTcp(int fd) {
|
||||
const int one = 1, idle = 2, interval = 1, count = 3, timeout = 5000;
|
||||
if (::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) != 0 ||
|
||||
::setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one)) != 0) return false;
|
||||
#if defined(TCP_KEEPIDLE)
|
||||
if (::setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)) != 0) return false;
|
||||
#endif
|
||||
#if defined(TCP_KEEPINTVL)
|
||||
if (::setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval)) != 0) return false;
|
||||
#endif
|
||||
#if defined(TCP_KEEPCNT)
|
||||
if (::setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count)) != 0) return false;
|
||||
#endif
|
||||
#if defined(TCP_USER_TIMEOUT)
|
||||
if (::setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout)) != 0) return false;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
MobileGLResult ConnectTcp(const std::string& name, std::uint32_t timeoutMs, int* outFd) {
|
||||
addrinfo* addresses = nullptr;
|
||||
if (!TcpAddress(name, &addresses)) return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
const auto deadline = std::chrono::steady_clock::now() +
|
||||
std::chrono::milliseconds(std::max(1u, timeoutMs));
|
||||
do {
|
||||
for (auto* address = addresses; address; address = address->ai_next) {
|
||||
const int fd = ::socket(address->ai_family, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (fd < 0) continue;
|
||||
const int flags = ::fcntl(fd, F_GETFL, 0);
|
||||
::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
bool connected = ::connect(fd, address->ai_addr, address->ai_addrlen) == 0;
|
||||
if (!connected && errno == EINPROGRESS) {
|
||||
const auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
deadline - std::chrono::steady_clock::now()).count();
|
||||
pollfd pfd{fd, POLLOUT, 0};
|
||||
int rc;
|
||||
do { rc = ::poll(&pfd, 1, static_cast<int>(std::max<std::int64_t>(0, remaining))); }
|
||||
while (rc < 0 && errno == EINTR && std::chrono::steady_clock::now() < deadline);
|
||||
int error = 0;
|
||||
socklen_t length = sizeof(error);
|
||||
connected = rc > 0 && ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &length) == 0 && error == 0;
|
||||
}
|
||||
if (connected && ConfigureTcp(fd)) {
|
||||
::fcntl(fd, F_SETFL, flags);
|
||||
::fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||
*outFd = fd;
|
||||
::freeaddrinfo(addresses);
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
::close(fd);
|
||||
if (std::chrono::steady_clock::now() >= deadline) break;
|
||||
}
|
||||
::usleep(5000);
|
||||
} while (std::chrono::steady_clock::now() < deadline);
|
||||
::freeaddrinfo(addresses);
|
||||
WireLogError("MG_Remote SocketTransport: TCP connect to %s exhausted %u ms", name.c_str(), timeoutMs);
|
||||
return MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
}
|
||||
|
||||
MobileGLResult ListenTcp(const std::string& name, int* outFd) {
|
||||
addrinfo* addresses = nullptr;
|
||||
if (!TcpAddress(name, &addresses)) return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
const char* token = std::getenv("MOBILEGL_IPC_TOKEN");
|
||||
const bool authenticated = token != nullptr && token[0] != '\0';
|
||||
MobileGLResult result = MOBILEGL_ERR_UNSUPPORTED;
|
||||
for (auto* address = addresses; address; address = address->ai_next) {
|
||||
if (!authenticated && !Loopback(address->ai_addr)) {
|
||||
WireLogError("MG_Remote: Refuse{AuthenticationRequired} non-loopback TCP listen needs MOBILEGL_IPC_TOKEN");
|
||||
result = MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
continue;
|
||||
}
|
||||
const int fd = ::socket(address->ai_family, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (fd < 0) continue;
|
||||
const int one = 1;
|
||||
::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
||||
::fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||
if (::bind(fd, address->ai_addr, address->ai_addrlen) == 0 && ::listen(fd, 8) == 0) {
|
||||
*outFd = fd;
|
||||
result = MOBILEGL_OK;
|
||||
break;
|
||||
}
|
||||
::close(fd);
|
||||
}
|
||||
::freeaddrinfo(addresses);
|
||||
return result;
|
||||
}
|
||||
|
||||
void CloseIfOpen(int& fd) {
|
||||
if (fd >= 0) {
|
||||
::close(fd);
|
||||
@@ -116,6 +258,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
}
|
||||
|
||||
MobileGLResult ConnectOne(const std::string& path, std::uint32_t timeoutMs, int* outFd) {
|
||||
if (TcpName(path)) return ConnectTcp(path, timeoutMs, outFd);
|
||||
sockaddr_un address{};
|
||||
socklen_t length = 0;
|
||||
if (!FillAddress(path, &address, &length)) {
|
||||
@@ -155,6 +298,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
*outListenFd = -1;
|
||||
if (TcpName(path)) return ListenTcp(path, outListenFd);
|
||||
sockaddr_un address{};
|
||||
socklen_t length = 0;
|
||||
if (!FillAddress(path, &address, &length)) {
|
||||
@@ -210,7 +354,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
}
|
||||
int accepted[2] = {-1, -1};
|
||||
for (int index = 0; index < 2; ++index) {
|
||||
const int ready = WaitReadable(listenFd, timeoutMs);
|
||||
const int ready = WaitReadable(listenFd, index == 0 ? timeoutMs : 2000);
|
||||
if (ready <= 0) {
|
||||
for (int fd : accepted) {
|
||||
if (fd >= 0) ::close(fd);
|
||||
@@ -218,7 +362,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
// Half a client is not a client. Both connections or neither, so
|
||||
// a peer that died between the two cannot leave a session that
|
||||
// looks up but has no way to receive a descriptor.
|
||||
WireLogError("MG_Remote SocketTransport: only %d of 2 connections arrived within "
|
||||
if (index != 0 || ready < 0) WireLogError("MG_Remote SocketTransport: only %d of 2 connections arrived within "
|
||||
"%u ms", index, timeoutMs);
|
||||
return ready == 0 ? MOBILEGL_ERR_TIMEOUT : MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
}
|
||||
@@ -230,6 +374,11 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
WireLogError("MG_Remote SocketTransport: accept failed: %s", std::strerror(errno));
|
||||
return MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
}
|
||||
::fcntl(accepted[index], F_SETFD, FD_CLOEXEC);
|
||||
if (TcpSocket(accepted[index]) && !ConfigureTcp(accepted[index])) {
|
||||
for (int fd : accepted) if (fd >= 0) ::close(fd);
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
// First connection is control, second is aux. The order IS the protocol;
|
||||
// it is stated here and in ConnectTo and nowhere else.
|
||||
@@ -292,7 +441,20 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
}
|
||||
|
||||
SocketTransport::SocketTransport(int streamFd, int auxFd, TransportRole role)
|
||||
: m_streamFd(streamFd), m_auxFd(auxFd), m_role(role) {}
|
||||
: m_streamFd(streamFd), m_auxFd(auxFd), m_tcp(TcpSocket(streamFd)), m_role(role) {}
|
||||
|
||||
int SocketTransport::TakeDataFd() {
|
||||
if (!m_tcp) return -1;
|
||||
const int fd = m_auxFd;
|
||||
m_auxFd = -1;
|
||||
return fd;
|
||||
}
|
||||
|
||||
void SocketTransport::CloseLocalCopy() {
|
||||
CloseIfOpen(m_streamFd);
|
||||
CloseIfOpen(m_auxFd);
|
||||
m_closed = true;
|
||||
}
|
||||
|
||||
SocketTransport::~SocketTransport() { Shutdown(); }
|
||||
|
||||
@@ -340,7 +502,8 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
m_closed = true;
|
||||
return MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
}
|
||||
WireLogError("MG_Remote SocketTransport: send failed after %llu of %llu bytes: %s",
|
||||
// SendFrame is also the log sink: do not recurse into the logger.
|
||||
std::fprintf(stderr, "MG_Remote SocketTransport: send failed after %llu of %llu bytes: %s\n",
|
||||
static_cast<unsigned long long>(written),
|
||||
static_cast<unsigned long long>(framed.size()), std::strerror(errno));
|
||||
m_closed = true;
|
||||
@@ -457,7 +620,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
}
|
||||
|
||||
MobileGLResult SocketTransport::ShareFd(int fd, MobileGLByteSpan sideband) {
|
||||
if (m_auxFd < 0) {
|
||||
if (m_tcp || m_auxFd < 0) {
|
||||
// A link that declared no descriptor passing. Rule G: it does not
|
||||
// invent a third answer, and the caller transfers the bytes instead.
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
@@ -472,13 +635,15 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
MobileGLResult SocketTransport::ReceiveFd(int* outFd, MobileGLMutableByteSpan sideband,
|
||||
std::uint64_t* outSidebandSize,
|
||||
std::uint32_t timeoutMs) {
|
||||
if (m_auxFd < 0) {
|
||||
if (m_tcp || m_auxFd < 0) {
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
return FdPassing::ReceiveFd(m_auxFd, outFd, sideband, outSidebandSize, timeoutMs);
|
||||
}
|
||||
|
||||
void SocketTransport::Shutdown() {
|
||||
// Wake a blocking ReceiveFrame before taking its lock.
|
||||
if (m_streamFd >= 0) ::shutdown(m_streamFd, SHUT_RDWR);
|
||||
// Idempotent, and tears down BOTH directions - which on a socket is what
|
||||
// closing does anyway. Taking both locks in a fixed order (send, then
|
||||
// recv) so a concurrent sender and the dedicated reader cannot deadlock
|
||||
@@ -510,6 +675,8 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
|
||||
SocketTransport::SocketTransport(int, int, TransportRole role) : m_role(role) {}
|
||||
SocketTransport::~SocketTransport() = default;
|
||||
int SocketTransport::TakeDataFd() { return -1; }
|
||||
void SocketTransport::CloseLocalCopy() {}
|
||||
|
||||
MobileGLResult SocketTransport::SendFrame(MobileGLByteSpan) { return MOBILEGL_ERR_UNSUPPORTED; }
|
||||
MobileGLResult SocketTransport::ReceiveFrame(MobileGLMutableByteSpan, std::uint64_t*,
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
// calls it, the peer's reads return 0 and its sends fail.
|
||||
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
|
||||
#include "Framing.h"
|
||||
#include "ITransport.h"
|
||||
@@ -124,6 +125,13 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
// transport owns, so a test can assert the child inherited exactly these.
|
||||
int StreamFd() const { return m_streamFd; }
|
||||
int AuxFd() const { return m_auxFd; }
|
||||
bool IsTcp() const { return m_tcp; }
|
||||
// TCP's second connection belongs exclusively to the data plane.
|
||||
// Taking it disables all descriptor passing on this transport.
|
||||
int TakeDataFd();
|
||||
// Parent of a session child closes its duplicate without shutdown(2),
|
||||
// which would also disconnect the child's inherited socket.
|
||||
void CloseLocalCopy();
|
||||
|
||||
private:
|
||||
// Pulls whatever the socket has into the reassembler. Returns
|
||||
@@ -132,6 +140,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
|
||||
int m_streamFd = -1;
|
||||
int m_auxFd = -1;
|
||||
bool m_tcp = false;
|
||||
TransportRole m_role = TransportRole::Client;
|
||||
|
||||
// ITransport's threading rule: callers serialise sends, and receives may
|
||||
@@ -143,7 +152,7 @@ namespace MobileGL::MG_Remote::Transport {
|
||||
std::mutex m_recvMutex;
|
||||
|
||||
FrameReader m_reader;
|
||||
bool m_closed = false;
|
||||
std::atomic<bool> m_closed{false};
|
||||
bool m_failed = false; // framing violated: latched, never recovers
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,506 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
#include "StreamLink.h"
|
||||
#include "SessionRings.h"
|
||||
#include "ShmLink.h"
|
||||
#include "Doorbell.h"
|
||||
#include "ReplySlot.h"
|
||||
#include "Framing.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#if !defined(_WIN32)
|
||||
#include <cerrno>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#if defined(__linux__)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace MobileGL::MG_Remote::Transport {
|
||||
namespace {
|
||||
constexpr std::uint32_t kDataMagic = 0x444c474d; // MGLD, little endian
|
||||
enum Kind : unsigned {
|
||||
Cmd = 1,
|
||||
Stage,
|
||||
ClientProgress,
|
||||
ProgressFrame,
|
||||
Reply,
|
||||
Event
|
||||
};
|
||||
void Put32(std::uint8_t* p, std::uint32_t v) {
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
p[i] = v >> (i * 8);
|
||||
}
|
||||
void Put64(std::uint8_t* p, std::uint64_t v) {
|
||||
for (unsigned i = 0; i < 8; ++i)
|
||||
p[i] = v >> (i * 8);
|
||||
}
|
||||
std::uint32_t Get32(const std::uint8_t* p) {
|
||||
std::uint32_t v = 0;
|
||||
for (unsigned i = 0; i < 4; ++i)
|
||||
v |= std::uint32_t(p[i]) << (i * 8);
|
||||
return v;
|
||||
}
|
||||
std::uint64_t Get64(const std::uint8_t* p) {
|
||||
std::uint64_t v = 0;
|
||||
for (unsigned i = 0; i < 8; ++i)
|
||||
v |= std::uint64_t(p[i]) << (i * 8);
|
||||
return v;
|
||||
}
|
||||
[[noreturn]] void Corrupt(const char* detail) {
|
||||
WireLogFatal("MGPipe: Fatal{ProtocolCorruption, \"StreamLink\"} %s", detail);
|
||||
}
|
||||
void Advance(std::atomic<std::uint64_t>& value, std::uint64_t next) {
|
||||
if (next < value.load(std::memory_order_acquire)) Corrupt("watermark moved backwards");
|
||||
value.store(next, std::memory_order_release);
|
||||
}
|
||||
class StreamBell final : public Doorbell {
|
||||
public:
|
||||
void Notify() override { m_bell.Notify(); }
|
||||
bool Park(std::uint32_t timeout) override { return m_bell.Park(timeout); }
|
||||
void Reset() override { m_bell.Reset(); }
|
||||
bool Dead() const override { return m_bell.Dead(); }
|
||||
bool PeerHungUp() const override { return m_peer.load(std::memory_order_acquire); }
|
||||
void Kill(bool peer) {
|
||||
if (peer) m_peer.store(true, std::memory_order_release);
|
||||
m_bell.Kill();
|
||||
}
|
||||
|
||||
private:
|
||||
CondVarDoorbell m_bell;
|
||||
std::atomic<bool> m_peer{false};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
struct StreamLink::Impl {
|
||||
std::unique_ptr<ShmLink> owned = std::make_unique<ShmLink>();
|
||||
SessionSegments* memory = nullptr;
|
||||
TransportRoleTag role = TransportRoleTag::ClientProducer;
|
||||
int fd = -1;
|
||||
std::atomic<bool> closed{false}, stopping{false};
|
||||
StreamBell consumer, producer;
|
||||
std::thread reader;
|
||||
std::mutex writeMutex, replyMutex;
|
||||
std::condition_variable replyReady;
|
||||
std::vector<LinkSpan> stage;
|
||||
std::vector<std::uint8_t> reply, readReply;
|
||||
std::uint64_t replySeq = 0, sentCmd = 0, receivedCmd = 0, sentEvent = 0, receivedEvent = 0;
|
||||
std::int32_t replyStatus = 0;
|
||||
std::uint64_t lastProgressSeq = 0;
|
||||
std::chrono::steady_clock::time_point lastProgress = std::chrono::steady_clock::now();
|
||||
LinkArena records{}, events{};
|
||||
LinkCursor recordCursor{}, eventCursor{};
|
||||
bool Client() const { return role == TransportRoleTag::ClientProducer; }
|
||||
RingControl& C() const { return *memory->CmdControl(); }
|
||||
RingControl& E() const { return *memory->EventControl(); }
|
||||
std::uint64_t MaxReply() const {
|
||||
return memory->ReplyBytes() / memory->ReplySlotCount() - sizeof(ReplySlotHeader);
|
||||
}
|
||||
void Die(bool peer) {
|
||||
closed.store(true, std::memory_order_release);
|
||||
consumer.Kill(peer);
|
||||
producer.Kill(peer);
|
||||
replyReady.notify_all();
|
||||
#if !defined(_WIN32)
|
||||
if (fd >= 0) ::shutdown(fd, SHUT_RDWR);
|
||||
#endif
|
||||
}
|
||||
bool Write(const void* data, std::size_t size) {
|
||||
#if !defined(_WIN32)
|
||||
auto* p = static_cast<const std::uint8_t*>(data);
|
||||
while (size) {
|
||||
const auto n = ::send(fd, p, size, MSG_NOSIGNAL);
|
||||
if (n < 0 && errno == EINTR) continue;
|
||||
if (n <= 0) {
|
||||
Die(!stopping.load(std::memory_order_acquire));
|
||||
return false;
|
||||
}
|
||||
p += n;
|
||||
size -= static_cast<std::size_t>(n);
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
(void)data;
|
||||
(void)size;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
bool Read(void* data, std::size_t size) {
|
||||
#if !defined(_WIN32)
|
||||
auto* p = static_cast<std::uint8_t*>(data);
|
||||
while (size) {
|
||||
const auto n = ::recv(fd, p, size, 0);
|
||||
if (n < 0 && errno == EINTR) continue;
|
||||
if (n <= 0) {
|
||||
Die(!stopping.load(std::memory_order_acquire));
|
||||
return false;
|
||||
}
|
||||
p += n;
|
||||
size -= static_cast<std::size_t>(n);
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
(void)data;
|
||||
(void)size;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
bool Send(unsigned kind, std::uint64_t a, std::uint64_t b, const void* payload, std::uint64_t size) {
|
||||
if (size > kMaxFramePayloadSize) Corrupt("outgoing frame exceeds limit");
|
||||
std::uint8_t h[28]{};
|
||||
Put32(h, kDataMagic);
|
||||
Put32(h + 4, static_cast<std::uint32_t>(size));
|
||||
h[8] = static_cast<std::uint8_t>(kind);
|
||||
Put64(h + 12, a);
|
||||
Put64(h + 20, b);
|
||||
return Write(h, sizeof h) && (!size || Write(payload, static_cast<std::size_t>(size)));
|
||||
}
|
||||
bool SendRing(unsigned kind, void* base, std::uint64_t capacity, std::uint64_t head, std::uint64_t& sent) {
|
||||
if (head < sent || head - sent > capacity) Corrupt("outgoing ring window invalid");
|
||||
while (sent < head) {
|
||||
const auto at = sent % capacity;
|
||||
const auto count = std::min({head - sent, capacity - at, kMaxFramePayloadSize});
|
||||
if (!Send(kind, sent, 0, static_cast<std::uint8_t*>(base) + at, count)) return false;
|
||||
sent += count;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool SendEvents() {
|
||||
return SendRing(Event, memory->EventRingBase(), memory->EventRingCapacity(),
|
||||
E().cmdHead.load(std::memory_order_acquire), sentEvent);
|
||||
}
|
||||
bool SendProgress() {
|
||||
// Events and replies always precede the applied watermark that publishes them.
|
||||
if (!SendEvents()) return false;
|
||||
std::uint8_t p[64]{};
|
||||
Put64(p, C().Progress.appliedSeq.load(std::memory_order_acquire));
|
||||
Put64(p + 8, C().Progress.retiredSeq.load(std::memory_order_acquire));
|
||||
Put64(p + 16, C().Progress.completedFrameSerial.load(std::memory_order_acquire));
|
||||
Put64(p + 24, C().Progress.presentAckSerial.load(std::memory_order_acquire));
|
||||
Put64(p + 32, C().cmdAppliedTail.load(std::memory_order_acquire));
|
||||
// FreeBytes uses RETIRED tail. Sending only applied tail silently permits
|
||||
// reuse of borrowed command slots, and omitting this tail wedges on wrap.
|
||||
Put64(p + 40, C().cmdRetiredTail.load(std::memory_order_acquire));
|
||||
Put64(p + 48, C().eventRingFull.load(std::memory_order_acquire));
|
||||
Put64(p + 56, C().eventDropped.load(std::memory_order_acquire));
|
||||
lastProgressSeq = Get64(p);
|
||||
lastProgress = std::chrono::steady_clock::now();
|
||||
return Send(ProgressFrame, 0, 0, p, sizeof p);
|
||||
}
|
||||
void ReceiveRing(void* base, std::uint64_t capacity, std::uint64_t tail, std::uint64_t a,
|
||||
const std::vector<std::uint8_t>& p, std::uint64_t& received) {
|
||||
if (a != received || a < tail || a - tail > capacity || p.size() > capacity - (a - tail) ||
|
||||
p.size() > capacity - a % capacity || p.empty())
|
||||
Corrupt("ring frame outside receive window");
|
||||
std::memcpy(static_cast<std::uint8_t*>(base) + a % capacity, p.data(), p.size());
|
||||
received += p.size();
|
||||
}
|
||||
void Loop() {
|
||||
#if defined(__linux__)
|
||||
pthread_setname_np(pthread_self(), Client() ? "mgl-cli-io" : "mgl-srv-io");
|
||||
#endif
|
||||
for (;;) {
|
||||
std::uint8_t h[28];
|
||||
if (!Read(h, sizeof h)) return;
|
||||
const auto size = Get32(h + 4), kind = unsigned(h[8]);
|
||||
const auto a = Get64(h + 12), b = Get64(h + 20);
|
||||
if (Get32(h) != kDataMagic || h[9] || h[10] || h[11] || size > kMaxFramePayloadSize)
|
||||
Corrupt("invalid data frame header");
|
||||
// Reject the size/window before allocating or reading a hostile payload.
|
||||
if ((kind == Stage && (Client() || a > memory->StageBytes() || size > memory->StageBytes() - a)) ||
|
||||
(kind == Cmd && (Client() || size > memory->CmdRingCapacity())) ||
|
||||
(kind == Event && (!Client() || size > memory->EventRingCapacity())) ||
|
||||
(kind == Reply && (!Client() || size > MaxReply())) ||
|
||||
(kind == ProgressFrame && (!Client() || size != 64)) ||
|
||||
(kind == ClientProgress && (Client() || size != 8)) || kind < Cmd || kind > Event)
|
||||
Corrupt("frame kind or size outside negotiated window");
|
||||
std::vector<std::uint8_t> p(size);
|
||||
if (size && !Read(p.data(), size)) return;
|
||||
if (kind == Stage) {
|
||||
std::memcpy(static_cast<std::uint8_t*>(memory->StageBase()) + a, p.data(), size);
|
||||
} else if (kind == Cmd) {
|
||||
ReceiveRing(memory->CmdRingBase(), memory->CmdRingCapacity(),
|
||||
C().cmdRetiredTail.load(std::memory_order_acquire), a, p, receivedCmd);
|
||||
} else if (kind == ClientProgress) {
|
||||
if (a != receivedCmd) Corrupt("cmdHead does not match delivered bytes");
|
||||
const auto tail = Get64(p.data());
|
||||
if (tail > E().cmdHead.load(std::memory_order_acquire)) Corrupt("event tail ahead of head");
|
||||
const auto oldTail = E().cmdRetiredTail.load(std::memory_order_acquire);
|
||||
Advance(E().cmdAppliedTail, tail);
|
||||
Advance(E().cmdRetiredTail, tail);
|
||||
// Only a real drain can clear a latched backlog. A command-only
|
||||
// publication carrying the old tail must not undo a new full latch.
|
||||
if (tail > oldTail) C().eventRingFull.store(0, std::memory_order_release);
|
||||
Watermark::AdvanceSubmitted(C(), b);
|
||||
Advance(C().cmdHead, a);
|
||||
consumer.Notify();
|
||||
} else if (kind == Event) {
|
||||
ReceiveRing(memory->EventRingBase(), memory->EventRingCapacity(),
|
||||
E().cmdRetiredTail.load(std::memory_order_acquire), a, p, receivedEvent);
|
||||
Advance(E().cmdHead, receivedEvent);
|
||||
producer.Notify();
|
||||
} else if (kind == ProgressFrame) {
|
||||
const auto* v = p.data();
|
||||
if (Get64(v + 8) > Get64(v) || Get64(v) > C().submittedSeq.load(std::memory_order_acquire) ||
|
||||
Get64(v + 40) > Get64(v + 32) || Get64(v + 32) > C().cmdHead.load(std::memory_order_acquire) ||
|
||||
Get64(v + 48) > 1)
|
||||
Corrupt("progress ahead of delivered work");
|
||||
Advance(C().cmdAppliedTail, Get64(v + 32));
|
||||
Advance(C().cmdRetiredTail, Get64(v + 40));
|
||||
C().eventRingFull.store(static_cast<std::uint32_t>(Get64(v + 48)), std::memory_order_release);
|
||||
C().eventDropped.store(static_cast<std::uint32_t>(Get64(v + 56)), std::memory_order_release);
|
||||
Watermark::AdvanceRetired(C(), Get64(v + 8));
|
||||
Watermark::AdvanceCompletedFrame(C(), Get64(v + 16));
|
||||
Watermark::AdvancePresentAck(C(), Get64(v + 24));
|
||||
Watermark::AdvanceApplied(C(), Get64(v));
|
||||
producer.Notify();
|
||||
} else if (kind == Reply) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(replyMutex);
|
||||
if (a <= replySeq) Corrupt("reply sequence moved backwards");
|
||||
replySeq = a;
|
||||
replyStatus = static_cast<std::int32_t>(static_cast<std::uint32_t>(b));
|
||||
reply = std::move(p);
|
||||
}
|
||||
replyReady.notify_all();
|
||||
producer.Notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
StreamLink::StreamLink() : m_impl(new Impl) {}
|
||||
StreamLink::~StreamLink() {
|
||||
Detach();
|
||||
}
|
||||
MobileGLResult StreamLink::Attach(int fd, SessionSegments& mirrors, TransportRoleTag role) {
|
||||
#if defined(_WIN32)
|
||||
(void)fd;
|
||||
(void)mirrors;
|
||||
(void)role;
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
#else
|
||||
if (fd < 0 || !mirrors.Valid() || !mirrors.ReplySlotCount() ||
|
||||
mirrors.ReplyBytes() / mirrors.ReplySlotCount() <= sizeof(ReplySlotHeader))
|
||||
return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
Detach();
|
||||
m_impl.reset(new Impl);
|
||||
auto& x = *m_impl;
|
||||
x.fd = fd;
|
||||
x.memory = &mirrors;
|
||||
x.role = role;
|
||||
x.records = {mirrors.CmdRingBase(), mirrors.CmdRingCapacity(), mirrors.CmdRingCapacity() - 1, 0};
|
||||
x.recordCursor = {mirrors.CmdRingBase(), mirrors.CmdRingCapacity(), mirrors.CmdRingCapacity() - 1, 0};
|
||||
x.events = {mirrors.EventRingBase(), mirrors.EventRingCapacity(), mirrors.EventRingCapacity() - 1, 0};
|
||||
x.eventCursor = {mirrors.EventRingBase(), mirrors.EventRingCapacity(), mirrors.EventRingCapacity() - 1, 0};
|
||||
x.reader = std::thread([&x] { x.Loop(); });
|
||||
return MOBILEGL_OK;
|
||||
#endif
|
||||
}
|
||||
MobileGLResult StreamLink::AttachOwned(int fd, const SessionSegmentSizes& sizes, TransportRoleTag role) {
|
||||
auto owned = std::make_unique<ShmLink>();
|
||||
const auto result = owned->Memory().CreatePrivate(
|
||||
sizes, role == TransportRoleTag::ClientProducer ? MemoryRole::Client : MemoryRole::Server);
|
||||
if (result != MOBILEGL_OK) return result;
|
||||
const auto attached = Attach(fd, owned->Memory(), role);
|
||||
if (attached == MOBILEGL_OK) m_impl->owned = std::move(owned);
|
||||
return attached;
|
||||
}
|
||||
MobileGLResult CreateStreamLink(int fd, const SessionSegmentSizes& sizes, TransportRoleTag role,
|
||||
std::unique_ptr<ILink>& out) {
|
||||
auto link = std::make_unique<StreamLink>();
|
||||
const auto result = link->AttachOwned(fd, sizes, role);
|
||||
if (result == MOBILEGL_OK) out = std::move(link);
|
||||
#if !defined(_WIN32)
|
||||
else if (fd >= 0)
|
||||
::close(fd);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
void StreamLink::InitializeEndpoints() {
|
||||
m_impl->owned->SetRole(m_impl->role);
|
||||
m_impl->owned->InitializeEndpoints();
|
||||
}
|
||||
SessionSegments& StreamLink::Memory() {
|
||||
return m_impl->owned->Memory();
|
||||
}
|
||||
RingProducer& StreamLink::CommandsOut() {
|
||||
return m_impl->owned->CommandsOut();
|
||||
}
|
||||
RingConsumer& StreamLink::CommandsIn() {
|
||||
return m_impl->owned->CommandsIn();
|
||||
}
|
||||
EventRingProducer& StreamLink::EventsOut() {
|
||||
return m_impl->owned->EventsOut();
|
||||
}
|
||||
EventRingConsumer& StreamLink::EventsIn() {
|
||||
return m_impl->owned->EventsIn();
|
||||
}
|
||||
LinkCapabilities StreamLink::Capabilities() const {
|
||||
if (!m_impl->memory) return {};
|
||||
return {false, false, false, m_impl->memory->CmdRingCapacity() / 2, m_impl->MaxReply()};
|
||||
}
|
||||
LinkProgress* StreamLink::Progress() {
|
||||
return m_impl->memory ? &m_impl->C().Progress : nullptr;
|
||||
}
|
||||
LinkEventFlags* StreamLink::EventFlags() {
|
||||
return m_impl->memory ? reinterpret_cast<LinkEventFlags*>(&m_impl->C().eventRingFull) : nullptr;
|
||||
}
|
||||
LinkArena* StreamLink::RecordArena() {
|
||||
return m_impl->Client() ? &m_impl->records : nullptr;
|
||||
}
|
||||
LinkCursor* StreamLink::RecordCursor() {
|
||||
return m_impl->Client() ? nullptr : &m_impl->recordCursor;
|
||||
}
|
||||
LinkArena* StreamLink::EventArena() {
|
||||
return m_impl->Client() ? nullptr : &m_impl->events;
|
||||
}
|
||||
LinkCursor* StreamLink::EventCursor() {
|
||||
return m_impl->Client() ? &m_impl->eventCursor : nullptr;
|
||||
}
|
||||
Doorbell& StreamLink::ConsumerBell() {
|
||||
return m_impl->consumer;
|
||||
}
|
||||
Doorbell& StreamLink::ProducerBell() {
|
||||
return m_impl->producer;
|
||||
}
|
||||
MobileGLResult StreamLink::StageBytes(std::uint64_t, LinkSpan*, void**) {
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
MobileGLResult StreamLink::ResolveSpan(LinkSegment segment, LinkSpan span, const void** out) {
|
||||
if (!out || !m_impl->memory) return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
auto& m = *m_impl->memory;
|
||||
const void* base = nullptr;
|
||||
std::uint64_t cap = 0;
|
||||
switch (segment) {
|
||||
case LinkSegment::Cmd:
|
||||
base = m.CmdRingBase();
|
||||
cap = m.CmdRingCapacity();
|
||||
break;
|
||||
case LinkSegment::Stage:
|
||||
base = m.StageBase();
|
||||
cap = m.StageBytes();
|
||||
break;
|
||||
case LinkSegment::Reply:
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
case LinkSegment::Event:
|
||||
base = m.EventSegmentBase();
|
||||
cap = m.AnnouncedSize(SessionSegmentSlot::Event);
|
||||
break;
|
||||
default:
|
||||
return MOBILEGL_ERR_UNSUPPORTED;
|
||||
}
|
||||
if (span.Offset > cap || span.Size > cap - span.Offset) return MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
*out = static_cast<const std::uint8_t*>(base) + span.Offset;
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
void StreamLink::NoteStage(LinkSpan span) {
|
||||
auto& x = *m_impl;
|
||||
if (!x.Client() || span.Offset > x.memory->StageBytes() || span.Size > x.memory->StageBytes() - span.Offset)
|
||||
Corrupt("staged span outside window");
|
||||
if (!x.stage.empty() && x.stage.back().Offset + x.stage.back().Size == span.Offset)
|
||||
x.stage.back().Size += span.Size;
|
||||
else
|
||||
x.stage.push_back(span);
|
||||
}
|
||||
MobileGLResult StreamLink::Flush() {
|
||||
auto& x = *m_impl;
|
||||
if (!Attached()) return MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
std::lock_guard<std::mutex> lock(x.writeMutex);
|
||||
if (!x.Client()) return x.SendEvents() ? MOBILEGL_OK : MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
for (const auto span : x.stage) {
|
||||
std::uint64_t sent = 0;
|
||||
while (sent < span.Size) {
|
||||
const auto count = std::min(span.Size - sent, kMaxFramePayloadSize);
|
||||
if (!x.Send(Stage, span.Offset + sent, 0,
|
||||
static_cast<std::uint8_t*>(x.memory->StageBase()) + span.Offset + sent, count))
|
||||
return MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
sent += count;
|
||||
}
|
||||
}
|
||||
x.stage.clear();
|
||||
const auto head = x.C().cmdHead.load(std::memory_order_acquire);
|
||||
if (!x.SendRing(Cmd, x.memory->CmdRingBase(), x.memory->CmdRingCapacity(), head, x.sentCmd))
|
||||
return MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
std::uint8_t tail[8];
|
||||
Put64(tail, x.E().cmdRetiredTail.load(std::memory_order_acquire));
|
||||
return x.Send(ClientProgress, head, x.C().submittedSeq.load(std::memory_order_acquire), tail, sizeof tail)
|
||||
? MOBILEGL_OK
|
||||
: MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
}
|
||||
MobileGLResult StreamLink::FlushProgress() {
|
||||
auto& x = *m_impl;
|
||||
if (!Attached()) return MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
if (x.Client()) return Flush();
|
||||
std::lock_guard<std::mutex> lock(x.writeMutex);
|
||||
return x.SendProgress() ? MOBILEGL_OK : MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
}
|
||||
void StreamLink::ProgressChanged() {
|
||||
auto& x = *m_impl;
|
||||
if (x.Client() || !Attached()) return;
|
||||
const auto seq = x.C().Progress.appliedSeq.load(std::memory_order_acquire);
|
||||
if (seq - x.lastProgressSeq >= 64 ||
|
||||
std::chrono::steady_clock::now() - x.lastProgress >= std::chrono::milliseconds(1))
|
||||
FlushProgress();
|
||||
}
|
||||
MobileGLResult StreamLink::PostReply(std::uint64_t seq, std::int32_t status, const void* p, std::uint64_t size) {
|
||||
auto& x = *m_impl;
|
||||
if (x.Client() || !Attached()) return MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
if (size > x.MaxReply() || (size && !p)) return MOBILEGL_ERR_BUFFER_TOO_SMALL;
|
||||
std::lock_guard<std::mutex> lock(x.writeMutex);
|
||||
return x.Send(Reply, seq, static_cast<std::uint32_t>(status), p, size) && x.SendProgress()
|
||||
? MOBILEGL_OK
|
||||
: MOBILEGL_ERR_TRANSPORT_CLOSED;
|
||||
}
|
||||
MobileGLResult StreamLink::ReadReply(std::uint64_t seq, std::int32_t* status, const void** p, std::uint64_t* size) {
|
||||
auto& x = *m_impl;
|
||||
if (!status || !p || !size || !x.Client()) return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
std::unique_lock<std::mutex> lock(x.replyMutex);
|
||||
// The applied watermark is sent after the reply, so normal callers never
|
||||
// wait here. A standalone reply reader may wait without polling.
|
||||
x.replyReady.wait(lock, [&] { return x.replySeq >= seq || x.closed.load(std::memory_order_acquire); });
|
||||
if (x.replySeq != seq) return x.closed.load() ? MOBILEGL_ERR_TRANSPORT_CLOSED : MOBILEGL_ERR_PROTOCOL_MISMATCH;
|
||||
x.readReply = x.reply;
|
||||
*status = x.replyStatus;
|
||||
*size = x.readReply.size();
|
||||
*p = x.readReply.data();
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
std::uint64_t StreamLink::EventPublishedHead() const {
|
||||
return m_impl->memory ? m_impl->E().cmdHead.load(std::memory_order_acquire) : 0;
|
||||
}
|
||||
MobileGLResult StreamLink::WaitForEventDelivery(std::uint64_t head, std::uint32_t timeoutMs) {
|
||||
auto& x = *m_impl;
|
||||
if (!x.memory) return MOBILEGL_ERR_NOT_INITIALIZED;
|
||||
const auto ready = [&] { return x.E().cmdHead.load(std::memory_order_acquire) >= head; };
|
||||
if (ready()) return MOBILEGL_OK;
|
||||
const bool delivered = x.producer.Wait(x.C().producerParked, ready, 0, timeoutMs);
|
||||
return delivered ? MOBILEGL_OK : x.producer.Dead() ? MOBILEGL_ERR_TRANSPORT_CLOSED : MOBILEGL_ERR_TIMEOUT;
|
||||
}
|
||||
bool StreamLink::Attached() const {
|
||||
return m_impl->memory && !m_impl->closed.load(std::memory_order_acquire);
|
||||
}
|
||||
bool StreamLink::PeerHungUp() const {
|
||||
return m_impl->producer.PeerHungUp();
|
||||
}
|
||||
TransportRoleTag StreamLink::Role() const {
|
||||
return m_impl->role;
|
||||
}
|
||||
void StreamLink::Detach() {
|
||||
auto& x = *m_impl;
|
||||
if (!x.memory) return;
|
||||
x.stopping.store(true, std::memory_order_release);
|
||||
x.Die(false);
|
||||
if (x.reader.joinable()) x.reader.join();
|
||||
#if !defined(_WIN32)
|
||||
if (x.fd >= 0) ::close(x.fd);
|
||||
#endif
|
||||
x.fd = -1;
|
||||
x.memory = nullptr;
|
||||
}
|
||||
} // namespace MobileGL::MG_Remote::Transport
|
||||
@@ -1,108 +1,50 @@
|
||||
// MobileGL - MobileGL/MG_Remote/Transport/StreamLink.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// ILink's second implementation, DECLARED BY c6 AND IMPLEMENTED BY NOBODY.
|
||||
// Authority: CONTRACT-P6.md §8.4. Its phase is P6.5.
|
||||
//
|
||||
// The link it exists for is TCP across machines (end state re-ruled 2026-09-22,
|
||||
// CONTRACT-P6.md §10.3, ARCHITECTURE.md §11.9), and it is one axis of two: the
|
||||
// control plane (ITransport) is chosen independently, so tcp control + shm data
|
||||
// on one machine is a legal pair and nothing above Transport/ may tell which.
|
||||
//
|
||||
// WHY A HEADER FOR SOMETHING THAT DOES NOT EXIST. ILink's adequacy - "can this
|
||||
// seam name what a byte stream needs?" - is the one claim c6 makes that an
|
||||
// argument cannot settle and a compile can. If ILink cannot express progress
|
||||
// transmission, a send window, a flush, span resolution without a mapping, and
|
||||
// replies that are messages rather than slots, then StreamLink cannot be
|
||||
// declared against it, and the build says so. That is c6's red-once: delete
|
||||
// one ILink method this class declares and the build goes red.
|
||||
//
|
||||
// It has already earned its keep. Writing it forced THREE methods that the
|
||||
// original brief's list - progress transmission, send window, flush - did not
|
||||
// name:
|
||||
//
|
||||
// ResolveSpan - a stream has no mapping to do pointer arithmetic over, so
|
||||
// the eleven ResolveOrFatal sites in the decoder need the
|
||||
// link to answer, not a segment table. Without it the seam
|
||||
// was unimplementable for the case it exists for.
|
||||
// FlushProgress - separate from Flush because the triggers differ: Flush is
|
||||
// about records the peer has not seen, FlushProgress is
|
||||
// about watermarks the peer is WAITING on. On a shared page
|
||||
// a release-store is both; on a stream, conflating them
|
||||
// either sends too much or hangs a waiter.
|
||||
// PostReply / - SEG_REPLY carries no message today: the server memcpys
|
||||
// ReadReply into a slot addressed `seq % slotCount` and the client
|
||||
// reads it after observing appliedSeq. A stream turns that
|
||||
// into a real message carrying its own seq, which DELETES
|
||||
// the slot pool, the modulus addressing and the stamp
|
||||
// self-check - so the seam has to own the operation rather
|
||||
// than hand out the pool.
|
||||
//
|
||||
// WHAT P6.5 STILL HAS TO SETTLE, recorded here so it is not rediscovered:
|
||||
// - SEG_STAGE's three ring cursors are DELIBERATELY DEAD (Ring.h's "DEAD IN P5, DELIBERATELY",
|
||||
// pinned by a test). Staging is an encoder-local linear allocator that
|
||||
// reclaims on retiredSeq. A send window either revives them COMPLETELY or
|
||||
// does not use them; the header names the middle state as a guaranteed
|
||||
// hang rather than a slow path.
|
||||
// - appliedSeq is excluded BY NAME from lazy publication (Ring.h's "BATCHING MAY ONLY MAKE A WATERMARK LATE").
|
||||
// P6.5 AMENDS that rule with a flush-on-idle discipline; it does not infer
|
||||
// around it.
|
||||
// - one dedicated reader thread per direction per process, or the event-ring
|
||||
// flow-control deadlock returns as write() blocked against write().
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ILink.h"
|
||||
#include <memory>
|
||||
|
||||
namespace MobileGL::MG_Remote::Transport {
|
||||
|
||||
// Every entry point is a named refusal. There is no partial implementation
|
||||
// and there may not be one: a half-wired stream link is the shape Ring.h
|
||||
// warns about, where FreeBytes() falls to zero on the first lap and never
|
||||
// recovers.
|
||||
//
|
||||
// The refusals live in StreamLink.cpp (package P6.5) rather than inline,
|
||||
// for the reason ILink.h gives: no header under Transport/ pulls the logger
|
||||
// in, and this one does not start.
|
||||
class SessionSegments;
|
||||
// One reader owns receive/reassembly. Writers serialize complete frames and use
|
||||
// the caller thread, so both directions can always drain a blocked peer writer.
|
||||
class StreamLink final : public ILink {
|
||||
public:
|
||||
StreamLink();
|
||||
~StreamLink() override;
|
||||
|
||||
MobileGLResult Attach(int dataFd, SessionSegments& mirrors, TransportRoleTag role);
|
||||
MobileGLResult AttachOwned(int dataFd, const struct SessionSegmentSizes& sizes, TransportRoleTag role);
|
||||
SessionSegments& Memory() override;
|
||||
void InitializeEndpoints() override;
|
||||
RingProducer& CommandsOut() override;
|
||||
RingConsumer& CommandsIn() override;
|
||||
EventRingProducer& EventsOut() override;
|
||||
EventRingConsumer& EventsIn() override;
|
||||
LinkCapabilities Capabilities() const override;
|
||||
|
||||
LinkProgress* Progress() override;
|
||||
LinkEventFlags* EventFlags() override;
|
||||
LinkArena* RecordArena() override;
|
||||
LinkCursor* RecordCursor() override;
|
||||
|
||||
MobileGLResult StageBytes(std::uint64_t bytes, LinkSpan* outSpan,
|
||||
void** outWritePtr) override;
|
||||
MobileGLResult ResolveSpan(LinkSegment segment, LinkSpan span,
|
||||
const void** outPtr) override;
|
||||
|
||||
MobileGLResult PostReply(std::uint64_t seq, std::int32_t status,
|
||||
const void* payload, std::uint64_t size) override;
|
||||
MobileGLResult ReadReply(std::uint64_t seq, std::int32_t* outStatus,
|
||||
const void** outPayload, std::uint64_t* outSize) override;
|
||||
|
||||
MobileGLResult StageBytes(std::uint64_t, LinkSpan*, void**) override;
|
||||
MobileGLResult ResolveSpan(LinkSegment, LinkSpan, const void**) override;
|
||||
MobileGLResult PostReply(std::uint64_t, std::int32_t, const void*, std::uint64_t) override;
|
||||
MobileGLResult ReadReply(std::uint64_t, std::int32_t*, const void**, std::uint64_t*) override;
|
||||
LinkArena* EventArena() override;
|
||||
LinkCursor* EventCursor() override;
|
||||
|
||||
std::uint64_t EventPublishedHead() const override;
|
||||
MobileGLResult WaitForEventDelivery(std::uint64_t head, std::uint32_t timeoutMs) override;
|
||||
Doorbell& ConsumerBell() override;
|
||||
Doorbell& ProducerBell() override;
|
||||
|
||||
MobileGLResult Flush() override;
|
||||
MobileGLResult FlushProgress() override;
|
||||
|
||||
void NoteStage(LinkSpan span) override;
|
||||
void ProgressChanged() override;
|
||||
bool PeerHungUp() const override;
|
||||
bool Attached() const override;
|
||||
void Detach() override;
|
||||
TransportRoleTag Role() const override;
|
||||
};
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
} // namespace MobileGL::MG_Remote::Transport
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
// first place. The static_asserts below pin the collision so it cannot be re-introduced by
|
||||
// someone who reads the comment and not this file.
|
||||
|
||||
#include <MG_Remote/Transport/LinkMetrics.h>
|
||||
#include "PipeWireCodec.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
@@ -392,6 +393,17 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// SegmentTable
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
void SegmentTable::AttachLink(Transport::ILink* link) {
|
||||
m_link = link;
|
||||
for (Uint32 id = kSegCmd; id <= kSegEvent; ++id) {
|
||||
const auto segment = static_cast<Transport::LinkSegment>(id);
|
||||
const Uint64 size = link ? link->SegmentSize(segment) : 0;
|
||||
const void* base = nullptr;
|
||||
if (link && link->ResolveSpan(segment, {0, size}, &base) != MOBILEGL_OK) base = nullptr;
|
||||
Install(static_cast<SegmentId>(id), {const_cast<void*>(base), size});
|
||||
}
|
||||
}
|
||||
|
||||
void SegmentTable::Install(SegmentId seg, SegmentView view) {
|
||||
if (seg == kSegNone || static_cast<Uint32>(seg) > static_cast<Uint32>(kSegAdopt)) {
|
||||
WireProtocolFatalAt("SegmentTable::Install", static_cast<Uint64>(seg),
|
||||
@@ -418,6 +430,11 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
if (size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
if (m_link) {
|
||||
const void* bytes = nullptr;
|
||||
return m_link->ResolveSpan(static_cast<Transport::LinkSegment>(seg), {offset, size}, &bytes)
|
||||
== MOBILEGL_OK ? bytes : nullptr;
|
||||
}
|
||||
const SegmentView& view = m_views[static_cast<SizeT>(seg)];
|
||||
if (view.Base == nullptr) {
|
||||
return nullptr;
|
||||
@@ -822,11 +839,24 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// Encoder
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
PipeWireEncoder::PipeWireEncoder(Transport::ILink* link, SegmentTable* segments)
|
||||
: m_segments(segments) {
|
||||
if (!link) return;
|
||||
m_progress = link->Progress(); m_signals = link->Signals(); m_cmd = &link->CommandsOut();
|
||||
m_link = link->Capabilities().PublishIsDelivery ? nullptr : link;
|
||||
}
|
||||
|
||||
PipeWireEncoder::PipeWireEncoder(Transport::RingControl* control, Transport::RingProducer* cmd,
|
||||
Transport::RingProducer* stage, SegmentTable* segments)
|
||||
: m_control(control), m_cmd(cmd), m_stage(stage), m_segments(segments) {}
|
||||
: m_cmd(cmd), m_stage(stage), m_segments(segments) {
|
||||
if (control) {
|
||||
m_progress = &control->Progress;
|
||||
m_signals = {&control->cmdHead, &control->submittedSeq, &control->consumerParked,
|
||||
&control->producerParked, &control->eventRingFull};
|
||||
}
|
||||
}
|
||||
|
||||
Bool PipeWireEncoder::Valid() const { return m_control != nullptr && m_cmd != nullptr; }
|
||||
Bool PipeWireEncoder::Valid() const { return m_progress != nullptr && m_cmd != nullptr; }
|
||||
|
||||
Uint8* PipeWireEncoder::StageAllocate(Uint64 size) {
|
||||
if (m_stageBase == nullptr) {
|
||||
@@ -896,6 +926,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
if ((m_stageHead + skip + need) - m_stageTail <= m_stageCapacity) {
|
||||
const Uint64 at = (m_stageHead + skip) % m_stageCapacity;
|
||||
m_stageHead += skip + need;
|
||||
Transport::LinkMetricsStageBytes(size);
|
||||
return m_stageBase + at;
|
||||
}
|
||||
if (!reclaimed) {
|
||||
@@ -907,7 +938,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
if (m_stageRetirementBell == nullptr || m_stageMarkFront == m_stageMarks.size()) break;
|
||||
const Uint64 pending = m_stageMarks[m_stageMarkFront].Seq;
|
||||
const auto ready = [&] {
|
||||
return m_control->Progress.retiredSeq.load(std::memory_order_acquire) >= pending;
|
||||
return m_progress->retiredSeq.load(std::memory_order_acquire) >= pending;
|
||||
};
|
||||
if (!ready()) {
|
||||
// The allocation still cannot progress after reclamation. Count this
|
||||
@@ -920,7 +951,8 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// again. The hook is null in every standalone codec, which has no session and
|
||||
// therefore no reverse channel to drain.
|
||||
if (m_stageWaitHook != nullptr) m_stageWaitHook(m_stageWaitSelf);
|
||||
if (!ready() && !m_stageRetirementBell->Wait(m_control->producerParked, ready, 0, 5000)) {
|
||||
if (m_link) m_link->Flush();
|
||||
if (!ready() && !m_stageRetirementBell->Wait(*m_signals.ProducerParked, ready, 0, 5000)) {
|
||||
SessionFail(MGFatalFamily::RetirementWaitFailed, "MGPipe: Fatal{RetirementWaitFailed, \"SEG_STAGE\"} producer wait "
|
||||
"ended before the pending allocation retired (shutdown or timeout)");
|
||||
}
|
||||
@@ -936,7 +968,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
static_cast<unsigned long long>(m_stageCapacity),
|
||||
static_cast<unsigned long long>(m_stageHead - m_stageTail),
|
||||
static_cast<unsigned long long>(
|
||||
m_control != nullptr ? m_control->Progress.retiredSeq.load(std::memory_order_acquire) : 0));
|
||||
m_progress != nullptr ? m_progress->retiredSeq.load(std::memory_order_acquire) : 0));
|
||||
}
|
||||
|
||||
MGPBlobRef PipeWireEncoder::StageBytes(const void* bytes, Uint64 size) {
|
||||
@@ -958,6 +990,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
Uint8* slot = StageAllocate(size);
|
||||
std::memcpy(slot, bytes, static_cast<SizeT>(size));
|
||||
const Uint64 offset = static_cast<Uint64>(slot - m_stageBase);
|
||||
if (m_link) m_link->NoteStage({offset, size});
|
||||
|
||||
MGPBlobRef ref{};
|
||||
ref.Seg = kSegStage;
|
||||
@@ -1222,10 +1255,10 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
}
|
||||
|
||||
void PipeWireEncoder::ReclaimStagedBytes() {
|
||||
if (m_control == nullptr) {
|
||||
if (m_progress == nullptr) {
|
||||
return;
|
||||
}
|
||||
const Uint64 retired = m_control->Progress.retiredSeq.load(std::memory_order_acquire);
|
||||
const Uint64 retired = m_progress->retiredSeq.load(std::memory_order_acquire);
|
||||
Uint64 upTo = m_stageTail;
|
||||
while (m_stageMarkFront < m_stageMarks.size() &&
|
||||
m_stageMarks[m_stageMarkFront].Seq <= retired) {
|
||||
@@ -1281,11 +1314,11 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// popping a ring, and the release store on SEG_CMD's head below is what orders the
|
||||
// staged writes before the record that names them.
|
||||
m_cmd->Publish();
|
||||
if (m_control != nullptr) {
|
||||
if (m_progress != nullptr) {
|
||||
// submittedSeq is the one watermark the PRODUCER owns (Ring.h's five-watermark
|
||||
// block). Nobody waits on it; it answers "how far ahead of the server is the
|
||||
// client right now".
|
||||
m_control->submittedSeq.store(m_emitSeq, std::memory_order_release);
|
||||
m_signals.SubmittedSeq->store(m_emitSeq, std::memory_order_release);
|
||||
}
|
||||
// The reclaim has a trigger in this package, rather than depending on a c1 barrier
|
||||
// that does not exist yet: every publish is a chance to notice what the server has
|
||||
@@ -1324,9 +1357,15 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// Decoder
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
PipeWireDecoder::PipeWireDecoder(Transport::ILink* link, SegmentTable* segments, ReplySink* replies)
|
||||
: m_segments(segments), m_replies(replies), m_valid(link && link->Attached()) {
|
||||
m_auditPoison = MG_Config::Ipc.Audit;
|
||||
InstallApplyHook();
|
||||
}
|
||||
|
||||
PipeWireDecoder::PipeWireDecoder(Transport::RingControl* control, SegmentTable* segments,
|
||||
ReplySink* replies)
|
||||
: m_control(control), m_segments(segments), m_replies(replies) {
|
||||
: m_segments(segments), m_replies(replies), m_valid(control != nullptr) {
|
||||
m_auditPoison = MG_Config::Ipc.Audit;
|
||||
// Once, at construction, beside the resolver it is modelled on - not per record from
|
||||
// the apply thread. The thunk is inert without a decoder on the calling thread, so an
|
||||
@@ -1340,7 +1379,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
|
||||
void PipeWireDecoder::UninstallApplyHook() { MG_Pipe::gMGPipeWireRecordApply = nullptr; }
|
||||
|
||||
Bool PipeWireDecoder::Valid() const { return m_control != nullptr && m_segments != nullptr; }
|
||||
Bool PipeWireDecoder::Valid() const { return m_valid && m_segments != nullptr; }
|
||||
|
||||
Uint64 PipeWireDecoder::AppliedSeq() const { return m_applySeq; }
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// span at all (it only ever writes Ptr = nullptr). Install() therefore takes the role.
|
||||
class SegmentTable {
|
||||
public:
|
||||
void AttachLink(Transport::ILink* link);
|
||||
void Install(SegmentId seg, SegmentView view);
|
||||
SegmentView Get(SegmentId seg) const;
|
||||
|
||||
@@ -100,6 +101,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
static void UninstallProcessResolver();
|
||||
|
||||
private:
|
||||
Transport::ILink* m_link = nullptr;
|
||||
SegmentView m_views[kSegAdopt + 1];
|
||||
};
|
||||
|
||||
@@ -218,6 +220,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
class PipeWireEncoder {
|
||||
public:
|
||||
PipeWireEncoder() = default;
|
||||
PipeWireEncoder(Transport::ILink* link, SegmentTable* segments);
|
||||
PipeWireEncoder(Transport::RingControl* control, Transport::RingProducer* cmd,
|
||||
Transport::RingProducer* stage, SegmentTable* segments);
|
||||
|
||||
@@ -338,6 +341,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
Uint64 StageReclaimWaits() const;
|
||||
// The live session supplies its shutdown-aware producer doorbell. Standalone codecs
|
||||
// without a consumer cannot wait for retirement and retain the named refusal.
|
||||
void SetLink(Transport::ILink* link) { m_link = link; }
|
||||
void SetStageRetirementDoorbell(Transport::Doorbell* bell) { m_stageRetirementBell = bell; }
|
||||
|
||||
// P5e (ra, CONTRACT-P5E §2.6): THE STAGE BELL IS A CLIENT WAIT, so it has to drain the
|
||||
@@ -373,6 +377,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// over the segment; the in-segment offset is `cursor % capacity` and a run that would
|
||||
// straddle the end skips to the boundary, exactly as a ring does, but WITHOUT touching
|
||||
// RingControl - see ReclaimStagedBytes above.
|
||||
Transport::ILink* m_link = nullptr;
|
||||
Uint8* StageAllocate(Uint64 size);
|
||||
|
||||
// AN EMPTY STAGE STARTS OVER AT ZERO, so that the wrap skip is only ever charged
|
||||
@@ -386,7 +391,8 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// the head and underflow StagedBytesInFlight().
|
||||
void RebaseEmptyStage();
|
||||
|
||||
Transport::RingControl* m_control = nullptr;
|
||||
Transport::LinkProgress* m_progress = nullptr;
|
||||
Transport::LinkSignals m_signals{};
|
||||
Transport::RingProducer* m_cmd = nullptr;
|
||||
Transport::RingProducer* m_stage = nullptr;
|
||||
SegmentTable* m_segments = nullptr;
|
||||
@@ -606,6 +612,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
class PipeWireDecoder {
|
||||
public:
|
||||
PipeWireDecoder() = default;
|
||||
PipeWireDecoder(Transport::ILink* link, SegmentTable* segments, ReplySink* replies);
|
||||
PipeWireDecoder(Transport::RingControl* control, SegmentTable* segments,
|
||||
ReplySink* replies);
|
||||
|
||||
@@ -707,7 +714,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
|
||||
friend Bool MGPipeWireRecordApplyThunk(MG_Pipe::MGPWireOp, const void*, Uint64, Uint64);
|
||||
|
||||
Transport::RingControl* m_control = nullptr;
|
||||
Bool m_valid = false;
|
||||
SegmentTable* m_segments = nullptr;
|
||||
ReplySink* m_replies = nullptr;
|
||||
WireVerbSink* m_verbs = nullptr;
|
||||
|
||||
@@ -22,7 +22,7 @@ if (NOT WIN32)
|
||||
# P6 so: the SAME nine ITransport-contract cases InProcessTransportTest runs, over a real
|
||||
# socketpair with real threads, plus the three a stream has and a message queue does not.
|
||||
# POSIX for the same reason as FdPassingTest - and because CONTRACT-P6 §2.6 lands POSIX only.
|
||||
list(APPEND MOBILEGL_WIRE_TESTS SocketTransportTest)
|
||||
list(APPEND MOBILEGL_WIRE_TESTS SocketTransportTest StreamLinkTest)
|
||||
# P6 sm: two MobileGL processes. Needs the real server image beside it, so
|
||||
# the dependency is declared after the loop rather than assumed.
|
||||
list(APPEND MOBILEGL_WIRE_TESTS ServerSpawnTest)
|
||||
|
||||
@@ -118,6 +118,8 @@ TEST(ProtocolSmokeTest, UnionTagsAreFrozenWireValues) {
|
||||
EXPECT_EQ(static_cast<int>(CtrlMsg::AuxRequest), 8);
|
||||
EXPECT_EQ(static_cast<int>(CtrlMsg::Fatal), 9);
|
||||
EXPECT_EQ(static_cast<int>(CtrlMsg::LogLine), 10);
|
||||
EXPECT_EQ(static_cast<int>(CtrlMsg::Refuse), 11);
|
||||
EXPECT_EQ(static_cast<int>(CtrlMsg::LogFlush), 12);
|
||||
|
||||
EXPECT_EQ(static_cast<int>(SegmentKind::Cmd), 1);
|
||||
EXPECT_EQ(static_cast<int>(SegmentKind::Adopt), 6);
|
||||
@@ -156,3 +158,27 @@ TEST(ProtocolSmokeTest, TravelsAcrossTheTransportUnchanged) {
|
||||
ASSERT_NE(hello, nullptr);
|
||||
EXPECT_EQ(hello->pid(), 4242u);
|
||||
}
|
||||
|
||||
TEST(ProtocolSmokeTest, StreamHandshakeCarriesIndependentWireBuildAndServerWindows) {
|
||||
::flatbuffers::FlatBufferBuilder builder(512);
|
||||
auto terms = CreateLinkTerms(builder, DataPlane::Stream, WireForm::StructImage,
|
||||
1048560, 8388608, 33554432, 262144);
|
||||
auto hello = CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR,
|
||||
"commit-123", 1, 42, nullptr, 0, 0x12345678, terms, "pairing-token", DialMode::Connect);
|
||||
auto envelope = CreateCtrlEnvelope(builder, CtrlMsg::Hello, hello.Union());
|
||||
FinishCtrlEnvelopeBuffer(builder, envelope);
|
||||
::flatbuffers::Verifier verifier(builder.GetBufferPointer(), builder.GetSize());
|
||||
ASSERT_TRUE(VerifyCtrlEnvelopeBuffer(verifier));
|
||||
const auto* parsed = GetCtrlEnvelope(builder.GetBufferPointer())->msg_as_Hello();
|
||||
ASSERT_NE(parsed, nullptr);
|
||||
EXPECT_EQ(parsed->wireFingerprint(), 0x12345678u);
|
||||
EXPECT_EQ(parsed->buildFingerprint()->str(), "commit-123");
|
||||
EXPECT_EQ(parsed->token()->str(), "pairing-token");
|
||||
EXPECT_EQ(parsed->dialMode(), DialMode::Connect);
|
||||
ASSERT_NE(parsed->linkTerms(), nullptr);
|
||||
EXPECT_EQ(parsed->linkTerms()->dataPlane(), DataPlane::Stream);
|
||||
EXPECT_EQ(parsed->linkTerms()->maxReplyBytes(), 1048560u);
|
||||
EXPECT_EQ(parsed->linkTerms()->cmdWindowBytes(), 8388608u);
|
||||
EXPECT_EQ(parsed->linkTerms()->stageWindowBytes(), 33554432u);
|
||||
EXPECT_EQ(parsed->linkTerms()->eventWindowBytes(), 262144u);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,8 @@ namespace {
|
||||
auto stamp = builder.CreateString(GIT_COMMIT_HASH_SHORT);
|
||||
auto hello = ::MobileGL::Wire::CreateHello(
|
||||
builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, stamp,
|
||||
/*backendType=*/0u, /*pid=*/0u, /*configBlob=*/0, CapsAbiFingerprint());
|
||||
/*backendType=*/0u, /*pid=*/0u, /*configBlob=*/0, CapsAbiFingerprint(),
|
||||
CapsAbiFingerprint(), ::MobileGL::Wire::CreateLinkTerms(builder));
|
||||
auto root = ::MobileGL::Wire::CreateCtrlEnvelope(
|
||||
builder, ::MobileGL::Wire::CtrlMsg::Hello, hello.Union());
|
||||
::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root);
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
#include "Includes.h"
|
||||
|
||||
#include <MG_Remote/CapsCodec.h>
|
||||
#include <MG_Remote/Handshake.h>
|
||||
#include <MG_Pipe/PipeWireLayout.h>
|
||||
#include <MG_Remote/Client/ClientSession.h>
|
||||
#include <MG_Remote/Protocol/generated/protocol_generated.h>
|
||||
#include <MG_Remote/Server/ServerSession.h>
|
||||
@@ -114,100 +116,148 @@ namespace {
|
||||
// below. Also red, separately, by deleting any one `mix(...)` line from MixAbiFingerprint: the
|
||||
// matching EXPECT_NE names the input that stopped being mixed. Both perturbations were run.
|
||||
TEST(SessionHandshakeTest, TheAbiFingerprintChangesWhenAnyOfItsInputsDoes) {
|
||||
const Uint64 production = CapsAbiFingerprint();
|
||||
EXPECT_NE(production, 0u) << "0 is reserved for \"not stated\"";
|
||||
EXPECT_EQ(production, CapsAbiFingerprint()) << "not stable within one build";
|
||||
|
||||
const Transport::AbiFingerprintInputs inputs = CapsAbiFingerprintInputs();
|
||||
EXPECT_EQ(production, Transport::MixAbiFingerprint(inputs))
|
||||
<< "CapsAbiFingerprint() is not MixAbiFingerprint over CapsAbiFingerprintInputs(): the "
|
||||
"handshake compares a value this case cannot reach, which is finding 6 again";
|
||||
|
||||
// The inputs are the real ones, so a CapsAbiFingerprintInputs() that hard-coded a size
|
||||
// would be caught here rather than agreed with by a peer built from a different tree.
|
||||
const Uint64 production = WireFingerprint();
|
||||
const auto inputs = CapsAbiFingerprintInputs();
|
||||
EXPECT_NE(production, 0u);
|
||||
EXPECT_EQ(production, CapsAbiFingerprint());
|
||||
EXPECT_EQ(production, Transport::MixAbiFingerprint(inputs));
|
||||
EXPECT_EQ(inputs.DynamicParamsSize, sizeof(MG_Backend::DynamicBackendParameters));
|
||||
EXPECT_EQ(inputs.CapsSize, sizeof(MG_Pipe::MGPCaps));
|
||||
EXPECT_EQ(inputs.FunctionTableSize, sizeof(MG_Backend::GLFunctionsTable));
|
||||
EXPECT_EQ(inputs.FormatCapabilityTargets,
|
||||
static_cast<Uint64>(MG_Backend::kFormatCapabilityTargetCount));
|
||||
EXPECT_EQ(inputs.FormatCapabilityFormats,
|
||||
static_cast<Uint64>(MG_Backend::kFormatCapabilityFormatCount));
|
||||
EXPECT_NE(inputs.FormatCapabilitiesCodecVersion, 0u);
|
||||
EXPECT_NE(inputs.RendererInfoCodecVersion, 0u);
|
||||
EXPECT_EQ(inputs.MemberLayout, MG_Pipe::kMGPipeWireMemberLayoutDigest);
|
||||
EXPECT_EQ(inputs.CatalogueLayout, MG_Pipe::kMGPipeWireCatalogueDigest);
|
||||
EXPECT_EQ(inputs.RenderStateLayout, MG_Pipe::WireRenderStateDigest());
|
||||
EXPECT_EQ(inputs.OpCount, static_cast<Uint64>(MG_Pipe::MGPWireOp::kOpCount));
|
||||
EXPECT_EQ(inputs.AbiVersion, static_cast<Uint32>(MOBILEGL_ABI_VERSION(
|
||||
MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR)));
|
||||
// CONTRACT-P6 4.2. THE ASSERTION SPLITS WITH THE MACRO PAIR, and it has to: this used to
|
||||
// demand a non-empty stamp unconditionally and was RED in every build whose git could not be
|
||||
// read - a real signal, but one that told the reader nothing about WHICH of the two states
|
||||
// the build was in. Now the build system states it, and both states are asserted.
|
||||
ASSERT_NE(inputs.BuildStamp, nullptr);
|
||||
EXPECT_EQ(inputs.BuildStampPresent, static_cast<Uint32>(MOBILEGL_BUILD_STAMP_PRESENT))
|
||||
<< "the fingerprint disagrees with the build system about whether this build has a stamp";
|
||||
#if MGL_HANDSHAKE_TEST_HAS_GIT_HASH
|
||||
EXPECT_STREQ(inputs.BuildStamp, MOBILEGL_BUILD_STAMP_VALUE);
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_STAMP_PRESENT
|
||||
EXPECT_NE(inputs.BuildStamp[0], '\0')
|
||||
<< "the build claims a stamp and the fingerprint mixed an empty one";
|
||||
#else
|
||||
// A stampless build is a KNOWN, DECLARED state - CMake warns at configure time - but it must
|
||||
// not be a silent one here either. What still has to hold is that the ABSENCE is mixed, which
|
||||
// the BuildStampPresent perturbation below proves.
|
||||
EXPECT_STREQ(inputs.BuildStamp, "")
|
||||
<< "PRESENT is 0 but a value came through; the two halves of the pair disagree";
|
||||
#endif
|
||||
|
||||
// Every input moves the answer. Each lambda changes exactly one field of a copy of the
|
||||
// REAL inputs, so what is proven is that the production value depends on that field.
|
||||
const auto perturbed = [&](auto&& mutate) {
|
||||
Transport::AbiFingerprintInputs copy = inputs;
|
||||
EXPECT_EQ(inputs.PointerBits, sizeof(void*) * 8);
|
||||
const auto perturbed = [&](auto mutate) {
|
||||
auto copy = inputs;
|
||||
mutate(copy);
|
||||
return Transport::MixAbiFingerprint(copy);
|
||||
};
|
||||
EXPECT_NE(production, perturbed([](Transport::AbiFingerprintInputs& i) { ++i.DynamicParamsSize; }))
|
||||
<< "sizeof(DynamicBackendParameters) is not mixed";
|
||||
EXPECT_NE(production, perturbed([](Transport::AbiFingerprintInputs& i) { ++i.CapsSize; }))
|
||||
<< "sizeof(MGPCaps) is not mixed";
|
||||
EXPECT_NE(production, perturbed([](Transport::AbiFingerprintInputs& i) { ++i.FunctionTableSize; }))
|
||||
<< "sizeof(GLFunctionsTable) is not mixed";
|
||||
EXPECT_NE(production,
|
||||
perturbed([](Transport::AbiFingerprintInputs& i) { ++i.FormatCapabilityTargets; }))
|
||||
<< "kFormatCapabilityTargetCount is not mixed";
|
||||
EXPECT_NE(production,
|
||||
perturbed([](Transport::AbiFingerprintInputs& i) { ++i.FormatCapabilityFormats; }))
|
||||
<< "kFormatCapabilityFormatCount is not mixed";
|
||||
EXPECT_NE(production, perturbed([](Transport::AbiFingerprintInputs& i) {
|
||||
++i.FormatCapabilitiesCodecVersion;
|
||||
}))
|
||||
<< "kFormatCapabilitiesCodecVersion is not mixed";
|
||||
EXPECT_NE(production,
|
||||
perturbed([](Transport::AbiFingerprintInputs& i) { ++i.RendererInfoCodecVersion; }))
|
||||
<< "kRendererInfoCodecVersion is not mixed";
|
||||
EXPECT_NE(production, perturbed([](Transport::AbiFingerprintInputs& i) { ++i.OpCount; }))
|
||||
<< "MGPWireOp::kOpCount is not mixed (ID-33)";
|
||||
EXPECT_NE(production, perturbed([](Transport::AbiFingerprintInputs& i) { ++i.AbiVersion; }))
|
||||
<< "the protocol ABI version is not mixed (ID-33)";
|
||||
EXPECT_NE(production,
|
||||
perturbed([](Transport::AbiFingerprintInputs& i) { i.BuildStamp = "not-this-build"; }))
|
||||
<< "the git stamp is not mixed";
|
||||
// A missing stamp is not the same as an empty one, and neither is the same as a real build.
|
||||
// THE PRESENCE FLAG IS ITSELF AN INPUT (4.2), and this is the assertion that makes a
|
||||
// stampless build safe to reason about: a peer that says "I could not name my commit" must
|
||||
// not produce the same fingerprint as one whose commit genuinely is "". It has to move the
|
||||
// answer in EITHER build state, which is why it sits outside the #if below.
|
||||
EXPECT_NE(production, perturbed([](Transport::AbiFingerprintInputs& i) { ++i.BuildStampPresent; }))
|
||||
<< "BuildStampPresent is not mixed; 'no stamp' and 'empty stamp' are one input again";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.DynamicParamsSize; })) << "DynamicParamsSize";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.CapsSize; })) << "CapsSize";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.MemberLayout; })) << "MemberLayout";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.CatalogueLayout; })) << "CatalogueLayout";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.RenderStateLayout; })) << "RenderStateLayout";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.FormatCapabilityTargets; })) << "FormatCapabilityTargets";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.FormatCapabilityFormats; })) << "FormatCapabilityFormats";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.FormatCapabilitiesCodecVersion; })) << "FormatCapabilitiesCodecVersion";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.RendererInfoCodecVersion; })) << "RendererInfoCodecVersion";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.OpCount; })) << "OpCount";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.AbiVersion; })) << "AbiVersion";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.PointerBits; })) << "PointerBits";
|
||||
EXPECT_NE(production, perturbed([](auto& i) { ++i.LittleEndian; })) << "LittleEndian";
|
||||
}
|
||||
|
||||
EXPECT_NE(production, perturbed([](Transport::AbiFingerprintInputs& i) { i.BuildStamp = nullptr; }));
|
||||
#if MOBILEGL_BUILD_STAMP_PRESENT
|
||||
// Vacuous without a stamp: production's value already IS "", so this perturbation would be
|
||||
// the identity and the assertion would pass while proving nothing.
|
||||
EXPECT_NE(production, perturbed([](Transport::AbiFingerprintInputs& i) { i.BuildStamp = ""; }));
|
||||
TEST(SessionHandshakeTest, SameWidthFieldReorderingChangesTheWireDigestWithoutABuildStamp) {
|
||||
using namespace MG_Pipe;
|
||||
std::vector<WireLayoutMember> fields(std::begin(kMGPipeWireLayoutMembers),
|
||||
std::end(kMGPipeWireLayoutMembers));
|
||||
// MGPRange.Offset and Size have the same width: sizeof-only checks cannot see this.
|
||||
SizeT offsetIndex = 0, sizeIndex = 0;
|
||||
for (SizeT i = 0; i < fields.size(); ++i) {
|
||||
if (std::strcmp(fields[i].Name, "MGPRange.Offset") == 0) offsetIndex = i;
|
||||
if (std::strcmp(fields[i].Name, "MGPRange.Size") == 0) sizeIndex = i;
|
||||
}
|
||||
ASSERT_NE(offsetIndex, sizeIndex);
|
||||
ASSERT_EQ(fields[offsetIndex].Size, fields[sizeIndex].Size);
|
||||
std::swap(fields[offsetIndex].Offset, fields[sizeIndex].Offset);
|
||||
EXPECT_NE(WireMemberLayoutDigest(fields.data(), fields.size()), kMGPipeWireMemberLayoutDigest);
|
||||
const auto before = WireFingerprint();
|
||||
EXPECT_STREQ(BuildFingerprint(), MOBILEGL_BUILD_STAMP_VALUE);
|
||||
EXPECT_EQ(BuildFingerprintPresent(), MOBILEGL_BUILD_STAMP_PRESENT != 0);
|
||||
EXPECT_EQ(before, WireFingerprint());
|
||||
}
|
||||
|
||||
namespace {
|
||||
class ScopedHandshakeEnvironment {
|
||||
public:
|
||||
ScopedHandshakeEnvironment(const char* name, const char* value) : m_name(name) {
|
||||
const char* previous = std::getenv(name);
|
||||
m_present = previous != nullptr;
|
||||
if (m_present) m_previous = previous;
|
||||
Set(value);
|
||||
}
|
||||
~ScopedHandshakeEnvironment() { Set(m_present ? m_previous.c_str() : nullptr); }
|
||||
private:
|
||||
void Set(const char* value) {
|
||||
#if defined(_WIN32)
|
||||
::_putenv_s(m_name, value == nullptr ? "" : value);
|
||||
#else
|
||||
if (value == nullptr) ::unsetenv(m_name); else ::setenv(m_name, value, 1);
|
||||
#endif
|
||||
EXPECT_NE(perturbed([](Transport::AbiFingerprintInputs& i) { i.BuildStamp = nullptr; }),
|
||||
perturbed([](Transport::AbiFingerprintInputs& i) { i.BuildStamp = ""; }))
|
||||
<< "\"no stamp\" and \"an empty stamp\" collapsed into one input";
|
||||
}
|
||||
const char* m_name;
|
||||
bool m_present = false;
|
||||
std::string m_previous;
|
||||
};
|
||||
|
||||
std::vector<Uint8> ReadHandshakeFrame(Transport::ITransport& transport) {
|
||||
Uint64 bytes = 0;
|
||||
if (transport.ReceiveFrame({nullptr, 0}, &bytes, 1000) != MOBILEGL_ERR_BUFFER_TOO_SMALL) return {};
|
||||
std::vector<Uint8> frame(bytes);
|
||||
if (transport.ReceiveFrame({frame.data(), frame.size()}, &bytes, 1000) != MOBILEGL_OK) return {};
|
||||
return frame;
|
||||
}
|
||||
|
||||
void CheckHandshake(Uint32 major, Uint64 wire, const char* build,
|
||||
::MobileGL::Wire::DialMode dial, ::MobileGL::Wire::RefuseCode expected) {
|
||||
using namespace ::MobileGL::Wire;
|
||||
std::unique_ptr<Transport::InProcessTransport> client, server;
|
||||
Transport::InProcessTransport::CreatePair(client, server);
|
||||
::flatbuffers::FlatBufferBuilder builder(512);
|
||||
auto terms = CreateLinkTerms(builder);
|
||||
auto hello = CreateHelloDirect(builder, major, MOBILEGL_PROTOCOL_ABI_MINOR, build,
|
||||
0, 1, nullptr, wire, wire, terms, nullptr, dial);
|
||||
auto root = CreateCtrlEnvelope(builder, CtrlMsg::Hello, hello.Union());
|
||||
FinishCtrlEnvelopeBuffer(builder, root);
|
||||
std::vector<Uint8> first(builder.GetBufferPointer(), builder.GetBufferPointer() + builder.GetSize());
|
||||
Server::ServerSession session;
|
||||
Transport::SessionSegmentSizes sizes;
|
||||
sizes.CmdRingBytes = 4096; sizes.StageBytes = 4096;
|
||||
sizes.ReplyBytes = 4096; sizes.EventRingBytes = 4096;
|
||||
session.SetSegmentSizes(sizes);
|
||||
const auto result = session.Accept(*server, &first);
|
||||
EXPECT_EQ(result, expected == RefuseCode::None ? MOBILEGL_OK : MOBILEGL_ERR_PROTOCOL_MISMATCH);
|
||||
const auto reply = ReadHandshakeFrame(*client);
|
||||
ASSERT_FALSE(reply.empty());
|
||||
::flatbuffers::Verifier verifier(reply.data(), reply.size());
|
||||
ASSERT_TRUE(VerifyCtrlEnvelopeBuffer(verifier));
|
||||
const auto* envelope = GetCtrlEnvelope(reply.data());
|
||||
if (expected == RefuseCode::None) {
|
||||
ASSERT_NE(envelope->msg_as_Welcome(), nullptr);
|
||||
EXPECT_EQ(envelope->msg_as_Welcome()->wireFingerprint(), WireFingerprint());
|
||||
ASSERT_NE(envelope->msg_as_Welcome()->linkTerms(), nullptr);
|
||||
EXPECT_EQ(envelope->msg_as_Welcome()->linkTerms()->cmdWindowBytes(), 4096u);
|
||||
} else {
|
||||
ASSERT_NE(envelope->msg_as_Refuse(), nullptr);
|
||||
EXPECT_EQ(envelope->msg_as_Refuse()->code(), expected);
|
||||
EXPECT_FALSE(session.Accepted());
|
||||
}
|
||||
session.Close();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SessionHandshakeTest, WireMajorMismatchReturnsNamedRefuseWithoutAborting) {
|
||||
CheckHandshake(99, WireFingerprint(), BuildFingerprint(), ::MobileGL::Wire::DialMode::No,
|
||||
::MobileGL::Wire::RefuseCode::ProtocolVersion);
|
||||
}
|
||||
TEST(SessionHandshakeTest, WireLayoutMismatchReturnsNamedRefuseWithoutAborting) {
|
||||
CheckHandshake(MOBILEGL_PROTOCOL_ABI_MAJOR, WireFingerprint() ^ 1, BuildFingerprint(),
|
||||
::MobileGL::Wire::DialMode::No, ::MobileGL::Wire::RefuseCode::WireFingerprint);
|
||||
}
|
||||
TEST(SessionHandshakeTest, ConnectAcceptsDifferentBuildWithIdenticalWire) {
|
||||
ScopedHandshakeEnvironment required("MOBILEGL_IPC_REQUIRE_SAME_BUILD", "0");
|
||||
CheckHandshake(MOBILEGL_PROTOCOL_ABI_MAJOR, WireFingerprint(), "different-commit",
|
||||
::MobileGL::Wire::DialMode::Connect, ::MobileGL::Wire::RefuseCode::None);
|
||||
}
|
||||
TEST(SessionHandshakeTest, ForkRefusesDifferentBuildWithIdenticalWire) {
|
||||
CheckHandshake(MOBILEGL_PROTOCOL_ABI_MAJOR, WireFingerprint(), "different-commit",
|
||||
::MobileGL::Wire::DialMode::Fork, ::MobileGL::Wire::RefuseCode::BuildFingerprint);
|
||||
}
|
||||
TEST(SessionHandshakeTest, ConnectCanRequireTheSameBuild) {
|
||||
ScopedHandshakeEnvironment required("MOBILEGL_IPC_REQUIRE_SAME_BUILD", "1");
|
||||
CheckHandshake(MOBILEGL_PROTOCOL_ABI_MAJOR, WireFingerprint(), "different-commit",
|
||||
::MobileGL::Wire::DialMode::Connect, ::MobileGL::Wire::RefuseCode::BuildFingerprint);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
@@ -58,6 +60,68 @@ using namespace MobileGL::MG_Remote::Transport;
|
||||
|
||||
#if !defined(_WIN32)
|
||||
|
||||
TEST(SocketTransportTest, TcpCarriesControlAndIndependentDataWithKeepalive) {
|
||||
// Find an available loopback port; Listen deliberately refuses ambiguous port 0 URIs.
|
||||
int reservation = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
ASSERT_GE(reservation, 0);
|
||||
sockaddr_in address{};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
ASSERT_EQ(::bind(reservation, reinterpret_cast<sockaddr*>(&address), sizeof(address)), 0);
|
||||
socklen_t addressSize = sizeof(address);
|
||||
ASSERT_EQ(::getsockname(reservation, reinterpret_cast<sockaddr*>(&address), &addressSize), 0);
|
||||
const auto endpoint = std::string("tcp://127.0.0.1:") + std::to_string(ntohs(address.sin_port));
|
||||
::close(reservation);
|
||||
int listener = -1;
|
||||
ASSERT_EQ(SocketTransport::Listen(endpoint, &listener), MOBILEGL_OK);
|
||||
std::unique_ptr<SocketTransport> client, server;
|
||||
ASSERT_EQ(SocketTransport::ConnectTo(endpoint, 1000, client), MOBILEGL_OK);
|
||||
ASSERT_EQ(SocketTransport::AcceptPair(listener, 1000, server), MOBILEGL_OK);
|
||||
::close(listener);
|
||||
ASSERT_TRUE(client->IsTcp());
|
||||
ASSERT_TRUE(server->IsTcp());
|
||||
for (auto* transport : {client.get(), server.get()}) {
|
||||
int option = 0;
|
||||
socklen_t size = sizeof(option);
|
||||
ASSERT_EQ(::getsockopt(transport->StreamFd(), IPPROTO_TCP, TCP_NODELAY, &option, &size), 0);
|
||||
EXPECT_EQ(option, 1);
|
||||
ASSERT_EQ(::getsockopt(transport->StreamFd(), SOL_SOCKET, SO_KEEPALIVE, &option, &size), 0);
|
||||
EXPECT_EQ(option, 1);
|
||||
EXPECT_EQ(transport->ShareFd(0, {nullptr, 0}), MOBILEGL_ERR_UNSUPPORTED);
|
||||
}
|
||||
const int clientData = client->TakeDataFd(), serverData = server->TakeDataFd();
|
||||
ASSERT_GE(clientData, 0);
|
||||
ASSERT_GE(serverData, 0);
|
||||
EXPECT_EQ(client->TakeDataFd(), -1);
|
||||
const char message[] = "independent";
|
||||
ASSERT_EQ(::send(clientData, message, sizeof(message), MSG_NOSIGNAL), sizeof(message));
|
||||
char received[sizeof(message)]{};
|
||||
ASSERT_EQ(::recv(serverData, received, sizeof(received), MSG_WAITALL), sizeof(received));
|
||||
EXPECT_EQ(std::memcmp(message, received, sizeof(message)), 0);
|
||||
ASSERT_EQ(server->SendFrame({message, sizeof(message)}), MOBILEGL_OK);
|
||||
std::uint64_t size = 0;
|
||||
ASSERT_EQ(client->ReceiveFrame({received, sizeof(received)}, &size, 1000), MOBILEGL_OK);
|
||||
EXPECT_EQ(size, sizeof(message));
|
||||
::close(clientData);
|
||||
::close(serverData);
|
||||
}
|
||||
|
||||
TEST(SocketTransportTest, TcpRejectsMalformedNamesAndUnauthenticatedWildcard) {
|
||||
int listener = -1;
|
||||
for (const char* endpoint : {"tcp://", "tcp://localhost", "tcp://localhost:0", "tcp://localhost:65536",
|
||||
"tcp://localhost:-1", "tcp://[::1]broken", "tcp://::1:40000"}) {
|
||||
EXPECT_EQ(SocketTransport::Listen(endpoint, &listener), MOBILEGL_ERR_INVALID_ARGUMENT) << endpoint;
|
||||
EXPECT_EQ(listener, -1);
|
||||
}
|
||||
const char* prior = std::getenv("MOBILEGL_IPC_TOKEN");
|
||||
const std::string saved = prior ? prior : "";
|
||||
const bool wasSet = prior != nullptr;
|
||||
::unsetenv("MOBILEGL_IPC_TOKEN");
|
||||
EXPECT_EQ(SocketTransport::Listen("tcp://0.0.0.0:40613", &listener), MOBILEGL_ERR_PROTOCOL_MISMATCH);
|
||||
EXPECT_EQ(listener, -1);
|
||||
if (wasSet) ::setenv("MOBILEGL_IPC_TOKEN", saved.c_str(), 1);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
MobileGLByteSpan Span(const std::string& text) {
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
#include <MG_Remote/Transport/StreamLink.h>
|
||||
#include <MG_Remote/Transport/SessionRings.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace MobileGL::MG_Remote::Transport;
|
||||
namespace {
|
||||
class StreamPair : public testing::Test {
|
||||
protected:
|
||||
SessionSegments clientMemory, serverMemory;
|
||||
StreamLink client, server;
|
||||
RingProducer commands;
|
||||
RingConsumer consumer;
|
||||
SessionProducer publish;
|
||||
SessionConsumer apply;
|
||||
void SetUp() override {
|
||||
SessionSegmentSizes sizes;
|
||||
sizes.CmdRingBytes = 1024;
|
||||
sizes.StageBytes = 4096;
|
||||
sizes.ReplyBytes = 4096;
|
||||
sizes.EventRingBytes = 1024;
|
||||
ASSERT_EQ(clientMemory.CreatePrivate(sizes, MemoryRole::Client), MOBILEGL_OK);
|
||||
ASSERT_EQ(serverMemory.CreatePrivate(sizes, MemoryRole::Server), MOBILEGL_OK);
|
||||
int sockets[2];
|
||||
ASSERT_EQ(::socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), 0);
|
||||
ASSERT_EQ(client.Attach(sockets[0], clientMemory, TransportRoleTag::ClientProducer), MOBILEGL_OK);
|
||||
ASSERT_EQ(server.Attach(sockets[1], serverMemory, TransportRoleTag::ServerConsumer), MOBILEGL_OK);
|
||||
commands = RingProducer(clientMemory.CmdControl(), clientMemory.CmdRingBase(), 1024, RingCursorSet::Cmd);
|
||||
consumer = RingConsumer(serverMemory.CmdControl(), serverMemory.CmdRingBase(), 1024, RingCursorSet::Cmd);
|
||||
publish.Attach(clientMemory.CmdControl(), &commands, &client.ConsumerBell(), &client.ProducerBell(), 0);
|
||||
publish.SetLink(&client);
|
||||
apply.Attach(serverMemory.CmdControl(), &consumer, &server.ProducerBell(), &server.ConsumerBell(), 0);
|
||||
apply.SetLink(&server);
|
||||
}
|
||||
void TearDown() override {
|
||||
client.Detach();
|
||||
server.Detach();
|
||||
}
|
||||
void Queue(std::uint64_t seq, std::uint64_t value) {
|
||||
auto* p = commands.Reserve(1, 0, sizeof value);
|
||||
ASSERT_NE(p, nullptr);
|
||||
std::memcpy(p, &value, sizeof value);
|
||||
publish.PublishAndNotify(seq);
|
||||
}
|
||||
template <class F>
|
||||
bool Eventually(F ready) {
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
while (!ready() && std::chrono::steady_clock::now() < deadline)
|
||||
std::this_thread::yield();
|
||||
return ready();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(StreamPair, StagingArrivesBeforeItsCommandAndLastRecordProgressFlushesOnIdle) {
|
||||
const std::array<unsigned char, 8> bytes{1, 2, 3, 4, 5, 6, 7, 8};
|
||||
std::memcpy(static_cast<unsigned char*>(clientMemory.StageBase()) + 24, bytes.data(), bytes.size());
|
||||
client.NoteStage({24, bytes.size()});
|
||||
Queue(1, 24);
|
||||
ASSERT_EQ(client.Flush(), MOBILEGL_OK);
|
||||
ASSERT_EQ(apply.WaitForWork(2000), SessionWait::Reached);
|
||||
ASSERT_TRUE(apply.ApplyOne([&](const RingRecordView& v) {
|
||||
std::uint64_t offset;
|
||||
std::memcpy(&offset, v.payload, sizeof offset);
|
||||
const void* p = nullptr;
|
||||
ASSERT_EQ(server.ResolveSpan(LinkSegment::Stage, {offset, bytes.size()}, &p), MOBILEGL_OK);
|
||||
EXPECT_EQ(std::memcmp(p, bytes.data(), bytes.size()), 0);
|
||||
}));
|
||||
apply.RetireThrough(1);
|
||||
// This is the production idle discipline, not a polling sender thread.
|
||||
EXPECT_EQ(apply.WaitForWork(1), SessionWait::TimedOut);
|
||||
EXPECT_EQ(publish.WaitForApplied(1, 2000), SessionWait::Reached);
|
||||
EXPECT_EQ(client.Progress()->retiredSeq.load(), 1u);
|
||||
}
|
||||
|
||||
TEST_F(StreamPair, RetiredByteCursorCrossesManyWrapsWithoutStageRingCursors) {
|
||||
for (std::uint64_t seq = 1; seq <= 160; ++seq) {
|
||||
Queue(seq, seq);
|
||||
ASSERT_EQ(client.Flush(), MOBILEGL_OK);
|
||||
ASSERT_EQ(apply.WaitForWork(2000), SessionWait::Reached);
|
||||
ASSERT_TRUE(apply.ApplyOne([&](const RingRecordView& v) {
|
||||
std::uint64_t value;
|
||||
std::memcpy(&value, v.payload, sizeof value);
|
||||
EXPECT_EQ(value, seq);
|
||||
}));
|
||||
apply.RetireThrough(seq);
|
||||
ASSERT_EQ(server.FlushProgress(), MOBILEGL_OK);
|
||||
ASSERT_EQ(publish.WaitForApplied(seq, 2000), SessionWait::Reached);
|
||||
}
|
||||
EXPECT_GT(commands.LocalHead(), 1024u);
|
||||
EXPECT_EQ(commands.FreeBytes(), 1024u);
|
||||
EXPECT_EQ(clientMemory.CmdControl()->stageHead.load(), 0u);
|
||||
EXPECT_EQ(clientMemory.CmdControl()->stageRetiredTail.load(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(StreamPair, UnwantedReplyDoesNotBlockLaterReplyAndAppliedPublication) {
|
||||
Queue(1, 1);
|
||||
Queue(2, 2);
|
||||
ASSERT_EQ(client.Flush(), MOBILEGL_OK);
|
||||
ASSERT_EQ(apply.WaitForWork(2000), SessionWait::Reached);
|
||||
ASSERT_TRUE(apply.ApplyOne([&](const RingRecordView&) {
|
||||
const std::uint64_t value = 11;
|
||||
EXPECT_EQ(server.PostReply(1, 0, &value, sizeof value), MOBILEGL_OK);
|
||||
}));
|
||||
ASSERT_TRUE(apply.ApplyOne([&](const RingRecordView&) {
|
||||
const std::uint64_t value = 22;
|
||||
EXPECT_EQ(server.PostReply(2, 0, &value, sizeof value), MOBILEGL_OK);
|
||||
}));
|
||||
apply.RetireThrough(2);
|
||||
ASSERT_EQ(server.FlushProgress(), MOBILEGL_OK);
|
||||
ASSERT_EQ(publish.WaitForApplied(2, 2000), SessionWait::Reached);
|
||||
std::int32_t status;
|
||||
const void* p = nullptr;
|
||||
std::uint64_t size = 0;
|
||||
ASSERT_EQ(client.ReadReply(2, &status, &p, &size), MOBILEGL_OK);
|
||||
ASSERT_EQ(size, sizeof(std::uint64_t));
|
||||
std::uint64_t value;
|
||||
std::memcpy(&value, p, sizeof value);
|
||||
EXPECT_EQ(value, 22u);
|
||||
}
|
||||
|
||||
TEST_F(StreamPair, EventDrainReturnsCreditAndClearsRemoteFullLatch) {
|
||||
EventRingProducer events(serverMemory.EventControl(), serverMemory.CmdControl(), serverMemory.EventRingBase(),
|
||||
1024);
|
||||
EventRingConsumer read(clientMemory.EventControl(), clientMemory.CmdControl(), clientMemory.EventRingBase(),
|
||||
1024, clientMemory.EventSegmentBase());
|
||||
read.SetLink(&client);
|
||||
auto* p = events.Reserve(kEventGlError, 64);
|
||||
ASSERT_NE(p, nullptr);
|
||||
std::memset(p, 0, 64);
|
||||
events.Ring().Publish();
|
||||
serverMemory.CmdControl()->eventRingFull.store(1);
|
||||
ASSERT_EQ(server.FlushProgress(), MOBILEGL_OK);
|
||||
ASSERT_TRUE(Eventually([&] { return clientMemory.CmdControl()->eventRingFull.load() != 0; }));
|
||||
RingRecordView record;
|
||||
ASSERT_TRUE(read.Pop(record));
|
||||
read.Drained();
|
||||
ASSERT_TRUE(Eventually([&] { return serverMemory.CmdControl()->eventRingFull.load() == 0; }));
|
||||
EXPECT_EQ(events.Ring().FreeBytes(), 1024u);
|
||||
}
|
||||
|
||||
TEST_F(StreamPair, PeerEofKillsBothLocalBellsAndWakesAWaiter) {
|
||||
server.Detach();
|
||||
ASSERT_TRUE(Eventually([&] { return client.PeerHungUp(); }));
|
||||
EXPECT_TRUE(client.ProducerBell().Dead());
|
||||
EXPECT_TRUE(client.ConsumerBell().Dead());
|
||||
EXPECT_EQ(publish.WaitForApplied(10, 2000), SessionWait::ShutDown);
|
||||
}
|
||||
|
||||
void WriteFrame(int fd, unsigned kind, std::uint64_t a, const void* payload, std::uint32_t size) {
|
||||
unsigned char h[28]{};
|
||||
auto put = [&](unsigned at, std::uint64_t value, unsigned width) {
|
||||
for (unsigned i = 0; i < width; ++i)
|
||||
h[at + i] = value >> (8 * i);
|
||||
};
|
||||
put(0, 0x444c474d, 4);
|
||||
put(4, size, 4);
|
||||
h[8] = kind;
|
||||
put(12, a, 8);
|
||||
ASSERT_EQ(::send(fd, h, sizeof h, MSG_NOSIGNAL), sizeof h);
|
||||
if (size) ASSERT_EQ(::send(fd, payload, size, MSG_NOSIGNAL), size);
|
||||
}
|
||||
|
||||
TEST(StreamLinkDeath, StageOutsideNegotiatedWindowIsNamedProtocolCorruption) {
|
||||
EXPECT_DEATH(
|
||||
{
|
||||
SessionSegments memory;
|
||||
SessionSegmentSizes sizes;
|
||||
sizes.StageBytes = 64;
|
||||
memory.CreatePrivate(sizes, MemoryRole::Server);
|
||||
int sockets[2];
|
||||
socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
|
||||
StreamLink link;
|
||||
link.Attach(sockets[0], memory, TransportRoleTag::ServerConsumer);
|
||||
unsigned char byte = 1;
|
||||
WriteFrame(sockets[1], 2, 64, &byte, 1);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
},
|
||||
"ProtocolCorruption.*window");
|
||||
}
|
||||
|
||||
TEST(StreamLinkDeath, RegressingProgressIsNamedProtocolCorruption) {
|
||||
EXPECT_DEATH(
|
||||
{
|
||||
SessionSegments memory;
|
||||
SessionSegmentSizes sizes;
|
||||
memory.CreatePrivate(sizes, MemoryRole::Client);
|
||||
memory.CmdControl()->submittedSeq.store(3);
|
||||
int sockets[2];
|
||||
socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
|
||||
StreamLink link;
|
||||
link.Attach(sockets[0], memory, TransportRoleTag::ClientProducer);
|
||||
unsigned char progress[64]{};
|
||||
progress[0] = 2;
|
||||
WriteFrame(sockets[1], 4, 0, progress, 64);
|
||||
progress[0] = 1;
|
||||
WriteFrame(sockets[1], 4, 0, progress, 64);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
},
|
||||
"ProtocolCorruption.*backwards");
|
||||
}
|
||||
} // namespace
|
||||
@@ -279,6 +279,7 @@ TEST(SurfaceControlFrameTest, TheReplyHalfRoundTripsAsASurfaceReply) {
|
||||
frame.ok = true;
|
||||
frame.eglMajor = 1;
|
||||
frame.eglMinor = 5;
|
||||
frame.eventHead = 0x123456789ull;
|
||||
|
||||
flatbuffers::FlatBufferBuilder builder(256);
|
||||
EncodeSurfaceReplyFrame(frame, &builder);
|
||||
@@ -294,6 +295,7 @@ TEST(SurfaceControlFrameTest, TheReplyHalfRoundTripsAsASurfaceReply) {
|
||||
EXPECT_TRUE(back.ok);
|
||||
EXPECT_EQ(back.eglMajor, 1);
|
||||
EXPECT_EQ(back.eglMinor, 5);
|
||||
EXPECT_EQ(back.eventHead, frame.eventHead) << "control replies must fence data events";
|
||||
}
|
||||
|
||||
TEST(SurfaceControlFrameTest, MalformedAndInprocOnlyFramesRefuseToEncode) {
|
||||
|
||||
@@ -12,12 +12,38 @@
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util::Debug {
|
||||
std::mutex& LogMutex();
|
||||
static FILE* s_logFile = nullptr;
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// The server role's own sink. Opened lazily, on the first line a server-role thread
|
||||
// writes, so a process that never plays the role never creates the file.
|
||||
static FILE* s_serverLogFile = nullptr;
|
||||
static LogForwarder s_forwarder = nullptr;
|
||||
static void* s_forwarderUser = nullptr;
|
||||
|
||||
void SetLogForwarder(LogForwarder forwarder, void* user) {
|
||||
std::lock_guard<std::mutex> lock(LogMutex());
|
||||
s_forwarder = forwarder;
|
||||
s_forwarderUser = user;
|
||||
}
|
||||
|
||||
void WithLogBarrier(void (*action)(void*), void* user) {
|
||||
std::lock_guard<std::mutex> lock(LogMutex());
|
||||
action(user);
|
||||
}
|
||||
|
||||
void WritePeerLog(const char* message) {
|
||||
std::lock_guard<std::mutex> lock(LogMutex());
|
||||
const char* path = std::getenv("MOBILEGL_LOG_FILE_PATH");
|
||||
if (!path || !*path) path = MOBILEGL_LOG_FILE_PATH;
|
||||
if (!path || !*path) return;
|
||||
if (!s_serverLogFile) s_serverLogFile = std::fopen(RoleLogPath(path, LogRole::Server).c_str(), "w");
|
||||
if (s_serverLogFile) {
|
||||
std::fputs(message, s_serverLogFile);
|
||||
std::fflush(s_serverLogFile);
|
||||
}
|
||||
}
|
||||
|
||||
// THE PROCESS DEFAULT, read once. A spawned server image is the server on every thread -
|
||||
// there is no other role in it - and MOBILEGL_IPC_ROLE is set by the launcher before
|
||||
@@ -256,6 +282,9 @@ namespace MobileGL {
|
||||
#endif
|
||||
|
||||
WriteToFile(out.c_str());
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (s_forwarder && ThreadIsServerRole()) s_forwarder(s_forwarderUser, out.c_str());
|
||||
#endif
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@@ -136,6 +136,10 @@ namespace MobileGL {
|
||||
void Close();
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
using LogForwarder = void (*)(void*, const char*);
|
||||
void SetLogForwarder(LogForwarder forwarder, void* user);
|
||||
void WritePeerLog(const char* message);
|
||||
void WithLogBarrier(void (*action)(void*), void* user);
|
||||
// P6: ONE LOG PER ROLE, and the role is a per-THREAD fact because under inproc both
|
||||
// roles live in one process. The client keeps MOBILEGL_LOG_FILE_PATH unchanged so
|
||||
// every existing reader keeps its path; the server's lines go to the same path with
|
||||
|
||||
@@ -2,7 +2,17 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name=".MobileGLServerService"
|
||||
android:exported="true"
|
||||
android:foregroundServiceType="dataSync"
|
||||
android:process=":mglsrv" />
|
||||
<activity
|
||||
android:name=".PluginActivity"
|
||||
tools:node="remove" />
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package top.mobilegl.plugin;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Map;
|
||||
|
||||
/** Trace-only entry point for a TCP supervisor; EGL belongs to its session children. */
|
||||
public final class MobileGLServerService extends Service {
|
||||
private static final String TAG = "MobileGLServer";
|
||||
private static final String CHANNEL = "mobilegl-server";
|
||||
private volatile Process supervisor;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
|
||||
@Override public IBinder onBind(Intent intent) { return null; }
|
||||
|
||||
@Override public void onCreate() {
|
||||
super.onCreate();
|
||||
NotificationManager notifications = getSystemService(NotificationManager.class);
|
||||
notifications.createNotificationChannel(new NotificationChannel(
|
||||
CHANNEL, "MobileGL TCP server", NotificationManager.IMPORTANCE_LOW));
|
||||
startForeground(40613, new Notification.Builder(this, CHANNEL)
|
||||
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
||||
.setContentTitle("MobileGL TCP server")
|
||||
.setContentText("Serving remote rendering sessions")
|
||||
.setOngoing(true).build());
|
||||
wakeLock = getSystemService(PowerManager.class).newWakeLock(
|
||||
PowerManager.PARTIAL_WAKE_LOCK, "MobileGL:TCPServer");
|
||||
wakeLock.acquire();
|
||||
}
|
||||
|
||||
// Keep the replay runner's KEY=VALUE;KEY grammar, including unset and empty values.
|
||||
static void applyEnvironment(Map<String, String> env, String overrides) {
|
||||
if (overrides == null) return;
|
||||
for (String entry : overrides.split(";", -1)) {
|
||||
int separator = entry.indexOf('=');
|
||||
if (entry.isEmpty() || separator == 0) continue;
|
||||
if (separator < 0) env.remove(entry);
|
||||
else env.put(entry.substring(0, separator), entry.substring(separator + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@Override public synchronized int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (supervisor != null && supervisor.isAlive()) {
|
||||
Log.w(TAG, "supervisor already running; stop the service before reconfiguring");
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
String endpoint = intent == null ? null : intent.getStringExtra("listen");
|
||||
if (endpoint == null) endpoint = "tcp://127.0.0.1:40613";
|
||||
try {
|
||||
File executable = new File(getApplicationInfo().nativeLibraryDir, "libMobileGLServer.so");
|
||||
ProcessBuilder builder = new ProcessBuilder(executable.getAbsolutePath(), endpoint, "--serve");
|
||||
builder.directory(getFilesDir()).redirectErrorStream(true);
|
||||
Map<String, String> env = builder.environment();
|
||||
applyEnvironment(env, intent == null ? null : intent.getStringExtra("env"));
|
||||
env.put("MOBILEGL_IPC_ROLE", "server");
|
||||
env.put("MOBILEGL_IPC_DIAL", "no");
|
||||
env.put("MOBILEGL_IPC_LOG_FORWARD", "1");
|
||||
env.remove("MOBILEGL_IPC_CONTROL");
|
||||
env.put("MOBILEGL_IPC_TOKEN", intent == null || intent.getStringExtra("token") == null
|
||||
? "" : intent.getStringExtra("token"));
|
||||
env.putIfAbsent("MOBILEGL_LOG_FILE_PATH", new File(getFilesDir(), "mgl.log").getAbsolutePath());
|
||||
env.put("LD_LIBRARY_PATH", getApplicationInfo().nativeLibraryDir);
|
||||
supervisor = builder.start();
|
||||
final Process child = supervisor;
|
||||
new Thread(() -> {
|
||||
try (BufferedReader output = new BufferedReader(new InputStreamReader(child.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = output.readLine()) != null) Log.i(TAG, line);
|
||||
Log.i(TAG, "supervisor exited " + child.waitFor());
|
||||
} catch (Exception error) {
|
||||
Log.e(TAG, "supervisor output failed", error);
|
||||
} finally {
|
||||
stopSelf(startId);
|
||||
}
|
||||
}, "mgl-supervisor-log").start();
|
||||
} catch (Exception error) {
|
||||
Log.e(TAG, "cannot start TCP supervisor", error);
|
||||
stopSelf(startId);
|
||||
}
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override public void onDestroy() {
|
||||
Process child = supervisor;
|
||||
if (child != null) child.destroy();
|
||||
if (wakeLock != null && wakeLock.isHeld()) wakeLock.release();
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,11 @@ skipped are three different answers and the caller needs all three.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
@@ -25,8 +30,33 @@ import xml.etree.ElementTree as ET
|
||||
ARM_PREFIXES = {
|
||||
'--require-split-ran': 'DirectGLES.Split.',
|
||||
'--require-spawn-ran': 'DirectGLES.Spawn.',
|
||||
'--require-tcp-ran': 'DirectGLES.Tcp.',
|
||||
'--require-tcp-device-ran': 'DirectGLES.TcpDevice.',
|
||||
}
|
||||
|
||||
TCP_ARM = re.compile(r'control=tcp data=stream server=\S+ pid=[1-9][0-9]*\b')
|
||||
|
||||
|
||||
def require_tcp_proof(path, prefix, discovery):
|
||||
helper_path = Path(__file__).resolve().parents[2] / 'MobileGL/MG_IntegrationTest/Harness/split_log_paths.py'
|
||||
spec = importlib.util.spec_from_file_location('split_log_paths', helper_path)
|
||||
helper = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(helper)
|
||||
logs = helper.marker_log_paths(json.loads(Path(discovery).read_text()))
|
||||
for case in ET.parse(path).getroot().iter('testcase'):
|
||||
if not case.get('name', '').startswith(prefix):
|
||||
continue
|
||||
if case.find('skipped') is not None or case.get('status') in ('notrun', 'disabled'):
|
||||
continue
|
||||
# The console sink is compiled out of CI builds, so system-out cannot
|
||||
# establish the transport. Read this entry's private role logs instead.
|
||||
name = case.get('name')
|
||||
if name not in logs:
|
||||
raise ValueError(f'{name}: discovery has no private log path')
|
||||
output = helper.read_role_logs(logs[name])
|
||||
if not TCP_ARM.search(output):
|
||||
raise ValueError(f"{case.get('name')}: missing TCP/stream endpoint and server pid arm proof")
|
||||
|
||||
|
||||
def tally(path, prefix=None):
|
||||
passed = failed = skipped = 0
|
||||
@@ -43,18 +73,25 @@ def tally(path, prefix=None):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) not in (2, 3) or (len(sys.argv) == 3 and sys.argv[2] not in ARM_PREFIXES):
|
||||
print("usage: junit_tally.py <junit.xml> [" + " | ".join(sorted(ARM_PREFIXES)) + "]",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('junit')
|
||||
arms = parser.add_mutually_exclusive_group()
|
||||
for flag, prefix in ARM_PREFIXES.items():
|
||||
arms.add_argument(flag, dest='prefix', action='store_const', const=prefix)
|
||||
parser.add_argument('--discovery', help='CTest --show-only=json-v1 output (required for TCP proof)')
|
||||
args = parser.parse_args()
|
||||
if args.prefix in ('DirectGLES.Tcp.', 'DirectGLES.TcpDevice.') and not args.discovery:
|
||||
parser.error('TCP arm proof requires --discovery to locate each private role log')
|
||||
try:
|
||||
prefix = ARM_PREFIXES[sys.argv[2]] if len(sys.argv) == 3 else None
|
||||
passed, failed, skipped = tally(sys.argv[1], prefix=prefix)
|
||||
prefix = args.prefix
|
||||
passed, failed, skipped = tally(args.junit, prefix=prefix)
|
||||
if prefix in ('DirectGLES.Tcp.', 'DirectGLES.TcpDevice.'):
|
||||
require_tcp_proof(args.junit, prefix, args.discovery)
|
||||
except Exception as exc: # a malformed file is not "zero of everything"
|
||||
print(f"junit_tally: cannot parse {sys.argv[1]}: {exc}", file=sys.stderr)
|
||||
print(f"junit_tally: cannot prove {args.junit}: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"{passed} {failed} {skipped}")
|
||||
if len(sys.argv) == 3 and (passed == 0 or failed):
|
||||
if prefix is not None and (passed == 0 or failed):
|
||||
print(f'{prefix} baseline FAILED: no successful entries with that arm prefix, or an '
|
||||
f'already-red selection', file=sys.stderr)
|
||||
return 1
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Summarize P65LinkMetrics frame logs without confusing traffic with link throughput.
|
||||
|
||||
Example: p65_link_metrics.py --arm spawn:/tmp/spawn.client.log \
|
||||
--arm tcp-credit1:/tmp/tcp.client.log --warmup-frames 5 --output /tmp/p65-metrics.json
|
||||
|
||||
Enable MOBILEGL_PIPE_STATS=1 in each measured arm. RTT percentiles are upper
|
||||
bounds of fixed power-of-two microsecond buckets, not exact sample quantiles.
|
||||
The separately measured 64 MiB Stage burst throughput can be supplied with
|
||||
--link-mib-per-second; ordinary frame traffic is never called link throughput.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
FIELDS = re.compile(r"(\w+)=([^\s]+)")
|
||||
|
||||
|
||||
def summarize(path: Path, warmup: int, throughput: float | None = None) -> dict:
|
||||
frames = []
|
||||
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
if "P65LinkMetrics " not in line:
|
||||
continue
|
||||
record = dict(FIELDS.findall(line.split("P65LinkMetrics ", 1)[1]))
|
||||
if record.get("kind") != "frame" or int(record["frame"]) <= warmup:
|
||||
continue
|
||||
frames.append(record)
|
||||
if not frames:
|
||||
raise ValueError(f"{path}: no measured frames after warmup={warmup}")
|
||||
histogram = [0] * 32
|
||||
count = samples = stage = wall = cpu = 0
|
||||
weighted_rtt_us = 0.0
|
||||
for frame in frames:
|
||||
buckets = [int(n) for n in frame["rtt_hist_us_pow2"].split(",")]
|
||||
if len(buckets) != 32 or sum(buckets) != int(frame["rtt_samples"]):
|
||||
raise ValueError(f"{path}: malformed RTT histogram in frame {frame['frame']}")
|
||||
histogram = [a + b for a, b in zip(histogram, buckets)]
|
||||
count += int(frame["wait_replies"])
|
||||
n = int(frame["rtt_samples"])
|
||||
samples += n
|
||||
weighted_rtt_us += float(frame["rtt_mean_us"]) * n
|
||||
stage += int(frame["stage_bytes"])
|
||||
wall += int(frame["wall_ns"])
|
||||
cpu += int(frame["client_thread_cpu_ns"])
|
||||
def percentile(q: float) -> int:
|
||||
target = math.ceil(samples * q)
|
||||
if target == 0:
|
||||
return 0
|
||||
seen = 0
|
||||
for i, bucket in enumerate(histogram):
|
||||
seen += bucket
|
||||
if seen >= target:
|
||||
return 1 << i
|
||||
raise AssertionError("histogram count is inconsistent")
|
||||
result = {
|
||||
"log": str(path), "frames": len(frames), "warmup_frames": warmup,
|
||||
"wait_replies": count, "wait_replies_per_frame": count / len(frames),
|
||||
"rtt_samples": samples,
|
||||
"rtt_mean_us": weighted_rtt_us / samples if samples else None,
|
||||
"rtt_p50_upper_us": percentile(0.5), "rtt_p99_upper_us": percentile(0.99),
|
||||
"rtt_histogram_upper_us": [1 << i for i in range(32)],
|
||||
"rtt_histogram_counts": histogram,
|
||||
"stage_bytes": stage, "stage_bytes_per_frame": stage / len(frames),
|
||||
"frame_traffic_mib_per_second": stage * 1e9 / wall / (1024 ** 2) if wall else None,
|
||||
"fps": len(frames) * 1e9 / wall if wall else None,
|
||||
"client_thread_cpu_ms_per_frame": cpu / len(frames) / 1e6,
|
||||
}
|
||||
if throughput is not None:
|
||||
traffic = result["frame_traffic_mib_per_second"]
|
||||
result["measured_link_mib_per_second"] = throughput
|
||||
result["link_bandwidth_fraction"] = traffic / throughput if traffic is not None else None
|
||||
return result
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--arm", action="append", required=True, metavar="LABEL:LOG")
|
||||
parser.add_argument("--warmup-frames", type=int, default=0)
|
||||
parser.add_argument("--link-mib-per-second", type=float)
|
||||
parser.add_argument("--output", type=Path)
|
||||
args = parser.parse_args()
|
||||
if args.warmup_frames < 0 or (args.link_mib_per_second is not None and args.link_mib_per_second <= 0):
|
||||
parser.error("warmup must be nonnegative and measured throughput must be positive")
|
||||
report = {}
|
||||
for arm in args.arm:
|
||||
label, sep, filename = arm.partition(":")
|
||||
if not sep or not label or label in report:
|
||||
parser.error("each --arm must have a unique LABEL:LOG")
|
||||
try:
|
||||
report[label] = summarize(Path(filename), args.warmup_frames, args.link_mib_per_second)
|
||||
except (OSError, ValueError, KeyError) as error:
|
||||
parser.error(str(error))
|
||||
if "spawn" in report:
|
||||
baseline = report["spawn"]["client_thread_cpu_ms_per_frame"]
|
||||
for label, arm in report.items():
|
||||
if label != "spawn":
|
||||
arm["client_thread_cpu_delta_vs_spawn_ms"] = arm["client_thread_cpu_ms_per_frame"] - baseline
|
||||
text = json.dumps(report, ensure_ascii=False, indent=2) + "\n"
|
||||
if args.output:
|
||||
args.output.write_text(text, encoding="utf-8")
|
||||
else:
|
||||
print(text, end="")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -29,14 +29,16 @@ CASE = re.compile(r"DirectGLES\.[A-Za-z0-9]+\.(?:F1\.|Ct\.)?([A-Za-z0-9_]+Scenar
|
||||
|
||||
|
||||
def lane_cases(build_dir, label):
|
||||
out = subprocess.run(["ctest", "-N", "-L", label], cwd=build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
out = subprocess.run(["ctest", "-N", "-L", "^" + label + "$"], cwd=build_dir,
|
||||
capture_output=True, text=True, check=True).stdout
|
||||
return {m.group(1) for m in CASE.finditer(out)}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("build_dir")
|
||||
parser.add_argument("--require-device", action="store_true",
|
||||
help="Also require the configured integration-tcp-device lane")
|
||||
args = parser.parse_args()
|
||||
|
||||
split = lane_cases(args.build_dir, "integration-split")
|
||||
@@ -63,7 +65,18 @@ def main():
|
||||
print("::error::cases in integration-spawn that integration-split does not run: " +
|
||||
", ".join(extra) + ". The split arm is the control; a case only the spawn arm runs "
|
||||
"has no baseline to be compared against.", file=sys.stderr)
|
||||
return 1 if (missing or extra) else 0
|
||||
failed = bool(missing or extra)
|
||||
labels = ["integration-tcp"]
|
||||
if args.require_device:
|
||||
labels.append("integration-tcp-device")
|
||||
for label in labels:
|
||||
cases = lane_cases(args.build_dir, label)
|
||||
missing, extra = sorted(spawn - cases), sorted(cases - spawn)
|
||||
print(f"{label} parity: spawn {len(spawn)}, tcp {len(cases)}")
|
||||
if not cases or missing or extra:
|
||||
print(f"::error::{label}: missing={missing}, extra={extra}", file=sys.stderr)
|
||||
failed = True
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Executable negative controls for TCP lane accounting and supervisor lifecycle."""
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def module(name):
|
||||
spec = importlib.util.spec_from_file_location(name, Path(__file__).with_name(name + '.py'))
|
||||
loaded = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(loaded)
|
||||
return loaded
|
||||
|
||||
|
||||
class Accounting(unittest.TestCase):
|
||||
def test_each_tcp_case_needs_a_real_endpoint_and_pid(self):
|
||||
tally = module('junit_tally')
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
name = 'DirectGLES.Tcp.TriangleScenario.Draw'
|
||||
junit, discovery, base = root / 'junit.xml', root / 'lane.json', root / 'case.log'
|
||||
junit.write_text(f'<testsuite><testcase name="{name}" /></testsuite>')
|
||||
discovery.write_text(json.dumps({'tests': [{'name': name, 'properties': [
|
||||
{'name': 'ENVIRONMENT', 'value': ['MOBILEGL_LOG_FILE_PATH=' + str(base)]}]}]}))
|
||||
role = root / 'case.client.log'
|
||||
role.write_text('control=tcp data=stream server=127.0.0.1:40613 pid=42\n')
|
||||
tally.require_tcp_proof(junit, 'DirectGLES.Tcp.', discovery)
|
||||
for bad in ('', 'control=tcp data=shm server=127.0.0.1:40613 pid=42',
|
||||
'control=tcp data=stream server=127.0.0.1:40613 pid=0',
|
||||
'spawn ARMED - the server role runs in pid 42'):
|
||||
role.write_text(bad)
|
||||
with self.assertRaises(ValueError):
|
||||
tally.require_tcp_proof(junit, 'DirectGLES.Tcp.', discovery)
|
||||
|
||||
def test_equal_counts_do_not_hide_a_missing_case(self):
|
||||
parity = module('spawn_lane_parity')
|
||||
a, b = 'TriangleScenario.Draw', 'TriangleScenario.Read'
|
||||
with patch.object(parity, 'lane_cases', side_effect=[{a}, {a}, {b}]), \
|
||||
patch.object(sys, 'argv', ['spawn_lane_parity.py', 'unused']):
|
||||
self.assertEqual(parity.main(), 1)
|
||||
with patch.object(parity, 'lane_cases', return_value={a}), \
|
||||
patch.object(sys, 'argv', ['spawn_lane_parity.py', 'unused']):
|
||||
self.assertEqual(parity.main(), 0)
|
||||
|
||||
|
||||
@unittest.skipUnless(sys.platform.startswith('linux'), 'supervisor fixture uses Linux /proc')
|
||||
class Supervisor(unittest.TestCase):
|
||||
def test_readiness_does_not_consume_a_connection_and_stop_owns_only_its_pid(self):
|
||||
fixture = module('tcp_server_fixture')
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
server = root / 'server'
|
||||
server.write_text('#!' + sys.executable + '\n'
|
||||
'import socket,sys\n'
|
||||
'port=int(sys.argv[1].rsplit(":",1)[1])\n'
|
||||
's=socket.socket();s.bind(("127.0.0.1",port));s.listen()\n'
|
||||
'i=0\n'
|
||||
'while True:\n'
|
||||
' c,_=s.accept();i+=1;c.sendall(str(i).encode());c.close()\n')
|
||||
server.chmod(0o755)
|
||||
with socket.socket() as reservation:
|
||||
reservation.bind(('127.0.0.1', 0))
|
||||
port = reservation.getsockname()[1]
|
||||
state = root / 'state.json'
|
||||
subprocess.run([sys.executable, str(Path(fixture.__file__)), 'start',
|
||||
'--server', str(server), '--endpoint', f'tcp://127.0.0.1:{port}',
|
||||
'--state', str(state)], check=True)
|
||||
try:
|
||||
with socket.create_connection(('127.0.0.1', port)) as peer:
|
||||
self.assertEqual(peer.recv(10), b'1')
|
||||
# An unrelated live PID with a different start token is not ours.
|
||||
unrelated = root / 'unrelated.json'
|
||||
unrelated.write_text(json.dumps({'pid': os.getpid(), 'start': 'not-this-process'}))
|
||||
fixture.stop(unrelated)
|
||||
finally:
|
||||
fixture.stop(state)
|
||||
with self.assertRaises(OSError):
|
||||
socket.create_connection(('127.0.0.1', port), timeout=.2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the four real arming scenarios with server log forwarding enabled and disabled."""
|
||||
import argparse
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import signal
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
CASES = {
|
||||
'unlocated': ('DirectGLES', 'UnlocatedIoBlockScenario.TheEmulationIsActuallyArmedWhenTheEnvironmentPinsItOn',
|
||||
{'MOBILEGL_ESPRYT_UNLOCATED_IO_BLOCKS': '1'}, 'WITHOUT their layout(location) qualifier'),
|
||||
'primitives': ('DirectVulkan', 'PrimitivesGeneratedNoXfbScenario.TheRerouteIsActuallyArmedWhenTheEnvironmentPinsItOn',
|
||||
{'MOBILEGL_MAGMA_PRIMGEN_QUERY_REROUTE': '1'}, 'PRIMITIVES_GENERATED reroute engaged'),
|
||||
'point-size': ('DirectGLES', 'PointSizeDemotionScenario.TheDemotionIsActuallyArmedWhenTheEnvironmentPinsItOn',
|
||||
{'MOBILEGL_POINT_SIZE_DEMOTION': '1'}, 'demoted to an ordinary varying'),
|
||||
'verify': ('DirectGLES', 'PipeVerifyArmingScenario.Armed',
|
||||
{'MOBILEGL_PIPE_VERIFY': '1', 'MGITEST_PIPE_ARMING_LANE': '1'}, 'MGPipe: verify armed'),
|
||||
}
|
||||
|
||||
|
||||
def read(path):
|
||||
return path.read_text(errors='replace') if path.exists() else ''
|
||||
|
||||
|
||||
def run_arm(build, out, case, forward):
|
||||
backend, test, knobs, marker = CASES[case]
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
with socket.socket() as reserve:
|
||||
reserve.bind(('127.0.0.1', 0))
|
||||
port = reserve.getsockname()[1]
|
||||
endpoint = f'tcp://127.0.0.1:{port}'
|
||||
env = dict(os.environ, MOBILEGL_TRANSPORT='spawn', MOBILEGL_BACKEND_TYPE=backend,
|
||||
MOBILEGL_IPC_REQUIRE_SAME_BUILD='1', MOBILEGL_IPC_TOKEN='', EGL_PLATFORM='surfaceless',
|
||||
LIBGL_ALWAYS_SOFTWARE='1', MOBILEGL_ITEST_REQUIRE_GPU='1', **knobs)
|
||||
for name in ('MOBILEGL_PIPE_VERIFY_CORRUPT', 'MOBILEGL_IPC_CONTROL', 'MOBILEGL_IPC_DIAL'):
|
||||
env.pop(name, None)
|
||||
if Path('/usr/share/glvnd/egl_vendor.d/50_mesa.json').exists():
|
||||
env['__EGL_VENDOR_LIBRARY_FILENAMES'] = '/usr/share/glvnd/egl_vendor.d/50_mesa.json'
|
||||
if Path('/usr/share/vulkan/icd.d/lvp_icd.json').exists():
|
||||
env['VK_ICD_FILENAMES'] = '/usr/share/vulkan/icd.d/lvp_icd.json'
|
||||
peer_env = dict(env, MOBILEGL_IPC_ROLE='server', MOBILEGL_IPC_DIAL='no',
|
||||
MOBILEGL_IPC_LOG_FORWARD=str(int(forward)), MOBILEGL_LOG_FILE_PATH=str(out / 'peer.log'))
|
||||
with (out / 'supervisor.log').open('wb') as output:
|
||||
server = subprocess.Popen([str(build / 'libMobileGLServer.so'), endpoint, '--serve'],
|
||||
env=peer_env, stdout=output, stderr=output, start_new_session=True)
|
||||
try:
|
||||
for _ in range(100):
|
||||
if server.poll() is not None:
|
||||
raise RuntimeError('supervisor exited before listening: ' + read(out / 'supervisor.log'))
|
||||
with socket.socket() as probe:
|
||||
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
probe.bind(('127.0.0.1', port))
|
||||
except OSError as error:
|
||||
if error.errno == errno.EADDRINUSE:
|
||||
break
|
||||
raise
|
||||
time.sleep(.05)
|
||||
else:
|
||||
raise RuntimeError('supervisor did not listen')
|
||||
client_env = dict(env, MOBILEGL_IPC_CONTROL=endpoint, MOBILEGL_IPC_DATA='stream',
|
||||
MOBILEGL_IPC_LOG_FORWARD='1', MOBILEGL_LOG_FILE_PATH=str(out / 'client.log'),
|
||||
MGITEST_SPLIT_LANE='1', MGITEST_TCP_LANE='1')
|
||||
result = subprocess.run([str(build / 'MobileGL/MG_IntegrationTest/MobileGLIntegrationTest'),
|
||||
'--gtest_filter=' + test, '--gtest_output=xml:' + str(out / 'result.xml')],
|
||||
env=client_env, capture_output=True, text=True, timeout=120)
|
||||
(out / 'stdout.log').write_text(result.stdout + result.stderr)
|
||||
finally:
|
||||
try:
|
||||
os.killpg(server.pid, signal.SIGTERM)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
server.wait(timeout=10)
|
||||
passed = failed = skipped = 0
|
||||
if (out / 'result.xml').exists():
|
||||
for entry in ET.parse(out / 'result.xml').getroot().iter('testcase'):
|
||||
if entry.find('skipped') is not None or entry.get('status') == 'notrun':
|
||||
skipped += 1
|
||||
elif entry.find('failure') is not None:
|
||||
failed += 1
|
||||
else:
|
||||
passed += 1
|
||||
client_log = read(out / 'client.client.log')
|
||||
return {'case': test, 'returncode': result.returncode, 'passed': passed, 'failed': failed, 'skipped': skipped,
|
||||
'tcp_armed': bool(re.search(r'control=tcp data=stream server=\S+ pid=[1-9][0-9]*', client_log)),
|
||||
'marker_in_client': marker in client_log,
|
||||
'marker_in_server': marker in read(out / 'peer.server.log'),
|
||||
'marker_forwarded': marker in read(out / 'client.server.log'),
|
||||
'output': str(out)}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--build-dir', type=Path, required=True)
|
||||
parser.add_argument('--verify-build-dir', type=Path)
|
||||
parser.add_argument('--out', type=Path, required=True)
|
||||
parser.add_argument('--case', action='append', choices=CASES,
|
||||
help='Explicit subset; the default runs all four and requires a verify build')
|
||||
args = parser.parse_args()
|
||||
selected = args.case or list(CASES)
|
||||
if 'verify' in selected and not args.verify_build_dir:
|
||||
parser.error('the verify scenario requires --verify-build-dir configured with MOBILEGL_PIPE_VERIFY=ON')
|
||||
args.out.mkdir(parents=True, exist_ok=True)
|
||||
results = {}
|
||||
for case in selected:
|
||||
build = args.verify_build_dir if case == 'verify' else args.build_dir
|
||||
arms = {}
|
||||
for forward in (True, False):
|
||||
name = 'forward-on' if forward else 'forward-off'
|
||||
try:
|
||||
arms[name] = run_arm(build, args.out / case / name, case, forward)
|
||||
except Exception as error:
|
||||
arms[name] = {'error': str(error)}
|
||||
on, off = arms['forward-on'], arms['forward-off']
|
||||
if case == 'verify':
|
||||
# PipeFill owns this marker in the client. Disabling peer forwarding
|
||||
# must leave it visible; requiring red here would test a false design.
|
||||
arms['expectation'] = 'client-owned marker remains green with peer forwarding disabled'
|
||||
arms['proved'] = all(arm.get('passed') == 1 and arm.get('returncode') == 0
|
||||
and arm.get('tcp_armed') and arm.get('marker_in_client')
|
||||
for arm in (on, off))
|
||||
else:
|
||||
arms['expectation'] = 'server-owned marker passes with forwarding and fails without it'
|
||||
arms['proved'] = bool(on.get('passed') == 1 and on.get('returncode') == 0
|
||||
and on.get('tcp_armed') and on.get('marker_in_server') and on.get('marker_forwarded')
|
||||
and off.get('failed') == 1 and not off.get('skipped') and off.get('tcp_armed')
|
||||
and off.get('marker_in_server') and not off.get('marker_forwarded')
|
||||
and not off.get('marker_in_client'))
|
||||
results[case] = arms
|
||||
print(case + ': ' + ('PASS' if arms['proved'] else 'NOT PROVEN'), flush=True)
|
||||
(args.out / 'summary.json').write_text(json.dumps(results, indent=2))
|
||||
return 0 if all(result['proved'] for result in results.values()) else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Start/stop the loopback supervisor used by the integration-tcp CTest fixture."""
|
||||
import argparse
|
||||
import errno
|
||||
import ipaddress
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import signal
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
|
||||
def process_start(pid):
|
||||
try:
|
||||
return Path(f'/proc/{pid}/stat').read_text().rsplit(')', 1)[1].split()[19]
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
|
||||
def stop(state):
|
||||
if not state.exists():
|
||||
return
|
||||
saved = json.loads(state.read_text())
|
||||
pid = saved['pid']
|
||||
# PID reuse must never let a stale fixture file kill an unrelated process.
|
||||
if process_start(pid) == saved['start']:
|
||||
os.killpg(pid, signal.SIGTERM)
|
||||
for _ in range(50):
|
||||
if process_start(pid) != saved['start']:
|
||||
break
|
||||
time.sleep(.05)
|
||||
else:
|
||||
try:
|
||||
os.killpg(pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
state.unlink()
|
||||
|
||||
|
||||
def start(args):
|
||||
endpoint = urlsplit(args.endpoint)
|
||||
if endpoint.scheme != 'tcp' or not ipaddress.ip_address(endpoint.hostname).is_loopback:
|
||||
raise ValueError('the local fixture requires a numeric loopback TCP endpoint')
|
||||
family = socket.AF_INET6 if ':' in endpoint.hostname else socket.AF_INET
|
||||
address = (endpoint.hostname, endpoint.port)
|
||||
# Do not connect as a readiness probe: it would consume the first half of
|
||||
# AcceptPair, so the next real client would be paired with the probe.
|
||||
with socket.socket(family) as probe:
|
||||
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
probe.bind(address)
|
||||
if args.state.exists():
|
||||
raise RuntimeError(f'stale fixture state; run stop first: {args.state}')
|
||||
env = dict(os.environ, MOBILEGL_IPC_ROLE='server', MOBILEGL_IPC_DIAL='no',
|
||||
MOBILEGL_IPC_TOKEN='', MOBILEGL_IPC_LOG_FORWARD='1')
|
||||
for key in ('MOBILEGL_IPC_CONTROL', 'MOBILEGL_IPC_ENDPOINT', 'MOBILEGL_BACKEND_TYPE'):
|
||||
env.pop(key, None)
|
||||
log_path = args.state.with_suffix('.log')
|
||||
with log_path.open('wb') as log:
|
||||
child = subprocess.Popen([args.server, args.endpoint, '--serve'], env=env,
|
||||
stdin=subprocess.DEVNULL, stdout=log, stderr=log,
|
||||
start_new_session=True)
|
||||
args.state.write_text(json.dumps({'pid': child.pid, 'start': process_start(child.pid)}))
|
||||
try:
|
||||
for _ in range(100):
|
||||
if child.poll() is not None:
|
||||
raise RuntimeError(f'supervisor exited {child.returncode}: {log_path.read_text(errors="replace")}')
|
||||
with socket.socket(family) as probe:
|
||||
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
probe.bind(address)
|
||||
except OSError as error:
|
||||
if error.errno == errno.EADDRINUSE:
|
||||
print(f'TCP supervisor pid={child.pid} endpoint={args.endpoint}')
|
||||
return
|
||||
raise
|
||||
time.sleep(.05)
|
||||
raise RuntimeError(f'supervisor did not listen: {log_path}')
|
||||
except BaseException:
|
||||
stop(args.state)
|
||||
raise
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('command', choices=('start', 'stop'))
|
||||
parser.add_argument('--state', type=Path, required=True)
|
||||
parser.add_argument('--server')
|
||||
parser.add_argument('--endpoint')
|
||||
args = parser.parse_args()
|
||||
if args.command == 'start':
|
||||
if not args.server or not args.endpoint:
|
||||
parser.error('start requires --server and --endpoint')
|
||||
start(args)
|
||||
else:
|
||||
stop(args.state)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exercise a real TCP supervisor's Busy, token, version, and build-policy controls."""
|
||||
import argparse
|
||||
from contextlib import contextmanager
|
||||
import importlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import signal
|
||||
import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def read_exact(peer, size):
|
||||
result = bytearray()
|
||||
while len(result) < size:
|
||||
chunk = peer.recv(size - len(result))
|
||||
if not chunk:
|
||||
raise RuntimeError('control channel closed before a complete frame')
|
||||
result.extend(chunk)
|
||||
return result
|
||||
|
||||
|
||||
def receive(peer, schema):
|
||||
magic, size = struct.unpack('<4sI', read_exact(peer, 8))
|
||||
if magic != b'MGLF' or size > 64 * 1024 * 1024:
|
||||
raise RuntimeError('invalid control frame')
|
||||
data = read_exact(peer, size)
|
||||
envelope = schema['CtrlEnvelope'].CtrlEnvelope.GetRootAs(data, 0)
|
||||
table = envelope.Msg()
|
||||
kind = envelope.MsgType()
|
||||
if kind == schema['CtrlMsg'].CtrlMsg.Refuse:
|
||||
refusal = schema['Refuse'].Refuse()
|
||||
refusal.Init(table.Bytes, table.Pos)
|
||||
return {'code': refusal.Code(), 'expected': refusal.Expected(),
|
||||
'detail': (refusal.Detail() or b'').decode()}
|
||||
if kind == schema['CtrlMsg'].CtrlMsg.Welcome:
|
||||
welcome = schema['Welcome'].Welcome()
|
||||
welcome.Init(table.Bytes, table.Pos)
|
||||
return {'welcome': welcome.ServerPid(), 'fingerprint': welcome.WireFingerprint()}
|
||||
raise RuntimeError(f'unexpected first control reply {kind}')
|
||||
|
||||
|
||||
def hello(schema, flatbuffers, fingerprint=0, major=1, token='p65-smoke-token'):
|
||||
builder = flatbuffers.Builder(512)
|
||||
build = builder.CreateString('intentionally-different-build-for-p65-control')
|
||||
auth = builder.CreateString(token)
|
||||
terms = schema['LinkTerms']
|
||||
terms.Start(builder)
|
||||
terms.AddDataPlane(builder, 1)
|
||||
link = terms.End(builder)
|
||||
message = schema['Hello']
|
||||
message.Start(builder)
|
||||
message.AddAbiMajor(builder, major)
|
||||
message.AddBuildFingerprint(builder, build)
|
||||
message.AddBackendType(builder, 0) # DirectGLES
|
||||
message.AddPid(builder, os.getpid())
|
||||
message.AddWireFingerprint(builder, fingerprint)
|
||||
message.AddLinkTerms(builder, link)
|
||||
message.AddToken(builder, auth)
|
||||
message.AddDialMode(builder, 2) # Connect
|
||||
body = message.End(builder)
|
||||
envelope = schema['CtrlEnvelope']
|
||||
envelope.Start(builder)
|
||||
envelope.AddMsgType(builder, schema['CtrlMsg'].CtrlMsg.Hello)
|
||||
envelope.AddMsg(builder, body)
|
||||
builder.Finish(envelope.End(builder), b'MGLC')
|
||||
payload = bytes(builder.Output())
|
||||
return struct.pack('<4sI', b'MGLF', len(payload)) + payload
|
||||
|
||||
|
||||
@contextmanager
|
||||
def supervisor(server, log, same_build=False):
|
||||
with socket.socket() as reserve:
|
||||
reserve.bind(('127.0.0.1', 0))
|
||||
port = reserve.getsockname()[1]
|
||||
env = dict(os.environ, MOBILEGL_IPC_ROLE='server', MOBILEGL_IPC_DIAL='no',
|
||||
MOBILEGL_IPC_TOKEN='p65-smoke-token', MOBILEGL_IPC_REQUIRE_SAME_BUILD=str(int(same_build)),
|
||||
MOBILEGL_IPC_LOG_FORWARD='0', MOBILEGL_LOG_FILE_PATH=str(log.with_suffix('.library.log')))
|
||||
for name in ('MOBILEGL_IPC_CONTROL', 'MOBILEGL_IPC_ENDPOINT', 'MOBILEGL_BACKEND_TYPE'):
|
||||
env.pop(name, None)
|
||||
with log.open('wb') as output:
|
||||
child = subprocess.Popen([server, f'tcp://127.0.0.1:{port}', '--serve'], env=env,
|
||||
stdout=output, stderr=output, start_new_session=True)
|
||||
try:
|
||||
yield port, child
|
||||
finally:
|
||||
try:
|
||||
os.killpg(child.pid, signal.SIGTERM)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
child.wait(timeout=10)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def pair(port):
|
||||
deadline = time.monotonic() + 5
|
||||
while True:
|
||||
try:
|
||||
control = socket.create_connection(('127.0.0.1', port), timeout=3)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
if time.monotonic() >= deadline:
|
||||
raise
|
||||
time.sleep(.02)
|
||||
try:
|
||||
data = socket.create_connection(('127.0.0.1', port), timeout=3)
|
||||
try:
|
||||
yield control
|
||||
finally:
|
||||
data.close()
|
||||
finally:
|
||||
control.close()
|
||||
|
||||
|
||||
def exchange(port, schema, message):
|
||||
# A prior rejected child may still be reaching _exit when the next pair
|
||||
# arrives. Only Busy may be retried here, and never in the Busy assertion.
|
||||
for _ in range(30):
|
||||
with pair(port) as control:
|
||||
control.sendall(message)
|
||||
reply = receive(control, schema)
|
||||
if reply.get('code') != 7:
|
||||
return reply
|
||||
time.sleep(.05)
|
||||
raise RuntimeError('supervisor remained Busy after the previous peer closed')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--server', required=True)
|
||||
parser.add_argument('--flatc', default='flatc')
|
||||
parser.add_argument('--out', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
args.out.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix='mgl-tcp-schema-') as directory:
|
||||
subprocess.run([args.flatc, '--python', '-o', directory,
|
||||
str(ROOT / 'MobileGL/MG_Remote/Protocol/protocol.fbs')], check=True)
|
||||
sys.path[:0] = [directory, str(ROOT / '3rdparty/flatbuffers/python')]
|
||||
import flatbuffers
|
||||
schema = {name: importlib.import_module('MobileGL.Wire.' + name)
|
||||
for name in ('CtrlEnvelope', 'CtrlMsg', 'Hello', 'LinkTerms', 'Refuse', 'Welcome')}
|
||||
evidence = {}
|
||||
with supervisor(args.server, args.out / 'supervisor.log') as (port, process):
|
||||
with pair(port) as held:
|
||||
with pair(port) as second:
|
||||
busy = receive(second, schema)
|
||||
assert busy.get('code') == 7, busy
|
||||
evidence['busy'] = busy
|
||||
token = exchange(port, schema, hello(schema, flatbuffers, token='wrong-token'))
|
||||
assert token.get('code') == 4, token
|
||||
evidence['wrong_token'] = token
|
||||
version = exchange(port, schema, hello(schema, flatbuffers, major=99))
|
||||
assert version.get('code') == 1, version
|
||||
evidence['wire_major_99'] = version
|
||||
layout = exchange(port, schema, hello(schema, flatbuffers))
|
||||
assert layout.get('code') == 2 and layout.get('expected', 0), layout
|
||||
fingerprint = layout['expected']
|
||||
evidence['wrong_layout'] = layout
|
||||
accepted = exchange(port, schema, hello(schema, flatbuffers, fingerprint=fingerprint))
|
||||
assert accepted.get('welcome', 0) and accepted['welcome'] != os.getpid(), accepted
|
||||
assert accepted.get('fingerprint') == fingerprint, accepted
|
||||
evidence['different_build_connect'] = accepted
|
||||
assert process.poll() is None, 'a rejected session killed the supervisor'
|
||||
with supervisor(args.server, args.out / 'same-build-supervisor.log', same_build=True) as (port, _):
|
||||
strict = exchange(port, schema, hello(schema, flatbuffers, fingerprint=fingerprint))
|
||||
assert strict.get('code') == 3, strict
|
||||
evidence['same_build_required'] = strict
|
||||
(args.out / 'protocol-controls.json').write_text(json.dumps(evidence, indent=2))
|
||||
print(json.dumps(evidence, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -692,6 +692,33 @@ def banner(name, title, sources):
|
||||
return GENERATED_BANNER.format(name=name, title=title, sources=sources)
|
||||
|
||||
|
||||
def gen_wire_layout(calls, payloads):
|
||||
"""Compiler-evaluated wire layout, including same-width member reorders."""
|
||||
out = [banner("PipeWireLayout.inc", "P6.5: wire layout and ordered catalogue.",
|
||||
"PipeFields.def and PipeCalls.def")]
|
||||
out += ["struct WireLayoutMember { const char* Name; Uint64 Offset; Uint64 Size; };",
|
||||
"inline constexpr WireLayoutMember kMGPipeWireLayoutMembers[] = {"]
|
||||
fields = parse_field_lists()
|
||||
for payload in payloads:
|
||||
out.append(' {"%s", 0, sizeof(%s)},' % (payload, payload))
|
||||
for field in fields[payload]:
|
||||
out.append(' {"%s.%s", offsetof(%s, %s), sizeof(((%s*)nullptr)->%s)},' %
|
||||
(payload, field, payload, field, payload, field))
|
||||
for payload, names in (("MGPipeHandle", ("Slot", "Gen")),
|
||||
("MGPWireRecHeader", ("Op", "Flags", "Size"))):
|
||||
out.append(' {"%s", 0, sizeof(%s)},' % (payload, payload))
|
||||
for field in names:
|
||||
out.append(' {"%s.%s", offsetof(%s, %s), sizeof(((%s*)nullptr)->%s)},' %
|
||||
(payload, field, payload, field, payload, field))
|
||||
out += ["};", "struct WireCallLayout { const char* Name; const char* Payload; Uint64 Op; Uint64 Flags; Uint64 Wait; };",
|
||||
"inline constexpr WireCallLayout kMGPipeWireCallLayouts[] = {"]
|
||||
for call in calls:
|
||||
out.append(' {"%s", "%s", static_cast<Uint64>(MGPWireOp::%s), static_cast<Uint64>(%s), static_cast<Uint64>(%s)},' %
|
||||
(call.Name, call.Payload, call.Name, " | ".join(call.Flags), call.Wait))
|
||||
out += ["};", ""]
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def gen_tables(calls):
|
||||
screen = [c for c in calls if c.IsScreen]
|
||||
context = [c for c in calls if not c.IsScreen]
|
||||
@@ -1698,6 +1725,7 @@ def main():
|
||||
write(os.path.join(GENERATED_DIR, "PipeThunks.inc"), gen_thunks(calls), args.check, changed)
|
||||
write(os.path.join(GENERATED_DIR, "PipeWire.inc"),
|
||||
gen_wire(calls, parse_field_lists().get("ResidualValueBlock")), args.check, changed)
|
||||
write(os.path.join(GENERATED_DIR, "PipeWireLayout.inc"), gen_wire_layout(calls, payloads), args.check, changed)
|
||||
write(os.path.join(GENERATED_DIR, "PipeVerify.inc"), gen_verify(payloads), args.check, changed)
|
||||
write(os.path.join(GENERATED_DIR, "PipeFilled.inc"), gen_filled(accessors, calls, sticky, emitted),
|
||||
args.check, changed)
|
||||
|
||||
@@ -319,6 +319,8 @@ else()
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
set(MOBILEGL_TRACE_TCP_ENDPOINT "" CACHE STRING "Optional TCP supervisor endpoint for the CI split trace subset")
|
||||
set(MOBILEGL_TRACE_TCP_TOKEN "" CACHE STRING "Token for the TCP trace supervisor")
|
||||
function(add_trace_replay_test CASE_NAME BACKEND)
|
||||
set(oneValueArgs
|
||||
TRACE_ARCHIVE
|
||||
@@ -340,7 +342,9 @@ function(add_trace_replay_test CASE_NAME BACKEND)
|
||||
# entry is a second arm of.
|
||||
VARIANT
|
||||
TRANSPORT
|
||||
IPC_SERVER_PATH)
|
||||
IPC_SERVER_PATH
|
||||
IPC_CONTROL
|
||||
IPC_TOKEN)
|
||||
cmake_parse_arguments(TRACE_CASE "" "${oneValueArgs}" "" ${ARGN})
|
||||
foreach(required TRACE_ARCHIVE GOLDEN TARGET_CALL WIDTH HEIGHT)
|
||||
if(NOT TRACE_CASE_${required})
|
||||
@@ -417,6 +421,8 @@ function(add_trace_replay_test CASE_NAME BACKEND)
|
||||
-DTRACE_COHERENT_AS_FLUSH=${TRACE_CASE_COHERENT_AS_FLUSH}
|
||||
-DTRACE_TRANSPORT=${TRACE_CASE_TRANSPORT}
|
||||
-DTRACE_IPC_SERVER_PATH=${TRACE_CASE_IPC_SERVER_PATH}
|
||||
-DTRACE_IPC_CONTROL=${TRACE_CASE_IPC_CONTROL}
|
||||
-DTRACE_IPC_TOKEN=${TRACE_CASE_IPC_TOKEN}
|
||||
-DTRACE_OUTPUT_DIR=${CMAKE_CURRENT_BINARY_DIR}/${CASE_NAME}/${trace_case_output_tag}
|
||||
-DTRACE_ARTIFACT_DIR=${CMAKE_CURRENT_BINARY_DIR}/${CASE_NAME}/${trace_case_artifact_tag}
|
||||
-P ${MOBILEGL_TRACE_ROOT}/run_trace_case.cmake)
|
||||
@@ -440,6 +446,9 @@ function(add_trace_replay_test CASE_NAME BACKEND)
|
||||
ENVIRONMENT "LIBGL_ALWAYS_SOFTWARE=1;MESA_GL_VERSION_OVERRIDE=3.3;MESA_GLSL_VERSION_OVERRIDE=330"
|
||||
LABELS "${trace_case_labels}")
|
||||
endif()
|
||||
if(TRACE_CASE_IPC_CONTROL)
|
||||
set_tests_properties(${trace_case_name} PROPERTIES RESOURCE_LOCK mobilegl-tcp-trace)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(add_trace_replay_test_for_backends CASE_NAME)
|
||||
@@ -491,6 +500,10 @@ function(add_trace_replay_split_test CASE_NAME)
|
||||
IPC_SERVER_PATH "${CMAKE_BINARY_DIR}/libMobileGLServer.so" ${ARGN})
|
||||
add_trace_replay_test(${CASE_NAME} ${backend} VARIANT SPAWN TRANSPORT spawn
|
||||
IPC_SERVER_PATH "${CMAKE_BINARY_DIR}/libMobileGLServer.so" ${ARGN})
|
||||
if(MOBILEGL_TRACE_TCP_ENDPOINT)
|
||||
add_trace_replay_test(${CASE_NAME} ${backend} VARIANT TCP TRANSPORT spawn
|
||||
IPC_CONTROL "${MOBILEGL_TRACE_TCP_ENDPOINT}" IPC_TOKEN "${MOBILEGL_TRACE_TCP_TOKEN}" ${ARGN})
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
|
||||
@@ -51,6 +51,13 @@ if(DEFINED TRACE_IPC_SERVER_PATH AND NOT "${TRACE_IPC_SERVER_PATH}" STREQUAL "")
|
||||
# "parsed and ignored" stop being the same observation.
|
||||
set(ENV{MOBILEGL_IPC_SERVER_PATH} "${TRACE_IPC_SERVER_PATH}")
|
||||
endif()
|
||||
if(DEFINED TRACE_IPC_CONTROL AND NOT "${TRACE_IPC_CONTROL}" STREQUAL "")
|
||||
set(ENV{MOBILEGL_IPC_CONTROL} "${TRACE_IPC_CONTROL}")
|
||||
set(ENV{MOBILEGL_IPC_DATA} stream)
|
||||
set(ENV{MOBILEGL_IPC_TOKEN} "${TRACE_IPC_TOKEN}")
|
||||
set(ENV{MOBILEGL_IPC_REQUIRE_SAME_BUILD} 1)
|
||||
set(ENV{MOBILEGL_IPC_LOG_FORWARD} 1)
|
||||
endif()
|
||||
|
||||
if(EXISTS "${TRACE_OUTPUT_DIR}")
|
||||
file(REMOVE_RECURSE "${TRACE_OUTPUT_DIR}")
|
||||
@@ -294,7 +301,17 @@ if(DEFINED ENV{MOBILEGL_TRANSPORT} AND NOT "$ENV{MOBILEGL_TRANSPORT}" STREQUAL "
|
||||
set(split_expected_marker "MOBILEGL_TRANSPORT=inproc - the MGPipe record stream")
|
||||
elseif("$ENV{MOBILEGL_TRANSPORT}" STREQUAL "spawn")
|
||||
set(split_expected_marker "MOBILEGL_TRANSPORT=spawn - the MGPipe record stream")
|
||||
set(split_expected_second "spawn ARMED - the server role runs in pid ")
|
||||
if("$ENV{MOBILEGL_IPC_CONTROL}" MATCHES "^tcp://")
|
||||
set(split_expected_second "control=tcp data=stream server=")
|
||||
if(NOT split_log MATCHES "control=tcp data=stream server=[^ \r\n]+ pid=[1-9][0-9]*")
|
||||
message(FATAL_ERROR "${split_case}: TCP session has no endpoint and server pid arm proof")
|
||||
endif()
|
||||
if(NOT EXISTS "${mobilegl_server_log}")
|
||||
message(FATAL_ERROR "${split_case}: TCP session has no forwarded server log")
|
||||
endif()
|
||||
else()
|
||||
set(split_expected_second "spawn ARMED - the server role runs in pid ")
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR
|
||||
"MOBILEGL_TRANSPORT=$ENV{MOBILEGL_TRANSPORT} is set for ${split_case} and this "
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Start or stop the trace APK's foreground TCP supervisor through adb."""
|
||||
import argparse
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('action', choices=('start', 'stop'))
|
||||
parser.add_argument('--adb', default='adb')
|
||||
parser.add_argument('--serial', default='2f7cbe2e')
|
||||
parser.add_argument('--package', default='top.mobilegl.plugin.trace')
|
||||
parser.add_argument('--listen', default='tcp://127.0.0.1:40613')
|
||||
parser.add_argument('--token', default='')
|
||||
parser.add_argument('--env', action='append', default=[], help='KEY=VALUE;KEY overrides (repeatable)')
|
||||
parser.add_argument('--forward', action='store_true', help='Forward local adb port 40613 to the device')
|
||||
args = parser.parse_args()
|
||||
adb = [args.adb, '-s', args.serial]
|
||||
if args.action == 'stop':
|
||||
command = ['am', 'force-stop', args.package]
|
||||
else:
|
||||
command = ['am', 'start-foreground-service', '-n', args.package + '/top.mobilegl.plugin.MobileGLServerService',
|
||||
'--es', 'listen', args.listen, '--es', 'token', args.token,
|
||||
'--es', 'env', ';'.join(args.env)]
|
||||
subprocess.run(adb + ['shell', shlex.join(command)], check=True)
|
||||
if args.action == 'start' and args.forward:
|
||||
subprocess.run(adb + ['forward', 'tcp:40613', 'tcp:40613'], check=True)
|
||||
if args.action == 'start':
|
||||
print('Read readiness: adb -s ' + args.serial + ' logcat -s MobileGLServer')
|
||||
print('Client: MOBILEGL_TRANSPORT=spawn MOBILEGL_IPC_DATA=stream '
|
||||
'MOBILEGL_IPC_REQUIRE_SAME_BUILD=1 MOBILEGL_IPC_CONTROL=tcp://<device-address>:40613')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user