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 <noreply@anthropic.com>
This commit is contained in:
Lexroid 2026-10-11 21:06:04 +03:00
parent a11c8f7f70
commit 5cd25dedbd
4 changed files with 15 additions and 3 deletions

View File

@ -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;

View File

@ -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<const void*>(display.address), AgcDriverDisplayBufferSize_nid_postfix(display), 65536);
}

View File

@ -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<void>(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;
}

View File

@ -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<VideoOutConfig> replacement;