mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-22 06:05:38 +00:00
Fixed a massive memory leak related to SVG images.
Also did a general update and cleanup of TextureData.
This commit is contained in:
parent
eb51877aab
commit
c2b3b029e5
1
NEWS.md
1
NEWS.md
|
@ -75,6 +75,7 @@ Many bugs have been fixed, and numerous features that were only partially implem
|
||||||
* Restart and power-off menu entries not working
|
* Restart and power-off menu entries not working
|
||||||
* Unknown command line options were silently accepted instead of generating an error and notifying the user
|
* Unknown command line options were silently accepted instead of generating an error and notifying the user
|
||||||
* The scraper didn't handle error conditions correctly
|
* The scraper didn't handle error conditions correctly
|
||||||
|
* Fixed a massive memory leak related to SVG images
|
||||||
* Toggling the screensaver didn't work as expected
|
* Toggling the screensaver didn't work as expected
|
||||||
* The setting to enable or disable audio for the video screensaver only worked on Raspberry Pi
|
* The setting to enable or disable audio for the video screensaver only worked on Raspberry Pi
|
||||||
* The screensaver random function did not consider the previously selected game and could potentially show the same image or video over and over again
|
* The screensaver random function did not consider the previously selected game and could potentially show the same image or video over and over again
|
||||||
|
|
|
@ -25,7 +25,7 @@ TextureData::TextureData(
|
||||||
bool tile)
|
bool tile)
|
||||||
: mTile(tile),
|
: mTile(tile),
|
||||||
mTextureID(0),
|
mTextureID(0),
|
||||||
mDataRGBA(nullptr),
|
mDataRGBA({}),
|
||||||
mScalable(false),
|
mScalable(false),
|
||||||
mWidth(0),
|
mWidth(0),
|
||||||
mHeight(0),
|
mHeight(0),
|
||||||
|
@ -48,54 +48,57 @@ void TextureData::initFromPath(const std::string& path)
|
||||||
mReloadable = true;
|
mReloadable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextureData::initSVGFromMemory(const unsigned char* fileData, size_t length)
|
bool TextureData::initSVGFromMemory(const std::string& fileData)
|
||||||
{
|
{
|
||||||
// If already initialised then don't read 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)
|
|
||||||
|
if (!mDataRGBA.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// nsvgParse excepts a modifiable, null-terminated string.
|
NSVGimage* svgImage = nsvgParse(const_cast<char*>(fileData.c_str()), "px", DPI);
|
||||||
char* copy = (char*)malloc(length + 1);
|
|
||||||
assert(copy != nullptr);
|
|
||||||
memcpy(copy, fileData, length);
|
|
||||||
copy[length] = '\0';
|
|
||||||
|
|
||||||
NSVGimage* svgImage = nsvgParse(copy, "px", DPI);
|
|
||||||
free(copy);
|
|
||||||
if (!svgImage) {
|
if (!svgImage) {
|
||||||
LOG(LogError) << "Error parsing SVG image.";
|
LOG(LogError) << "Error parsing SVG image.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We want to rasterise this texture at a specific resolution. If the source size
|
// We want to rasterize this texture at a specific resolution. If the source size
|
||||||
// variables are set then use them otherwise set them from the parsed file.
|
// variables are set then use them, otherwise get them from the parsed file.
|
||||||
if ((mSourceWidth == 0.0f) && (mSourceHeight == 0.0f)) {
|
if ((mSourceWidth == 0.0f) && (mSourceHeight == 0.0f)) {
|
||||||
mSourceWidth = svgImage->width;
|
mSourceWidth = svgImage->width;
|
||||||
mSourceHeight = svgImage->height;
|
mSourceHeight = svgImage->height;
|
||||||
}
|
}
|
||||||
mWidth = (size_t)Math::round(mSourceWidth);
|
mWidth = static_cast<size_t>(Math::round(mSourceWidth));
|
||||||
mHeight = (size_t)Math::round(mSourceHeight);
|
mHeight = static_cast<size_t>(Math::round(mSourceHeight));
|
||||||
|
|
||||||
if (mWidth == 0) {
|
if (mWidth == 0) {
|
||||||
// Autoscale width to keep aspect.
|
// Auto scale width to keep aspect ratio.
|
||||||
mWidth = (size_t)Math::round(((float)mHeight / svgImage->height) * svgImage->width);
|
mWidth = static_cast<size_t>(Math::round((static_cast<float>(mHeight) /
|
||||||
|
svgImage->height) * svgImage->width));
|
||||||
}
|
}
|
||||||
else if (mHeight == 0) {
|
else if (mHeight == 0) {
|
||||||
// Autoscale height to keep aspect.
|
// Auto scale height to keep aspect ratio.
|
||||||
mHeight = (size_t)Math::round(((float)mWidth / svgImage->width) * svgImage->height);
|
mHeight = static_cast<size_t>(Math::round((static_cast<float>(mWidth) /
|
||||||
|
svgImage->width) * svgImage->height));
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char* dataRGBA = new unsigned char[mWidth * mHeight * 4];
|
std::vector<unsigned char> tempVector;
|
||||||
|
tempVector.reserve(mWidth * mHeight * 4);
|
||||||
|
|
||||||
NSVGrasterizer* rast = nsvgCreateRasterizer();
|
NSVGrasterizer* rast = nsvgCreateRasterizer();
|
||||||
nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, dataRGBA,
|
|
||||||
(int)mWidth, (int)mHeight, (int)mWidth * 4);
|
nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, tempVector.data(),
|
||||||
|
static_cast<int>(mWidth), static_cast<int>(mHeight), static_cast<int>(mWidth) * 4);
|
||||||
|
|
||||||
|
// This is important in order to avoid memory leaks.
|
||||||
nsvgDeleteRasterizer(rast);
|
nsvgDeleteRasterizer(rast);
|
||||||
|
nsvgDelete(svgImage);
|
||||||
|
|
||||||
ImageIO::flipPixelsVert(dataRGBA, mWidth, mHeight);
|
mDataRGBA.insert(mDataRGBA.begin(),
|
||||||
|
tempVector.data(), tempVector.data() + (mWidth * mHeight * 4));
|
||||||
|
|
||||||
mDataRGBA = dataRGBA;
|
ImageIO::flipPixelsVert(mDataRGBA.data(), mWidth, mHeight);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -104,25 +107,25 @@ bool TextureData::initImageFromMemory(const unsigned char* fileData, size_t leng
|
||||||
{
|
{
|
||||||
size_t width, height;
|
size_t width, height;
|
||||||
|
|
||||||
// If already initialised then don't read 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)
|
if (!mDataRGBA.empty())
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> imageRGBA = ImageIO::loadFromMemoryRGBA32(
|
std::vector<unsigned char> imageRGBA = ImageIO::loadFromMemoryRGBA32(
|
||||||
(const unsigned char*)(fileData), length, width, height);
|
static_cast<const unsigned char*>(fileData), length, width, height);
|
||||||
|
|
||||||
if (imageRGBA.size() == 0) {
|
if (imageRGBA.size() == 0) {
|
||||||
LOG(LogError) << "Could not initialize texture from memory, invalid data! (file path: "
|
LOG(LogError) << "Could not initialize texture from memory, invalid data! (file path: "
|
||||||
<< mPath << ", data ptr: " << (size_t)fileData <<
|
<< mPath << ", data ptr: " << reinterpret_cast<size_t>(fileData) <<
|
||||||
", reported size: " << length << ")";
|
", reported size: " << length << ")";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mSourceWidth = (float) width;
|
mSourceWidth = static_cast<float>(width);
|
||||||
mSourceHeight = (float) height;
|
mSourceHeight = static_cast<float>(height);
|
||||||
mScalable = false;
|
mScalable = false;
|
||||||
|
|
||||||
return initFromRGBA(imageRGBA.data(), width, height);
|
return initFromRGBA(imageRGBA.data(), width, height);
|
||||||
|
@ -130,14 +133,14 @@ bool TextureData::initImageFromMemory(const unsigned char* fileData, size_t leng
|
||||||
|
|
||||||
bool TextureData::initFromRGBA(const unsigned char* dataRGBA, size_t width, size_t height)
|
bool TextureData::initFromRGBA(const unsigned char* dataRGBA, size_t width, size_t height)
|
||||||
{
|
{
|
||||||
// If already initialised then don't read 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)
|
if (!mDataRGBA.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Take a copy.
|
mDataRGBA.reserve(width * height * 4);
|
||||||
mDataRGBA = new unsigned char[width * height * 4];
|
mDataRGBA.insert(mDataRGBA.begin(), dataRGBA, dataRGBA+(width * height * 4));
|
||||||
memcpy(mDataRGBA, dataRGBA, width * height * 4);
|
|
||||||
mWidth = width;
|
mWidth = width;
|
||||||
mHeight = height;
|
mHeight = height;
|
||||||
return true;
|
return true;
|
||||||
|
@ -154,10 +157,13 @@ bool TextureData::load()
|
||||||
// Is it an SVG?
|
// Is it an SVG?
|
||||||
if (mPath.substr(mPath.size() - 4, std::string::npos) == ".svg") {
|
if (mPath.substr(mPath.size() - 4, std::string::npos) == ".svg") {
|
||||||
mScalable = true;
|
mScalable = true;
|
||||||
retval = initSVGFromMemory((const unsigned char*)data.ptr.get(), data.length);
|
std::string dataString;
|
||||||
|
dataString.assign(std::string(reinterpret_cast<char*>(data.ptr.get()), data.length));
|
||||||
|
retval = initSVGFromMemory(dataString);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
retval = initImageFromMemory((const unsigned char*)data.ptr.get(), data.length);
|
retval = initImageFromMemory(static_cast<const unsigned char*>(data.ptr.get()),
|
||||||
|
data.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return retval;
|
return retval;
|
||||||
|
@ -166,30 +172,27 @@ bool TextureData::load()
|
||||||
bool TextureData::isLoaded()
|
bool TextureData::isLoaded()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
if (mDataRGBA || (mTextureID != 0))
|
if (!mDataRGBA.empty() || mTextureID != 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextureData::uploadAndBind()
|
bool TextureData::uploadAndBind()
|
||||||
{
|
{
|
||||||
// See if it's already been uploaded.
|
// Check if it has already been uploaded.
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
if (mTextureID != 0) {
|
if (mTextureID != 0) {
|
||||||
Renderer::bindTexture(mTextureID);
|
Renderer::bindTexture(mTextureID);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Load it if necessary.
|
|
||||||
if (!mDataRGBA)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Make sure we're ready to upload.
|
// Make sure we're ready to upload.
|
||||||
if ((mWidth == 0) || (mHeight == 0) || (mDataRGBA == nullptr))
|
if (mWidth == 0 || mHeight == 0 || mDataRGBA.empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Upload texture.
|
// Upload texture.
|
||||||
mTextureID = Renderer::createTexture(Renderer::Texture::RGBA, true,
|
mTextureID = Renderer::createTexture(Renderer::Texture::RGBA, true,
|
||||||
mTile, mWidth, mHeight, mDataRGBA);
|
mTile, mWidth, mHeight, mDataRGBA.data());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -206,8 +209,11 @@ void TextureData::releaseVRAM()
|
||||||
void TextureData::releaseRAM()
|
void TextureData::releaseRAM()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mMutex);
|
std::unique_lock<std::mutex> lock(mMutex);
|
||||||
delete[] mDataRGBA;
|
std::vector<unsigned char> swapVector;
|
||||||
mDataRGBA = 0;
|
if (!mDataRGBA.empty()) {
|
||||||
|
mDataRGBA.clear();
|
||||||
|
mDataRGBA.swap(swapVector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t TextureData::width()
|
size_t TextureData::width()
|
||||||
|
@ -252,7 +258,7 @@ void TextureData::setSourceSize(float width, float height)
|
||||||
|
|
||||||
size_t TextureData::getVRAMUsage()
|
size_t TextureData::getVRAMUsage()
|
||||||
{
|
{
|
||||||
if ((mTextureID != 0) || (mDataRGBA != nullptr))
|
if (mTextureID != 0 || !mDataRGBA.empty())
|
||||||
return mWidth * mHeight * 4;
|
return mWidth * mHeight * 4;
|
||||||
else
|
else
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class TextureResource;
|
class TextureResource;
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ public:
|
||||||
|
|
||||||
//!!!! Needs to be canonical path. Caller should check for duplicates before calling this.
|
//!!!! Needs to be canonical path. Caller should check for duplicates before calling this.
|
||||||
void initFromPath(const std::string& path);
|
void initFromPath(const std::string& path);
|
||||||
bool initSVGFromMemory(const unsigned char* fileData, size_t length);
|
bool initSVGFromMemory(const std::string& fileData);
|
||||||
bool initImageFromMemory(const unsigned char* fileData, size_t length);
|
bool initImageFromMemory(const unsigned char* fileData, size_t length);
|
||||||
bool initFromRGBA(const unsigned char* dataRGBA, size_t width, size_t height);
|
bool initFromRGBA(const unsigned char* dataRGBA, size_t width, size_t height);
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ public:
|
||||||
bool isLoaded();
|
bool isLoaded();
|
||||||
|
|
||||||
// Upload the texture to VRAM if necessary and bind.
|
// Upload the texture to VRAM if necessary and bind.
|
||||||
// Returns true if bound ok or false if either not loaded.
|
// Returns true if bound correctly.
|
||||||
bool uploadAndBind();
|
bool uploadAndBind();
|
||||||
|
|
||||||
// Release the texture from VRAM.
|
// Release the texture from VRAM.
|
||||||
|
@ -59,7 +60,7 @@ private:
|
||||||
bool mTile;
|
bool mTile;
|
||||||
std::string mPath;
|
std::string mPath;
|
||||||
unsigned int mTextureID;
|
unsigned int mTextureID;
|
||||||
unsigned char* mDataRGBA;
|
std::vector<unsigned char> mDataRGBA;
|
||||||
size_t mWidth;
|
size_t mWidth;
|
||||||
size_t mHeight;
|
size_t mHeight;
|
||||||
float mSourceWidth;
|
float mSourceWidth;
|
||||||
|
|
Loading…
Reference in a new issue