render: occlusion-cull statics hidden behind rendered geometry
This commit is contained in:
parent
13447a041f
commit
73c19c8cd3
5 changed files with 69 additions and 9 deletions
|
|
@ -1167,6 +1167,16 @@ REXCVAR_DEFINE_INT32(skate3_native_render_scene_perf_interval, 600, "Skate 3",
|
||||||
.range(60, 6000)
|
.range(60, 6000)
|
||||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
|
|
||||||
|
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_occlusion_cull, true, "Skate 3",
|
||||||
|
"Skip drawing static world items whose bounds are "
|
||||||
|
"provably hidden behind already-rendered geometry (depth-"
|
||||||
|
"grid test, conservative: anything unprovable still "
|
||||||
|
"draws). Shadow passes are exempt, so hidden geometry "
|
||||||
|
"keeps casting shadows. Dense areas with extended world "
|
||||||
|
"streaming spend roughly half their per-item CPU on such "
|
||||||
|
"items.")
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
|
|
||||||
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_perf_items, false, "Skate 3",
|
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_perf_items, false, "Skate 3",
|
||||||
"Deep per-item CPU profiling: adds a perf-items log line "
|
"Deep per-item CPU profiling: adds a perf-items log line "
|
||||||
"per perf window attributing per-item cost to pipeline "
|
"per perf window attributing per-item cost to pipeline "
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ REXCVAR_DECLARE(bool, skate3_native_render_scene_pause_native);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_perf_log);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_perf_log);
|
||||||
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_perf_interval);
|
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_perf_interval);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_perf_items);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_perf_items);
|
||||||
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_occlusion_cull);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_photo_display_yield);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_photo_display_yield);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_photo_grab_native);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_photo_grab_native);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_photo_native);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_photo_native);
|
||||||
|
|
@ -7138,7 +7139,7 @@ void LogFrameStats(const FrameScene& scene, uint64_t frames, uint32_t drawn,
|
||||||
"reused={}] dynobj[valid={} drawn={}] water[valid={} drawn={}] "
|
"reused={}] dynobj[valid={} drawn={}] water[valid={} drawn={}] "
|
||||||
"lw[ctxs={} ents={} stamp={} fade0={} resc={} fill={} pal={} rows={}] "
|
"lw[ctxs={} ents={} stamp={} fade0={} resc={} fill={} pal={} rows={}] "
|
||||||
"refl[pair={} flat={} gate={:#x}] flips={} alt[bones={} shadow={}] "
|
"refl[pair={} flat={} gate={:#x}] flips={} alt[bones={} shadow={}] "
|
||||||
"rows_inst={} pal_srv={}",
|
"rows_inst={} pal_srv={} occl_culled={}",
|
||||||
frames, scene.items.size(), drawn, g_draws_2d.load(), drawn_2d,
|
frames, scene.items.size(), drawn, g_draws_2d.load(), drawn_2d,
|
||||||
drawn_spline, g_draws_spline.load(),
|
drawn_spline, g_draws_spline.load(),
|
||||||
g_draws_2d_other.load(), g_draws_2d_dropped.load(),
|
g_draws_2d_other.load(), g_draws_2d_dropped.load(),
|
||||||
|
|
@ -7172,7 +7173,7 @@ void LogFrameStats(const FrameScene& scene, uint64_t frames, uint32_t drawn,
|
||||||
g_refl_pair.load(), g_refl_flat.load(), g_refl_gate.load(),
|
g_refl_pair.load(), g_refl_flat.load(), g_refl_gate.load(),
|
||||||
g_hair_route_flips.load(), g_hair_bone_alternations.load(),
|
g_hair_route_flips.load(), g_hair_bone_alternations.load(),
|
||||||
g_shadow_alternations.load(), g_char_rows_inst_served.load(),
|
g_shadow_alternations.load(), g_char_rows_inst_served.load(),
|
||||||
g_pal_served_total.load());
|
g_pal_served_total.load(), g_occl_culled.load());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -9580,7 +9581,10 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
// completed slot wins, every completed slot retires. A grid that stops
|
// completed slot wins, every completed slot retires. A grid that stops
|
||||||
// refreshing (AO off, loading flows) goes invalid instead of classifying
|
// refreshing (AO off, loading flows) goes invalid instead of classifying
|
||||||
// against ancient depth.
|
// against ancient depth.
|
||||||
if (prof_items && !g_r.occl_failed && g_r.occl_readback_ptr[1] != nullptr) {
|
const bool occl_cull_wanted =
|
||||||
|
REXCVAR_GET(skate3_native_render_scene_occlusion_cull);
|
||||||
|
if ((prof_items || occl_cull_wanted) && !g_r.occl_failed &&
|
||||||
|
g_r.occl_readback_ptr[1] != nullptr) {
|
||||||
const uint64_t occl_done = context.device->CompletedSubmission();
|
const uint64_t occl_done = context.device->CompletedSubmission();
|
||||||
int newest = -1;
|
int newest = -1;
|
||||||
for (int i = 0; i < 2; ++i) {
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
|
@ -9604,6 +9608,8 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
}
|
}
|
||||||
std::memcpy(g_r.occl_grid_vp, g_r.occl_vp[newest],
|
std::memcpy(g_r.occl_grid_vp, g_r.occl_vp[newest],
|
||||||
sizeof(g_r.occl_grid_vp));
|
sizeof(g_r.occl_grid_vp));
|
||||||
|
std::memcpy(g_r.occl_grid_cam, g_r.occl_cam[newest],
|
||||||
|
sizeof(g_r.occl_grid_cam));
|
||||||
g_r.occl_grid_frame = frame_number;
|
g_r.occl_grid_frame = frame_number;
|
||||||
g_r.occl_grid_valid = true;
|
g_r.occl_grid_valid = true;
|
||||||
for (int i = 0; i < 2; ++i) {
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
|
@ -9691,6 +9697,27 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
REXCVAR_GET(skate3_native_render_scene_entity_fade) &&
|
REXCVAR_GET(skate3_native_render_scene_entity_fade) &&
|
||||||
CharFadeAlpha(it) < 0.999f;
|
CharFadeAlpha(it) < 0.999f;
|
||||||
};
|
};
|
||||||
|
// Occlusion cull: statics provably hidden behind already-rendered
|
||||||
|
// geometry skip the color pass entirely (every scene.items consumer that
|
||||||
|
// must still see them - shadow casters, the world-shadow map, outline,
|
||||||
|
// warm-settle - iterates the list itself and is unaffected). Engaged only
|
||||||
|
// while the grid's camera matches this frame's within 1 m: the stored-
|
||||||
|
// frustum check inside ItemOccludedByGrid already keeps rotation safe
|
||||||
|
// (bounds outside the grid's view classify visible), and past 1 m of
|
||||||
|
// translation the 1-2 frame-old depth could hide content parallax has
|
||||||
|
// revealed. At gameplay speeds the grid refreshes well inside that
|
||||||
|
// budget, so the gate only stands the cull down across teleports and
|
||||||
|
// camera cuts.
|
||||||
|
bool occl_cull_active = occl_cull_wanted && debug_mode == 0 &&
|
||||||
|
g_r.occl_grid_valid;
|
||||||
|
if (occl_cull_active) {
|
||||||
|
float cam_d2 = 0.0f;
|
||||||
|
for (int a = 0; a < 3; ++a) {
|
||||||
|
const float d = scene.cam_pos[a] - g_r.occl_grid_cam[a];
|
||||||
|
cam_d2 += d * d;
|
||||||
|
}
|
||||||
|
occl_cull_active = cam_d2 < 1.0f;
|
||||||
|
}
|
||||||
// Opaque items draw front-to-back (bbox-center distance): early-z rejects
|
// Opaque items draw front-to-back (bbox-center distance): early-z rejects
|
||||||
// occluded pixels before the heavy material PS runs. The game's sort-list
|
// occluded pixels before the heavy material PS runs. The game's sort-list
|
||||||
// order is by render state, not depth; depth-write LESS_EQUAL makes the
|
// order is by render state, not depth; depth-write LESS_EQUAL makes the
|
||||||
|
|
@ -9876,6 +9903,18 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// Occlusion cull (statics only; same classifiability gate as the
|
||||||
|
// profiler): the 1 m depth margin on top of the conservative bbox test
|
||||||
|
// keeps reveal edges safe at speed - deeply hidden mass sits many
|
||||||
|
// meters behind its occluders, so the margin costs almost none of the
|
||||||
|
// win. Ring route 3 = culled.
|
||||||
|
if (occl_cull_active && !item.skinned && !item.ropa &&
|
||||||
|
!item.cloth_quads && item.bones.empty() &&
|
||||||
|
ItemOccludedByGrid(item, 1.0f)) {
|
||||||
|
g_occl_culled.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
stamp_route(3);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// The exact ocean surface (fam 31) is an OPAQUE draw in the game (its
|
// The exact ocean surface (fam 31) is an OPAQUE draw in the game (its
|
||||||
// PS outputs alpha 0 and it writes z-prepass depth); routing it through
|
// PS outputs alpha 0 and it writes z-prepass depth); routing it through
|
||||||
// the sorted alpha sub-pass would composite the blended horizon sheet
|
// the sorted alpha sub-pass would composite the blended horizon sheet
|
||||||
|
|
|
||||||
|
|
@ -642,12 +642,14 @@ struct RendererState {
|
||||||
uint8_t* occl_readback_ptr[2] = {};
|
uint8_t* occl_readback_ptr[2] = {};
|
||||||
uint64_t occl_submission[2] = {};
|
uint64_t occl_submission[2] = {};
|
||||||
float occl_vp[2][16] = {};
|
float occl_vp[2][16] = {};
|
||||||
|
float occl_cam[2][3] = {}; // camera position the grid's frame rendered from
|
||||||
bool occl_pending[2] = {};
|
bool occl_pending[2] = {};
|
||||||
int occl_write_index = 0;
|
int occl_write_index = 0;
|
||||||
bool occl_failed = false;
|
bool occl_failed = false;
|
||||||
// Newest completed grid, CPU-side (render thread only).
|
// Newest completed grid, CPU-side (render thread only).
|
||||||
std::vector<float> occl_grid;
|
std::vector<float> occl_grid;
|
||||||
float occl_grid_vp[16] = {};
|
float occl_grid_vp[16] = {};
|
||||||
|
float occl_grid_cam[3] = {};
|
||||||
uint64_t occl_grid_frame = 0; // frame_number the grid was captured on
|
uint64_t occl_grid_frame = 0; // frame_number the grid was captured on
|
||||||
bool occl_grid_valid = false;
|
bool occl_grid_valid = false;
|
||||||
// HDR pipeline (hdr.hlsl): the scene renders pre-tonemap linear into a
|
// HDR pipeline (hdr.hlsl): the scene renders pre-tonemap linear into a
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
// Cvars defined in skate3_native_scene.cpp.
|
// Cvars defined in skate3_native_scene.cpp.
|
||||||
REXCVAR_DECLARE(double, skate3_menu_blur_sigma);
|
REXCVAR_DECLARE(double, skate3_menu_blur_sigma);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_perf_items);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_perf_items);
|
||||||
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_occlusion_cull);
|
||||||
REXCVAR_DECLARE(bool, skate3_native_render_scene_bloom);
|
REXCVAR_DECLARE(bool, skate3_native_render_scene_bloom);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_bloom_intensity);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_bloom_intensity);
|
||||||
REXCVAR_DECLARE(double, skate3_native_render_scene_bloom_knee);
|
REXCVAR_DECLARE(double, skate3_native_render_scene_bloom_knee);
|
||||||
|
|
@ -442,12 +443,15 @@ bool ApplySsaoPass(const NativeGuestOutputRenderContext& context,
|
||||||
nrhi::ResourceState::kRenderTarget);
|
nrhi::ResourceState::kRenderTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Occlusion-attribution grid (perf-items profiling): while the linear
|
// Occlusion grid (perf-items attribution AND the occlusion cull): while
|
||||||
// depth is still bound as an SRV, tile-MAX reduce it into the small R32F
|
// the linear depth is still bound as an SRV, tile-MAX reduce it into the
|
||||||
// grid and queue a never-waited copy into the readback ring (the
|
// small R32F grid and queue a never-waited copy into the readback ring
|
||||||
// photo-grab pattern; consumed 1-2 frames later by the item classifier
|
// (the photo-grab pattern; consumed 1-2 frames later by the item
|
||||||
// in RenderScene). Any creation failure disables only this attribution.
|
// classifier / cull in RenderScene). Any creation failure disables only
|
||||||
if (REXCVAR_GET(skate3_native_render_scene_perf_items) && !g_r.occl_failed) {
|
// these consumers.
|
||||||
|
if ((REXCVAR_GET(skate3_native_render_scene_perf_items) ||
|
||||||
|
REXCVAR_GET(skate3_native_render_scene_occlusion_cull)) &&
|
||||||
|
!g_r.occl_failed) {
|
||||||
if (g_r.pso_occl_reduce == nullptr) {
|
if (g_r.pso_occl_reduce == nullptr) {
|
||||||
nrhi::Shader* rvs = device->CreateShader(
|
nrhi::Shader* rvs = device->CreateShader(
|
||||||
MakeShaderDesc(nrhi::ShaderStage::kVertex, "ssao.hlsl",
|
MakeShaderDesc(nrhi::ShaderStage::kVertex, "ssao.hlsl",
|
||||||
|
|
@ -536,6 +540,7 @@ bool ApplySsaoPass(const NativeGuestOutputRenderContext& context,
|
||||||
nrhi::ResourceState::kRenderTarget);
|
nrhi::ResourceState::kRenderTarget);
|
||||||
cmd->FlushBarriers();
|
cmd->FlushBarriers();
|
||||||
std::memcpy(g_r.occl_vp[w], scene.view_proj, sizeof(g_r.occl_vp[w]));
|
std::memcpy(g_r.occl_vp[w], scene.view_proj, sizeof(g_r.occl_vp[w]));
|
||||||
|
std::memcpy(g_r.occl_cam[w], scene.cam_pos, sizeof(g_r.occl_cam[w]));
|
||||||
g_r.occl_submission[w] = device->CurrentSubmission();
|
g_r.occl_submission[w] = device->CurrentSubmission();
|
||||||
g_r.occl_pending[w] = true;
|
g_r.occl_pending[w] = true;
|
||||||
g_r.occl_write_index = 1 - w;
|
g_r.occl_write_index = 1 - w;
|
||||||
|
|
|
||||||
|
|
@ -960,6 +960,10 @@ inline std::atomic<uint64_t> g_vis_in_indices{0}; // indices submitted, visibl
|
||||||
inline std::atomic<uint64_t> g_vis_occ_indices{0}; // indices submitted, occluded items
|
inline std::atomic<uint64_t> g_vis_occ_indices{0}; // indices submitted, occluded items
|
||||||
inline std::atomic<uint64_t> g_vis_out_indices{0}; // indices submitted, out-of-frustum items
|
inline std::atomic<uint64_t> g_vis_out_indices{0}; // indices submitted, out-of-frustum items
|
||||||
inline std::atomic<uint64_t> g_vis_out_retained{0}; // out-of-frustum draws from retention
|
inline std::atomic<uint64_t> g_vis_out_retained{0}; // out-of-frustum draws from retention
|
||||||
|
// Session-cumulative count of statics skipped by the occlusion cull (the
|
||||||
|
// skate3_native_render_scene_occlusion_cull cvar; reported on the frame
|
||||||
|
// stats line like the other cumulative counters there).
|
||||||
|
inline std::atomic<uint64_t> g_occl_culled{0};
|
||||||
// Guest render thread: decomposition of the per-item build walk.
|
// Guest render thread: decomposition of the per-item build walk.
|
||||||
inline PerfWindow g_pw_bi_core; // BuildItemFromMeshCached total (hit validate + misses)
|
inline PerfWindow g_pw_bi_core; // BuildItemFromMeshCached total (hit validate + misses)
|
||||||
inline PerfWindow g_pw_bi_fp; // ComputeItemFingerprint on the cache-hit path
|
inline PerfWindow g_pw_bi_fp; // ComputeItemFingerprint on the cache-hit path
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue