From 1b0a5ba4d563625df1887a268258cbd05023b369 Mon Sep 17 00:00:00 2001 From: Emil <65846814+emil28092005@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:59:36 +0300 Subject: [PATCH] Preserve TAA pipelines when GPU visibility switch fails --- src/render/renderer.cpp | 30 ++++++++++++++++++++++++-- tests/render_reload_tests.cpp | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/render/renderer.cpp b/src/render/renderer.cpp index 968caa8..5d0dacd 100644 --- a/src/render/renderer.cpp +++ b/src/render/renderer.cpp @@ -4168,11 +4168,37 @@ void Renderer::set_visibility_mode(VisibilityMode mode) { return; if (mode != VisibilityMode::Direct && impl_->scene.available && !impl_->scene.graphics_layout) { + check(vkDeviceWaitIdle(impl_->device), "Wait visibility switch"); try { impl_->make_scene_descriptors_and_pipelines(); if (impl_->temporal.resolve_layout) { - impl_->destroy_temporal_interfaces(); - impl_->make_temporal_interfaces_and_pipelines(); + auto& temporal = impl_->temporal; + // Retain the active pipelines and descriptor interfaces until the + // GPU variant is complete; a bad shader package must leave TAA usable. + const std::array pipelines{ + &temporal.resolve_pipeline, &temporal.composite_pipeline, + &temporal.direct_pipeline, &temporal.transparent_pipeline, + &temporal.gpu_pipeline}; + std::array previous{}; + const auto previous_layouts = temporal.shader_layouts; + const auto previous_scene_layouts = temporal.scene_shader_layouts; + for (std::size_t i = 0; i < pipelines.size(); ++i) + previous[i] = std::exchange(*pipelines[i], VkPipeline{}); + try { + impl_->make_temporal_interfaces_and_pipelines(); + } catch (...) { + for (std::size_t i = 0; i < pipelines.size(); ++i) { + if (*pipelines[i]) + vkDestroyPipeline(impl_->device, *pipelines[i], nullptr); + *pipelines[i] = previous[i]; + } + temporal.shader_layouts = previous_layouts; + temporal.scene_shader_layouts = previous_scene_layouts; + throw; + } + for (auto pipeline : previous) + if (pipeline) + vkDestroyPipeline(impl_->device, pipeline, nullptr); } } catch (...) { impl_->destroy_scene_interfaces(); diff --git a/tests/render_reload_tests.cpp b/tests/render_reload_tests.cpp index 9cdd36a..8a0b5a4 100644 --- a/tests/render_reload_tests.cpp +++ b/tests/render_reload_tests.cpp @@ -130,6 +130,46 @@ int main() { opaque_cube.instance_key = "shader-reload-cube"; opaque_cube.cast_shadow = false; opaque_scene.draws.push_back(opaque_cube); + { + // Starting in Direct+Off defers GPU scene pipelines. Enabling TAA later + // leaves an active temporal direct pipeline during the GPU-mode switch. + render::Renderer switching(configuration); + render::Renderer reference(configuration); + switching.set_temporal_mode(render::TemporalMode::TAA); + reference.set_temporal_mode(render::TemporalMode::TAA); + for (int frame = 0; frame < 2; ++frame) { + switching.render(opaque_scene); + reference.render(opaque_scene); + } + require(switching.stats().effective_temporal_mode == render::TemporalMode::TAA && + switching.stats().temporal_history_valid, + "Visibility rollback fixture must have live TAA history"); + const auto temporal_spirv = read_text(bundle / "temporalResolveMain.spv"); + fs::remove(native_io_path(bundle / "temporalResolveMain.spv")); + bool rejected_temporal_switch = false; + try { + switching.set_visibility_mode(render::VisibilityMode::GpuFrustum); + } catch (const std::exception&) { + rejected_temporal_switch = true; + } + atomic_write(bundle / "temporalResolveMain.spv", temporal_spirv); + require(rejected_temporal_switch, + "Incomplete temporal package must reject a GPU visibility switch"); + switching.render(opaque_scene); + reference.render(opaque_scene); + require(switching.visibility_mode() == render::VisibilityMode::Direct && + switching.stats().effective_temporal_mode == render::TemporalMode::TAA && + switching.stats().validation_errors == 0 && + switching.pixels() == reference.pixels(), + "Rejected GPU visibility switch must preserve active TAA rendering"); + switching.set_visibility_mode(render::VisibilityMode::GpuFrustum); + switching.render(opaque_scene); + require(switching.stats().effective_visibility_mode == + render::VisibilityMode::GpuFrustum && + switching.stats().effective_temporal_mode == render::TemporalMode::TAA && + switching.stats().validation_errors == 0, + "Restored temporal package must allow retrying GPU visibility with TAA"); + } auto temporal_configuration = configuration; temporal_configuration.temporal_mode = render::TemporalMode::TAA; render::Renderer temporal_renderer(temporal_configuration);