Made a large optimization to the SVG rasterization logic.

This commit is contained in:
Leon Styhre 2021-10-25 18:39:58 +02:00
parent 270a2e3857
commit 7ed0267f5b
10 changed files with 81 additions and 50 deletions

View file

@ -600,8 +600,8 @@ void Window::renderLoadingScreen(std::string text)
static_cast<float>(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF); static_cast<float>(Renderer::getScreenHeight()), 0x000000FF, 0x000000FF);
ImageComponent splash(this, true); ImageComponent splash(this, true);
splash.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f);
splash.setImage(":/graphics/splash.svg"); splash.setImage(":/graphics/splash.svg");
splash.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f);
splash.setPosition((Renderer::getScreenWidth() - splash.getSize().x) / 2.0f, splash.setPosition((Renderer::getScreenWidth() - splash.getSize().x) / 2.0f,
(Renderer::getScreenHeight() - splash.getSize().y) / 2.0f * 0.6f); (Renderer::getScreenHeight() - splash.getSize().y) / 2.0f * 0.6f);
splash.render(trans); splash.render(trans);

View file

@ -109,7 +109,8 @@ private:
bool mTargetIsMin; bool mTargetIsMin;
// Calculates the correct mSize from our resizing information (set by setResize/setMaxSize). // 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(); void resize();
Renderer::Vertex mVertices[4]; Renderer::Vertex mVertices[4];

View file

@ -59,7 +59,7 @@ void NinePatchComponent::buildVertices()
else else
scaleFactor = glm::clamp(Renderer::getScreenWidthModifier(), 0.4f, 3.0f); 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{}) { if (mTexture->getSize() == glm::ivec2{}) {
mVertices = nullptr; mVertices = nullptr;

View file

@ -76,7 +76,11 @@ public:
// Handles positioning/resizing of text and arrows. // Handles positioning/resizing of text and arrows.
void onSizeChanged() override void onSizeChanged() override
{ {
if (mText.getFont()->getLetterHeight() != mLeftArrow.getSize().y ||
mLeftArrow.getTexture()->getPendingRasterization())
mLeftArrow.setResize(0, mText.getFont()->getLetterHeight()); mLeftArrow.setResize(0, mText.getFont()->getLetterHeight());
if (mText.getFont()->getLetterHeight() != mRightArrow.getSize().y ||
mRightArrow.getTexture()->getPendingRasterization())
mRightArrow.setResize(0, mText.getFont()->getLetterHeight()); mRightArrow.setResize(0, mText.getFont()->getLetterHeight());
if (mSize.x < (mLeftArrow.getSize().x + mRightArrow.getSize().x)) { if (mSize.x < (mLeftArrow.getSize().x + mRightArrow.getSize().x)) {

View file

@ -49,16 +49,6 @@ void SwitchComponent::render(const glm::mat4& parentTrans)
renderChildren(trans); 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) void SwitchComponent::setState(bool state)
{ {
mState = state; mState = state;
@ -81,6 +71,7 @@ void SwitchComponent::setValue(const std::string& statestring)
void SwitchComponent::onStateChanged() void SwitchComponent::onStateChanged()
{ {
mImage.setImage(mState ? ":/graphics/on.svg" : ":/graphics/off.svg"); mImage.setImage(mState ? ":/graphics/on.svg" : ":/graphics/off.svg");
mImage.setResize(mSize);
// Change the color of the switch to reflect the changes. // Change the color of the switch to reflect the changes.
if (mState == mOriginalValue) if (mState == mOriginalValue)

View file

@ -22,8 +22,7 @@ public:
void render(const glm::mat4& parentTrans) override; void render(const glm::mat4& parentTrans) override;
void onSizeChanged() override { mImage.setSize(mSize); } 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; } bool getState() const { return mState; }
void setState(bool state); void setState(bool state);
std::string getValue() const override; std::string getValue() const override;

View file

@ -24,16 +24,18 @@
#define DPI 96 #define DPI 96
TextureData::TextureData(bool tile) TextureData::TextureData(bool tile)
: mTile(tile) : mTile{tile}
, mTextureID(0) , mTextureID{0}
, mDataRGBA({}) , mDataRGBA({})
, mWidth(0) , mWidth{0}
, mHeight(0) , mHeight{0}
, mSourceWidth(0.0f) , mSourceWidth{0.0f}
, mSourceHeight(0.0f) , mSourceHeight{0.0f}
, mScaleDuringLoad(1.0f) , mScaleDuringLoad{1.0f}
, mScalable(false) , mScalable{false}
, mLinearMagnify(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) bool TextureData::initSVGFromMemory(const std::string& fileData)
{ {
// If already initialized then don't process it again. // If already initialized then don't process it again.
std::unique_lock<std::mutex> lock(mMutex); std::unique_lock<std::mutex> lock{mMutex};
if (!mDataRGBA.empty()) if (!mDataRGBA.empty())
return true; return true;
NSVGimage* svgImage = nsvgParse(const_cast<char*>(fileData.c_str()), "px", DPI); NSVGimage* svgImage{nsvgParse(const_cast<char*>(fileData.c_str()), "px", DPI)};
if (!svgImage) { if (!svgImage) {
LOG(LogError) << "Couldn't parse SVG image"; LOG(LogError) << "Couldn't parse SVG image";
return false; return false;
} }
// We want to rasterize this texture at a specific resolution. If the source size bool rasterize{true};
// variables are set then use them, otherwise get them from the parsed file.
if ((mSourceWidth == 0.0f) && (mSourceHeight == 0.0f)) { // 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; mSourceWidth = svgImage->width;
mSourceHeight = svgImage->height; mSourceHeight = svgImage->height;
} }
@ -87,6 +94,7 @@ bool TextureData::initSVGFromMemory(const std::string& fileData)
std::round((static_cast<float>(mWidth) / svgImage->width) * svgImage->height)); std::round((static_cast<float>(mWidth) / svgImage->width) * svgImage->height));
} }
if (rasterize) {
std::vector<unsigned char> tempVector; std::vector<unsigned char> tempVector;
tempVector.reserve(mWidth * mHeight * 4); tempVector.reserve(mWidth * mHeight * 4);
@ -95,14 +103,21 @@ bool TextureData::initSVGFromMemory(const std::string& fileData)
nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, tempVector.data(), mWidth, nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, tempVector.data(), mWidth,
mHeight, mWidth * 4); mHeight, mWidth * 4);
// This is important in order to avoid memory leaks.
nsvgDeleteRasterizer(rast); nsvgDeleteRasterizer(rast);
nsvgDelete(svgImage);
mDataRGBA.insert(mDataRGBA.begin(), tempVector.data(), mDataRGBA.insert(mDataRGBA.begin(), tempVector.data(),
tempVector.data() + (mWidth * mHeight * 4)); tempVector.data() + (mWidth * mHeight * 4));
ImageIO::flipPixelsVert(mDataRGBA.data(), mWidth, mHeight); 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;
}
nsvgDelete(svgImage);
return true; return true;
} }

View file

@ -61,6 +61,11 @@ public:
void setScaleDuringLoad(float scale) { mScaleDuringLoad = scale; } void setScaleDuringLoad(float scale) { mScaleDuringLoad = scale; }
// Whether to use linear filtering when magnifying the texture. // Whether to use linear filtering when magnifying the texture.
void setLinearMagnify(bool setting) { mLinearMagnify = setting; } 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<unsigned char> getRawRGBAData() { return mDataRGBA; } std::vector<unsigned char> getRawRGBAData() { return mDataRGBA; }
std::string getTextureFilePath() { return mPath; } std::string getTextureFilePath() { return mPath; }
@ -80,6 +85,8 @@ private:
bool mScalable; bool mScalable;
bool mLinearMagnify; bool mLinearMagnify;
bool mReloadable; bool mReloadable;
bool mAlwaysRasterize;
bool mPendingRasterization;
}; };
#endif // ES_CORE_RESOURCES_TEXTURE_DATA_H #endif // ES_CORE_RESOURCES_TEXTURE_DATA_H

