From 1e8cdc5931f47d9fd9a5831b32a2c258e725ba3a Mon Sep 17 00:00:00 2001 From: rereview Date: Tue, 8 Sep 2026 17:30:39 -0400 Subject: [PATCH] [Fix] (Espryt): take the sampler CSO record from the handle the caller carries - a content-addressed record can never be named by the twins own identity handle, so every bound sampler objects parameters were refused on the integrated tree --- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 85 ++++++++++++++++----- MobileGL/MG_Backend/DirectGLES/Managers.h | 25 ++++++ 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 07694e5b..92cd867b 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -12287,8 +12287,14 @@ namespace MobileGL::MG_Backend::DirectGLES { m_backendSamplerId = 0; } +#if MOBILEGL_PIPE_PUSH + void BackendSamplerObject::SyncToBackend( + const SharedPtr& stateSamplerObject, + MG_Pipe::MGPipeHandle pushedCso) { +#else void BackendSamplerObject::SyncToBackend( const SharedPtr& stateSamplerObject) { +#endif #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif @@ -12309,24 +12315,69 @@ namespace MobileGL::MG_Backend::DirectGLES { #if MOBILEGL_PIPE_PUSH const SamplerParameters* pushedParams = nullptr; if (SamplerSubsystemEnabled()) { - // MONOLITH GLUE: the SamplerCso handle of an object this backend still arrives - // holding. Under a real split it rides in the payload, and every path P4a - // switches over already carries it - this is for the paths that do not. - const MG_Pipe::MGPipeHandle cso = g_backendSamplerObjects.HandleOf(stateSamplerObject.get()); - const auto* record = PipeSamplerCsoRecordForHandle(cso); - if (record == nullptr) { - MGLOG_E_ONCE("MGPipe: sampler %u has no applier record on the handle arm, so its " - "parameters cannot be pushed (handle {%u, %u})", - stateSamplerObject->GetExternalIndex(), cso.Slot, cso.Gen); - return; + // THE HANDLE COMES FROM THE CALLER, NOT FROM THIS TWIN'S REGISTRY, and the + // difference is the seam D's verification round found on the integrated tree. + // + // v2 asked g_backendSamplerObjects.HandleOf(object) - the twin's own identity + // handle, minted off the frontend lifetime id - and looked the record up by it. + // That can only ever miss: a SamplerCso is CONTENT-ADDRESSED on the client + // (D-F1, ID-14/ID-17), its handles come out of MGPipeSlots().Allocate keyed on a + // parameter hash, and nothing ever emits a create_sampler_state at an identity + // handle. On the integrated tree every glBindSampler-driven parameter set was + // therefore refused and the driver sampler kept its defaults - two integration + // scenarios red, and the twin's own comment below (content-addressed, the + // record's Serial is the authority) already said why it could not work. + // + // The carried fact is MGPipeApplier().BoundSamplerStates[unit], written by the + // client at bind_sampler_states; the caller that knows the unit passes it as + // `pushedCso`. See the declaration for why the parameter is push-only. + if (!MG_Pipe::MGPipeHandleIsNull(pushedCso)) { + const auto* record = PipeSamplerCsoRecordForHandle(pushedCso); + if (record == nullptr) { + MGLOG_E_ONCE("MGPipe: sampler %u has no applier record on the handle arm, so its " + "parameters cannot be pushed (handle {%u, %u})", + stateSamplerObject->GetExternalIndex(), pushedCso.Slot, pushedCso.Gen); + return; + } + if (m_isInitialized && m_syncedSamplerSerial != 0 && m_syncedSamplerSerial == record->Serial) { + MGLOG_D("Sampler parameters have not changed for sampler ID: %u, skipping sync.", + stateSamplerObject->GetExternalIndex()); + return; + } + m_syncedSamplerSerial = record->Serial; + pushedParams = &record->Params; + } else { + // NO HANDLE CARRIED. Two shapes, and they are told apart by whether this + // object is in the twin registry at all: + // + // * not registered -> a sampler this BACKEND minted for its own use (the + // raw-depth-fetch sampler, DirectGLES.cpp:207-218). The client has never + // seen it, no record can exist for it now or after any package lands, + // and the object IS the authority for server-owned state. Not a seam. + // * registered -> an application sampler whose caller did not carry the + // unit's handle. That is the E-side call-site gap; it is named once and + // the values are taken from the object, which in monolith are the very + // values the client content-addressed, so the picture stays right while + // the gap is visible rather than silent. + if (!MG_Pipe::MGPipeHandleIsNull( + g_backendSamplerObjects.HandleOf(stateSamplerObject.get()))) { + MGLOG_E_ONCE("MGPipe: sampler %u was synced without its unit's SamplerCso handle, so " + "the content-addressed record cannot be named and the object's own " + "parameters are used - the caller must pass " + "MGPipeApplier().BoundSamplerStates[unit]", + stateSamplerObject->GetExternalIndex()); + } + // Spelled exactly as the pre-handle arm below spells it (compare widened, + // assign narrowed) so the two cannot drift on a version past 65535. + const Uint currentSamplerVersion = stateSamplerObject->GetVersion(); + if (m_isInitialized && m_syncedSamplerVersion == currentSamplerVersion) { + MGLOG_D("Sampler parameters have not changed for sampler ID: %u, skipping sync.", + stateSamplerObject->GetExternalIndex()); + return; + } + m_syncedSamplerVersion = currentSamplerVersion; + pushedParams = &stateSamplerObject->GetAllSamplerParameters(); } - if (m_isInitialized && m_syncedSamplerSerial != 0 && m_syncedSamplerSerial == record->Serial) { - MGLOG_D("Sampler parameters have not changed for sampler ID: %u, skipping sync.", - stateSamplerObject->GetExternalIndex()); - return; - } - m_syncedSamplerSerial = record->Serial; - pushedParams = &record->Params; } else { #if !MOBILEGL_PIPE_LEGACY_MEMOS // UNREACHABLE: ResolveSamplerSubsystemArm stops at its first call when the bit is diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 48c57903..3d7c2a34 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -2527,7 +2527,32 @@ namespace MobileGL::MG_Backend::DirectGLES { ~BackendSamplerObject(); BackendSamplerObject(const BackendSamplerObject&) = delete; BackendSamplerObject& operator=(const BackendSamplerObject&) = delete; +#if MOBILEGL_PIPE_PUSH + // THE SAMPLER CSO HANDLE IS CARRIED BY THE CALLER, and it has to be, because a + // SamplerCso is CONTENT-ADDRESSED on the client (D-F1) while this twin is keyed on + // the frontend OBJECT. g_backendSamplerObjects mints a SamplerCso slot off the + // SamplerObject's lifetime id - that handle is this twin's identity and is what + // FindByHandle memos index - but the client's cache allocates its handles by + // CONTENT (MGPipeSlots().Allocate, SamplerEmit.h), so no create_sampler_state ever + // lands at the identity handle and looking a record up by it can only ever miss. + // The carried fact that DOES name the right record is the applier's own + // MGPipeApplier().BoundSamplerStates[unit], which the client writes per unit at + // bind_sampler_states; the caller that knows the unit passes it here. + // + // Defaulted so a caller that has no unit - the backend's OWN raw-depth-fetch + // sampler (DirectGLES.cpp:217), a SamplerObject the client has never seen and for + // which no record can exist - keeps working: that arm reads the object, which is + // the authority for server-owned state. An APPLICATION sampler reaching here + // without a handle is the E-side call-site gap and says so once. + // + // Push-only spelling on purpose: a defaulted parameter is still part of the + // signature, so widening it unconditionally would rename this symbol in the PULL + // build and P4a's admitted-change set is EMPTY (D-P/G1). + void SyncToBackend(const SharedPtr& stateSamplerObject, + MG_Pipe::MGPipeHandle pushedCso = MG_Pipe::kMGPipeNullHandle); +#else void SyncToBackend(const SharedPtr& stateSamplerObject); +#endif void Bind(Uint unit); Uint GetBackendSamplerId() const;