diff --git a/src/skate3_native_scene_gpu.cpp b/src/skate3_native_scene_gpu.cpp index ec4b1dd..b527c9f 100644 --- a/src/skate3_native_scene_gpu.cpp +++ b/src/skate3_native_scene_gpu.cpp @@ -6801,6 +6801,7 @@ bool RenderShadowAtlas(const NativeGuestOutputRenderContext& context, cmd->ClearRenderTarget(g_r.world_shadow, ws_clear); std::memcpy(g_r.world_shadow_rows, scene.dynobj_ws, sizeof(g_r.world_shadow_rows)); + g_r.world_shadow_drawn.clear(); if (!g_r.world_shadow_primed) { REXLOG_INFO( "native-scene: world-shadow map primed (dynobj c5/c6/c7 rows)"); @@ -6843,6 +6844,21 @@ bool RenderShadowAtlas(const NativeGuestOutputRenderContext& context, if (mit == g_r.meshes.end()) { continue; } + // Accumulate each item exactly once per map generation: MIN-blend + // re-draws are idempotent, so anything already in the map is pure + // redundant work (the whole static item list re-rendered every 4th + // frame paced this pass). Keyed on mesh + content fingerprint + + // world transform; an in-place content swap or a moved instance + // reads as new and lands on the next accumulation frame. + uint64_t ws_key = 1469598103934665603ull ^ item.mesh; + ws_key = (ws_key ^ mit->second.fingerprint) * 1099511628211ull; + const uint8_t* wb = reinterpret_cast(item.world); + for (size_t bi = 0; bi < sizeof(item.world); ++bi) { + ws_key = (ws_key ^ wb[bi]) * 1099511628211ull; + } + if (!g_r.world_shadow_drawn.insert(ws_key).second) { + continue; + } float constants[52] = {}; std::memcpy(constants, item.world, sizeof(item.world)); float* mvp = constants + 16; diff --git a/src/skate3_native_scene_gpu_internal.h b/src/skate3_native_scene_gpu_internal.h index bbceecf..5003a3a 100644 --- a/src/skate3_native_scene_gpu_internal.h +++ b/src/skate3_native_scene_gpu_internal.h @@ -8,6 +8,8 @@ #include "skate3_native_scene_state.h" +#include + #if (defined(REX_HAS_D3D12) && REX_HAS_D3D12) || (defined(REX_HAS_VULKAN) && REX_HAS_VULKAN) #include @@ -915,6 +917,13 @@ struct RendererState { bool world_shadow_primed = false; float world_shadow_rows[12] = {}; static constexpr uint32_t kWorldShadowSize = 512; + // Items already accumulated into the current map (mesh + world-transform + // hash): MIN-blend re-draws of the same geometry are idempotent, so each + // static item needs to land exactly once per map generation. The periodic + // accumulation pass then only draws newly streamed/decoded items (normally + // zero) instead of the whole static item list. Reset on re-prime (clear). + // Render thread only. + std::unordered_set world_shadow_drawn; static constexpr uint32_t kUiRegionSize = 1u << 20; static constexpr uint32_t kUiRegions = 8; nrhi::Buffer* ui_ring = nullptr; diff --git a/third_party/rexglue-sdk b/third_party/rexglue-sdk index 17caf72..41dd2d5 160000 --- a/third_party/rexglue-sdk +++ b/third_party/rexglue-sdk @@ -1 +1 @@ -Subproject commit 17caf72bddd25a91500436e4df5a32a00a259d73 +Subproject commit 41dd2d5cb4a2648f667365a7984a520959e644a6