diff --git a/rpcs3/Emu/RSX/VK/VKFrameGen.cpp b/rpcs3/Emu/RSX/VK/VKFrameGen.cpp index 859f5158f..1c8d135ba 100644 --- a/rpcs3/Emu/RSX/VK/VKFrameGen.cpp +++ b/rpcs3/Emu/RSX/VK/VKFrameGen.cpp @@ -831,6 +831,7 @@ namespace vk::frame_gen } void release_shared_images() {} + void release_device_resources() {} void commit_capture() {} #endif @@ -916,6 +917,46 @@ namespace vk::frame_gen } + void release_device_resources() + { + // Everything here is a child of the VkDevice and MUST die before it does. + // + // Nothing called this. shutdown() only finalised the old dlopen'd library, so at + // emulation stop the fences, command pool, command buffers, allocator, queue and the + // whole pass chain stayed live against a destroyed device -- and worse, on the NEXT boot + // in the same process g_native.valid() was still true, so the stack was not rebuilt: + // generate() then waited on a fence from the dead device and submitted to its queue. + // + // That is the boot -> stop -> boot path, and it is deterministic. It is the most likely + // explanation for frame generation working in one game and failing immediately in the + // next one launched without restarting the app. + destroy_native_stack(); + + for (auto& out : g_shared_out) + { + out.destroy(); + } + + + g_context = -1; + g_context_outputs = 0; + g_shared_w = 0; + g_shared_h = 0; + g_captures = 0; + g_planned_generations = 0; + g_source_image = VK_NULL_HANDLE; + g_source_layout = VK_IMAGE_LAYOUT_UNDEFINED; + g_plan_ready.store(false); + g_recorded_capture.store(false); + g_fresh_capture.store(false); + g_display_fps.store(0.f); + + // A new device may not support what the last one did. + g_disabled = false; + + framegen_log.notice("Frame generation device resources released"); + } + bool capture_presented_frame(const vk::command_buffer& cmd, const vk::render_device& dev, VkImage src, VkImageLayout src_layout, u32 width, u32 height, u32 guest_width, u32 guest_height) diff --git a/rpcs3/Emu/RSX/VK/VKFrameGen.h b/rpcs3/Emu/RSX/VK/VKFrameGen.h index 90e06d064..0b0149298 100644 --- a/rpcs3/Emu/RSX/VK/VKFrameGen.h +++ b/rpcs3/Emu/RSX/VK/VKFrameGen.h @@ -160,6 +160,11 @@ namespace vk::frame_gen // Release the shared images. Safe to call when none exist. void release_shared_images(); + /// Destroy every Vulkan object frame generation owns. MUST be called while the device is + /// still alive -- these are all its children, and they also gate whether the next boot + /// rebuilds rather than reusing handles from a destroyed device. + void release_device_resources(); + // How many frames the current setting asks us to generate between each real pair. // Zero when frame generation is off, unsupported, or has no shaders. u32 generated_frame_count(); diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 40943aad6..00404da15 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -8,6 +8,7 @@ #include "VKCommonPipelineLayout.h" #include "VKCompute.h" #include "VKGSRender.h" +#include "VKFrameGen.h" #include "Emu/RSX/rsx_profiler.h" #include "vkutils/gpu_timer.h" #include "VKHelpers.h" @@ -858,6 +859,11 @@ VKGSRender::~VKGSRender() //Wait for device to finish up with resources vkDeviceWaitIdle(*m_device); + // Frame generation owns fences, a command pool, an allocator and images, all children of this + // device. Released here, while it is still alive: without this they outlived it, and the next + // boot in the same process reused the stale handles because the stack still looked valid. + vk::frame_gen::release_device_resources(); + // Globals. TODO: Refactor lifetime management if (auto async_scheduler = g_fxo->try_get()) {