fix(videoout): cancel flips on a port the title closed instead of failing the driver
A flip still queued or in presentation when the title closes its port is discarded, as on hardware, rather than stored as a driver failure. A closed port no longer terminates the presentation thread nor surfaces from Shutdown. The lifetime test now expects cancellation and the reopen mode is registered in ctest.
This commit is contained in:
parent
59c98bde58
commit
a1efdab629
|
|
@ -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 decode controls)
|
||||
foreach(flipMode IN ITEMS lifetime reopen decode controls)
|
||||
if(flipMode STREQUAL "lifetime")
|
||||
add_test(NAME video_out_flip_${flipMode} COMMAND video_out_flip_tests)
|
||||
else()
|
||||
|
|
|
|||
|
|
@ -139,10 +139,16 @@ struct VideoOutConfig {
|
|||
std::array<BufferReuseTracker, VIDEO_OUT_BUFFER_NUM_MAX> bufferReuse;
|
||||
std::array<BufferAttributeGroup, VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX> groups{};
|
||||
|
||||
void Check() const {
|
||||
bool Closed() const { return !opened || closing; }
|
||||
|
||||
void CheckAlive() const {
|
||||
if (failure) std::rethrow_exception(failure);
|
||||
if (shutdownToken.stop_requested()) throw ProcessShutdown{};
|
||||
if (!opened || closing) throw std::runtime_error("VideoOut: port is closed");
|
||||
}
|
||||
|
||||
void Check() const {
|
||||
CheckAlive();
|
||||
if (Closed()) throw std::runtime_error("VideoOut: port is closed");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -173,6 +179,8 @@ struct FlipRequest final : AgcDriver::IFlipRequest, std::enable_shared_from_this
|
|||
~FlipRequest() override;
|
||||
void GpuReady(const std::shared_ptr<AgcDriver::FrameTiming>& frameTiming) override;
|
||||
void Fail(std::exception_ptr error) noexcept override;
|
||||
void Cancel() noexcept;
|
||||
void ReleaseLocked() noexcept;
|
||||
};
|
||||
|
||||
struct FlipQueue {
|
||||
|
|
|
|||
|
|
@ -74,10 +74,10 @@ public:
|
|||
void Wait() override {
|
||||
std::unique_lock lock(_config->mutex);
|
||||
_config->vblankCond.wait(lock, _config->shutdownToken, [&] {
|
||||
return _config->failure || _config->closing || !_config->opened ||
|
||||
return _config->failure || _config->Closed() ||
|
||||
_config->bufferReuse[_index].IsComplete(_ticket);
|
||||
});
|
||||
checkConfig(*_config);
|
||||
_config->CheckAlive();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -166,15 +166,27 @@ private:
|
|||
FlipRequest::~FlipRequest() {
|
||||
if (!reserved) return;
|
||||
std::lock_guard lock(cfg->mutex);
|
||||
if (!terminal) {
|
||||
--cfg->flipStatus.flipPendingNum;
|
||||
--queue->reservations;
|
||||
if (index >= 0) {
|
||||
--cfg->bufferPending[index];
|
||||
cfg->bufferReuse[index].Complete(reuseTicket);
|
||||
}
|
||||
cfg->vblankCond.notify_all();
|
||||
ReleaseLocked();
|
||||
}
|
||||
|
||||
void FlipRequest::ReleaseLocked() noexcept {
|
||||
if (!reserved || terminal) return;
|
||||
--cfg->flipStatus.flipPendingNum;
|
||||
--queue->reservations;
|
||||
if (index >= 0) {
|
||||
--cfg->bufferPending[index];
|
||||
cfg->bufferReuse[index].Complete(reuseTicket);
|
||||
}
|
||||
terminal = true;
|
||||
cfg->vblankCond.notify_all();
|
||||
}
|
||||
|
||||
void FlipRequest::Cancel() noexcept {
|
||||
{
|
||||
std::lock_guard lock(cfg->mutex);
|
||||
ReleaseLocked();
|
||||
}
|
||||
queue->changed.notify_all();
|
||||
}
|
||||
|
||||
void FlipRequest::GpuReady(const std::shared_ptr<AgcDriver::FrameTiming>& frameTiming) {
|
||||
|
|
@ -187,7 +199,11 @@ void FlipRequest::GpuReady(const std::shared_ptr<AgcDriver::FrameTiming>& frameT
|
|||
if (queue->failure) std::rethrow_exception(queue->failure);
|
||||
if (queue->stopping || cfg->shutdownToken.stop_requested()) throw ProcessShutdown{};
|
||||
std::lock_guard lock(cfg->mutex);
|
||||
checkConfig(*cfg);
|
||||
cfg->CheckAlive();
|
||||
if (cfg->Closed()) {
|
||||
ReleaseLocked();
|
||||
return;
|
||||
}
|
||||
require(reserved && !ready && !terminal && cfg->generation == generation, "invalid flip readiness transition");
|
||||
readiness.Mark("locks_validate");
|
||||
queuedAt = AgcDriver::FrameTiming::Clock::now();
|
||||
|
|
@ -212,16 +228,7 @@ void FlipRequest::Fail(std::exception_ptr error) noexcept {
|
|||
if (!error) std::terminate();
|
||||
std::lock_guard lock(cfg->mutex);
|
||||
if (!cfg->failure) cfg->failure = error;
|
||||
if (reserved && !terminal) {
|
||||
--cfg->flipStatus.flipPendingNum;
|
||||
--queue->reservations;
|
||||
if (index >= 0) {
|
||||
--cfg->bufferPending[index];
|
||||
cfg->bufferReuse[index].Complete(reuseTicket);
|
||||
}
|
||||
terminal = true;
|
||||
}
|
||||
cfg->vblankCond.notify_all();
|
||||
ReleaseLocked();
|
||||
queue->changed.notify_all();
|
||||
}
|
||||
|
||||
|
|
@ -420,7 +427,11 @@ void VideoOutDriver::processFlip(FlipRequest& req) {
|
|||
{
|
||||
std::unique_lock lock(req.cfg->mutex);
|
||||
timing.Mark("config_mutex_wait");
|
||||
checkConfig(*req.cfg);
|
||||
req.cfg->CheckAlive();
|
||||
if (req.cfg->Closed()) {
|
||||
req.ReleaseLocked();
|
||||
return;
|
||||
}
|
||||
require(req.ready && !req.terminal && req.generation == req.cfg->generation, "stale or incomplete flip request");
|
||||
const auto interval = static_cast<uint64_t>(req.flipRate + 1);
|
||||
require(req.cfg->lastFlipVblank <= std::numeric_limits<uint64_t>::max() - interval, "flip interval overflow");
|
||||
|
|
@ -428,7 +439,11 @@ void VideoOutDriver::processFlip(FlipRequest& req) {
|
|||
timing.Mark("validate");
|
||||
req.cfg->vblankCond.wait(lock, req.cfg->shutdownToken, [&] { return req.cfg->vblankStatus.count >= target || req.cfg->failure || req.cfg->closing; });
|
||||
timing.Mark("vblank_wait");
|
||||
checkConfig(*req.cfg);
|
||||
req.cfg->CheckAlive();
|
||||
if (req.cfg->Closed()) {
|
||||
req.ReleaseLocked();
|
||||
return;
|
||||
}
|
||||
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");
|
||||
|
|
@ -439,7 +454,8 @@ void VideoOutDriver::processFlip(FlipRequest& req) {
|
|||
const auto gpuReady = [](void* context) {
|
||||
auto& request = *static_cast<FlipRequest*>(context);
|
||||
std::lock_guard lock(request.cfg->mutex);
|
||||
checkConfig(*request.cfg);
|
||||
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();
|
||||
|
|
@ -455,7 +471,11 @@ void VideoOutDriver::processFlip(FlipRequest& req) {
|
|||
timing.Mark("window_title");
|
||||
std::lock_guard lock(req.cfg->mutex);
|
||||
timing.Mark("completion_mutex_wait");
|
||||
checkConfig(*req.cfg);
|
||||
req.cfg->CheckAlive();
|
||||
if (req.cfg->Closed()) {
|
||||
req.ReleaseLocked();
|
||||
return;
|
||||
}
|
||||
require(!req.terminal && req.cfg->generation == req.generation, "flip cancelled during presentation");
|
||||
require(req.gpuComplete, "flip submitted before GPU completion");
|
||||
require(req.cfg->flipStatus.count != std::numeric_limits<uint64_t>::max(), "flip counter overflow");
|
||||
|
|
|
|||
|
|
@ -99,24 +99,21 @@ void testLifetime(bool reopen) {
|
|||
check(replacement != cfg && replacement->generation > cfg->generation, "reopen reused old port state");
|
||||
}
|
||||
gate->Release();
|
||||
std::exception_ptr failure;
|
||||
{
|
||||
std::unique_lock lock(cfg->mutex);
|
||||
check(cfg->vblankCond.wait_for(lock, std::chrono::seconds(10), [&] { return cfg->failure != nullptr; }), "flip failure did not wake waiters");
|
||||
failure = cfg->failure;
|
||||
check(cfg->flipStatus.count == 0 && cfg->flipStatus.flipPendingNum == 0, "failed flip has successful or pending status");
|
||||
check(cfg->vblankCond.wait_for(lock, std::chrono::seconds(10), [&] { return cfg->flipStatus.flipPendingNum == 0; }), "closed port kept its pending flip");
|
||||
check(cfg->failure == nullptr, "closing a port failed the driver");
|
||||
check(cfg->flipStatus.count == 0, "cancelled flip has successful status");
|
||||
}
|
||||
const auto message = expectFailure([&] { std::rethrow_exception(failure); });
|
||||
check(message.find("closed") != std::string::npos, "flip used a closed port");
|
||||
if (replacement) {
|
||||
std::lock_guard lock(replacement->mutex);
|
||||
check(replacement->flipStatus.flipPendingNum == 0 && replacement->flipStatus.count == 0, "old request changed new port counters");
|
||||
} else {
|
||||
check(expectFailure([&] { sceVideoOutWaitVblank(handle); }).find("closed") != std::string::npos, "closed port accepted a vblank wait");
|
||||
}
|
||||
check(expectFailure([&] { sceVideoOutWaitVblank(handle); }).find("closed") != std::string::npos, "VideoOut lost worker failure");
|
||||
AgcDriverUnregisterVideoOutput_nid_postfix(7, gate);
|
||||
if (reopen) sceVideoOutClose(handle);
|
||||
const auto shutdown = expectFailure([] { LibcRunShutdown_nid_postfix(); });
|
||||
check(shutdown.find("closed") != std::string::npos, "shutdown lost asynchronous error");
|
||||
LibcRunShutdown_nid_postfix();
|
||||
}
|
||||
|
||||
std::size_t tiledOffset(uint32_t x, uint32_t y, uint32_t width) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue