Files
AnyPS5/core/libs/tests/GuestVideoOutOutputMode.cpp
T3cm0andClaude Fable 5.1 762410ced4 feat(libSceVideoOut): answer the HMD2 bus open as the console does
sceVideoOutOpen with bus type 0x20, the PS VR2 output PPSA05666 opens as
SCE_VIDEO_OUT_BUS_TYPE_HMD2_MAIN after sceHmd2ReprojectionEnableVrMode, threw
VIDEO_OUT_ERROR_INVALID_VALUE and aborted the title. The console returns that
code to a process that is not a game, with the headset connected and worn or
not, for users 255 and 0 and for a null or zeroed open parameter, so the bus
is answered that way before the other argument checks and the title carries
on with the error as its handle, as it does on the console without a headset.

The debt entry records the sequence the title follows on that handle
(RegisterBuffers2 with category 16 and 4000x2040 descriptors that point at
Hmd2 per-eye textures, ConfigureOutput and IsOutputSupported with 0x2000F, a
GPU wait on buffer 0), which a VR backend will have to model, and the test
covers the bus and the HMD output modes 0x20001 and 0x2000C to 0x20010, which
the console and this implementation answer with VIDEO_OUT_ERROR_UNKNOWN_OUTPUT_MODE
on the TV bus.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-19 02:24:36 -05:00

106 lines
5.7 KiB
C++

