[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).

This commit is contained in:
2026-10-07 12:15:10 -04:00
parent e6281b26de
commit afcf8d96f9
3 changed files with 54 additions and 1 deletions
@@ -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
+38 -1
View File
@@ -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<Uint32>(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<Uint32>(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<MG_State::GLState::VertexArrayObject>& 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();
+15
View File
@@ -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<MG_State::GLState::VertexArrayObject>& 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<MG_Pipe::MGPVertexBuffer, MG_Pipe::kMGPipeMaxVertexAttribs> 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<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject, MG_Pipe::MGPipeKind::VertexElementsCso>