Merge pull request #11948 from 00xDisaster/feat/video-out-host-policy-current

feat(libSceVideoOut): limit the guest display to the host resolution
This commit is contained in:
Dean authored and GitHub committed 2026-11-05 13:00:08 +13:00
commit c42e3a000c
4 files changed
+243 -2

No files matched your search

@@ -284,6 +284,37 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_video_out_host_resolution_limit_tests ${LIBS_INCLUDE_DIR}/tests/GuestVideoOutHostResolutionLimit.cpp)
target_include_directories(guest_video_out_host_resolution_limit_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR})
target_link_libraries(guest_video_out_host_resolution_limit_tests PRIVATE libSceVideoOut libSceAgcDriver libkernel libc)
configure_windows_unwind(guest_video_out_host_resolution_limit_tests)
add_test(NAME guest_video_out_host_resolution_limit COMMAND guest_video_out_host_resolution_limit_tests)
set_tests_properties(guest_video_out_host_resolution_limit PROPERTIES TIMEOUT 60 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0)
add_test(NAME guest_video_out_host_resolution_height_only COMMAND guest_video_out_host_resolution_limit_tests height-only)
set_tests_properties(guest_video_out_host_resolution_height_only PROPERTIES TIMEOUT 60 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0)
add_test(NAME guest_video_out_host_resolution_width_only COMMAND guest_video_out_host_resolution_limit_tests width-only)
set_tests_properties(guest_video_out_host_resolution_width_only PROPERTIES TIMEOUT 60 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0)
add_test(NAME guest_video_out_host_resolution_four_k COMMAND guest_video_out_host_resolution_limit_tests four-k)
set_tests_properties(guest_video_out_host_resolution_four_k PROPERTIES TIMEOUT 60 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0)
add_test(NAME guest_video_out_host_resolution_malformed COMMAND guest_video_out_host_resolution_limit_tests malformed)
set_tests_properties(guest_video_out_host_resolution_malformed PROPERTIES TIMEOUT 60 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0)
add_test(NAME guest_video_out_host_resolution_disabled COMMAND guest_video_out_host_resolution_limit_tests disabled)
set_tests_properties(guest_video_out_host_resolution_disabled PROPERTIES TIMEOUT 60 SKIP_RETURN_CODE 77 RESOURCE_LOCK videoout_app0)
if(WIN32)
set_tests_properties(guest_video_out_host_resolution_limit PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(guest_video_out_host_resolution_height_only PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(guest_video_out_host_resolution_width_only PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(guest_video_out_host_resolution_four_k PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(guest_video_out_host_resolution_malformed PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(guest_video_out_host_resolution_disabled PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_video_out_color_space_tests ${LIBS_INCLUDE_DIR}/tests/GuestVideoOutColorSpace.cpp)
target_include_directories(guest_video_out_color_space_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_video_out_color_space_tests PRIVATE libSceVideoOut libSceAgcDriver libkernel libc)
+44 -2
View File
@@ -1,12 +1,17 @@
#include "prx/libSceVideoOut/include/VideoOut.hpp"
#include <algorithm>
#include <array>
#include <charconv>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <stdexcept>
#include <cmath>
#include <string>
#include <string_view>
#include <system_error>
#include "SceTypes.hpp"
#include "prx/libSceVideoOut/include/Output.hpp"
@@ -15,6 +20,41 @@
#include "prx/libkernel/AppMetadata/include/AppMetadata.hpp"
#include "prx/libSceAgcDriver/Execution/include/HostHdr.hpp"
namespace {
struct VideoOutHostResolution {
std::uint32_t width = 0;
std::uint32_t height = 0;
};
std::uint32_t ParseVideoOutHostDimension(const char* text, const char* name) {
if (text == nullptr) return 0;
const std::string_view value(text);
std::uint32_t parsed = 0;
const auto result = std::from_chars(value.data(), value.data() + value.size(), parsed);
if (value.empty() || result.ec != std::errc{} || result.ptr != value.data() + value.size() || parsed == 0) {
throw std::runtime_error(std::string("VideoOut: invalid ") + name + " '" + std::string(value) + "'");
}
return parsed;
}
VideoOutHostResolution VideoOutHostResolutionLimit() {
static const VideoOutHostResolution limit = [] {
VideoOutHostResolution parsed{};
if (const char* text = std::getenv("ANYPS5_VIDEO_OUT_MAX_WIDTH")) parsed.width = ParseVideoOutHostDimension(text, "ANYPS5_VIDEO_OUT_MAX_WIDTH");
if (const char* text = std::getenv("ANYPS5_VIDEO_OUT_MAX_HEIGHT")) parsed.height = ParseVideoOutHostDimension(text, "ANYPS5_VIDEO_OUT_MAX_HEIGHT");
return parsed;
}();
return limit;
}
bool VideoOutHostResolutionLimited() {
const auto limit = VideoOutHostResolutionLimit();
return (limit.width != 0 && limit.width < 3840) || (limit.height != 0 && limit.height < 2160);
}
}
static constexpr std::uint32_t VIDEO_OUT_OUTPUT_OPTIONS_FIRST_WORD = 0x00FF0000;
static constexpr std::size_t VIDEO_OUT_OUTPUT_OPTIONS_FREE_WORD = 3;
static constexpr int VIDEO_OUT_ERROR_0x8029001C = -2144796644;
@@ -332,7 +372,9 @@ int APS5_VABI sceVideoOutInitializeOutputOptions(VideoOutOutputOptions* options)
}
int APS5_VABI sceVideoOutIsOutputSupported(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) try {
return validateOutputConfig(handle, mode, options, reservedPtr, reserved);
const int result = validateOutputConfig(handle, mode, options, reservedPtr, reserved);
if (result == VIDEO_OUT_ERROR_UNKNOWN_OUTPUT_MODE && VideoOutHostResolutionLimited()) return 0;
return result;
} catch (const ProcessShutdown&) {
LibcAwaitExit_nid_postfix();
}
@@ -0,0 +1,166 @@
#include "SceTypes.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include "prx/libSceVideoOut/include/Buffer.hpp"
#include "prx/libSceVideoOut/include/Output.hpp"
#include "prx/libSceVideoOut/include/VideoOutDriver.hpp"
#include "tests/VideoOutTestEnvironment.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <mutex>
#include <stdexcept>
#include <string_view>
#include <vector>
#undef main
static constexpr int SYSTEM_USER = 255;
static constexpr int MAIN_BUS = 0;
static constexpr int UNSUPPORTED_OUTPUT_MODE = static_cast<int>(0x80290016);
static constexpr int UNKNOWN_OUTPUT_MODE = static_cast<int>(0x8029001E);
static constexpr int UNAVAILABLE_OUTPUT_MODE = static_cast<int>(0x80290019);
static constexpr std::uint64_t TILING_MODE_LINEAR = 0;
static constexpr std::uint64_t PIXEL_FORMAT = 0x8000000022000000ull;
static void Require(bool value) { if (!value) std::abort(); }
static void SetEnvironment(const char* name, const char* value) {
#ifdef _WIN32
Require(_putenv_s(name, value) == 0);
#else
Require(setenv(name, value, 1) == 0);
#endif
}
static void UnsetEnvironment(const char* name) {
#ifdef _WIN32
Require(_putenv_s(name, "") == 0);
#else
Require(unsetenv(name) == 0);
#endif
}
int main(int argc, char** argv) {
const std::string_view mode = argc > 1 ? std::string_view(argv[1]) : std::string_view{};
const bool heightOnly = mode == "height-only";
const bool widthOnly = mode == "width-only";
const bool fourK = mode == "four-k";
const bool malformed = mode == "malformed";
const bool disabled = mode == "disabled";
if (disabled || heightOnly) {
UnsetEnvironment("ANYPS5_VIDEO_OUT_MAX_WIDTH");
} else if (fourK) {
SetEnvironment("ANYPS5_VIDEO_OUT_MAX_WIDTH", "3840");
} else if (malformed) {
SetEnvironment("ANYPS5_VIDEO_OUT_MAX_WIDTH", "1920px");
} else {
SetEnvironment("ANYPS5_VIDEO_OUT_MAX_WIDTH", "1920");
}
if (disabled || widthOnly) {
UnsetEnvironment("ANYPS5_VIDEO_OUT_MAX_HEIGHT");
} else if (fourK) {
SetEnvironment("ANYPS5_VIDEO_OUT_MAX_HEIGHT", "2160");
} else {
SetEnvironment("ANYPS5_VIDEO_OUT_MAX_HEIGHT", "1080");
}
VideoOutTestEnvironment environment;
VideoOutOutputOptions options{};
Require(sceVideoOutInitializeOutputOptions(&options) == 0);
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);
Require(VideoOutOutputSupport_nid_no_patch(1, &options, 60) == 1);
Require(VideoOutOutputSupport_nid_no_patch(15, &options, 59) == 0);
Require(VideoOutOutputSupport_nid_no_patch(15, &options, 120) == 1);
Require(VideoOutOutputSupport_nid_no_patch(0x4ull, &options, 120) == UNSUPPORTED_OUTPUT_MODE);
Require(VideoOutOutputSupport_nid_no_patch(0xD000000Aull, &options, 120) == UNKNOWN_OUTPUT_MODE);
Require(sceVideoOutIsOutputSupported(handle, 1, &options, nullptr, 0) == 1);
Require(sceVideoOutConfigureOutput(handle, 1, &options, nullptr, 0) == 0);
for (std::uint64_t unknownMode : {0xD000000Aull, 0x8000000000000001ull, ~0ull}) {
if (malformed) continue;
if (disabled || fourK) {
Require(sceVideoOutIsOutputSupported(handle, unknownMode, &options, nullptr, 0) == UNKNOWN_OUTPUT_MODE);
Require(sceVideoOutConfigureOutput(handle, unknownMode, &options, nullptr, 0) == UNKNOWN_OUTPUT_MODE);
} else {
Require(sceVideoOutIsOutputSupported(handle, unknownMode, &options, nullptr, 0) == 0);
Require(sceVideoOutConfigureOutput(handle, unknownMode, &options, nullptr, 0) == UNAVAILABLE_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);
}
VideoOutBufferAttribute2 attribute{};
sceVideoOutSetBufferAttribute2(&attribute, PIXEL_FORMAT, TILING_MODE_LINEAR, 3840, 2160, 0, 0, 0);
Require(attribute.width == 3840);
Require(attribute.height == 2160);
sceVideoOutSetBufferAttribute2(&attribute, PIXEL_FORMAT, TILING_MODE_LINEAR, 1280, 720, 0, 0, 0);
Require(attribute.width == 1280);
Require(attribute.height == 720);
VideoOutBufferAttribute ps4{};
sceVideoOutSetBufferAttribute(&ps4, 0x80002200u, 1, 0, 3840, 2160, 0);
Require(ps4.width == 3840);
Require(ps4.height == 2160);
std::vector<std::byte> allocation(64u * 1024u * 1024u);
const auto address = reinterpret_cast<std::uintptr_t>(allocation.data());
const auto aligned = (address + 65535u) & ~static_cast<std::uintptr_t>(65535u);
VideoOutBuffers buffer{reinterpret_cast<void*>(aligned), nullptr, {nullptr, nullptr}};
VideoOutBufferAttribute2 direct{};
direct.width = 3840;
direct.height = 2160;
direct.pixel_format = PIXEL_FORMAT;
direct.tiling_mode = TILING_MODE_LINEAR;
Require(sceVideoOutRegisterBuffers2(handle, 0, 0, &buffer, 1, &direct, 0, nullptr) == 0);
auto cfg = VideoOutDriver::Get().GetConfig(handle);
Require(cfg != nullptr);
{
std::lock_guard lock(cfg->mutex);
Require(cfg->groups[0].attribute.width == 3840);
Require(cfg->groups[0].attribute.height == 2160);
Require(cfg->width == 3840);
Require(cfg->height == 2160);
}
VideoOutBufferAttribute2 changed{};
changed.width = 3840;
changed.height = 2160;
changed.pixel_format = PIXEL_FORMAT;
changed.tiling_mode = TILING_MODE_LINEAR;
Require(sceVideoOutSubmitChangeBufferAttribute2(handle, 0, &changed, nullptr) == 0);
{
std::lock_guard lock(cfg->mutex);
Require(cfg->groups[0].attribute.width == 3840);
Require(cfg->groups[0].attribute.height == 2160);
}
if (malformed) {
bool supportedThrew = false;
try {
(void)sceVideoOutIsOutputSupported(handle, 0xD000000Aull, &options, nullptr, 0);
} catch (const std::runtime_error& error) {
supportedThrew = std::string_view(error.what()).find("ANYPS5_VIDEO_OUT_MAX_WIDTH") != std::string_view::npos;
}
Require(supportedThrew);
bool configureThrew = false;
try {
(void)sceVideoOutConfigureOutput(handle, 0xD000000Aull, &options, nullptr, 0);
} catch (const std::runtime_error& error) {
configureThrew = std::string_view(error.what()).find("ANYPS5_VIDEO_OUT_MAX_WIDTH") != std::string_view::npos;
}
Require(configureThrew);
}
Require(sceVideoOutClose(handle) == 0);
LibcRunShutdown_nid_postfix();
}
+2
View File
@@ -628,6 +628,8 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti
| `ANYPS5_INPUT_CONFIG` | string | `core/libs/prx/libSceVideoOut/src/InputMapping.cpp` | | path of the input mapping file that binds host keys and mouse to guest pad actions |
| `ANYPS5_PAD_PORT0_STATE` | string | `core/libs/prx/libSceVideoOut/include/Port0Presence.hpp` | default | `default` keeps port 0 connected and user 0 logged in as before; `controller` makes port 0 and user 0 follow the controller bound to slot 0, a change committed after 500 ms, and a withdrawn port 0 reads zeroed and disconnected; any other value, including an empty one, stops the game when video output starts with an error naming the variable |
| `ANYPS5_REQUIRE_DISPLAY` | presence | `core/libs/prx/libSceVideoOut/tests/Flip.cpp` | | makes video-out tests fail instead of skipping when no display or Vulkan device is available |
| `ANYPS5_VIDEO_OUT_MAX_HEIGHT` | integer | `core/libs/prx/libSceVideoOut/src/Output.cpp` | | host display height hint: when set to a positive integer below 2160, an unknown output mode is reported as unsupported so the title can fall back to the host resolution; a malformed or zero value makes output-mode queries throw; buffer attributes are left untouched |
| `ANYPS5_VIDEO_OUT_MAX_WIDTH` | integer | `core/libs/prx/libSceVideoOut/src/Output.cpp` | | host display width hint: when set to a positive integer below 3840, an unknown output mode is reported as unsupported so the title can fall back to the host resolution; a malformed or zero value makes output-mode queries throw; buffer attributes are left untouched |
| `APS5_NO_PAD_INPUT` | presence | `core/libs/prx/libSceVideoOut/src/PadInput.cpp` | | ignores keyboard and mouse input so a measurement run is not advanced by a stray press |
| `APS5_NO_WGI` | presence | `core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp` | | turns off SDL's Windows.Gaming.Input joystick backend at default hint priority, on Windows hosts where it faults a few seconds into every run |
| `APS5_OUTPUT_RESOLUTION` | string | `core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp` | | `<width>x<height>` in pixels that replaces the host display's size when `sceVideoOutGetOutputStatus` and `sceVideoOutGetOutputStatusWithVrr` report the output resolution: 1 for a size below 3840 wide and 2160 high, 2 otherwise, as for the display; unset or empty follows the display, and any other value makes those calls throw |