Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -239,5 +239,6 @@ std::size_t CaptureCallerOffsets(std::span<unsigned long long> 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
7 changes: 7 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1986,3 +1986,10 @@ void Write(std::uint64_t address, std::span<const std::byte> 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<std::uint64_t>::required_alignment, true);
AgcDriver::GuestMemory::StoreOwnBytes(reinterpret_cast<std::uintptr_t>(pointer), sizeof(*pointer), [&] {
std::atomic_ref(*pointer).store(value, std::memory_order_release);
});
}
9 changes: 9 additions & 0 deletions core/libs/prx/libSceVideoOut/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ 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:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()

set_target_properties(video_out_flip_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
foreach(flipMode IN ITEMS lifetime reopen decode controls overlay unopened)
if(flipMode STREQUAL "lifetime")
Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libSceVideoOut/include/Buffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ struct VideoOutConfig {
VideoOutVblankStatus preVblankStatus{};

std::array<VideoOutBuffer, VIDEO_OUT_BUFFER_NUM_MAX> buffers{};
alignas(std::atomic_ref<std::uint64_t>::required_alignment) std::array<std::uint64_t, VIDEO_OUT_BUFFER_NUM_MAX> bufferLabels{};
std::array<uint32_t, VIDEO_OUT_BUFFER_NUM_MAX> bufferPending{};
std::array<BufferReuseTracker, VIDEO_OUT_BUFFER_NUM_MAX> bufferReuse;
std::array<BufferAttributeGroup, VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX> groups{};
Expand Down
17 changes: 17 additions & 0 deletions core/libs/prx/libSceVideoOut/src/Buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uintptr_t>(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");
Expand Down Expand Up @@ -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&) {
Expand Down
6 changes: 6 additions & 0 deletions core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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"
Expand Down Expand Up @@ -557,6 +558,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;
Expand Down
72 changes: 72 additions & 0 deletions core/libs/prx/libSceVideoOut/tests/Flip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#include <string>
#include <limits>
Expand Down Expand Up @@ -665,6 +666,75 @@ void testFlipAfterGpuWork() {
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<int>::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<std::uint64_t>::required_alignment == 0, "label array is absent or unaligned");
auto* labels = reinterpret_cast<std::uint64_t*>(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<std::byte, sizeof(address) + 2> unaligned;
unaligned.fill(std::byte{0x5a});
check(sceVideoOutGetBufferLabelAddress(handle, reinterpret_cast<std::uintptr_t*>(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<std::byte> allocation(2 * 65536 + 65535);
const auto storage = alignedBuffer(allocation);
std::array<VideoOutBuffers, 2> 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<std::uint64_t*>(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 testUnopenedEventRegistration() {
KernelEqueue eq = 0;
check(sceKernelCreateEqueue(&eq, "unopened") == 0, "the event queue was not created");
Expand Down Expand Up @@ -722,6 +792,8 @@ 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]) == "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();
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/libSceVideoOut.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading