From 03ebac577b9a682b1e77e4310cc028f7ab5f732f Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 20 May 2026 16:31:37 -0500 Subject: [PATCH] Lib.VideoOut: Properly remove events on close (#4456) * Fix flip status on close * Store equeues by handle instead of pointer As is, a game could call sceKernelDeleteEqueue on the equeue registered for flip/vblank events, and terminate the equeue while our VideoOut driver retains a valid pointer to it. Changing to storing the equeue handle means we can check if the equeue still exists and prevent this. * Remove flip and vblank events on sceVideoOutClose * Don't forget to clear vectors * Oops Intended to erase the memset on FlipStatus, not VblankStatus. --- src/core/libraries/videoout/driver.cpp | 45 ++++++++++++++++------- src/core/libraries/videoout/driver.h | 4 +- src/core/libraries/videoout/video_out.cpp | 8 ++-- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 23d7ca476..5f98527c8 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -70,8 +70,8 @@ void VideoOutDriver::Close(s32 handle) { // Clear port information std::memset(main_port.buffer_labels.data(), 0, sizeof(main_port.buffer_labels)); std::memset(main_port.groups.data(), 0, sizeof(main_port.groups)); - std::memset(&main_port.flip_status, 0, sizeof(main_port.flip_status)); std::memset(&main_port.vblank_status, 0, sizeof(main_port.vblank_status)); + main_port.flip_status = FlipStatus{}; // Re-initialize buffers std::memset(main_port.buffer_slots.data(), 0, sizeof(main_port.buffer_slots)); @@ -79,9 +79,23 @@ void VideoOutDriver::Close(s32 handle) { buffer.group_index = -1; } - // TODO: Remove events? - ASSERT(main_port.flip_events.empty()); - ASSERT(main_port.vblank_events.empty()); + // Clear events + for (auto event : main_port.flip_events) { + auto equeue = Kernel::GetEqueue(event); + if (equeue != nullptr) { + equeue->RemoveEvent(static_cast(OrbisVideoOutInternalEventId::Flip), + Kernel::OrbisKernelEvent::Filter::VideoOut); + } + } + main_port.flip_events.clear(); + for (auto event : main_port.vblank_events) { + auto equeue = Kernel::GetEqueue(event); + if (equeue != nullptr) { + equeue->RemoveEvent(static_cast(OrbisVideoOutInternalEventId::Vblank), + Kernel::OrbisKernelEvent::Filter::VideoOut); + } + } + main_port.vblank_events.clear(); } VideoOutPort* VideoOutDriver::GetPort(int handle) { @@ -243,9 +257,10 @@ void VideoOutDriver::Flip(const Request& req) { } // Trigger flip events for the port. - for (auto& event : port->flip_events) { - if (event != nullptr) { - event->TriggerEvent( + for (auto event : port->flip_events) { + auto equeue = Kernel::GetEqueue(event); + if (equeue != nullptr) { + equeue->TriggerEvent( static_cast(OrbisVideoOutInternalEventId::Flip), Kernel::OrbisKernelEvent::Filter::VideoOut, reinterpret_cast(static_cast(OrbisVideoOutInternalEventId::Flip) | @@ -372,13 +387,15 @@ void VideoOutDriver::PresentThread(std::stop_token token) { std::scoped_lock lock{main_port.vo_mutex}; // Trigger flip events for the port - for (auto& event : main_port.vblank_events) { - if (event != nullptr) { - event->TriggerEvent(static_cast(OrbisVideoOutInternalEventId::Vblank), - Kernel::OrbisKernelEvent::Filter::VideoOut, - reinterpret_cast( - static_cast(OrbisVideoOutInternalEventId::Vblank) | - (vblank_status.count << 16))); + for (auto event : main_port.vblank_events) { + auto equeue = Kernel::GetEqueue(event); + if (equeue != nullptr) { + equeue->TriggerEvent( + static_cast(OrbisVideoOutInternalEventId::Vblank), + Kernel::OrbisKernelEvent::Filter::VideoOut, + reinterpret_cast( + static_cast(OrbisVideoOutInternalEventId::Vblank) | + (vblank_status.count << 16))); } } diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index 253ec0958..ca2b6456d 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -25,8 +25,8 @@ struct VideoOutPort { std::array groups; FlipStatus flip_status; SceVideoOutVblankStatus vblank_status; - std::vector flip_events; - std::vector vblank_events; + std::vector flip_events; + std::vector vblank_events; std::mutex vo_mutex; std::mutex port_mutex; std::condition_variable vo_cv; diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index 1c0310972..46fe80d1d 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -61,7 +61,7 @@ s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::OrbisKernelEqueue eq, s32 handl event.data = port; equeue->AddEvent(event); - port->flip_events.push_back(equeue); + port->flip_events.push_back(eq); return ORBIS_OK; } @@ -76,7 +76,7 @@ s32 PS4_SYSV_ABI sceVideoOutDeleteFlipEvent(Kernel::OrbisKernelEqueue eq, s32 ha return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; } equeue->RemoveEvent(handle, Kernel::OrbisKernelEvent::Filter::VideoOut); - port->flip_events.erase(find(port->flip_events.begin(), port->flip_events.end(), equeue)); + port->flip_events.erase(find(port->flip_events.begin(), port->flip_events.end(), eq)); return ORBIS_OK; } @@ -103,7 +103,7 @@ s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::OrbisKernelEqueue eq, s32 han event.data = port; equeue->AddEvent(event); - port->vblank_events.push_back(equeue); + port->vblank_events.push_back(eq); return ORBIS_OK; } @@ -118,7 +118,7 @@ s32 PS4_SYSV_ABI sceVideoOutDeleteVblankEvent(Kernel::OrbisKernelEqueue eq, s32 return ORBIS_VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE; } equeue->RemoveEvent(handle, Kernel::OrbisKernelEvent::Filter::VideoOut); - port->vblank_events.erase(find(port->vblank_events.begin(), port->vblank_events.end(), equeue)); + port->vblank_events.erase(find(port->vblank_events.begin(), port->vblank_events.end(), eq)); return ORBIS_OK; }