From aef1b2989cec6b60b70fa09aa525d517623bbc5b Mon Sep 17 00:00:00 2001 From: JJenkx Date: Mon, 12 Oct 2026 23:11:59 -0400 Subject: [PATCH 1/3] feat(videoout): expose buffer labels and release replaced frames --- .../Execution/include/GuestMemory.hpp | 1 + .../Execution/src/GuestMemory.cpp | 7 ++ core/libs/prx/libSceVideoOut/CMakeLists.txt | 2 +- .../prx/libSceVideoOut/include/Buffer.hpp | 1 + .../libSceVideoOut/include/VideoOutDriver.hpp | 1 + core/libs/prx/libSceVideoOut/src/Buffer.cpp | 17 +++++ .../prx/libSceVideoOut/src/VideoOutDriver.cpp | 6 ++ core/libs/prx/libSceVideoOut/tests/Flip.cpp | 72 +++++++++++++++++++ docs/dev/debt/libSceVideoOut.md | 3 + 9 files changed, 109 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp index 740f1f45e82..c60420e40eb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp @@ -239,5 +239,6 @@ std::size_t CaptureCallerOffsets(std::span frames, unsigned } extern "C" void AgcDriverCheckGuestMemory_nid_postfix(const void* pointer, std::size_t bytes, std::size_t alignment, bool writable = false); +extern "C" void AgcDriverStoreGuestLabel_nid_postfix(std::uint64_t* pointer, std::uint64_t value); #endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index 20ac655e173..0288a0eb950 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -1959,3 +1959,10 @@ void Write(std::uint64_t address, std::span source, std::size_t extern "C" void AgcDriverCheckGuestMemory_nid_postfix(const void* pointer, std::size_t bytes, std::size_t alignment, bool writable) { AgcDriver::GuestMemory::CheckRange(pointer, bytes, alignment, writable); } + +extern "C" void AgcDriverStoreGuestLabel_nid_postfix(std::uint64_t* pointer, std::uint64_t value) { + AgcDriver::GuestMemory::CheckRange(pointer, sizeof(*pointer), std::atomic_ref::required_alignment, true); + AgcDriver::GuestMemory::StoreOwnBytes(reinterpret_cast(pointer), sizeof(*pointer), [&] { + std::atomic_ref(*pointer).store(value, std::memory_order_release); + }); +} diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index faef0b45832..faf4bcb1c16 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -37,7 +37,7 @@ if(BUILD_TESTING) "SDL video driver for VideoOut flip tests") set_target_properties(video_out_flip_tests PROPERTIES EXCLUDE_FROM_ALL OFF) - foreach(flipMode IN ITEMS lifetime reopen decode controls overlay unopened) + foreach(flipMode IN ITEMS lifetime reopen decode controls overlay unopened labels labels_main) if(flipMode STREQUAL "lifetime") add_test(NAME video_out_flip_${flipMode} COMMAND video_out_flip_tests) else() diff --git a/core/libs/prx/libSceVideoOut/include/Buffer.hpp b/core/libs/prx/libSceVideoOut/include/Buffer.hpp index 41a36673a72..90bd1cabaf2 100644 --- a/core/libs/prx/libSceVideoOut/include/Buffer.hpp +++ b/core/libs/prx/libSceVideoOut/include/Buffer.hpp @@ -14,6 +14,7 @@ void APS5_VABI sceVideoOutAddBufferAttributeOption(VideoOutBufferAttribute2* att int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int set_index, const VideoOutBufferAttribute2* attribute, void* option); int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flip_mode, int64_t flip_arg); int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int set_index); +int APS5_VABI sceVideoOutGetBufferLabelAddress(int handle, uintptr_t* address); #endif diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index 11fa5eb89f2..22c57dffa18 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -140,6 +140,7 @@ struct VideoOutConfig { VideoOutVblankStatus preVblankStatus{}; std::array buffers{}; + alignas(std::atomic_ref::required_alignment) std::array bufferLabels{}; std::array bufferPending{}; std::array bufferReuse; std::array groups{}; diff --git a/core/libs/prx/libSceVideoOut/src/Buffer.cpp b/core/libs/prx/libSceVideoOut/src/Buffer.cpp index 82b62b6c24f..898f378abe7 100644 --- a/core/libs/prx/libSceVideoOut/src/Buffer.cpp +++ b/core/libs/prx/libSceVideoOut/src/Buffer.cpp @@ -31,6 +31,21 @@ AgcDriver::DisplayBuffer DescribeVideoOutBuffer(const VideoOutBuffer& buffer, co extern "C" { +int APS5_VABI sceVideoOutGetBufferLabelAddress(int handle, uintptr_t* address) try { + if (address == nullptr) return VIDEO_OUT_ERROR_INVALID_ADDRESS; + auto& driver = VideoOutDriver::Get(); + if (!driver.HasConfig(handle)) return VIDEO_OUT_ERROR_INVALID_HANDLE; + auto cfg = driver.GetConfig(handle); + std::lock_guard lock(cfg->mutex); + cfg->CheckAlive(); + if (cfg->Closed()) return VIDEO_OUT_ERROR_INVALID_HANDLE; + const auto labels = reinterpret_cast(cfg->bufferLabels.data()); + std::memcpy(address, &labels, sizeof(labels)); + return VIDEO_OUT_BUFFER_NUM_MAX; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); +} + void APS5_VABI sceVideoOutSetBufferAttribute2(VideoOutBufferAttribute2* attribute, uint64_t pixelFormat, uint32_t tilingMode, uint32_t width, uint32_t height, uint64_t option, uint32_t dccControl, uint64_t dccCbRegisterClearColor) { if (attribute == nullptr) { throw std::runtime_error("sceVideoOutSetBufferAttribute2: null attribute"); @@ -104,6 +119,8 @@ int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int setIndex, int bufferIn cfg->height = attribute->height; for (int i = 0; i < bufferNum; i++) { cfg->buffers[bufferIndexStart + i] = registered[i]; + auto& label = cfg->bufferLabels[bufferIndexStart + i]; + AgcDriverStoreGuestLabel_nid_postfix(&label, 0); } return 0; } catch (const ProcessShutdown&) { diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index fbe9da37b04..9bc7dbb5849 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -23,6 +23,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Presentation.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include "prx/libc/include/PreciseWait.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -538,6 +539,11 @@ void VideoOutDriver::processFlip(FlipRequest& req) { req.cfg->flipStatus.processTime = sceKernelGetProcessTime(); req.cfg->flipStatus.processTimeCounter = sceKernelGetProcessTimeCounter(); req.cfg->flipStatus.flipArg = req.flipArg; + const auto previous = req.cfg->flipStatus.currentBuffer; + if (previous >= 0) { + auto& label = req.cfg->bufferLabels[previous]; + AgcDriverStoreGuestLabel_nid_postfix(&label, 0); + } req.cfg->flipStatus.currentBuffer = req.index; req.cfg->width = req.width; req.cfg->height = req.height; diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index 2be38015fbe..46c65a014af 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -669,6 +670,75 @@ void testUnopenedEventRegistration() { LibcRunShutdown_nid_postfix(); } +void testLabels(bool overlay) { + std::uintptr_t address = 0x1234; + for (const int invalid : {-1, 0, VIDEO_OUT_NUM_MAX, std::numeric_limits::max()}) { + check(sceVideoOutGetBufferLabelAddress(invalid, &address) == VIDEO_OUT_ERROR_INVALID_HANDLE && address == 0x1234, "invalid label query changed its output"); + } + check(sceVideoOutGetBufferLabelAddress(1, nullptr) == VIDEO_OUT_ERROR_INVALID_ADDRESS, "null label output was accepted"); + check(sceVideoOutGetBufferLabelAddress(2, &address) == VIDEO_OUT_ERROR_INVALID_HANDLE && address == 0x1234, "unopened label query changed its output"); + const int bus = overlay ? VIDEO_OUT_BUS_TYPE_OVERLAY : VIDEO_OUT_BUS_TYPE_MAIN; + const int handle = sceVideoOutOpen(255, bus, 0, nullptr); + auto cfg = VideoOutDriver::Get().GetConfig(handle); + check(sceVideoOutGetBufferLabelAddress(handle, &address) == VIDEO_OUT_BUFFER_NUM_MAX, "label query returned the wrong count"); + check(address != 0 && address % std::atomic_ref::required_alignment == 0, "label array is absent or unaligned"); + auto* labels = reinterpret_cast(address); + for (int i = 0; i < VIDEO_OUT_BUFFER_NUM_MAX; ++i) check(std::atomic_ref(labels[i]).load() == 0, "new port has a nonzero label"); + alignas(std::uintptr_t) std::array unaligned; + unaligned.fill(std::byte{0x5a}); + check(sceVideoOutGetBufferLabelAddress(handle, reinterpret_cast(unaligned.data() + 1)) == VIDEO_OUT_BUFFER_NUM_MAX, "unaligned label-address output failed"); + std::uintptr_t repeated = 0; + std::memcpy(&repeated, unaligned.data() + 1, sizeof(repeated)); + check(repeated == address && unaligned.front() == std::byte{0x5a} && unaligned.back() == std::byte{0x5a}, "label query changed its address or output canaries"); + for (int i : {0, 1, 15}) std::atomic_ref(labels[i]).store(55); + std::vector allocation(2 * 65536 + 65535); + const auto storage = alignedBuffer(allocation); + std::array buffers{}; + buffers[0].data = storage.data(); + buffers[1].data = storage.data() + 65536; + VideoOutBufferAttribute2 attribute{}; + attribute.width = 64; + attribute.height = 64; + attribute.pixel_format = 0x8000000000000000ull; + check(sceVideoOutRegisterBuffers2(handle, 0, 0, buffers.data(), 2, &attribute, 0, nullptr) == 0, "label-test buffers failed to register"); + check(std::atomic_ref(labels[0]).load() == 0 && std::atomic_ref(labels[1]).load() == 0 && std::atomic_ref(labels[15]).load() == 55, "registration reset the wrong labels"); + std::atomic_ref(labels[0]).store(1); + std::atomic_ref(labels[1]).store(1); + const auto flip = [&](int index, std::uint64_t count) { + check(sceVideoOutSubmitFlip(handle, index, VIDEO_OUT_FLIP_MODE_VSYNC, index) == 0, "label-test flip failed to submit"); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + VideoOutFlipStatus status{}; + do { + check(sceVideoOutGetFlipStatus(handle, &status) == 0, "label-test flip status failed"); + if (status.count >= count) return; + std::this_thread::yield(); + } while (std::chrono::steady_clock::now() < deadline); + throw std::runtime_error("label-test flip timed out"); + }; + flip(0, 1); + check(std::atomic_ref(labels[0]).load() == 1 && std::atomic_ref(labels[1]).load() == 1, "first flip released a label before replacement"); + flip(1, 2); + check(std::atomic_ref(labels[0]).load() == 0 && std::atomic_ref(labels[1]).load() == 1, "replacement flip released the wrong label"); + flip(VIDEO_OUT_BUFFER_INDEX_BLACK, 3); + check(std::atomic_ref(labels[1]).load() == 0 && std::atomic_ref(labels[15]).load() == 55, "black flip did not release only the previous buffer"); + flip(VIDEO_OUT_BUFFER_INDEX_BLANK, 4); + check(std::atomic_ref(labels[15]).load() == 55, "blank flip accessed a negative label index"); + const int otherHandle = sceVideoOutOpen(255, overlay ? VIDEO_OUT_BUS_TYPE_MAIN : VIDEO_OUT_BUS_TYPE_OVERLAY, 0, nullptr); + std::uintptr_t other = 0; + check(sceVideoOutGetBufferLabelAddress(otherHandle, &other) == VIDEO_OUT_BUFFER_NUM_MAX && other != address, "ports share their labels"); + sceVideoOutClose(otherHandle); + sceVideoOutClose(handle); + repeated = 0x1234; + check(sceVideoOutGetBufferLabelAddress(handle, &repeated) == VIDEO_OUT_ERROR_INVALID_HANDLE && repeated == 0x1234, "closed label query changed its output"); + const int reopened = sceVideoOutOpen(255, bus, 0, nullptr); + check(sceVideoOutGetBufferLabelAddress(reopened, &repeated) == VIDEO_OUT_BUFFER_NUM_MAX && repeated != address, "reopened port shares live old-generation labels"); + auto* fresh = reinterpret_cast(repeated); + for (int i = 0; i < VIDEO_OUT_BUFFER_NUM_MAX; ++i) check(std::atomic_ref(fresh[i]).load() == 0, "reopened port retained an old label"); + check(std::atomic_ref(labels[15]).load() == 55, "reopening changed old-generation labels"); + sceVideoOutClose(reopened); + LibcRunShutdown_nid_postfix(); +} + void testOneDevice() { const int handle = sceVideoOutOpen(255, 0, 0, nullptr); auto cfg = VideoOutDriver::Get().GetConfig(handle); @@ -715,6 +785,8 @@ int main(int argc, char** argv) { else if (argc == 2 && std::string(argv[1]) == "unavailable") testUnavailable(); else if (argc == 2 && std::string(argv[1]) == "onedevice") testOneDevice(); else if (argc == 2 && std::string(argv[1]) == "unopened") testUnopenedEventRegistration(); + else if (argc == 2 && std::string(argv[1]) == "labels") testLabels(true); + else if (argc == 2 && std::string(argv[1]) == "labels_main") testLabels(false); else if (argc == 2 && std::string(argv[1]) == "overlay") testOverlay(); else if (argc == 2 && std::string(argv[1]) == "deferred") testDeferredBuffer(); else if (argc == 2 && std::string(argv[1]) == "unregistered") testUnregisteredBuffer(); diff --git a/docs/dev/debt/libSceVideoOut.md b/docs/dev/debt/libSceVideoOut.md index e2de5493e81..bf5cb7335f2 100644 --- a/docs/dev/debt/libSceVideoOut.md +++ b/docs/dev/debt/libSceVideoOut.md @@ -21,3 +21,6 @@ ### Functional - [Overlay output bus](../../../core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp) (libSceVideoOut) - flips on a port opened with `SCE_VIDEO_OUT_BUS_TYPE_OVERLAY` complete and signal their events like main-bus flips, but their buffers are not shown: the overlay is not composited over the main output. Presenting them as whole frames made the window alternate between the two buses +### Buffer labels + +- `sceVideoOutGetBufferLabelAddress` exposes a port-owned contiguous array of 16 aligned 64-bit labels and returns its count. Astro Bot PPSA21564's bundled AGC driver passes a pointer output and addresses each label at `base + index * 8`. The count, zero initialization on registration, and release of the previous displayed buffer when its replacement completes follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/23c41333822fe8eca4c609b43119180af63ae4ac/src/core/libraries/videoout/video_out.cpp) and its driver; PS5 console equivalence has not been measured. Labels live until the port closes, and reopening creates a new array. Error precedence follows the reference for null output and the existing open-port policy for handles. Exact scanout release timing, repeated same-buffer flips and concurrent guest label stores remain unverified. EOP-flip registration and interrupt tokens remain unimplemented; exposing these labels does not implement that separate API. From aaa5fd9c602ef15ff9ffb69b7f87a7293469848c Mon Sep 17 00:00:00 2001 From: JJenkx Date: Tue, 13 Oct 2026 20:13:38 -0400 Subject: [PATCH 2/3] test(videoout): separate label cases from EOP additions --- core/libs/prx/libSceVideoOut/tests/Flip.cpp | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index 7f7aa9c0b27..6df2306f9fc 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -666,19 +666,6 @@ void testFlipAfterGpuWork() { LibcRunShutdown_nid_postfix(); } -void testUnopenedEventRegistration() { - KernelEqueue eq = 0; - check(sceKernelCreateEqueue(&eq, "unopened") == 0, "the event queue was not created"); - check(sceVideoOutAddFlipEvent(eq, 0, nullptr) == VIDEO_OUT_ERROR_INVALID_HANDLE, "flip event on an unopened port was accepted"); - check(sceVideoOutAddVblankEvent(eq, 1, nullptr) == VIDEO_OUT_ERROR_INVALID_HANDLE, "vblank event on an unopened port was accepted"); - const int handle = sceVideoOutOpen(255, 0, 0, nullptr); - check(sceVideoOutAddFlipEvent(eq, handle, nullptr) == 0, "flip event on an open port was refused"); - sceVideoOutClose(handle); - check(sceVideoOutAddFlipEvent(eq, handle, nullptr) == VIDEO_OUT_ERROR_INVALID_HANDLE, "flip event on a closed port was accepted"); - check(sceKernelDeleteEqueue(eq) == 0, "the event queue was not deleted"); - LibcRunShutdown_nid_postfix(); -} - void testLabels(bool overlay) { std::uintptr_t address = 0x1234; for (const int invalid : {-1, 0, VIDEO_OUT_NUM_MAX, std::numeric_limits::max()}) { @@ -748,6 +735,19 @@ void testLabels(bool overlay) { LibcRunShutdown_nid_postfix(); } +void testUnopenedEventRegistration() { + KernelEqueue eq = 0; + check(sceKernelCreateEqueue(&eq, "unopened") == 0, "the event queue was not created"); + check(sceVideoOutAddFlipEvent(eq, 0, nullptr) == VIDEO_OUT_ERROR_INVALID_HANDLE, "flip event on an unopened port was accepted"); + check(sceVideoOutAddVblankEvent(eq, 1, nullptr) == VIDEO_OUT_ERROR_INVALID_HANDLE, "vblank event on an unopened port was accepted"); + const int handle = sceVideoOutOpen(255, 0, 0, nullptr); + check(sceVideoOutAddFlipEvent(eq, handle, nullptr) == 0, "flip event on an open port was refused"); + sceVideoOutClose(handle); + check(sceVideoOutAddFlipEvent(eq, handle, nullptr) == VIDEO_OUT_ERROR_INVALID_HANDLE, "flip event on a closed port was accepted"); + check(sceKernelDeleteEqueue(eq) == 0, "the event queue was not deleted"); + LibcRunShutdown_nid_postfix(); +} + void testOneDevice() { const int handle = sceVideoOutOpen(255, 0, 0, nullptr); auto cfg = VideoOutDriver::Get().GetConfig(handle); @@ -792,10 +792,10 @@ int main(int argc, char** argv) { 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") testUnavailable(); - else if (argc == 2 && std::string(argv[1]) == "onedevice") testOneDevice(); - else if (argc == 2 && std::string(argv[1]) == "unopened") testUnopenedEventRegistration(); else if (argc == 2 && std::string(argv[1]) == "labels") testLabels(true); else if (argc == 2 && std::string(argv[1]) == "labels_main") testLabels(false); + else if (argc == 2 && std::string(argv[1]) == "onedevice") testOneDevice(); + else if (argc == 2 && std::string(argv[1]) == "unopened") testUnopenedEventRegistration(); else if (argc == 2 && std::string(argv[1]) == "overlay") testOverlay(); else if (argc == 2 && std::string(argv[1]) == "deferred") testDeferredBuffer(); else if (argc == 2 && std::string(argv[1]) == "unregistered") testUnregisteredBuffer(); From d1de55fb3306fe658d34ba7db87ad6b3dfe05977 Mon Sep 17 00:00:00 2001 From: JJenkx Date: Tue, 13 Oct 2026 20:14:13 -0400 Subject: [PATCH 3/3] test(videoout): register label modes independently of flip-master tests --- core/libs/prx/libSceVideoOut/CMakeLists.txt | 11 ++++++++++- docs/dev/debt/libSceVideoOut.md | 5 +---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index c7524f4d411..8de55cc8bff 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -71,8 +71,17 @@ if(BUILD_TESTING) set(ANYPS5_VIDEO_OUT_TEST_DRIVER "offscreen" CACHE STRING "SDL video driver for VideoOut flip tests") + foreach(flipMode IN ITEMS labels labels_main) + add_test(NAME video_out_flip_${flipMode} COMMAND video_out_flip_tests ${flipMode}) + set_tests_properties(video_out_flip_${flipMode} PROPERTIES TIMEOUT 30 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0 ENVIRONMENT "SDL_VIDEODRIVER=${ANYPS5_VIDEO_OUT_TEST_DRIVER}") + if(WIN32) + set_tests_properties(video_out_flip_${flipMode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + set_target_properties(video_out_flip_tests PROPERTIES EXCLUDE_FROM_ALL OFF) - foreach(flipMode IN ITEMS lifetime reopen decode controls overlay unopened labels labels_main) + foreach(flipMode IN ITEMS lifetime reopen decode controls overlay unopened) if(flipMode STREQUAL "lifetime") add_test(NAME video_out_flip_${flipMode} COMMAND video_out_flip_tests) else() diff --git a/docs/dev/debt/libSceVideoOut.md b/docs/dev/debt/libSceVideoOut.md index 4b719b13e86..242745fbf14 100644 --- a/docs/dev/debt/libSceVideoOut.md +++ b/docs/dev/debt/libSceVideoOut.md @@ -16,6 +16,7 @@ - [sceVideoOutIsOutputSupported](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - mode 1 is supported; mode 15 is supported when the current host display runs at 120 Hz or above. Configuring mode 15 requires attribute3 bit 0x40; this single-bit mask is inferred from the measured full value 0x80040, not isolated by a console measurement. Vblanks run at 119.88 Hz until mode 1 restores 59.94 Hz. Display modes are read through SDL; a move to a slower display rejects further high-frame-rate presentation. Selecting another host display mode and VRR are not implemented. The console answers per display and per title: with a 120 Hz TV it also answers 1 for mode 15 (119.88 Hz), and [PS5_Vulkan](https://github.com/mihawk-99/PS5_Vulkan/blob/5b5e4fc2d80fdb67a4f61ba9e6a8a424026bb8a5/docs/HARDWARE_FINDINGS.md) measured that `sceVideoOutConfigureOutput` accepts 15 only from a title whose param.json declares high-frame-rate output (attribute3 0x80040). Modes 4, 7, 8, 12-14, 16-17 and 19 return `VIDEO_OUT_ERROR_UNSUPPORTED_OUTPUT_MODE` here, as they did on the measured display. Mode 18 throws, because one console answered 0x80290016 and another 0x8029001E. The name `VIDEO_OUT_ERROR_UNKNOWN_OUTPUT_MODE` for 0x8029001E is ours. Word 3 of the output options is accepted with any value, as on the console, and its meaning is unknown. PPSA12544 probes `0xd000000a`, which the console answers with 0x8029001E, so the title selects 3840 pixels - [sceVideoOutGetResolutionStatus](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the 48-byte layout follows the declarations in ps5-opengl and EVO-PLAYER-PS5 (#1578), which agree; not confirmed on a console. `screenSizeInInch` is 0, the value EDID uses for an undefined size, since the host display's size is not read; `flags` is 0, its bits are unknown. The pane equals the full size because `sceVideoOutSetWindowModeMargins` applies no margins - [sceVideoOutOpen](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown +- [sceVideoOutGetBufferLabelAddress](../../../core/libs/prx/libSceVideoOut/src/Buffer.cpp) exposes a port-owned contiguous array of 16 aligned 64-bit labels and returns its count. Astro Bot PPSA21564's bundled AGC driver passes a pointer output and addresses each label at `base + index * 8`. The count, zero initialization on registration, and release of the previous displayed buffer when its replacement completes follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/23c41333822fe8eca4c609b43119180af63ae4ac/src/core/libraries/videoout/video_out.cpp) and its driver; PS5 console equivalence has not been measured. Labels live until the port closes, and reopening creates a new array. Error precedence follows the reference for null output and the existing open-port policy for handles. Exact scanout release timing, repeated same-buffer flips and concurrent guest label stores remain unverified. EOP-flip registration and interrupt tokens remain unimplemented; exposing these labels does not implement that separate API. - [GPU flips](../../../core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp) (libSceVideoOut) - a command-buffer flip of a buffer index with no registered buffer when the GPU reaches it presents black and completes like any flip (flip event, count, `flipPendingNum`). The console's behaviour is unknown; PPSA05684 requires that it completes: its renderer submits a flip of buffer 0 while another thread waits for `sceVideoOutIsFlipPending` to reach 0 before registering the buffers, and `flipPendingNum` counts that flip from its submission as in shadPS4 - [sceVideoOutAddPreVblankStartEvent](../../../core/libs/prx/libSceVideoOut/src/Event.cpp) (libSceVideoOut) - how long before the vblank the console signals pre-vblank start is unknown. The event is triggered on each emulated vblank tick immediately before the vblank event, with its own count as the event data @@ -23,7 +24,3 @@ - [Overlay output bus](../../../core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp) (libSceVideoOut) - flips on a port opened with `SCE_VIDEO_OUT_BUS_TYPE_OVERLAY` complete and signal their events like main-bus flips, but their buffers are not shown: the overlay is not composited over the main output. Presenting them as whole frames made the window alternate between the two buses - [Mouse capture and delta scaling](../../../core/libs/prx/libSceVideoOut/src/MouseInput.cpp) (libSceVideoOut) - the console's mouse reports relative motion only, so a host cursor can never coincide with the title's cursor; while a title has a mouse open and the window is focused the host cursor is hidden and captured (SDL relative mode, released on focus loss or while the pad's mouse-to-stick toggle is on). Motion deltas are scaled from the presented image's size in window units to the title's display resolution with sub-pixel carry; the console's deltas are counts, so a title that applies its own sensitivity moves its cursor that much faster than the host pointer would have. The scale takes SDL's relative motion in logical window units, the units of `SDL_GetWindowSize`; under Windows DPI scaling SDL may report it in physical pixels, which is unverified (no Windows run yet), and a drawable larger than the window does not change the scale. - -### Buffer labels - -- `sceVideoOutGetBufferLabelAddress` exposes a port-owned contiguous array of 16 aligned 64-bit labels and returns its count. Astro Bot PPSA21564's bundled AGC driver passes a pointer output and addresses each label at `base + index * 8`. The count, zero initialization on registration, and release of the previous displayed buffer when its replacement completes follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/23c41333822fe8eca4c609b43119180af63ae4ac/src/core/libraries/videoout/video_out.cpp) and its driver; PS5 console equivalence has not been measured. Labels live until the port closes, and reopening creates a new array. Error precedence follows the reference for null output and the existing open-port policy for handles. Exact scanout release timing, repeated same-buffer flips and concurrent guest label stores remain unverified. EOP-flip registration and interrupt tokens remain unimplemented; exposing these labels does not implement that separate API.