From 5cd25dedbd2e673d210b6567a078b5f6f7590d28 Mon Sep 17 00:00:00 2001 From: Lexroid Date: Sun, 11 Oct 2026 21:06:04 +0300 Subject: [PATCH] fix(videoout): accept a buffer attribute change while a flip is pending sceVideoOutSubmitChangeBufferAttribute2 threw when a buffer of the group had a flip pending, and Marvel's Wolverine makes that call with flips in the queue. A flip now takes its buffer group when it is reserved, in submission order, so flips submitted before the change keep the attributes their buffers were rendered with and later flips use the new ones. Co-Authored-By: Claude Opus 5.5 --- .../libs/prx/libSceVideoOut/include/VideoOutDriver.hpp | 1 + core/libs/prx/libSceVideoOut/src/Buffer.cpp | 1 - core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp | 6 +++++- core/libs/prx/libSceVideoOut/tests/Flip.cpp | 10 +++++++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index 0bab9d5e5f..11fa5eb89f 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -173,6 +173,7 @@ struct FlipRequest final : AgcDriver::IFlipRequest, std::enable_shared_from_this uint32_t height = 0; VideoOutBuffer buffer; BufferAttributeGroup group; + bool groupCaptured = false; bool reserved = false; bool unregistered = false; bool ready = false; diff --git a/core/libs/prx/libSceVideoOut/src/Buffer.cpp b/core/libs/prx/libSceVideoOut/src/Buffer.cpp index 481c92c2ff..25589edd26 100644 --- a/core/libs/prx/libSceVideoOut/src/Buffer.cpp +++ b/core/libs/prx/libSceVideoOut/src/Buffer.cpp @@ -127,7 +127,6 @@ int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int setIndex, group.attribute = *attribute; for (int i = 0; i < VIDEO_OUT_BUFFER_NUM_MAX; ++i) { if (cfg->buffers[i].groupIndex != setIndex) continue; - if (cfg->bufferPending[i] != 0) throw std::runtime_error("VideoOut: buffer attributes are in use by a pending flip"); const auto display = DescribeVideoOutBuffer(cfg->buffers[i], group); AgcDriverCheckGuestMemory_nid_postfix(reinterpret_cast(display.address), AgcDriverDisplayBufferSize_nid_postfix(display), 65536); } diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index 5e7b5c1a31..fbe9da37b0 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -84,7 +84,7 @@ void resolveFlipBuffer(FlipRequest& request, const VideoOutConfig& cfg) { return; } require(request.buffer.groupIndex < VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX, "invalid buffer group"); - request.group = cfg.groups[request.buffer.groupIndex]; + if (!request.groupCaptured) request.group = cfg.groups[request.buffer.groupIndex]; require(request.group.occupied, "buffer group is not registered"); require(request.buffer.dataAddress != 0, "null registered buffer address"); static_cast(DescribeVideoOutBuffer(request.buffer, request.group)); @@ -152,6 +152,10 @@ public: ++queue->reservations; ++cfg->flipStatus.flipPendingNum; if (info.index >= 0) ++cfg->bufferPending[info.index]; + if (info.index >= 0 && cfg->buffers[info.index].Occupied() && cfg->buffers[info.index].groupIndex < VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX && cfg->groups[cfg->buffers[info.index].groupIndex].occupied) { + request->group = cfg->groups[cfg->buffers[info.index].groupIndex]; + request->groupCaptured = true; + } request->reserved = true; return request; } diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index 6078706267..bfc3bd07df 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -86,8 +86,16 @@ void testLifetime(bool reopen) { std::lock_guard lock(cfg->mutex); check(cfg->flipStatus.flipPendingNum == 1 && cfg->flipStatus.count == 0, "reservation status is wrong"); } + auto smaller = attribute; + smaller.width = 32; + smaller.height = 32; + check(sceVideoOutSubmitChangeBufferAttribute2(handle, 0, &smaller, nullptr) == 0, "attributes of a buffer with a pending flip could not be changed"); + { + std::lock_guard lock(cfg->mutex); + check(cfg->groups[0].attribute.width == 32 && cfg->groups[0].attribute.height == 32 && cfg->flipStatus.flipPendingNum == 1, "the attribute change did not reach later flips or dropped the pending one"); + } attribute.dcc_control = 1; - check(expectFailure([&] { sceVideoOutSubmitChangeBufferAttribute2(handle, 0, &attribute, nullptr); }).find("pending flip") != std::string::npos, "pending attributes were changed"); + check(expectFailure([&] { sceVideoOutSubmitChangeBufferAttribute2(handle, 0, &attribute, nullptr); }).find("DCC") != std::string::npos, "an invalid attribute change was accepted"); check(expectFailure([&] { sceVideoOutUnregisterBuffers(handle, 0); }).find("pending flip") != std::string::npos, "pending buffer was unregistered"); check(sceVideoOutWaitVblank(handle) == 0 && sceVideoOutWaitVblank(handle) == 0, "vblank stalled behind the GPU queue"); std::shared_ptr replacement;