#include "SceTypes.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include "tests/VideoOutTestEnvironment.hpp"
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
extern "C" {
int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param);
int APS5_VABI sceVideoOutClose(int handle);
int APS5_VABI sceVideoOutInitializeOutputOptions(VideoOutOutputOptions* options);
int APS5_VABI sceVideoOutIsOutputSupported(int handle, std::uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, std::uint64_t reserved);
int APS5_VABI sceVideoOutConfigureOutput(int handle, std::uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, std::uint64_t reserved);
}
static constexpr int SYSTEM_USER = 255;
static constexpr int MAIN_BUS = 0;
static constexpr int HMD2_MAIN_BUS = 0x20;
static constexpr int INVALID_VALUE = static_cast<int>(0x80290001);
static constexpr int INVALID_ADDRESS = static_cast<int>(0x80290002);
static constexpr int INVALID_HANDLE = static_cast<int>(0x8029000B);
static constexpr int RESOURCE_BUSY = static_cast<int>(0x80290009);
static constexpr int UNSUPPORTED_OUTPUT_MODE = static_cast<int>(0x80290016);
static constexpr int UNKNOWN_OUTPUT_MODE = static_cast<int>(0x8029001E);
static void Require(bool value) { if (!value) std::abort(); }
int main() {
VideoOutTestEnvironment environment;
std::array<std::uint8_t, sizeof(VideoOutOutputOptions)> raw{};
raw.fill(0xAA);
VideoOutOutputOptions options{};
std::memcpy(&options, raw.data(), raw.size());
Require(sceVideoOutInitializeOutputOptions(&options) == 0);
std::memcpy(raw.data(), &options, raw.size());
for (std::size_t index = 0; index < raw.size(); ++index) Require(raw[index] == (index == 2 ? 0xFF : 0));
Require(sceVideoOutInitializeOutputOptions(nullptr) == INVALID_ADDRESS);
std::array<std::uint8_t, 64> zeroedOpenParam{};
Require(sceVideoOutOpen(SYSTEM_USER, HMD2_MAIN_BUS, 0, nullptr) == INVALID_VALUE);
Require(sceVideoOutOpen(SYSTEM_USER, HMD2_MAIN_BUS, 0, zeroedOpenParam.data()) == INVALID_VALUE);
Require(sceVideoOutOpen(0, HMD2_MAIN_BUS, 0, nullptr) == INVALID_VALUE);
int handle = 0;
try {
for (int unopened : {-1, 0, 1, 0x7FFFFFFF}) {
Require(sceVideoOutIsOutputSupported(unopened, 1, &options, nullptr, 0) == INVALID_HANDLE);
Require(sceVideoOutIsOutputSupported(unopened, 2, nullptr, &options, 1) == INVALID_HANDLE);
}
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);
Require(sceVideoOutOpen(SYSTEM_USER, MAIN_BUS, 0, nullptr) == RESOURCE_BUSY);
Require(sceVideoOutIsOutputSupported(handle, 1, &options, nullptr, 0) == 1);
Require(sceVideoOutIsOutputSupported(handle, 1, nullptr, nullptr, 0) == 1);
const int highFrameRate = sceVideoOutIsOutputSupported(handle, 15, &options, nullptr, 0);
Require(highFrameRate == 1 || highFrameRate == UNSUPPORTED_OUTPUT_MODE);
Require(sceVideoOutConfigureOutput(handle, 15, &options, nullptr, 0) == UNSUPPORTED_OUTPUT_MODE);
for (std::uint64_t mode : {0x4ull, 0x7ull, 0x8ull, 0xCull, 0xDull, 0xEull, 0x10ull, 0x11ull, 0x13ull}) {
Require(sceVideoOutIsOutputSupported(handle, mode, &options, nullptr, 0) == UNSUPPORTED_OUTPUT_MODE);
Require(sceVideoOutConfigureOutput(handle, mode, &options, nullptr, 0) == UNSUPPORTED_OUTPUT_MODE);
}
for (std::uint64_t mode : {0x0ull, 0x2ull, 0x3ull, 0x5ull, 0x6ull, 0x9ull, 0xAull, 0xBull, 0x14ull, 0x20ull, 0x3Full, 0x100ull, 0xD000000Aull, 0x8000000000000001ull, ~0ull}) {
Require(sceVideoOutIsOutputSupported(handle, mode, &options, nullptr, 0) == UNKNOWN_OUTPUT_MODE);
}
for (std::uint64_t mode : {0x20001ull, 0x2000Cull, 0x2000Dull, 0x2000Eull, 0x2000Full, 0x20010ull}) {
Require(sceVideoOutIsOutputSupported(handle, mode, nullptr, nullptr, 0) == UNKNOWN_OUTPUT_MODE);
Require(sceVideoOutConfigureOutput(handle, mode, nullptr, nullptr, 0) == UNKNOWN_OUTPUT_MODE);
}
bool thrown = false;
try {
sceVideoOutIsOutputSupported(handle, 0x12, &options, nullptr, 0);
} catch (const std::runtime_error&) {
thrown = true;
}
Require(thrown);
Require(sceVideoOutIsOutputSupported(handle, 1, &options, &options, 0) == INVALID_VALUE);
Require(sceVideoOutIsOutputSupported(handle, 1, &options, nullptr, 1) == INVALID_VALUE);
Require(sceVideoOutIsOutputSupported(handle, 2, &options, nullptr, 1) == INVALID_VALUE);
VideoOutOutputOptions zeroed{};
Require(sceVideoOutIsOutputSupported(handle, 1, &zeroed, nullptr, 0) == INVALID_VALUE);
Require(sceVideoOutIsOutputSupported(handle, 2, &zeroed, nullptr, 0) == UNKNOWN_OUTPUT_MODE);
Require(sceVideoOutIsOutputSupported(handle, 4, &zeroed, nullptr, 0) == UNSUPPORTED_OUTPUT_MODE);
for (std::size_t word = 0; word < 16; ++word) {
VideoOutOutputOptions changed = options;
changed.internalData[word] ^= 1;
Require(sceVideoOutIsOutputSupported(handle, 1, &changed, nullptr, 0) == (word == 3 ? 1 : INVALID_VALUE));
}
VideoOutOutputOptions freeWord = options;
freeWord.internalData[3] = 0xFFFFFFFF;
Require(sceVideoOutIsOutputSupported(handle, 1, &freeWord, nullptr, 0) == 1);
Require(sceVideoOutConfigureOutput(handle, 1, &options, nullptr, 0) == 0);
Require(sceVideoOutClose(handle) == 0);
Require(sceVideoOutIsOutputSupported(handle, 1, &options, nullptr, 0) == INVALID_HANDLE);
LibcRunShutdown_nid_postfix();
}