render: surface the sticky-failure fallback (EMULATED readout, F5 retry, forced-failure debug hook)

This commit is contained in:
mchughalex 2026-07-21 03:31:11 +01:00
parent cac49d19ef
commit 77283fe3fb
4 changed files with 56 additions and 7 deletions

View file

@ -1221,12 +1221,14 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) {
void RenderModeIndicator::OnDraw(ImGuiIO& io) { void RenderModeIndicator::OnDraw(ImGuiIO& io) {
// Force-show while the native scene renderer is switched off (F5, the // Force-show while the native scene renderer is switched off (F5, the
// settings Renderer row, or the boot-time hook-layer master): running // settings Renderer row, or the boot-time hook-layer master) or has hit an
// emulated is a degraded state the player should be able to see even with // unrecoverable failure and permanently yields to the emulated output:
// the indicator cvar off. Natural per-frame yields while the scene // running emulated is a degraded state the player should be able to see
// renderer is enabled (menus/loading) do not trigger this. // even with the indicator cvar off. Natural per-frame yields while the
// scene renderer is enabled (menus/loading) do not trigger this.
const bool scene_off = !REXCVAR_GET(skate3_native_render) || const bool scene_off = !REXCVAR_GET(skate3_native_render) ||
!REXCVAR_GET(skate3_native_render_scene); !REXCVAR_GET(skate3_native_render_scene) ||
skate3::native_scene::SceneFailed();
if (!REXCVAR_GET(skate3_native_render_mode_indicator) && !scene_off) { if (!REXCVAR_GET(skate3_native_render_mode_indicator) && !scene_off) {
return; return;
} }

View file

@ -710,6 +710,17 @@ REXCVAR_DEFINE_INT32(skate3_native_render_scene_shadow_static_size, 4096,
"~9 cm. Applies live: the map recreates on change.") "~9 cm. Applies live: the map recreates on change.")
.range(1024, 8192) .range(1024, 8192)
.lifecycle(rex::cvar::Lifecycle::kHotReload); .lifecycle(rex::cvar::Lifecycle::kHotReload);
REXCVAR_DEFINE_INT32(skate3_native_render_scene_debug_fail_static_sun, 0,
"Skate 3",
"DEBUG: make static sun-shadow map creation fail as if "
"the allocation had been rejected, driving the "
"renderer's unrecoverable-failure path (sticky fallback "
"to emulated output). 0 = off, 1 = fail every creation, "
"2 = fail only hot recreates (size changes after a "
"successful first build). Exercises the failure "
"indicator and the shutdown path from the failed state.")
.range(0, 2)
.lifecycle(rex::cvar::Lifecycle::kHotReload);
REXCVAR_DEFINE_BOOL(skate3_native_render_scene_shadow_pcss, true, "Skate 3", REXCVAR_DEFINE_BOOL(skate3_native_render_scene_shadow_pcss, true, "Skate 3",
"Contact-hardening soft shadows (PCSS): a blocker search " "Contact-hardening soft shadows (PCSS): a blocker search "
"estimates the caster distance per pixel and the filter " "estimates the caster distance per pixel and the filter "
@ -2551,6 +2562,10 @@ bool ToggleSceneEnabled() {
} }
const bool enabled = !SceneEnabled(); const bool enabled = !SceneEnabled();
if (enabled) { if (enabled) {
// Re-enabling doubles as the retry gesture after a hard pipeline
// failure (which yields to the emulated output while the cvars stay
// on): clear the latch so EnsurePipeline runs again.
ResetSceneFailure();
// Capture idles while the emulated renderer is active, so anything still // Capture idles while the emulated renderer is active, so anything still
// published is from before the switch away (stale camera). Drop it: // published is from before the switch away (stale camera). Drop it:
// RenderScene yields to the emulated frame until the capture hooks // RenderScene yields to the emulated frame until the capture hooks

View file

@ -650,6 +650,17 @@ void Install();
// layer was not enabled at boot. // layer was not enabled at boot.
bool ToggleSceneEnabled(); bool ToggleSceneEnabled();
// True once the scene renderer has hit an unrecoverable pipeline/resource
// failure and yields to the emulated output until re-enabled. Surfaced so
// the render-mode indicator can show the degraded state even though the
// renderer cvars are still enabled.
bool SceneFailed();
// Clears the sticky failure so the next frame retries the full pipeline
// build (ToggleSceneEnabled calls this on re-enable: a transient allocation
// failure can succeed on retry; a persistent one simply re-latches).
void ResetSceneFailure();
// Debug-dialog cache flushes (F12 native-render debug menu): retire every // Debug-dialog cache flushes (F12 native-render debug menu): retire every
// cached GPU texture / mesh decode so hot-toggled decode settings (mip // cached GPU texture / mesh decode so hot-toggled decode settings (mip
// chains, revalidation, ...) rebuild immediately. // chains, revalidation, ...) rebuild immediately.

View file

@ -106,6 +106,7 @@ 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);
REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_radius); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_radius);
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_shadow_static_size); REXCVAR_DECLARE(int32_t, skate3_native_render_scene_shadow_static_size);
REXCVAR_DECLARE(int32_t, skate3_native_render_scene_debug_fail_static_sun);
REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_strength); REXCVAR_DECLARE(double, skate3_native_render_scene_shadow_static_strength);
REXCVAR_DECLARE(bool, skate3_native_render_scene_shadows); REXCVAR_DECLARE(bool, skate3_native_render_scene_shadows);
REXCVAR_DECLARE(bool, skate3_native_render_scene_shafts); REXCVAR_DECLARE(bool, skate3_native_render_scene_shafts);
@ -3312,6 +3313,13 @@ bool EnsureShadowResources(const NativeGuestOutputRenderContext& context) {
// tiles side by side: inner (r/6, centimeter contact detail with // tiles side by side: inner (r/6, centimeter contact detail with
// useful reach), mid (r/2) and far (full radius, large-caster // useful reach), mid (r/2) and far (full radius, large-caster
// coverage); size is per tile. // coverage); size is per tile.
// Forced-failure debug hook (see the cvar's help text): mode 1 fails
// every creation, mode 2 only hot recreates; retries are skipped so the
// sticky-failed path below engages like a hard driver rejection.
const int32_t debug_fail =
REXCVAR_GET(skate3_native_render_scene_debug_fail_static_sun);
const bool force_fail =
debug_fail == 1 || (debug_fail == 2 && g_r.static_sun_requested != 0);
g_r.static_sun_requested = want_static_size; g_r.static_sun_requested = want_static_size;
g_r.static_sun_size = want_static_size; g_r.static_sun_size = want_static_size;
nrhi::TextureDesc desc; nrhi::TextureDesc desc;
@ -3322,11 +3330,11 @@ bool EnsureShadowResources(const NativeGuestOutputRenderContext& context) {
desc.initial_state = nrhi::ResourceState::kRenderTarget; desc.initial_state = nrhi::ResourceState::kRenderTarget;
desc.clear_color[0] = 1.0f; // depth: far = lit desc.clear_color[0] = 1.0f; // depth: far = lit
desc.clear_color[1] = 1.0f; desc.clear_color[1] = 1.0f;
g_r.static_sun = device->CreateTexture(desc); g_r.static_sun = force_fail ? nullptr : device->CreateTexture(desc);
// Allocation-failure fallback (VRAM pressure, driver limits): a // Allocation-failure fallback (VRAM pressure, driver limits): a
// coarser static sun map beats marking the whole renderer failed and // coarser static sun map beats marking the whole renderer failed and
// dropping the session to emulated output. // dropping the session to emulated output.
while (g_r.static_sun == nullptr && g_r.static_sun_size > 1024) { while (!force_fail && g_r.static_sun == nullptr && g_r.static_sun_size > 1024) {
g_r.static_sun_size /= 2; g_r.static_sun_size /= 2;
desc.width = g_r.static_sun_size * 3; desc.width = g_r.static_sun_size * 3;
desc.height = g_r.static_sun_size; desc.height = g_r.static_sun_size;
@ -10633,6 +10641,17 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat
} // namespace } // namespace
bool SceneFailed() { return g_r.failed; }
void ResetSceneFailure() {
if (g_r.failed) {
g_r.failed = false;
REXLOG_INFO(
"native-scene: sticky pipeline failure cleared; the next frame "
"retries the full pipeline build");
}
}
void Install() { void Install() {
// Registered even when the scene cvar starts off: RenderScene yields to the // Registered even when the scene cvar starts off: RenderScene yields to the
// emulated output while disabled, and the runtime toggle (F5) can flip the // emulated output while disabled, and the runtime toggle (F5) can flip the
@ -10652,6 +10671,8 @@ void Install() {
namespace skate3::native_scene { namespace skate3::native_scene {
void Install() {} void Install() {}
bool SceneFailed() { return false; }
void ResetSceneFailure() {}
} // namespace skate3::native_scene } // namespace skate3::native_scene
#endif // REX_HAS_D3D12 || REX_HAS_VULKAN #endif // REX_HAS_D3D12 || REX_HAS_VULKAN