From 7ed0267f5b05d6d12ad74379d594d01359563b98 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 25 Oct 2021 18:39:58 +0200 Subject: [PATCH] Made a large optimization to the SVG rasterization logic. --- es-core/src/Window.cpp | 2 +- es-core/src/components/ImageComponent.h | 3 +- es-core/src/components/NinePatchComponent.cpp | 2 +- es-core/src/components/OptionListComponent.h | 8 ++- es-core/src/components/SwitchComponent.cpp | 11 +-- es-core/src/components/SwitchComponent.h | 3 +- es-core/src/resources/TextureData.cpp | 67 ++++++++++++------- es-core/src/resources/TextureData.h | 7 ++ es-core/src/resources/TextureResource.cpp | 22 ++++-- es-core/src/resources/TextureResource.h | 6 ++ 10 files changed, 81 insertions(+), 50 deletions(-) diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 0943e8f04..629872054 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -600,8 +600,8 @@ void Window::renderLoadingScreen(std::string text) static_cast(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF); ImageComponent splash(this, true); - splash.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f); splash.setImage(":/graphics/splash.svg"); + splash.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f); splash.setPosition((Renderer::getScreenWidth() - splash.getSize().x) / 2.0f, (Renderer::getScreenHeight() - splash.getSize().y) / 2.0f * 0.6f); splash.render(trans); diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index 40b15cf3d..0f22d86a1 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -109,7 +109,8 @@ private: bool mTargetIsMin; // Calculates the correct mSize from our resizing information (set by setResize/setMaxSize). - // Used internally whenever the resizing parameters or texture change. + // Used internally whenever the resizing parameters or texture change. This function also + // initiates the SVG rasterization. void resize(); Renderer::Vertex mVertices[4]; diff --git a/es-core/src/components/NinePatchComponent.cpp b/es-core/src/components/NinePatchComponent.cpp index d908499fd..ad6eaab3f 100644 --- a/es-core/src/components/NinePatchComponent.cpp +++ b/es-core/src/components/NinePatchComponent.cpp @@ -59,7 +59,7 @@ void NinePatchComponent::buildVertices() else scaleFactor = glm::clamp(Renderer::getScreenWidthModifier(), 0.4f, 3.0f); - mTexture = TextureResource::get(mPath, false, false, true, true, scaleFactor); + mTexture = TextureResource::get(mPath, false, false, true, true, true, scaleFactor); if (mTexture->getSize() == glm::ivec2{}) { mVertices = nullptr; diff --git a/es-core/src/components/OptionListComponent.h b/es-core/src/components/OptionListComponent.h index c25812743..181b8693a 100644 --- a/es-core/src/components/OptionListComponent.h +++ b/es-core/src/components/OptionListComponent.h @@ -76,8 +76,12 @@ public: // Handles positioning/resizing of text and arrows. void onSizeChanged() override { - mLeftArrow.setResize(0, mText.getFont()->getLetterHeight()); - mRightArrow.setResize(0, mText.getFont()->getLetterHeight()); + if (mText.getFont()->getLetterHeight() != mLeftArrow.getSize().y || + mLeftArrow.getTexture()->getPendingRasterization()) + mLeftArrow.setResize(0, mText.getFont()->getLetterHeight()); + if (mText.getFont()->getLetterHeight() != mRightArrow.getSize().y || + mRightArrow.getTexture()->getPendingRasterization()) + mRightArrow.setResize(0, mText.getFont()->getLetterHeight()); if (mSize.x < (mLeftArrow.getSize().x + mRightArrow.getSize().x)) { LOG(LogWarning) << "OptionListComponent too narrow"; diff --git a/es-core/src/components/SwitchComponent.cpp b/es-core/src/components/SwitchComponent.cpp index a2712ec5d..e1249009a 100644 --- a/es-core/src/components/SwitchComponent.cpp +++ b/es-core/src/components/SwitchComponent.cpp @@ -49,16 +49,6 @@ void SwitchComponent::render(const glm::mat4& parentTrans) renderChildren(trans); } -void SwitchComponent::setResize(float width, float height) -{ - // Reposition the switch after resizing to make it centered. - const glm::vec2 oldSize = mImage.getSize(); - mImage.setResize(width, height); - const float xDiff = oldSize.x - mImage.getSize().x; - const float yDiff = oldSize.y - mImage.getSize().y; - mImage.setPosition(mImage.getPosition().x + xDiff, mImage.getPosition().y + yDiff / 2.0f); -} - void SwitchComponent::setState(bool state) { mState = state; @@ -81,6 +71,7 @@ void SwitchComponent::setValue(const std::string& statestring) void SwitchComponent::onStateChanged() { mImage.setImage(mState ? ":/graphics/on.svg" : ":/graphics/off.svg"); + mImage.setResize(mSize); // Change the color of the switch to reflect the changes. if (mState == mOriginalValue) diff --git a/es-core/src/components/SwitchComponent.h b/es-core/src/components/SwitchComponent.h index a0d1ac33c..47011b29d 100644 --- a/es-core/src/components/SwitchComponent.h +++ b/es-core/src/components/SwitchComponent.h @@ -22,8 +22,7 @@ public: void render(const glm::mat4& parentTrans) override; void onSizeChanged() override { mImage.setSize(mSize); } - void setResize(float width, float height) override; - + void setResize(float width, float height) override { setSize(width, height); } bool getState() const { return mState; } void setState(bool state); std::string getValue() const override; diff --git a/es-core/src/resources/TextureData.cpp b/es-core/src/resources/TextureData.cpp index 9537b25d7..927a03854 100644 --- a/es-core/src/resources/TextureData.cpp +++ b/es-core/src/resources/TextureData.cpp @@ -24,16 +24,18 @@ #define DPI 96 TextureData::TextureData(bool tile) - : mTile(tile) - , mTextureID(0) + : mTile{tile} + , mTextureID{0} , mDataRGBA({}) - , mWidth(0) - , mHeight(0) - , mSourceWidth(0.0f) - , mSourceHeight(0.0f) - , mScaleDuringLoad(1.0f) - , mScalable(false) - , mLinearMagnify(false) + , mWidth{0} + , mHeight{0} + , mSourceWidth{0.0f} + , mSourceHeight{0.0f} + , mScaleDuringLoad{1.0f} + , mScalable{false} + , mLinearMagnify{false} + , mAlwaysRasterize{false} + , mPendingRasterization{false} { } @@ -54,21 +56,26 @@ void TextureData::initFromPath(const std::string& path) bool TextureData::initSVGFromMemory(const std::string& fileData) { // If already initialized then don't process it again. - std::unique_lock lock(mMutex); + std::unique_lock lock{mMutex}; if (!mDataRGBA.empty()) return true; - NSVGimage* svgImage = nsvgParse(const_cast(fileData.c_str()), "px", DPI); + NSVGimage* svgImage{nsvgParse(const_cast(fileData.c_str()), "px", DPI)}; if (!svgImage) { LOG(LogError) << "Couldn't parse SVG image"; return false; } - // We want to rasterize this texture at a specific resolution. If the source size - // variables are set then use them, otherwise get them from the parsed file. - if ((mSourceWidth == 0.0f) && (mSourceHeight == 0.0f)) { + bool rasterize{true}; + + // If there is no image size defined yet, then don't rasterize unless mAlwaysRasterize has + // been set (this is only used by NinePatchComponent to avoid flickering menus). + if (mSourceWidth == 0.0f && mSourceHeight == 0.0f) { + if (!mAlwaysRasterize) + rasterize = false; + mSourceWidth = svgImage->width; mSourceHeight = svgImage->height; } @@ -87,23 +94,31 @@ bool TextureData::initSVGFromMemory(const std::string& fileData) std::round((static_cast(mWidth) / svgImage->width) * svgImage->height)); } - std::vector tempVector; - tempVector.reserve(mWidth * mHeight * 4); + if (rasterize) { + std::vector tempVector; + tempVector.reserve(mWidth * mHeight * 4); - NSVGrasterizer* rast = nsvgCreateRasterizer(); + NSVGrasterizer* rast = nsvgCreateRasterizer(); - nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, tempVector.data(), mWidth, - mHeight, mWidth * 4); + nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, tempVector.data(), mWidth, + mHeight, mWidth * 4); + + nsvgDeleteRasterizer(rast); + + mDataRGBA.insert(mDataRGBA.begin(), tempVector.data(), + tempVector.data() + (mWidth * mHeight * 4)); + + ImageIO::flipPixelsVert(mDataRGBA.data(), mWidth, mHeight); + mPendingRasterization = false; + } + else { + // TODO: Fix this properly instead of using the single byte texture workaround. + mDataRGBA.push_back(0); + mPendingRasterization = true; + } - // This is important in order to avoid memory leaks. - nsvgDeleteRasterizer(rast); nsvgDelete(svgImage); - mDataRGBA.insert(mDataRGBA.begin(), tempVector.data(), - tempVector.data() + (mWidth * mHeight * 4)); - - ImageIO::flipPixelsVert(mDataRGBA.data(), mWidth, mHeight); - return true; } diff --git a/es-core/src/resources/TextureData.h b/es-core/src/resources/TextureData.h index 0e76d8159..16394167e 100644 --- a/es-core/src/resources/TextureData.h +++ b/es-core/src/resources/TextureData.h @@ -61,6 +61,11 @@ public: void setScaleDuringLoad(float scale) { mScaleDuringLoad = scale; } // Whether to use linear filtering when magnifying the texture. void setLinearMagnify(bool setting) { mLinearMagnify = setting; } + // Whether to rasterize the image even if a size has not been set yet. + void setAlwaysRasterize(bool setting) { mAlwaysRasterize = setting; } + + // Has the image been loaded but not yet been rasterized as the size was not known? + bool getPendingRasterization() { return mPendingRasterization; } std::vector getRawRGBAData() { return mDataRGBA; } std::string getTextureFilePath() { return mPath; } @@ -80,6 +85,8 @@ private: bool mScalable; bool mLinearMagnify; bool mReloadable; + bool mAlwaysRasterize; + bool mPendingRasterization; }; #endif // ES_CORE_RESOURCES_TEXTURE_DATA_H diff --git a/es-core/src/resources/TextureResource.cpp b/es-core/src/resources/TextureResource.cpp index b6f7b8d8a..0b2c3820d 100644 --- a/es-core/src/resources/TextureResource.cpp +++ b/es-core/src/resources/TextureResource.cpp @@ -8,16 +8,20 @@ #include "resources/TextureResource.h" -#include "resources/TextureData.h" #include "utils/FileSystemUtil.h" +#include "utils/StringUtil.h" TextureDataManager TextureResource::sTextureDataManager; std::map> TextureResource::sTextureMap; std::set TextureResource::sAllTextures; -TextureResource::TextureResource( - const std::string& path, bool tile, bool dynamic, bool linearMagnify, float scaleDuringLoad) +TextureResource::TextureResource(const std::string& path, + bool tile, + bool dynamic, + bool linearMagnify, + bool alwaysRasterize, + float scaleDuringLoad) : mTextureData(nullptr) , mForceLoad(false) { @@ -32,6 +36,7 @@ TextureResource::TextureResource( if (scaleDuringLoad != 1.0f) data->setScaleDuringLoad(scaleDuringLoad); data->setLinearMagnify(linearMagnify); + data->setAlwaysRasterize(alwaysRasterize); // Force the texture manager to load it using a blocking load. sTextureDataManager.load(data, true); } @@ -42,6 +47,7 @@ TextureResource::TextureResource( if (scaleDuringLoad != 1.0f) data->setScaleDuringLoad(scaleDuringLoad); data->setLinearMagnify(linearMagnify); + data->setAlwaysRasterize(alwaysRasterize); // Load it so we can read the width/height. data->load(); } @@ -148,6 +154,7 @@ std::shared_ptr TextureResource::get(const std::string& path, bool forceLoad, bool dynamic, bool linearMagnify, + bool alwaysRasterize, float scaleDuringLoad) { std::shared_ptr& rm = ResourceManager::getInstance(); @@ -155,7 +162,7 @@ std::shared_ptr TextureResource::get(const std::string& path, const std::string canonicalPath = Utils::FileSystem::getCanonicalPath(path); if (canonicalPath.empty()) { std::shared_ptr tex( - new TextureResource("", tile, false, linearMagnify, scaleDuringLoad)); + new TextureResource("", tile, false, linearMagnify, alwaysRasterize, scaleDuringLoad)); // Make sure we get properly deinitialized even though we do nothing on reinitialization. rm->addReloadable(tex); return tex; @@ -171,12 +178,13 @@ std::shared_ptr TextureResource::get(const std::string& path, // Need to create it. std::shared_ptr tex; - tex = std::shared_ptr( - new TextureResource(key.first, tile, dynamic, linearMagnify, scaleDuringLoad)); + tex = std::shared_ptr(new TextureResource( + key.first, tile, dynamic, linearMagnify, alwaysRasterize, scaleDuringLoad)); std::shared_ptr data = sTextureDataManager.get(tex.get()); // Is it an SVG? - if (key.first.substr(key.first.size() - 4, std::string::npos) != ".svg") { + if (Utils::String::toLower(key.first.substr(key.first.size() - 4, std::string::npos)) != + ".svg") { // Probably not. Add it to our map. We don't add SVGs because 2 SVGs might be // rasterized at different sizes. sTextureMap[key] = std::weak_ptr(tex); diff --git a/es-core/src/resources/TextureResource.h b/es-core/src/resources/TextureResource.h index 91ba5ef7e..9e5f02a2f 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -10,6 +10,7 @@ #define ES_CORE_RESOURCES_TEXTURE_RESOURCE_H #include "resources/ResourceManager.h" +#include "resources/TextureData.h" #include "resources/TextureDataManager.h" #include "utils/MathUtil.h" @@ -30,6 +31,7 @@ public: bool forceLoad = false, bool dynamic = true, bool linearMagnify = false, + bool alwaysRasterize = false, float scaleDuringLoad = 1.0f); void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height); virtual void initFromMemory(const char* data, size_t length); @@ -38,6 +40,9 @@ public: // Returns the raw pixel values. std::vector getRawRGBAData(); + // Has the image been loaded but not yet been rasterized as the size was not known? + bool getPendingRasterization() { return mTextureData->getPendingRasterization(); } + std::string getTextureFilePath(); // For SVG graphics this function effectively rescales the image to the defined size. @@ -65,6 +70,7 @@ protected: bool tile, bool dynamic, bool linearMagnify, + bool alwaysRasterize, float scaleDuringLoad); virtual void unload(std::shared_ptr& rm); virtual void reload(std::shared_ptr& rm);