mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Feat] (P13 W4a): Espryt monolith takes the record arm for buffers, indirect, client arrays, binding points and fp64 through an in-process verb port
This commit is contained in:
+2
-1
@@ -744,7 +744,8 @@ namespace MobileGL::MG_Config {
|
||||
// caps, whether a store copies).
|
||||
//
|
||||
// MOBILEGL_PIPE_DATA_ARM=record (default) | frontend picks monolith's arm for the families
|
||||
// already switched. It exists for W4-W6 only - bisecting a monolith red to the switch, and
|
||||
// already switched, on a backend whose monolith takes the record arm at all (Espryt from W4a,
|
||||
// Magma at its single flip). It exists for W4-W6 only - bisecting a monolith red to the switch, and
|
||||
// the per-thread CPU record (ID-P13-2) - and goes in W6 with the frontend arms. Latched once
|
||||
// by MG_ConfigLoader::Init(): the two arms keep their twins in different places.
|
||||
extern Bool MonolithTakesRecordArm;
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace MobileGL::MG_Config {
|
||||
String TransportEndpoint;
|
||||
Bool SplitTransportRequestedByConfig = false;
|
||||
IpcTable Ipc;
|
||||
Bool MonolithTakesRecordArm = true;
|
||||
Bool MonolithTakesRecordArm = false;
|
||||
#endif
|
||||
} // namespace MobileGL::MG_Config
|
||||
|
||||
@@ -465,7 +465,11 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
String lowered = value;
|
||||
std::transform(lowered.begin(), lowered.end(), lowered.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
MG_Config::MonolithTakesRecordArm = true;
|
||||
// P13 W4: the backends switch one at a time. Espryt's record arm runs in monolith from W4a;
|
||||
// Magma's verb entry points pick one arm for every family at once, so it joins at its
|
||||
// single flip, after Espryt's last family.
|
||||
const Bool backendReady = MG_Config::ActiveBackendType == BackendType::DirectGLES;
|
||||
MG_Config::MonolithTakesRecordArm = backendReady;
|
||||
if (lowered.empty() || lowered == "record") return;
|
||||
if (lowered == "frontend") {
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h> // MGPipeVerifyFlushSummary, before Debug::Close in DestroyImpl
|
||||
#endif
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <MG_Remote/Client/EmitTables.h>
|
||||
#include <MG_Remote/Client/ServerProbe.h>
|
||||
#endif
|
||||
|
||||
@@ -182,6 +183,15 @@ namespace MobileGL {
|
||||
#endif
|
||||
{
|
||||
MG_Backend::Init();
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P13 W4: monolith's record arm reads the verb's own state, which only a verb sink
|
||||
// writes; the port routes the ported verbs through one (MG_Remote/Client/EmitTables.cpp).
|
||||
// Installed HERE, by the library's role-neutral init, rather than by MG_Backend's: the
|
||||
// port is client code, and the server image must not need it to link.
|
||||
if (MG_Config::RecordArmAliasesFrontend() && MG_Backend::pActiveBackendObject) {
|
||||
MG_Remote::Client::InstallMonolithVerbPort(MG_Backend::gBackendFunctionsTable);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
MGLOG_D("MG_Backend initialized");
|
||||
MG_Impl::Init();
|
||||
|
||||
@@ -364,7 +364,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// object's MappedData()/GetSize() (B3) and the client slot allocator (T2) are never
|
||||
// read. Coverage is asserted: commands read from bytes no record staged would be
|
||||
// zero-filled, which is a missing record, not a valid command stream.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
if (MG_Config::DataArmIsRecord()) {
|
||||
const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbIndirectBuffer;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(handle)) {
|
||||
auto* resource = BufferImpl::FindBufferResourceForHandle(handle);
|
||||
@@ -591,7 +591,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// the .def's own "WHO READS IT" section names this function, and now it does. The reason
|
||||
// is the table's and is printed from it.
|
||||
Bool ResolveBufferBindingSubsystemArm() {
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return false;
|
||||
if (!MG_Config::DataArmIsRecord()) return false;
|
||||
const Uint64 mask = MG_Config::Features.PipePush;
|
||||
const Bool bitSet = (mask & MG_Pipe::kMGPipeSubsystemBufferBindings) != 0;
|
||||
if (bitSet &&
|
||||
@@ -819,7 +819,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
//
|
||||
// The applier's ShaderBufferWritableMask survives as the server's record of WHICH
|
||||
// points a shader may write through, which is what P9's narrowing channel will name.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return;
|
||||
if (MG_Config::DataArmIsRecord()) return;
|
||||
#endif
|
||||
const SizeT bindingPointCnt =
|
||||
MG_Pipe::gPipeInputs.GetTouchedBufferBindingPointCount(BufferTarget::ShaderStorage);
|
||||
@@ -1378,7 +1378,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// P5c (hd, CONTRACT-P5C §3.5): under an active transport the buffer is the verb
|
||||
// record's handle; the frontend binding slot and the client allocator are
|
||||
// never read (T2). The twin's ensure is what SyncBoundBuffer's did.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
if (MG_Config::DataArmIsRecord()) {
|
||||
const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbIndirectBuffer;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(handle)) {
|
||||
auto* resource = EnsureBufferResourceForHandle(nullptr, handle);
|
||||
@@ -1450,7 +1450,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): see the draw-indirect twin above - the verb
|
||||
// record's handle, never the frontend binding slot or the client allocator.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
if (MG_Config::DataArmIsRecord()) {
|
||||
const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeApplier().VerbDispatchIndirectBuffer;
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(handle)) {
|
||||
auto* resource = EnsureBufferResourceForHandle(nullptr, handle);
|
||||
@@ -2506,7 +2506,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// from the GL thread (MG_Impl/Pipe/OwnedDrawInputs.h), so there is nothing for the
|
||||
// server to stage - and reading gPipeInputs there is the role violation this family's
|
||||
// other monolith glue is guarded against.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return true;
|
||||
if (MG_Config::DataArmIsRecord()) return true;
|
||||
const auto& currentVAO = MG_Pipe::gPipeInputs.GetBoundVertexArray();
|
||||
if (!currentVAO) return true;
|
||||
|
||||
@@ -2560,7 +2560,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
static Bool SyncClientSideVertexArraysForIndirectFetch(
|
||||
const Uint8* commandBytes, SizeT commandOffset, GLsizei stride, GLsizei index, Uint8 indexSize,
|
||||
const SharedPtr<MG_State::GLState::BufferObject>& commandBuffer) {
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return true;
|
||||
if (MG_Config::DataArmIsRecord()) return true;
|
||||
// P13 W4a (ID-P8-14a): ASK FIRST WHETHER THERE IS ANYTHING TO SNAPSHOT. The command
|
||||
// words below are read only to size a client-memory fetch; a VAO whose attributes all
|
||||
// come from buffers has none, and reading them anyway cost a whole-buffer readback of a
|
||||
@@ -2601,7 +2601,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (count <= 0 || instanceCount <= 0) return true;
|
||||
// MONOLITH ONLY - see SyncClientSideVertexArraysForFetch; this wrapper's own read of
|
||||
// the bound VAO is the frontend read that must not happen on an apply thread.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return true;
|
||||
if (MG_Config::DataArmIsRecord()) return true;
|
||||
const auto& currentVAO = MG_Pipe::gPipeInputs.GetBoundVertexArray();
|
||||
if (!currentVAO) return true;
|
||||
const Uint8 indexSize = static_cast<Uint8>(MG_Util::GetGLTypeSize(type));
|
||||
@@ -4283,6 +4283,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
static_cast<MG_State::GLState::TextureObjectBuffer*>(imageBinding.Texture.get());
|
||||
const auto& bufferObject = textureBuffer->GetBufferBindingSlot().GetBoundObject();
|
||||
BufferImpl::MarkBufferGpuWritten(bufferObject);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P13 W4a: image units stay on this arm until W4c, but monolith's buffer readers
|
||||
// are already the record arm's (SplitHostBytesForCpuRead), and they ask the TWIN.
|
||||
if (MG_Config::DataArmIsRecord() && bufferObject) {
|
||||
if (auto* twin = BufferImpl::FindBufferResourceForHandle(
|
||||
BufferImpl::HandleOfBuffer(bufferObject.get()))) {
|
||||
twin->serverGpuWritten = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8302,13 +8312,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// resource resolves from it by handle - the frontend binding slot and the client slot
|
||||
// allocator are never read (T2).
|
||||
const MG_Pipe::MGPipeHandle verbBufferHandle =
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
MG_Config::DataArmIsRecord()
|
||||
? MG_Pipe::MGPipeApplier().VerbIndirectBuffer
|
||||
: MG_Pipe::kMGPipeNullHandle;
|
||||
#endif
|
||||
const Bool useNative =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
MG_Config::DataArmIsRecord()
|
||||
? !MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws()
|
||||
:
|
||||
#endif
|
||||
@@ -8325,7 +8335,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (paramsBinding >= 0) {
|
||||
auto* resource =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
MG_Config::DataArmIsRecord()
|
||||
? BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle)
|
||||
:
|
||||
#endif
|
||||
@@ -8404,13 +8414,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// P5c (hd, CONTRACT-P5C §3.5): see ExecuteIndexedIndirectCommands - the verb's handle,
|
||||
// never the frontend binding slot or the client allocator.
|
||||
const MG_Pipe::MGPipeHandle verbBufferHandle =
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
MG_Config::DataArmIsRecord()
|
||||
? MG_Pipe::MGPipeApplier().VerbIndirectBuffer
|
||||
: MG_Pipe::kMGPipeNullHandle;
|
||||
#endif
|
||||
const Bool useNative =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
MG_Config::DataArmIsRecord()
|
||||
? !MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws()
|
||||
:
|
||||
#endif
|
||||
@@ -8425,7 +8435,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (paramsBinding >= 0) {
|
||||
auto* resource =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
MG_Config::DataArmIsRecord()
|
||||
? BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle)
|
||||
:
|
||||
#endif
|
||||
@@ -8868,7 +8878,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// the family's back.
|
||||
void RefuseElementArrayBufferFromTheFrontend(const char* entry) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||
if (!MG_Config::DataArmIsRecord()) return;
|
||||
if (!BufferImpl::VertexInputReadsRecords()) return;
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"MGPipeSlots\"} - %s read the frontend VAO's "
|
||||
"element-array slot with a live transport. The index buffer of this family is "
|
||||
@@ -8971,7 +8981,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// carried client indices (no index buffer bound) takes the client-pointer arm below,
|
||||
// `indices` already resolved through the server's segment resolver. Monolith takes
|
||||
// the original body, verbatim.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
if (MG_Config::DataArmIsRecord()) {
|
||||
const auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
const MG_Pipe::MGPipeHandle elements = applierState.IndexBuffer.Res;
|
||||
serverElementBinding = 0;
|
||||
@@ -9270,7 +9280,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot
|
||||
// is never read - the Execute* helpers resolve the buffer from the verb record's
|
||||
// handle (T2). Monolith reads the slot as it always did.
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
MG_Config::DataArmIsRecord()
|
||||
? SharedPtr<MG_State::GLState::BufferObject>(nullptr)
|
||||
:
|
||||
#endif
|
||||
@@ -9311,7 +9321,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// reads from the SERVER's staged shadow - the frontend binding slots, the frontend
|
||||
// accessors (B3) and the client allocator (T2) are never read. Monolith takes the
|
||||
// original body below, verbatim.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
if (MG_Config::DataArmIsRecord()) {
|
||||
const auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
|
||||
const SizeT commandBytes = commandOffset + static_cast<SizeT>(stride) * static_cast<SizeT>(maxdrawcount - 1) +
|
||||
@@ -9439,7 +9449,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot
|
||||
// is never read - the Execute* helpers resolve the buffer from the verb record's
|
||||
// handle (T2). Monolith reads the slot as it always did.
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
MG_Config::DataArmIsRecord()
|
||||
? SharedPtr<MG_State::GLState::BufferObject>(nullptr)
|
||||
:
|
||||
#endif
|
||||
@@ -9477,7 +9487,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.5): see the indexed twin - the verb record's handles and
|
||||
// the SERVER's staged shadow, never the frontend bindings or the client allocator.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
if (MG_Config::DataArmIsRecord()) {
|
||||
const auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
|
||||
const SizeT commandBytes = commandOffset + static_cast<SizeT>(stride) * static_cast<SizeT>(maxdrawcount - 1) +
|
||||
@@ -9710,7 +9720,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot
|
||||
// is never read - the Execute* helpers resolve the buffer from the verb record's
|
||||
// handle (T2). Monolith reads the slot as it always did.
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
MG_Config::DataArmIsRecord()
|
||||
? SharedPtr<MG_State::GLState::BufferObject>(nullptr)
|
||||
:
|
||||
#endif
|
||||
@@ -9759,7 +9769,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// P5c (hd, CONTRACT-P5C §3.5): with an active transport the frontend binding slot
|
||||
// is never read - the Execute* helpers resolve the buffer from the verb record's
|
||||
// handle (T2). Monolith reads the slot as it always did.
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
MG_Config::DataArmIsRecord()
|
||||
? SharedPtr<MG_State::GLState::BufferObject>(nullptr)
|
||||
:
|
||||
#endif
|
||||
|
||||
@@ -2105,6 +2105,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void Ops_H_Respecify(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc,
|
||||
const void* initialBytes) {
|
||||
auto* resource = FindBufferResourceForHandle(res);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P13 W4a (§1.4): ON MONOLITH'S RECORD ARM THE TWIN IS MINTED HERE, not lazily at
|
||||
// the first draw. The record arm's readers have no frontend object to re-read a
|
||||
// base from, so the base this call carries - the client's shadow, for an orphaning
|
||||
// respecify too (PipeFill passes it there on this arm) - is the only way they learn
|
||||
// it. Minting is GL-free; the storage stays lazy.
|
||||
const Bool aliasesFrontend = MG_Config::RecordArmAliasesFrontend();
|
||||
if (resource == nullptr && aliasesFrontend && initialBytes != nullptr) {
|
||||
resource = GetOrCreateBufferResourceForHandle(res);
|
||||
}
|
||||
#else
|
||||
constexpr Bool aliasesFrontend = false;
|
||||
#endif
|
||||
if (resource != nullptr) {
|
||||
// A respecify's companion pointer IS the shadow base (offset 0) - and an
|
||||
// ORPHANING one (glBufferData with NULL) carries HasDefinedContent clear and
|
||||
@@ -2116,7 +2129,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// every reader below treats a null base as "no bytes to move", which is the
|
||||
// honest answer, and the ensure path re-reads the live base from the
|
||||
// frontend object it still holds.
|
||||
if (desc.HasDefinedContent != 0 && initialBytes != nullptr) {
|
||||
// P13 W4a: an ALIASING store records the base whether or not the content is
|
||||
// defined - the pointer is the frontend shadow's new allocation either way, and
|
||||
// the upload below stays gated on the descriptor (RespecifyStorageWith's
|
||||
// initialData), so an orphan still uploads nothing.
|
||||
if ((desc.HasDefinedContent != 0 || aliasesFrontend) && initialBytes != nullptr) {
|
||||
// R-11: in monolith this is the client's shadow base, unchanged; under
|
||||
// split it is copied into server-owned storage first. A respecify's
|
||||
// companion pointer is the base at offset 0 and covers the whole store.
|
||||
@@ -2134,6 +2151,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P8-C: whatever a shader wrote belonged to the store this call replaces.
|
||||
resource->serverGpuWritten = false;
|
||||
resource->contentSinceRespecify = false;
|
||||
#endif
|
||||
}
|
||||
if (!resource) return; // lazy: the ensure path full-uploads on creation
|
||||
@@ -2205,7 +2223,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// Monolith is untouched: the twin stays lazy there because the frontend object's
|
||||
// MappedData is the source and nothing is lost by deferring the allocation (D-A2).
|
||||
if (resource == nullptr && bytes != nullptr &&
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
MG_Config::DataArmIsRecord()) {
|
||||
resource = GetOrCreateBufferResourceForHandle(res);
|
||||
}
|
||||
#endif
|
||||
@@ -2252,6 +2270,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// ("these bytes, at this base") and the drain takes this mutex to lift them.
|
||||
resource->hostBytes =
|
||||
MGL_SERVER_STAGED_ADOPT(*resource, ResourceWidthOf(res), bytes, offset, size);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (size != 0) resource->contentSinceRespecify = true;
|
||||
#endif
|
||||
}
|
||||
if (resource->pendingRespecify) return; // full re-upload pending anyway
|
||||
if (!CanTouchGLNow() || resource->id == 0 ||
|
||||
@@ -2312,6 +2333,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
const std::lock_guard<std::mutex> lock(resource->pendingMutex);
|
||||
resource->hostBytes = MGL_SERVER_STAGED_ADOPT(*resource, ResourceWidthOf(res), bytes,
|
||||
start, end - start);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (end > start) resource->contentSinceRespecify = true;
|
||||
#endif
|
||||
}
|
||||
if (resource->pendingRespecify) return;
|
||||
if (!CanTouchGLNow() || resource->id == 0 ||
|
||||
@@ -2373,7 +2397,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// An empty capture may conservatively request a writeback before
|
||||
// any draw materialized this buffer. Resolve its staged resource
|
||||
// exactly as a GPU consumer would, then return the real bytes.
|
||||
auto* resource = MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
auto* resource = MG_Config::DataArmIsRecord()
|
||||
? EnsureBufferResourceForHandle(nullptr, res) : FindBufferResourceForHandle(res);
|
||||
#else
|
||||
auto* resource = FindBufferResourceForHandle(res);
|
||||
@@ -2446,6 +2470,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// The shadow now matches the backend byte for byte; without this the next draw
|
||||
// would see a newer serial and re-upload the readback over it.
|
||||
resource->syncedChangeSerial = ResourceSerialOf(res);
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P13 W4a: on monolith's record arm the base this twin reads IS the shadow the
|
||||
// writeback just filled, so a whole-store readback is also the refresh
|
||||
// SplitHostBytesForCpuRead would owe. (A copying store's copy is not the
|
||||
// client's shadow, and keeps its mark.)
|
||||
if (MG_Config::RecordArmAliasesFrontend() && readOffset == 0 && size >= ResourceWidthOf(res)) {
|
||||
resource->serverGpuWritten = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Ops_H_Destroy(MG_Pipe::MGPipeHandle res) {
|
||||
@@ -3455,6 +3488,64 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return record != nullptr && record->Desc.HasDefinedContent != 0;
|
||||
}
|
||||
|
||||
// P13 W4a (§1.4): THE GPU-WRITE REFRESH OF AN ALIASING STORE. Monolith's record arm reads
|
||||
// the frontend shadow in place (StagedShadowStore keeps the caller's pointer), so a store a
|
||||
// shader wrote is refreshed the way the client refreshes it under split: the bytes are
|
||||
// read back from this process's GL buffer and handed to OnBufferWriteback, whose consumer
|
||||
// (the frontend, one call away) copies them into that shadow. The backend never writes
|
||||
// frontend memory itself. Ops_H_Readback's order, for its reasons: queued uploads first,
|
||||
// resident writes next, then the read, the serial stamp and - last - the epoch bump.
|
||||
const Uint8* RefreshAliasedStoreFromGpu(GLESBufferResource& resource, MG_Pipe::MGPipeHandle res,
|
||||
const char* site) {
|
||||
if (!CanTouchGLNow() || resource.id == 0 || !resource.storageInitialized ||
|
||||
resource.pendingRespecify || resource.contextGeneration != g_bufferContextGeneration ||
|
||||
!g_GLESFuncs.glMapBufferRange || !g_GLESFuncs.glUnmapBuffer) {
|
||||
MGLOG_E_ONCE("%s: buffer {%u, %u} was written on the GPU but its GL store cannot be read "
|
||||
"back here, so the CPU reads the shadow as it is",
|
||||
site, res.Slot, res.Gen);
|
||||
return SplitHostBytes(resource);
|
||||
}
|
||||
const SizeT width = ResourceWidthOf(res);
|
||||
const SizeT size = std::min(width, resource.storageSize);
|
||||
if (size == 0) return SplitHostBytes(resource);
|
||||
if (MG_Pipe::gMGPipeCallbacks.OnBufferWriteback == nullptr) {
|
||||
MGLOG_E_ONCE("%s: buffer {%u, %u} was written on the GPU and no reverse channel is "
|
||||
"installed to refresh the shadow it aliases",
|
||||
site, res.Slot, res.Gen);
|
||||
return SplitHostBytes(resource);
|
||||
}
|
||||
FlushPendingRangesFrom(resource, resource.hostBytes, width);
|
||||
DrainResidentWritesNow(resource);
|
||||
BindBufferId(TempBufferTarget, resource.id);
|
||||
void* mapped = g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast<GLsizeiptr>(size),
|
||||
GL_MAP_READ_BIT);
|
||||
if (mapped == nullptr) {
|
||||
MGLOG_E_ONCE("%s: glMapBufferRange(read) failed for GPU-written buffer %u; the CPU reads the "
|
||||
"shadow as it is",
|
||||
site, resource.id);
|
||||
return SplitHostBytes(resource);
|
||||
}
|
||||
MG_Pipe::gMGPipeCallbacks.OnBufferWriteback(
|
||||
res, 0,
|
||||
MG_Pipe::MGPBlobRef{reinterpret_cast<Uint64>(mapped), static_cast<Uint64>(size),
|
||||
MG_Pipe::kMGHostSpanSegNone, 0});
|
||||
if (resource.hostBytes == nullptr) {
|
||||
// Every content-carrying record and every respecify of a store with a size hands
|
||||
// its base over on this arm, so no base here means a shape nothing recorded. Keep
|
||||
// the bytes in a private copy rather than answer "nothing" for GPU-written bytes.
|
||||
MGLOG_W_ONCE("%s: GPU-written buffer {%u, %u} has no recorded shadow base; its refreshed "
|
||||
"bytes are kept in a private copy",
|
||||
site, res.Slot, res.Gen);
|
||||
const std::lock_guard<std::mutex> lock(resource.pendingMutex);
|
||||
resource.hostBytes = ServerStaged().AdoptPrivateCopy(&resource, width, mapped, size);
|
||||
}
|
||||
g_GLESFuncs.glUnmapBuffer(TempBufferTarget);
|
||||
resource.serverGpuWritten = false;
|
||||
resource.syncedChangeSerial = ResourceSerialOf(res);
|
||||
BumpBufferMutationEpoch();
|
||||
return resource.hostBytes;
|
||||
}
|
||||
|
||||
// P8-C. The declaration says what; this says in what order, and it is Ops_H_Readback's
|
||||
// order because it is the same question asked by the server of itself instead of by the
|
||||
// client: (1) queued uploads land in the GL store first - reading back past them would
|
||||
@@ -3465,15 +3556,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// serial stamp and the epoch bump, as Ops_H_ReadbackTracked does them.
|
||||
const Uint8* SplitHostBytesForCpuRead(GLESBufferResource& resource, MG_Pipe::MGPipeHandle res,
|
||||
const char* site) {
|
||||
// A coherent store - T0's import, or (P13 W4a) a map monolith's record arm donated -
|
||||
// has no copy: its bytes are read through the map once queued writes have landed.
|
||||
const Bool coherent = resource.persistentMapped && resource.persistentPtr != nullptr;
|
||||
Bool residentWritesQueued = false;
|
||||
if (resource.externalAhb != nullptr) {
|
||||
if (coherent) {
|
||||
const std::lock_guard<std::mutex> lock(resource.pendingMutex);
|
||||
residentWritesQueued = !resource.pendingResidentWrites.empty();
|
||||
}
|
||||
if (!resource.serverGpuWritten && !residentWritesQueued) return SplitHostBytes(resource);
|
||||
// The marks are made only under a transport, where the staged store COPIES; the guard
|
||||
// keeps a monolith store (which keeps the caller's pointer) from adopting a mapping.
|
||||
if (!ServerStaged().CopiesIntoServerStorage()) return SplitHostBytes(resource);
|
||||
if (!ServerStaged().CopiesIntoServerStorage() && !coherent) {
|
||||
// P13 W4a (§1.4): an ALIASING store (monolith's record arm) is refreshed through
|
||||
// the frontend - the writeback lands in the shadow the base points into. Any other
|
||||
// non-copying store has no marks to answer.
|
||||
if (MG_Config::RecordArmAliasesFrontend()) return RefreshAliasedStoreFromGpu(resource, res, site);
|
||||
return SplitHostBytes(resource);
|
||||
}
|
||||
if (!CanTouchGLNow() || resource.id == 0 || !resource.storageInitialized ||
|
||||
resource.pendingRespecify || resource.contextGeneration != g_bufferContextGeneration ||
|
||||
!g_GLESFuncs.glMapBufferRange || !g_GLESFuncs.glUnmapBuffer) {
|
||||
@@ -3482,7 +3580,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
site, res.Slot, res.Gen);
|
||||
return SplitHostBytes(resource);
|
||||
}
|
||||
if (resource.externalAhb != nullptr) {
|
||||
if (coherent) {
|
||||
// T0: the copy IS this server's coherent map of the client's pages. Landing the
|
||||
// queued writes and finishing makes them, and every shader write already queued,
|
||||
// visible through it - Ops_H_Readback's persistent-map arm, for the same reason.
|
||||
@@ -3803,7 +3901,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// A null object is the "no frontend to ask" case and is treated as "not mapped",
|
||||
// which is what the record already says.
|
||||
const Bool askTheObjectWhetherItIsMapped =
|
||||
MG_Config::Transport == MG_Config::TransportMode::Monolith;
|
||||
!MG_Config::DataArmIsRecord();
|
||||
if (askTheObjectWhetherItIsMapped && frontend != nullptr && frontend->IsMapped()) return false;
|
||||
if (resource->pendingRespecify || !resource->storageInitialized) return false;
|
||||
if (!resource->pendingRanges.empty()) return false;
|
||||
@@ -3870,7 +3968,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// so a corrupt staged upload rendered the frontend's correct bytes and the R-2.5
|
||||
// 0xDD audit could not reach a draw. R-2 / table 3: honest inproc is inproc that
|
||||
// does not read the client object's memory. Under monolith nothing changes.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
if (MG_Config::DataArmIsRecord()) {
|
||||
return resource->hostBytes;
|
||||
}
|
||||
#endif
|
||||
@@ -3907,7 +4005,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// run on every twin's first ensure - harmless there only because liveHostBase()
|
||||
// prefers MappedData() under monolith, and "under monolith nothing changes" should
|
||||
// be true by construction rather than by luck.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
|
||||
if (ServerStaged().CopiesIntoServerStorage() &&
|
||||
!ServerStaged().HasShadow(resource)) {
|
||||
resource->hostBytes = nullptr;
|
||||
}
|
||||
@@ -4015,7 +4113,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// layer-1 surface ("buffer-legacy-arm") and the client's own pre-verb
|
||||
// persistent-map push is the only producer; the call is skipped outright. Under
|
||||
// monolith D-N's placement stands.
|
||||
if (bufferObject && MG_Config::Transport == MG_Config::TransportMode::Monolith) {
|
||||
if (bufferObject && !MG_Config::DataArmIsRecord()) {
|
||||
bufferObject->SyncPersistentMappedRange();
|
||||
}
|
||||
#else
|
||||
@@ -4064,9 +4162,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// application's own declaration and the upload stays a pure NULL reallocation.
|
||||
const Bool shadowHasContent =
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith
|
||||
MG_Config::DataArmIsRecord()
|
||||
? (record->Desc.HasDefinedContent != 0 ||
|
||||
ServerStaged().CoveredRunCount(resource) != 0)
|
||||
(ServerStaged().CopiesIntoServerStorage() ? ServerStaged().CoveredRunCount(resource) != 0
|
||||
: resource->contentSinceRespecify))
|
||||
:
|
||||
#endif
|
||||
(bufferObject ? bufferObject->HasDefinedContent() : (record->Desc.HasDefinedContent != 0));
|
||||
@@ -6216,7 +6315,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// rather than a stale read.
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
const Bool sourceGpuWritten =
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith && resource->serverGpuWritten;
|
||||
MG_Config::DataArmIsRecord() && resource->serverGpuWritten;
|
||||
if (sourceGpuWritten) {
|
||||
BufferImpl::SplitHostBytesForCpuRead(*resource, binding.Res, "fp64_vertex_narrowing");
|
||||
}
|
||||
|
||||
@@ -1085,6 +1085,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// by SplitHostBytesForCpuRead's refresh and by a respecify. Only the server's own
|
||||
// CPU readers consult it; the client keeps its own set (GpuWritePending.h).
|
||||
Bool serverGpuWritten = false;
|
||||
// P13 W4a: content-carrying records (sub-data, flush-range) arrived since the last
|
||||
// respecify. A copying store answers this with its coverage; an ALIASING one (monolith's
|
||||
// record arm) keeps no coverage, so the twin says it - the streaming idiom (orphan, then
|
||||
// glBufferSubData) defines content no descriptor declares.
|
||||
Bool contentSinceRespecify = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -1159,7 +1164,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// exactly as the monolith arm reads the adopted map through MappedData().
|
||||
inline const Uint8* SplitHostBytes(const GLESBufferResource& resource) {
|
||||
if (resource.hostBytes != nullptr) return resource.hostBytes;
|
||||
return resource.externalAhb != nullptr ? static_cast<const Uint8*>(resource.persistentPtr) : nullptr;
|
||||
// P13 W4a: any coherent persistent store, not only T0's - monolith's record arm donates
|
||||
// its maps in-process (Ops_H_MapPersistent), and such a store keeps no shadow at all.
|
||||
// Under split the wire's map_persistent declines, so there this is still T0 alone.
|
||||
return resource.persistentMapped && resource.persistentPtr != nullptr
|
||||
? static_cast<const Uint8*>(resource.persistentPtr)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
// P8-C: THE SAME BYTES, MADE CURRENT FIRST - for a reader that CONSUMES them (the restart
|
||||
@@ -1315,7 +1325,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// selectors that can drift apart. One definition, spelled at every site that reads it.
|
||||
inline Bool VertexInputReadsRecords() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
return MG_Config::Transport != MG_Config::TransportMode::Monolith &&
|
||||
return MG_Config::DataArmIsRecord() &&
|
||||
VertexInputSubsystemEnabled();
|
||||
#else
|
||||
return false;
|
||||
|
||||
@@ -294,7 +294,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
|
||||
// above, never inferred from a null).
|
||||
Uint BoundDrawIndirectBufferId() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
if (MG_Config::DataArmIsRecord()) {
|
||||
const MG_Pipe::MGPipeHandle res = MG_Pipe::MGPipeApplier().VerbIndirectBuffer;
|
||||
if (MG_Pipe::MGPipeHandleIsNull(res)) return 0;
|
||||
auto* resource = BufferImpl::EnsureBufferResourceForHandle(nullptr, res);
|
||||
|
||||
@@ -984,8 +984,13 @@ namespace MobileGL::MG_Pipe {
|
||||
// InitialBytesNotCarried self-check aborted by name on the first
|
||||
// glBufferData(target, 0, NULL, usage) of the bsl-esc-menu trace. `GetSize() > 0` is
|
||||
// what makes the answer mean "there are bytes here" rather than "the store is defined".
|
||||
const void* initialBytes =
|
||||
(desc.HasDefinedContent != 0 && buffer.GetSize() > 0) ? buffer.MappedData() : nullptr;
|
||||
//
|
||||
// P13 W4a: ON MONOLITH'S RECORD ARM THE BASE RIDES AN ORPHANING RESPECIFY TOO. That arm's
|
||||
// twin aliases this shadow and has no object to re-read it from, and an orphan is exactly
|
||||
// the call that reallocates it; the backend still uploads nothing for it, because its
|
||||
// upload is gated on the descriptor's HasDefinedContent and not on the pointer.
|
||||
const Bool carryBase = desc.HasDefinedContent != 0 || MG_Config::RecordArmAliasesFrontend();
|
||||
const void* initialBytes = (carryBase && buffer.GetSize() > 0) ? buffer.MappedData() : nullptr;
|
||||
// kNeedsAck rides on the CALL and MGPipeResourceRespecifyNeedsAck(desc) decides per
|
||||
// record: only an immutable store (a glBufferStorage*) is a real synchronous
|
||||
// allocation and only it is allowed one. In monolith the acknowledgement is
|
||||
|
||||
@@ -385,14 +385,14 @@ namespace MobileGL::MG_Pipe {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Client addresses never cross the transport. Draw emission snapshots
|
||||
// their referenced elements into owned buffers with a zero byte origin.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
|
||||
if (MG_Config::DataArmIsRecord() &&
|
||||
!vao.GetAttribute(static_cast<Uint>(i)).Buffer) m_attributes[i].Offset = 0;
|
||||
#endif
|
||||
}
|
||||
for (SizeT i = 0; i < kBindings; ++i) {
|
||||
m_bindingPoints[i] = MGPipeBuildVertexBindingPointWire(vao.GetBindingPoint(static_cast<Uint>(i)));
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
|
||||
if (MG_Config::DataArmIsRecord() &&
|
||||
!vao.GetBindingPoint(static_cast<Uint>(i)).Buffer) m_bindingPoints[i].Offset = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ namespace MGITest {
|
||||
// The resolved transport, for a message: "monolith", "inproc", "spawn", "unix", "pipe".
|
||||
std::string transportName = "monolith";
|
||||
// P13 W4: MG_Config::DataArmIsRecord() - false in a build without the peek (and without
|
||||
// MG_Remote), which stays on the frontend arm until W5.
|
||||
// MG_Remote), which stays on the frontend arm until W5, and for Magma's monolith until
|
||||
// its single flip.
|
||||
bool dataArmIsRecord = false;
|
||||
// ClientSession::Active() != nullptr.
|
||||
bool sessionActive = false;
|
||||
|
||||
@@ -236,7 +236,9 @@ void main() {
|
||||
// THE ARMS THIS FILE SKIPS, BY NAME: the monolith arm of a backend whose reason is
|
||||
// non-null. Every split arm, and the inproc A/B, runs the case.
|
||||
bool SkipBrokenMonolith(const char* espryt, const char* magma) {
|
||||
if (PeekSplitRuntime().transportName != "monolith") return false;
|
||||
// P13 W4a: a monolith on the record arm runs the split arm's code, so it runs the case.
|
||||
const SplitRuntimeState runtime = PeekSplitRuntime();
|
||||
if (runtime.transportName != "monolith" || runtime.dataArmIsRecord) return false;
|
||||
const std::string backend = Gl().BackendName();
|
||||
const char* why = backend == "DirectGLES" ? espryt : backend == "DirectVulkan" ? magma : nullptr;
|
||||
if (why == nullptr) return false;
|
||||
|
||||
@@ -304,7 +304,7 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x];
|
||||
// monolith defects), left to P13 (ID-P8-13).
|
||||
TEST_F(WireIndirectDrawScenario, ComputeWrittenCountWordIsTheOneTheGpuReads) {
|
||||
if (!Ready() || IsSkipped()) return;
|
||||
if (!m_magma && m_transport == "monolith") {
|
||||
if (!m_magma && m_transport == "monolith" && !PeekSplitRuntime().dataArmIsRecord) {
|
||||
GTEST_SKIP() << "Espryt's monolith arm reads the count word from the frontend shadow without "
|
||||
"a sync (OQ15; recorded for P13, ID-P8-13)";
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
#include "PersistentMapTracker.h"
|
||||
|
||||
#include "../Server/ServerLoop.h"
|
||||
#include "../Server/PipeApplier.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
|
||||
#include <MG_Util/Converters/GLToMG/TextureEnumConverter.h>
|
||||
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
||||
@@ -239,6 +241,87 @@ namespace MobileGL::MG_Remote::Client {
|
||||
MG_Pipe::MGPipeDrainDeferredDestroys();
|
||||
}
|
||||
|
||||
// ---- P13 W4: THE MONOLITH VERB PORT ----------------------------------------------
|
||||
//
|
||||
// Monolith's record arm (MG_Config::RecordArmAliasesFrontend) reads the verb's own state -
|
||||
// VerbIndirectBuffer, VerbDispatchIndirectBuffer, ... - and nothing in monolith wrote it:
|
||||
// ServerVerbSink is its only writer, and monolith never decoded a verb record. So the
|
||||
// ported slots run THESE emitters - the same plan, the same pre-verb hooks in the same
|
||||
// order (persistent-map push, owned client-memory inputs, the GPU-write marks) - and hand
|
||||
// the planned record, in this process and on this thread, to a ServerVerbSink bound to
|
||||
// the one backend object, which stamps the verb state exactly as the apply thread does
|
||||
// and calls the backend's REAL table (ServerVerbSink::Table, never gBackendFunctionsTable,
|
||||
// so nothing re-enters this port). One planner and one sink body serve both shapes: the
|
||||
// monolith record arm is the split record arm minus the codec and the ring.
|
||||
Bool g_monolithVerbPort = false;
|
||||
|
||||
Server::ServerVerbSink& MonolithVerbSink() {
|
||||
// Leaked at exit like every other MG_Remote singleton (ID-8).
|
||||
static Server::ServerVerbSink& sink = *new Server::ServerVerbSink();
|
||||
return sink;
|
||||
}
|
||||
|
||||
// The session a ported verb emits through - or null, which is the monolith port.
|
||||
ClientSession* VerbSessionFor(const char* slot) {
|
||||
if (g_monolithVerbPort) return nullptr;
|
||||
return &RequireSession(slot);
|
||||
}
|
||||
|
||||
void ApplyOnMonolithPort(MG_Pipe::MGPWireOp op, const void* payload, const Wire::WireTail* tails,
|
||||
Uint32 tailCount) {
|
||||
Server::ServerVerbSink& sink = MonolithVerbSink();
|
||||
MG_Backend::BackendObject* backend = MG_Backend::pActiveBackendObject.get();
|
||||
if (sink.Backend() != backend) sink.SetBackend(backend);
|
||||
Bool applied = false;
|
||||
const char* row = nullptr;
|
||||
switch (op) {
|
||||
case MG_Pipe::MGPWireOp::DrawVbo: {
|
||||
row = "draw_vbo";
|
||||
const auto& info = *static_cast<const MG_Pipe::MGPDrawInfo*>(payload);
|
||||
const auto* ranges = (tailCount > 0 && tails[0].Size != 0)
|
||||
? static_cast<const MG_Pipe::MGPDrawRange*>(tails[0].Bytes)
|
||||
: nullptr;
|
||||
const auto* indirect = ((info.Flags & MG_Pipe::kDrawIsIndirect) != 0 && tailCount > 1)
|
||||
? static_cast<const MG_Pipe::MGPDrawIndirect*>(tails[1].Bytes)
|
||||
: nullptr;
|
||||
applied = sink.OnDrawVbo(info, ranges, nullptr, indirect);
|
||||
break;
|
||||
}
|
||||
case MG_Pipe::MGPWireOp::LaunchGrid:
|
||||
row = "launch_grid";
|
||||
applied = sink.OnLaunchGrid(*static_cast<const MG_Pipe::MGPGridInfo*>(payload));
|
||||
break;
|
||||
default:
|
||||
// A slot InstallMonolithVerbPort routes here whose row this switch does not know:
|
||||
// the two lists drifted, and falling through to the driver would be the very
|
||||
// "ran the other arm and looked fine" this port exists to end.
|
||||
UnmigratedVerbFatal("MonolithVerbPort");
|
||||
}
|
||||
if (!applied) {
|
||||
MGLOG_E_ONCE("MGPipe: the monolith verb port's %s was declined by the sink; nothing was "
|
||||
"drawn or dispatched",
|
||||
row);
|
||||
}
|
||||
}
|
||||
|
||||
// The emission of a ported verb: the session's, or the port's.
|
||||
void EmitVerbTails(ClientSession* session, MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes,
|
||||
const Wire::WireTail* tails, Uint32 tailCount) {
|
||||
if (session != nullptr) {
|
||||
session->EmitAndWaitTails(op, payload, payloadBytes, tails, tailCount, nullptr, 0, nullptr);
|
||||
return;
|
||||
}
|
||||
ApplyOnMonolithPort(op, payload, tails, tailCount);
|
||||
}
|
||||
void EmitVerbNoTail(ClientSession* session, MG_Pipe::MGPWireOp op, const void* payload,
|
||||
Uint64 payloadBytes) {
|
||||
if (session != nullptr) {
|
||||
session->EmitAndWait(op, payload, payloadBytes, nullptr, 0, nullptr, 0, nullptr);
|
||||
return;
|
||||
}
|
||||
ApplyOnMonolithPort(op, payload, nullptr, 0);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// CLASS B - the five slots the verb census measured (CONTRACT-P5.md §7)
|
||||
// =============================================================================
|
||||
@@ -519,7 +602,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
const MG_Pipe::MGPDrawRange* ranges, Uint32 numDraws,
|
||||
const void* clientIndices, Uint64 clientIndexBytes,
|
||||
const MG_Pipe::MGPDrawIndirect* indirect) {
|
||||
ClientSession& session = RequireSession(slot);
|
||||
ClientSession* session = VerbSessionFor(slot);
|
||||
PersistentMapTracker::Instance().PushDrawConsumers();
|
||||
MG_Pipe::MGPipeDrainDeferredDestroys();
|
||||
// Snapshot before marking THIS draw's potential GPU writes: resolving
|
||||
@@ -554,8 +637,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
tails[1] = {indirect, sizeof(*indirect)};
|
||||
tailCount = 2;
|
||||
}
|
||||
session.EmitAndWaitTails(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), tails,
|
||||
tailCount, nullptr, 0, nullptr);
|
||||
EmitVerbTails(session, MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), tails, tailCount);
|
||||
}
|
||||
|
||||
// ---- the single-draw indexed family: DrawElements, DrawElementsBaseVertex, the two
|
||||
@@ -1293,7 +1375,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// carried both calls before its Fatal precisely so that the package which flipped this
|
||||
// slot would inherit a call site that was already correct.
|
||||
void EmitDispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) {
|
||||
ClientSession& session = RequireSession("DispatchCompute");
|
||||
ClientSession* session = VerbSessionFor("DispatchCompute");
|
||||
PersistentMapTracker::Instance().PushDrawConsumers();
|
||||
MarkGpuWritesForDispatch();
|
||||
|
||||
@@ -1307,12 +1389,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
record.IndirectBuffer = MG_Pipe::kMGPipeNullHandle;
|
||||
record.IndirectOffset = 0;
|
||||
record.IsIndirect = 0;
|
||||
session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0,
|
||||
nullptr, 0, nullptr);
|
||||
EmitVerbNoTail(session, MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record));
|
||||
}
|
||||
|
||||
void EmitDispatchComputeIndirect(GLintptr indirect) {
|
||||
ClientSession& session = RequireSession("DispatchComputeIndirect");
|
||||
ClientSession* session = VerbSessionFor("DispatchComputeIndirect");
|
||||
PersistentMapTracker::Instance().PushDrawConsumers();
|
||||
MarkGpuWritesForDispatch();
|
||||
|
||||
@@ -1333,8 +1414,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
MG_Pipe::MGPipeKind::Buffer, bound->GetLifetimeId());
|
||||
}
|
||||
}
|
||||
session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0,
|
||||
nullptr, 0, nullptr);
|
||||
EmitVerbNoTail(session, MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record));
|
||||
}
|
||||
|
||||
// glMemoryBarrier / glMemoryBarrierByRegion. The bits cross VERBATIM: the frontend has
|
||||
@@ -2125,6 +2205,36 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return table;
|
||||
}
|
||||
|
||||
void InstallMonolithVerbPort(MG_Backend::GlobalBackendFunctionsTable& table) {
|
||||
// P13 W4a: the draw and dispatch families. Later W4 steps add their verbs here AND to
|
||||
// ApplyOnMonolithPort's switch, in the same commit.
|
||||
table.GL.DrawArrays = &EmitDrawArrays;
|
||||
table.GL.DrawElements = &EmitDrawElements;
|
||||
table.GL.DrawElementsBaseVertex = &EmitDrawElementsBaseVertex;
|
||||
table.GL.DrawRangeElements = &EmitDrawRangeElements;
|
||||
table.GL.DrawRangeElementsBaseVertex = &EmitDrawRangeElementsBaseVertex;
|
||||
table.GL.DrawElementsInstanced = &EmitDrawElementsInstanced;
|
||||
table.GL.DrawElementsInstancedBaseVertex = &EmitDrawElementsInstancedBaseVertex;
|
||||
table.GL.DrawElementsInstancedBaseInstance = &EmitDrawElementsInstancedBaseInstance;
|
||||
table.GL.DrawElementsInstancedBaseVertexBaseInstance = &EmitDrawElementsInstancedBaseVertexBaseInstance;
|
||||
table.GL.DrawArraysInstanced = &EmitDrawArraysInstanced;
|
||||
table.GL.DrawArraysInstancedBaseInstance = &EmitDrawArraysInstancedBaseInstance;
|
||||
table.GL.MultiDrawArrays = &EmitMultiDrawArrays;
|
||||
table.GL.MultiDrawElements = &EmitMultiDrawElements;
|
||||
table.GL.MultiDrawElementsBaseVertex = &EmitMultiDrawElementsBaseVertex;
|
||||
table.GL.DrawArraysIndirect = &EmitDrawArraysIndirect;
|
||||
table.GL.DrawElementsIndirect = &EmitDrawElementsIndirect;
|
||||
table.GL.MultiDrawArraysIndirect = &EmitMultiDrawArraysIndirect;
|
||||
table.GL.MultiDrawElementsIndirect = &EmitMultiDrawElementsIndirect;
|
||||
table.GL.MultiDrawArraysIndirectCount = &EmitMultiDrawArraysIndirectCount;
|
||||
table.GL.MultiDrawElementsIndirectCount = &EmitMultiDrawElementsIndirectCount;
|
||||
table.GL.DispatchCompute = &EmitDispatchCompute;
|
||||
table.GL.DispatchComputeIndirect = &EmitDispatchComputeIndirect;
|
||||
g_monolithVerbPort = true;
|
||||
}
|
||||
|
||||
Bool MonolithVerbPortInstalled() { return g_monolithVerbPort; }
|
||||
|
||||
Uint32 ImplementedVerbCount() { return kEmittedSlots; }
|
||||
Uint32 LocallyAnsweredSlotCount() { return kLocallyAnsweredSlots; }
|
||||
Uint32 UnmigratedSlotCount() { return kUnmigratedSlots; }
|
||||
|
||||
@@ -88,6 +88,13 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// that the message wording lives in exactly one place.
|
||||
[[noreturn]] void UnmigratedVerbFatal(const char* slot);
|
||||
|
||||
// P13 W4: monolith's record arm. Overwrites the ported verb slots of the backend's own
|
||||
// table with this file's emitters, which hand their planned record to an in-process
|
||||
// ServerVerbSink instead of a session (EmitTables.cpp, "THE MONOLITH VERB PORT"). Installed
|
||||
// by MG_Backend's bring-up when MG_Config::RecordArmAliasesFrontend() holds.
|
||||
void InstallMonolithVerbPort(MG_Backend::GlobalBackendFunctionsTable& table);
|
||||
Bool MonolithVerbPortInstalled();
|
||||
|
||||
// How many of the 71 slots have a real emitter. Reported at bring-up and asserted by the
|
||||
// gate: a table that silently loses an emitter should not be able to look the same as one
|
||||
// that never had it.
|
||||
|
||||
@@ -124,7 +124,9 @@ namespace MobileGL::MG_Remote::Client {
|
||||
} // namespace
|
||||
|
||||
Bool GpuWriteSetIsClientSide() {
|
||||
return MG_Config::Transport != MG_Config::TransportMode::Monolith;
|
||||
// P13 W4a: wherever the backend reads records - every wire, and monolith's record arm -
|
||||
// the client owns the conservative set; the backend marks only its own twins.
|
||||
return MG_Config::DataArmIsRecord();
|
||||
}
|
||||
|
||||
Bool ImageUnitIsAWritableBufferTexture(const ImageTextureBinding& binding) {
|
||||
|
||||
@@ -704,7 +704,9 @@ namespace MobileGL::MG_Remote::Client {
|
||||
}
|
||||
|
||||
Bool PersistentMapTracker::PushIsArmed() {
|
||||
return MG_Config::Transport != MG_Config::TransportMode::Monolith;
|
||||
// P13 W4a: the record arm's buffer reads never ask the frontend object to sync a map, so
|
||||
// the client pushes the mapped span wherever that arm runs - monolith's included.
|
||||
return MG_Config::DataArmIsRecord();
|
||||
}
|
||||
|
||||
// P5d round 3 (package D): the body this forwards to is now one relaxed load of the apply
|
||||
|
||||
@@ -152,6 +152,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// The server's private backend. Null until ServerLoop::CreateBackend has run, and a
|
||||
// verb that arrives before then declines by name rather than dereferencing.
|
||||
void SetBackend(MG_Backend::BackendObject* backend);
|
||||
MG_Backend::BackendObject* Backend() const { return m_backend; }
|
||||
void SetMaxReplyBytes(Uint64 bytes) { m_maxReplyBytes = bytes; }
|
||||
|
||||
// P11 B2: T0's map_persistent - ServerSession::AdoptStoreT0 (it owns the aux socket).
|
||||
|
||||
@@ -120,6 +120,22 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return shadow.Bytes.data();
|
||||
}
|
||||
|
||||
// P13 W4a: a COPY even in a non-copying store - the fallback for a GPU-written store on
|
||||
// monolith's record arm that never recorded the shadow base it aliases (Managers.cpp's
|
||||
// RefreshAliasedStoreFromGpu). Whole-store, covered, and dropped with the key.
|
||||
const Uint8* AdoptPrivateCopy(const void* key, SizeT width, const void* bytes, SizeT size) {
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
Shadow& shadow = m_shadows[key];
|
||||
shadow.SessionKey = CurrentStagedBucket().SessionKey;
|
||||
if (shadow.Bytes.size() < std::max(width, size)) shadow.Bytes.resize(std::max(width, size), 0);
|
||||
if (size != 0 && bytes != nullptr) {
|
||||
std::memcpy(shadow.Bytes.data(), bytes, size);
|
||||
CoverageAdd(shadow.Covered, 0, size);
|
||||
}
|
||||
m_any.store(true, std::memory_order_release);
|
||||
return shadow.Bytes.data();
|
||||
}
|
||||
|
||||
void Drop(const void* key) {
|
||||
if (!m_any.load(std::memory_order_acquire)) return;
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
@@ -544,8 +544,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
// cleared by the time the applier calls back, and a second clear here would also
|
||||
// retire a GPU write announced by something other than a readback - a ReadPixels into
|
||||
// a pack PBO writes back through this same function.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
|
||||
atOffset == 0 && data.size >= m_size) {
|
||||
//
|
||||
// P13 W4a: monolith's record arm refreshes an aliased store through this function too
|
||||
// (Managers.cpp RefreshAliasedStoreFromGpu), and a whole-store landing there is exactly as
|
||||
// current as one under split.
|
||||
if (MG_Config::DataArmIsRecord() && atOffset == 0 && data.size >= m_size) {
|
||||
m_gpuWritePending = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -23,6 +23,31 @@
|
||||
|
||||
using namespace MobileGL;
|
||||
|
||||
namespace {
|
||||
// P13 W4a: THE MONOLITH FRONTEND ARM, PINNED. The cases that use this assert how the frontend
|
||||
// object pushes a map's bytes when the backend reads the object itself - the arm W4 retires
|
||||
// family by family and W6 deletes. On the record arm (every wire, and monolith's from W4a) the
|
||||
// client's persistent-map tracker owns that push instead, which the split lanes cover. When
|
||||
// W6 deletes the frontend arm, these cases go with it.
|
||||
class ScopedMonolithFrontendArm {
|
||||
public:
|
||||
ScopedMonolithFrontendArm() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
m_saved = MG_Config::MonolithTakesRecordArm;
|
||||
MG_Config::MonolithTakesRecordArm = false;
|
||||
#endif
|
||||
}
|
||||
~ScopedMonolithFrontendArm() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Config::MonolithTakesRecordArm = m_saved;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
Bool m_saved = false;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class BufferTest : public ::testing::Test {
|
||||
protected:
|
||||
// GL error flags are sticky per error code and the context outlives an individual test in this
|
||||
@@ -232,6 +257,7 @@ TEST_F(BufferTest, AcquireMemoryRangeWithoutExplicit) {
|
||||
}
|
||||
|
||||
TEST_F(BufferTest, AcquireMemoryRangeWithExplicit) {
|
||||
const ScopedMonolithFrontendArm frontendArm;
|
||||
auto& slot = MobileGL::MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Uniform);
|
||||
Vector<Uint> bufferNames;
|
||||
MobileGL::MG_State::pGLContext->GenBufferNames(1, bufferNames);
|
||||
@@ -2493,6 +2519,7 @@ TEST_F(BufferTest, MapBufferRangeAndUnmapBufferReseedAnAdoptedShaderStorageBuffe
|
||||
// before - the staging copy is written back into the shadow and the backend's flush op
|
||||
// carries the range down.
|
||||
TEST_F(BufferTest, ANonPersistentWriteMapOfAShadowBackedStoreStillFlushesThroughTheBackend) {
|
||||
const ScopedMonolithFrontendArm frontendArm;
|
||||
ZeroCopyMockBackend mock;
|
||||
mock.provideMap = false;
|
||||
g_zeroCopyMock = &mock;
|
||||
@@ -2953,6 +2980,7 @@ TEST_F(BufferTest, RespecifyingAStoreWhileItIsMappedDoesNotLandTheStagedBytesInt
|
||||
}
|
||||
|
||||
TEST_F(BufferTest, RespecifyingAShadowBackedStoreWhileItIsMappedPushesNoRangeDown) {
|
||||
const ScopedMonolithFrontendArm frontendArm;
|
||||
ZeroCopyMockBackend mock;
|
||||
mock.provideMap = false;
|
||||
g_zeroCopyMock = &mock;
|
||||
|
||||
Reference in New Issue
Block a user