diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 4d0cba868f..0f80b9c593 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -169,6 +169,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "SpriteBorderFix", &flags_.SpriteBorderFix); CheckSetting(iniFile, gameID, "TextureCLUTInShader", &flags_.TextureCLUTInShader); CheckSetting(iniFile, gameID, "DisableRangeCulling", &flags_.DisableRangeCulling); + CheckSetting(iniFile, gameID, "PaceVideocodecDecode", &flags_.PaceVideocodecDecode); } void Compatibility::CheckVRSettings(IniFile &iniFile, const std::string &gameID) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index 1908a7bcb3..e7f1220d33 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -124,6 +124,7 @@ struct CompatFlags { float SpriteBorderFix; bool TextureCLUTInShader; bool DisableRangeCulling; + bool PaceVideocodecDecode; }; struct VRCompat { diff --git a/Core/HLE/sceVideocodec.cpp b/Core/HLE/sceVideocodec.cpp index 654e58da05..290bb48403 100644 --- a/Core/HLE/sceVideocodec.cpp +++ b/Core/HLE/sceVideocodec.cpp @@ -33,6 +33,9 @@ #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" +#include "Core/Config.h" +#include "Core/CoreTiming.h" +#include "Core/System.h" #include "Core/HLE/ErrorCodes.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" @@ -95,6 +98,9 @@ struct VideocodecCtx { u32 frameBuffersSize = 0; int frameBufferWidth = 0; int frameBufferHeight = 0; + // When the next decode may finish, for PaceVideocodecDecode. Not serialized: a restored state + // just paces from scratch. + s64 pacedUntilUs = 0; }; static std::map g_videocodecCtxs; @@ -583,12 +589,25 @@ static int sceVideocodecDecode(u32 ctxAddr, int type) { out32(36, published ? 0 : 1); } + // This is a compat hack for games that do not seem to pace playback in any way, such as Ys I & II. + if (gotFrame && PSP_CoreParameter().compat.flags().PaceVideocodecDecode && vctx.decoder) { + const int period = vctx.decoder->FramePeriodUs(); + if (period > 0) { + const s64 now = CoreTiming::GetGlobalTimeUs(); + const int wait = (int)std::max((s64)0, vctx.pacedUntilUs - now); + vctx.pacedUntilUs = now + wait + period; + if (wait > 0) { + return hleDelayResult(hleLogDebug(Log::ME, 0, "type %d, %d bytes -> frame %dx%d", + type, auBytes, width, height), "videocodec decode", wait); + } + } + } return hleLogDebug(Log::ME, 0, "type %d, %d bytes -> %s %dx%d", type, auBytes, gotFrame ? "frame" : "no frame yet", width, height); } // Stopping or deleting the decoder is an ME round-trip and takes real time on hardware. Returning -// immediately matters beyond speed: a game can be relying on a thread of its own getting to run +// immediately is not correct, because a game can be relying on a thread of its own getting to run // once more before it tears things down. Jak and Daxter deletes its video_sound_thread straight // after sceVideocodecDelete without waiting for it to exit, and with no time passing here the audio // thread never gets to deliver the wake that would let it exit - so the delete fails with diff --git a/Core/HW/AvcDecoder.cpp b/Core/HW/AvcDecoder.cpp index 4627112440..09fc39f12a 100644 --- a/Core/HW/AvcDecoder.cpp +++ b/Core/HW/AvcDecoder.cpp @@ -193,6 +193,17 @@ bool AvcDecoder::Decode(const u8 *data, int size) { #endif } +int AvcDecoder::FramePeriodUs() const { +#ifdef USE_FFMPEG + if (!codecCtx_ || codecCtx_->framerate.num <= 0 || codecCtx_->framerate.den <= 0) { + return 0; + } + return (int)((s64)1000000 * codecCtx_->framerate.den / codecCtx_->framerate.num); +#else + return 0; +#endif +} + const u8 *AvcDecoder::Plane(int index) const { #ifdef USE_FFMPEG if (!haveFrame_ || !frame_ || index < 0 || index >= 3) { diff --git a/Core/HW/AvcDecoder.h b/Core/HW/AvcDecoder.h index 5724f3d5a7..8ee1caf4d8 100644 --- a/Core/HW/AvcDecoder.h +++ b/Core/HW/AvcDecoder.h @@ -56,6 +56,8 @@ public: // necessarily the width. const u8 *Plane(int index) const; int Stride(int index) const; + // The frame period the bitstream declares, in microseconds, or 0 if it says nothing useful. + int FramePeriodUs() const; // True if we were built without ffmpeg, in which case nothing decodes. static bool IsAvailable(); diff --git a/assets/compat.ini b/assets/compat.ini index 4b248b0118..54bde52431 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -2209,3 +2209,10 @@ UCJS10100 = true UCKS45127 = true UCJS18055 = true NPJG00027 = true + +[PaceVideocodecDecode] +# Ys I & II Chronicles - the game never paces the video itself, so an instant decode runs +# the picture ahead of the audio and the movie ends early. +ULUS10547 = true +ULJM05475 = true +NPUH10091 = true