fix(video): recreate swapchain after window minimization

This commit is contained in:
boykopovar 2026-09-25 10:49:46 +03:00
parent 46c799da67
commit a7fb4cbf72
5 changed files with 76 additions and 11 deletions

View File

@ -54,6 +54,7 @@ add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/BdaFeatures.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Pm4.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/VulkanDevice.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/SwapchainState.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/AspectFit.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/PresentationScaler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Draw.cpp
@ -281,6 +282,7 @@ if(AGC_BUILD_VISUAL_TEST)
add_executable(agc_driver_visual_test EXCLUDE_FROM_ALL
tests/Visual.cpp
Execution/src/VulkanDevice.cpp
Execution/src/SwapchainState.cpp
Execution/src/AspectFit.cpp
Execution/src/PresentationScaler.cpp
Execution/src/GuestMemory.cpp

View File

@ -0,0 +1,20 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SWAPCHAINSTATE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_SWAPCHAINSTATE_HPP
#include <vulkan/vulkan.h>
namespace AgcDriver {
class SwapchainState {
public:
bool NeedsRecreation() const;
void Recreated();
bool ProcessResult(VkResult result, const char* operation);
private:
bool outdated = false;
};
}
#endif

View File

@ -0,0 +1,28 @@
#include "prx/libSceAgcDriver/Execution/include/SwapchainState.hpp"
#include <stdexcept>
#include <string>
namespace AgcDriver {
bool SwapchainState::NeedsRecreation() const {
return outdated;
}
void SwapchainState::Recreated() {
outdated = false;
}
bool SwapchainState::ProcessResult(VkResult result, const char* operation) {
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
outdated = true;
return false;
}
if (result == VK_SUBOPTIMAL_KHR) {
outdated = true;
return true;
}
if (result != VK_SUCCESS) throw std::runtime_error(std::string(operation) + ": Vulkan result " + std::to_string(result));
return true;
}
}

View File

@ -7,6 +7,7 @@
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
#include "prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp"
#include "prx/libSceAgcDriver/Execution/include/SwapchainState.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp"
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
@ -52,6 +53,7 @@ struct VulkanDevice::State {
void* window = nullptr;
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
SwapchainState swapchainState;
VkExtent2D extent{};
std::vector<VkImage> images;
VkFence acquireFence = VK_NULL_HANDLE;
@ -520,11 +522,18 @@ void VulkanDevice::Resize(std::uint32_t width, std::uint32_t height) {
state->extent = {0, 0};
return;
}
if (state->extent.width == width && state->extent.height == height) return;
WaitIdle();
VkSurfaceCapabilitiesKHR surface{};
check(state->InstanceFunction<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>("vkGetPhysicalDeviceSurfaceCapabilitiesKHR")(state->physical, state->surface, &surface), "vkGetPhysicalDeviceSurfaceCapabilitiesKHR resize");
require(surface.currentExtent.width == std::numeric_limits<std::uint32_t>::max() || (surface.currentExtent.width == width && surface.currentExtent.height == height), "resized surface extent differs from output");
if (surface.currentExtent.width != std::numeric_limits<std::uint32_t>::max()) {
width = surface.currentExtent.width;
height = surface.currentExtent.height;
}
if (width == 0 || height == 0) {
state->extent = {0, 0};
return;
}
if (!state->swapchainState.NeedsRecreation() && state->extent.width == width && state->extent.height == height) return;
WaitIdle();
require(width >= surface.minImageExtent.width && width <= surface.maxImageExtent.width && height >= surface.minImageExtent.height && height <= surface.maxImageExtent.height, "unsupported resized output extent");
require((surface.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0 && (surface.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) != 0 && (surface.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) != 0, "resized surface capabilities are unsupported");
VkSwapchainCreateInfoKHR create{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
@ -553,6 +562,7 @@ void VulkanDevice::Resize(std::uint32_t width, std::uint32_t height) {
check(getImages(state->device, replacement, &count, state->images.data()), "vkGetSwapchainImagesKHR resize");
state->images.resize(count);
state->rendered.assign(count, VK_NULL_HANDLE);
state->swapchainState.Recreated();
}
bool VulkanDevice::Presentable() const {
@ -599,7 +609,7 @@ void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu
check(reset(state->device, static_cast<std::uint32_t>(fences.size()), fences.data()), "vkResetFences");
std::uint32_t index = 0;
timing.Mark("fence_reset");
check(state->DeviceFunction<PFN_vkAcquireNextImageKHR>("vkAcquireNextImageKHR")(state->device, state->swapchain, 5'000'000'000ULL, VK_NULL_HANDLE, state->acquireFence, &index), "vkAcquireNextImageKHR");
if (!state->swapchainState.ProcessResult(state->DeviceFunction<PFN_vkAcquireNextImageKHR>("vkAcquireNextImageKHR")(state->device, state->swapchain, 5'000'000'000ULL, VK_NULL_HANDLE, state->acquireFence, &index), "vkAcquireNextImageKHR")) return;
timing.Mark("acquire_image");
check(wait(state->device, 1, &state->acquireFence, VK_TRUE, std::numeric_limits<std::uint64_t>::max()), "vkWaitForFences acquire");
timing.Mark("acquire_fence_wait");
@ -678,7 +688,7 @@ void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu
present.swapchainCount = 1;
present.pSwapchains = &state->swapchain;
present.pImageIndices = &index;
check(state->DeviceFunction<PFN_vkQueuePresentKHR>("vkQueuePresentKHR")(state->queue, &present), "vkQueuePresentKHR");
state->swapchainState.ProcessResult(state->DeviceFunction<PFN_vkQueuePresentKHR>("vkQueuePresentKHR")(state->queue, &present), "vkQueuePresentKHR");
timing.Mark("queue_present");
}

View File

@ -349,6 +349,11 @@ void VideoOutDriver::processFlip(FlipRequest& req) {
if (!SDL_Vulkan_CreateSurface(static_cast<SDL_Window*>(context), instance, &surface)) throw std::runtime_error(std::string("SDL_Vulkan_CreateSurface failed: ") + SDL_GetError());
return surface;
}, [](void* context, std::uint32_t* width, std::uint32_t* height) {
if ((SDL_GetWindowFlags(static_cast<SDL_Window*>(context)) & SDL_WINDOW_MINIMIZED) != 0) {
*width = 0;
*height = 0;
return;
}
int drawableWidth = 0;
int drawableHeight = 0;
SDL_Vulkan_GetDrawableSize(static_cast<SDL_Window*>(context), &drawableWidth, &drawableHeight);
@ -401,6 +406,12 @@ void VideoOutDriver::presentLoop(std::stop_token token) {
PadInput padInput;
try {
while (!token.stop_requested()) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
require(event.type != SDL_QUIT, "window was closed");
padInput.HandleEvent(event);
}
padInput.Update();
{
std::unique_lock lock(flipQueue->mutex);
flipQueue->changed.wait_for(lock, std::chrono::milliseconds(10), [&] { return token.stop_requested() || flipQueue->failure || !flipQueue->requests.empty(); });
@ -427,12 +438,6 @@ void VideoOutDriver::presentLoop(std::stop_token token) {
current->timing->Print(current->outputHandle, current->index, current->flipArg, finished, interval);
}
current.reset();
SDL_Event event;
while (SDL_PollEvent(&event)) {
require(event.type != SDL_QUIT, "window was closed");
padInput.HandleEvent(event);
}
padInput.Update();
}
std::list<std::shared_ptr<FlipRequest>> cancelled;
{