Preserve TAA pipelines when GPU visibility switch fails

This commit is contained in:
Emil
2026-09-24 06:00:37 +03:00
parent cba949a558
commit 1b0a5ba4d5
2 changed files with 68 additions and 2 deletions
+28 -2
View File
@@ -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<VkPipeline*, 5> pipelines{
&temporal.resolve_pipeline, &temporal.composite_pipeline,
&temporal.direct_pipeline, &temporal.transparent_pipeline,
&temporal.gpu_pipeline};
std::array<VkPipeline, 5> 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();
+40
View File
@@ -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);