feat(libSceVideoOut): deliver pre-vblank start events

sceVideoOutAddPreVblankStartEvent threw "physical pre-vblank timing is
not implemented", so titles that pace on it stopped at startup. It now
registers the event like the vblank event does, and each emulated vblank
tick triggers the pre-vblank start events just before the vblank events,
with the pre-vblank count as data. The console's lead time is unknown.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MathiasDevelopes 2026-10-08 19:59:34 +02:00
parent 43972e69a1
commit 10f57dea29
4 changed files with 13 additions and 4 deletions

View File

@ -120,10 +120,7 @@ int APS5_VABI sceVideoOutAddVblankEvent(KernelEqueue eq, int handle, void* udata
}
int APS5_VABI sceVideoOutAddPreVblankStartEvent(KernelEqueue eq, int handle, void* udata) try {
static_cast<void>(eq);
static_cast<void>(udata);
VideoOutDriver::Get().GetConfig(handle);
throw std::runtime_error("VideoOut: physical pre-vblank timing is not implemented");
return registerVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_PRE_VBLANK_START, udata);
} catch (const ProcessShutdown&) {
LibcAwaitExit_nid_postfix();
}

View File

@ -406,6 +406,10 @@ void VideoOutDriver::vblankEnd() {
std::lock_guard cfgLock(cfg->mutex);
if (!cfg->opened || cfg->failure) continue;
require(cfg->vblankStatus.count != std::numeric_limits<uint64_t>::max(), "vblank counter overflow");
++cfg->preVblankStatus.count;
cfg->preVblankStatus.processTime = sceKernelGetProcessTime();
cfg->preVblankStatus.processTimeCounter = sceKernelGetProcessTimeCounter();
triggerEvents(*cfg, VIDEO_OUT_EVENT_PRE_VBLANK_START, reinterpret_cast<void*>(cfg->preVblankStatus.count));
++cfg->vblankStatus.count;
cfg->vblankStatus.processTime = sceKernelGetProcessTime();
cfg->vblankStatus.processTimeCounter = sceKernelGetProcessTimeCounter();

View File

@ -258,6 +258,13 @@ void testControls() {
}
check(sceVideoOutWaitVblank(handle) == 0, "vblank wait failed");
check(owner->GetTriggeredEvents(&event, 1) == 1 && event.udata == &settings && sceVideoOutGetEventId(&event) == VIDEO_OUT_EVENT_VBLANK, "vblank event or updated user data missing");
check(sceVideoOutAddPreVblankStartEvent(queue, handle, &mode) == 0, "pre-vblank subscription failed");
check(sceVideoOutWaitVblank(handle) == 0, "vblank wait failed");
KernelEvent events[2]{};
check(owner->GetTriggeredEvents(events, 2) == 2, "pre-vblank or vblank event missing");
const auto& preVblank = sceVideoOutGetEventId(&events[0]) == VIDEO_OUT_EVENT_PRE_VBLANK_START ? events[0] : events[1];
check(sceVideoOutGetEventId(&preVblank) == VIDEO_OUT_EVENT_PRE_VBLANK_START && preVblank.udata == &mode, "pre-vblank event missing");
check(sceVideoOutDeletePreVblankStartEvent(queue, handle) == 0, "pre-vblank unsubscription failed");
std::vector<std::byte> allocation(65536 + 65535);
const auto storage = alignedBuffer(allocation);
VideoOutBuffers buffer{storage.data(), nullptr, {allocation.data(), storage.data()}};

View File

@ -239,6 +239,7 @@ Throughout the project, every function at every stage either **does exactly what
- [sceAjmBatchJobSetResampleParameters, sceAjmBatchJobGetResampleInfo](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm) - the ratio is taken as decoded samples per output sample and num_samples as the decoded samples the resampler holds, both inferred from FMOD (Hades II); the console's interpolation is unknown, a 4-point Catmull-Rom is used. The flags argument is ignored, as in shadPS4 and Kyty
- [scePlayerReviewDialogOpen, scePlayerReviewDialogGetResult](../../core/libs/prx/libSceCdlgPlayerReview/Export.cpp) (libSceCdlgPlayerReview) - the PS5-only dialog has no public reference: the parameter is not read and the result is assumed to start with the 32-bit common dialog result, as `sceWebBrowserDialogGetResult` assumes
- [sceHttpWaitRequest](../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - the timeout is assumed to be in microseconds and a negative timeout to wait without limit; the invalid-value error for a null handle or event array or a non-positive event count is assumed, not measured. No request is ever pending, so the call waits for the timeout and returns 0 events, and a wait without limit throws
- [sceVideoOutAddPreVblankStartEvent](../../core/libs/prx/libSceVideoOut/src/Event.cpp) (libSceVideoOut) - how long before the vblank the console signals pre-vblank start is unknown. The event is triggered on each emulated vblank tick immediately before the vblank event, with its own count as the event data
### Functional