From afcf8d96f9ce334c18d16b0082885f049921fe5d Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 7 Oct 2026 12:15:10 -0400 Subject: [PATCH] [Perf] (Espryt): a VAO twin adopts an unchanged buffer set and element buffer instead of re-emitting them (rd12 monolith 75.2 -> 80.5 fps). --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 1 + MobileGL/MG_Backend/DirectGLES/Managers.cpp | 39 ++++++++++++++++++- MobileGL/MG_Backend/DirectGLES/Managers.h | 15 +++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 9f5c894c..f0c72bf5 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -4690,6 +4690,7 @@ namespace MobileGL::MG_Backend::DirectGLES { VertexArrayImpl::BackendVertexArrayObject* vaoTwin = nullptr; Uint32 vaoConfigVersion = 0; vaoTwin = VertexArrayImpl::ResolveVaoTwin(MG_Pipe::MGPipeApplier().BoundVertexElements); + if (vaoTwin != nullptr) vaoTwin->AdoptUnchangedBufferSets(MG_Pipe::MGPipeApplier()); // One program resolve and one texture-key capture serve the whole draw, for // the same reason the twin resolve does: only frontend GL entry points move // either, and none can run inside this preparation. GetProgramForDraw is a diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 63b7e2bb..1f0faf6c 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -5138,6 +5138,8 @@ namespace MobileGL::MG_Backend::DirectGLES { indexBufferSynced = true; } if (indexBufferSynced) { + m_syncedIndexRes = st.IndexBuffer.Res; + m_syncedIndexValid = true; // The two element-array restore scopes (the restart substitution's and // MultiDrawImpl's) put the DRIVER id back without touching this serial, // which is correct: the serial records what the APPLIER last said, and @@ -5151,6 +5153,15 @@ namespace MobileGL::MG_Backend::DirectGLES { m_syncedElementsSerial = rec->ContentSerial; m_syncedVertexBuffersSerial = st.VertexBuffersSerial; m_hasSyncedElements = true; + // The window this walk emitted, for AdoptUnchangedBufferSets. + m_syncedBufferStart = st.VertexBufferStart; + m_syncedBufferCount = std::min(st.VertexBufferCount, + MG_Pipe::kMGPipeMaxVertexAttribs - st.VertexBufferStart); + m_syncedBufferBaseInstance = st.VertexFetchBaseInstance; + for (Uint32 i = 0; i < m_syncedBufferCount; ++i) + m_syncedBuffers[i] = st.VertexBuffers[m_syncedBufferStart + i]; + m_syncedBuffersValid = st.VertexBufferStart <= MG_Pipe::kMGPipeMaxVertexAttribs; + m_syncedContextSerial = st.ContextSerial; } if (emitAttributes) { m_syncedFetchBaseInstance = fetchBaseInstance; @@ -5159,6 +5170,31 @@ namespace MobileGL::MG_Backend::DirectGLES { m_syncedBufferIdGeneration = currentBufferIdGeneration; } + void BackendVertexArrayObject::AdoptUnchangedBufferSets(const MG_Pipe::MGPipeApplierState& st) { + if (!m_hasSyncedElements || m_syncedContextSerial != st.ContextSerial) return; + if (m_syncedBuffersValid && m_syncedVertexBuffersSerial != st.VertexBuffersSerial && + st.VertexBufferStart == m_syncedBufferStart && st.VertexFetchBaseInstance == m_syncedBufferBaseInstance && + std::min(st.VertexBufferCount, MG_Pipe::kMGPipeMaxVertexAttribs - st.VertexBufferStart) == + m_syncedBufferCount) { + Bool same = true; + for (Uint32 i = 0; same && i < m_syncedBufferCount; ++i) { + const auto& a = m_syncedBuffers[i]; + const auto& b = st.VertexBuffers[m_syncedBufferStart + i]; + same = a.Res == b.Res && a.Offset == b.Offset && a.Stride == b.Stride && + a.Divisor == b.Divisor && a.BindingIndex == b.BindingIndex; + } + if (same) { + if (m_resolvedDrawBuffers.valid && m_resolvedDrawBuffers.buffersSerial == m_syncedVertexBuffersSerial) + m_resolvedDrawBuffers.buffersSerial = st.VertexBuffersSerial; + m_syncedVertexBuffersSerial = st.VertexBuffersSerial; + } + } + if (m_syncedIndexValid && m_syncedIndexSerial != st.IndexBufferSerial && m_syncedIndexRes == st.IndexBuffer.Res) { + if (m_resolvedDrawBuffers.iboSerial == m_syncedIndexSerial) m_resolvedDrawBuffers.iboSerial = st.IndexBufferSerial; + m_syncedIndexSerial = st.IndexBufferSerial; + } + } + void BackendVertexArrayObject::SyncClientSideAttributesForDrawArrays( const SharedPtr& stateVAOObject, GLint first, GLsizei count, Uint32 fetchBaseInstance) { @@ -5166,8 +5202,9 @@ namespace MobileGL::MG_Backend::DirectGLES { return; } // This rewrites the driver VAO's arrays behind SyncToBackendFromApplier's gate, so - // the next sync must not take its buffers-only walk. + // the next sync must not take its buffers-only walk, nor adopt an equal buffer set. m_lastWalkWasPlain = false; + m_syncedBuffersValid = false; Bind(); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 9089d6c9..32e85be1 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -1181,6 +1181,13 @@ namespace MobileGL::MG_Backend::DirectGLES { // overload, not defaulted to null") is what this obeys. The body is unchanged and // was already record-only - it is this family's existence proof (scout S1 ยง2). void SyncToBackendFromApplier(); + // P14: the applier's buffer-set serials move whenever ANY VAO's set is published, so + // switching between two VAOs moves them although neither twin's buffers changed. Called + // before the draw's syncs: when the window this twin last emitted is the applier's + // window value for value, take the new serials as already synced (and carry the + // resolved-buffers memo along) instead of re-emitting a driver VAO that already holds + // exactly that. Only within the applier context the twin last synced against. + void AdoptUnchangedBufferSets(const MG_Pipe::MGPipeApplierState& st); void SyncClientSideAttributesForDrawArrays( const SharedPtr& stateVAOObject, GLint first, GLsizei count, Uint32 fetchBaseInstance = 0); @@ -1396,6 +1403,14 @@ namespace MobileGL::MG_Backend::DirectGLES { // path, so the driver VAO's enable flags are exactly the configuration's and a walk // for a moved buffer set alone may skip them (SyncToBackendFromApplier). Bool m_lastWalkWasPlain = false; + // P14: the vertex-buffer window and element buffer as last emitted into the driver + // VAO, for AdoptUnchangedBufferSets. Handles are {slot, gen}, never reused. + Array m_syncedBuffers{}; + Uint32 m_syncedBufferStart = 0, m_syncedBufferCount = 0, m_syncedBufferBaseInstance = 0; + Bool m_syncedBuffersValid = false; + MG_Pipe::MGPipeHandle m_syncedIndexRes = MG_Pipe::kMGPipeNullHandle; + Bool m_syncedIndexValid = false; + Uint64 m_syncedContextSerial = 0; }; extern TwinRegistry