[Perf] (Espryt): a VAO twin whose buffer set alone moved re-points enabled arrays without the 32-slot enable walk (rd12 monolith 70.3 -> 73.5 fps).

This commit is contained in:
2026-10-07 10:07:43 -04:00
parent 267211e8b4
commit a9108e1a0b
2 changed files with 40 additions and 10 deletions
+36 -10
View File
@@ -4962,10 +4962,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// half (an applier reset is not a slot recycle, so a re-created CSO at the same // 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 // {slot, gen} restarts its ContentSerial at 1). If that rule is ever reverted, this
// gate is unsafe on any application that changes contexts. // gate is unsafe on any application that changes contexts.
const Bool attributesDirty = bufferIdsRemitted || !m_hasSyncedElements || const Bool elementsDirty = bufferIdsRemitted || !m_hasSyncedElements ||
!(m_syncedElementsHandle == st.BoundVertexElements) || !(m_syncedElementsHandle == st.BoundVertexElements) ||
m_syncedElementsSerial != rec->ContentSerial || m_syncedElementsSerial != rec->ContentSerial;
m_syncedVertexBuffersSerial != st.VertexBuffersSerial; const Bool attributesDirty = elementsDirty || m_syncedVertexBuffersSerial != st.VertexBuffersSerial;
const Bool indexBufferDirty = bufferIdsRemitted || m_syncedIndexSerial != st.IndexBufferSerial; const Bool indexBufferDirty = bufferIdsRemitted || m_syncedIndexSerial != st.IndexBufferSerial;
// Emulation is server-owned: the client sends the draw's RAW base instance and never // Emulation is server-owned: the client sends the draw's RAW base instance and never
// learns the answer. Applied here as well as in the applier so the decision is the // learns the answer. Applied here as well as in the applier so the decision is the
@@ -4977,6 +4977,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!emitAttributes && !indexBufferDirty) { if (!emitAttributes && !indexBufferDirty) {
return; return;
} }
// P14: ONLY THE BUFFER SET MOVED (a Minecraft chunk draw: same layout, next chunk's
// buffer). The driver VAO's enable flags are a function of the configuration alone,
// and the last full walk left them exactly as this configuration wants them, so the
// walk below re-points just the enabled attributes and skips the 32 enable/disable
// calls. Only after a walk in which every enabled attribute took the plain pointer
// path: the fp64, zero-stride, BGRA and no-buffer branches toggle enables or bind
// through other state, and the client-memory upload rewrites the VAO behind this
// gate, so any of them sends the next sync through the whole walk again.
const Bool buffersOnly = emitAttributes && !elementsDirty && m_lastWalkWasPlain &&
!m_hasConvertedFloat64Attribute;
Bool walkWasPlain = true;
m_hasConvertedFloat64Attribute = false; m_hasConvertedFloat64Attribute = false;
Bind(); Bind();
@@ -4996,6 +5007,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (Uint attribIndex = 0; attribIndex < MG_Pipe::kMGPipeMaxVertexAttribs && emitAttributes; for (Uint attribIndex = 0; attribIndex < MG_Pipe::kMGPipeMaxVertexAttribs && emitAttributes;
++attribIndex) { ++attribIndex) {
const MGPVertexAttribWire& attrib = rec->Attributes[attribIndex]; const MGPVertexAttribWire& attrib = rec->Attributes[attribIndex];
if (buffersOnly && !attrib.Enabled) continue;
const MG_Pipe::MGPVertexBuffer* binding = const MG_Pipe::MGPVertexBuffer* binding =
VertexBufferForAttributeIndex(st, attribIndex); VertexBufferForAttributeIndex(st, attribIndex);
const Uint32 divisor = binding != nullptr ? binding->Divisor : 0u; const Uint32 divisor = binding != nullptr ? binding->Divisor : 0u;
@@ -5003,11 +5015,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The enable/disable block. On this arm there is no per-attribute version to // The enable/disable block. On this arm there is no per-attribute version to
// compare: the applier's Attributes[] IS what was last pushed, so a moved // compare: the applier's Attributes[] IS what was last pushed, so a moved
// ContentSerial means re-emit and an unchanged one means the early-out above // ContentSerial means re-emit and an unchanged one means the early-out above
// already returned. // already returned. A buffers-only walk leaves the flags as they are (above).
if (attrib.Enabled) { if (!buffersOnly) {
g_GLESFuncs.glEnableVertexAttribArray(attribIndex); if (attrib.Enabled) {
} else { g_GLESFuncs.glEnableVertexAttribArray(attribIndex);
g_GLESFuncs.glDisableVertexAttribArray(attribIndex); } else {
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
}
} }
// The fp64 narrowing, verbatim in behaviour including the Adreno workaround: // The fp64 narrowing, verbatim in behaviour including the Adreno workaround:
@@ -5017,6 +5031,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// carried separately from Type == Float64 on the wire precisely so this test // carried separately from Type == Float64 on the wire precisely so this test
// can still tell the two apart. // can still tell the two apart.
if (attrib.IsLong || attrib.Type == static_cast<Uint32>(DataType::Float64)) { if (attrib.IsLong || attrib.Type == static_cast<Uint32>(DataType::Float64)) {
walkWasPlain = false;
if (attrib.Enabled && attrib.Type == static_cast<Uint32>(DataType::Float64) && if (attrib.Enabled && attrib.Type == static_cast<Uint32>(DataType::Float64) &&
binding != nullptr && binding != nullptr &&
SyncFloat64AttributeAsFloat32ByHandle(attribIndex, attrib, *binding, fetchBaseInstance)) { SyncFloat64AttributeAsFloat32ByHandle(attribIndex, attrib, *binding, fetchBaseInstance)) {
@@ -5036,12 +5051,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
continue; continue;
} }
if (!attrib.Enabled || binding == nullptr) continue; if (!attrib.Enabled) continue;
if (binding == nullptr) {
walkWasPlain = false;
continue;
}
// A resolved stride of zero is the binding model's "never advance" and // A resolved stride of zero is the binding model's "never advance" and
// glVertexAttribPointer cannot say it - its zero means "tightly packed", i.e. // glVertexAttribPointer cannot say it - its zero means "tightly packed", i.e.
// the opposite. ES 3.1's binding-point API can. // the opposite. ES 3.1's binding-point API can.
if (attrib.Stride == 0 && HasVertexBindingApi()) { if (attrib.Stride == 0 && HasVertexBindingApi()) {
walkWasPlain = false;
if (!SyncZeroStrideAttributeByHandle(attribIndex, attrib, *binding)) { if (!SyncZeroStrideAttributeByHandle(attribIndex, attrib, *binding)) {
continue; continue;
} }
@@ -5051,6 +5071,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
if (!BindAttributeBufferByHandle(binding->Res)) { if (!BindAttributeBufferByHandle(binding->Res)) {
walkWasPlain = false;
continue; continue;
} }
@@ -5061,6 +5082,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// format: the per-draw sync must not grow a glGetError round trip for the // format: the per-draw sync must not grow a glGetError round trip for the
// formats real applications use. // formats real applications use.
const Bool formatMayBeRefused = attrib.IsBgra != 0; const Bool formatMayBeRefused = attrib.IsBgra != 0;
if (formatMayBeRefused) walkWasPlain = false;
if (formatMayBeRefused) { if (formatMayBeRefused) {
while (g_GLESFuncs.glGetError() != GL_NO_ERROR) { while (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
} // start from a clean slate so the check below is about THIS call } // start from a clean slate so the check below is about THIS call
@@ -5132,6 +5154,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
if (emitAttributes) { if (emitAttributes) {
m_syncedFetchBaseInstance = fetchBaseInstance; m_syncedFetchBaseInstance = fetchBaseInstance;
m_lastWalkWasPlain = walkWasPlain;
} }
m_syncedBufferIdGeneration = currentBufferIdGeneration; m_syncedBufferIdGeneration = currentBufferIdGeneration;
} }
@@ -5142,6 +5165,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!stateVAOObject || count <= 0 || first < 0) { if (!stateVAOObject || count <= 0 || first < 0) {
return; return;
} }
// This rewrites the driver VAO's arrays behind SyncToBackendFromApplier's gate, so
// the next sync must not take its buffers-only walk.
m_lastWalkWasPlain = false;
Bind(); Bind();
@@ -1392,6 +1392,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// baked into the driver VAO's attribute/element bindings may be dead even // baked into the driver VAO's attribute/element bindings may be dead even
// though every frontend version matches, so the next sync re-emits them all. // though every frontend version matches, so the next sync re-emits them all.
Uint64 m_syncedBufferIdGeneration = 0; Uint64 m_syncedBufferIdGeneration = 0;
// P14: the last attribute walk sent every enabled attribute down the plain pointer
// 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;
}; };
extern TwinRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject, MG_Pipe::MGPipeKind::VertexElementsCso> extern TwinRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject, MG_Pipe::MGPipeKind::VertexElementsCso>