diff --git a/src/skate3_native_debug_dialog.cpp b/src/skate3_native_debug_dialog.cpp index bf123ea..a3bd7b4 100644 --- a/src/skate3_native_debug_dialog.cpp +++ b/src/skate3_native_debug_dialog.cpp @@ -1221,12 +1221,14 @@ void NativeDebugDialog::OnDraw(ImGuiIO& io) { void RenderModeIndicator::OnDraw(ImGuiIO& io) { // Force-show while the native scene renderer is switched off (F5, the - // settings Renderer row, or the boot-time hook-layer master): running - // emulated is a degraded state the player should be able to see even with - // the indicator cvar off. Natural per-frame yields while the scene - // renderer is enabled (menus/loading) do not trigger this. + // settings Renderer row, or the boot-time hook-layer master) or has hit an + // unrecoverable failure and permanently yields to the emulated output: + // running emulated is a degraded state the player should be able to see + // 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) || - !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) { return; } diff --git a/src/skate3_native_scene.cpp b/src/skate3_native_scene.cpp index ee143aa..630b984 100644 --- a/src/skate3_native_scene.cpp +++ b/src/skate3_native_scene.cpp @@ -710,6 +710,17 @@ REXCVAR_DEFINE_INT32(skate3_native_render_scene_shadow_static_size, 4096, "~9 cm. Applies live: the map recreates on change.") .range(1024, 8192) .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", "Contact-hardening soft shadows (PCSS): a blocker search " "estimates the caster distance per pixel and the filter " @@ -2551,6 +2562,10 @@ bool ToggleSceneEnabled() { } const bool enabled = !SceneEnabled(); 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 // published is from before the switch away (stale camera). Drop it: // RenderScene yields to the emulated frame until the capture hooks diff --git a/src/skate3_native_scene.h b/src/skate3_native_scene.h index b233a29..7923352 100644 --- a/src/skate3_native_scene.h +++ b/src/skate3_native_scene.h @@ -650,6 +650,17 @@ void Install(); // layer was not enabled at boot. 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 // cached GPU texture / mesh decode so hot-toggled decode settings (mip // chains, revalidation, ...) rebuild immediately. diff --git a/src/skate3_native_scene_gpu.cpp b/src/skate3_native_scene_gpu.cpp index 245b3b7..605acc0 100644 --- a/src/skate3_native_scene_gpu.cpp +++ b/src/skate3_native_scene_gpu.cpp @@ -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_radius); 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(bool, skate3_native_render_scene_shadows); 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 // useful reach), mid (r/2) and far (full radius, large-caster // 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_size = want_static_size; nrhi::TextureDesc desc; @@ -3322,11 +3330,11 @@ bool EnsureShadowResources(const NativeGuestOutputRenderContext& context) { desc.initial_state = nrhi::ResourceState::kRenderTarget; desc.clear_color[0] = 1.0f; // depth: far = lit 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 // coarser static sun map beats marking the whole renderer failed and // 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; desc.width = g_r.static_sun_size * 3; desc.height = g_r.static_sun_size; @@ -10633,6 +10641,17 @@ bool RenderScene(const NativeGuestOutputRenderContext& context, void* /*user_dat } // 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() { // Registered even when the scene cvar starts off: RenderScene yields to the // emulated output while disabled, and the runtime toggle (F5) can flip the @@ -10652,6 +10671,8 @@ void Install() { namespace skate3::native_scene { void Install() {} +bool SceneFailed() { return false; } +void ResetSceneFailure() {} } // namespace skate3::native_scene #endif // REX_HAS_D3D12 || REX_HAS_VULKAN