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
2 changes: 1 addition & 1 deletion core/libs/prx/libSceVideoOut/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ set_target_properties(video_out_flip_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(video_out_flip_tests)
if(BUILD_TESTING)
set_target_properties(video_out_flip_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
foreach(flipMode IN ITEMS lifetime reopen decode controls)
foreach(flipMode IN ITEMS lifetime reopen decode controls overlay)
if(flipMode STREQUAL "lifetime")
add_test(NAME video_out_flip_${flipMode} COMMAND video_out_flip_tests)
else()
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 @@ -123,6 +123,7 @@ struct VideoOutConfig {
uint32_t width = VIDEO_OUT_DEFAULT_WIDTH;
uint32_t height = VIDEO_OUT_DEFAULT_HEIGHT;
uint64_t generation = 0;
int busType = VIDEO_OUT_BUS_TYPE_MAIN;
bool opened = false;
bool closing = false;
std::exception_ptr failure;
Expand Down
50 changes: 30 additions & 20 deletions core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ int VideoOutDriver::Open(int busType) {
}
auto cfg = std::make_shared<VideoOutConfig>();
cfg->generation = generation;
cfg->busType = busType;
cfg->opened = true;
cfg->flipStatus.flipArg = -1;
cfg->flipStatus.currentBuffer = -1;
Expand Down Expand Up @@ -464,28 +465,37 @@ void VideoOutDriver::processFlip(FlipRequest& req) {
req.cfg->lastFlipVblank = req.cfg->vblankStatus.count;
}
require(req.width != 0 && req.height != 0 && req.width <= static_cast<uint32_t>(std::numeric_limits<int>::max()) && req.height <= static_cast<uint32_t>(std::numeric_limits<int>::max()), "invalid window dimensions");
window.Ensure(req.width, req.height);
const auto extensions = windowExtensions(window.Handle());
const AgcDriver::PresentationWindow target{window.Handle(), extensions, &createWindowSurface, &windowDrawableSize, req.width, req.height, req.timing};
timing.Mark("window_prepare");
const auto gpuReady = [](void* context) {
auto& request = *static_cast<FlipRequest*>(context);
std::lock_guard lock(request.cfg->mutex);
request.cfg->CheckAlive();
if (request.cfg->Closed()) return;
require(!request.terminal && !request.gpuComplete, "invalid GPU completion transition");
request.gpuComplete = true;
request.cfg->vblankCond.notify_all();
};
if (req.index >= 0) {
const auto display = DescribeVideoOutBuffer(req.buffer, req.group);
AgcDriverPresentBuffer_nid_postfix(target, display, gpuReady, &req);
if (req.cfg->busType == VIDEO_OUT_BUS_TYPE_OVERLAY) {
std::lock_guard lock(req.cfg->mutex);
req.cfg->CheckAlive();
if (!req.cfg->Closed()) {
require(!req.terminal && !req.gpuComplete, "invalid GPU completion transition");
req.gpuComplete = true;
}
} else {
AgcDriverPresentClear_nid_postfix(target, req.index == VIDEO_OUT_BUFFER_INDEX_BLACK, gpuReady, &req);
window.Ensure(req.width, req.height);
const auto extensions = windowExtensions(window.Handle());
const AgcDriver::PresentationWindow target{window.Handle(), extensions, &createWindowSurface, &windowDrawableSize, req.width, req.height, req.timing};
timing.Mark("window_prepare");
const auto gpuReady = [](void* context) {
auto& request = *static_cast<FlipRequest*>(context);
std::lock_guard lock(request.cfg->mutex);
request.cfg->CheckAlive();
if (request.cfg->Closed()) return;
require(!request.terminal && !request.gpuComplete, "invalid GPU completion transition");
request.gpuComplete = true;
request.cfg->vblankCond.notify_all();
};
if (req.index >= 0) {
const auto display = DescribeVideoOutBuffer(req.buffer, req.group);
AgcDriverPresentBuffer_nid_postfix(target, display, gpuReady, &req);
} else {
AgcDriverPresentClear_nid_postfix(target, req.index == VIDEO_OUT_BUFFER_INDEX_BLACK, gpuReady, &req);
}
timing.Mark("present");
window.UpdateTitle();
timing.Mark("window_title");
}
timing.Mark("present");
window.UpdateTitle();
timing.Mark("window_title");
std::lock_guard lock(req.cfg->mutex);
timing.Mark("completion_mutex_wait");
req.cfg->CheckAlive();
Expand Down
53 changes: 53 additions & 0 deletions core/libs/prx/libSceVideoOut/tests/Flip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,58 @@ void testPresentation() {
LibcRunShutdown_nid_postfix();
}

void testOverlay() {
const int mainHandle = sceVideoOutOpen(255, 0, 0, nullptr);
const int overlayHandle = sceVideoOutOpen(255, 1, 0, nullptr);
check(mainHandle != overlayHandle, "the overlay bus shares the main port");
auto mainCfg = VideoOutDriver::Get().GetConfig(mainHandle);
auto overlayCfg = VideoOutDriver::Get().GetConfig(overlayHandle);
std::vector<std::byte> mainAllocation(6 * 65536 + 65535);
std::vector<std::byte> overlayAllocation(6 * 65536 + 65535);
const auto mainStorage = alignedBuffer(mainAllocation);
const auto overlayStorage = alignedBuffer(overlayAllocation);
fillBuffer(mainStorage, 259, 137);
fillBuffer(overlayStorage, 259, 137);
VideoOutBuffers mainBuffer{mainStorage.data(), nullptr, {nullptr, nullptr}};
VideoOutBuffers overlayBuffer{overlayStorage.data(), nullptr, {nullptr, nullptr}};
VideoOutBufferAttribute2 attribute{};
sceVideoOutSetBufferAttribute2(&attribute, 0x8000000000000000ull, 0, 259, 137, 0, 0, 0);
check(sceVideoOutRegisterBuffers2(mainHandle, 0, 0, &mainBuffer, 1, &attribute, 0, nullptr) == 0, "main buffer registration failed");
check(sceVideoOutRegisterBuffers2(overlayHandle, 0, 0, &overlayBuffer, 1, &attribute, 0, nullptr) == 0, "overlay buffer registration failed");
const auto flip = [](int handle, const std::shared_ptr<VideoOutConfig>& cfg, std::int64_t argument) {
std::uint64_t target;
{
std::lock_guard lock(cfg->mutex);
target = cfg->flipStatus.count + 1;
}
check(sceVideoOutSubmitFlip(handle, 0, 1, argument) == 0, "the flip was not accepted");
std::unique_lock lock(cfg->mutex);
check(cfg->vblankCond.wait_for(lock, std::chrono::seconds(15), [&] { return (cfg->failure && cfg->flipStatus.flipPendingNum == 0) || cfg->flipStatus.count == target; }), "the flip did not complete");
if (cfg->failure) std::rethrow_exception(cfg->failure);
check(cfg->flipStatus.flipArg == argument && cfg->flipStatus.currentBuffer == 0 && cfg->flipStatus.flipPendingNum == 0, "flip status is wrong");
};
const auto presentsAfter = [](std::uint64_t previous) {
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
while (AgcDriver::VulkanDevice::PresentCounts().presents == previous && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(10));
return AgcDriver::VulkanDevice::PresentCounts().presents;
};
const auto start = AgcDriver::VulkanDevice::PresentCounts().presents;
flip(mainHandle, mainCfg, 1);
const auto afterMain = presentsAfter(start);
check(afterMain > start, "a main-bus flip was not presented");
flip(overlayHandle, overlayCfg, 2);
flip(overlayHandle, overlayCfg, 3);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
check(AgcDriver::VulkanDevice::PresentCounts().presents == afterMain, "an overlay-bus flip replaced the main output");
flip(mainHandle, mainCfg, 4);
check(presentsAfter(afterMain) > afterMain, "a main-bus flip after overlay flips was not presented");
sceVideoOutUnregisterBuffers(overlayHandle, 0);
sceVideoOutUnregisterBuffers(mainHandle, 0);
sceVideoOutClose(overlayHandle);
sceVideoOutClose(mainHandle);
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 @@ -560,6 +612,7 @@ int main(int argc, char** argv) {
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]) == "onedevice") testOneDevice();
else if (argc == 2 && std::string(argv[1]) == "overlay") testOverlay();
else testLifetime(argc == 2 && std::string(argv[1]) == "reopen");
std::puts("VideoOut flip tests passed");
return 0;
Expand Down
1 change: 1 addition & 0 deletions docs/dev/TechnicalDebt.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ Throughout the project, every function at every stage either **does exactly what
- [Packed f16 NaN results](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (`v_pk_add/mul/fma/min/max_f16`) follow the hardware measured on RDNA2 (gfx1035) at IEEE=0 and IEEE=1 with f16 round-to-nearest-even and f16 denormals kept: with IEEE=1 a signaling NaN input is quieted and min/max return the other operand only for a quiet NaN; with IEEE=0 NaN inputs pass through unquieted and min/max ignore signaling. In `v_pk_fma_f16` an invalid product (inf * 0) gives the default NaN even when src2 is a NaN. A shader without a known float mode is translated as IEEE=0, the mode titles use; a mode with another f16 rounding or that flushes f16 denormals throws.
- [f32 denormals](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follow the shader's `FLOAT_MODE` f32 denormal field (bits 5:4) when it flushes them (0, and shaders without a known float mode, as in the 0xc0 most titles use), measured on RDNA2 (gfx1035) over 1444 rows per instruction at IEEE=0 and IEEE=1. `v_add/sub/subrev/mul/mul_legacy/fma/fmac/fmamk/fmaak/min/max/min3/max3/med3/fract/floor/ceil/frexp_mant/sin/cos_f32` flush their f32 inputs and their result to a zero of the same sign; the f32 compares, `v_cvt_i32/u32/rpi_i32/flr_i32_f32`, `v_frexp_exp_i32_f32`, `v_cvt_f64_f32`, `v_cvt_f16_f32`, `v_cvt_pkrtz_f16_f32`, `v_cvt_pknorm_i16/u16_f32` and `v_cvt_pk_u8_f32` flush their f32 inputs. `v_div_fixup_f32` flushes its src1 and src2 and keeps a denormal src0 and result, measured on RDNA2 (gfx1036) over 6859 rows per mode at IEEE=0 and IEEE=1. Tininess is detected before rounding: a product whose exact value is below 2^-126 gives zero even when it rounds to 2^-126, computed exactly from the significands for `v_mul_f32`, `v_mul_legacy_f32`, and for `v_fma_f32`, `v_fmac_f32`, `v_fmamk_f32`, `v_fmaak_f32` and `v_fma_mix_f32` when the addend is zero; with a nonzero addend the fused result is kept, so an exact result just below 2^-126 that rounds to it is not flushed. `v_fma_mix_f32` was measured with f32 sources only; its f16 sources are not flushed. `v_fma_mixlo_f16` and `v_fma_mixhi_f16` flush their f32 sources too and keep a product below 2^-126, measured on RDNA2 (gfx1036). `v_mad_legacy_f32` flushes its inputs, its product and its result in every mode, see its entry. Denormal modes 1 and 2 throw for the other instructions. Not covered, and not measurable on RDNA2 (no such opcodes) or not measured: `v_mac_f32`, `v_mad_f32`, `v_madak_f32` and `v_madmk_f32` (rounded separately), `v_mac_legacy_f32`, `v_dot2_f32_f16`, `v_div_scale_f32` and `v_div_fmas_f32` (on hardware `v_div_fmas_f32` flushes only its result), f32 interpolation and LDS float atomics keep the previous lowering. With f32 denormals kept (3) the results depend on the driver's default denormal behaviour, as no `DenormPreserve` execution mode is requested, and `v_ldexp_f32` flushes in every mode.
- The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability.
- [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
- [PA_CL_CLIP_CNTL](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented.
- [10_11_11 unorm color targets](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into R32_UINT attachments: the pixel shader packs each export, rounded to nearest even like format-30 image stores, which is assumed to match the CB's conversion.
- [PA_SU_SC_MODE_CNTL.PROVOKING_VTX_LAST](../../core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp) (libSceAgcDriver) uses Vulkan's last-vertex mode for native primitives. Bit 19 follows [opengnm's `flatshade_first` mapping](https://github.com/blackbearreloaded/ps5-opengl/blob/db752da4c89380af228497b5b6852163a4e61069/src/gallium/ps5/ps5_screen.c#L2244); vertex selection follows [Vulkan](https://docs.vulkan.org/spec/latest/chapters/drawing.html#drawing-primitive-topologies), without console measurements. Last mode throws without `VK_EXT_provoking_vertex`, for generated rectangle, mesh or tessellation primitives, explicit per-vertex flat inputs, and triangle strips with per-vertex inputs, whose [ordering depends on the device](https://docs.vulkan.org/refpages/latest/refpages/source/VkPhysicalDeviceFragmentShaderBarycentricPropertiesKHR.html).
Expand Down
Loading