diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 7b3157e2..12fc08f1 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -600,13 +600,26 @@ namespace MobileGL::MG_Backend::DirectGLES { const auto& st = MG_Pipe::MGPipeApplier(); const MG_Pipe::MGPipeHandle elements = st.BoundVertexElements; Uint64 elementsSerial = 0; + Bool haveElementsRecord = false; if (!MG_Pipe::MGPipeHandleIsNull(elements) && elements.Slot < st.VertexElementsCsos.size()) { const auto& record = st.VertexElementsCsos[elements.Slot]; - if (record.Live && record.Gen == elements.Gen) elementsSerial = record.ContentSerial; + if (record.Live && record.Gen == elements.Gen) { + elementsSerial = record.ContentSerial; + haveElementsRecord = true; + } } const Uint64 buffersSerial = st.VertexBuffersSerial; - if (memo && memo->valid && memo->elementsHandle == elements && + // NO LIVE ELEMENTS RECORD IS A MISS, NEVER A HIT. With none, the key above is + // {null, 0, buffersSerial} - a key that describes no configuration at all and that + // NEVER CHANGES while the state stays that way, so a memo stamped with it would hit + // on every later draw of a VAO whose attributes have moved. That state is reachable: + // MGPipeApplierReset() empties VertexElementsCsos and BoundVertexElements at every + // change of the current context, and the client re-emits only at its next + // create/bind. The legacy arm's configuration version caught exactly this by moving. + const Bool memoKeyIsMeaningful = haveElementsRecord; + + if (memo && memo->valid && memoKeyIsMeaningful && memo->elementsHandle == elements && memo->elementsSerial == elementsSerial && memo->buffersSerial == buffersSerial) { if (memo->vboCleanEpoch != bufferEpoch) { Bool allClean = true; @@ -660,7 +673,10 @@ namespace MobileGL::MG_Backend::DirectGLES { memo->elementsHandle = elements; memo->elementsSerial = elementsSerial; memo->buffersSerial = buffersSerial; - memo->valid = true; + // Only a key that describes a real configuration is worth remembering; see + // memoKeyIsMeaningful above. The walk still ran and the buffers are ensured - + // this only refuses to let the NEXT draw skip it. + memo->valid = memoKeyIsMeaningful; // Rebuilt via EnsureBufferResource, not probed clean: the next probe pass // stamps the epoch. memo->vboCleanEpoch = 0; diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index ffd0a047..bd056f01 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -3971,6 +3971,21 @@ namespace MobileGL::MG_Backend::DirectGLES { const Uint64 currentBufferIdGeneration = BufferImpl::g_bufferBackendIdGeneration; const Bool bufferIdsRemitted = m_syncedBufferIdGeneration != currentBufferIdGeneration; + // THIS GATE DEPENDS ON A SERIAL RULE THAT LIVES IN ANOTHER PACKAGE, and it is named + // here because nothing else in this file would say it: MGPipeApplierReset() runs on + // EVERY change of the current GLContext (MG_Impl/Pipe/Tracker.h's `if (m_context != + // &ctx) Reset();`, not only on a fresh one), while this twin SURVIVES the excursion - + // BackendVertexArrayObject has no context-generation member and + // OnBackendContextDestroyed runs on destroy, not on make-current. A reset that sent + // VertexBuffersSerial and IndexBufferSerial back to ZERO would therefore walk them + // back through values this twin has already stamped, and a memo could read clean over + // state the applier had just cleared. MG_Pipe/PipeApply.cpp's reset must ADVANCE + // those two serials instead (wire's C2), which is what makes + // m_syncedVertexBuffersSerial below unable to match after a reset - and that in turn + // forces the whole AND dirty, which is also what rescues the per-record ContentSerial + // half (an applier reset is not a slot recycle, so a re-created CSO at the same + // {slot, gen} restarts its ContentSerial at 1). If that rule is ever reverted, this + // gate is unsafe on any application that changes contexts. const Bool attributesDirty = bufferIdsRemitted || !m_hasSyncedElements || !(m_syncedElementsHandle == st.BoundVertexElements) || m_syncedElementsSerial != rec->ContentSerial ||