diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 59b6819c..24459b5b 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -2394,7 +2394,15 @@ namespace MobileGL::MG_Backend::DirectGLES { } void Ops_H_Readback(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPReadback& record) { +#if MOBILEGL_BUILD_DISAGGREGATED + // An empty capture may conservatively request a writeback before + // any draw materialized this buffer. Resolve its staged resource + // exactly as a GPU consumer would, then return the real bytes. + auto* resource = MG_Config::Transport != MG_Config::TransportMode::Monolith + ? EnsureBufferResourceForHandle(nullptr, res) : FindBufferResourceForHandle(res); +#else auto* resource = FindBufferResourceForHandle(res); +#endif if (!resource || resource->id == 0 || !resource->storageInitialized) return; if (!CanTouchGLNow() || resource->contextGeneration != g_bufferContextGeneration) return; if (resource->persistentMapped) { @@ -2715,9 +2723,12 @@ namespace MobileGL::MG_Backend::DirectGLES { if (bytes == nullptr || record.Blob.Size == 0) return; const auto* stored = PipeTextureRecordForHandle(res); if (stored == nullptr) return; - const IntVec3 extent = MG_Remote::Server::StagedTextureMipExtent( - stored->Desc.Target, stored->Desc.Width, stored->Desc.Height, stored->Desc.Depth, - static_cast(record.Level)); + const IntVec3 extent = record.LevelWidth && record.LevelHeight && record.LevelDepth + ? IntVec3{static_cast(record.LevelWidth), static_cast(record.LevelHeight), + static_cast(record.LevelDepth)} + : MG_Remote::Server::StagedTextureMipExtent( + stored->Desc.Target, stored->Desc.Width, stored->Desc.Height, stored->Desc.Depth, + static_cast(record.Level)); store.Adopt(MG_Remote::Server::StagedTextureStore::KeyForHandle(res), MG_Pipe::MGPipeSubDataUploadTargetOf(record.Target), record.Level, extent, bytes, static_cast(record.Blob.Size)); diff --git a/MobileGL/MG_Impl/Pipe/Tracker.h b/MobileGL/MG_Impl/Pipe/Tracker.h index c4a0860f..93c55d59 100644 --- a/MobileGL/MG_Impl/Pipe/Tracker.h +++ b/MobileGL/MG_Impl/Pipe/Tracker.h @@ -737,6 +737,15 @@ namespace MobileGL::MG_Pipe { } m_lastVaoLifetime = vaoLifetime; m_lastVaoConfig = vaoConfig; + // A redundant bind of the default texture can grow the unit window + // without changing either binding or sampling generations. Both sets + // must still publish that new prefix, including its null samplers. + const Int maxTextureUnit = ctx.GetMaxTouchedTextureUnit(); + if (!m_primed || maxTextureUnit != m_lastMaxTextureUnit) { + dirty |= MGPipeDirtyBit(MGPipeDirty::NewSamplerViews) | + MGPipeDirtyBit(MGPipeDirty::NewSamplers); + } + m_lastMaxTextureUnit = maxTextureUnit; // ---- bit 2: the PACK half of the pixel store, BitwiseEqual ---- const PixelStoreParameters pack = ctx.GetPixelStoreParameters(false); @@ -791,6 +800,7 @@ namespace MobileGL::MG_Pipe { std::memset(m_lastPushed, 0, sizeof(m_lastPushed)); m_lastVaoLifetime = 0; m_lastVaoConfig = 0; + m_lastMaxTextureUnit = -1; m_renderStateVersion.Reset(); m_pipelineStateVersion.Reset(); m_framebufferBind.Reset(); @@ -881,6 +891,7 @@ namespace MobileGL::MG_Pipe { Uint64 m_lastPushed[kMGPipeDirtyCount]{}; Uint64 m_lastVaoLifetime = 0; Uint32 m_lastVaoConfig = 0; + Int m_lastMaxTextureUnit = -1; MGPipeWidenedCounter m_renderStateVersion; MGPipeWidenedCounter m_pipelineStateVersion; // The draw framebuffer BINDING slot version, widened for the same reason: a Uint16 diff --git a/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.cpp b/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.cpp index d2d09e68..d4433450 100644 --- a/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.cpp @@ -17,6 +17,12 @@ #include #include #include +#if MOBILEGL_BUILD_DISAGGREGATED +#include +#include +#include +#include +#endif #define MGITEST_PIPE_APPLY_PEEK_LIVE 1 #endif #endif @@ -74,8 +80,70 @@ namespace MGITest { } } // namespace + static bool ReadAppliedTextureParams(MGB::TextureImpl::BackendTextureObject* twin, + unsigned glTarget, EsprytAppliedTextureParamsPeek* out); + + static void CopyTextureParamsRecord(const MGP::MGPipeResourceRecord& record, unsigned slot, + PipeTextureParamsRecordPeek& out) { + out.Slot = slot; + out.Gen = static_cast(record.Gen); + out.ParamsSerial = static_cast(record.ParamsSerial); + for (int channel = 0; channel < 4; ++channel) out.Swizzle[channel] = SwizzleToGLEnum(record.Params.Swizzle[channel]); + out.DepthStencilMode = DepthStencilModeToGLEnum(record.Params.DepthStencilMode); + } + +#if MOBILEGL_BUILD_DISAGGREGATED + enum class ServerPeekKind { Record, Applied, View }; + static bool PeekOnApplyThread(unsigned glTextureName, unsigned target, ServerPeekKind kind, void* output) { + auto* object = FrontendTexture(glTextureName); + auto* session = MobileGL::MG_Remote::Client::ClientSession::Active(); + if (!object || !session || !output) return false; + struct Request { + MGP::MGPipeHandle Texture, View; + unsigned Target; + ServerPeekKind Kind; + void* Output; + bool Result = false; + } request{MGP::MGPipeSlots().FindByLifetimeId(MGP::MGPipeKind::Texture, object->GetLifetimeId()), + MGP::MGPipeSlots().FindByLifetimeId(MGP::MGPipeKind::SamplerViewCso, object->GetLifetimeId()), + target, kind, output}; + if (MGP::MGPipeHandleIsNull(request.Texture)) return false; + if (session->WaitForApplied(session->LastPublishedSeq(), 30000) != + MobileGL::MG_Remote::Transport::SessionWait::Reached) return false; + const auto result = MobileGL::MG_Remote::Server::ServerLoopInstance().RunProbeOnApplyThreadForTesting( + +[](void* opaque) -> MobileGLResult { + auto& r = *static_cast(opaque); + const auto& records = MGP::MGPipeApplier().TextureResources; + if (r.Texture.Slot >= records.size()) return MOBILEGL_OK; + const auto& record = records[r.Texture.Slot]; + if (!record.Live || record.Gen != r.Texture.Gen) return MOBILEGL_OK; + if (r.Kind == ServerPeekKind::Record) { + CopyTextureParamsRecord(record, r.Texture.Slot, *static_cast(r.Output)); + r.Result = true; + return MOBILEGL_OK; + } + auto* found = MGB::TextureImpl::g_backendTextureObjects.FindByHandle(r.Texture); + if (!found || !*found) return MOBILEGL_OK; + if (r.Kind == ServerPeekKind::Applied) + r.Result = ReadAppliedTextureParams(found->get(), r.Target, + static_cast(r.Output)); + else { + *static_cast(r.Output) = !MGP::MGPipeHandleIsNull(r.View) && + MGB::SamplerViewImpl::FindSamplerViewForHandle(r.View) != nullptr; + r.Result = true; + } + return MOBILEGL_OK; + }, &request); + return result == MOBILEGL_OK && request.Result; + } +#endif + bool PeekPipeTextureParamsRecord(unsigned glTextureName, PipeTextureParamsRecordPeek* out) { if (out == nullptr) return false; +#if MOBILEGL_BUILD_DISAGGREGATED + if (MobileGL::MG_Config::Transport != MobileGL::MG_Config::TransportMode::Monolith) + return PeekOnApplyThread(glTextureName, 0, ServerPeekKind::Record, out); +#endif const MGP::MGPipeApplierState& applier = MGP::MGPipeApplier(); // Slot 0 is the reserved null handle and is never live (MGPipeHandles.h), so the scan // starts at 1 and a match at 0 is impossible rather than merely unlikely. @@ -83,13 +151,7 @@ namespace MGITest { const MGP::MGPipeResourceRecord& record = applier.TextureResources[slot]; if (!record.Live) continue; if (record.Desc.GlNameForDiag != static_cast(glTextureName)) continue; - out->Slot = static_cast(slot); - out->Gen = static_cast(record.Gen); - out->ParamsSerial = static_cast(record.ParamsSerial); - for (int channel = 0; channel < 4; ++channel) { - out->Swizzle[channel] = SwizzleToGLEnum(record.Params.Swizzle[channel]); - } - out->DepthStencilMode = DepthStencilModeToGLEnum(record.Params.DepthStencilMode); + CopyTextureParamsRecord(record, static_cast(slot), *out); return true; } return false; @@ -97,10 +159,18 @@ namespace MGITest { bool PeekEsprytAppliedTextureParams(unsigned glTextureName, unsigned glTarget, EsprytAppliedTextureParamsPeek* out) { +#if MOBILEGL_BUILD_DISAGGREGATED + if (MobileGL::MG_Config::Transport != MobileGL::MG_Config::TransportMode::Monolith) + return PeekOnApplyThread(glTextureName, glTarget, ServerPeekKind::Applied, out); +#endif + return ReadAppliedTextureParams(EsprytTwin(glTextureName), glTarget, out); + } + + static bool ReadAppliedTextureParams(MGB::TextureImpl::BackendTextureObject* twin, + unsigned glTarget, EsprytAppliedTextureParamsPeek* out) { if (out == nullptr) return false; const int bindingQuery = BindingQueryFor(glTarget); if (bindingQuery == 0) return false; - MGB::TextureImpl::BackendTextureObject* const twin = EsprytTwin(glTextureName); if (twin == nullptr) return false; const MobileGL::Uint backendId = twin->GetBackendTextureId(); if (backendId == 0) return false; @@ -151,6 +221,10 @@ namespace MGITest { } bool PeekEsprytHasSamplerViewForTexture(unsigned glTextureName, bool* outExists) { +#if MOBILEGL_BUILD_DISAGGREGATED + if (MobileGL::MG_Config::Transport != MobileGL::MG_Config::TransportMode::Monolith) + return PeekOnApplyThread(glTextureName, 0, ServerPeekKind::View, outExists); +#endif if (outExists == nullptr) return false; MobileGL::MG_State::GLState::ITextureObject* const object = FrontendTexture(glTextureName); if (object == nullptr) return false; diff --git a/MobileGL/MG_Pipe/PipeApply.h b/MobileGL/MG_Pipe/PipeApply.h index 419a0e5d..79b0cf4d 100644 --- a/MobileGL/MG_Pipe/PipeApply.h +++ b/MobileGL/MG_Pipe/PipeApply.h @@ -848,6 +848,7 @@ namespace MobileGL::MG_Pipe { MGPipeHandle VerbCopyTexDst = kMGPipeNullHandle; MGPipeHandle VerbStorageBlockProgram = kMGPipeNullHandle; Uint64 BoundStreamOutputLifetimeId = 0; + Uint64 VerbDeleteStreamOutputLifetimeId = 0; UnorderedMap StreamOutputSpans; // generate_mipmap's texture (MGPMipPlan::Res). MGPipeHandle VerbMipRes = kMGPipeNullHandle; @@ -876,6 +877,7 @@ namespace MobileGL::MG_Pipe { VerbBlitNamedConsumed = false; VerbCopyTexDst = kMGPipeNullHandle; VerbStorageBlockProgram = kMGPipeNullHandle; + VerbDeleteStreamOutputLifetimeId = 0; VerbMipRes = kMGPipeNullHandle; VerbMipBaseLevel = VerbMipLevelCount = 0; VerbIndirectBuffer = kMGPipeNullHandle; diff --git a/MobileGL/MG_Remote/Server/PipeApplier.cpp b/MobileGL/MG_Remote/Server/PipeApplier.cpp index 39f82a49..ecd07100 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.cpp +++ b/MobileGL/MG_Remote/Server/PipeApplier.cpp @@ -1069,7 +1069,9 @@ namespace MobileGL::MG_Remote::Server { state.StreamOutputSpans.erase(object.LifetimeId); if (state.BoundStreamOutputLifetimeId == object.LifetimeId) state.BoundStreamOutputLifetimeId = 0; } + state.VerbDeleteStreamOutputLifetimeId = object.LifetimeId; if (table->GL.DeleteTransformFeedback) table->GL.DeleteTransformFeedback(object.GlName); + state.VerbDeleteStreamOutputLifetimeId = 0; return true; } diff --git a/MobileGL/MG_Test/Pipe/TrackerTest.cpp b/MobileGL/MG_Test/Pipe/TrackerTest.cpp index c3224c82..7ec73332 100644 --- a/MobileGL/MG_Test/Pipe/TrackerTest.cpp +++ b/MobileGL/MG_Test/Pipe/TrackerTest.cpp @@ -80,6 +80,7 @@ namespace { X(TrackerWalk, TheIndexBufferBitFiresWhenTheSlotVersionWrapsOntoADifferentBuffer) \ X(TrackerWalk, ABaseInstanceSurvivesTheFirstWalkOnAFreshContext) \ X(TrackerWalk, ASamplerBindAloneFiresTheSamplerStateBit) \ + X(TrackerWalk, ARedundantDefaultBindStillPublishesAGrowingSamplerWindow) \ X(TrackerWalk, ARestagedProgramPipelineFiresTheProgramBits) \ X(TrackerWalk, ARelinkOfAStageProgramFiresTheProgramBits) \ X(TrackerWalk, UseProgramZeroLeavesTheBoundPipelineDrivingTheProgramBits) \ @@ -784,6 +785,18 @@ namespace { EXPECT_EQ(Walk(), 0u) << "the widened shutter fires forever"; } + TEST_F(TrackerWalk, ARedundantDefaultBindStillPublishesAGrowingSamplerWindow) { + Walk(); + ASSERT_EQ(Walk(), 0u); + const Uint64 generation = Ctx().GetTextureBindGeneration(); + Ctx().NoteTextureUnitTouched(7, false); + ASSERT_EQ(Ctx().GetTextureBindGeneration(), generation); + const Uint32 dirty = Walk(); + EXPECT_NE(dirty & MGPipeDirtyBit(MGPipeDirty::NewSamplerViews), 0u); + EXPECT_NE(dirty & MGPipeDirtyBit(MGPipeDirty::NewSamplers), 0u); + EXPECT_EQ(Walk(), 0u); + } + // BITS 6/7/8 UNDER A SEPARABLE PROGRAM PIPELINE. GetCurrentProgram() is null for the whole // life of a bound pipeline, so all three shutters used to latch 0 and never move again // after the first walk: glUseProgramStages would rebuild the composite and EmitShaderState