feat(agc): select the swapchain present mode with ANYPS5_PRESENT_MODE

This commit is contained in:
fma-rc 2026-10-12 01:22:43 +02:00
parent 2ccbd376f8
commit 21b9e2efeb
No known key found for this signature in database
5 changed files with 84 additions and 3 deletions

View File

@ -657,6 +657,7 @@ add_test_executable(agc_driver_flip_tests EXCLUDE_FROM_ALL
target_include_directories(agc_driver_flip_tests PRIVATE
${LIBS_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include
)
target_link_libraries(agc_driver_flip_tests PRIVATE
@ -1153,10 +1154,11 @@ if(BUILD_TESTING)
set_tests_properties(agc_driver_pm4_priority_refused PROPERTIES ENVIRONMENT
"ANYPS5_NO_SHADER_CACHE=1;APS5_THREAD_PRIORITY=high;DBUS_SYSTEM_BUS_ADDRESS=unix:path=/nonexistent/aps5-no-rtkit" SKIP_RETURN_CODE 77)
endif()
add_test(NAME agc_driver_present_mode COMMAND agc_driver_flip_tests present-mode)
add_test(NAME agc_driver_flip_compute COMMAND agc_driver_flip_tests compute)
set_tests_properties(agc_driver_flip_compute PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77)
if(WIN32)
set_tests_properties(agc_driver_flip_compute PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_flip_compute agc_driver_present_mode PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_draw_cache_reuse_off COMMAND agc_driver_draw_cache_reuse_tests)

View File

@ -0,0 +1,27 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PRESENTMODE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PRESENTMODE_HPP
#include <vulkan/vulkan.h>
#include <algorithm>
#include <span>
#include <stdexcept>
#include <string_view>
namespace AgcDriver {
inline VkPresentModeKHR SelectPresentMode(const char* requested, std::span<const VkPresentModeKHR> supported) {
VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR;
if (requested != nullptr) {
const std::string_view name(requested);
if (name == "mailbox") mode = VK_PRESENT_MODE_MAILBOX_KHR;
else if (name == "immediate") mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
else if (name == "fifo_latest_ready") mode = VK_PRESENT_MODE_FIFO_LATEST_READY_KHR;
else if (name != "fifo") throw std::runtime_error("Vulkan presentation: ANYPS5_PRESENT_MODE must be fifo, fifo_latest_ready, mailbox or immediate");
}
if (std::find(supported.begin(), supported.end(), mode) == supported.end()) throw std::runtime_error("Vulkan presentation: the surface does not support the requested present mode");
return mode;
}
}
#endif

View File

@ -13,6 +13,7 @@
#include "prx/libSceAgcDriver/Execution/include/PipelineLinkOptimization.hpp"
#include "prx/libSceAgcDriver/Execution/include/SubgroupClock.hpp"
#include "prx/libSceAgcDriver/Execution/include/ShaderDeviceProfile.hpp"
#include "prx/libSceAgcDriver/Execution/include/PresentMode.hpp"
#include "prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Graphics/include/PlaceholderImages.hpp"
@ -241,6 +242,7 @@ struct VulkanDevice::State {
VkExtent2D extent{};
std::vector<VkImage> images;
VkFence acquireFence = VK_NULL_HANDLE;
VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
struct RetiredSwapchain {
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
std::vector<VkSemaphore> rendered;
@ -1167,6 +1169,16 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
}
if (state->sampleLocationSampleCounts != 0) deviceExtensions.push_back(VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME);
}
const char* fifoLatestReadyExtension = hasExtension(VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME) ? VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME : hasExtension("VK_EXT_present_mode_fifo_latest_ready") ? "VK_EXT_present_mode_fifo_latest_ready" : nullptr;
const char* requestedPresentMode = std::getenv("ANYPS5_PRESENT_MODE");
VkPhysicalDevicePresentModeFifoLatestReadyFeaturesKHR fifoLatestReadyFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_MODE_FIFO_LATEST_READY_FEATURES_KHR};
bool fifoLatestReady = false;
if (requestedPresentMode != nullptr && std::strcmp(requestedPresentMode, "fifo_latest_ready") == 0 && fifoLatestReadyExtension != nullptr) {
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &fifoLatestReadyFeatures};
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
fifoLatestReady = fifoLatestReadyFeatures.presentModeFifoLatestReady == VK_TRUE;
if (fifoLatestReady) deviceExtensions.push_back(fifoLatestReadyExtension);
}
// Guest dispatches cover whole thread groups past the edge of small images; robust image access
// drops those writes instead of faulting the device.
const bool imageRobustness = hasExtension(VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME);
@ -1436,6 +1448,12 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
minLodFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
deviceInfo.pNext = &minLodFeatures;
}
fifoLatestReadyFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_MODE_FIFO_LATEST_READY_FEATURES_KHR};
fifoLatestReadyFeatures.presentModeFifoLatestReady = VK_TRUE;
if (fifoLatestReady) {
fifoLatestReadyFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
deviceInfo.pNext = &fifoLatestReadyFeatures;
}
if (state->maintenance8) {
maintenance8Features.pNext = const_cast<void*>(deviceInfo.pNext);
deviceInfo.pNext = &maintenance8Features;
@ -1612,6 +1630,13 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
std::vector<VkSurfaceFormatKHR> formats(formatCount);
check(getFormats(selected, state->surface, &formatCount, formats.data()), "vkGetPhysicalDeviceSurfaceFormatsKHR");
require(std::any_of(formats.begin(), formats.end(), [](const auto& format) { return format.format == VK_FORMAT_B8G8R8A8_UNORM && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; }), "BGRA8 sRGB-nonlinear surface format is unavailable");
std::uint32_t presentModeCount = 0;
auto getPresentModes = state->InstanceFunction<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR>("vkGetPhysicalDeviceSurfacePresentModesKHR");
check(getPresentModes(selected, state->surface, &presentModeCount, nullptr), "vkGetPhysicalDeviceSurfacePresentModesKHR");
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
check(getPresentModes(selected, state->surface, &presentModeCount, presentModes.data()), "vkGetPhysicalDeviceSurfacePresentModesKHR");
presentModes.resize(presentModeCount);
state->presentMode = SelectPresentMode(std::getenv("ANYPS5_PRESENT_MODE"), presentModes);
VkSwapchainCreateInfoKHR swapchain{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
swapchain.surface = state->surface;
swapchain.minImageCount = surface.minImageCount;
@ -1623,7 +1648,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
swapchain.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchain.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
swapchain.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
swapchain.presentMode = VK_PRESENT_MODE_FIFO_KHR;
swapchain.presentMode = state->presentMode;
swapchain.clipped = VK_FALSE;
check(state->DeviceFunction<PFN_vkCreateSwapchainKHR>("vkCreateSwapchainKHR")(state->device, &swapchain, nullptr, &state->swapchain), "vkCreateSwapchainKHR");
std::uint32_t imageCount = 0;
@ -2302,7 +2327,7 @@ void VulkanDevice::Resize(std::uint32_t width, std::uint32_t height) {
create.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
create.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
create.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
create.presentMode = VK_PRESENT_MODE_FIFO_KHR;
create.presentMode = state->presentMode;
create.oldSwapchain = state->swapchain;
state->retiredSwapchains.reserve(state->retiredSwapchains.size() + 1);
VkSwapchainKHR replacement = VK_NULL_HANDLE;

View File

@ -1,4 +1,5 @@
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/PresentMode.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp"
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
@ -298,8 +299,32 @@ void testReset(bool compute) {
}
void testPresentMode() {
using AgcDriver::SelectPresentMode;
const std::array wayland{VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR};
const std::array all{VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_KHR};
check(SelectPresentMode(nullptr, wayland) == VK_PRESENT_MODE_FIFO_KHR, "the default present mode is not FIFO");
check(SelectPresentMode("fifo", wayland) == VK_PRESENT_MODE_FIFO_KHR, "fifo was not selected");
check(SelectPresentMode("immediate", wayland) == VK_PRESENT_MODE_IMMEDIATE_KHR, "immediate was not selected");
check(SelectPresentMode("mailbox", all) == VK_PRESENT_MODE_MAILBOX_KHR, "mailbox was not selected");
const std::array latest{VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_LATEST_READY_KHR};
check(SelectPresentMode("fifo_latest_ready", latest) == VK_PRESENT_MODE_FIFO_LATEST_READY_KHR, "fifo_latest_ready was not selected");
check(SelectPresentMode(nullptr, latest) == VK_PRESENT_MODE_FIFO_KHR, "the default changed on a surface with fifo_latest_ready");
check(expectFailure([&] { SelectPresentMode("fifo_latest_ready", wayland); }).find("does not support") != std::string::npos, "fifo_latest_ready was accepted on a surface without it");
check(expectFailure([&] { SelectPresentMode("mailbox", wayland); }).find("does not support") != std::string::npos, "an unsupported present mode was accepted");
check(expectFailure([&] { SelectPresentMode("Immediate", all); }).find("must be fifo, fifo_latest_ready, mailbox or immediate") != std::string::npos, "an unknown present mode name was accepted");
check(expectFailure([&] { SelectPresentMode("", all); }).find("must be fifo, fifo_latest_ready, mailbox or immediate") != std::string::npos, "an empty present mode name was accepted");
const std::array<VkPresentModeKHR, 0> none{};
check(expectFailure([&] { SelectPresentMode(nullptr, none); }).find("does not support") != std::string::npos, "FIFO was selected on a surface without it");
}
int main(int argc, char** argv) {
try {
if (argc == 2 && std::string(argv[1]) == "present-mode") {
testPresentMode();
std::puts("present mode selection tests passed");
return 0;
}
if (argc == 2) testReset(std::string(argv[1]) == "compute");
else { testFlipAndBoundary(); testFlipHold(); testFailure(); }
const auto shutdown = expectFailure([] { LibcRunShutdown_nid_postfix(); });

View File

@ -131,6 +131,8 @@ Games ask for the console's language with `sceSystemServiceParamGetInt` and show
The game runs on the first Vulkan 1.1 device with graphics and compute queues and swapchain presentation, preferring a discrete GPU over an integrated one. Set `ANYPS5_GPU` to a part of a device name, compared without regard to case, to run on another device; the names are printed at start-up in the `Physical device candidate` lines. When no usable device contains the text, the start fails and the error lists the device names.
The window presents with vertical sync (`fifo`). Set `ANYPS5_PRESENT_MODE` to `fifo_latest_ready`, `mailbox` or `immediate` to keep the game from waiting for the display: `fifo_latest_ready` and `mailbox` still show whole frames at the display's refresh and drop the ones in between, `immediate` presents at once and can tear. The start fails on any other value and when the surface does not offer the mode (`fifo_latest_ready` needs `VK_KHR_present_mode_fifo_latest_ready` or its EXT form). Flips stay paced at 59.94 Hz in every mode. A Wayland compositor can stop delivering frames to a window it does not show, for example one on another workspace: on Hyprland with the NVIDIA driver a `fifo` swapchain then presents about once per second, while `fifo_latest_ready` and `immediate` keep the game at its normal rate.
On Windows, the OpenCL, OpenGL, and Vulkan Compatibility Pack can install Mesa Dozen, which exposes D3D12 devices through Vulkan. Some D3D12 drivers can crash while the Vulkan loader enumerates adapters, even when AnyPS5 would select a native Vulkan device. If this happens and the system has another Vulkan driver, disable Dozen for the current PowerShell session before launching AnyPS5:
```powershell