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);
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);

View file

@ -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];

View file

@ -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;

View file

@ -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";

View file

@ -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)

View file

@ -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;

View file

@ -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<std::mutex> lock(mMutex);
std::unique_lock<std::mutex> lock{mMutex};
if (!mDataRGBA.empty())
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) {
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<float>(mWidth) / svgImage->width) * svgImage->height));
}
std::vector<unsigned char> tempVector;
tempVector.reserve(mWidth * mHeight * 4);
if (rasterize) {
std::vector<unsigned char> 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;
}

View file

@ -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<unsigned char> 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

View file

@ -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::TextureKeyType, std::weak_ptr<TextureResource>>
TextureResource::sTextureMap;
std::set<TextureResource*> 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> TextureResource::get(const std::string& path,
bool forceLoad,
bool dynamic,
bool linearMagnify,
bool alwaysRasterize,
float scaleDuringLoad)
{
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);
if (canonicalPath.empty()) {
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.
rm->addReloadable(tex);
return tex;
@ -171,12 +178,13 @@ std::shared_ptr<TextureResource> TextureResource::get(const std::string& path,
// Need to create it.
std::shared_ptr<TextureResource> tex;
tex = std::shared_ptr<TextureResource>(
new TextureResource(key.first, tile, dynamic, linearMagnify, scaleDuringLoad));
tex = std::shared_ptr<TextureResource>(new TextureResource(
key.first, tile, dynamic, linearMagnify, alwaysRasterize, scaleDuringLoad));
std::shared_ptr<TextureData> 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<TextureResource>(tex);

View file

@ -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<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();
// 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<ResourceManager>& rm);
virtual void reload(std::shared_ptr<ResourceManager>& rm);