Skip to content
Closed
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
2 changes: 1 addition & 1 deletion core/libs/prx/libSceVideoOut/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,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 flipmaster)
if(flipMode STREQUAL "lifetime")
add_test(NAME video_out_flip_${flipMode} COMMAND video_out_flip_tests)
else()
Expand Down
8 changes: 0 additions & 8 deletions core/libs/prx/libSceVideoOut/Unimplemented.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,7 @@

extern "C" {

int APS5_VABI sceVideoOutSetFlipMaster() {
NotImplemented_nid_no_patch(__func__);
return 0;
}

int APS5_VABI sceVideoOutClearFlipMaster() {
NotImplemented_nid_no_patch(__func__);
return 0;
}

int APS5_VABI sceVideoOutGetOutputStatusWithVrr(int handle, void* status) {
NotImplemented_nid_no_patch(__func__);
Expand Down
2 changes: 2 additions & 0 deletions core/libs/prx/libSceVideoOut/include/Output.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ int APS5_VABI sceVideoOutColorSettingsSetGamma(VideoOutColorSettings* settings,
int APS5_VABI sceVideoOutAdjustColor(int handle, const VideoOutColorSettings* settings);
int APS5_VABI sceVideoOutVrrPegToFixedRate(int handle, uint64_t arg1, uint64_t arg2);
int APS5_VABI sceVideoOutVrrUnpegFromFixedRate(int handle);
int APS5_VABI sceVideoOutSetFlipMaster(int slaveHandle, int masterHandle);
int APS5_VABI sceVideoOutClearFlipMaster(int slaveHandle);

}

Expand Down
5 changes: 5 additions & 0 deletions core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ static constexpr int VIDEO_OUT_ERROR_UNSUPPORTED_OUTPUT_MODE = -2144796650;
static constexpr int VIDEO_OUT_ERROR_UNAVAILABLE_OUTPUT_MODE = -2144796647;
static constexpr int VIDEO_OUT_ERROR_INVALID_EVENT = -2144796659;
static constexpr int VIDEO_OUT_ERROR_UNKNOWN_OUTPUT_MODE = -2144796642;
static constexpr int VIDEO_OUT_ERROR_UNSUPPORTED_OPERATION = -2144796644;

static constexpr int VIDEO_OUT_BUS_TYPE_MAIN = 0;
static constexpr int VIDEO_OUT_BUS_TYPE_OVERLAY = 1;
Expand Down Expand Up @@ -134,6 +135,8 @@ struct VideoOutConfig {
std::chrono::steady_clock::time_point lastTimingFlip{};
uint64_t outputMode = VIDEO_OUT_OUTPUT_MODE_DEFAULT;
float gamma = 1.0f;
int flipMaster = 0;
std::uint32_t flipSlaves = 0;

VideoOutFlipStatus flipStatus{};
VideoOutVblankStatus vblankStatus{};
Expand Down Expand Up @@ -217,6 +220,8 @@ class VideoOutDriver {
bool HasConfig(int handle);
bool SupportsHighFrameRate();
int GetDisplayRefreshRate();
int SetFlipMaster(int slaveHandle, int masterHandle);
int ClearFlipMaster(int slaveHandle);

// 0, or VIDEO_OUT_ERROR_FLIP_QUEUE_FULL when the title has VIDEO_OUT_FLIP_QUEUE_CAPACITY flips pending.
int SubmitFlip(int handle, int index, int flipMode, int64_t flipArg);
Expand Down
3 changes: 3 additions & 0 deletions core/libs/prx/libSceVideoOut/src/Buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flipMode, int64_t
if (cfg->closing) {
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE");
}
if (cfg->flipMaster != 0 || cfg->flipSlaves != 0) {
throw std::runtime_error(std::string(__func__) + ": flips on a port tied by sceVideoOutSetFlipMaster are not modelled");
}
const bool special = (index == VIDEO_OUT_BUFFER_INDEX_BLANK || index == VIDEO_OUT_BUFFER_INDEX_BLACK);
if (!special && !cfg->buffers[index].Occupied()) {
throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_INDEX");
Expand Down
12 changes: 12 additions & 0 deletions core/libs/prx/libSceVideoOut/src/Output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,18 @@ int APS5_VABI sceVideoOutVrrPegToFixedRate(int handle, uint64_t arg1, uint64_t a
LibcAwaitExit_nid_postfix();
}

int APS5_VABI sceVideoOutSetFlipMaster(int slaveHandle, int masterHandle) try {
return VideoOutDriver::Get().SetFlipMaster(slaveHandle, masterHandle);
} catch (const ProcessShutdown&) {
LibcAwaitExit_nid_postfix();
}

int APS5_VABI sceVideoOutClearFlipMaster(int slaveHandle) try {
return VideoOutDriver::Get().ClearFlipMaster(slaveHandle);
} catch (const ProcessShutdown&) {
LibcAwaitExit_nid_postfix();
}

int APS5_VABI sceVideoOutAddVrrStatusFlagsPrivilege() {
return 0;
}
Expand Down
60 changes: 60 additions & 0 deletions core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class VideoOutput final : public AgcDriver::IVideoOutput {
if (queue->stopping || cfg->shutdownToken.stop_requested()) throw ProcessShutdown{};
std::lock_guard lock(cfg->mutex);
checkConfig(*cfg);
require(cfg->flipMaster == 0 && cfg->flipSlaves == 0, "flips on a port tied by sceVideoOutSetFlipMaster are not modelled");
request->generation = cfg->generation;
request->flipRate = cfg->flipRate;
if (info.index >= 0) request->reuseTicket = cfg->bufferReuse[info.index].Reserve();
Expand Down Expand Up @@ -357,6 +358,10 @@ int VideoOutDriver::Open(int busType) {
bool VideoOutDriver::Close(int handle) {
std::lock_guard lock(mutex);
if (LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{};
if (handle > 0 && handle < VIDEO_OUT_NUM_MAX && contexts[handle] != nullptr) {
std::lock_guard cfgLock(contexts[handle]->mutex);
require(contexts[handle]->flipSlaves == 0, "closing a flip master that still has slave ports is not modelled");
}
return close(handle);
}

Expand All @@ -365,6 +370,12 @@ bool VideoOutDriver::close(int handle) {
AgcDriverUnregisterVideoOutput_nid_postfix(static_cast<uint32_t>(handle), outputs[handle]);
outputs[handle].reset();
auto cfg = contexts[handle];
if (cfg->flipMaster != 0) {
auto& master = *contexts[cfg->flipMaster];
std::scoped_lock tieLock(cfg->mutex, master.mutex);
cfg->flipMaster = 0;
--master.flipSlaves;
}
std::lock_guard cfgLock(cfg->mutex);
cfg->opened = false;
cfg->closing = true;
Expand Down Expand Up @@ -418,6 +429,55 @@ bool VideoOutDriver::HasConfig(int handle) {
return cfg->opened && !cfg->closing;
}

int VideoOutDriver::SetFlipMaster(int slaveHandle, int masterHandle) {
std::lock_guard lock(mutex);
{
std::lock_guard queueLock(flipQueue->mutex);
if (flipQueue->failure) std::rethrow_exception(flipQueue->failure);
if (flipQueue->stopping || LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{};
}
if (slaveHandle <= 0 || slaveHandle >= VIDEO_OUT_NUM_MAX || contexts[slaveHandle] == nullptr) return VIDEO_OUT_ERROR_INVALID_HANDLE;
if (masterHandle <= 0 || masterHandle >= VIDEO_OUT_NUM_MAX || contexts[masterHandle] == nullptr) return VIDEO_OUT_ERROR_INVALID_HANDLE;
auto& slave = *contexts[slaveHandle];
auto& master = *contexts[masterHandle];
if (slaveHandle == masterHandle) {
std::lock_guard cfgLock(slave.mutex);
slave.CheckAlive();
return slave.Closed() ? VIDEO_OUT_ERROR_INVALID_HANDLE : VIDEO_OUT_ERROR_UNSUPPORTED_OPERATION;
}
std::scoped_lock cfgLock(slave.mutex, master.mutex);
slave.CheckAlive();
master.CheckAlive();
if (slave.Closed() || master.Closed()) return VIDEO_OUT_ERROR_INVALID_HANDLE;
if (slave.flipMaster != 0 || slave.flipSlaves != 0 || master.flipMaster != 0) return VIDEO_OUT_ERROR_UNSUPPORTED_OPERATION;
if (slave.flipStatus.flipPendingNum != 0 || master.flipStatus.flipPendingNum != 0) return VIDEO_OUT_ERROR_RESOURCE_BUSY;
slave.flipMaster = masterHandle;
++master.flipSlaves;
return 0;
}

int VideoOutDriver::ClearFlipMaster(int slaveHandle) {
std::lock_guard lock(mutex);
{
std::lock_guard queueLock(flipQueue->mutex);
if (flipQueue->failure) std::rethrow_exception(flipQueue->failure);
if (flipQueue->stopping || LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{};
}
if (slaveHandle <= 0 || slaveHandle >= VIDEO_OUT_NUM_MAX || contexts[slaveHandle] == nullptr) return VIDEO_OUT_ERROR_INVALID_HANDLE;
auto& slave = *contexts[slaveHandle];
{
std::lock_guard cfgLock(slave.mutex);
slave.CheckAlive();
if (slave.Closed()) return VIDEO_OUT_ERROR_INVALID_HANDLE;
require(slave.flipMaster != 0, "sceVideoOutClearFlipMaster on a port without a flip master: the console's answer is unknown");
}
auto& master = *contexts[slave.flipMaster];
std::scoped_lock cfgLock(slave.mutex, master.mutex);
slave.flipMaster = 0;
--master.flipSlaves;
return 0;
}

int VideoOutDriver::SubmitFlip(int handle, int index, int flipMode, int64_t flipArg) {
if (LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{};
// A title that does not pace on flipPendingNum can run ahead of the presenter now that the queue
Expand Down
63 changes: 63 additions & 0 deletions core/libs/prx/libSceVideoOut/tests/Flip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,68 @@ void testOverlay() {
LibcRunShutdown_nid_postfix();
}

void testFlipMaster() {
const int mainHandle = sceVideoOutOpen(255, 0, 0, nullptr);
const int overlayHandle = sceVideoOutOpen(255, 1, 0, nullptr);
auto mainCfg = VideoOutDriver::Get().GetConfig(mainHandle);
auto overlayCfg = VideoOutDriver::Get().GetConfig(overlayHandle);
std::vector<std::byte> allocation(65536 + 65535);
const auto storage = alignedBuffer(allocation);
VideoOutBuffers buffer{storage.data(), nullptr, {nullptr, nullptr}};
VideoOutBufferAttribute2 attribute{};
sceVideoOutSetBufferAttribute2(&attribute, 0x8000000000000000ull, 0, 64, 64, 0, 0, 0);
check(sceVideoOutRegisterBuffers2(mainHandle, 0, 0, &buffer, 1, &attribute, 0, nullptr) == 0, "buffer registration failed");
const auto waitFlips = [&](std::uint64_t count) {
std::unique_lock lock(mainCfg->mutex);
const bool done = mainCfg->vblankCond.wait_for(lock, std::chrono::seconds(15), [&] { return mainCfg->failure || mainCfg->flipStatus.count == count; });
if (mainCfg->failure) std::rethrow_exception(mainCfg->failure);
check(done && mainCfg->flipStatus.flipPendingNum == 0, "the flip did not complete");
};
const auto tied = [](const std::shared_ptr<VideoOutConfig>& cfg, int master, std::uint32_t slaves) {
std::lock_guard lock(cfg->mutex);
return cfg->flipMaster == master && cfg->flipSlaves == slaves;
};
for (int invalid : {-1, 0, 3, VIDEO_OUT_NUM_MAX, 0x7fffffff}) {
check(sceVideoOutSetFlipMaster(invalid, mainHandle) == VIDEO_OUT_ERROR_INVALID_HANDLE, "an invalid slave handle was tied");
check(sceVideoOutSetFlipMaster(overlayHandle, invalid) == VIDEO_OUT_ERROR_INVALID_HANDLE, "an invalid master handle was tied");
check(sceVideoOutClearFlipMaster(invalid) == VIDEO_OUT_ERROR_INVALID_HANDLE, "an invalid handle was untied");
}
check(sceVideoOutSetFlipMaster(mainHandle, mainHandle) == VIDEO_OUT_ERROR_UNSUPPORTED_OPERATION, "a port became its own flip master");
check(expectFailure([&] { sceVideoOutClearFlipMaster(mainHandle); }).find("without a flip master") != std::string::npos, "a port without a flip master was untied");
check(tied(mainCfg, 0, 0) && tied(overlayCfg, 0, 0), "a rejected call changed the tie");

auto gate = std::make_shared<Gate>();
AgcDriverRegisterVideoOutput_nid_postfix(7, gate);
std::array<uint32_t, 6> held{0xc004105c, 7, 0xfffffffeu, 1, 0, 0};
Packet heldPacket{held.data(), 6, 0, {}};
check(sceAgcDriverSubmitDcb(&heldPacket) == 0, "the gated flip was not accepted");
check(sceVideoOutSubmitFlip(mainHandle, 0, 1, 1) == 0, "the flip was not accepted");
check(sceVideoOutSetFlipMaster(overlayHandle, mainHandle) == VIDEO_OUT_ERROR_RESOURCE_BUSY, "a master with a pending flip was tied");
gate->Release();
waitFlips(1);
AgcDriverUnregisterVideoOutput_nid_postfix(7, gate);

check(sceVideoOutSetFlipMaster(overlayHandle, mainHandle) == 0, "the overlay port was not tied to the main port");
check(tied(overlayCfg, mainHandle, 0) && tied(mainCfg, 0, 1), "the tie was not recorded");
check(sceVideoOutSetFlipMaster(overlayHandle, mainHandle) == VIDEO_OUT_ERROR_UNSUPPORTED_OPERATION, "a slave port was tied twice");
check(sceVideoOutSetFlipMaster(mainHandle, overlayHandle) == VIDEO_OUT_ERROR_UNSUPPORTED_OPERATION, "a master port became a slave");
check(expectFailure([&] { sceVideoOutSubmitFlip(mainHandle, 0, 1, 2); }).find("sceVideoOutSetFlipMaster") != std::string::npos, "a flip on a tied port was accepted");
check(expectFailure([&] { sceVideoOutClose(mainHandle); }).find("flip master") != std::string::npos, "a master with a slave port was closed");
check(sceVideoOutClearFlipMaster(overlayHandle) == 0, "the tie was not cleared");
check(tied(overlayCfg, 0, 0) && tied(mainCfg, 0, 0), "the tie was not removed");
check(sceVideoOutSubmitFlip(mainHandle, 0, 1, 3) == 0, "the flip after clearing the tie was not accepted");
waitFlips(2);

check(sceVideoOutSetFlipMaster(overlayHandle, mainHandle) == 0, "the overlay port was not tied again");
check(sceVideoOutClose(overlayHandle) == 0, "the slave port was not closed");
check(tied(mainCfg, 0, 0), "closing the slave port kept the tie");
check(sceVideoOutSubmitFlip(mainHandle, 0, 1, 4) == 0, "the flip after closing the slave port was not accepted");
waitFlips(3);
sceVideoOutUnregisterBuffers(mainHandle, 0);
check(sceVideoOutClose(mainHandle) == 0, "the master port was not closed");
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");
Expand Down Expand Up @@ -725,6 +787,7 @@ int main(int argc, char** argv) {
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]) == "flipmaster") testFlipMaster();
else if (argc == 2 && std::string(argv[1]) == "deferred") testDeferredBuffer();
else if (argc == 2 && std::string(argv[1]) == "unregistered") testUnregisteredBuffer();
else testLifetime(argc == 2 && std::string(argv[1]) == "reopen");
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/debt/libSceHmd2.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

### Unknown function info

- PPSA29866 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [sceHmd2ReprojectionQuerySeeThroughBufferSizeAlign](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2), [sceVideoOutSetFlipMaster and sceVideoOutClearFlipMaster](../../../core/libs/prx/libSceVideoOut/Unimplemented.cpp) (libSceVideoOut)
- PPSA29866 imports declared without parameters, signatures unknown, added without an implementation because the title fails to load without them: [sceHmd2ReprojectionQuerySeeThroughBufferSizeAlign](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2)
- [sceHmd2Initialize](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - the error the console returns when the PS VR2 runtime is unavailable is unknown; it returns libSceHmd's `UNSUPPORTED_FEATURE` (0x81110016). PPSA01317 passes a pointer to 16 zeroed bytes and only tests the result for non-zero
- PPSA01317 imports declared without parameters, signatures unknown: the remaining [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) and [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp) exports. Names from the aerolib NID list
- PPSA15878 imports declared without parameters, signatures unknown: the [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) reprojection and gaze exports, `sceVrTracker2Finalize`, `sceVrTracker2GetPlayAreaOrientedBoundingBox` and `sceVrTracker2LocateCoordinateSystem` in [libSceVrTracker2](../../../core/libs/prx/libSceVrTracker2/Export.cpp), and the `scePadVrController` trigger effect exports in [libScePad](../../../core/libs/prx/libScePad/Export.cpp). Names from the aerolib NID list
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 @@ -23,3 +23,4 @@

- [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.
- [sceVideoOutSetFlipMaster, sceVideoOutClearFlipMaster](../../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - grouped master/slave flips (modes 8 and 9) are not modelled, so a flip on a tied port throws, from `sceVideoOutSubmitFlip` and from a command-buffer flip
Loading