diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index b91ad9fe..99d0d58e 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -11206,6 +11206,40 @@ namespace MobileGL::MG_Backend::DirectGLES { ReseedShaderStorageBlockBindings(m_backendProgramId, *stateProgramObject); m_syncedLinkVersion = stateProgramObject->GetLinkVersion(); m_syncedImageUnitVersion = stateProgramObject->GetImageUnitVersion(); +#if MOBILEGL_PIPE_PUSH + // P4a (D-B3): the ShaderCso record's Serial, stamped in the same breath as the two + // frontend versions it replaces. GetSyncedShaderCsoSerial() beside + // GetSyncedLinkVersion()/GetSyncedImageUnitVersion() is what the draw path's + // nine-clause rebuild condition reads on the handle arm; the clause COUNT does not + // shrink, its inputs move (D-H5). + // + // WHAT DOES *NOT* MOVE, and it is a design statement rather than an omission: the + // ARTEFACTS. D-H3 rules that in monolith all seven MGPBlobRefs are declared with + // Size 0 - "this record does not declare its blob" - and the LinkArtifacts / + // SpirvArtifacts ride beside the record through the entry point's companion + // pointers, so the applier stores the DESCRIPTOR and the identity and the server + // reads the frontend's own archive. That is what keeps the codec off the monolith + // hot path entirely, and it is why every artefact read above is still a read of + // stateProgramObject. The verify build is where the codec is exercised, by + // serialising, deserialising and field-comparing before storing. + if (ProgramSubsystemEnabled()) { + // MONOLITH GLUE: the ShaderCso handle of a program this backend still arrives + // holding. The reader hides the composite band, so a program-pipeline composite + // - which the server must never learn is one - resolves through the same call. + const MG_Pipe::MGPipeHandle cso = g_backendProgramObjects.HandleOf(stateProgramObject.get()); + const auto* record = PipeShaderCsoRecordForHandle(cso); + if (record != nullptr) { + m_syncedShaderCsoSerial = record->Serial; + } else { + // NOT a fall-back and not a silent zero: a stamped 0 would make every later + // serial compare fire for ever, which is the safe direction but hides the + // missing record. Name it and leave the memo where it was. + MGLOG_E_ONCE("MGPipe: program %u has no shader-CSO applier record on the handle " + "arm, so its synced serial cannot be stamped (handle {%u, %u})", + stateProgramObject->GetExternalIndex(), cso.Slot, cso.Gen); + } + } +#endif m_isInitialized = true; MGLOG_D("Program sync completed. backend ID %u", m_backendProgramId); @@ -11431,6 +11465,57 @@ namespace MobileGL::MG_Backend::DirectGLES { return; } + // P4a (D-F1): a SamplerObject is a pure 100-byte value with no driver-side per-object + // binding state, so its CSO is CONTENT-ADDRESSED on the client at capacity 256 and + // two identical samplers share one record. What that means here is that the record's + // server-owned Serial, not the frontend object's version, is what says the values + // moved - and that the values themselves cross byte for byte INCLUDING + // borderColorForm, which is why all four border comparands below are still compared. + // + // The whole arm choice is a preprocessor #if/#else so the PULL build's text is the + // pre-P4a text token for token (D-P). +#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; + } + 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 + // clear and the pre-handle arm is not compiled. Kept, and kept loud. + MGLOG_E_ONCE("MGPipe: the sampler subsystem bit is clear and " + "MOBILEGL_PIPE_LEGACY_MEMOS=0 removed the pre-handle sampler-version " + "memo, so this configuration has no arm at all"); + return; +#else + 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(); +#endif + } +#else Uint currentSamplerVersion = stateSamplerObject->GetVersion(); if (m_isInitialized && m_syncedSamplerVersion == currentSamplerVersion) { MGLOG_D("Sampler parameters have not changed for sampler ID: %u, skipping sync.", @@ -11439,11 +11524,16 @@ namespace MobileGL::MG_Backend::DirectGLES { } m_syncedSamplerVersion = currentSamplerVersion; +#endif MGLOG_D("Syncing sampler with backend ID %u to backend for state ID %u", m_backendSamplerId, stateSamplerObject->GetExternalIndex()); +#if MOBILEGL_PIPE_PUSH + const SamplerParameters& samplerParams = *pushedParams; +#else const auto& samplerParams = stateSamplerObject->GetAllSamplerParameters(); +#endif #define SYNC_SAMPLER_PARAM_IF_CHANGED(internalName, glName, type) \ if (m_cacheSamplerParameters.internalName != samplerParams.internalName) { \ diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 67a1148e..c74e60f7 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -2254,6 +2254,22 @@ namespace MobileGL::MG_Backend::DirectGLES { // stale as one built before a relink - while the sampler half, which really is // re-issued per draw, needs nothing of the sort. Uint32 GetSyncedImageUnitVersion() const { return m_syncedImageUnitVersion; } +#if MOBILEGL_PIPE_PUSH + // P4a (D-B3, D-H5): the ShaderCso record's Serial this backend program was built + // from. It is what the draw path's nine-clause rebuild condition reads on the handle + // arm INSTEAD OF the two frontend versions above - one server-owned counter that + // moves on every create_shader_state the applier applies to this handle, including a + // RE-create on the same handle, which is how a relink travels (Gen moves only on slot + // reuse, never on a respecify). + // + // THE CLAUSE COUNT DOES NOT SHRINK, and a brief that treated create_shader_state as + // self-contained would produce a per-draw rebuild: the other eight inputs - the draw + // FBO's snorm/unorm clamp masks, the fragColor broadcast count, the storage-block + // binding signature, the atomic-counter set, the live image formats and the patch + // parameters - are all still specialised at the verb, from state this backend holds. + // 0 means "never stamped", which is a guaranteed miss (applier serials start at 1). + Uint64 GetSyncedShaderCsoSerial() const { return m_syncedShaderCsoSerial; } +#endif // Whether the (unit, bound format) pairs this program's FORMAT-LESS image uniforms // resolve to are still the ones its ESSL was generated against. // @@ -2353,6 +2369,11 @@ namespace MobileGL::MG_Backend::DirectGLES { BufferImpl::UboRingAllocation m_globalUboRingAllocation; Uint32 m_syncedLinkVersion = ~0u; Uint32 m_syncedImageUnitVersion = ~0u; +#if MOBILEGL_PIPE_PUSH + // P4a's replacement for the two above on the handle arm; see GetSyncedShaderCsoSerial. + // Push-only, so the pull build's object is byte-for-byte the pre-P4a one (D-P). + Uint64 m_syncedShaderCsoSerial = 0; +#endif // Image units addressed by the program's FORMAT-LESS image uniforms, and the digest // of the (unit, format) pairs the generated ESSL baked. Empty/0 for every program // that declares a format on all of its images, which is the overwhelming majority - @@ -2466,6 +2487,19 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool m_isInitialized = false; SamplerParameters m_cacheSamplerParameters; Uint16 m_syncedSamplerVersion = 0; +#if MOBILEGL_PIPE_PUSH + // P4a (D-B3): the SamplerCso record's Serial at the last completed sync. It replaces + // m_syncedSamplerVersion, which stays beside it because the pre-handle arm compiles + // under MOBILEGL_PIPE_LEGACY_MEMOS through P3a/P4a (ARCHITECTURE.md:369). + // + // The two are not interchangeable and that is the point: the frontend version is per + // OBJECT, while the serial is per CONTENT-ADDRESSED CSO, and two frontend samplers + // with identical parameters share one CSO and therefore one serial - so under the + // handle arm the second of them costs no driver call at all. + // + // Push-only, so the pull build's object is byte-for-byte the pre-P4a one (D-P). + Uint64 m_syncedSamplerSerial = 0; +#endif }; void UnbindSampler(Uint unit);