[Perf] (MG_Backend): give DirectVulkan's draw memo a table that fits in cache lines

The per-VAO resolved-bindings map probe was ~45% of
UploadAndBindVertexBuffers' self time, and the aux-memo pointer chase
was the single hottest instruction left in TrySetupDrawFastPath. Both
die together: a fixed 2048-slot two-probe 64B-aligned VaoDrawMemo table
embeds the VAO key, content-hash-validated layout facts and the
bindings payload reordered hot-to-cold. Layout facts hold exactly while
the slot's content hash equals the live VAO's own config-guarded hash;
a recycled VAO address either misses or reproduces a byte-identical
config, for which the facts are correct by construction. Bindings keep
their full per-draw revalidation; recycled slots zero their frame
serials so half-filled entries can never match.

ComputePipelineStateHash, the depth/stencil probe and the
primitive-restart probe now take one bulk GetRenderStateParameters()
fetch instead of ~17 cross-TU accessor calls (verified pure field
reads, identical bit packing). The EBO slice memo gained the same
manager-wide epoch one-compare rescue the vertex half uses.
GetShaderTransformFlags is memoized on pre-transform. Sodium's
MultiDrawElementsBaseVertex hoists GetGLTypeSize out of the
per-sub-draw loop, replaces the division with a shift, and skips
unsupported index types loudly instead of dividing by zero.

Also verified: a GL_BLEND toggle recompiles nothing in steady state -
the glslang frames in earlier state_toggle profiles were startup
contamination.

Quiet-box load-gated 6-round A/B: sodium_multidraw -8.0%, tex_param
-4.1%, use_program -3.3%; steady-state vanilla_draw CPU -20% ns/op at
4096 frames (the 80-frame matrix compresses CPU wins under GPU boost
clocks; profiles confirm UploadAndBindVertexBuffers 6.3% -> 4.4%
including the table probe, and the aux cold-line load gone). The one
matrix flag (pass_switch +7.5%) reversed to -3.2% in 10-pair isolated
re-runs. Unit tests 421/421.
This commit is contained in:
BZLZHH
2026-08-06 14:07:49 -04:00
parent 25a8f51db5
commit 990e518e33
3 changed files with 272 additions and 110 deletions
@@ -16,6 +16,7 @@
#include "MG_Util/Metrics/TextureMetrics.h"
#include "MG_Util/Miscellany/IndexGenerator.h"
#include <atomic>
#include <bit>
#include <cstring>
#include <spirv_reflect.h>
@@ -1450,6 +1451,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
payload.mode = mode;
payload.indexBufferView.indexType = type;
// Loop-invariant: the index type is fixed for the whole multi-draw, so resolve
// its byte size once instead of twice per sub-draw (a cross-TU switch that
// showed up in per-frame profiles of sodium-style 132x32 multi-draws). Index
// sizes are 1/2/4, so the per-sub-draw offset division below reduces to a
// shift - the hardware divide was the hottest instruction of this loop.
const SizeT indexSize = MG_Util::GetGLTypeSize(type);
if (indexSize == 0) {
MGLOG_E("MultiDrawElementsBaseVertex skipped: unsupported index type 0x%x", type);
return;
}
const Uint32 indexSizeShift = static_cast<Uint32>(std::countr_zero(indexSize));
// TODO: allocate draw cmd buf elsewhere
static Vector<DrawIndexedCmdParam> params;
params.clear();
@@ -1464,14 +1477,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
payload.indexBufferView.indexByteOffset = 0;
payload.indexBufferView.indexByteSize =
std::max(reinterpret_cast<SizeT>(indices[i]) + count[i] * MG_Util::GetGLTypeSize(type),
std::max(reinterpret_cast<SizeT>(indices[i]) + count[i] * indexSize,
payload.indexBufferView.indexByteSize);
auto& param = params[i];
param.indexCount = count[i];
param.instanceCount = 1;
param.firstIndex = reinterpret_cast<SizeT>(indices[i]) / MG_Util::GetGLTypeSize(type);
param.firstIndex = reinterpret_cast<SizeT>(indices[i]) >> indexSizeShift;
param.vertexOffset = basevertex[i];
param.firstInstance = 0;
}