From 1a2bbc5d3e8c26318adb31389dd91fc91c623366 Mon Sep 17 00:00:00 2001 From: raghav314 Date: Thu, 8 Oct 2026 04:55:13 +0530 Subject: [PATCH] fix(videoout): create the window and the device when VideoOut starts The driver created a windowless Vulkan device at the first dispatch and replaced it with a windowed one at the first flip. The replacement built a second device and re-imported every host import, and the first flip paid for it. VideoOut now creates its window when it starts (the first VideoOut call), at the default 1920x1080 output, and attaches it to the driver (AgcDriverAttachWindow) before the call returns. The driver creates its device with that window, so GPU work and presentation use one device. Present no longer creates or replaces a device. A device created by GPU work before VideoOut started is still replaced, now in AttachWindow. A failure to create the window or the device is thrown from that first VideoOut call instead of terminating the presentation thread. Tests: - video_out_flip_tests onedevice: GPU work, then a flip, leaves one device. - pacing makes its first presentation span vblanks with GPU work queued ahead of the flip; it relied on the device being created at the flip. - unavailable expects the failure from sceVideoOutOpen. - guest_video_out_wqhd_detection writes a param.json for the window title and is skipped without a display or Vulkan device unless ANYPS5_REQUIRE_DISPLAY is set. Co-Authored-By: Claude Opus 5.5 --- .../Execution/include/Driver/Driver.hpp | 1 + .../Execution/include/Presentation.hpp | 1 + .../Execution/src/Driver/EntryPoints.cpp | 8 + .../Execution/src/Driver/Presentation.cpp | 24 +-- core/libs/prx/libSceVideoOut/CMakeLists.txt | 4 +- .../libSceVideoOut/include/VideoOutDriver.hpp | 10 +- .../prx/libSceVideoOut/src/VideoOutDriver.cpp | 68 ++++++--- core/libs/prx/libSceVideoOut/tests/Flip.cpp | 144 ++++++++++++------ .../libs/tests/GuestVideoOutWqhdDetection.cpp | 18 ++- 9 files changed, 189 insertions(+), 89 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index cf2941e62..500d02a07 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -53,6 +53,7 @@ public: void SuspendPoint(); void RegisterVideoOutput(std::uint32_t handle, const std::shared_ptr& output); void UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptr& output); + void AttachWindow(const PresentationWindow& window); void Present(const PresentationWindow& window, const DisplayBuffer* buffer, bool opaque, void (*gpuReady)(void*), void* context); void ReleaseWindow(void* window); void RegisterShader(const Shader* shader); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Presentation.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Presentation.hpp index 88dd83ead..3f771fbb1 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Presentation.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Presentation.hpp @@ -25,6 +25,7 @@ struct PresentationWindow { } +extern "C" void AgcDriverAttachWindow_nid_postfix(const AgcDriver::PresentationWindow& window); extern "C" void AgcDriverPresentClear_nid_postfix(const AgcDriver::PresentationWindow& window, bool opaque, void (*gpuReady)(void*), void* context); extern "C" void AgcDriverPresentBuffer_nid_postfix(const AgcDriver::PresentationWindow& window, const AgcDriver::DisplayBuffer& buffer, void (*gpuReady)(void*), void* context); extern "C" void AgcDriverReleaseWindow_nid_postfix(void* window); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp index 91e589164..7240ce151 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/EntryPoints.cpp @@ -33,6 +33,10 @@ void UnregisterVideoOutput(std::uint32_t handle, const std::shared_ptrWindow() == nullptr, "a window is already attached to the device"); + device->PrepareForReplacement(); + replacedDevices.push_back(device); + } + device = std::make_shared(&window); +} + void Driver::Present(const PresentationWindow& window, const DisplayBuffer* buffer, bool opaque, void (*gpuReady)(void*), void* context) { PerformanceContext timingContext(window.timing.get()); @@ -48,19 +61,10 @@ void Driver::Present(const PresentationWindow& window, const DisplayBuffer* buff bool trailing = false; double waitedMs = 0; { - std::unique_lock replacing(deviceReplacement, std::defer_lock); - if (const auto current = device.Load(); current == nullptr || current->Window() == nullptr) replacing.lock(); GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Present); std::lock_guard lock(GuestMemory::GpuMutex()); timing.Mark("gpu_mutex_wait"); - if (device == nullptr || device->Window() == nullptr) { - if (device) { - device->PrepareForReplacement(); - replacedDevices.push_back(device); - } - device = std::make_shared(&window); - } - require(device->Window() == window.context, "presentation window does not match device surface"); + require(device != nullptr && device->Window() == window.context, "presentation window is not attached to the device"); presenting = device; timing.Mark("device_setup"); std::uint32_t drawableWidth = 0; diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index 075b2aab4..35c71425e 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -27,7 +27,7 @@ foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") endforeach() add_test_executable(video_out_flip_tests EXCLUDE_FROM_ALL tests/Flip.cpp) -target_include_directories(video_out_flip_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include) +target_include_directories(video_out_flip_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include) target_link_libraries(video_out_flip_tests PRIVATE libSceVideoOut libSceAgcDriver libkernel libc) set_target_properties(video_out_flip_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(video_out_flip_tests) @@ -44,7 +44,7 @@ if(BUILD_TESTING) target_link_libraries(guest_video_out_wqhd_detection_tests PRIVATE libSceVideoOut libSceAgcDriver libkernel libc) configure_windows_unwind(guest_video_out_wqhd_detection_tests) add_test(NAME guest_video_out_wqhd_detection COMMAND guest_video_out_wqhd_detection_tests) - set_tests_properties(guest_video_out_wqhd_detection PROPERTIES TIMEOUT 20) + set_tests_properties(guest_video_out_wqhd_detection PROPERTIES TIMEOUT 20 SKIP_RETURN_CODE 77) if(WIN32) set_tests_properties(guest_video_out_wqhd_detection PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index 97c1ef47f..02adf6ea0 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" @@ -41,6 +42,9 @@ static constexpr int VIDEO_OUT_BUS_TYPE_MAIN = 0; static constexpr int VIDEO_OUT_BUS_TYPE_OVERLAY = 1; static constexpr int VIDEO_OUT_BUS_TYPE_SUB = 2; +static constexpr uint32_t VIDEO_OUT_DEFAULT_WIDTH = 1920; +static constexpr uint32_t VIDEO_OUT_DEFAULT_HEIGHT = 1080; + static constexpr std::uint32_t VIDEO_OUT_OPEN_PARAM_FIRST_WORD = 16; static constexpr std::int32_t VIDEO_OUT_SERVICE_THREAD_PRIORITY_HIGHEST = 256; static constexpr std::int32_t VIDEO_OUT_SERVICE_THREAD_PRIORITY_LOWEST = 767; @@ -113,8 +117,8 @@ struct VideoOutConfig { std::vector preVblankEvents; std::vector outputModeEvents; - uint32_t width = 1920; - uint32_t height = 1080; + uint32_t width = VIDEO_OUT_DEFAULT_WIDTH; + uint32_t height = VIDEO_OUT_DEFAULT_HEIGHT; uint64_t generation = 0; bool opened = false; bool closing = false; @@ -201,7 +205,7 @@ public: private: bool close(int handle); - void presentLoop(std::stop_token token); + void presentLoop(std::stop_token token, std::promise& started); void vblankLoop(std::stop_token token); void vblankEnd(); void processFlip(FlipRequest& req); diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index b61d35d41..8f17017d2 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -28,6 +28,34 @@ void require(bool condition, const char* reason) { if (!condition) throw std::runtime_error(std::string("VideoOut: ") + reason); } +std::vector windowExtensions(SDL_Window* window) { + unsigned extensionCount = 0; + if (!SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr)) throw std::runtime_error(std::string("SDL_Vulkan_GetInstanceExtensions failed: ") + SDL_GetError()); + std::vector extensions(extensionCount); + if (!SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, extensions.data())) throw std::runtime_error(std::string("SDL_Vulkan_GetInstanceExtensions failed: ") + SDL_GetError()); + extensions.resize(extensionCount); + return extensions; +} + +VkSurfaceKHR createWindowSurface(void* context, VkInstance instance) { + VkSurfaceKHR surface = VK_NULL_HANDLE; + if (!SDL_Vulkan_CreateSurface(static_cast(context), instance, &surface)) throw std::runtime_error(std::string("SDL_Vulkan_CreateSurface failed: ") + SDL_GetError()); + return surface; +} + +void windowDrawableSize(void* context, std::uint32_t* width, std::uint32_t* height) { + if ((SDL_GetWindowFlags(static_cast(context)) & SDL_WINDOW_MINIMIZED) != 0) { + *width = 0; + *height = 0; + return; + } + int drawableWidth = 0; + int drawableHeight = 0; + SDL_Vulkan_GetDrawableSize(static_cast(context), &drawableWidth, &drawableHeight); + *width = drawableWidth > 0 ? static_cast(drawableWidth) : 0; + *height = drawableHeight > 0 ? static_cast(drawableHeight) : 0; +} + void checkConfig(const VideoOutConfig& cfg) { cfg.Check(); } @@ -205,7 +233,10 @@ VideoOutDriver::VideoOutDriver() { } try { AgcDriverWaitIdle_nid_postfix(); - presentThread = std::jthread([this](std::stop_token token) { presentLoop(token); }); + std::promise started; + auto attached = started.get_future(); + presentThread = std::jthread([this, &started](std::stop_token token) { presentLoop(token, started); }); + attached.get(); vblankThread = std::jthread([this](std::stop_token token) { vblankLoop(token); }); LibcRegisterShutdown_nid_postfix([] { VideoOutDriver::Get().Shutdown(); }); } catch (...) { @@ -398,27 +429,8 @@ void VideoOutDriver::processFlip(FlipRequest& req) { } require(req.width != 0 && req.height != 0 && req.width <= static_cast(std::numeric_limits::max()) && req.height <= static_cast(std::numeric_limits::max()), "invalid window dimensions"); window.Ensure(req.width, req.height); - unsigned extensionCount = 0; - if (!SDL_Vulkan_GetInstanceExtensions(window.Handle(), &extensionCount, nullptr)) throw std::runtime_error(std::string("SDL_Vulkan_GetInstanceExtensions failed: ") + SDL_GetError()); - std::vector extensions(extensionCount); - if (!SDL_Vulkan_GetInstanceExtensions(window.Handle(), &extensionCount, extensions.data())) throw std::runtime_error(std::string("SDL_Vulkan_GetInstanceExtensions failed: ") + SDL_GetError()); - extensions.resize(extensionCount); - const AgcDriver::PresentationWindow target{window.Handle(), extensions, [](void* context, VkInstance instance) { - VkSurfaceKHR surface = VK_NULL_HANDLE; - if (!SDL_Vulkan_CreateSurface(static_cast(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(context)) & SDL_WINDOW_MINIMIZED) != 0) { - *width = 0; - *height = 0; - return; - } - int drawableWidth = 0; - int drawableHeight = 0; - SDL_Vulkan_GetDrawableSize(static_cast(context), &drawableWidth, &drawableHeight); - *width = drawableWidth > 0 ? static_cast(drawableWidth) : 0; - *height = drawableHeight > 0 ? static_cast(drawableHeight) : 0; - }, req.width, req.height, req.timing}; + const auto extensions = windowExtensions(window.Handle()); + const AgcDriver::PresentationWindow target{window.Handle(), extensions, &createWindowSurface, &windowDrawableSize, req.width, req.height, req.timing}; timing.Mark("window_prepare"); const auto gpuReady = [](void* context) { auto& request = *static_cast(context); @@ -462,7 +474,17 @@ void VideoOutDriver::processFlip(FlipRequest& req) { timing.Mark("notify_game"); } -void VideoOutDriver::presentLoop(std::stop_token token) { +void VideoOutDriver::presentLoop(std::stop_token token, std::promise& started) { + try { + window.Ensure(VIDEO_OUT_DEFAULT_WIDTH, VIDEO_OUT_DEFAULT_HEIGHT); + const auto extensions = windowExtensions(window.Handle()); + AgcDriverAttachWindow_nid_postfix({window.Handle(), extensions, &createWindowSurface, &windowDrawableSize, VIDEO_OUT_DEFAULT_WIDTH, VIDEO_OUT_DEFAULT_HEIGHT, nullptr}); + } catch (...) { + window.Destroy(); + started.set_exception(std::current_exception()); + return; + } + started.set_value(); std::shared_ptr current; try { PadInput padInput; diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index 048a7e892..c97504a8b 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -4,6 +4,7 @@ #include "prx/libSceVideoOut/include/Event.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include "prx/libc/include/Shutdown.hpp" #include @@ -263,7 +264,7 @@ void testControls() { LibcRunShutdown_nid_postfix(); } -void testPresentation(bool expectUnavailable) { +void testPresentation() { const int handle = sceVideoOutOpen(255, 0, 0, nullptr); auto cfg = VideoOutDriver::Get().GetConfig(handle); std::vector allocation(6 * 65536 + 65535); @@ -289,16 +290,6 @@ void testPresentation(bool expectUnavailable) { sceVideoOutSubmitFlip(handle, index, 1, -123456789); std::unique_lock lock(cfg->mutex); check(cfg->vblankCond.wait_for(lock, std::chrono::seconds(15), [&] { return (cfg->failure && cfg->flipStatus.flipPendingNum == 0) || cfg->flipStatus.count == target; }), "presentation did not complete"); - if (expectUnavailable) { - check(cfg->failure != nullptr && cfg->flipStatus.count == 0 && cfg->bufferPending[0] == 0, "failed presentation was marked complete or retained its buffer"); - const auto error = cfg->failure; - lock.unlock(); - const auto message = expectFailure([&] { std::rethrow_exception(error); }); - check(message.find("required instance extension missing") != std::string::npos, "unexpected presentation failure"); - check(expectFailure([] { AgcDriverWaitIdle_nid_postfix(); }) == message, "AGC lost presentation failure"); - check(expectFailure([] { LibcRunShutdown_nid_postfix(); }) == message, "shutdown lost presentation failure"); - return; - } if (cfg->failure) std::rethrow_exception(cfg->failure); check(cfg->flipStatus.flipArg == -123456789 && cfg->flipStatus.currentBuffer == index && cfg->flipStatus.flipPendingNum == 0, "presentation status is wrong"); check(cfg->lastFlipVblank >= previousVblank + 3, "flip rate did not wait for its interval"); @@ -312,6 +303,13 @@ void testPresentation(bool expectUnavailable) { LibcRunShutdown_nid_postfix(); } +void testUnavailable() { + const auto message = expectFailure([] { sceVideoOutOpen(255, 0, 0, nullptr); }); + check(expectFailure([] { sceVideoOutOpen(255, 0, 0, nullptr); }) == message, "a second open lost the presentation failure"); + std::printf("presentation unavailable: %s\n", message.c_str()); + LibcRunShutdown_nid_postfix(); +} + void testCompressedPresentation() { const int handle = sceVideoOutOpen(255, 0, 0, nullptr); auto cfg = VideoOutDriver::Get().GetConfig(handle); @@ -370,6 +368,49 @@ void testBackToBack() { LibcRunShutdown_nid_postfix(); } +std::uint32_t busyLoopResult(std::uint32_t iterations) { + std::uint32_t value = 1; + for (std::uint32_t i = 0; i < iterations; ++i) value = value * value + 1u; + return value; +} + +struct BusyLoop { + volatile std::uint32_t* result; + volatile std::uint32_t* label; + std::vector commands; +}; + +BusyLoop prepareBusyLoop(std::uint32_t iterations) { + alignas(256) static const std::array code{ + 0xbe850380, 0x7e040281, 0xd5690002, 0x00020502, 0x4a040481, 0x80058105, + 0xbf070405, 0xbf85fffa, 0x7e020280, 0xe0701000, 0x80000201, 0xbf810000}; + static Shader shader{}; + shader.file_header = 0x34333231; + shader.version = 0x18; + shader.header_size = sizeof(Shader); + shader.shader_size = sizeof(code); + shader.code = code.data(); + AgcDriverRegisterShader_nid_postfix(&shader); + std::int64_t physical = 0; + void* mapped = nullptr; + check(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, 65536, 65536, 0, &physical) == 0, "direct memory allocation failed"); + check(sceKernelMapDirectMemory(&mapped, 65536, 0x33, 0, physical, 65536) == 0, "direct memory mapping failed"); + auto* result = &static_cast(mapped)[0]; + auto* label = &static_cast(mapped)[64]; + *result = 0; + *label = 0; + const auto program = reinterpret_cast(code.data()); + const auto output = reinterpret_cast(result); + const auto target = reinterpret_cast(label); + return {result, label, { + 0xc0027600, 0x20c, static_cast(program >> 8u), static_cast(program >> 40u), + 0xc0017600, 0x213, 5u << 1u, + 0xc0037600, 0x207, 32, 1, 1, + 0xc0057600, 0x240, static_cast(output), static_cast(output >> 32u) & 0xffffu, 4, 0x31016fac, iterations, + 0xc0031500, 1, 1, 1, 0x8041, + 0xc0064900, 0x514, 1u << 29u, static_cast(target), static_cast(target >> 32u), 1, 0, 0}}; +} + void testReleaseVblank() { const int handle = sceVideoOutOpen(255, 0, 0, nullptr); auto cfg = VideoOutDriver::Get().GetConfig(handle); @@ -381,26 +422,22 @@ void testReleaseVblank() { sceVideoOutSetBufferAttribute2(&attribute, 0x8000000000000000ull, 0, 259, 137, 0, 0, 0); sceVideoOutRegisterBuffers2(handle, 0, 0, &buffer, 1, &attribute, 0, nullptr); check(sceVideoOutSetFlipRate(handle, 2) == 0, "flip rate was not applied"); + auto busy = prepareBusyLoop(100000000u); + Packet packet{busy.commands.data(), static_cast(busy.commands.size()), 0, {}}; + check(sceAgcDriverSubmitDcb(&packet) == 0, "the GPU work was not accepted"); sceVideoOutSubmitFlip(handle, 0, 1, 1); { std::unique_lock lock(cfg->mutex); const bool done = cfg->vblankCond.wait_for(lock, std::chrono::seconds(15), [&] { return cfg->failure || cfg->flipStatus.count == 1; }); if (cfg->failure) std::rethrow_exception(cfg->failure); check(done, "the flip did not complete"); - check(cfg->vblankStatus.count > 3, "the first presentation did not outlast the vblank it was released at"); - check(cfg->lastFlipVblank == 3, "the next flip is not paced from the vblank this flip was released at"); + check(cfg->lastFlipVblank >= 3 && cfg->vblankStatus.count >= cfg->lastFlipVblank + 3, "the next flip is not paced from the vblank this flip was released at"); } sceVideoOutUnregisterBuffers(handle, 0); sceVideoOutClose(handle); LibcRunShutdown_nid_postfix(); } -std::uint32_t busyLoopResult(std::uint32_t iterations) { - std::uint32_t value = 1; - for (std::uint32_t i = 0; i < iterations; ++i) value = value * value + 1u; - return value; -} - void testFlipAfterGpuWork() { const int handle = sceVideoOutOpen(255, 0, 0, nullptr); auto cfg = VideoOutDriver::Get().GetConfig(handle); @@ -411,36 +448,11 @@ void testFlipAfterGpuWork() { VideoOutBufferAttribute2 attribute{}; sceVideoOutSetBufferAttribute2(&attribute, 0x8000000000000000ull, 0, 259, 137, 0, 0, 0); sceVideoOutRegisterBuffers2(handle, 0, 0, &buffer, 1, &attribute, 0, nullptr); - alignas(256) static const std::array code{ - 0xbe850380, 0x7e040281, 0xd5690002, 0x00020502, 0x4a040481, 0x80058105, - 0xbf070405, 0xbf85fffa, 0x7e020280, 0xe0701000, 0x80000201, 0xbf810000}; - Shader shader{}; - shader.file_header = 0x34333231; - shader.version = 0x18; - shader.header_size = sizeof(Shader); - shader.shader_size = sizeof(code); - shader.code = code.data(); - AgcDriverRegisterShader_nid_postfix(&shader); - std::int64_t physical = 0; - void* mapped = nullptr; - check(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, 65536, 65536, 0, &physical) == 0, "direct memory allocation failed"); - check(sceKernelMapDirectMemory(&mapped, 65536, 0x33, 0, physical, 65536) == 0, "direct memory mapping failed"); - auto& result = static_cast(mapped)[0]; - auto& label = static_cast(mapped)[64]; - result = 0; - label = 0; constexpr std::uint32_t iterations = 100000000u; - const auto program = reinterpret_cast(code.data()); - const auto output = reinterpret_cast(&result); - const auto target = reinterpret_cast(&label); - std::vector commands{ - 0xc0027600, 0x20c, static_cast(program >> 8u), static_cast(program >> 40u), - 0xc0017600, 0x213, 5u << 1u, - 0xc0037600, 0x207, 32, 1, 1, - 0xc0057600, 0x240, static_cast(output), static_cast(output >> 32u) & 0xffffu, 4, 0x31016fac, iterations, - 0xc0031500, 1, 1, 1, 0x8041, - 0xc0064900, 0x514, 1u << 29u, static_cast(target), static_cast(target >> 32u), 1, 0, 0}; - Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + auto busy = prepareBusyLoop(iterations); + auto& result = *busy.result; + auto& label = *busy.label; + Packet packet{busy.commands.data(), static_cast(busy.commands.size()), 0, {}}; check(sceVideoOutSubmitFlip(handle, 0, 1, 4) == 0, "the first flip was not accepted"); { std::unique_lock lock(cfg->mutex); @@ -476,18 +488,50 @@ void testFlipAfterGpuWork() { LibcRunShutdown_nid_postfix(); } +void testOneDevice() { + const int handle = sceVideoOutOpen(255, 0, 0, nullptr); + auto cfg = VideoOutDriver::Get().GetConfig(handle); + std::vector allocation(6 * 65536 + 65535); + const auto storage = alignedBuffer(allocation); + fillBuffer(storage, 259, 137); + VideoOutBuffers buffer{storage.data(), nullptr, {nullptr, nullptr}}; + VideoOutBufferAttribute2 attribute{}; + sceVideoOutSetBufferAttribute2(&attribute, 0x8000000000000000ull, 0, 259, 137, 0, 0, 0); + sceVideoOutRegisterBuffers2(handle, 0, 0, &buffer, 1, &attribute, 0, nullptr); + constexpr std::uint32_t iterations = 1000u; + auto busy = prepareBusyLoop(iterations); + Packet packet{busy.commands.data(), static_cast(busy.commands.size()), 0, {}}; + check(sceAgcDriverSubmitDcb(&packet) == 0, "the GPU work was not accepted"); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(30); + while (*busy.label != 1 && std::chrono::steady_clock::now() < deadline) std::this_thread::yield(); + check(*busy.label == 1 && *busy.result == busyLoopResult(iterations), "the GPU work did not run"); + check(sceVideoOutSubmitFlip(handle, 0, 1, 1) == 0, "the flip was not accepted"); + { + std::unique_lock lock(cfg->mutex); + const bool done = cfg->vblankCond.wait_for(lock, std::chrono::seconds(30), [&] { return cfg->failure || cfg->flipStatus.count == 1; }); + if (cfg->failure) std::rethrow_exception(cfg->failure); + check(done, "the flip did not complete"); + } + sceVideoOutUnregisterBuffers(handle, 0); + sceVideoOutClose(handle); + LibcRunShutdown_nid_postfix(); + const AgcDriver::VulkanDevice next; + check(next.Serial() == 2, "the GPU work and the flip used more than one device"); +} + } int main(int argc, char** argv) { try { if (argc == 2 && std::string(argv[1]) == "decode") testDecode(); else if (argc == 2 && std::string(argv[1]) == "controls") testControls(); - else if (argc == 2 && std::string(argv[1]) == "present") testPresentation(false); + else if (argc == 2 && std::string(argv[1]) == "present") testPresentation(); else if (argc == 2 && std::string(argv[1]) == "backtoback") testBackToBack(); else if (argc == 2 && std::string(argv[1]) == "dcc") testCompressedPresentation(); else if (argc == 2 && std::string(argv[1]) == "pacing") testReleaseVblank(); else if (argc == 2 && std::string(argv[1]) == "aftergpu") testFlipAfterGpuWork(); - else if (argc == 2 && std::string(argv[1]) == "unavailable") testPresentation(true); + else if (argc == 2 && std::string(argv[1]) == "unavailable") testUnavailable(); + else if (argc == 2 && std::string(argv[1]) == "onedevice") testOneDevice(); else testLifetime(argc == 2 && std::string(argv[1]) == "reopen"); std::puts("VideoOut flip tests passed"); return 0; diff --git a/core/libs/tests/GuestVideoOutWqhdDetection.cpp b/core/libs/tests/GuestVideoOutWqhdDetection.cpp index d0196accf..0cb8a54ec 100644 --- a/core/libs/tests/GuestVideoOutWqhdDetection.cpp +++ b/core/libs/tests/GuestVideoOutWqhdDetection.cpp @@ -1,7 +1,10 @@ #include "SceTypes.hpp" #include "prx/libc/include/Shutdown.hpp" +#include #include #include +#include +#include #include extern "C" { @@ -31,7 +34,20 @@ static bool SameStatus(const VideoOutOutputStatus& a, const VideoOutOutputStatus } int main() { - const int handle = sceVideoOutOpen(SYSTEM_USER, MAIN_BUS, 0, nullptr); + std::filesystem::create_directories("app0/sce_sys"); + { + std::ofstream param("app0/sce_sys/param.json", std::ios::binary); + param << R"({"titleId":"PPSA00000","localizedParameters":{"en-US":{"titleName":"Example"}},"downloadDataSize":0})"; + Require(static_cast(param)); + } + int handle = 0; + try { + handle = sceVideoOutOpen(SYSTEM_USER, MAIN_BUS, 0, nullptr); + } catch (const std::runtime_error& error) { + if (std::getenv("ANYPS5_REQUIRE_DISPLAY") != nullptr) throw; + std::printf("skipped, no display or Vulkan device: %s\n", error.what()); + return 77; + } Require(handle > 0); VideoOutOutputStatus before{};