render: time the submit-record loop and pre-size per-frame bookkeeping

This commit is contained in:
mchughalex 2026-07-22 11:48:06 +01:00
parent 2aef1222a0
commit 8f8642aec2
3 changed files with 26 additions and 4 deletions

View file

@ -8236,6 +8236,9 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
FrameScene scene; FrameScene scene;
scene.items.reserve(count); scene.items.reserve(count);
std::unordered_set<uint32_t> seen; std::unordered_set<uint32_t> seen;
// Pre-size the per-frame bookkeeping: these fill with thousands of
// entries every frame, and growing from empty rehashes repeatedly.
seen.reserve(count);
// Dynamic contexts are submitted several times per frame (once per pass); // Dynamic contexts are submitted several times per frame (once per pass);
// each submission carries that pass's culled island list. Keep the fullest // each submission carries that pass's culled island list. Keep the fullest
// one; a shadow-pass list can be missing body parts the main view needs. // one; a shadow-pass list can be missing body parts the main view needs.
@ -8253,6 +8256,11 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
for (const DrawEntry& e : d.draws) n += e.index_count; for (const DrawEntry& e : d.draws) n += e.index_count;
return n; return n;
}; };
const bool wloop_prof = REXCVAR_GET(skate3_native_render_scene_perf_items);
const auto wloop_t0 = wloop_prof ? PerfClock::now() : PerfClock::time_point{};
if (wloop_prof) {
g_bi_records.fetch_add(count, std::memory_order_relaxed);
}
for (size_t i = 0; i < count; ++i) { for (size_t i = 0; i < count; ++i) {
const SubmitRecord& r = records[i]; const SubmitRecord& r = records[i];
// Primary opaque list of the chosen view only; other lists (shadow // Primary opaque list of the chosen view only; other lists (shadow
@ -8400,6 +8408,9 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
} }
} }
} }
if (wloop_prof) {
g_pw_bi_wloop.Add(PerfNsSince(wloop_t0));
}
// Cross-frame palette rescue + cache refresh (see g_bones_cache_ctx and // Cross-frame palette rescue + cache refresh (see g_bones_cache_ctx and
// g_ropa_state_cache): published copies refresh their cache entries; a // g_ropa_state_cache): published copies refresh their cache entries; a
// refused/pending capture re-publishes with the cached palette (one frame // refused/pending capture re-publishes with the cached palette (one frame

View file

@ -7081,7 +7081,8 @@ void LogFrameStats(const FrameScene& scene, uint64_t frames, uint32_t drawn,
"grid[valid={} age={}] " "grid[valid={} age={}] "
"stages_us[mesh={:.2f} tex={:.2f} const={:.2f} submit={:.2f} n={}] " "stages_us[mesh={:.2f} tex={:.2f} const={:.2f} submit={:.2f} n={}] "
"build_us[core n={} av={:.2f} fp n={} av={:.2f} walk n={} av={:.2f} " "build_us[core n={} av={:.2f} fp n={} av={:.2f} walk n={} av={:.2f} "
"fetch av={:.2f}] retain[{:.2f}/{:.2f}ms app={} live={}]", "fetch av={:.2f}] wloop[{:.2f}/{:.2f}ms recs={}] "
"retain[{:.2f}/{:.2f}ms app={} live={}]",
g_pw_di_in.count.load(std::memory_order_relaxed), avg_us(g_pw_di_in), g_pw_di_in.count.load(std::memory_order_relaxed), avg_us(g_pw_di_in),
g_pw_di_in.MaxMs(), g_pw_di_in.MaxMs(),
g_pw_di_occ.count.load(std::memory_order_relaxed), g_pw_di_occ.count.load(std::memory_order_relaxed),
@ -7101,8 +7102,10 @@ void LogFrameStats(const FrameScene& scene, uint64_t frames, uint32_t drawn,
avg_us(g_pw_bi_core), avg_us(g_pw_bi_core),
g_pw_bi_fp.count.load(std::memory_order_relaxed), avg_us(g_pw_bi_fp), g_pw_bi_fp.count.load(std::memory_order_relaxed), avg_us(g_pw_bi_fp),
g_pw_bi_walk.count.load(std::memory_order_relaxed), g_pw_bi_walk.count.load(std::memory_order_relaxed),
avg_us(g_pw_bi_walk), avg_us(g_pw_bi_fetch), g_pw_bi_retain.AvgMs(), avg_us(g_pw_bi_walk), avg_us(g_pw_bi_fetch), g_pw_bi_wloop.AvgMs(),
g_pw_bi_retain.MaxMs(), g_pw_bi_wloop.MaxMs(),
g_bi_records.exchange(0, std::memory_order_relaxed),
g_pw_bi_retain.AvgMs(), g_pw_bi_retain.MaxMs(),
g_retained_appended.exchange(0, std::memory_order_relaxed), g_retained_appended.exchange(0, std::memory_order_relaxed),
g_retained_live.load(std::memory_order_relaxed)); g_retained_live.load(std::memory_order_relaxed));
} }
@ -7114,7 +7117,8 @@ void LogFrameStats(const FrameScene& scene, uint64_t frames, uint32_t drawn,
&g_pw_di_in, &g_pw_di_occ, &g_pw_di_out, &g_pw_di_in, &g_pw_di_occ, &g_pw_di_out,
&g_pw_di_mesh, &g_pw_di_tex, &g_pw_di_const, &g_pw_di_mesh, &g_pw_di_tex, &g_pw_di_const,
&g_pw_di_submit, &g_pw_bi_core, &g_pw_bi_fp, &g_pw_di_submit, &g_pw_bi_core, &g_pw_bi_fp,
&g_pw_bi_walk, &g_pw_bi_fetch, &g_pw_bi_retain}) { &g_pw_bi_walk, &g_pw_bi_fetch, &g_pw_bi_wloop,
&g_pw_bi_retain}) {
w->Reset(); w->Reset();
} }
// Refill the settle offender-log + slow-frame budgets for the next window. // Refill the settle offender-log + slow-frame budgets for the next window.

View file

@ -973,6 +973,13 @@ inline PerfWindow g_pw_bi_core; // BuildItemFromMeshCached total (hit validat
inline PerfWindow g_pw_bi_fp; // ComputeItemFingerprint on the cache-hit path inline PerfWindow g_pw_bi_fp; // ComputeItemFingerprint on the cache-hit path
inline PerfWindow g_pw_bi_walk; // full BuildItemFromMesh rebuilds (cache miss) inline PerfWindow g_pw_bi_walk; // full BuildItemFromMesh rebuilds (cache miss)
inline PerfWindow g_pw_bi_fetch; // AdoptDrawFetchOverrides per-frame guest reads inline PerfWindow g_pw_bi_fetch; // AdoptDrawFetchOverrides per-frame guest reads
// Whole submit-record loop of BuildFrameScene (one Add per frame). Contains
// bi_core/bi_fetch plus everything they do not: per-record hash-set
// bookkeeping, the dynamic merge, draw-list copies and DrawItem churn.
// build - b2d - bspl - wloop - bpal - retain - remainder = the post-loop
// tail (camera smoothing, capture blocks, publish).
inline PerfWindow g_pw_bi_wloop;
inline std::atomic<uint64_t> g_bi_records{0}; // submit records per window
// Off-screen retention pass (always measured, one Add per frame: two clock // Off-screen retention pass (always measured, one Add per frame: two clock
// reads): its cost and how many items it re-appends beyond the guest's own // reads): its cost and how many items it re-appends beyond the guest's own
// submission are core visibility-culling telemetry, not deep profiling. // submission are core visibility-culling telemetry, not deep profiling.