diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 9fdf03ee..4afaa583 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -2250,16 +2250,18 @@ namespace MobileGL::MG_Backend::DirectGLES { void DeleteTransformFeedback(GLuint name) { #if MOBILEGL_BUILD_DISAGGREGATED - // No delete_stream_output opcode exists yet; the client refuses this class-C - // verb before emission. Never reinterpret its GL name as a lifetime id. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) - MG_Pipe::MGPipeUnmigratedEmulation("delete-transform-feedback"); auto& roleState = ActiveXfbState(); auto& g_currentXfbState = roleState.Cached; auto& g_xfbObjects = roleState.Objects; auto& g_currentXfbName = roleState.Current; -#endif + const Bool server = MG_Config::Transport != MG_Config::TransportMode::Monolith; + 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; + const auto it = g_xfbObjects.find(key); +#else const auto it = g_xfbObjects.find(name); +#endif if (it == g_xfbObjects.end()) return; if (it->second.esId != 0 && g_GLESFuncs.glDeleteTransformFeedbacks != nullptr) { g_GLESFuncs.glDeleteTransformFeedbacks(1, &it->second.esId); @@ -2267,7 +2269,23 @@ namespace MobileGL::MG_Backend::DirectGLES { g_currentXfbState = nullptr; // erase shifts the probe cluster, moving other entries g_xfbObjects.erase(it); // The frontend reverts to the default object when the bound one is deleted. - if (g_currentXfbName == name) { + if (g_currentXfbName == +#if MOBILEGL_BUILD_DISAGGREGATED + key +#else + name +#endif + ) { +#if MOBILEGL_BUILD_DISAGGREGATED + if (server) { + // The following BindStreamOutput names this context's actual + // default lifetime. Never create a native object for key 0. + g_currentXfbName = 0; + roleState.NeedsBind = true; + BufferImpl::InvalidateTransformFeedbackBindingShadows(); + return; + } +#endif BindTransformFeedback(0); } } @@ -4191,7 +4209,14 @@ namespace MobileGL::MG_Backend::DirectGLES { if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { for (Uint32 unit = 0; unit < end; ++unit) { if (unit >= st.BoundShaderImages.size()) break; - SyncImageTextureBinding(st.BoundShaderImages[unit]); + // The union can include a slot no set_shader_images + // record has described yet. Its zero-initialized Unit + // is not a binding identity: treating it as one unbinds + // real unit 0 after binding the current program's images. + // Array position identifies both retained and empty slots. + auto view = st.BoundShaderImages[unit]; + view.Unit = unit; + SyncImageTextureBinding(view); } return; } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/P4aSeamAuditScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/P4aSeamAuditScenario.cpp index 9a548a77..b88191f2 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/P4aSeamAuditScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/P4aSeamAuditScenario.cpp @@ -61,6 +61,7 @@ #include "../Harness/HeadlessGL.h" #include "../Harness/P4aSeamPeek.h" +#include "../Harness/SplitRuntimePeek.h" #include "../Harness/ScenarioFixture.h" #ifdef GLAPI @@ -675,6 +676,10 @@ void main() { imageStore(i1, 0, imageLoad(i0, 0) + uvec4(2u, 0u, 0u, 0u)); } glMemoryBarrier(GL_ALL_BARRIER_BITS); EXPECT_EQ(FirstGLError(), 0u) << "the first dispatch leaked a GL error"; if (whiteBox) { + // A memory-barrier command orders GPU work; run-ahead need not + // have applied it before this client-side white-box observation. + const auto runtime = PeekSplitRuntime(); + if (runtime.sessionActive) ASSERT_TRUE(WaitForSplitAppliedForTesting(runtime.emitSeq)); PipeShaderImageWindowPeek window{}; ASSERT_TRUE(PeekPipeShaderImageWindow(&window)); EXPECT_EQ(window.Start, 0u); @@ -688,6 +693,8 @@ void main() { imageStore(i1, 0, imageLoad(i0, 0) + uvec4(2u, 0u, 0u, 0u)); } glMemoryBarrier(GL_ALL_BARRIER_BITS); EXPECT_EQ(FirstGLError(), 0u) << "the second dispatch leaked a GL error"; if (whiteBox) { + const auto runtime = PeekSplitRuntime(); + if (runtime.sessionActive) ASSERT_TRUE(WaitForSplitAppliedForTesting(runtime.emitSeq)); PipeShaderImageWindowPeek window{}; ASSERT_TRUE(PeekPipeShaderImageWindow(&window)); EXPECT_EQ(window.Start, 0u); diff --git a/MobileGL/MG_Remote/Client/EmitTables.cpp b/MobileGL/MG_Remote/Client/EmitTables.cpp index 8766a35c..eb14b127 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.cpp +++ b/MobileGL/MG_Remote/Client/EmitTables.cpp @@ -1297,6 +1297,7 @@ namespace MobileGL::MG_Remote::Client { // 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; + BeforeReadOnlyVerb(); MG_Pipe::MGPStorageBlockBinding record{}; record.GlName = static_cast(program); @@ -1305,8 +1306,14 @@ namespace MobileGL::MG_Remote::Client { if (MG_State::pGLContext != nullptr) { const auto& programObject = MG_State::pGLContext->GetProgramObject(program); if (programObject) { - record.ShaderCso = MG_Pipe::MGPipeSlots().FindByLifetimeId( - MG_Pipe::MGPipeKind::ShaderCso, programObject->GetLifetimeId()); + // The application may rebind a stage program before it is + // current or attached to a pipeline. A minted slot alone does + // not publish its archive; this verb must follow that birth. + programObject->JoinLinkAndSpirv(); + Uint64 bytes = 0; + auto& emitter = MG_Pipe::MGPipeProgramEmitterInstance(); + record.ShaderCso = emitter.AcquireShaderCso(*programObject, bytes); + emitter.EmitProgramBindings(*programObject, record.ShaderCso); } } // Size = strlen + 1: THE NUL TRAVELS (contract table 0's block-name row). The