From b6d9442b456b7ce20f5a779dc966b65ac3f696fc Mon Sep 17 00:00:00 2001 From: mchughalex <25671999+mchughalex@users.noreply.github.com> Date: Tue, 21 Jul 2026 07:57:12 +0100 Subject: [PATCH] render: add a Vulkan-only static sun receiver bias margin for toolchain rounding differences --- src/skate3_native_scene.cpp | 10 ++++++++++ src/skate3_native_scene_gpu.cpp | 10 +++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/skate3_native_scene.cpp b/src/skate3_native_scene.cpp index 9494e9e..86cdece 100644 --- a/src/skate3_native_scene.cpp +++ b/src/skate3_native_scene.cpp @@ -753,6 +753,16 @@ REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_shadow_static_bias, 0.08, "static shadows visibly detach from their casters.") .range(0.0, 0.5) .lifecycle(rex::cvar::Lifecycle::kHotReload); +REXCVAR_DEFINE_DOUBLE(skate3_native_render_scene_shadow_static_bias_vk, 0.05, + "Skate 3", + "Additional static sun-shadow receiver bias in METERS " + "applied on the Vulkan backend only. The caster and " + "receiver depth terms compile through different " + "toolchains per backend; their rounding differences " + "left borderline texels flipping at map rebuilds on " + "Vulkan (triangular ground flicker while moving).") + .range(0.0, 0.5) + .lifecycle(rex::cvar::Lifecycle::kHotReload); REXCVAR_DEFINE_BOOL(skate3_native_render_scene_char_shadow_exact, true, "Skate 3", "Characters sample the CSM atlas with the game's own math: " diff --git a/src/skate3_native_scene_gpu.cpp b/src/skate3_native_scene_gpu.cpp index d03e3bd..3d37082 100644 --- a/src/skate3_native_scene_gpu.cpp +++ b/src/skate3_native_scene_gpu.cpp @@ -104,6 +104,7 @@ REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_pcss_max_m); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_pcss_min_texel); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_pcss_sun_deg); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_bias); +REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_bias_vk); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_radius); REXCVAR_DECLARE(int32_t, skate3_native_render_scene_shadow_static_size); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_strength); @@ -7548,8 +7549,15 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat float(REXCVAR_GET( skate3_native_render_scene_shadow_pcss_min_texel)) / float(std::max(1u, g_r.static_sun_size)); - const float bias_m = float( + float bias_m = float( REXCVAR_GET(skate3_native_render_scene_shadow_static_bias)); + if (context.device->backend() == nrhi::Backend::kVulkan) { + // The two backends compile the caster/receiver depth math through + // different shader toolchains; their rounding differences leave + // borderline texels flipping at map rebuilds without this margin. + bias_m += float( + REXCVAR_GET(skate3_native_render_scene_shadow_static_bias_vk)); + } cb[160] = bias_m / g_r.nsm_depth_range; // base receiver bias cb[161] = bias_m / g_r.nsm_depth_range; // slope-scaled term cb[162] = 0.0f; // reserved (cascade ratios are shader constants)