diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index e4c27169d..17dbb8716 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -277,6 +277,8 @@ void Settings::setDefaults() mBoolMap["DebugSkipInputLogging"] = {false, false}; mStringMap["ROMDirectory"] = {"", ""}; mStringMap["UIMode_passkey"] = {"uuddlrlrba", "uuddlrlrba"}; + mIntMap["LottieMaxFileCache"] = {100, 100}; + mIntMap["LottieMaxTotalCache"] = {1024, 1024}; // // Hardcoded or program-internal settings. diff --git a/es-core/src/components/LottieComponent.cpp b/es-core/src/components/LottieComponent.cpp index d93f553d8..64d1b3729 100644 --- a/es-core/src/components/LottieComponent.cpp +++ b/es-core/src/components/LottieComponent.cpp @@ -17,6 +17,10 @@ LottieComponent::LottieComponent(Window* window) : GuiComponent{window} + , mCacheFrames{true} + , mMaxCacheSize{0} + , mCacheSize{0} + , mFrameSize{0} , mAnimation{nullptr} , mSurface{nullptr} , mTotalFrames{0} @@ -35,6 +39,17 @@ LottieComponent::LottieComponent(Window* window) mTexture->setFormat(Renderer::Texture::BGRA); #endif + // Keep per-file cache size within 0 to 1024 MiB. + mMaxCacheSize = static_cast( + glm::clamp(Settings::getInstance()->getInt("LottieMaxFileCache"), 0, 1024) * 1024 * 1024); + + // Keep total cache size within 0 to 4096 MiB. + int maxTotalCache = + glm::clamp(Settings::getInstance()->getInt("LottieMaxTotalCache"), 0, 4096) * 1024 * 1024; + + if (mMaxTotalFrameCache != static_cast(maxTotalCache)) + mMaxTotalFrameCache = static_cast(maxTotalCache); + // Set component defaults. setOrigin(0.5f, 0.5f); setSize(Renderer::getScreenWidth() * 0.2f, Renderer::getScreenHeight() * 0.2f); @@ -43,6 +58,13 @@ LottieComponent::LottieComponent(Window* window) setZIndex(30.0f); } +LottieComponent::~LottieComponent() +{ + if (mFuture.valid()) + mFuture.get(); + mTotalFrameCache -= mCacheSize; +} + void LottieComponent::setAnimation(const std::string& path) { if (mAnimation != nullptr) { @@ -51,6 +73,7 @@ void LottieComponent::setAnimation(const std::string& path) mSurface.reset(); mAnimation.reset(); mPictureRGBA.clear(); + mCacheSize = 0; } mPath = path; @@ -98,6 +121,7 @@ void LottieComponent::setAnimation(const std::string& path) double duration = mAnimation->duration(); mTotalFrames = mAnimation->totalFrame(); mFrameRate = mAnimation->frameRate(); + mFrameSize = width * height * 4; mTargetPacing = static_cast(1000.0 / mFrameRate); @@ -109,7 +133,8 @@ void LottieComponent::setAnimation(const std::string& path) void LottieComponent::onSizeChanged() { // Setting the animation again will completely reinitialize it. - setAnimation(mPath); + if (mPath != "") + setAnimation(mPath); } void LottieComponent::applyTheme(const std::shared_ptr& theme, @@ -156,16 +181,45 @@ void LottieComponent::render(const glm::mat4& parentTrans) if (mFuture.valid()) { if (mFuture.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready) { + mFuture.get(); + + // Cache frame if caching is enabled and we're not exceeding either the per-file + // max cache size or the total cache size. Note that this is completely unrelated + // to the texture caching used for images. + if (mCacheFrames && mFrameCache.find(mFrameNum) == mFrameCache.end()) { + size_t newCacheSize = mCacheSize + mFrameSize; + if (newCacheSize < mMaxCacheSize && + mTotalFrameCache + mFrameSize < mMaxTotalFrameCache) { + mFrameCache[mFrameNum] = mPictureRGBA; + mCacheSize += mFrameSize; + mTotalFrameCache += mFrameSize; + } + } + mTexture->initFromPixels(&mPictureRGBA.at(0), static_cast(mSize.x), static_cast(mSize.y)); - mFuture.get(); + ++mFrameNum; - renderNextFrame = true; + + if (mFrameNum == mTotalFrames) + renderNextFrame = false; + else + renderNextFrame = true; } } else { - renderNextFrame = true; + if (mFrameCache.find(mFrameNum) != mFrameCache.end()) { + if (!mHoldFrame) { + mTexture->initFromPixels(&mFrameCache[mFrameNum][0], static_cast(mSize.x), + static_cast(mSize.y)); + ++mFrameNum; + } + } + else { + renderNextFrame = true; + } } + if (renderNextFrame && !mHoldFrame) mFuture = mAnimation->render(mFrameNum, *mSurface, mKeepAspectRatio); diff --git a/es-core/src/components/LottieComponent.h b/es-core/src/components/LottieComponent.h index d9e171d3f..67a33b3f1 100644 --- a/es-core/src/components/LottieComponent.h +++ b/es-core/src/components/LottieComponent.h @@ -12,19 +12,27 @@ #include "GuiComponent.h" #include "renderers/Renderer.h" #include "resources/TextureResource.h" +#include "utils/MathUtil.h" #include "rlottie.h" #include #include +#include class LottieComponent : public GuiComponent { public: LottieComponent(Window* window); + ~LottieComponent(); void setAnimation(const std::string& path); void setKeepAspectRatio(bool value) { mKeepAspectRatio = value; } + void setFrameCaching(bool value) { mCacheFrames = value; } + void setMaxCacheSize(int value) + { + mMaxCacheSize = static_cast(glm::clamp(value, 0, 1024) * 1024 * 1024); + } void onSizeChanged() override; @@ -39,6 +47,14 @@ private: std::shared_ptr mTexture; std::vector mPictureRGBA; + std::unordered_map> mFrameCache; + // Set a 1024 MiB total Lottie animation cache as default. + inline static size_t mMaxTotalFrameCache = 1024 * 1024 * 1024; + inline static size_t mTotalFrameCache; + bool mCacheFrames; + size_t mMaxCacheSize; + size_t mCacheSize; + size_t mFrameSize; std::unique_ptr mAnimation; std::unique_ptr mSurface; @@ -53,6 +69,7 @@ private: bool mKeepAspectRatio; + // TEMPORARY. std::chrono::time_point mAnimationStartTime; std::chrono::time_point mAnimationEndTime; };