[Feat] (P13 W4): Magma monolith takes the record arm in one flip, with in-process persistent-map donation

This commit is contained in:
2026-10-06 08:59:53 -04:00
parent c86be0c274
commit f3a2fd9308
15 changed files with 141 additions and 73 deletions
+4 -4
View File
@@ -465,10 +465,10 @@ 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)); });
// 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;
// P13 W4: both backends' monolith runs the record arm - Espryt's from W4a, family by family;
// Magma's at its single flip (its verb entry points pick one arm for every family at once).
const Bool backendReady = MG_Config::ActiveBackendType == BackendType::DirectGLES ||
MG_Config::ActiveBackendType == BackendType::DirectVulkan;
MG_Config::MonolithTakesRecordArm = backendReady;
if (lowered.empty() || lowered == "record") return;
if (lowered == "frontend") {
@@ -40,7 +40,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
namespace {
#if MOBILEGL_BUILD_DISAGGREGATED
void RejectWireLegacyBuffer() {
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
MGLOG_F("MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} (Magma P7 buffer consumer)");
std::abort();
}
@@ -647,7 +647,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, type, indirect, drawcount, stride, true);
return;
}
@@ -658,7 +658,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, 0, indirect, drawcount, stride, false);
return;
}
@@ -715,7 +715,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, type, indirect, maxdrawcount, stride, true, true, drawcount);
return;
}
@@ -727,7 +727,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, 0, indirect, maxdrawcount, stride, false, true, drawcount);
return;
}
@@ -805,7 +805,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void DrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, type, indirect, 1, 0, true);
return;
}
@@ -870,7 +870,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void DrawArraysIndirect(GLenum mode, const void* indirect) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
DrawWireIndirect(mode, 0, indirect, 1, 0, false);
return;
}
@@ -948,7 +948,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// DispatchComputeIndirect takes its transport branch into DispatchWireComputeIndirect
// (WireDraw.inc), which issues vkCmdDispatchIndirect from the verb's store. The CPU read
// that was here (ReadWireBuffer, a whole-GPU wait once a shader had written the store) is gone.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
pVulkanRenderer->DispatchComputeIndirect(indirect);
return;
}
@@ -1015,7 +1015,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
auto& state = MG_Pipe::MGPipeApplier();
const auto handle = state.VerbStorageBlockProgram;
if (handle.Slot >= state.ShaderCsos.size() || !state.ShaderCsos[handle.Slot].Live ||
@@ -1141,7 +1141,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static Bool BuildClosedLineLoopIndices(GLsizei count, GLenum type, const void* indices,
Vector<Uint32>& outIndices) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const SizeT width = MG_Util::GetGLTypeSize(type);
if ((width != 1 && width != 2 && width != 4) || count < 2) return false;
const auto& bound = MG_Pipe::MGPipeApplier().IndexBuffer;
@@ -1249,7 +1249,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// batch that command buffer belongs to - the handle SetupDraw is holding goes stale, and
// the next vkCmd* records into a command buffer that is no longer the frame's. Reconciling
// HERE, before SetupDraw starts recording, keeps the wait out of the recording.
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) {
if (!MG_Config::DataArmIsRecord()) {
const auto& currentVAO = MG_Pipe::gPipeInputs.GetBoundVertexArray();
Bool clientArray = false;
if (currentVAO) {
@@ -1319,7 +1319,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// offset is a hardcoded 0, so UploadAndBindIndexBuffer saw a null client pointer,
// declined the whole batch and painted nothing.)
#if MOBILEGL_BUILD_DISAGGREGATED
const Bool noIndexBuffer = MG_Config::Transport != MG_Config::TransportMode::Monolith
const Bool noIndexBuffer = MG_Config::DataArmIsRecord()
? MG_Pipe::MGPipeHandleIsNull(MG_Pipe::MGPipeApplier().IndexBuffer.Res)
: MG_Pipe::gPipeInputs.GetBoundVertexArray()->GetIndexBufferBindingSlot().GetBoundObject() == nullptr;
if (noIndexBuffer) {
@@ -1451,6 +1451,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// old renderer's GPU work is long complete, and the index must
// not be compared against the new renderer's restarted counter.
Uint64 rendererGeneration = 0;
// P13 W4: status polls answered "not yet" while the batch was still unsubmitted.
mutable Uint32 unsubmittedPolls = 0;
};
} // namespace
@@ -1509,7 +1511,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
// Pure status read (glGetSynciv must not flush).
return pVulkanRenderer->IsSubmitIndexComplete(sync->submitIndex);
if (pVulkanRenderer->IsSubmitIndexComplete(sync->submitIndex)) return true;
#if MOBILEGL_BUILD_DISAGGREGATED
// P13 W4: A STATUS POLL THAT CAN NEVER BE ANSWERED. On the record arm a frame's draws stay
// in one command buffer (and one render pass) until something submits it, so a client that
// only polls - glGetSynciv in a loop, no flush bit, no swap - waits for work nothing will
// ever hand the GPU. Under split the apply thread submits when it goes idle (the server's
// fence report, one ClientWaitSync(FLUSH, 0)); monolith has no idle moment, so a poller that
// keeps asking the same unsubmitted fence is that moment. The threshold keeps an ordinary
// once-a-frame status read from splitting the frame it is polled in.
constexpr Uint32 kPollsBeforeIdleFlush = 64;
if (MG_Config::RecordArmAliasesFrontend() && ++sync->unsubmittedPolls >= kPollsBeforeIdleFlush) {
sync->unsubmittedPolls = 0;
pVulkanRenderer->FlushForSyncPoint(sync->submitIndex);
return pVulkanRenderer->IsSubmitIndexComplete(sync->submitIndex);
}
#endif
return false;
}
namespace {
@@ -132,7 +132,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Split staging may also be the destination of a GPU UBO range copy.
// Persistent host writes must be coherent: Upload deliberately writes
// the mapped pointer without a per-range VMA flush.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && m_desc.persistentlyMapped) {
if (MG_Config::DataArmIsRecord() && m_desc.persistentlyMapped) {
bufferDesc.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
#endif
@@ -228,7 +228,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// A layout transition writes the image too. Order it after the wire
// render pass's storeOp, clears and copies instead of relying on the old
// per-draw queue-idle path. This applies even when contents are discarded.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
if (MG_Config::DataArmIsRecord())
presentBarrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
#endif
presentBarrier.dstAccessMask = 0;
@@ -244,7 +244,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
presentBarrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(commandBuffer,
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::Transport != MG_Config::TransportMode::Monolith
MG_Config::DataArmIsRecord()
? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
#else
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
@@ -591,7 +591,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_programFactory = programFactory;
m_minDynamicOffsetAlignment = std::max<VkDeviceSize>(1, minUniformBufferOffsetAlignment);
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
VkPhysicalDeviceProperties properties{};
vkGetPhysicalDeviceProperties(m_physicalDevice, &properties);
m_wireStorageOffsetAlignment = std::max<VkDeviceSize>(1, properties.limits.minStorageBufferOffsetAlignment);
@@ -802,7 +802,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false;
}
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto found = m_wireVertexFormatSupport.find(format);
if (found != m_wireVertexFormatSupport.end()) return found->second;
VkFormatProperties properties{};
@@ -216,8 +216,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
},
.Destroy = [](auto res) { if (auto* m = WireManager("destroy")) m->DestroyWireBuffer(res); },
// Split mapping remains T2: the client owns its map and pushes exact
// modified ranges. A server pointer is never donated across the wire.
.MapPersistent = [](MG_Pipe::MGPipeHandle, Uint64, const void*) -> void* { return nullptr; },
// modified ranges. A server pointer is never donated across the wire. Monolith's record
// arm (P13 W4) is one process: there the store IS handed over, as the legacy arm's
// AcquirePersistentMap always did.
.MapPersistent = [](MG_Pipe::MGPipeHandle res, Uint64 size, const void* seed) -> void* {
if (!MG_Config::RecordArmAliasesFrontend()) return nullptr;
auto* m = WireManager("map_persistent");
return m != nullptr ? m->DonateWireBuffer(res, size, seed) : nullptr;
},
.UnmapPersistent = [](MG_Pipe::MGPipeHandle) {},
#if defined(__ANDROID__)
// P11 B2 (T0): the one door a server pointer is still never donated through stays shut;
@@ -236,7 +242,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
#if MOBILEGL_BUILD_DISAGGREGATED
void VkBufferManager::RegisterWireResourceOps() {
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
MG_Pipe::MGPipeSetResourceOps(&g_vulkanWireResourceOps);
}
}
@@ -771,6 +777,29 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} // namespace
#endif
// P13 W4: THE IN-PROCESS DONATION. Every wire store is already host-visible, coherent and
// persistently mapped (RespecifyWireBuffer), so in one process the client can write straight
// into it, exactly as the legacy arm's persistent maps did. The store is then the client's
// pages - T0's shape without an AHardwareBuffer - so it takes T0's flag: a readback is "done, no
// bytes" (the client reads its own pointer once this side's GPU is through with it) and a
// respecify retires the store behind the serial. It is seeded from the client's shadow, after
// any GPU use of the store has drained, because the shadow is what the application sees.
void* VkBufferManager::DonateWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 size, const void* seedBytes) {
auto* resource = FindWireBuffer(res);
if (resource == nullptr || size == 0 || size != resource->size) return nullptr;
void* mapped = resource->buffer.GetMappedData();
if (mapped == nullptr) return nullptr;
if (resource->imported) return mapped; // already donated: the same pointer
if (!WaitForWireBufferHostAccess(*resource)) return nullptr;
if (seedBytes != nullptr) std::memcpy(mapped, seedBytes, static_cast<SizeT>(size));
resource->imported = true;
resource->gpuWritesPending = false;
resource->stagedCoverage.clear();
MG_Remote::Server::StagedShadowStore::CoverageAdd(resource->stagedCoverage, 0, static_cast<SizeT>(size));
++m_sliceEpochCounter;
return mapped;
}
Bool VkBufferManager::ImportWireBuffer(MG_Pipe::MGPipeHandle res, void* ahb, Uint64 size) {
#if defined(__ANDROID__)
auto* resource = FindWireBuffer(res);
@@ -1371,7 +1400,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
#if MOBILEGL_BUILD_DISAGGREGATED
(MG_Config::Transport != MG_Config::TransportMode::Monolith ? VkBufferUsageFlags{VK_BUFFER_USAGE_TRANSFER_DST_BIT} : 0u) |
(MG_Config::DataArmIsRecord() ? VkBufferUsageFlags{VK_BUFFER_USAGE_TRANSFER_DST_BIT} : 0u) |
#endif
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.memoryUsage = m_initInfo.transientMemoryUsage,
@@ -153,6 +153,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// applies unchanged. The T2 store it replaces goes through DeferWireRelease. False = not
// imported (the T2 store is untouched and the client is DECLINED).
Bool ImportWireBuffer(MG_Pipe::MGPipeHandle res, void* ahb, Uint64 size);
// P13 W4: monolith's record arm donates the wire store itself as the client's persistent
// map (see the definition). Null when the store cannot be handed over.
void* DonateWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 size, const void* seedBytes);
// The POST self-test of the sustained-lock pattern on this device (see the definition).
static Bool SelfTestWireImport(char* why, Uint64 whyBytes);
#endif
@@ -2487,9 +2487,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
auto& store = MG_Remote::Server::ServerStagedTexture();
// This arm is only ever entered under an active transport, where the store copies.
MOBILEGL_ASSERT(store.CopiesIntoServerStorage(),
"UploadPendingWireLevels under a non-copying staged store: the wire arm is transport-only");
// This arm runs under a transport (the store copies) or on monolith's record arm (it
// aliases the frontend's level shadows); a store that holds neither has nothing to upload.
MOBILEGL_ASSERT(store.Holds(),
"UploadPendingWireLevels under a staged store that holds no levels: the wire arm needs one");
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(handle);
const TextureInternalFormat internalFormat = static_cast<TextureInternalFormat>(record.Desc.InternalFormat);
const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(internalFormat);
@@ -3220,7 +3220,7 @@ void main() {
inline ProgramFactory::CompileOptionFlags GetShaderTransformFlags(VkSurfaceTransformFlagBitsKHR preTransform) {
ProgramFactory::CompileOptionFlags flags = ProgramFactory::CompileOptionBit::PositionZRemap;
#if MOBILEGL_BUILD_DISAGGREGATED
const Bool wire = MG_Config::Transport != MG_Config::TransportMode::Monolith;
const Bool wire = MG_Config::DataArmIsRecord();
const auto* wireFbo = wire ? MG_Pipe::MGPipeApplier().DrawFramebuffer() : nullptr;
const auto currentDrawFBO = wire ? SharedPtr<MG_State::GLState::FramebufferObject>{} :
MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
@@ -5632,7 +5632,7 @@ void main() {
// image is a packed depth-stencil texture attached through only one half.
{
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer();
if (fbo && !fbo->IsDefault) {
if (fbo->Depth.Kind == MG_Pipe::kMGPipeSurfaceKindNone) depthTestEnabled = false;
@@ -5866,7 +5866,7 @@ void main() {
"GetOrCreatePipeline: colorAttachmentCount=%u exceeds payload capacity",
payload.colorAttachmentCount);
#if MOBILEGL_BUILD_DISAGGREGATED
const auto* wireFbo = MG_Config::Transport != MG_Config::TransportMode::Monolith ?
const auto* wireFbo = MG_Config::DataArmIsRecord() ?
MG_Pipe::MGPipeApplier().DrawFramebuffer() : nullptr;
const auto drawFboBinding = wireFbo ? SharedPtr<MG_State::GLState::FramebufferObject>{} :
MG_Pipe::gPipeInputs.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
@@ -6551,7 +6551,7 @@ void main() {
MOBILEGL_ASSERT(
[&] {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer();
return isDefaultFbo == (fbo != nullptr && fbo->IsDefault);
}
@@ -6984,7 +6984,7 @@ void main() {
// the same loading frame grows it the same way there.
SplitOversizedRecording();
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
if (!RewindWireDescriptorSetsIfDue()) return false;
// The wire route returns before the monolith branch's draw-gated
// sweep. Dead wire texture/renderbuffer records otherwise live
@@ -7619,7 +7619,7 @@ void main() {
// Before the wire branch, for SetupDraw's reason.
SplitOversizedRecording();
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
if (!RewindWireDescriptorSetsIfDue()) return;
m_textureManager->CollectGarbage();
DispatchWireCompute(numGroupsX, numGroupsY, numGroupsZ);
@@ -7685,7 +7685,7 @@ void main() {
SplitOversizedRecording();
#if MOBILEGL_BUILD_DISAGGREGATED
// P8-SV: the wire arm's own indirect dispatch, DispatchCompute's shape one call over.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
if (!RewindWireDescriptorSetsIfDue()) return;
m_textureManager->CollectGarbage();
DispatchWireComputeIndirect(indirect);
@@ -7797,7 +7797,7 @@ void main() {
#if MOBILEGL_BUILD_DISAGGREGATED
// ALL_COMMANDS does not include HOST. The barrier's
// HOST_WRITE access must have a matching source stage.
MG_Config::Transport != MG_Config::TransportMode::Monolith
MG_Config::DataArmIsRecord()
? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT
: VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
#else
@@ -7963,7 +7963,7 @@ void main() {
void VulkanRenderer::Clear(GLbitfield mask) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer();
if (!fbo) MagmaWireFatal("clear-framebuffer-record");
ClearAttachmentPayload payload{};
@@ -8367,7 +8367,7 @@ void main() {
void VulkanRenderer::QueueClearBufferPayload(GLenum buffer, GLint drawbuffer,
const ClearAttachmentPayload& clearPayload) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer();
if (!fbo) MagmaWireFatal("clear-buffer-framebuffer-record");
ClearWireFramebuffer(*fbo,clearPayload,buffer == GL_COLOR ? drawbuffer : -1);
@@ -9506,7 +9506,7 @@ void main() {
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
BlitWireFramebuffers(srcX0,srcY0,srcX1,srcY1,dstX0,dstY0,dstX1,dstY1,mask,filter);
return;
}
@@ -10148,7 +10148,7 @@ void main() {
void VulkanRenderer::CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
GLint x, GLint y, GLsizei width, GLsizei height) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
CopyWireFramebufferToTexture(target,level,xoffset,yoffset,x,y,width,height);
return;
}
@@ -10467,7 +10467,7 @@ void main() {
Uint CopyImageEndpointName(const CopyImageEndpoint& endpoint) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
if (MG_Config::DataArmIsRecord())
return endpoint.IsRenderbuffer() ? endpoint.RenderbufferHandle.Slot : endpoint.TextureHandle.Slot;
#endif
if (endpoint.IsRenderbuffer()) return endpoint.Renderbuffer->GetExternalIndex();
@@ -10481,7 +10481,7 @@ void main() {
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
#if MOBILEGL_BUILD_DISAGGREGATED
const Bool wire = MG_Config::Transport != MG_Config::TransportMode::Monolith;
const Bool wire = MG_Config::DataArmIsRecord();
MG_Pipe::MGPipeHandle dstStorageHandle = dstEndpoint.TextureHandle;
if (wire && HasPendingRecordedWork() && !FlushPendingCommands()) {
if (LatchWireDeviceLoss("copy-image-flush")) return;
@@ -10529,7 +10529,7 @@ void main() {
// render-pass manager, which is where a renderbuffer's VkImage lives.
const auto resolveImage = [this](const CopyImageEndpoint& endpoint, CopyImageVkImage& out) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
const Bool renderbuffer = endpoint.IsRenderbuffer();
// The same handle-keyed allocation used by wire FBO clear/draw/read.
// The legacy render-pass manager owns a different, frontend-keyed map.
@@ -10787,7 +10787,7 @@ void main() {
const auto materializeClear = [this, &frame](const CopyImageEndpoint& endpoint) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return true;
if (MG_Config::DataArmIsRecord()) return true;
#endif
if (endpoint.IsRenderbuffer()) {
return MaterializePendingClearForRenderbuffer(frame.commandBuffer, endpoint.Renderbuffer);
@@ -11067,7 +11067,7 @@ void main() {
void VulkanRenderer::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
void* pixels) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
ReadWirePixels(x,y,width,height,format,type,pixels);
return;
}
@@ -11769,7 +11769,7 @@ void main() {
#endif
const auto& pixelPackBufferObject =
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::Transport != MG_Config::TransportMode::Monolith ? wireReplyHasNoPackBuffer :
MG_Config::DataArmIsRecord() ? wireReplyHasNoPackBuffer :
#endif
MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject();
const auto packParams = MG_Pipe::gPipeInputs.GetPixelStoreParameters(false);
@@ -12047,7 +12047,7 @@ void main() {
void VulkanRenderer::GenerateMipmap(GLenum target) {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
GenerateWireMipmap();
return;
}
@@ -12156,7 +12156,7 @@ void main() {
// server's staged shadow (keyed by the synced TextureResource above) and the client's
// level storage is never written; in monolith the client-object path runs unchanged.
const Bool allocatedMipmapStorage =
MG_Config::Transport != MG_Config::TransportMode::Monolith
MG_Config::DataArmIsRecord()
? EnsureGenerateMipmapShadowAllocated(*resource, baseMipLevel, uploadTargets)
: EnsureGenerateMipmapStorageAllocated(*mipmapTexture, baseMipLevel, endMipLevel);
#else
@@ -12452,7 +12452,7 @@ void main() {
return false;
}
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
if (MG_Config::DataArmIsRecord())
return BeginWireXfbCaptureForDraw(frame);
#endif
const auto& program = MG_Pipe::gPipeInputs.GetTransformFeedbackProgram();
@@ -13474,7 +13474,7 @@ void main() {
vertexRange.instanceCount = 1;
// MONOLITH ONLY: with a transport the frontend VAO is not this side's to read at all
// (the client owns those bytes and stages them itself), and this runs on the apply thread.
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) {
if (!MG_Config::DataArmIsRecord()) {
const auto& currentVAO = MG_Pipe::gPipeInputs.GetBoundVertexArray();
Bool clientArray = false;
if (currentVAO) {
@@ -13865,7 +13865,7 @@ void main() {
}
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
// Texture uploads submit on this queue with their own fences. The
// renderer watermark alone cannot prove images/views are idle.
if (m_textureManager && !m_textureManager->WireUploadsAreIdle()) return false;
@@ -15344,7 +15344,7 @@ void main() {
// CreateInstance requests Vulkan 1.1 on these platforms. A 1.2+ GPU
// does not promote descriptor indexing into that application's core
// API; the wire path must enable VK_EXT_descriptor_indexing instead.
(MG_Config::Transport == MG_Config::TransportMode::Monolith) &&
(!MG_Config::DataArmIsRecord()) &&
#endif
m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_2;
const Bool descriptorIndexingExtension =
@@ -15367,7 +15367,7 @@ void main() {
m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_1 &&
IsExtensionSupported(availableExtensions, VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME) &&
IsExtensionSupported(availableExtensions, VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
const Bool wireDepthResolveEnabled = MG_Config::Transport != MG_Config::TransportMode::Monolith &&
const Bool wireDepthResolveEnabled = MG_Config::DataArmIsRecord() &&
getPhysicalDeviceProperties2 && (wireDepthResolveCore || wireDepthResolveExtensions);
m_wireCreateRenderPass2 = nullptr;
m_wireDepthResolveModes = m_wireStencilResolveModes = 0;
@@ -15391,7 +15391,7 @@ void main() {
// write the stencil aspect without it. Enabled on the same terms as the resolve
// extensions above - wire arms only - and the arm declines stencil where it is absent
// rather than producing an undefined aspect.
m_wireShaderStencilExport = MG_Config::Transport != MG_Config::TransportMode::Monolith &&
m_wireShaderStencilExport = MG_Config::DataArmIsRecord() &&
IsExtensionSupported(availableExtensions, VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
if (m_wireShaderStencilExport)
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions,
@@ -15408,7 +15408,7 @@ void main() {
m_wireGetAhbProperties = nullptr;
m_sharedImageSyncFd = false;
#if defined(__ANDROID__)
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
if (MG_Config::DataArmIsRecord() &&
m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_1 &&
IsExtensionSupported(availableExtensions, VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME) &&
IsExtensionSupported(availableExtensions, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME)) {
@@ -15963,7 +15963,7 @@ void main() {
IsExtensionSupported(availableExtensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) ? VK_EXT_ROBUSTNESS_2_EXTENSION_NAME
: IsExtensionSupported(availableExtensions, VK_KHR_ROBUSTNESS_2_EXTENSION_NAME) ? VK_KHR_ROBUSTNESS_2_EXTENSION_NAME
: nullptr;
if (MG_Config::Transport != MG_Config::TransportMode::Monolith && robustness2Name != nullptr &&
if (MG_Config::DataArmIsRecord() && robustness2Name != nullptr &&
getPhysicalDeviceFeatures2 != nullptr) {
VkPhysicalDeviceFeatures2 featureQuery{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
featureQuery.pNext = &robustness2Features;
@@ -15977,7 +15977,7 @@ void main() {
m_wireNullDescriptor = true;
}
}
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
if (MG_Config::DataArmIsRecord()) {
MGLOG_I("Magma wire: robustness2 nullDescriptor %s (%s)", m_wireNullDescriptor ? "enabled" : "unavailable",
robustness2Name != nullptr ? robustness2Name : "no robustness2 extension");
}
@@ -16216,7 +16216,7 @@ void main() {
// red when the real recording, readback or tally stops detecting it too.
void VulkanRenderer::ArmWireDepthResolveOrder() {
m_wirePreferShaderDepthResolve = false;
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
if (!MG_Config::DataArmIsRecord()) return;
// ResolveWireDepthStencil's own test for the render-pass arm, less its per-format half
// (the probe asks that per format): without it the shader pass is the only arm.
const Bool renderPassArmAvailable = !MagmaWireForcedShaderDepthResolve() && m_wireCreateRenderPass2 &&
+5 -1
View File
@@ -1251,8 +1251,12 @@ mgl_itest_join_environment(MGL_ITEST_VULKAN_HANDLE_HANDLES_ENVIRONMENT
"MOBILEGL_TRANSPORT=monolith"
"MOBILEGL_BACKEND_TYPE=DirectVulkan" "MGITEST_HANDLE_ARM=handles" ${MGL_ITEST_HANDLES_ARM_KNOBS}
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_VULKAN_ENV})
# P13 W4: THE ABA CONTROL'S SUBJECT IS THE FRONTEND ARM. The knob defeats the identity half of
# Magma's frontend-arm keys (MagmaPipeIdentityTables); monolith's record arm keys every object on
# the applier's {slot, gen}, which the knob does not - and must not - reach. So the lane pins the
# frontend arm (MOBILEGL_PIPE_DATA_ARM, a W4-W6 knob) and goes with that arm in W6.
mgl_itest_join_environment(MGL_ITEST_VULKAN_HANDLE_ABA_HANDLES_ENVIRONMENT
"MOBILEGL_TRANSPORT=monolith"
"MOBILEGL_TRANSPORT=monolith" "MOBILEGL_PIPE_DATA_ARM=frontend"
"MOBILEGL_BACKEND_TYPE=DirectVulkan" "MGITEST_HANDLE_ARM=aba"
${MGL_ITEST_HANDLES_ARM_KNOBS} ${MGL_ITEST_ABA_ARM_KNOBS}
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_VULKAN_ENV})
@@ -109,7 +109,8 @@ void main() { o_color = texture(uTex, vec2(0.5)); }
constexpr std::uint64_t kWireDescriptorSetBudget = 2048;
std::uint64_t BurstSetsHeldAtOnce() {
const std::uint64_t draws = static_cast<std::uint64_t>(kDraws);
return PeekSplitRuntime().transportResolved ? std::min(draws, kWireDescriptorSetBudget) : draws;
// The wire budget binds the RECORD arm, which monolith takes too from P13 W4.
return PeekSplitRuntime().dataArmIsRecord ? std::min(draws, kWireDescriptorSetBudget) : draws;
}
// Pools a geometric policy adds for n sets from an empty flavour: 64, 128, ... up to the cap,
@@ -73,6 +73,7 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x];
if (!Ready()) return;
m_magma = Gl().BackendName() == "DirectVulkan";
m_transport = PeekSplitRuntime().transportName;
m_recordArm = PeekSplitRuntime().dataArmIsRecord;
m_mark = CompileCompute(kMarkSource);
ASSERT_NE(m_mark, 0u);
m_copy = CompileCompute(kCopySource);
@@ -192,7 +193,10 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x];
// Magma's server in THIS process: the inproc arm. Spawn and tcp servers are other
// processes, and the monolith arm never takes the wire path at all.
bool CountersReadable() const { return m_magma && m_transport == "inproc"; }
// The counters are this process's: inproc, and (P13 W4) monolith on the record arm.
bool CountersReadable() const {
return m_magma && (m_transport == "inproc" || (m_transport == "monolith" && m_recordArm));
}
WireIndirectCounters Delta() const {
WireIndirectCounters now{};
@@ -209,6 +213,7 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x];
bool m_magma = false;
std::string m_transport;
bool m_recordArm = false;
WireIndirectCounters m_before{};
GLuint m_mark = 0, m_copy = 0, m_groupCounts = 0, m_source = 0;
std::vector<GLuint> m_marks;
@@ -266,7 +271,7 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x];
// dependency here (P8-D recorded that half for dev), and Espryt's is its driver's.
TEST_F(WireIndirectDispatchScenario, ShaderWrittenGroupCountsWithoutAnApplicationBarrierAreOrdered) {
if (!Ready() || IsSkipped()) return;
if (!m_magma || m_transport == "monolith") {
if (!m_magma || !m_recordArm) {
GTEST_SKIP() << "the implicit INDIRECT_COMMAND_READ barrier is the Magma wire arm's (P8-D, P8-SV)";
}
Parameters({1, 1, 1});
@@ -90,6 +90,7 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x];
if (!Ready()) return;
m_magma = Gl().BackendName() == "DirectVulkan";
m_transport = PeekSplitRuntime().transportName;
m_recordArm = PeekSplitRuntime().dataArmIsRecord;
std::string error;
m_program = CompileProgram(kVertexSource, kFragmentSource, &error);
ASSERT_NE(m_program, 0u) << error;
@@ -203,7 +204,10 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x];
// Magma's server in THIS process: the inproc arm. Spawn and tcp servers are other
// processes, and the monolith arm never takes the wire path at all.
bool CountersReadable() const { return m_magma && m_transport == "inproc"; }
// The counters are this process's: inproc, and (P13 W4) monolith on the record arm.
bool CountersReadable() const {
return m_magma && (m_transport == "inproc" || (m_transport == "monolith" && m_recordArm));
}
WireIndirectCounters Delta() const {
WireIndirectCounters now{};
@@ -215,6 +219,7 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x];
bool m_magma = false;
std::string m_transport;
bool m_recordArm = false;
WireIndirectCounters m_before{};
GLuint m_program = 0, m_copy = 0, m_vao = 0, m_vbo = 0, m_ebo = 0;
GLuint m_indirect = 0, m_parameter = 0, m_source = 0;
@@ -304,7 +309,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" && !PeekSplitRuntime().dataArmIsRecord) {
if (!m_magma && !m_recordArm) {
GTEST_SKIP() << "Espryt's monolith arm reads the count word from the frontend shadow without "
"a sync (OQ15; recorded for P13, ID-P8-13)";
}
@@ -316,7 +321,7 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x];
glMultiDrawElementsIndirectCount(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr, 0, 3, 0);
});
ExpectStrips(image, {true, false, false, false}, "glMultiDrawElementsIndirectCount, shader-written count 1");
if (m_transport == "monolith") {
if (!m_recordArm) {
// DirectVulkan.cpp's monolith MultiDrawArraysIndirectCount reads this word from the
// frontend shadow without SyncGpuWrites: recorded for dev (ID-P8-3), not this lane's.
return;
@@ -344,7 +349,7 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x];
// arm records no dependency here (recorded for dev), and Espryt's is its driver's.
TEST_F(WireIndirectDrawScenario, ShaderWrittenCommandsWithoutAnApplicationBarrierAreOrdered) {
if (!Ready() || IsSkipped()) return;
if (!m_magma || m_transport == "monolith") {
if (!m_magma || !m_recordArm) {
GTEST_SKIP() << "the implicit INDIRECT_COMMAND_READ barrier is the Magma wire arm's (P8-D)";
}
Fill(m_indirect, GL_DRAW_INDIRECT_BUFFER, std::vector<ArraysCommand>{Arrays(3), Arrays(2)});
+8 -6
View File
@@ -1435,7 +1435,7 @@ namespace MobileGL::MG_Pipe {
// existing backend hooks authoritative when present, and use this fallback otherwise.
void AdoptTextureWithoutBackendHook(const MGPipeResourceRecord& stored, const MGPSubData& upload,
const void* bytes, const MGPSubRegion* regions) {
if (MG_Config::Transport == MG_Config::TransportMode::Monolith || !bytes || upload.Blob.Size == 0) return;
if (!MG_Config::DataArmIsRecord() || !bytes || upload.Blob.Size == 0) return;
auto& store = MG_Remote::Server::ServerStagedTexture();
const auto& desc = stored.Desc;
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(upload.Res);
@@ -1445,18 +1445,20 @@ namespace MobileGL::MG_Pipe {
// is adopted with the spelling that replaces the level; every other record names a RUN
// of the level by its own box (fix A2: a level too large to stage whole crosses as
// slabs, one record each, and the first of them is at the level's first byte).
// A client record's run: on monolith's record arm it is the frontend level shadow, which
// an aliasing store keeps by address (P13 W4).
if (upload.RegionCount == 0) {
store.Adopt(key, target, upload.Level, extent, bytes,
static_cast<SizeT>(upload.Blob.Size));
static_cast<SizeT>(upload.Blob.Size), /*frontendShadow=*/true);
return;
}
store.AdoptRun(key, target, upload.Level, extent,
MG_Remote::Server::StagedTextureRunImageOffset(upload, regions), bytes,
static_cast<SizeT>(upload.Blob.Size));
static_cast<SizeT>(upload.Blob.Size), /*frontendShadow=*/true);
}
void DefineTextureWithoutBackendHook(const MGPResourceDesc& desc, const MGPRespecifiedLevel* level) {
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
if (!MG_Config::DataArmIsRecord()) return;
auto& store = MG_Remote::Server::ServerStagedTexture();
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(desc.Resource);
const auto define = [&](Uint16 target, Uint16 mip, const IntVec3& extent) {
@@ -1910,7 +1912,7 @@ namespace MobileGL::MG_Pipe {
void MGPipeApplierReleaseObjectRecords() {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
if (MG_Config::DataArmIsRecord() &&
(g_resourceOps == nullptr || g_resourceOps->TextureDestroy == nullptr)) {
MG_Remote::Server::ServerStagedTexture().DropAll();
}
@@ -2686,7 +2688,7 @@ namespace MobileGL::MG_Pipe {
if (static_cast<MGPipeKind>(handle.Kind) == MGPipeKind::Texture) {
if (g_resourceOps != nullptr && g_resourceOps->TextureDestroy != nullptr)
g_resourceOps->TextureDestroy(handle.Handle);
else if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
else if (MG_Config::DataArmIsRecord())
MG_Remote::Server::ServerStagedTexture().Drop(
MG_Remote::Server::StagedTextureStore::KeyForHandle(handle.Handle));
}