diff --git a/src/skate3_native_scene.cpp b/src/skate3_native_scene.cpp index af75b37..fc8fd3e 100644 --- a/src/skate3_native_scene.cpp +++ b/src/skate3_native_scene.cpp @@ -8236,6 +8236,9 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) { FrameScene scene; scene.items.reserve(count); std::unordered_set 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); // 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. @@ -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; 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) { const SubmitRecord& r = records[i]; // 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 // g_ropa_state_cache): published copies refresh their cache entries; a // refused/pending capture re-publishes with the cached palette (one frame diff --git a/src/skate3_native_scene_gpu.cpp b/src/skate3_native_scene_gpu.cpp index 5cf06c8..b233a3e 100644 --- a/src/skate3_native_scene_gpu.cpp +++ b/src/skate3_native_scene_gpu.cpp @@ -7081,7 +7081,8 @@ void LogFrameStats(const FrameScene& scene, uint64_t frames, uint32_t drawn, "grid[valid={} age={}] " "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} " - "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.MaxMs(), 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), 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), - avg_us(g_pw_bi_walk), avg_us(g_pw_bi_fetch), g_pw_bi_retain.AvgMs(), - g_pw_bi_retain.MaxMs(), + avg_us(g_pw_bi_walk), avg_us(g_pw_bi_fetch), g_pw_bi_wloop.AvgMs(), + 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_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_mesh, &g_pw_di_tex, &g_pw_di_const, &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(); } // Refill the settle offender-log + slow-frame budgets for the next window. diff --git a/src/skate3_native_scene_state.h b/src/skate3_native_scene_state.h index 4519d48..5c2652f 100644 --- a/src/skate3_native_scene_state.h +++ b/src/skate3_native_scene_state.h @@ -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_walk; // full BuildItemFromMesh rebuilds (cache miss) 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 g_bi_records{0}; // submit records per window // 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 // submission are core visibility-culling telemetry, not deep profiling.