View file

@ -8,16 +8,20 @@
#include "resources/TextureResource.h" #include "resources/TextureResource.h"
#include "resources/TextureData.h"
#include "utils/FileSystemUtil.h" #include "utils/FileSystemUtil.h"
#include "utils/StringUtil.h"
TextureDataManager TextureResource::sTextureDataManager; TextureDataManager TextureResource::sTextureDataManager;
std::map<TextureResource::TextureKeyType, std::weak_ptr<TextureResource>> std::map<TextureResource::TextureKeyType, std::weak_ptr<TextureResource>>
TextureResource::sTextureMap; TextureResource::sTextureMap;
std::set<TextureResource*> TextureResource::sAllTextures; std::set<TextureResource*> TextureResource::sAllTextures;
TextureResource::TextureResource( TextureResource::TextureResource(const std::string& path,
const std::string& path, bool tile, bool dynamic, bool linearMagnify, float scaleDuringLoad) bool tile,
bool dynamic,
bool linearMagnify,
bool alwaysRasterize,
float scaleDuringLoad)
: mTextureData(nullptr) : mTextureData(nullptr)
, mForceLoad(false) , mForceLoad(false)
{ {
@ -32,6 +36,7 @@ TextureResource::TextureResource(
if (scaleDuringLoad != 1.0f) if (scaleDuringLoad != 1.0f)
data->setScaleDuringLoad(scaleDuringLoad); data->setScaleDuringLoad(scaleDuringLoad);
data->setLinearMagnify(linearMagnify); data->setLinearMagnify(linearMagnify);
data->setAlwaysRasterize(alwaysRasterize);
// Force the texture manager to load it using a blocking load. // Force the texture manager to load it using a blocking load.
sTextureDataManager.load(data, true); sTextureDataManager.load(data, true);
} }
@ -42,6 +47,7 @@ TextureResource::TextureResource(
if (scaleDuringLoad != 1.0f) if (scaleDuringLoad != 1.0f)
data->setScaleDuringLoad(scaleDuringLoad); data->setScaleDuringLoad(scaleDuringLoad);
data->setLinearMagnify(linearMagnify); data->setLinearMagnify(linearMagnify);
data->setAlwaysRasterize(alwaysRasterize);
// Load it so we can read the width/height. // Load it so we can read the width/height.
data->load(); data->load();
} }
@ -148,6 +154,7 @@ std::shared_ptr<TextureResource> TextureResource::get(const std::string& path,
bool forceLoad, bool forceLoad,
bool dynamic, bool dynamic,
bool linearMagnify, bool linearMagnify,
bool alwaysRasterize,
float scaleDuringLoad) float scaleDuringLoad)
{ {
std::shared_ptr<ResourceManager>& rm = ResourceManager::getInstance(); std::shared_ptr<ResourceManager>& rm = ResourceManager::getInstance();
@ -155,7 +162,7 @@ std::shared_ptr<TextureResource> TextureResource::get(const std::string& path,
const std::string canonicalPath = Utils::FileSystem::getCanonicalPath(path); const std::string canonicalPath = Utils::FileSystem::getCanonicalPath(path);
if (canonicalPath.empty()) { if (canonicalPath.empty()) {
std::shared_ptr<TextureResource> tex( std::shared_ptr<TextureResource> 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. // Make sure we get properly deinitialized even though we do nothing on reinitialization.
rm->addReloadable(tex); rm->addReloadable(tex);
return tex; return tex;
@ -171,12 +178,13 @@ std::shared_ptr<TextureResource> TextureResource::get(const std::string& path,
// Need to create it. // Need to create it.
std::shared_ptr<TextureResource> tex; std::shared_ptr<TextureResource> tex;
tex = std::shared_ptr<TextureResource>( tex = std::shared_ptr<TextureResource>(new TextureResource(
new TextureResource(key.first, tile, dynamic, linearMagnify, scaleDuringLoad)); key.first, tile, dynamic, linearMagnify, alwaysRasterize, scaleDuringLoad));
std::shared_ptr<TextureData> data = sTextureDataManager.get(tex.get()); std::shared_ptr<TextureData> data = sTextureDataManager.get(tex.get());
// Is it an SVG? // 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 // Probably not. Add it to our map. We don't add SVGs because 2 SVGs might be
// rasterized at different sizes. // rasterized at different sizes.
sTextureMap[key] = std::weak_ptr<TextureResource>(tex); sTextureMap[key] = std::weak_ptr<TextureResource>(tex);

View file

@ -10,6 +10,7 @@
#define ES_CORE_RESOURCES_TEXTURE_RESOURCE_H #define ES_CORE_RESOURCES_TEXTURE_RESOURCE_H
#include "resources/ResourceManager.h" #include "resources/ResourceManager.h"
#include "resources/TextureData.h"
#include "resources/TextureDataManager.h" #include "resources/TextureDataManager.h"
#include "utils/MathUtil.h" #include "utils/MathUtil.h"
@ -30,6 +31,7 @@ public:
bool forceLoad = false, bool forceLoad = false,
bool dynamic = true, bool dynamic = true,
bool linearMagnify = false, bool linearMagnify = false,
bool alwaysRasterize = false,
float scaleDuringLoad = 1.0f); float scaleDuringLoad = 1.0f);
void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height); void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height);
virtual void initFromMemory(const char* data, size_t length); virtual void initFromMemory(const char* data, size_t length);
@ -38,6 +40,9 @@ public:
// Returns the raw pixel values. // Returns the raw pixel values.
std::vector<unsigned char> getRawRGBAData(); std::vector<unsigned char> 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(); std::string getTextureFilePath();
// For SVG graphics this function effectively rescales the image to the defined size. // For SVG graphics this function effectively rescales the image to the defined size.
@ -65,6 +70,7 @@ protected:
bool tile, bool tile,
bool dynamic, bool dynamic,
bool linearMagnify, bool linearMagnify,
bool alwaysRasterize,
float scaleDuringLoad); float scaleDuringLoad);
virtual void unload(std::shared_ptr<ResourceManager>& rm); virtual void unload(std::shared_ptr<ResourceManager>& rm);
virtual void reload(std::shared_ptr<ResourceManager>& rm); virtual void reload(std::shared_ptr<ResourceManager>& rm);