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{};