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.
This commit is contained in:
Stephen Miller 2026-05-20 16:31:37 -05:00 committed by GitHub
parent f95edd27e0
commit 03ebac577b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 37 additions and 20 deletions

View File

@ -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<u64>(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<u64>(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<u64>(OrbisVideoOutInternalEventId::Flip),
Kernel::OrbisKernelEvent::Filter::VideoOut,
reinterpret_cast<void*>(static_cast<u64>(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<u64>(OrbisVideoOutInternalEventId::Vblank),
Kernel::OrbisKernelEvent::Filter::VideoOut,
reinterpret_cast<void*>(
static_cast<u64>(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<u64>(OrbisVideoOutInternalEventId::Vblank),
Kernel::OrbisKernelEvent::Filter::VideoOut,
reinterpret_cast<void*>(
static_cast<u64>(OrbisVideoOutInternalEventId::Vblank) |
(vblank_status.count << 16)));
}
}

View File

@ -25,8 +25,8 @@ struct VideoOutPort {
std::array<BufferAttributeGroup, MaxDisplayBufferGroups> groups;
FlipStatus flip_status;
SceVideoOutVblankStatus vblank_status;
std::vector<Kernel::EqueueInternal*> flip_events;
std::vector<Kernel::EqueueInternal*> vblank_events;
std::vector<Kernel::OrbisKernelEqueue> flip_events;
std::vector<Kernel::OrbisKernelEqueue> vblank_events;
std::mutex vo_mutex;
std::mutex port_mutex;
std::condition_variable vo_cv;

View File

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