From 280ae098568ee3fa7fcff81b2cc97eb770d45c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 28 Sep 2026 18:43:14 -0600 Subject: [PATCH] sceUsbCam: Make sceUsbCamReadVideoFrameBlocking wait for the next frame It returned at once. Go!Edit reads frames in a loop on a high-priority thread (bhCameraGetJpeg) that only yields to its own priority level, so after the "Loading complete" dialog it spun and starved the rest of the game. Return at the camera's next frame tick, at the rate from the setup params. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceUsbCam.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Core/HLE/sceUsbCam.cpp b/Core/HLE/sceUsbCam.cpp index 608a0edf60..3ec2270250 100644 --- a/Core/HLE/sceUsbCam.cpp +++ b/Core/HLE/sceUsbCam.cpp @@ -27,6 +27,7 @@ #include "Core/HLE/HLE.h" #include "Core/HLE/sceUsbCam.h" #include "Core/HLE/sceUsbMic.h" +#include "Core/CoreTiming.h" #include "Core/HW/Camera.h" #include "Core/MemMapHelpers.h" @@ -43,6 +44,8 @@ Camera::Config *config; unsigned int videoBufferLength = 0; unsigned int nextVideoFrame = 0; +// When video capture started, which the frame clock counts from. Not saved in states, it only sets the phase. +static u64 videoStartUs = 0; uint8_t *videoBuffer; std::mutex videoBufferMutex; @@ -206,6 +209,7 @@ static int sceUsbCamStartVideo() { jpegData = nullptr; } + videoStartUs = CoreTiming::GetGlobalTimeUs(); Camera::startCapture(); return 0; } @@ -215,13 +219,29 @@ static int sceUsbCamStopVideo() { return 0; } +// How often the camera delivers a frame, from the framerate in the setup params +// (PSPSDK's PSP_USBCAM_FRAMERATE_*: 3.75, 5, 7.5, 10, 15, 20, 30 and 60 fps). +static int getFrameIntervalUs() { + static const int intervalsUs[] = { 266667, 200000, 133333, 100000, 66667, 50000, 33333, 16667 }; + int framerate = config->type == Camera::ConfigType::CfVideoEx ? config->videoExParam.framerate : config->videoParam.framerate; + if (framerate < 0 || framerate >= (int)ARRAY_SIZE(intervalsUs)) { + framerate = 6; // 30 fps + } + return intervalsUs[framerate]; +} + static int sceUsbCamReadVideoFrameBlocking(u32 bufAddr, u32 size) { std::lock_guard lock(videoBufferMutex); u32 transferSize = std::min(videoBufferLength, size); if (Memory::IsValidRange(bufAddr, size)) { Memory::Memcpy(bufAddr, videoBuffer, transferSize); } - return transferSize; + // This blocks until the camera's next frame. Returning at once lets a high-priority capture thread + // (Go!Edit's bhCameraGetJpeg) spin in its read loop and starve the rest of the game. + const int intervalUs = getFrameIntervalUs(); + const u64 sinceStartUs = CoreTiming::GetGlobalTimeUs() - videoStartUs; + const int waitUs = intervalUs - (int)(sinceStartUs % intervalUs); + return hleDelayResult(hleLogDebug(Log::HLE, transferSize), "camera frame", waitUs); } static int sceUsbCamReadVideoFrame(u32 bufAddr, u32 size) {