mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 16:43:05 -05:00
Detect HDR output on the host: on Windows the monitor showing the window must run in HDR10 (DXGI IDXGIOutput6), and on every host the window's Vulkan surface must offer a 10-bit VK_COLOR_SPACE_HDR10_ST2084_EXT swapchain with VK_EXT_swapchain_colorspace and VK_EXT_hdr_metadata. The monitor's luminance comes from DXGI; hosts that report none get 1000 nits peak and full frame, 0 nits black. While HDR output is available, sceVideoOutGetOutputStatus reports dynamic range 2 and flag bit 0, sceSystemServiceGetHdrToneMapLuminance reports the monitor's luminance, and every open port's output mode event fires when that changes. PQ display buffers are presented unchanged through an HDR10 swapchain with HDR metadata; any other display buffer switches back to the SDR swapchain. Without HDR output the PQ to SDR conversion uses the peak the title was last told as its white level. AI-assisted: yes
121 lines
6.2 KiB
C++
121 lines
6.2 KiB
C++
#include "SceTypes.hpp"
|
|
#include "prx/libc/include/Shutdown.hpp"
|
|
#include "prx/libkernel/Equeue/Equeue.hpp"
|
|
#include "prx/libSceAgcDriver/Execution/include/HostHdr.hpp"
|
|
#include "prx/libSceSystemService/SystemService.hpp"
|
|
#include "tests/VideoOutTestEnvironment.hpp"
|
|
#include <array>
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
|
|
extern "C" {
|
|
int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param);
|
|
int APS5_VABI sceVideoOutClose(int handle);
|
|
int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status);
|
|
int APS5_VABI sceVideoOutGetOutputStatusWithVrr(int handle, void* status);
|
|
int APS5_VABI sceVideoOutAddOutputModeEvent(KernelEqueue eq, int handle, void* udata);
|
|
int APS5_VABI sceVideoOutGetEventId(const KernelEvent* ev);
|
|
int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name);
|
|
int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq);
|
|
int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo);
|
|
int APS5_VABI sceSystemServiceGetHdrToneMapLuminance(SystemServiceHdrToneMapLuminance* luminance);
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr int SystemUser = 255;
|
|
constexpr int MainBus = 0;
|
|
constexpr int OutputModeEvent = 8;
|
|
|
|
void Require(bool value, const char* what) {
|
|
if (value) return;
|
|
std::fprintf(stderr, "FAIL: %s\n", what);
|
|
std::abort();
|
|
}
|
|
|
|
VideoOutOutputStatus Status(int handle) {
|
|
VideoOutOutputStatus status{};
|
|
Require(sceVideoOutGetOutputStatus(handle, &status) == 0, "sceVideoOutGetOutputStatus failed");
|
|
std::array<std::byte, 80> withVrr{};
|
|
Require(sceVideoOutGetOutputStatusWithVrr(handle, withVrr.data()) == 0, "sceVideoOutGetOutputStatusWithVrr failed");
|
|
Require(std::memcmp(withVrr.data(), &status, sizeof(status)) == 0, "the output status with VRR differs from the output status");
|
|
return status;
|
|
}
|
|
|
|
SystemServiceHdrToneMapLuminance ToneMap() {
|
|
SystemServiceHdrToneMapLuminance luminance{-1.0f, -1.0f, -1.0f};
|
|
Require(sceSystemServiceGetHdrToneMapLuminance(&luminance) == SYSTEM_SERVICE_OK, "sceSystemServiceGetHdrToneMapLuminance failed");
|
|
return luminance;
|
|
}
|
|
|
|
bool OutputModeEventArrives(KernelEqueue queue, KernelUseconds timeout) {
|
|
KernelEvent event{};
|
|
int count = 0;
|
|
return sceKernelWaitEqueue(queue, &event, 1, &count, &timeout) == 0 && count == 1 && sceVideoOutGetEventId(&event) == OutputModeEvent;
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
VideoOutTestEnvironment environment;
|
|
int handle = 0;
|
|
try {
|
|
handle = sceVideoOutOpen(SystemUser, MainBus, 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, "sceVideoOutOpen failed");
|
|
AgcDriverSetHostDisplayHdr_nid_postfix({});
|
|
AgcDriverSetSurfaceHdr10_nid_postfix(false);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
auto status = Status(handle);
|
|
Require(status.dynamicRange == 1 && status.flags == 0, "an SDR host reports HDR output");
|
|
auto luminance = ToneMap();
|
|
Require(luminance.max_tone_map_luminance == 100.0f && luminance.max_full_frame_tone_map_luminance == 100.0f && luminance.min_tone_map_luminance == 0.0f, "an SDR host does not report SDR reference white");
|
|
|
|
KernelEqueue queue = 0;
|
|
Require(sceKernelCreateEqueue(&queue, "HDR test") == 0, "event queue creation failed");
|
|
Require(sceVideoOutAddOutputModeEvent(queue, handle, nullptr) == 0, "output mode subscription failed");
|
|
Require(OutputModeEventArrives(queue, 0), "the output mode subscription is not triggered once");
|
|
|
|
AgcDriverSetHostDisplayHdr_nid_postfix(AgcDriver::DisplayHdrFromOutput(true, 1015.0f, 0.005f, 640.0f));
|
|
status = Status(handle);
|
|
Require(status.dynamicRange == 1 && status.flags == 0, "an HDR10 display without an HDR10 surface reports HDR output");
|
|
Require(ToneMap().max_tone_map_luminance == 100.0f, "an HDR10 display without an HDR10 surface reports its luminance");
|
|
Require(!OutputModeEventArrives(queue, 200'000), "an HDR10 display without an HDR10 surface fires the output mode event");
|
|
|
|
AgcDriverSetSurfaceHdr10_nid_postfix(true);
|
|
Require(OutputModeEventArrives(queue, 2'000'000), "HDR output becoming available does not fire the output mode event");
|
|
status = Status(handle);
|
|
Require(status.dynamicRange == 2 && status.flags == 1, "HDR output is not reported");
|
|
Require(status.resolution != 0 && status.refreshRate == 3 && status.reserved[0] == 0 && status.reserved[1] == 0 && status.reserved[2] == 0, "the rest of the output status changed with HDR");
|
|
luminance = ToneMap();
|
|
Require(luminance.max_tone_map_luminance == 1015.0f && luminance.max_full_frame_tone_map_luminance == 640.0f && luminance.min_tone_map_luminance == 0.005f, "the display's luminance is not the tone-map luminance");
|
|
Require(AgcDriver::CurrentHostHdr().reportedToneMapPeak == 1015.0f, "the tone-map peak given to the title is not recorded for the SDR conversion");
|
|
|
|
AgcDriverSetHostDisplayHdr_nid_postfix(AgcDriver::DisplayHdrFromOutput(true, 400.0f, 0.05f, 300.0f));
|
|
Require(OutputModeEventArrives(queue, 2'000'000), "a new display luminance does not fire the output mode event");
|
|
Require(ToneMap().max_tone_map_luminance == 400.0f && Status(handle).dynamicRange == 2, "the new display luminance is not reported");
|
|
|
|
AgcDriverSetHostDisplayHdr_nid_postfix({});
|
|
Require(OutputModeEventArrives(queue, 2'000'000), "HDR output going away does not fire the output mode event");
|
|
status = Status(handle);
|
|
Require(status.dynamicRange == 1 && status.flags == 0, "HDR output is reported after the display left HDR mode");
|
|
Require(ToneMap().max_tone_map_luminance == 100.0f && AgcDriver::CurrentHostHdr().reportedToneMapPeak == 100.0f, "SDR reference white is not reported after the display left HDR mode");
|
|
|
|
Require(sceVideoOutClose(handle) == 0, "sceVideoOutClose failed");
|
|
Require(sceKernelDeleteEqueue(queue) == 0, "event queue deletion failed");
|
|
std::printf("guest VideoOut HDR tests passed\n");
|
|
LibcRunShutdown_nid_postfix();
|
|
return 0;
|
|
}
|