render: hold shadow-caster parity through short ortho-capture gaps

This commit is contained in:
mchughalex 2026-07-21 08:11:38 +01:00
parent 5218b5f94c
commit c03ef926eb

View file

@ -753,14 +753,12 @@ REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_shadow_static_bias, 0.08,
"static shadows visibly detach from their casters.") "static shadows visibly detach from their casters.")
.range(0.0, 0.5) .range(0.0, 0.5)
.lifecycle(rex::cvar::Lifecycle::kHotReload); .lifecycle(rex::cvar::Lifecycle::kHotReload);
REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_shadow_static_bias_vk, 0.05, REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_shadow_static_bias_vk, 0.0,
"Skate 3", "Skate 3",
"Additional static sun-shadow receiver bias in METERS " "Additional static sun-shadow receiver bias in METERS "
"applied on the Vulkan backend only. The caster and " "applied on the Vulkan backend only (a tuning margin "
"receiver depth terms compile through different " "for backend shader-toolchain rounding differences; "
"toolchains per backend; their rounding differences " "raise only if acne appears on Vulkan specifically).")
"left borderline texels flipping at map rebuilds on "
"Vulkan (triangular ground flicker while moving).")
.range(0.0, 0.5) .range(0.0, 0.5)
.lifecycle(rex::cvar::Lifecycle::kHotReload); .lifecycle(rex::cvar::Lifecycle::kHotReload);
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_char_shadow_exact, true, REXCVAR_DEFINE_BOOL(skate3_native_render_scene_char_shadow_exact, true,
@ -9937,16 +9935,36 @@ void BuildFrameScene(uint8_t* base, const SubmitRecord* records, size_t count) {
} }
} }
// Game shadow-pass parity (see g_frame_ortho_ctx): mark the items whose // Game shadow-pass parity (see g_frame_ortho_ctx): mark the items whose
// ctx the game submitted through an ortho caster bank this frame. Runs // ctx the game submitted through an ortho caster bank. Runs after every
// after every item source (merge, rescues, gap fill) and is OR-only: // item source (merge, rescues, gap fill) and is OR-only. The ortho
// fresh captures default false, rescued/gap copies keep their stored // capture races the publish (more often the higher the render rate), and
// flag, so a frame whose shadow capture was refused doesn't blink the // a fresh capture defaults false: marking from THIS frame's set alone
// entity's shadow off. // blinked the skater's shadow off for single frames while the body kept
// rendering. A ctx therefore counts as a caster while it was
// ortho-submitted within the last kOrthoHoldFrames build frames;
// never-submitted pieces (the trucker hat) stay non-casters forever.
{
static std::unordered_map<uint32_t, uint64_t> s_ortho_last;
static uint64_t s_ortho_frame = 0;
++s_ortho_frame;
if (s_ortho_last.size() > 4096) {
s_ortho_last.clear();
}
for (uint32_t ctx : ortho_ctx) {
s_ortho_last[ctx] = s_ortho_frame;
}
constexpr uint64_t kOrthoHoldFrames = 60;
for (DrawItem& item : scene.items) { for (DrawItem& item : scene.items) {
if (item.ctx != 0 && ortho_ctx.count(item.ctx) != 0) { if (item.ctx == 0) {
continue;
}
const auto it = s_ortho_last.find(item.ctx);
if (it != s_ortho_last.end() &&
s_ortho_frame - it->second <= kOrthoHoldFrames) {
item.shadow_caster = true; item.shadow_caster = true;
} }
} }
}
g_last_publish_ns.store(std::chrono::duration_cast<std::chrono::nanoseconds>( g_last_publish_ns.store(std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch()) std::chrono::steady_clock::now().time_since_epoch())
.count(), .count(),