From 2af4728e70db03ee104a93c274b08edfc8fc86f5 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 6 Oct 2026 08:14:54 -0400 Subject: [PATCH] [Feat] (P13 W4b/W4c): Espryt monolith takes the record arm for textures, readbacks, framebuffers, units, images, programs and XFB --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 90 +++--- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 88 ++++-- MobileGL/MG_Backend/DirectGLES/Managers.h | 2 +- MobileGL/MG_Backend/DirectGLES/Utils.cpp | 2 +- .../DirectGLES/WireTextureReadback.inc | 2 +- .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 2 +- .../MG_Impl/GLImpl/Texture/GL_Texture.cpp | 10 +- MobileGL/MG_Impl/Pipe/PipeFill.cpp | 2 +- MobileGL/MG_Impl/Pipe/TextureEmit.h | 3 + .../GenerateMipmapServerScenario.cpp | 2 + MobileGL/MG_Pipe/PipeApply.cpp | 10 + MobileGL/MG_Pipe/PipeApply.h | 19 ++ MobileGL/MG_Pipe/PipeRoute.cpp | 17 ++ MobileGL/MG_Remote/Client/EmitTables.cpp | 274 ++++++++++++++---- MobileGL/MG_Remote/Client/QueryEmit.inc | 2 +- .../MG_Remote/Client/TextureReadbackEmit.inc | 2 +- MobileGL/MG_Remote/Server/PipeApplier.cpp | 1 + .../MG_Remote/Server/StagedTextureStore.h | 98 +++++-- MobileGL/MG_Test/Buffer/BufferTest.cpp | 26 +- MobileGL/MG_Test/ScopedMonolithFrontendArm.h | 49 ++++ MobileGL/MG_Test/Texture/TextureTest.cpp | 4 + MobileGL/MG_Test/Texture/TextureViewTest.cpp | 2 + 22 files changed, 520 insertions(+), 187 deletions(-) create mode 100644 MobileGL/MG_Test/ScopedMonolithFrontendArm.h diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 6a0ea539..54b86f75 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -194,7 +194,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // push-monolith build keeps its frontend arms token for token, and a mask that never // armed framebuffers has no record to decline from. static Bool FramebufferRecordArmIsMandatory() { - return MG_Config::Transport != MG_Config::TransportMode::Monolith && FramebufferSubsystemEnabled(); + return MG_Config::DataArmIsRecord() && FramebufferSubsystemEnabled(); } [[noreturn]] static void RefuseFramebufferBindingSlotRead() { MG_Pipe::MGPipeInputPoisonFatalForVerb(MG_Pipe::MGPipeInputField::GetFramebufferBindingSlot, @@ -235,7 +235,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // MGPipeResourceOps, and this is neither. SamplerImpl::BackendSamplerObject* GetRawDepthFetchSampler() { #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { static UniquePtr native; static Uint generation = 0; if (generation != g_backendContextGeneration) { @@ -1585,7 +1585,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Int g_activeXfbRole = -1; RoleXfbState& ActiveXfbState() { - const Int role = MG_Config::Transport != MG_Config::TransportMode::Monolith; + const Int role = MG_Config::DataArmIsRecord(); auto& state = g_roleXfbState[role]; if (state.NativeGeneration != g_backendContextGeneration) { state = {}; @@ -1602,7 +1602,7 @@ namespace MobileGL::MG_Backend::DirectGLES { XfbObjectState& CurrentXfb() { #if MOBILEGL_BUILD_DISAGGREGATED auto& state = ActiveXfbState(); - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const Uint64 lifetime = MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId; if (state.Current != lifetime || state.Cached == nullptr || state.NeedsBind) BindTransformFeedback(0); // identity is the applier's lifetime, not this argument @@ -1684,7 +1684,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (g_GLESFuncs.glMapBufferRange != nullptr && g_GLESFuncs.glUnmapBuffer != nullptr) { for (const auto& target : targets) { #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport == MG_Config::TransportMode::Monolith) + if (!MG_Config::DataArmIsRecord()) #endif if (!target.buffer) continue; #if MOBILEGL_BUILD_DISAGGREGATED @@ -1692,7 +1692,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // memory (rule E), so the persistence question is the server // resource's and the captured bytes go back as a writeback EVENT - // WritebackFromBackend from the apply thread is the R1/R2 shape. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const MG_Pipe::MGPipeHandle res = target.handle; auto* resource = BufferImpl::FindBufferResourceForHandle(res); if (resource == nullptr) continue; @@ -1788,7 +1788,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // direct binding (which produces a wrong layout, but is what happened before). Bool BindScatterCaptureBuffer(SizeT packedStride, SizeT capacityVertices) { #if MOBILEGL_BUILD_DISAGGREGATED - const Bool server = MG_Config::Transport != MG_Config::TransportMode::Monolith; + const Bool server = MG_Config::DataArmIsRecord(); auto& g_scatterBufferId = server ? CurrentXfb().serverScatterBuffer : ActiveXfbState().ScatterBuffer; auto& g_scatterBufferSize = server ? CurrentXfb().serverScatterSize : ActiveXfbState().ScatterSize; #endif @@ -1829,7 +1829,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // application had put there - which is the whole point of the feature. void ScatterCapturedRecords(XfbObjectState& xfb) { #if MOBILEGL_BUILD_DISAGGREGATED - auto& g_scatterBufferId = MG_Config::Transport != MG_Config::TransportMode::Monolith + auto& g_scatterBufferId = MG_Config::DataArmIsRecord() ? xfb.serverScatterBuffer : ActiveXfbState().ScatterBuffer; #endif #if MOBILEGL_BUILD_DISAGGREGATED @@ -1874,7 +1874,7 @@ namespace MobileGL::MG_Backend::DirectGLES { for (SizeT targetIndex = 0; targetIndex < xfb.targets.size(); ++targetIndex) { const auto& target = xfb.targets[targetIndex]; #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport == MG_Config::TransportMode::Monolith) + if (!MG_Config::DataArmIsRecord()) #endif if (!target.buffer) continue; // By BUFFER index, not by position in the compacted list - see XfbCaptureTarget. @@ -1890,7 +1890,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // poke into client memory. MG_Pipe::MGPipeHandle splitRes = MG_Pipe::kMGPipeNullHandle; BufferImpl::GLESBufferResource* splitResource = nullptr; - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { splitRes = target.handle; splitResource = BufferImpl::FindBufferResourceForHandle(splitRes); if (splitResource == nullptr) { @@ -1948,7 +1948,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { if (MG_Pipe::gMGPipeCallbacks.OnBufferWriteback != nullptr) { // Sliced for the reason the readback path above is sliced: the bytes // are INLINE in a SEG_EVENT record and the ring refuses a record wider @@ -2029,7 +2029,7 @@ namespace MobileGL::MG_Backend::DirectGLES { #if MOBILEGL_BUILD_DISAGGREGATED XfbProgramSource program; const MG_Pipe::MGPStreamOutputBegin* span = nullptr; - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const auto& state = MG_Pipe::MGPipeApplier(); const auto found = state.StreamOutputSpans.find(state.BoundStreamOutputLifetimeId); if (found != state.StreamOutputSpans.end()) { @@ -2063,7 +2063,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const SizeT bufferCount = program->GetTransformFeedbackBufferCount(); for (SizeT i = 0; i < bufferCount; ++i) { #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { if (!span || i >= 4) continue; const auto& range = span->Targets[i]; if (MG_Pipe::MGPipeHandleIsNull(range.Res) || range.Size == 0) continue; @@ -2090,7 +2090,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport == MG_Config::TransportMode::Monolith) + if (!MG_Config::DataArmIsRecord()) #endif BufferImpl::SyncTransformFeedbackBindingPoints(bufferCount); @@ -2236,7 +2236,7 @@ namespace MobileGL::MG_Backend::DirectGLES { auto& g_currentXfbState = roleState.Cached; auto& g_xfbObjects = roleState.Objects; auto& g_currentXfbName = roleState.Current; - const Bool server = MG_Config::Transport != MG_Config::TransportMode::Monolith; + const Bool server = MG_Config::DataArmIsRecord(); const Uint64 key = server ? MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId : name; roleState.NeedsBind = false; #endif @@ -2281,7 +2281,7 @@ namespace MobileGL::MG_Backend::DirectGLES { auto& g_currentXfbState = roleState.Cached; auto& g_xfbObjects = roleState.Objects; auto& g_currentXfbName = roleState.Current; - const Bool server = MG_Config::Transport != MG_Config::TransportMode::Monolith; + const Bool server = MG_Config::DataArmIsRecord(); const Uint64 key = server ? MG_Pipe::MGPipeApplier().VerbDeleteStreamOutputLifetimeId : name; // A generated but never-bound name has no native object or lifetime. if (server && key == 0) return; @@ -3209,7 +3209,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // right. Ruling 19 makes the missing window unrepresentable at the sink; this is // the same statement one level down, for the backend that would have papered over // it. Under monolith the fall-back is correct and stays. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && maxTouchedUnit >= 0) { + if (MG_Config::DataArmIsRecord() && maxTouchedUnit >= 0) { // The windows are the PEER's records, so in a served session this ends THAT // session (the in-process display server serves every client from one // process; an abort here took the compositor down with the offender). The @@ -3348,7 +3348,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // texture-resource record, and half of each is not an arm. The push-monolith build keeps // the frontend walk token for token - there is no server to answer from there. static Bool UnitTexturesByHandle() { - return MG_Config::Transport != MG_Config::TransportMode::Monolith && SamplerSubsystemEnabled() && + return MG_Config::DataArmIsRecord() && SamplerSubsystemEnabled() && TextureResourceSubsystemEnabled(); } @@ -4250,7 +4250,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // reach of an apply thread entirely. void MarkWritableImageBufferTexturesGpuWritten() { #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { // P8-C: the SERVER's twin of the buffer behind each writable buffer image, for // this side's CPU readers (SplitHostBytesForCpuRead). Every read is a record: // the unit set SyncImageTextureBinding(view) tracked, the applier's image view, @@ -4365,7 +4365,7 @@ namespace MobileGL::MG_Backend::DirectGLES { static_cast(g_imageUnitHighWaterMark)), static_cast(unitCount)); #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { for (Uint32 unit = 0; unit < end; ++unit) { if (unit >= st.BoundShaderImages.size()) break; // The union can include a slot no set_shader_images @@ -4392,7 +4392,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // g_imageUnitHighWaterMark is raised only by the funnel above, so if no set has // ever arrived no unit has ever been given an image, and re-binding 0 on units that // never held one is the no-op the comment above already proved. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return; + if (MG_Config::DataArmIsRecord()) return; #endif for (Uint unit = 0; unit < unitCount; ++unit) { SyncImageTextureBinding(unit); @@ -4463,7 +4463,7 @@ namespace MobileGL::MG_Backend::DirectGLES { void SyncImageTextureBindingsForDraw(const DrawTextureSyncKeys& keys) { if (g_imageUnitHighWaterMark == 0) return; #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const auto& st = MG_Pipe::MGPipeApplier(); if (g_imageSweepValid && g_imageSweepShaderImagesSerial == st.ShaderImagesSerial && g_imageSweepTextureShutterSerial == st.TextureShutterSerial && @@ -4616,7 +4616,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // identity (rule E) and the check does not run - the record was resolved from the // applier's OWN bound handle, so it is this binding's by construction (ID-19). // Monolith keeps the corroboration verbatim. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return true; + if (MG_Config::DataArmIsRecord()) return true; #endif return record.Fbo == g_backendFramebufferObjects.HandleOf(bound.get()); } @@ -6933,7 +6933,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // the window rule (§5.3) makes the case it exists for UNREPRESENTABLE: a unit whose // BoundSamplerStates[u] is null while the frontend held a sampler is a MISSING RECORD, // not a unit to be served from the client's object. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"BindSamplerStates.Count\"} - unit %d holds a " "frontend sampler object that the applied bind_sampler_states window does not " "describe. Minting a twin for it would probe the client's allocator from the " @@ -7043,7 +7043,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // P5e (tx2), CONTRACT-P5E §5.3: the same refusal CurrentUnitBindingsEpoch makes - // the frontend walk below reads GetTextureUnitObject per unit, which an active // transport may not do for a record whose client has moved on. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"BindSamplerStates.Count\"} - a draw " "touches units 0..%d and no bind_sampler_states has ever been applied " "while the sampler subsystem bit is set; the pre-handle sampler walk " @@ -10995,7 +10995,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // function's binding-slot read at the bottom are gone from the split path), and the // sink is told the pair was consumed - a backend that reaches here without consuming // it has no named arm and the verb declines there. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { auto& applierState = MG_Pipe::MGPipeApplier(); const MG_Pipe::MGPipeHandle readFbo = applierState.VerbBlitReadFbo; const MG_Pipe::MGPipeHandle drawFbo = applierState.VerbBlitDrawFbo; @@ -11160,7 +11160,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // P5c (hd, CONTRACT-P5C §3.4): under an active transport the destination is the handle // the copy_framebuffer_to_texture record carried; the unit binding slot and the client // allocator are never read (T2/T4). - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const MG_Pipe::MGPipeHandle dstHandle = MG_Pipe::MGPipeApplier().VerbCopyTexDst; auto* backendTextureSlot = TextureImpl::g_backendTextureObjects.GetOrCreateByHandle(dstHandle); if (backendTextureSlot == nullptr) { @@ -12350,7 +12350,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // the handle the copy_framebuffer_to_texture record carried (MGPCopyFromFramebuffer:: // Dst, the verb stash) and its format is the applier descriptor's - the client's // texture-unit binding slot and the client slot allocator are never read (T2/T4). - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const MG_Pipe::MGPipeHandle dstHandle = MG_Pipe::MGPipeApplier().VerbCopyTexDst; const auto* dstRecord = PipeTextureRecordForHandle(dstHandle); // P5e (tx2): SYNCED, not merely looked up. The destination of a copy is not sampled @@ -12474,7 +12474,7 @@ namespace MobileGL::MG_Backend::DirectGLES { #if MOBILEGL_BUILD_DISAGGREGATED // P5c (hd, CONTRACT-P5C §3.4): see CopyTexImage2D - the record's Dst handle, never the // client's unit binding slot or the client allocator (T2/T4). - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const MG_Pipe::MGPipeHandle dstHandle = MG_Pipe::MGPipeApplier().VerbCopyTexDst; // P5e (tx2): synced by handle - see CopyTexImage2D's note. A copy destination is not // in the sampler-view window, so nothing else on this path would build its twin. @@ -12691,7 +12691,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // whose level the driver cannot read has no answer here. if (!MG_Pipe::MGPipeHandleIsNull(record.Desc.ViewOf)) { declined = "a view has no staged level of its own"; - } else if (!store.CopiesIntoServerStorage()) { + } else if (!store.Holds()) { declined = "this arm keeps no staged store"; } else if (driverWrittenBefore) { declined = "the driver has written the texture since its bytes crossed"; @@ -12914,7 +12914,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // so that the unit it reads is the unit the call was made on. With the unit read gone the // wait has nothing left to protect, which is what lets PipeCalls.def's WaitClass column // for GenerateMipmap move to kWaitNone. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const auto mipRes = MG_Pipe::MGPipeApplier().VerbMipRes; // P8-SE: whether the staged store still stands for the texture is read BEFORE the note // below sets the mark, or the CPU arm's store route could never answer at all. @@ -13092,7 +13092,7 @@ namespace MobileGL::MG_Backend::DirectGLES { GLint z, GLESCopyImageEndpoint& out) { if (endpoint.IsRenderbuffer()) { #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { if (!PipeRenderbufferRecordForHandle(endpoint.RenderbufferHandle)) return false; auto* slot = RenderbufferImpl::g_backendRenderbufferObjects.GetOrCreateByHandle(endpoint.RenderbufferHandle); if (!slot) return false; @@ -13122,7 +13122,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // reaches here - but the assertion that says so is compiled out of a release build, and // SyncTextureObjectToBackend would register a null state object. #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { out.texture = TextureImpl::SyncTextureToBackendByHandle(endpoint.TextureHandle); } else #endif @@ -13148,7 +13148,7 @@ namespace MobileGL::MG_Backend::DirectGLES { static TextureInternalFormat GetCopyImageEndpointFormat(const CopyImageEndpoint& endpoint) { #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const auto* record = endpoint.IsRenderbuffer() ? PipeRenderbufferRecordForHandle(endpoint.RenderbufferHandle) : PipeTextureRecordForHandle(endpoint.TextureHandle); @@ -13207,7 +13207,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // and the arm is the TRANSPORT and not the build - build-split runs its unit and // integration-gpu lanes under MOBILEGL_TRANSPORT=monolith, where this mirror is on an // ordinary correct path and must still run. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return; + if (MG_Config::DataArmIsRecord()) return; #endif // P4a (D-M) named this site `copy-image-shadow-mirror` with MGPipeUnmigratedEmulation. // P8-SE removed the call: the return above means no transport reaches it, and on a @@ -13306,7 +13306,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // renderbuffer, a cube or 1D-array endpoint, a source the driver has written, a level whose // bytes did not all cross) it is marked driver-written instead, so it declines rather than // answering with stale bytes. Nothing moves to the client (ROADMAP P8's original row). - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && handleArmDestination) { + if (MG_Config::DataArmIsRecord() && handleArmDestination) { const Bool followed = !srcEndpoint.IsRenderbuffer() && !MG_Pipe::MGPipeHandleIsNull(srcEndpoint.TextureHandle) && TextureImpl::FollowCopyImageInStagedStore( @@ -13391,7 +13391,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MOBILEGL_ASSERT(false, "glCopyImageSubData failed after frontend validation accepted the request."); #if MOBILEGL_BUILD_DISAGGREGATED // P8-E: the store already made the move the driver just refused. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && handleArmDestination) { + if (MG_Config::DataArmIsRecord() && handleArmDestination) { TextureImpl::NoteDriverSideTextureWriteByHandle(dstEndpoint.TextureHandle); } #endif @@ -13419,7 +13419,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // the bind from the record; reading the frontend image binding here is the // GetImageTextureBinding row that rule F forbids, and `bind_shader_image` is not a // barriered row. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { TextureImpl::NoteImageUnitBoundWithoutReadingTheFrontend(unit, texture != 0); return; } @@ -13466,7 +13466,7 @@ namespace MobileGL::MG_Backend::DirectGLES { void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) { if (!storageBlockName) return; #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const auto handle = MG_Pipe::MGPipeApplier().VerbStorageBlockProgram; auto* slot = PrgramImpl::g_backendProgramObjects.FindByHandle(handle); if (slot && *slot && (*slot)->GetBackendProgramId()) { @@ -13687,7 +13687,7 @@ namespace MobileGL::MG_Backend::DirectGLES { const SizeT packedSize = dstOffset + static_cast(height - 1) * dstRowStride + rowBytes; const auto& pixelPackBufferObject = #if MOBILEGL_BUILD_DISAGGREGATED - MG_Config::Transport != MG_Config::TransportMode::Monolith + MG_Config::DataArmIsRecord() ? SplitReadbackPackBuffer() : #endif MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); @@ -14708,7 +14708,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } const auto& pixelPackBufferObject = #if MOBILEGL_BUILD_DISAGGREGATED - MG_Config::Transport != MG_Config::TransportMode::Monolith + MG_Config::DataArmIsRecord() ? SplitReadbackPackBuffer() : #endif MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); @@ -14961,7 +14961,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // (P8-SE dropped copy-image-shadow-mirror, a site no transport reached). Only this call // site is retired here, and only because its own premise did not hold (texture-remint-pull // went the same way in P9 W2, and P8-B the two split generate-mipmap names, likewise). - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { if (shadow == nullptr) { MGLOG_E_ONCE("GetTexImage: no level shadow on this side for target=0x%x level=%d, and under an " "active transport there is no client shadow to fall back to - the read is " @@ -14979,7 +14979,7 @@ namespace MobileGL::MG_Backend::DirectGLES { MG_Pipe::MGPipeUnmigratedEmulation("get-tex-image-shadow"); const auto& pixelPackBufferObject = #if MOBILEGL_BUILD_DISAGGREGATED - MG_Config::Transport != MG_Config::TransportMode::Monolith + MG_Config::DataArmIsRecord() ? SplitReadbackPackBuffer() : #endif MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); @@ -15302,7 +15302,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // capturing subsequent client-memory readbacks into it). auto& pixelPackBufferObject = #if MOBILEGL_BUILD_DISAGGREGATED - MG_Config::Transport != MG_Config::TransportMode::Monolith + MG_Config::DataArmIsRecord() ? SplitReadbackPackBuffer() : #endif MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); @@ -15749,7 +15749,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // on every exit path, so a later readback can never land in a stale PBO. auto& pixelPackBufferObject = #if MOBILEGL_BUILD_DISAGGREGATED - MG_Config::Transport != MG_Config::TransportMode::Monolith + MG_Config::DataArmIsRecord() ? SplitReadbackPackBuffer() : #endif MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index c72f7061..19f70fb2 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -2247,6 +2247,46 @@ namespace MobileGL::MG_Backend::DirectGLES { resource->syncedChangeSerial = ResourceSerialOf(res); return; } + // P13 W4b: A SERVER-ORIGINATED WRITE INTO AN ALIASING STORE (monolith's record arm) + // lands in the GL store, never in the base: the bytes are the sink's scratch, and the + // base is the frontend's shadow, which this side does not write. Queued uploads land + // first, so they cannot later overwrite these bytes from the stale shadow; the store + // is then marked GPU-written, which sends the frontend (its own pack-buffer mark) and + // every CPU reader here (SplitHostBytesForCpuRead) back to the GL store for them. + if (MG_Pipe::MGPipeApplyingServerOriginatedWrite() && MG_Config::RecordArmAliasesFrontend() && + bytes != nullptr && size != 0) { + // The store must exist to take them: a pack buffer is often never drawn from, so + // nothing else has built it. Only a store that does not exist yet is ensured: the + // applier has already counted THIS write into the record's serial, so the ensure + // of a built store would read it as an untracked mutation and re-upload the + // shadow over the runs this landing wrote before it. + if (CanTouchGLNow() && (resource->id == 0 || !resource->storageInitialized || + resource->pendingRespecify || + resource->contextGeneration != g_bufferContextGeneration)) { + resource = EnsureBufferResourceForHandle(nullptr, res); + } + if (resource != nullptr && CanTouchGLNow() && resource->id != 0 && resource->storageInitialized && + !resource->persistentMapped && !resource->pendingRespecify && + resource->contextGeneration == g_bufferContextGeneration && + StorageMatchesSize(*resource, ResourceWidthOf(res))) { + FlushPendingRangesFrom(*resource, resource->hostBytes, ResourceWidthOf(res)); + BindBufferId(TempBufferTarget, resource->id); + g_GLESFuncs.glBufferSubData(TempBufferTarget, static_cast(offset), + static_cast(size), bytes); + // The store holds everything the record now says (the applier bumped its + // serial for this write): without the stamp the next ensure would see a + // newer serial and re-upload the shadow over the bytes that just landed. + resource->syncedChangeSerial = ResourceSerialOf(res); + } else if (resource != nullptr) { + const std::lock_guard lock(resource->pendingMutex); + auto& write = resource->pendingResidentWrites.emplace_back(); + write.offset = offset; + const auto* source = static_cast(bytes); + write.bytes.assign(source, source + size); + } + if (resource != nullptr) resource->serverGpuWritten = true; + return; + } #endif // M-2: UNDER pendingMutex, because this line runs BEFORE the CanTouchGLNow() // test below - i.e. on the arm D-A2 deliberately keeps reachable off the render @@ -3093,7 +3133,9 @@ namespace MobileGL::MG_Backend::DirectGLES { // the level image when the record carries a piece of the level rather than the // whole of it (fix A2), which is why it is read here now. auto& store = MG_Remote::Server::ServerStagedTexture(); - if (!store.CopiesIntoServerStorage()) return; + // P13 W4b: an aliasing store (monolith's record arm) adopts too - it records where + // the run sits in the frontend's level shadow instead of copying it. + if (!store.Holds()) return; // The applier's gate has already faulted every shape that reaches here without // bytes, and under split the codec declared the run's length (Blob.Size) - // TextureEmit.h's "the bytes this record declares ARE the level shadow". @@ -3132,19 +3174,21 @@ namespace MobileGL::MG_Backend::DirectGLES { // RegionCount == 0 IS the whole-level spelling - "the run is the level shadow", // beginning at the level's first byte - so it is adopted with the spelling that // replaces the level; every other record names a RUN of the level by its own box. + // A client record's run: under monolith's record arm it IS the frontend level shadow, + // the one source an aliasing store keeps by address. if (record.RegionCount == 0) { - store.Adopt(key, uploadTarget, record.Level, extent, bytes, runBytes); + store.Adopt(key, uploadTarget, record.Level, extent, bytes, runBytes, /*frontendShadow=*/true); return; } store.AdoptRun(key, uploadTarget, record.Level, extent, MG_Remote::Server::StagedTextureRunImageOffset(record, regions), bytes, - runBytes); + runBytes, /*frontendShadow=*/true); } void Ops_H_TextureRespecify(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc, const MG_Pipe::MGPRespecifiedLevel* level) { auto& store = MG_Remote::Server::ServerStagedTexture(); - if (!store.CopiesIntoServerStorage()) return; + if (!store.Holds()) return; const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForHandle(res); if (level != nullptr) { // ONE glTexImage*D redefined one level: its exact extent crossed with the @@ -5044,7 +5088,7 @@ namespace MobileGL::MG_Backend::DirectGLES { "it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables the pre-handle g_fboSynced* arm"); } #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && + if (MG_Config::DataArmIsRecord() && verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { StopOnTransportWithoutRecordArm( "kMGPipeSubsystemFramebuffer (bit 9)", @@ -5089,7 +5133,7 @@ namespace MobileGL::MG_Backend::DirectGLES { "cheap-gate trio"); } #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && + if (MG_Config::DataArmIsRecord() && verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { StopOnTransportWithoutRecordArm( "kMGPipeSubsystemTextureResources (bit 10)", @@ -5125,7 +5169,7 @@ namespace MobileGL::MG_Backend::DirectGLES { "arm and SamplerPassMemo's raw-pointer rows"); } #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && + if (MG_Config::DataArmIsRecord() && verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { StopOnTransportWithoutRecordArm( "kMGPipeSubsystemSamplers (bit 11)", @@ -5154,7 +5198,7 @@ namespace MobileGL::MG_Backend::DirectGLES { "MOBILEGL_PIPE_LEGACY_MEMOS=0 disables g_programTwinLookupMemo"); } #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && + if (MG_Config::DataArmIsRecord() && verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { StopOnTransportWithoutRecordArm( "kMGPipeSubsystemPrograms (bit 12)", @@ -6501,7 +6545,9 @@ namespace MobileGL::MG_Backend::DirectGLES { return; } #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + // P13 W4b (defect h): monolith's record arm takes the body too; its old arm only + // cleared m_isInitialized and replayed the frontend shadow at the next sync. + if (MG_Config::DataArmIsRecord()) { const auto format = static_cast(record.Desc.InternalFormat); // ES image binding needs immutable storage, but an already // immutable core-format allocation needs no replacement. Keep @@ -7806,7 +7852,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // re-open ID-107's hole somewhere else. #if MOBILEGL_BUILD_DISAGGREGATED #define MGB_TEXTURE_RECORD_ARM_SELECTED() \ - (MG_Config::Transport != MG_Config::TransportMode::Monolith && TextureResourceSubsystemEnabled()) + (MG_Config::DataArmIsRecord() && TextureResourceSubsystemEnabled()) #else #define MGB_TEXTURE_RECORD_ARM_SELECTED() (false) #endif @@ -8092,7 +8138,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // monolith). MGB_STAGED_TEXTURE_LIVE is the runtime discriminator; pushedStorage/pushedRes // are the function's own locals. All are #undef'd with the rest after the function. #define MGB_STAGED_TEXTURE_LIVE \ - (pushedStorage != nullptr && MG_Remote::Server::ServerStagedTexture().CopiesIntoServerStorage()) + (pushedStorage != nullptr && MG_Remote::Server::ServerStagedTexture().Holds()) #define MGB_TEXTURE_TARGET(obj) \ (MGB_STAGED_TEXTURE_LIVE ? BufferImpl::StagedTextureTargetForPipeTarget(pushedStorage->Desc.Target) \ : (obj)->GetTarget()) @@ -8450,12 +8496,12 @@ namespace MobileGL::MG_Backend::DirectGLES { // Ruling 1's arm: the push-monolith build keeps the frontend test below token for // token, because there the two answers are the same answer and the verify comparator // needs the frontend arm. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && pushedStorage != nullptr && + if (MG_Config::DataArmIsRecord() && pushedStorage != nullptr && !MG_Pipe::MGPipeHandleIsNull(pushedStorage->Desc.ViewOf)) { SyncTextureViewToBackendByRecord(pushedRes, *pushedStorage, stateTextureObject); return; } - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && pushedStorage != nullptr) { + if (MG_Config::DataArmIsRecord() && pushedStorage != nullptr) { // Not a view: fall through to the storage body with the frontend test skipped. } else #endif @@ -8510,7 +8556,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // P5e (tx2), ruling 1's arm: under a transport the re-dirty half is a NAMED // refusal at its entry (§5.2) instead of a walk of the client's level shadows; // the push-monolith build keeps the frontend transition exactly as it is. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { RequireImageBindableStorageByHandle(pushedRes, *pushedStorage); } else #endif @@ -11238,7 +11284,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // because tx2's record arm inside SyncMipmapsToBackend is itself selected by // `Transport != Monolith` and cannot answer here. #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { backendTextureObject->SyncMipmapsToBackendByHandle(surface.Res); // From here on every draw, clear and blit into this framebuffer can write the // texture behind the staged shadow's back (the frontend arm's note, by handle). @@ -11327,7 +11373,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // under a transport there is no application on this side to report it to. On the // push-monolith arm there is, so the monolith arm keeps the object form. #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { backendRenderbufferObject->SyncToBackendByHandle(surface.Res); } else #endif @@ -11379,7 +11425,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // the storage holds whatever the draw wrote there, so the readback has to overwrite it. Bool IsAlphaWidenedFallbackReadAttachment() { #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer(); return record && IsAlphaWidenedColorSurface(record->ReadSurface); } @@ -11393,7 +11439,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool IsFixedPointFallbackReadAttachment() { #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer(); return record && record->ReadSurface.Kind != MG_Pipe::kMGPipeSurfaceKindNone && IsSnormFormat(static_cast(record->ReadSurface.InternalFormat)); @@ -11566,7 +11612,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // (m_pushedSyncHandle), never the client allocator's - see SyncToBackend. const MG_Pipe::MGPipeHandle fbo = #if MOBILEGL_BUILD_DISAGGREGATED - MG_Config::Transport != MG_Config::TransportMode::Monolith + MG_Config::DataArmIsRecord() ? m_pushedSyncHandle : #endif @@ -11744,7 +11790,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // record, which the null-record refusal below names. const MG_Pipe::MGPipeHandle fbo = #if MOBILEGL_BUILD_DISAGGREGATED - MG_Config::Transport != MG_Config::TransportMode::Monolith + MG_Config::DataArmIsRecord() ? m_pushedSyncHandle : #endif @@ -13057,7 +13103,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // it - MGPImageView::InternalFormat is that same GLenum, copied by the client // out of the unit's own binding. A unit the applier has never been told about // answers 0, which is what an unbound unit answers on the frontend arm too. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { const auto& st = MG_Pipe::MGPipeApplier(); if (static_cast(unit) >= st.BoundShaderImages.size()) return 0; return static_cast(st.BoundShaderImages[static_cast(unit)].InternalFormat); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index dcffc32b..892f717c 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -794,7 +794,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // ProgramSubsystemEnabled() already latches, so this is one load and one test, and a latch // here would only hide which half answered. inline Bool ProgramHandleArm() { - return MG_Config::Transport != MG_Config::TransportMode::Monolith && ProgramSubsystemEnabled(); + return MG_Config::DataArmIsRecord() && ProgramSubsystemEnabled(); } // ---- P4a: what the twins read INSTEAD of the frontend object ---- diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index 8d866b29..ca043741 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -2363,7 +2363,7 @@ namespace MobileGL::MG_Backend::DirectGLES { GLsizei sliceCount, void* pixels, Bool applyPackImageParams, FillRow&& fillRow) { const auto& pixelPackBufferObject = #if MOBILEGL_BUILD_DISAGGREGATED - MG_Config::Transport != MG_Config::TransportMode::Monolith + MG_Config::DataArmIsRecord() ? SplitReadbackPackBuffer() : #endif MG_Pipe::gPipeInputs.GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); diff --git a/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc b/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc index 7564cf69..cff4f160 100644 --- a/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc +++ b/MobileGL/MG_Backend/DirectGLES/WireTextureReadback.inc @@ -361,7 +361,7 @@ const SizeT texels = static_cast(std::max(extent.x(), 0)) * static_cast(std::max(extent.y(), 0)) * static_cast(std::max(extent.z(), 0)); const char* declined = nullptr; - if (!store.CopiesIntoServerStorage()) { + if (!store.Holds()) { declined = "this arm keeps no staged store"; } else if (store.IsDriverWritten(key)) { declined = "the driver has written the texture since its bytes crossed"; diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 232d80fb..2f433775 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -1344,7 +1344,7 @@ namespace MobileGL::MG_Impl::GLImpl { // BeginXfbCaptureForDraw does mark the capture targets - where the fence at the // caller still runs, so the readback it emits is pure new work on the monolith // path and integration-gpu cannot see it. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) buffer->SyncGpuWrites(); + if (MG_Config::DataArmIsRecord()) buffer->SyncGpuWrites(); #endif const Range1D range = bindingPoint.GetRange(); const Uint8* mapped = buffer->MappedData(); diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index 41411e60..74d53248 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -50,7 +50,7 @@ // read as it was: its ReadPixels maps the driver PBO back into the shadow inside the call, and the // pull build compiles this to nothing (G1). #if MOBILEGL_BUILD_DISAGGREGATED -#define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) do { if (MG_Config::Transport != MG_Config::TransportMode::Monolith) (buffer)->SyncGpuWrites(); } while (0) +#define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) do { if (MG_Config::DataArmIsRecord()) (buffer)->SyncGpuWrites(); } while (0) #else #define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) ((void)0) #endif @@ -6122,7 +6122,7 @@ namespace MobileGL::MG_Impl::GLImpl { viewObject->SetSamples(storageOwner->GetSamples()); viewObject->SetFixedSampleLocations(storageOwner->HasFixedSampleLocations()); #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) + if (MG_Config::DataArmIsRecord()) MG_Pipe::MGPipeEmitSamplerViewCreate(*viewObject); #endif } @@ -6587,7 +6587,7 @@ namespace MobileGL::MG_Impl::GLImpl { TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels, const char* caller) { #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { MGP_FILL(GetTextureImage); MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type, bufSize, pixels); @@ -6616,7 +6616,7 @@ namespace MobileGL::MG_Impl::GLImpl { auto uploadTarget = GetPrimaryUploadTarget(textureObject); #if MOBILEGL_BUILD_DISAGGREGATED // Unlike glGetTexImage's face target, this DSA call reads all six faces. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && + if (MG_Config::DataArmIsRecord() && textureObject->GetTarget() == TextureTarget::TextureCubeMap) uploadTarget = TextureUploadTarget::Unknown; #endif @@ -6881,7 +6881,7 @@ namespace MobileGL::MG_Impl::GLImpl { void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) { if (!GetTexImage_State(target, level, format, type, pixels)) return; #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { GetTexImage_Backend(target, level, format, type, pixels); return; } diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.cpp b/MobileGL/MG_Impl/Pipe/PipeFill.cpp index 3481c385..3bb2d586 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.cpp +++ b/MobileGL/MG_Impl/Pipe/PipeFill.cpp @@ -2261,7 +2261,7 @@ namespace MobileGL::MG_Pipe { Bool PipeInputs::HasOpenTransformFeedbackSpan(Uint64 lifetimeId) const { #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { + if (MG_Config::DataArmIsRecord()) { return lifetimeId != 0 && MGPipeApplier().StreamOutputSpans.count(lifetimeId) != 0; } #endif diff --git a/MobileGL/MG_Impl/Pipe/TextureEmit.h b/MobileGL/MG_Impl/Pipe/TextureEmit.h index b5c31ec7..a1d8b785 100644 --- a/MobileGL/MG_Impl/Pipe/TextureEmit.h +++ b/MobileGL/MG_Impl/Pipe/TextureEmit.h @@ -1585,6 +1585,9 @@ namespace MobileGL::MG_Pipe { Bool pieceAccepted = true; if constexpr (MGPipeTextureRecordsReachTheApplier()) { dispatched = true; + // P13 W4b: monolith's record arm keeps a staged-texture store that ALIASES the + // level shadow, and it needs the run's length the way a split server does. + if (MG_Config::RecordArmAliasesFrontend()) m_lastSubData.Blob.Size = runBytes; // `runBytes` closes CONTRACT-P5 table 1 row 7's open half. The record still // declares Blob.Size 0 on the monolith arm - where the applier reads the // companion pointer and the destination box bounds the write - but under split diff --git a/MobileGL/MG_IntegrationTest/Scenarios/GenerateMipmapServerScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/GenerateMipmapServerScenario.cpp index be3b29bc..e9a33724 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/GenerateMipmapServerScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/GenerateMipmapServerScenario.cpp @@ -172,6 +172,8 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; } // knob runs it anyway, which is how each defect is shown red and how dev's fix is checked. bool MonolithDefect(bool onEspryt, bool onMagma) const { if (m_wire || std::getenv(kRunMonolithDefects) != nullptr) return false; + // P13 W4b: a monolith on the record arm runs the wire's generation, so it meets none. + if (PeekSplitRuntime().dataArmIsRecord) return false; return m_espryt ? onEspryt : onMagma; } static constexpr const char* kM1 = diff --git a/MobileGL/MG_Pipe/PipeApply.cpp b/MobileGL/MG_Pipe/PipeApply.cpp index ebe76afd..71fbf0fa 100644 --- a/MobileGL/MG_Pipe/PipeApply.cpp +++ b/MobileGL/MG_Pipe/PipeApply.cpp @@ -3946,6 +3946,16 @@ namespace MobileGL::MG_Pipe { // build would turn 1117 integration cases red for running code that is correct in the role // they run it in. None of the five is on P5's reduced path, so under a real transport this // costs nothing and catches a lot. + namespace { + thread_local Bool tl_serverOriginatedWrite = false; + } // namespace + + Bool MGPipeApplyingServerOriginatedWrite() { return tl_serverOriginatedWrite; } + MGPipeServerOriginatedWriteScope::MGPipeServerOriginatedWriteScope() : m_previous(tl_serverOriginatedWrite) { + tl_serverOriginatedWrite = true; + } + MGPipeServerOriginatedWriteScope::~MGPipeServerOriginatedWriteScope() { tl_serverOriginatedWrite = m_previous; } + void MGPipeUnmigratedEmulation(const char* name) { #if MOBILEGL_BUILD_DISAGGREGATED if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { diff --git a/MobileGL/MG_Pipe/PipeApply.h b/MobileGL/MG_Pipe/PipeApply.h index cb8437e4..4df68128 100644 --- a/MobileGL/MG_Pipe/PipeApply.h +++ b/MobileGL/MG_Pipe/PipeApply.h @@ -1440,6 +1440,25 @@ namespace MobileGL::MG_Pipe { // name a TEXTURE target: Buffer, Renderbuffer and anything at or above // MGPipeResourceTarget::Count are Fatal{ProtocolCorruption} rather than an upload onto // whatever object holds that slot in the texture slot space. + // P13 W4b: A WRITE THE SERVER PRODUCED, going through the ordinary resource_subdata gate - the + // pack-buffer landing of a readback (PipeApplier.cpp LandReadbackInBuffer) is the one producer. + // Its bytes are the server's own scratch and live for the call only, so a store that ALIASES + // the frontend's shadow (monolith's record arm) must not take them as its base: it lands them + // in the GL store and marks it GPU-written instead, which is how the frontend and every CPU + // reader learn of them (SyncGpuWrites / SplitHostBytesForCpuRead). A copying store takes them + // as it always has. Thread-local; scoped by the producer. + Bool MGPipeApplyingServerOriginatedWrite(); + class MGPipeServerOriginatedWriteScope { + public: + MGPipeServerOriginatedWriteScope(); + ~MGPipeServerOriginatedWriteScope(); + MGPipeServerOriginatedWriteScope(const MGPipeServerOriginatedWriteScope&) = delete; + MGPipeServerOriginatedWriteScope& operator=(const MGPipeServerOriginatedWriteScope&) = delete; + + private: + Bool m_previous; + }; + Bool MGPipeApplyResourceSubData(const MGPSubData& record, const void* bytes, const MGPSubRegion* regions = nullptr); // buffer_subdata_resident: same shape; `bytes` is the application's staging store and is diff --git a/MobileGL/MG_Pipe/PipeRoute.cpp b/MobileGL/MG_Pipe/PipeRoute.cpp index 6cea73c5..87f7734f 100644 --- a/MobileGL/MG_Pipe/PipeRoute.cpp +++ b/MobileGL/MG_Pipe/PipeRoute.cpp @@ -32,6 +32,8 @@ #include +#include +#include #include #include @@ -308,6 +310,21 @@ namespace MobileGL::MG_Pipe { // the monolith twin reads GetLinkedShaderStages() off the frontend object it is // handed. Named and discarded rather than left out of the signature, so the two // arms stay one row. +#if MOBILEGL_BUILD_DISAGGREGATED + // P13 W4c: MONOLITH'S RECORD ARM READS THE RECORD'S ARCHIVE (ProgramHandleArm), so it + // gets one: the link's snapshot, taken here, once per link - the same thing the wire + // decodes into the record, without the codec. A copy rather than a borrow, because the + // record is a snapshot of THIS link and the frontend object's artefacts are replaced + // by the next one while the record may still be in use. + if (MG_Config::RecordArmAliasesFrontend() && link != nullptr && spirv != nullptr) { + auto archive = MakeShared(); + archive->Link = *link; + archive->Spirv = *spirv; + if (linkedStages != nullptr) archive->LinkedStages.assign(linkedStages, linkedStages + linkedStageCount); + MGPipeApplyCreateShaderState(*desc, link, spirv, std::move(archive)); + return; + } +#endif (void)linkedStages; (void)linkedStageCount; // P5e (pg): NO ARCHIVE ON THIS ARM, and the null is the arm selection rather than a diff --git a/MobileGL/MG_Remote/Client/EmitTables.cpp b/MobileGL/MG_Remote/Client/EmitTables.cpp index 314ca2a9..716dc78f 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.cpp +++ b/MobileGL/MG_Remote/Client/EmitTables.cpp @@ -261,22 +261,46 @@ namespace MobileGL::MG_Remote::Client { 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); - } + // The answer of a reply row on the port: written straight into the caller's buffer, which + // is what the session's reply slot would have been copied into. + class MonolithReplySink final : public Wire::ReplySink { + public: + MonolithReplySink(void* out, Uint64 capacity) : m_out(out), m_capacity(capacity) {} + void PostReply(Uint64, Int32 status, const void* bytes, Uint64 size) override { + m_posted = true; + m_status = status; + m_size = size; + if (status == kStatusOk && bytes != nullptr && m_out != nullptr && size != 0) { + std::memcpy(m_out, bytes, static_cast(std::min(size, m_capacity))); + } + } + Bool Posted() const { return m_posted; } + Int32 Status() const { return m_status; } + Uint64 Size() const { return m_size; } - void ApplyOnMonolithPort(MG_Pipe::MGPWireOp op, const void* payload, const Wire::WireTail* tails, - Uint32 tailCount) { + private: + void* m_out; + Uint64 m_capacity; + Bool m_posted = false; + Int32 m_status = kStatusError; + Uint64 m_size = 0; + }; + + // The port's reply cap: one band answers any read a process can hold (PlanReadbackBands + // still cuts a row wider than this, as it would for a slot). + constexpr Uint32 kMonolithMaxReplyBytes = 0x7fffffffu; + + Bool ApplyOnMonolithPort(MG_Pipe::MGPWireOp op, const void* payload, const Wire::WireTail* tails, + Uint32 tailCount, Wire::ReplySink* replies) { 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; + if (sink.Backend() != backend) { + sink.SetBackend(backend); + sink.SetMaxReplyBytes(kMonolithMaxReplyBytes); + } + using Op = MG_Pipe::MGPWireOp; switch (op) { - case MG_Pipe::MGPWireOp::DrawVbo: { - row = "draw_vbo"; + case Op::DrawVbo: { const auto& info = *static_cast(payload); const auto* ranges = (tailCount > 0 && tails[0].Size != 0) ? static_cast(tails[0].Bytes) @@ -284,42 +308,132 @@ namespace MobileGL::MG_Remote::Client { const auto* indirect = ((info.Flags & MG_Pipe::kDrawIsIndirect) != 0 && tailCount > 1) ? static_cast(tails[1].Bytes) : nullptr; - applied = sink.OnDrawVbo(info, ranges, nullptr, indirect); - break; + return sink.OnDrawVbo(info, ranges, nullptr, indirect); + } + case Op::LaunchGrid: + return sink.OnLaunchGrid(*static_cast(payload)); + // ---- W4b: the texture family's verbs ---- + case Op::GenerateMipmap: + return sink.OnGenerateMipmap(*static_cast(payload)); + case Op::CopyFramebufferToTexture: + return sink.OnCopyFramebufferToTexture(*static_cast(payload)); + case Op::ResourceCopyRegion: + return sink.OnResourceCopyRegion(*static_cast(payload)); + // ---- W4b: the readbacks. The reply rows answer through `replies`, which writes the + // caller's buffer; the pack-buffer rows land in the buffer through the applier. + case Op::ReadPixels: + return sink.OnReadPixels(*static_cast(payload), 0, replies); + case Op::GetTextureImage: + return sink.OnGetTextureImage(*static_cast(payload), 0, replies); + case Op::ReadPixelsToBuffer: + return sink.OnReadPixelsToBuffer(*static_cast(payload)); + case Op::GetTextureImageToBuffer: + return sink.OnGetTextureImageToBuffer(*static_cast(payload)); + // ---- W4c: framebuffers, image units, storage-block bindings ---- + case Op::Clear: + return sink.OnClear(*static_cast(payload)); + case Op::Blit: + return sink.OnBlit(*static_cast(payload)); + case Op::BindShaderImage: + return sink.OnBindShaderImage(*static_cast(payload)); + // ---- W4a's XFB half: the capture span and its object, by lifetime id ---- + case Op::BeginStreamOutput: + return sink.OnBeginStreamOutput(*static_cast(payload)); + case Op::EndStreamOutput: + return sink.OnEndStreamOutput(*static_cast(payload)); + case Op::PauseStreamOutput: + return sink.OnPauseStreamOutput(*static_cast(payload)); + case Op::ResumeStreamOutput: + return sink.OnResumeStreamOutput(*static_cast(payload)); + case Op::BindStreamOutput: + return sink.OnBindStreamOutput(*static_cast(payload)); + case Op::DeleteStreamOutput: + return sink.OnDeleteStreamOutput(*static_cast(payload)); + case Op::SetStorageBlockBinding: { + // The name is the emitter's own NUL-terminated string (VerbChannel::StageBytes on + // the port names it by address), alive for this call. + const auto& record = *static_cast(payload); + const auto* name = reinterpret_cast(static_cast(record.Name.Offset)); + return sink.OnSetStorageBlockBinding(record, name); } - case MG_Pipe::MGPWireOp::LaunchGrid: - row = "launch_grid"; - applied = sink.OnLaunchGrid(*static_cast(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; + // WHERE A PORTED EMITTER'S RECORD GOES: the session's ring, or - on monolith's record arm - + // the in-process sink above. Spelled like the session (EmitAndWait / EmitAndWaitTails / + // MaxReplyBytes / ...) so an emitter reads the same either way. + class VerbChannel { + public: + explicit VerbChannel(ClientSession* session) : m_session(session) {} + + Uint64 EmitAndWait(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, + const void* varTail, Uint64 varTailBytes, void* replyOut, Uint64 replyBytes, + Int32* statusOut, Uint64* replySizeOut = nullptr) { + if (m_session != nullptr) { + return m_session->EmitAndWait(op, payload, payloadBytes, varTail, varTailBytes, replyOut, + replyBytes, statusOut, replySizeOut); + } + const Wire::WireTail tail{varTail, varTailBytes}; + Port(op, payload, &tail, varTail != nullptr ? 1u : 0u, replyOut, replyBytes, statusOut, replySizeOut); + return 0; } - 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; + Uint64 EmitAndWaitTails(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, + const Wire::WireTail* tails, Uint32 tailCount, void* replyOut, Uint64 replyBytes, + Int32* statusOut, Uint64* replySizeOut = nullptr) { + if (m_session != nullptr) { + return m_session->EmitAndWaitTails(op, payload, payloadBytes, tails, tailCount, replyOut, + replyBytes, statusOut, replySizeOut); + } + Port(op, payload, tails, tailCount, replyOut, replyBytes, statusOut, replySizeOut); + return 0; } - ApplyOnMonolithPort(op, payload, nullptr, 0); + Uint32 MaxReplyBytes() const { + return m_session != nullptr ? m_session->MaxReplyBytes() : kMonolithMaxReplyBytes; + } + void RequireReadPixelsReplyFits(Uint32 width, Uint32 height, Uint32 format, Uint32 type, + Uint64 bytes) const { + if (m_session != nullptr) m_session->RequireReadPixelsReplyFits(width, height, format, type, bytes); + } + // A blob the record names by reference: staged in SEG_STAGE under a session, its own + // address on the port (kMGHostSpanSegNone, the monolith spelling MGPipeHostBytes reads). + MG_Pipe::MGPBlobRef StageBytes(const void* bytes, Uint64 size) { + if (m_session != nullptr) return m_session->Encoder().StageBytes(bytes, size); + return MG_Pipe::MGPBlobRef{static_cast(reinterpret_cast(bytes)), size, + MG_Pipe::kMGHostSpanSegNone, 0}; + } + + private: + void Port(MG_Pipe::MGPWireOp op, const void* payload, const Wire::WireTail* tails, Uint32 tailCount, + void* replyOut, Uint64 replyBytes, Int32* statusOut, Uint64* replySizeOut) { + MonolithReplySink replies(replyOut, replyBytes); + const Bool applied = ApplyOnMonolithPort(op, payload, tails, tailCount, &replies); + // A sink that declined without answering is the decoder's DECLINED; an answer it + // posted is the answer, whatever the return. + const Int32 status = replies.Posted() + ? replies.Status() + : (applied ? Wire::ReplySink::kStatusOk : Wire::ReplySink::kStatusDeclined); + if (statusOut != nullptr) *statusOut = status; + if (replySizeOut != nullptr) *replySizeOut = replies.Posted() ? replies.Size() : 0; + if (!applied && replyOut == nullptr) { + MGLOG_E_ONCE("MGPipe: the monolith verb port's op %u was declined by the sink; nothing was " + "drawn, dispatched or changed", + static_cast(op)); + } + } + + ClientSession* m_session; + }; + + // The channel a ported emitter uses: the port when monolith's record arm installed it + // (only the ported slots reach an emitter there), the active session otherwise. + VerbChannel ChannelFor(const char* slot) { + if (g_monolithVerbPort) return VerbChannel(nullptr); + return VerbChannel(&RequireSession(slot)); } // ============================================================================= @@ -327,7 +441,7 @@ namespace MobileGL::MG_Remote::Client { // ============================================================================= void EmitClear(GLbitfield mask) { - ClientSession& session = RequireSession("Clear"); + VerbChannel session = ChannelFor("Clear"); BeforeReadOnlyVerb(); if (g_dropClearEmission) { @@ -359,7 +473,7 @@ namespace MobileGL::MG_Remote::Client { void EmitF1Clear(const char* slot, MG_Pipe::MGPipeHandle fbo, GLenum buffer, GLint drawbuffer, Uint8 valueClass, const void* value, GLfloat depth = 0, GLint stencil = 0) { - auto& session = RequireSession(slot); + VerbChannel session = ChannelFor(slot); BeforeReadOnlyVerb(); MG_Pipe::MGPClear record{}; record.Fbo = fbo; @@ -435,7 +549,7 @@ namespace MobileGL::MG_Remote::Client { } void EmitF1Copy(GLenum target, GLint level, GLenum format, GLint x, GLint y, GLsizei width, GLsizei height, GLint xoffset, GLint yoffset, Bool subImage) { - auto& session = RequireSession(subImage ? "CopyTexSubImage2D" : "CopyTexImage2D"); + VerbChannel session = ChannelFor(subImage ? "CopyTexSubImage2D" : "CopyTexImage2D"); BeforeReadOnlyVerb(); MG_Pipe::MGPCopyFromFramebuffer record{}; record.Dst = MG_Pipe::MGPipeTextureEmitterInstance().FindTexture(*F1BoundTexture(target)); @@ -458,7 +572,7 @@ namespace MobileGL::MG_Remote::Client { EmitF1Copy(target, level, 0, x, y, width, height, xoffset, yoffset, true); } void EmitGenerateMipmap(GLenum target) { - auto& session = RequireSession("GenerateMipmap"); + VerbChannel session = ChannelFor("GenerateMipmap"); BeforeReadOnlyVerb(); const auto& texture = F1BoundTexture(target); MG_Pipe::MGPMipPlan record{}; @@ -602,7 +716,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 = VerbSessionFor(slot); + VerbChannel session = ChannelFor(slot); PersistentMapTracker::Instance().PushDrawConsumers(); MG_Pipe::MGPipeDrainDeferredDestroys(); // Snapshot before marking THIS draw's potential GPU writes: resolving @@ -637,7 +751,8 @@ namespace MobileGL::MG_Remote::Client { tails[1] = {indirect, sizeof(*indirect)}; tailCount = 2; } - EmitVerbTails(session, MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), tails, tailCount); + session.EmitAndWaitTails(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), tails, + tailCount, nullptr, 0, nullptr); } // ---- the single-draw indexed family: DrawElements, DrawElementsBaseVertex, the two @@ -849,7 +964,7 @@ namespace MobileGL::MG_Remote::Client { void EmitBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { - ClientSession& session = RequireSession("BlitFramebuffer"); + VerbChannel session = ChannelFor("BlitFramebuffer"); BeforeReadOnlyVerb(); MG_Pipe::MGPBlit record{}; @@ -877,7 +992,7 @@ namespace MobileGL::MG_Remote::Client { const SharedPtr& draw, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { - ClientSession& session = RequireSession("BlitNamedFramebuffer"); + VerbChannel session = ChannelFor("BlitNamedFramebuffer"); // P5c (hd, CONTRACT-P5C §3.3): the scoped rebind of the client's own read/draw // binding slots is DELETED. It existed only to stage values for the server's // binding-slot read, and that read is gone: the sink resolves both framebuffers @@ -1035,7 +1150,7 @@ namespace MobileGL::MG_Remote::Client { void EmitReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) { - ClientSession& session = RequireSession("ReadPixels"); + VerbChannel session = ChannelFor("ReadPixels"); // A bound PACK buffer makes pixels an offset. The owned reply is // packed on this thread and only the requested rows are uploaded. @@ -1342,7 +1457,7 @@ namespace MobileGL::MG_Remote::Client { // same maps twice per dispatch and inflate b1's per-row counters. void EmitBindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format) { - ClientSession& session = RequireSession("BindImageTexture"); + VerbChannel session = ChannelFor("BindImageTexture"); MG_Pipe::MGPImageBind record{}; // The unit's binding is the frontend's and has just been written by the caller, so @@ -1375,7 +1490,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 = VerbSessionFor("DispatchCompute"); + VerbChannel session = ChannelFor("DispatchCompute"); PersistentMapTracker::Instance().PushDrawConsumers(); MarkGpuWritesForDispatch(); @@ -1389,11 +1504,12 @@ namespace MobileGL::MG_Remote::Client { record.IndirectBuffer = MG_Pipe::kMGPipeNullHandle; record.IndirectOffset = 0; record.IsIndirect = 0; - EmitVerbNoTail(session, MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record)); + session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0, + nullptr, 0, nullptr); } void EmitDispatchComputeIndirect(GLintptr indirect) { - ClientSession* session = VerbSessionFor("DispatchComputeIndirect"); + VerbChannel session = ChannelFor("DispatchComputeIndirect"); PersistentMapTracker::Instance().PushDrawConsumers(); MarkGpuWritesForDispatch(); @@ -1414,7 +1530,8 @@ namespace MobileGL::MG_Remote::Client { MG_Pipe::MGPipeKind::Buffer, bound->GetLifetimeId()); } } - EmitVerbNoTail(session, MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record)); + session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0, + nullptr, 0, nullptr); } // glMemoryBarrier / glMemoryBarrierByRegion. The bits cross VERBATIM: the frontend has @@ -1427,7 +1544,7 @@ namespace MobileGL::MG_Remote::Client { // No pre-verb hook: a barrier orders memory the GPU already holds. It starts no shader // and reads no mapped buffer. void EmitMemoryBarrier(GLbitfield barriers) { - ClientSession& session = RequireSession("MemoryBarrier"); + VerbChannel session = ChannelFor("MemoryBarrier"); MG_Pipe::MGPMemoryBarrier record{}; record.Bits = static_cast(barriers); record.ByRegion = 0; @@ -1436,7 +1553,7 @@ namespace MobileGL::MG_Remote::Client { } void EmitMemoryBarrierByRegion(GLbitfield barriers) { - ClientSession& session = RequireSession("MemoryBarrierByRegion"); + VerbChannel session = ChannelFor("MemoryBarrierByRegion"); MG_Pipe::MGPMemoryBarrier record{}; record.Bits = static_cast(barriers); record.ByRegion = 1; @@ -1451,7 +1568,7 @@ namespace MobileGL::MG_Remote::Client { const MG_Backend::CopyImageEndpoint& dst, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { - ClientSession& session = RequireSession("CopyImageSubData"); + VerbChannel session = ChannelFor("CopyImageSubData"); // The GL target selects the texture or renderbuffer handle namespace. MG_Pipe::MGPCopyRegion record{}; @@ -1493,7 +1610,7 @@ namespace MobileGL::MG_Remote::Client { // (BackendObject.h:216-221) - the name is the one coordinate all three agree on. void EmitShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) { - ClientSession& session = RequireSession("ShaderStorageBlockBinding"); + VerbChannel session = ChannelFor("ShaderStorageBlockBinding"); // The backend slot's own first line (DirectGLES.cpp:9201), kept here so a null name // never becomes a zero-size blob - which rule A forbids spelling at all. if (storageBlockName == nullptr) return; @@ -1520,7 +1637,7 @@ namespace MobileGL::MG_Remote::Client { // decoder re-terminates into a bounded local and refuses a run whose last byte is // not NUL, so the two sides agree on where the name ends. const Uint64 nameBytes = static_cast(std::strlen(storageBlockName)) + 1ull; - record.Name = session.Encoder().StageBytes(storageBlockName, nameBytes); + record.Name = session.StageBytes(storageBlockName, nameBytes); session.EmitAndWait(MG_Pipe::MGPWireOp::SetStorageBlockBinding, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); } @@ -1548,7 +1665,7 @@ namespace MobileGL::MG_Remote::Client { // this state changes at the capture-span boundary, so it rides Begin itself. void EmitBeginTransformFeedback(GLenum primitiveMode) { - ClientSession& session = RequireSession("BeginTransformFeedback"); + VerbChannel session = ChannelFor("BeginTransformFeedback"); BeforeReadOnlyVerb(); MG_Pipe::MGPStreamOutputBegin record{}; @@ -1578,7 +1695,7 @@ namespace MobileGL::MG_Remote::Client { } void EmitEndTransformFeedback() { - ClientSession& session = RequireSession("EndTransformFeedback"); + VerbChannel session = ChannelFor("EndTransformFeedback"); BeforeReadOnlyVerb(); MG_Pipe::MGPXfbAccounting record{}; @@ -1598,7 +1715,7 @@ namespace MobileGL::MG_Remote::Client { } void EmitPauseTransformFeedback() { - ClientSession& session = RequireSession("PauseTransformFeedback"); + VerbChannel session = ChannelFor("PauseTransformFeedback"); BeforeReadOnlyVerb(); // Reserved IS zero and the contract says so (MGPStreamOutputControl{Reserved = 0}). @@ -1611,7 +1728,7 @@ namespace MobileGL::MG_Remote::Client { } void EmitResumeTransformFeedback() { - ClientSession& session = RequireSession("ResumeTransformFeedback"); + VerbChannel session = ChannelFor("ResumeTransformFeedback"); BeforeReadOnlyVerb(); MG_Pipe::MGPStreamOutputControl record{}; @@ -1621,7 +1738,7 @@ namespace MobileGL::MG_Remote::Client { } void EmitBindTransformFeedback(GLuint name) { - ClientSession& session = RequireSession("BindTransformFeedback"); + VerbChannel session = ChannelFor("BindTransformFeedback"); BeforeReadOnlyVerb(); MG_Pipe::MGPStreamOutputBind record{}; @@ -1640,7 +1757,7 @@ namespace MobileGL::MG_Remote::Client { } void EmitPatchParameteri(GLenum pname, GLint value) { - ClientSession& session = RequireSession("PatchParameteri"); + VerbChannel session = ChannelFor("PatchParameteri"); BeforeReadOnlyVerb(); MG_Pipe::MGPPatchParameter record{}; @@ -2230,6 +2347,39 @@ namespace MobileGL::MG_Remote::Client { table.GL.MultiDrawElementsIndirectCount = &EmitMultiDrawElementsIndirectCount; table.GL.DispatchCompute = &EmitDispatchCompute; table.GL.DispatchComputeIndirect = &EmitDispatchComputeIndirect; + // P13 W4b: the texture family - its record arm resolves the texture from the verb's own + // handle (VerbMipRes, VerbCopyTexDst, the copy's two endpoints). + table.GL.GenerateMipmap = &EmitGenerateMipmap; + table.GL.CopyTexImage2D = &EmitCopyTexImage2D; + table.GL.CopyTexSubImage2D = &EmitCopyTexSubImage2D; + table.GL.CopyImageSubData = &EmitCopyImageSubData; + table.GL.ReadPixels = &EmitReadPixels; + table.GL.GetTexImage = &EmitGetTexImage; + table.GL.GetTextureImage = &EmitGetTextureImage; + // P13 W4c: framebuffer verbs name their framebuffers by handle (MGPClear::Fbo, the blit's + // pair), image binds and storage-block bindings carry their objects' handles. + table.GL.Clear = &EmitClear; + table.GL.ClearBufferfi = &EmitClearBufferfi; + table.GL.ClearBufferfv = &EmitClearBufferfv; + table.GL.ClearBufferiv = &EmitClearBufferiv; + table.GL.ClearBufferuiv = &EmitClearBufferuiv; + table.GL.ClearNamedFramebufferfi = &EmitClearNamedFramebufferfi; + table.GL.ClearNamedFramebufferfv = &EmitClearNamedFramebufferfv; + table.GL.ClearNamedFramebufferiv = &EmitClearNamedFramebufferiv; + table.GL.ClearNamedFramebufferuiv = &EmitClearNamedFramebufferuiv; + table.GL.BlitFramebuffer = &EmitBlitFramebuffer; + table.GL.BlitNamedFramebuffer = &EmitBlitNamedFramebuffer; + table.GL.BindImageTexture = &EmitBindImageTexture; + table.GL.ShaderStorageBlockBinding = &EmitShaderStorageBlockBinding; + // The XFB family: the span's capture program and targets ride Begin; the object is named + // by lifetime id. It follows the program family because Begin names the capture program + // by its shader CSO, whose archive monolith records carry from W4c. + table.GL.BeginTransformFeedback = &EmitBeginTransformFeedback; + table.GL.EndTransformFeedback = &EmitEndTransformFeedback; + table.GL.PauseTransformFeedback = &EmitPauseTransformFeedback; + table.GL.ResumeTransformFeedback = &EmitResumeTransformFeedback; + table.GL.BindTransformFeedback = &EmitBindTransformFeedback; + table.GL.DeleteTransformFeedback = &EmitDeleteTransformFeedback; g_monolithVerbPort = true; } diff --git a/MobileGL/MG_Remote/Client/QueryEmit.inc b/MobileGL/MG_Remote/Client/QueryEmit.inc index 6b54c7a1..96db610d 100644 --- a/MobileGL/MG_Remote/Client/QueryEmit.inc +++ b/MobileGL/MG_Remote/Client/QueryEmit.inc @@ -128,7 +128,7 @@ } void EmitDeleteTransformFeedback(GLuint name) { - auto& session = RequireSession("DeleteTransformFeedback"); + VerbChannel session = ChannelFor("DeleteTransformFeedback"); BeforeReadOnlyVerb(); const auto& context = *MG_State::pGLContext; const MG_Pipe::MGPStreamOutputBind object{name, 0, context.GetTransformFeedbackLifetimeId(name)}; diff --git a/MobileGL/MG_Remote/Client/TextureReadbackEmit.inc b/MobileGL/MG_Remote/Client/TextureReadbackEmit.inc index 9bd06e55..ad6e5067 100644 --- a/MobileGL/MG_Remote/Client/TextureReadbackEmit.inc +++ b/MobileGL/MG_Remote/Client/TextureReadbackEmit.inc @@ -94,7 +94,7 @@ return; } const Uint64 tight = layout.Tight; - ClientSession& session = RequireSession("GetTextureImage"); + VerbChannel session = ChannelFor("GetTextureImage"); BeforeReadOnlyVerb(); // A by-name texture need not occur in any currently bound sampler. MG_Pipe::MGPipeTextureEmitterInstance().DrainTextureSubData(*MG_State::pGLContext); diff --git a/MobileGL/MG_Remote/Server/PipeApplier.cpp b/MobileGL/MG_Remote/Server/PipeApplier.cpp index afa771af..35a3d40e 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.cpp +++ b/MobileGL/MG_Remote/Server/PipeApplier.cpp @@ -703,6 +703,7 @@ namespace MobileGL::MG_Remote::Server { ++m_bufferReadbackWrites; // The acceptance is the applier's (a dead handle is a counted no-op, a range // past the store a trip wire), so a refused write ends the landing here. + const MG_Pipe::MGPipeServerOriginatedWriteScope serverWrite; if (!MG_Pipe::MGPipeApplyResourceSubData(write, bytes, nullptr)) return false; } } diff --git a/MobileGL/MG_Remote/Server/StagedTextureStore.h b/MobileGL/MG_Remote/Server/StagedTextureStore.h index 4e22da8c..19e5cc92 100644 --- a/MobileGL/MG_Remote/Server/StagedTextureStore.h +++ b/MobileGL/MG_Remote/Server/StagedTextureStore.h @@ -298,6 +298,18 @@ namespace MobileGL::MG_Remote::Server { explicit StagedTextureStore(Bool copies) : m_copies(copies) {} Bool CopiesIntoServerStorage() const { return m_copies; } + // P13 W4b: THE ALIASING MODE - monolith's record arm. The store keeps every piece of + // bookkeeping it keeps under split (definitions, extents, the exact covered runs, the + // driver-written and GPU-dirty marks), but a level's bytes are the FRONTEND's level shadow + // in place: a run records the address it arrived at (minus its offset in the level) and + // nothing is copied. The readers then ask exactly what they ask under split - is the + // level covered, what are its bytes - and get the same answers from memory that is + // already there. The two copy-only operations (FollowCopy, AdoptClientBoxes) stay + // copy-only: this store never writes frontend memory, so a copy it cannot follow marks + // the destination driver-written, which sends its readers to the driver. + Bool Aliases() const { return !m_copies && MG_Config::RecordArmAliasesFrontend(); } + // Does this store answer at all: a copying store, or an aliasing one. + Bool Holds() const { return m_copies || Aliases(); } void SetDeviceLimits(Int maxTextureSize, Int max3DTextureSize, Int maxCubeMapTextureSize, Int maxArrayTextureLayers, Int maxTextureBufferSize) { const std::lock_guard lock(m_mutex); @@ -330,9 +342,13 @@ namespace MobileGL::MG_Remote::Server { // reallocates it, which is StagedShadowStore::Adopt's note one level down. Nothing needs // the base while a level is still being assembled - the readers run at the barrier, after // the last piece - so callers here ignore it. + // `frontendShadow`: the bytes ARE the frontend's level shadow (a client record's run) - the + // one source an aliasing store may keep by address. Every other adoption (the server's own + // readbacks, regenerations and promotions) is server memory that dies with the call, and is + // copied even by an aliasing store. const Uint8* AdoptRun(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent, - Uint64 imageOffset, const void* bytes, SizeT byteSize) { - if (!m_copies) return nullptr; + Uint64 imageOffset, const void* bytes, SizeT byteSize, Bool frontendShadow = false) { + if (!Holds()) return nullptr; const ShadowKey bucketed = Bucketed(key); const std::lock_guard lock(m_mutex); LevelShadow& shadow = m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)]; @@ -348,6 +364,7 @@ namespace MobileGL::MG_Remote::Server { // belt that keeps B4's rule if that refusal is ever relaxed. if (!shadow.Defined || shadow.Extent != extent) { shadow.Bytes.clear(); + shadow.Alias = nullptr; shadow.Covered.clear(); } #endif @@ -358,7 +375,7 @@ namespace MobileGL::MG_Remote::Server { m_driverWritten[bucketed] = true; } shadow.GpuDirty = false; - return CopyRunInto(shadow, key, uploadTarget, level, imageOffset, bytes, byteSize); + return CopyRunInto(shadow, key, uploadTarget, level, imageOffset, bytes, byteSize, frontendShadow); } // THE WHOLE-LEVEL SPELLING: the run is the level's COMPLETE current content, beginning at @@ -368,8 +385,8 @@ namespace MobileGL::MG_Remote::Server { // into; under monolith both do nothing and return nullptr, and the caller // (Ops_H_TextureSubData) has already returned before reaching either. const Uint8* Adopt(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent, - const void* bytes, SizeT byteSize) { - if (!m_copies) return nullptr; + const void* bytes, SizeT byteSize, Bool frontendShadow = false) { + if (!Holds()) return nullptr; const ShadowKey bucketed = Bucketed(key); const std::lock_guard lock(m_mutex); LevelShadow& shadow = m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)]; @@ -378,8 +395,9 @@ namespace MobileGL::MG_Remote::Server { // P8-E: the caller states these bytes ARE the level, which a followed copy is part of. shadow.HoldsFollowedCopy = false; shadow.Bytes.clear(); + shadow.Alias = nullptr; shadow.Covered.clear(); - return CopyRunInto(shadow, key, uploadTarget, level, 0, bytes, byteSize); + return CopyRunInto(shadow, key, uploadTarget, level, 0, bytes, byteSize, frontendShadow); } // A storage-defining respecify named this level: it EXISTS from here on, at the exact @@ -408,7 +426,7 @@ namespace MobileGL::MG_Remote::Server { void NoteLevelDefined(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent, Uint8 resourceTarget = static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), Uint32 internalFormat = static_cast(TextureInternalFormat::RGBA8)) { - if (!m_copies) return; + if (!Holds()) return; const ShadowKey bucketed = Bucketed(key); const std::lock_guard lock(m_mutex); Uint64 byteBound = 0; @@ -431,6 +449,8 @@ namespace MobileGL::MG_Remote::Server { if (!shadow.Defined || shadow.Extent != extent || shadow.InternalFormat != internalFormat || shadow.ResourceTarget != resourceTarget || shadow.DeclaredByteBound != byteBound) { shadow.Bytes.clear(); + // An aliased level's base is the frontend allocation this redefinition replaced. + shadow.Alias = nullptr; // The covered set is a statement about THIS coordinate system, so it goes with // the bytes it described. shadow.Covered.clear(); @@ -460,7 +480,7 @@ namespace MobileGL::MG_Remote::Server { // never crossed - and the mark is what answers "dirty region, level has no pending // upload" without asking the client (§2.2's last row). void MarkLevelGpuDirty(Uint64 key, Uint16 uploadTarget, Uint16 level, Bool dirty) { - if (!m_copies) return; + if (!Holds()) return; const ShadowKey bucketed = Bucketed(key); const std::lock_guard lock(m_mutex); m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)].GpuDirty = dirty; @@ -510,7 +530,7 @@ namespace MobileGL::MG_Remote::Server { // next run for it is the CLIENT's shadow, which never saw the copy: AdoptClientBoxes // lands only the texels that run says the client wrote. void MarkDriverWritten(Uint64 key) { - if (!m_copies) return; + if (!Holds()) return; const std::lock_guard lock(m_mutex); m_driverWritten[Bucketed(key)] = true; m_any.store(true, std::memory_order_release); @@ -645,15 +665,10 @@ namespace MobileGL::MG_Remote::Server { // reads the level image WHOLE (LevelByteSize / the sync's texel base), so a level with // one piece missing is as unreadable as one with none. const Uint8* RequireLevelBytes(Uint64 key, Uint16 uploadTarget, Uint16 level, const char* site) const { - if (!m_copies) return nullptr; + if (!Holds()) return nullptr; const std::lock_guard lock(m_mutex); const LevelShadow* shadow = FindLevel(key, uploadTarget, level); - if (shadow != nullptr && shadow->Defined && shadow->DeclaredByteBound != 0 && - shadow->Bytes.size() == shadow->DeclaredByteBound && - StagedShadowStore::CoverageHas(shadow->Covered, 0, - static_cast(shadow->DeclaredByteBound))) { - return shadow->Bytes.data(); - } + if (shadow != nullptr && LevelComplete(*shadow)) return LevelData(*shadow); // Verbatim what the MGLOG_F said, through the funnel that publishes it (P7 wave 0). SessionFail(MGFatalFamily::StageSnapshotTooNarrow, "MGPipe: Fatal{StageSnapshotTooNarrow, \"%s\"} - the texture sync wants the " @@ -675,7 +690,7 @@ namespace MobileGL::MG_Remote::Server { // covered level answers, and nothing is fatal: an uncovered box is the caller's to skip. const Uint8* CoveredBoxBytes(Uint64 key, Uint16 uploadTarget, Uint16 level, Int x, Int y, Uint32 w, Uint32 h, SizeT texel, Int* rowTexels) const { - if (!m_copies) return nullptr; + if (!Holds()) return nullptr; const std::lock_guard lock(m_mutex); const LevelShadow* shadow = FindLevel(key, uploadTarget, level); if (shadow == nullptr || !shadow->Defined || x < 0 || y < 0 || w == 0 || h == 0 || @@ -684,11 +699,11 @@ namespace MobileGL::MG_Remote::Server { for (Uint32 row = 0; row < h; ++row) { const SizeT begin = TexelOffset(shadow->Extent, x, y + static_cast(row), 0, texel); const SizeT end = begin + static_cast(w) * texel; - if (end > shadow->Bytes.size() || !StagedShadowStore::CoverageHas(shadow->Covered, begin, end)) + if (end > LevelBytesHeld(*shadow) || !StagedShadowStore::CoverageHas(shadow->Covered, begin, end)) return nullptr; } *rowTexels = shadow->Extent.x(); - return shadow->Bytes.data(); + return LevelData(*shadow); } // Diagnostics the sync path and the unit cases read, so that a check can assert WHAT @@ -697,7 +712,7 @@ namespace MobileGL::MG_Remote::Server { const std::lock_guard lock(m_mutex); const LevelShadow* shadow = FindLevel(key, uploadTarget, level); if (shadow == nullptr || !shadow->Defined || shadow->DeclaredByteBound == 0 || - shadow->Bytes.size() != shadow->DeclaredByteBound) return false; + LevelBytesHeld(*shadow) != shadow->DeclaredByteBound) return false; // THE WHOLE LEVEL, not merely the currently assembled prefix: all runs must cover // the server-derived bound before a reader can treat the level as complete. return StagedShadowStore::CoverageHas( @@ -737,7 +752,7 @@ namespace MobileGL::MG_Remote::Server { const std::lock_guard lock(m_mutex); const LevelShadow* shadow = FindLevel(key, uploadTarget, level); if (shadow == nullptr || !shadow->Defined) return 0; - return shadow->Bytes.size(); + return LevelBytesHeld(*shadow); } Uint64 LevelDeclaredByteBound(Uint64 key, Uint16 uploadTarget, Uint16 level) const { if (!m_any.load(std::memory_order_acquire)) return 0; @@ -820,7 +835,20 @@ namespace MobileGL::MG_Remote::Server { Bool GpuDirty = false; // P8-E: FollowCopy wrote server-side texels into Bytes that the client's shadow lacks. Bool HoldsFollowedCopy = false; + // P13 W4b, the aliasing mode: the level's first byte in the FRONTEND's level shadow, + // learned from the first run that arrived (its address minus its offset in the level). + // Null until then, and nulled with the coordinate system it belongs to. Bytes stays + // empty in this mode; LevelData / LevelBytesHeld are the two readers' spellings. + const Uint8* Alias = nullptr; }; + static const Uint8* LevelData(const LevelShadow& shadow) { + return shadow.Alias != nullptr ? shadow.Alias : shadow.Bytes.data(); + } + // How many bytes of the level image this store can hand out: an aliased level is the + // frontend's whole level allocation, which is exactly its declared bound. + static SizeT LevelBytesHeld(const LevelShadow& shadow) { + return shadow.Alias != nullptr ? static_cast(shadow.DeclaredByteBound) : shadow.Bytes.size(); + } struct TextureShadow { ska::flat_hash_map Levels; }; @@ -872,7 +900,7 @@ namespace MobileGL::MG_Remote::Server { // Check the run's exclusive end against the extent recorded by the server's prior // resource_respecify before any cast, coverage edit, or vector growth. const Uint8* CopyRunInto(LevelShadow& shadow, Uint64 key, Uint16 uploadTarget, Uint16 level, - Uint64 imageOffset, const void* bytes, SizeT byteSize) { + Uint64 imageOffset, const void* bytes, SizeT byteSize, Bool frontendShadow) { if (bytes != nullptr && byteSize != 0) { constexpr Uint64 max = std::numeric_limits::max(); const Uint64 runBytes = static_cast(byteSize); @@ -901,11 +929,35 @@ namespace MobileGL::MG_Remote::Server { const auto* raw = static_cast(bytes); const SizeT begin = static_cast(imageOffset); const SizeT needed = static_cast(end); + if (!m_copies && frontendShadow) { + // The aliasing mode: the run IS the frontend level shadow at `begin`, so the + // level's base is one subtraction away and nothing is copied. Every run of one + // level comes from one allocation; a run from another is a level redefined + // without a respecify (or one the server had been holding a copy of), which + // would leave earlier coverage describing other memory - so it starts the + // coverage over. + const Uint8* base = raw - begin; + if (shadow.Alias != base) { + shadow.Covered.clear(); + shadow.Bytes.clear(); + shadow.Alias = base; + } + StagedShadowStore::CoverageAdd(shadow.Covered, begin, needed); + m_any.store(true, std::memory_order_release); + return shadow.Alias; + } + if (shadow.Alias != nullptr) { + // A server copy landing on an aliased level: the level becomes server-owned, so + // the bytes it already stands for are taken into it first. + shadow.Bytes.assign(shadow.Alias, shadow.Alias + static_cast(shadow.DeclaredByteBound)); + shadow.Alias = nullptr; + } if (shadow.Bytes.size() < needed) shadow.Bytes.resize(needed, 0); std::memcpy(shadow.Bytes.data() + begin, raw, byteSize); StagedShadowStore::CoverageAdd(shadow.Covered, begin, needed); } m_any.store(true, std::memory_order_release); + if (shadow.Alias != nullptr) return shadow.Alias; return shadow.Bytes.empty() ? nullptr : shadow.Bytes.data(); } @@ -927,7 +979,7 @@ namespace MobileGL::MG_Remote::Server { // that does not divide evenly is a level this cannot address texel by texel. static Bool LevelComplete(const LevelShadow& shadow) { return shadow.Defined && shadow.DeclaredByteBound != 0 && - shadow.Bytes.size() == shadow.DeclaredByteBound && + LevelBytesHeld(shadow) == shadow.DeclaredByteBound && StagedShadowStore::CoverageHas(shadow.Covered, 0, static_cast(shadow.DeclaredByteBound)); } static SizeT LevelTexelBytes(const LevelShadow& shadow) { diff --git a/MobileGL/MG_Test/Buffer/BufferTest.cpp b/MobileGL/MG_Test/Buffer/BufferTest.cpp index 1cf4d222..714e4a1b 100644 --- a/MobileGL/MG_Test/Buffer/BufferTest.cpp +++ b/MobileGL/MG_Test/Buffer/BufferTest.cpp @@ -20,33 +20,11 @@ #include #include #include +#include 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 +using MobileGL::MG_Test::ScopedMonolithFrontendArm; class BufferTest : public ::testing::Test { protected: diff --git a/MobileGL/MG_Test/ScopedMonolithFrontendArm.h b/MobileGL/MG_Test/ScopedMonolithFrontendArm.h new file mode 100644 index 00000000..d7403ed5 --- /dev/null +++ b/MobileGL/MG_Test/ScopedMonolithFrontendArm.h @@ -0,0 +1,49 @@ +// MobileGL - MobileGL/MG_Test/ScopedMonolithFrontendArm.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header +#pragma once +#include +#include + +namespace MobileGL::MG_Test { + + // P13 W4: THE MONOLITH FRONTEND ARM, PINNED FOR ONE CASE. A case that uses this asserts how + // the frontend answers when the backend reads the frontend's objects itself - pushing a map's + // bytes through the object, reading a texture back from the frontend's own level shadow - which + // is the arm W4 retires family by family and W6 deletes. On the record arm (every wire, and + // monolith's from W4) the client tracker and the backend's record readers own those answers, + // and the split and integration lanes cover them. When W6 deletes the frontend arm, the cases + // that use this go with it. + // + // Both halves of the arm: the predicate, and the backend table (the verb port's slots go back + // to the backend's own entry points for the scope). + class ScopedMonolithFrontendArm { + public: + ScopedMonolithFrontendArm() : m_savedTable(MG_Backend::gBackendFunctionsTable) { +#if MOBILEGL_BUILD_DISAGGREGATED + m_saved = MG_Config::MonolithTakesRecordArm; + MG_Config::MonolithTakesRecordArm = false; + if (MG_Config::Transport == MG_Config::TransportMode::Monolith && MG_Backend::pActiveBackendObject) { + MG_Backend::gBackendFunctionsTable = MG_Backend::pActiveBackendObject->GetBackendFunctions(); + } +#endif + } + ~ScopedMonolithFrontendArm() { +#if MOBILEGL_BUILD_DISAGGREGATED + MG_Config::MonolithTakesRecordArm = m_saved; +#endif + MG_Backend::gBackendFunctionsTable = m_savedTable; + } + ScopedMonolithFrontendArm(const ScopedMonolithFrontendArm&) = delete; + ScopedMonolithFrontendArm& operator=(const ScopedMonolithFrontendArm&) = delete; + + private: + MG_Backend::GlobalBackendFunctionsTable m_savedTable; + Bool m_saved = false; + }; + +} // namespace MobileGL::MG_Test diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index 8ace546c..5efd099a 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -13,6 +13,7 @@ #include "Includes.h" #include "Init.h" +#include #include #include #include @@ -1065,6 +1066,7 @@ TEST_F(TextureTest, EverySamplerScalarPnameConvertsToTheQueriedType) { // caller's buffer: on glGetTexImage, which has no bufSize argument, that is a heap overflow of // (shadowTexelSize - clientTexelSize) * texelCount bytes. It must refuse instead. TEST_F(TextureTest, ShadowReadbackRefusesALayoutItCannotProduceInsteadOfOverrunningTheBuffer) { + const MobileGL::MG_Test::ScopedMonolithFrontendArm frontendArm; GLuint texture = 0; MG_Impl::GLImpl::GenTextures(1, &texture); ASSERT_NE(texture, 0u); @@ -1106,6 +1108,7 @@ TEST_F(TextureTest, ShadowReadbackRefusesALayoutItCannotProduceInsteadOfOverrunn // readback path DOES write it. Refusing keeps the two paths from answering the same call with two // different destination layouts. TEST_F(TextureTest, ShadowReadbackRefusesAPackStateItCannotHonour) { + const MobileGL::MG_Test::ScopedMonolithFrontendArm frontendArm; GLuint texture = 0; MG_Impl::GLImpl::GenTextures(1, &texture); ASSERT_NE(texture, 0u); @@ -2144,6 +2147,7 @@ TEST_F(TextureTest, GetTextureSubImageSelectsTheCubeFaceZOffsetNames) { // precisely. glGetTexImage passes no bufSize, which skips the destination-size branch but NOT the // PBO one, so this is the only spelling where the six-face sizing was reachable at all. TEST_F(TextureTest, GetTexImageOfOneCubeFacePacksIntoAOneFacePixelPackBuffer) { + const MobileGL::MG_Test::ScopedMonolithFrontendArm frontendArm; constexpr GLsizei kEdge = 2; constexpr SizeT kFaceBytes = static_cast(kEdge) * kEdge * 4; diff --git a/MobileGL/MG_Test/Texture/TextureViewTest.cpp b/MobileGL/MG_Test/Texture/TextureViewTest.cpp index 0729a654..8081b1dc 100644 --- a/MobileGL/MG_Test/Texture/TextureViewTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureViewTest.cpp @@ -23,6 +23,7 @@ #include "Includes.h" #include "Init.h" +#include #include #include #include @@ -521,6 +522,7 @@ namespace { // resolves the face into a Vulkan baseArrayLayer and was always right, which is what made this // a disagreement between the two backends rather than a uniform wrong answer.) TEST_F(TextureViewTest, CubeMapViewOfAnArrayReadsTheFaceEachTokenNames) { + const MobileGL::MG_Test::ScopedMonolithFrontendArm frontendArm; constexpr GLint kLayers = 8; constexpr GLint kViewMinLayer = 2;