mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-22 14:15:38 +00:00
Fix WSOD by loading textures on demand in a separate thread when a user configurable texture memory threshold is reached
This commit is contained in:
parent
1836db5be1
commit
6872f47277
|
@ -2,7 +2,6 @@
|
|||
#include "Renderer.h"
|
||||
#include "Window.h"
|
||||
#include "Util.h"
|
||||
#include "resources/SVGResource.h"
|
||||
|
||||
RatingComponent::RatingComponent(Window* window) : GuiComponent(window)
|
||||
{
|
||||
|
@ -45,16 +44,13 @@ void RatingComponent::onSizeChanged()
|
|||
else if(mSize.x() == 0)
|
||||
mSize[0] = mSize.y() * NUM_RATING_STARS;
|
||||
|
||||
auto filledSVG = dynamic_cast<SVGResource*>(mFilledTexture.get());
|
||||
auto unfilledSVG = dynamic_cast<SVGResource*>(mUnfilledTexture.get());
|
||||
|
||||
if(mSize.y() > 0)
|
||||
{
|
||||
size_t heightPx = (size_t)round(mSize.y());
|
||||
if(filledSVG)
|
||||
filledSVG->rasterizeAt(heightPx, heightPx);
|
||||
if(unfilledSVG)
|
||||
unfilledSVG->rasterizeAt(heightPx, heightPx);
|
||||
if (mFilledTexture)
|
||||
mFilledTexture->rasterizeAt(heightPx, heightPx);
|
||||
if(mUnfilledTexture)
|
||||
mUnfilledTexture->rasterizeAt(heightPx, heightPx);
|
||||
}
|
||||
|
||||
updateVertices();
|
||||
|
|
|
@ -178,6 +178,12 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN
|
|||
s->addWithLabel("PARSE GAMESLISTS ONLY", parse_gamelists);
|
||||
s->addSaveFunc([parse_gamelists] { Settings::getInstance()->setBool("ParseGamelistOnly", parse_gamelists->getState()); });
|
||||
|
||||
// maximum vram
|
||||
auto max_vram = std::make_shared<SliderComponent>(mWindow, 0.f, 1000.f, 10.f, "Mb");
|
||||
max_vram->setValue((float)(Settings::getInstance()->getInt("MaxVRAM")));
|
||||
s->addWithLabel("VRAM LIMIT", max_vram);
|
||||
s->addSaveFunc([max_vram] { Settings::getInstance()->setInt("MaxVRAM", (int)round(max_vram->getValue())); });
|
||||
|
||||
mWindow->pushGui(s);
|
||||
});
|
||||
|
||||
|
|
|
@ -74,6 +74,10 @@ bool parseArgs(int argc, char* argv[], unsigned int* width, unsigned int* height
|
|||
}else if(strcmp(argv[i], "--scrape") == 0)
|
||||
{
|
||||
scrape_cmdline = true;
|
||||
}else if(strcmp(argv[i], "--max-vram") == 0)
|
||||
{
|
||||
int maxVRAM = atoi(argv[i + 1]);
|
||||
Settings::getInstance()->setInt("MaxVRAM", maxVRAM);
|
||||
}else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
|
||||
{
|
||||
#ifdef WIN32
|
||||
|
@ -99,6 +103,7 @@ bool parseArgs(int argc, char* argv[], unsigned int* width, unsigned int* height
|
|||
"--scrape scrape using command line interface\n"
|
||||
"--windowed not fullscreen, should be used with --resolution\n"
|
||||
"--vsync [1/on or 0/off] turn vsync on or off (default is on)\n"
|
||||
"--max-vram [size] Max VRAM to use in Mb before swapping. 0 for unlimited\n"
|
||||
"--help, -h summon a sentient, angry tuba\n\n"
|
||||
"More information available in README.md.\n";
|
||||
return false; //exit after printing help
|
||||
|
|
|
@ -43,13 +43,13 @@ void SystemView::populate()
|
|||
// make logo
|
||||
if(theme->getElement("system", "logo", "image"))
|
||||
{
|
||||
ImageComponent* logo = new ImageComponent(mWindow);
|
||||
ImageComponent* logo = new ImageComponent(mWindow, false, false);
|
||||
logo->setMaxSize(Eigen::Vector2f(logoSize().x(), logoSize().y()));
|
||||
logo->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
|
||||
logo->setPosition((logoSize().x() - logo->getSize().x()) / 2, (logoSize().y() - logo->getSize().y()) / 2); // center
|
||||
e.data.logo = std::shared_ptr<GuiComponent>(logo);
|
||||
|
||||
ImageComponent* logoSelected = new ImageComponent(mWindow);
|
||||
ImageComponent* logoSelected = new ImageComponent(mWindow, false, false);
|
||||
logoSelected->setMaxSize(Eigen::Vector2f(logoSize().x() * SELECTED_SCALE, logoSize().y() * SELECTED_SCALE * 0.70f));
|
||||
logoSelected->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
|
||||
logoSelected->setPosition((logoSize().x() - logoSelected->getSize().x()) / 2,
|
||||
|
|
|
@ -406,7 +406,9 @@ void ViewController::reloadAll()
|
|||
mCurrentView = getGameListView(mState.getSystem());
|
||||
}else if(mState.viewing == SYSTEM_SELECT)
|
||||
{
|
||||
mSystemListView->goToSystem(mState.getSystem(), false);
|
||||
SystemData* system = mState.getSystem();
|
||||
goToSystemView(SystemData::sSystemVector.front());
|
||||
mSystemListView->goToSystem(system, false);
|
||||
mCurrentView = mSystemListView;
|
||||
}else{
|
||||
goToSystemView(SystemData::sSystemVector.front());
|
||||
|
|
|
@ -53,8 +53,9 @@ set(CORE_HEADERS
|
|||
# Resources
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/SVGResource.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.h
|
||||
|
||||
# Embedded assets (needed by ResourceManager)
|
||||
${emulationstation-all_SOURCE_DIR}/data/Resources.h
|
||||
|
@ -108,8 +109,9 @@ set(CORE_SOURCES
|
|||
# Resources
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/SVGResource.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.cpp
|
||||
)
|
||||
|
||||
set(EMBEDDED_ASSET_SOURCES
|
||||
|
|
|
@ -69,6 +69,7 @@ void Settings::setDefaults()
|
|||
mIntMap["ScreenSaverTime"] = 5*60*1000; // 5 minutes
|
||||
mIntMap["ScraperResizeWidth"] = 400;
|
||||
mIntMap["ScraperResizeHeight"] = 0;
|
||||
mIntMap["MaxVRAM"] = 100;
|
||||
|
||||
mStringMap["TransitionStyle"] = "fade";
|
||||
mStringMap["ThemeSet"] = "";
|
||||
|
@ -154,4 +155,4 @@ void Settings::setMethodName(const std::string& name, type value) \
|
|||
SETTINGS_GETSET(bool, mBoolMap, getBool, setBool);
|
||||
SETTINGS_GETSET(int, mIntMap, getInt, setInt);
|
||||
SETTINGS_GETSET(float, mFloatMap, getFloat, setFloat);
|
||||
SETTINGS_GETSET(const std::string&, mStringMap, getString, setString);
|
||||
SETTINGS_GETSET(const std::string&, mStringMap, getString, setString);
|
||||
|
|
|
@ -160,11 +160,12 @@ void Window::update(int deltaTime)
|
|||
ss << std::fixed << std::setprecision(2) << ((float)mFrameTimeElapsed / (float)mFrameCountElapsed) << "ms";
|
||||
|
||||
// vram
|
||||
float textureVramUsageMb = TextureResource::getTotalMemUsage() / 1000.0f / 1000.0f;;
|
||||
float textureVramUsageMb = TextureResource::getTotalMemUsage() / 1000.0f / 1000.0f;
|
||||
float textureTotalUsageMb = TextureResource::getTotalTextureSize() / 1000.0f / 1000.0f;
|
||||
float fontVramUsageMb = Font::getTotalMemUsage() / 1000.0f / 1000.0f;;
|
||||
float totalVramUsageMb = textureVramUsageMb + fontVramUsageMb;
|
||||
ss << "\nVRAM: " << totalVramUsageMb << "mb (texs: " << textureVramUsageMb << "mb, fonts: " << fontVramUsageMb << "mb)";
|
||||
|
||||
ss << "\nFont VRAM: " << fontVramUsageMb << " Tex VRAM: " << textureVramUsageMb <<
|
||||
" Tex Max: " << textureTotalUsageMb;
|
||||
mFrameDataText = std::unique_ptr<TextCache>(mDefaultFonts.at(1)->buildTextCache(ss.str(), 50.f, 50.f, 0xFF00FFFF));
|
||||
}
|
||||
|
||||
|
@ -242,7 +243,7 @@ void Window::renderLoadingScreen()
|
|||
Renderer::setMatrix(trans);
|
||||
Renderer::drawRect(0, 0, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0xFFFFFFFF);
|
||||
|
||||
ImageComponent splash(this);
|
||||
ImageComponent splash(this, true);
|
||||
splash.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f);
|
||||
splash.setImage(":/splash.svg");
|
||||
splash.setPosition((Renderer::getScreenWidth() - splash.getSize().x()) / 2, (Renderer::getScreenHeight() - splash.getSize().y()) / 2 * 0.6f);
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "Renderer.h"
|
||||
#include "ThemeData.h"
|
||||
#include "Util.h"
|
||||
#include "resources/SVGResource.h"
|
||||
|
||||
Eigen::Vector2i ImageComponent::getTextureSize() const
|
||||
{
|
||||
|
@ -22,8 +21,9 @@ Eigen::Vector2f ImageComponent::getCenter() const
|
|||
mPosition.y() - (getSize().y() * mOrigin.y()) + getSize().y() / 2);
|
||||
}
|
||||
|
||||
ImageComponent::ImageComponent(Window* window) : GuiComponent(window),
|
||||
mTargetIsMax(false), mFlipX(false), mFlipY(false), mOrigin(0.0, 0.0), mTargetSize(0, 0), mColorShift(0xFFFFFFFF)
|
||||
ImageComponent::ImageComponent(Window* window, bool forceLoad, bool dynamic) : GuiComponent(window),
|
||||
mTargetIsMax(false), mFlipX(false), mFlipY(false), mOrigin(0.0, 0.0), mTargetSize(0, 0), mColorShift(0xFFFFFFFF),
|
||||
mForceLoad(forceLoad), mDynamic(dynamic), mFadeOpacity(0.0f), mFading(false)
|
||||
{
|
||||
updateColors();
|
||||
}
|
||||
|
@ -37,9 +37,7 @@ void ImageComponent::resize()
|
|||
if(!mTexture)
|
||||
return;
|
||||
|
||||
SVGResource* svg = dynamic_cast<SVGResource*>(mTexture.get());
|
||||
|
||||
const Eigen::Vector2f textureSize = svg ? svg->getSourceImageSize() : Eigen::Vector2f((float)mTexture->getSize().x(), (float)mTexture->getSize().y());
|
||||
const Eigen::Vector2f textureSize = mTexture->getSourceImageSize();
|
||||
if(textureSize.isZero())
|
||||
return;
|
||||
|
||||
|
@ -90,12 +88,8 @@ void ImageComponent::resize()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(svg)
|
||||
{
|
||||
// mSize.y() should already be rounded
|
||||
svg->rasterizeAt((int)round(mSize.x()), (int)round(mSize.y()));
|
||||
}
|
||||
// mSize.y() should already be rounded
|
||||
mTexture->rasterizeAt((int)round(mSize.x()), (int)round(mSize.y()));
|
||||
|
||||
onSizeChanged();
|
||||
}
|
||||
|
@ -110,7 +104,7 @@ void ImageComponent::setImage(std::string path, bool tile)
|
|||
if(path.empty() || !ResourceManager::getInstance()->fileExists(path))
|
||||
mTexture.reset();
|
||||
else
|
||||
mTexture = TextureResource::get(path, tile);
|
||||
mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic);
|
||||
|
||||
resize();
|
||||
}
|
||||
|
@ -166,6 +160,9 @@ void ImageComponent::setFlipY(bool flip)
|
|||
void ImageComponent::setColorShift(unsigned int color)
|
||||
{
|
||||
mColorShift = color;
|
||||
// Grab the opacity from the color shift because we may need to apply it if
|
||||
// fading textures in
|
||||
mOpacity = color & 0xff;
|
||||
updateColors();
|
||||
}
|
||||
|
||||
|
@ -247,7 +244,10 @@ void ImageComponent::render(const Eigen::Affine3f& parentTrans)
|
|||
if(mTexture->isInitialized())
|
||||
{
|
||||
// actually draw the image
|
||||
mTexture->bind();
|
||||
// The bind() function returns false if the texture is not currently loaded. A blank
|
||||
// texture is bound in this case but we want to handle a fade so it doesn't just 'jump' in
|
||||
// when it finally loads
|
||||
fadeIn(mTexture->bind());
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glEnable(GL_BLEND);
|
||||
|
@ -278,6 +278,47 @@ void ImageComponent::render(const Eigen::Affine3f& parentTrans)
|
|||
GuiComponent::renderChildren(trans);
|
||||
}
|
||||
|
||||
void ImageComponent::fadeIn(bool textureLoaded)
|
||||
{
|
||||
if (!mForceLoad)
|
||||
{
|
||||
if (!textureLoaded)
|
||||
{
|
||||
// Start the fade if this is the first time we've encountered the unloaded texture
|
||||
if (!mFading)
|
||||
{
|
||||
// Start with a zero opacity and flag it as fading
|
||||
mFadeOpacity = 0;
|
||||
mFading = true;
|
||||
// Set the colours to be translucent
|
||||
mColorShift = (mColorShift >> 8 << 8) | 0;
|
||||
updateColors();
|
||||
}
|
||||
}
|
||||
else if (mFading)
|
||||
{
|
||||
// The texture is loaded and we need to fade it in. The fade is based on the frame rate
|
||||
// and is 1/4 second if running at 60 frames per second although the actual value is not
|
||||
// that important
|
||||
int opacity = mFadeOpacity + 255 / 15;
|
||||
// See if we've finished fading
|
||||
if (opacity >= 255)
|
||||
{
|
||||
mFadeOpacity = 255;
|
||||
mFading = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
mFadeOpacity = (unsigned char)opacity;
|
||||
}
|
||||
// Apply the combination of the target opacity and current fade
|
||||
float newOpacity = (float)mOpacity * ((float)mFadeOpacity / 255.0f);
|
||||
mColorShift = (mColorShift >> 8 << 8) | (unsigned char)newOpacity;
|
||||
updateColors();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ImageComponent::hasImage()
|
||||
{
|
||||
return (bool)mTexture;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
class ImageComponent : public GuiComponent
|
||||
{
|
||||
public:
|
||||
ImageComponent(Window* window);
|
||||
ImageComponent(Window* window, bool forceLoad = false, bool dynamic = true);
|
||||
virtual ~ImageComponent();
|
||||
|
||||
//Loads the image at the given filepath. Will tile if tile is true (retrieves texture as tiling, creates vertices accordingly).
|
||||
|
@ -81,10 +81,15 @@ private:
|
|||
|
||||
void updateVertices();
|
||||
void updateColors();
|
||||
void fadeIn(bool textureLoaded);
|
||||
|
||||
unsigned int mColorShift;
|
||||
|
||||
std::shared_ptr<TextureResource> mTexture;
|
||||
unsigned char mFadeOpacity;
|
||||
bool mFading;
|
||||
bool mForceLoad;
|
||||
bool mDynamic;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
#include "SVGResource.h"
|
||||
#include "nanosvg/nanosvg.h"
|
||||
#include "nanosvg/nanosvgrast.h"
|
||||
#include "Log.h"
|
||||
#include "Util.h"
|
||||
#include "ImageIO.h"
|
||||
|
||||
#define DPI 96
|
||||
|
||||
SVGResource::SVGResource(const std::string& path, bool tile) : TextureResource(path, tile), mSVGImage(NULL)
|
||||
{
|
||||
mLastWidth = 0;
|
||||
mLastHeight = 0;
|
||||
}
|
||||
|
||||
SVGResource::~SVGResource()
|
||||
{
|
||||
deinitSVG();
|
||||
}
|
||||
|
||||
void SVGResource::unload(std::shared_ptr<ResourceManager>& rm)
|
||||
{
|
||||
deinitSVG();
|
||||
TextureResource::unload(rm);
|
||||
}
|
||||
|
||||
void SVGResource::initFromMemory(const char* file, size_t length)
|
||||
{
|
||||
deinit();
|
||||
deinitSVG();
|
||||
|
||||
// nsvgParse excepts a modifiable, null-terminated string
|
||||
char* copy = (char*)malloc(length + 1);
|
||||
assert(copy != NULL);
|
||||
memcpy(copy, file, length);
|
||||
copy[length] = '\0';
|
||||
|
||||
mSVGImage = nsvgParse(copy, "px", DPI);
|
||||
free(copy);
|
||||
|
||||
if(!mSVGImage)
|
||||
{
|
||||
LOG(LogError) << "Error parsing SVG image.";
|
||||
return;
|
||||
}
|
||||
|
||||
if(mLastWidth && mLastHeight)
|
||||
rasterizeAt(mLastWidth, mLastHeight);
|
||||
else
|
||||
rasterizeAt((size_t)round(mSVGImage->width), (size_t)round(mSVGImage->height));
|
||||
}
|
||||
|
||||
void SVGResource::rasterizeAt(size_t width, size_t height)
|
||||
{
|
||||
if(!mSVGImage || (width == 0 && height == 0))
|
||||
return;
|
||||
|
||||
if(width == 0)
|
||||
{
|
||||
// auto scale width to keep aspect
|
||||
width = (size_t)round((height / mSVGImage->height) * mSVGImage->width);
|
||||
}else if(height == 0)
|
||||
{
|
||||
// auto scale height to keep aspect
|
||||
height = (size_t)round((width / mSVGImage->width) * mSVGImage->height);
|
||||
}
|
||||
|
||||
if(width != (size_t)round(mSVGImage->width) && height != (size_t)round(mSVGImage->height))
|
||||
{
|
||||
mLastWidth = width;
|
||||
mLastHeight = height;
|
||||
}
|
||||
|
||||
unsigned char* imagePx = (unsigned char*)malloc(width * height * 4);
|
||||
assert(imagePx != NULL);
|
||||
|
||||
NSVGrasterizer* rast = nsvgCreateRasterizer();
|
||||
nsvgRasterize(rast, mSVGImage, 0, 0, height / mSVGImage->height, imagePx, width, height, width * 4);
|
||||
nsvgDeleteRasterizer(rast);
|
||||
|
||||
ImageIO::flipPixelsVert(imagePx, width, height);
|
||||
|
||||
initFromPixels(imagePx, width, height);
|
||||
free(imagePx);
|
||||
}
|
||||
|
||||
Eigen::Vector2f SVGResource::getSourceImageSize() const
|
||||
{
|
||||
if(mSVGImage)
|
||||
return Eigen::Vector2f(mSVGImage->width, mSVGImage->height);
|
||||
|
||||
return Eigen::Vector2f::Zero();
|
||||
}
|
||||
|
||||
void SVGResource::deinitSVG()
|
||||
{
|
||||
if(mSVGImage)
|
||||
nsvgDelete(mSVGImage);
|
||||
|
||||
mSVGImage = NULL;
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "resources/TextureResource.h"
|
||||
|
||||
struct NSVGimage;
|
||||
|
||||
class SVGResource : public TextureResource
|
||||
{
|
||||
public:
|
||||
virtual ~SVGResource();
|
||||
|
||||
virtual void unload(std::shared_ptr<ResourceManager>& rm) override;
|
||||
|
||||
virtual void initFromMemory(const char* image, size_t length) override;
|
||||
|
||||
void rasterizeAt(size_t width, size_t height);
|
||||
Eigen::Vector2f getSourceImageSize() const;
|
||||
|
||||
protected:
|
||||
friend TextureResource;
|
||||
SVGResource(const std::string& path, bool tile);
|
||||
void deinitSVG();
|
||||
|
||||
NSVGimage* mSVGImage;
|
||||
size_t mLastWidth;
|
||||
size_t mLastHeight;
|
||||
};
|
259
es-core/src/resources/TextureData.cpp
Normal file
259
es-core/src/resources/TextureData.cpp
Normal file
|
@ -0,0 +1,259 @@
|
|||
#include "resources/TextureData.h"
|
||||
#include "resources/ResourceManager.h"
|
||||
#include "Log.h"
|
||||
#include "ImageIO.h"
|
||||
#include "string.h"
|
||||
#include "Util.h"
|
||||
#include "nanosvg/nanosvg.h"
|
||||
#include "nanosvg/nanosvgrast.h"
|
||||
#include <vector>
|
||||
|
||||
#define DPI 96
|
||||
|
||||
TextureData::TextureData(bool tile) : mTile(tile), mTextureID(0), mDataRGBA(nullptr), mScalable(false),
|
||||
mWidth(0), mHeight(0), mSourceWidth(0.0f), mSourceHeight(0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
TextureData::~TextureData()
|
||||
{
|
||||
releaseVRAM();
|
||||
releaseRAM();
|
||||
}
|
||||
|
||||
void TextureData::initFromPath(const std::string& path)
|
||||
{
|
||||
// Just set the path. It will be loaded later
|
||||
mPath = path;
|
||||
// Only textures with paths are reloadable
|
||||
mReloadable = true;
|
||||
}
|
||||
|
||||
bool TextureData::initSVGFromMemory(const unsigned char* fileData, size_t length)
|
||||
{
|
||||
// If already initialised then don't read again
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mDataRGBA)
|
||||
return true;
|
||||
}
|
||||
|
||||
// nsvgParse excepts a modifiable, null-terminated string
|
||||
char* copy = (char*)malloc(length + 1);
|
||||
assert(copy != NULL);
|
||||
memcpy(copy, fileData, length);
|
||||
copy[length] = '\0';
|
||||
|
||||
NSVGimage* svgImage = nsvgParse(copy, "px", DPI);
|
||||
free(copy);
|
||||
if (!svgImage)
|
||||
{
|
||||
LOG(LogError) << "Error parsing SVG image.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// We want to rasterise this texture at a specific resolution. If the source size
|
||||
// variables are set then use them otherwise set them from the parsed file
|
||||
if ((mSourceWidth == 0.0f) && (mSourceHeight == 0.0f))
|
||||
{
|
||||
mSourceWidth = svgImage->width;
|
||||
mSourceHeight = svgImage->height;
|
||||
}
|
||||
mWidth = (size_t)round(mSourceWidth);
|
||||
mHeight = (size_t)round(mSourceHeight);
|
||||
|
||||
if (mWidth == 0)
|
||||
{
|
||||
// auto scale width to keep aspect
|
||||
mWidth = (size_t)round(((float)mHeight / svgImage->height) * svgImage->width);
|
||||
}
|
||||
else if (mHeight == 0)
|
||||
{
|
||||
// auto scale height to keep aspect
|
||||
mHeight = (size_t)round(((float)mWidth / svgImage->width) * svgImage->height);
|
||||
}
|
||||
|
||||
unsigned char* dataRGBA = new unsigned char[mWidth * mHeight * 4];
|
||||
|
||||
NSVGrasterizer* rast = nsvgCreateRasterizer();
|
||||
nsvgRasterize(rast, svgImage, 0, 0, mHeight / svgImage->height, dataRGBA, mWidth, mHeight, mWidth * 4);
|
||||
nsvgDeleteRasterizer(rast);
|
||||
|
||||
ImageIO::flipPixelsVert(dataRGBA, mWidth, mHeight);
|
||||
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mDataRGBA = dataRGBA;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureData::initImageFromMemory(const unsigned char* fileData, size_t length)
|
||||
{
|
||||
size_t width, height;
|
||||
|
||||
// If already initialised then don't read again
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mDataRGBA)
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> imageRGBA = ImageIO::loadFromMemoryRGBA32((const unsigned char*)(fileData), length, width, height);
|
||||
if (imageRGBA.size() == 0)
|
||||
{
|
||||
LOG(LogError) << "Could not initialize texture from memory, invalid data! (file path: " << mPath << ", data ptr: " << (size_t)fileData << ", reported size: " << length << ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
mSourceWidth = width;
|
||||
mSourceHeight = height;
|
||||
mScalable = false;
|
||||
|
||||
return initFromRGBA(imageRGBA.data(), width, height);
|
||||
}
|
||||
|
||||
bool TextureData::initFromRGBA(const unsigned char* dataRGBA, size_t width, size_t height)
|
||||
{
|
||||
// If already initialised then don't read again
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mDataRGBA)
|
||||
return true;
|
||||
|
||||
// Take a copy
|
||||
mDataRGBA = new unsigned char[width * height * 4];
|
||||
memcpy(mDataRGBA, dataRGBA, width * height * 4);
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureData::load()
|
||||
{
|
||||
bool retval = false;
|
||||
|
||||
// Need to load. See if there is a file
|
||||
if (!mPath.empty())
|
||||
{
|
||||
std::shared_ptr<ResourceManager>& rm = ResourceManager::getInstance();
|
||||
const ResourceData& data = rm->getFileData(mPath);
|
||||
// is it an SVG?
|
||||
if (mPath.substr(mPath.size() - 4, std::string::npos) == ".svg")
|
||||
{
|
||||
mScalable = true;
|
||||
retval = initSVGFromMemory((const unsigned char*)data.ptr.get(), data.length);
|
||||
}
|
||||
else
|
||||
retval = initImageFromMemory((const unsigned char*)data.ptr.get(), data.length);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool TextureData::isLoaded()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mDataRGBA || (mTextureID != 0))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextureData::uploadAndBind()
|
||||
{
|
||||
// See if it's already been uploaded
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mTextureID != 0)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, mTextureID);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load it if necessary
|
||||
if (!mDataRGBA)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Make sure we're ready to upload
|
||||
if ((mWidth == 0) || (mHeight == 0) || (mDataRGBA == nullptr))
|
||||
return false;
|
||||
glGetError();
|
||||
//now for the openGL texture stuff
|
||||
glGenTextures(1, &mTextureID);
|
||||
glBindTexture(GL_TEXTURE_2D, mTextureID);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, mDataRGBA);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
const GLint wrapMode = mTile ? GL_REPEAT : GL_CLAMP_TO_EDGE;
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextureData::releaseVRAM()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mTextureID != 0)
|
||||
{
|
||||
glDeleteTextures(1, &mTextureID);
|
||||
mTextureID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TextureData::releaseRAM()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
delete[] mDataRGBA;
|
||||
mDataRGBA = 0;
|
||||
}
|
||||
|
||||
size_t TextureData::width()
|
||||
{
|
||||
if (mWidth == 0)
|
||||
load();
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
size_t TextureData::height()
|
||||
{
|
||||
if (mHeight == 0)
|
||||
load();
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
float TextureData::sourceWidth()
|
||||
{
|
||||
if (mSourceWidth == 0)
|
||||
load();
|
||||
return mSourceWidth;
|
||||
}
|
||||
|
||||
float TextureData::sourceHeight()
|
||||
{
|
||||
if (mSourceHeight == 0)
|
||||
load();
|
||||
return mSourceHeight;
|
||||
}
|
||||
|
||||
void TextureData::setSourceSize(float width, float height)
|
||||
{
|
||||
if (mScalable)
|
||||
{
|
||||
if ((mSourceWidth != width) || (mSourceHeight != height))
|
||||
{
|
||||
mSourceWidth = width;
|
||||
mSourceHeight = height;
|
||||
releaseVRAM();
|
||||
releaseRAM();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t TextureData::getVRAMUsage()
|
||||
{
|
||||
if ((mTextureID != 0) || (mDataRGBA != nullptr))
|
||||
return mWidth * mHeight * 4;
|
||||
else
|
||||
return 0;
|
||||
}
|
63
es-core/src/resources/TextureData.h
Normal file
63
es-core/src/resources/TextureData.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "platform.h"
|
||||
#include <mutex>
|
||||
#include GLHEADER
|
||||
|
||||
class TextureResource;
|
||||
|
||||
class TextureData
|
||||
{
|
||||
public:
|
||||
TextureData(bool tile);
|
||||
~TextureData();
|
||||
|
||||
// These functions populate mDataRGBA but do not upload the texture to VRAM
|
||||
|
||||
//!!!! Needs to be canonical path. Caller should check for duplicates before calling this
|
||||
void initFromPath(const std::string& path);
|
||||
bool initSVGFromMemory(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);
|
||||
|
||||
// Read the data into memory if necessary
|
||||
bool load();
|
||||
|
||||
bool isLoaded();
|
||||
|
||||
// Upload the texture to VRAM if necessary and bind. Returns true if bound ok or
|
||||
// false if either not loaded
|
||||
bool uploadAndBind();
|
||||
|
||||
// Release the texture from VRAM
|
||||
void releaseVRAM();
|
||||
|
||||
// Release the texture from conventional RAM
|
||||
void releaseRAM();
|
||||
|
||||
// Get the amount of VRAM currenty used by this texture
|
||||
size_t getVRAMUsage();
|
||||
|
||||
size_t width();
|
||||
size_t height();
|
||||
float sourceWidth();
|
||||
float sourceHeight();
|
||||
void setSourceSize(float width, float height);
|
||||
|
||||
bool tiled() { return mTile; }
|
||||
|
||||
private:
|
||||
std::mutex mMutex;
|
||||
bool mTile;
|
||||
std::string mPath;
|
||||
GLuint mTextureID;
|
||||
unsigned char* mDataRGBA;
|
||||
size_t mWidth;
|
||||
size_t mHeight;
|
||||
float mSourceWidth;
|
||||
float mSourceHeight;
|
||||
bool mScalable;
|
||||
bool mReloadable;
|
||||
};
|
226
es-core/src/resources/TextureDataManager.cpp
Normal file
226
es-core/src/resources/TextureDataManager.cpp
Normal file
|
@ -0,0 +1,226 @@
|
|||
#include "resources/TextureDataManager.h"
|
||||
#include "resources/TextureResource.h"
|
||||
#include "Settings.h"
|
||||
|
||||
TextureDataManager::TextureDataManager()
|
||||
{
|
||||
unsigned char data[5 * 5 * 4];
|
||||
mBlank = std::shared_ptr<TextureData>(new TextureData(false));
|
||||
for (int i = 0; i < (5 * 5); ++i)
|
||||
{
|
||||
data[i*4] = (i % 2) * 255;
|
||||
data[i*4+1] = (i % 2) * 255;
|
||||
data[i*4+2] = (i % 2) * 255;
|
||||
data[i*4+3] = 0;
|
||||
}
|
||||
mBlank->initFromRGBA(data, 5, 5);
|
||||
mLoader = new TextureLoader;
|
||||
}
|
||||
|
||||
TextureDataManager::~TextureDataManager()
|
||||
{
|
||||
delete mLoader;
|
||||
}
|
||||
|
||||
std::shared_ptr<TextureData> TextureDataManager::add(const TextureResource* key, bool tiled)
|
||||
{
|
||||
remove(key);
|
||||
std::shared_ptr<TextureData> data(new TextureData(tiled));
|
||||
mTextures.push_front(data);
|
||||
mTextureLookup[key] = mTextures.begin();
|
||||
return data;
|
||||
}
|
||||
|
||||
void TextureDataManager::remove(const TextureResource* key)
|
||||
{
|
||||
// Find the entry in the list
|
||||
auto it = mTextureLookup.find(key);
|
||||
if (it != mTextureLookup.end())
|
||||
{
|
||||
// Remove the list entry
|
||||
mTextures.erase((*it).second);
|
||||
// And the lookup
|
||||
mTextureLookup.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<TextureData> TextureDataManager::get(const TextureResource* key)
|
||||
{
|
||||
// If it's in the cache then we want to remove it from it's current location and
|
||||
// move it to the top
|
||||
std::shared_ptr<TextureData> tex;
|
||||
auto it = mTextureLookup.find(key);
|
||||
if (it != mTextureLookup.end())
|
||||
{
|
||||
tex = *(*it).second;
|
||||
// Remove the list entry
|
||||
mTextures.erase((*it).second);
|
||||
// Put it at the top
|
||||
mTextures.push_front(tex);
|
||||
// Store it back in the lookup
|
||||
mTextureLookup[key] = mTextures.begin();
|
||||
|
||||
// Make sure it's loaded or queued for loading
|
||||
load(tex);
|
||||
}
|
||||
return tex;
|
||||
}
|
||||
|
||||
bool TextureDataManager::bind(const TextureResource* key)
|
||||
{
|
||||
std::shared_ptr<TextureData> tex = get(key);
|
||||
bool bound = false;
|
||||
if (tex != nullptr)
|
||||
bound = tex->uploadAndBind();
|
||||
if (!bound)
|
||||
mBlank->uploadAndBind();
|
||||
return bound;
|
||||
}
|
||||
|
||||
size_t TextureDataManager::getTotalSize()
|
||||
{
|
||||
size_t total = 0;
|
||||
for (auto tex : mTextures)
|
||||
total += tex->width() * tex->height() * 4;
|
||||
return total;
|
||||
}
|
||||
|
||||
size_t TextureDataManager::getCommittedSize()
|
||||
{
|
||||
size_t total = 0;
|
||||
for (auto tex : mTextures)
|
||||
total += tex->getVRAMUsage();
|
||||
return total;
|
||||
}
|
||||
|
||||
size_t TextureDataManager::getQueueSize()
|
||||
{
|
||||
return mLoader->getQueueSize();
|
||||
}
|
||||
|
||||
void TextureDataManager::load(std::shared_ptr<TextureData> tex, bool block)
|
||||
{
|
||||
// See if it's already loaded
|
||||
if (tex->isLoaded())
|
||||
return;
|
||||
// Not loaded. Make sure there is room
|
||||
size_t size = TextureResource::getTotalMemUsage();
|
||||
size_t max_texture = (size_t)Settings::getInstance()->getInt("MaxVRAM") * 1024 * 1024;
|
||||
|
||||
size_t in = size;
|
||||
|
||||
for (auto it = mTextures.rbegin(); it != mTextures.rend(); ++it)
|
||||
{
|
||||
if (size < max_texture)
|
||||
break;
|
||||
//size -= (*it)->getVRAMUsage();
|
||||
(*it)->releaseVRAM();
|
||||
(*it)->releaseRAM();
|
||||
// It may be already in the loader queue. In this case it wouldn't have been using
|
||||
// any VRAM yet but it will be. Remove it from the loader queue
|
||||
mLoader->remove(*it);
|
||||
size = TextureResource::getTotalMemUsage();
|
||||
}
|
||||
if (!block)
|
||||
mLoader->load(tex);
|
||||
else
|
||||
tex->load();
|
||||
}
|
||||
|
||||
TextureLoader::TextureLoader() : mExit(false)
|
||||
{
|
||||
mThread = new std::thread(&TextureLoader::threadProc, this);
|
||||
}
|
||||
|
||||
TextureLoader::~TextureLoader()
|
||||
{
|
||||
// Just abort any waiting texture
|
||||
mTextureDataQ.clear();
|
||||
mTextureDataLookup.clear();
|
||||
|
||||
// Exit the thread
|
||||
mExit = true;
|
||||
mEvent.notify_one();
|
||||
mThread->join();
|
||||
delete mThread;
|
||||
}
|
||||
|
||||
void TextureLoader::threadProc()
|
||||
{
|
||||
while (!mExit)
|
||||
{
|
||||
std::shared_ptr<TextureData> textureData;
|
||||
{
|
||||
// Wait for an event to say there is something in the queue
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mEvent.wait(lock);
|
||||
if (!mTextureDataQ.empty())
|
||||
{
|
||||
textureData = mTextureDataQ.front();
|
||||
mTextureDataQ.pop_front();
|
||||
mTextureDataLookup.erase(mTextureDataLookup.find(textureData.get()));
|
||||
}
|
||||
}
|
||||
// Queue has been released here but we might have a texture to process
|
||||
while (textureData)
|
||||
{
|
||||
textureData->load();
|
||||
|
||||
// See if there is another item in the queue
|
||||
textureData = nullptr;
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (!mTextureDataQ.empty())
|
||||
{
|
||||
textureData = mTextureDataQ.front();
|
||||
mTextureDataQ.pop_front();
|
||||
mTextureDataLookup.erase(mTextureDataLookup.find(textureData.get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextureLoader::load(std::shared_ptr<TextureData> textureData)
|
||||
{
|
||||
// Make sure it's not already loaded
|
||||
if (!textureData->isLoaded())
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
// Remove it from the queue if it is already there
|
||||
auto td = mTextureDataLookup.find(textureData.get());
|
||||
if (td != mTextureDataLookup.end())
|
||||
{
|
||||
mTextureDataQ.erase((*td).second);
|
||||
mTextureDataLookup.erase(td);
|
||||
}
|
||||
|
||||
// Put it on the start of the queue as we want the newly requested textures to load first
|
||||
mTextureDataQ.push_front(textureData);
|
||||
mTextureDataLookup[textureData.get()] = mTextureDataQ.begin();
|
||||
mEvent.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void TextureLoader::remove(std::shared_ptr<TextureData> textureData)
|
||||
{
|
||||
// Just remove it from the queue so we don't attempt to load it
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
auto td = mTextureDataLookup.find(textureData.get());
|
||||
if (td != mTextureDataLookup.end())
|
||||
{
|
||||
mTextureDataQ.erase((*td).second);
|
||||
mTextureDataLookup.erase(td);
|
||||
}
|
||||
}
|
||||
|
||||
size_t TextureLoader::getQueueSize()
|
||||
{
|
||||
// Gets the amount of video memory that will be used once all textures in
|
||||
// the queue are loaded
|
||||
size_t mem = 0;
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
for (auto tex : mTextureDataQ)
|
||||
{
|
||||
mem += tex->width() * tex->height() * 4;
|
||||
}
|
||||
return mem;
|
||||
}
|
86
es-core/src/resources/TextureDataManager.h
Normal file
86
es-core/src/resources/TextureDataManager.h
Normal file
|
@ -0,0 +1,86 @@
|
|||
#pragma once
|
||||
|
||||
#include "resources/ResourceManager.h"
|
||||
#include "platform.h"
|
||||
#include "resources/TextureData.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
|
||||
class TextureResource;
|
||||
|
||||
class TextureLoader
|
||||
{
|
||||
public:
|
||||
TextureLoader();
|
||||
~TextureLoader();
|
||||
|
||||
void load(std::shared_ptr<TextureData> textureData);
|
||||
void remove(std::shared_ptr<TextureData> textureData);
|
||||
|
||||
size_t getQueueSize();
|
||||
|
||||
private:
|
||||
void processQueue();
|
||||
void threadProc();
|
||||
|
||||
std::list<std::shared_ptr<TextureData> > mTextureDataQ;
|
||||
std::map<TextureData*, std::list<std::shared_ptr<TextureData> >::iterator > mTextureDataLookup;
|
||||
|
||||
std::thread* mThread;
|
||||
std::mutex mMutex;
|
||||
std::condition_variable mEvent;
|
||||
bool mExit;
|
||||
};
|
||||
|
||||
//
|
||||
// This class manages the loading and unloading of textures
|
||||
//
|
||||
// When textures are added, the texture data is just stored as-is. The texture
|
||||
// data should only have been constructed and not loaded for this to work correctly.
|
||||
// When the get() function is called it indicates that a texture wants to be used so
|
||||
// at this point the texture data is loaded (via a call to load()).
|
||||
//
|
||||
// Once the load is complete (which may not be on the first call to get() if the
|
||||
// data is loaded in a background thread) then the get() function call uploadAndBind()
|
||||
// to upload to VRAM if necessary and bind the texture. This is followed by a call
|
||||
// to releaseRAM() which frees the memory buffer if the texture can be reloaded from
|
||||
// disk if needed again
|
||||
//
|
||||
class TextureDataManager
|
||||
{
|
||||
public:
|
||||
TextureDataManager();
|
||||
~TextureDataManager();
|
||||
|
||||
std::shared_ptr<TextureData> add(const TextureResource* key, bool tiled);
|
||||
|
||||
// The texturedata being removed may be loading in a different thread. However it will
|
||||
// be referenced by a smart point so we only need to remove it from our array and it
|
||||
// will be deleted when the other thread has finished with it
|
||||
void remove(const TextureResource* key);
|
||||
|
||||
std::shared_ptr<TextureData> get(const TextureResource* key);
|
||||
bool bind(const TextureResource* key);
|
||||
|
||||
// Get the total size of all textures managed by this object, loaded and unloaded in bytes
|
||||
size_t getTotalSize();
|
||||
// Get the total size of all committed textures (in VRAM) in bytes
|
||||
size_t getCommittedSize();
|
||||
// Get the total size of all load-pending textures in the queue - these will
|
||||
// be committed to VRAM as the queue is processed
|
||||
size_t getQueueSize();
|
||||
// Load a texture, freeing resources as necessary to make space
|
||||
void load(std::shared_ptr<TextureData> tex, bool block = false);
|
||||
|
||||
private:
|
||||
|
||||
std::list<std::shared_ptr<TextureData> > mTextures;
|
||||
std::map<const TextureResource*, std::list<std::shared_ptr<TextureData> >::iterator > mTextureLookup;
|
||||
std::shared_ptr<TextureData> mBlank;
|
||||
TextureLoader* mLoader;
|
||||
};
|
||||
|
|
@ -5,108 +5,113 @@
|
|||
#include "ImageIO.h"
|
||||
#include "Renderer.h"
|
||||
#include "Util.h"
|
||||
#include "resources/SVGResource.h"
|
||||
#include "Settings.h"
|
||||
|
||||
TextureDataManager TextureResource::sTextureDataManager;
|
||||
std::map< TextureResource::TextureKeyType, std::weak_ptr<TextureResource> > TextureResource::sTextureMap;
|
||||
std::list< std::weak_ptr<TextureResource> > TextureResource::sTextureList;
|
||||
std::set<TextureResource*> TextureResource::sAllTextures;
|
||||
|
||||
TextureResource::TextureResource(const std::string& path, bool tile) :
|
||||
mTextureID(0), mPath(path), mTextureSize(Eigen::Vector2i::Zero()), mTile(tile)
|
||||
TextureResource::TextureResource(const std::string& path, bool tile, bool dynamic) : mTextureData(nullptr), mForceLoad(false)
|
||||
{
|
||||
// Create a texture data object for this texture
|
||||
if (!path.empty())
|
||||
{
|
||||
// If there is a path then the 'dynamic' flag tells us whether to use the texture
|
||||
// data manager to manage loading/unloading of this texture
|
||||
std::shared_ptr<TextureData> data;
|
||||
if (dynamic)
|
||||
{
|
||||
data = sTextureDataManager.add(this, tile);
|
||||
data->initFromPath(path);
|
||||
// Force the texture manager to load it using a blocking load
|
||||
sTextureDataManager.load(data, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
mTextureData = std::shared_ptr<TextureData>(new TextureData(tile));
|
||||
data = mTextureData;
|
||||
data->initFromPath(path);
|
||||
// Load it so we can read the width/height
|
||||
data->load();
|
||||
}
|
||||
|
||||
mSize << data->width(), data->height();
|
||||
mSourceSize << data->sourceWidth(), data->sourceHeight();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a texture managed by this class because it cannot be dynamically loaded and unloaded
|
||||
mTextureData = std::shared_ptr<TextureData>(new TextureData(tile));
|
||||
}
|
||||
sAllTextures.insert(this);
|
||||
}
|
||||
|
||||
TextureResource::~TextureResource()
|
||||
{
|
||||
deinit();
|
||||
}
|
||||
if (mTextureData == nullptr)
|
||||
sTextureDataManager.remove(this);
|
||||
|
||||
void TextureResource::unload(std::shared_ptr<ResourceManager>& rm)
|
||||
{
|
||||
deinit();
|
||||
}
|
||||
|
||||
void TextureResource::reload(std::shared_ptr<ResourceManager>& rm)
|
||||
{
|
||||
if(!mPath.empty())
|
||||
{
|
||||
const ResourceData& data = rm->getFileData(mPath);
|
||||
initFromMemory((const char*)data.ptr.get(), data.length);
|
||||
}
|
||||
sAllTextures.erase(sAllTextures.find(this));
|
||||
}
|
||||
|
||||
void TextureResource::initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height)
|
||||
{
|
||||
deinit();
|
||||
|
||||
assert(width > 0 && height > 0);
|
||||
|
||||
//now for the openGL texture stuff
|
||||
glGenTextures(1, &mTextureID);
|
||||
glBindTexture(GL_TEXTURE_2D, mTextureID);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, dataRGBA);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
const GLint wrapMode = mTile ? GL_REPEAT : GL_CLAMP_TO_EDGE;
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
|
||||
|
||||
mTextureSize << width, height;
|
||||
// This is only valid if we have a local texture data object
|
||||
assert(mTextureData != nullptr);
|
||||
mTextureData->releaseVRAM();
|
||||
mTextureData->releaseRAM();
|
||||
mTextureData->initFromRGBA(dataRGBA, width, height);
|
||||
// Cache the image dimensions
|
||||
mSize << width, height;
|
||||
mSourceSize << mTextureData->sourceWidth(), mTextureData->sourceHeight();
|
||||
}
|
||||
|
||||
void TextureResource::initFromMemory(const char* data, size_t length)
|
||||
{
|
||||
size_t width, height;
|
||||
std::vector<unsigned char> imageRGBA = ImageIO::loadFromMemoryRGBA32((const unsigned char*)(data), length, width, height);
|
||||
|
||||
if(imageRGBA.size() == 0)
|
||||
{
|
||||
LOG(LogError) << "Could not initialize texture from memory, invalid data! (file path: " << mPath << ", data ptr: " << (size_t)data << ", reported size: " << length << ")";
|
||||
return;
|
||||
}
|
||||
|
||||
initFromPixels(imageRGBA.data(), width, height);
|
||||
// This is only valid if we have a local texture data object
|
||||
assert(mTextureData != nullptr);
|
||||
mTextureData->releaseVRAM();
|
||||
mTextureData->releaseRAM();
|
||||
mTextureData->initImageFromMemory((const unsigned char*)data, length);
|
||||
// Get the size from the texture data
|
||||
mSize << mTextureData->width(), mTextureData->height();
|
||||
mSourceSize << mTextureData->sourceWidth(), mTextureData->sourceHeight();
|
||||
}
|
||||
|
||||
void TextureResource::deinit()
|
||||
const Eigen::Vector2i TextureResource::getSize() const
|
||||
{
|
||||
if(mTextureID != 0)
|
||||
{
|
||||
glDeleteTextures(1, &mTextureID);
|
||||
mTextureID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const Eigen::Vector2i& TextureResource::getSize() const
|
||||
{
|
||||
return mTextureSize;
|
||||
return mSize;
|
||||
}
|
||||
|
||||
bool TextureResource::isTiled() const
|
||||
{
|
||||
return mTile;
|
||||
if (mTextureData != nullptr)
|
||||
return mTextureData->tiled();
|
||||
std::shared_ptr<TextureData> data = sTextureDataManager.get(this);
|
||||
return data->tiled();
|
||||
}
|
||||
|
||||
void TextureResource::bind() const
|
||||
bool TextureResource::bind()
|
||||
{
|
||||
if(mTextureID != 0)
|
||||
glBindTexture(GL_TEXTURE_2D, mTextureID);
|
||||
if (mTextureData != nullptr)
|
||||
{
|
||||
mTextureData->uploadAndBind();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
LOG(LogError) << "Tried to bind uninitialized texture!";
|
||||
{
|
||||
return sTextureDataManager.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<TextureResource> TextureResource::get(const std::string& path, bool tile)
|
||||
std::shared_ptr<TextureResource> TextureResource::get(const std::string& path, bool tile, bool forceLoad, bool dynamic)
|
||||
{
|
||||
std::shared_ptr<ResourceManager>& rm = ResourceManager::getInstance();
|
||||
|
||||
const std::string canonicalPath = getCanonicalPath(path);
|
||||
|
||||
if(canonicalPath.empty())
|
||||
{
|
||||
std::shared_ptr<TextureResource> tex(new TextureResource("", tile));
|
||||
std::shared_ptr<TextureResource> tex(new TextureResource("", tile, false));
|
||||
rm->addReloadable(tex); //make sure we get properly deinitialized even though we do nothing on reinitialization
|
||||
return tex;
|
||||
}
|
||||
|
@ -121,58 +126,100 @@ std::shared_ptr<TextureResource> TextureResource::get(const std::string& path, b
|
|||
|
||||
// need to create it
|
||||
std::shared_ptr<TextureResource> tex;
|
||||
tex = std::shared_ptr<TextureResource>(new TextureResource(key.first, tile, dynamic));
|
||||
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(key.first.substr(key.first.size() - 4, std::string::npos) != ".svg")
|
||||
{
|
||||
// probably
|
||||
// don't add it to our map because 2 svgs might be rasterized at different sizes
|
||||
tex = std::shared_ptr<SVGResource>(new SVGResource(key.first, tile));
|
||||
sTextureList.push_back(tex); // add it to our list though
|
||||
rm->addReloadable(tex);
|
||||
tex->reload(rm);
|
||||
return tex;
|
||||
}else{
|
||||
// normal texture
|
||||
tex = std::shared_ptr<TextureResource>(new TextureResource(key.first, tile));
|
||||
// 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);
|
||||
sTextureList.push_back(tex);
|
||||
rm->addReloadable(tex);
|
||||
tex->reload(ResourceManager::getInstance());
|
||||
return tex;
|
||||
}
|
||||
|
||||
// Add it to the reloadable list
|
||||
rm->addReloadable(tex);
|
||||
|
||||
// Force load it if necessary. Note that it may get dumped from VRAM if we run low
|
||||
if (forceLoad)
|
||||
{
|
||||
tex->mForceLoad = forceLoad;
|
||||
data->load();
|
||||
}
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
// For scalable source images in textures we want to set the resolution to rasterize at
|
||||
void TextureResource::rasterizeAt(size_t width, size_t height)
|
||||
{
|
||||
std::shared_ptr<TextureData> data;
|
||||
if (mTextureData != nullptr)
|
||||
data = mTextureData;
|
||||
else
|
||||
data = sTextureDataManager.get(this);
|
||||
mSourceSize << (float)width, (float)height;
|
||||
data->setSourceSize((float)width, (float)height);
|
||||
if (mForceLoad || (mTextureData != nullptr))
|
||||
data->load();
|
||||
}
|
||||
|
||||
Eigen::Vector2f TextureResource::getSourceImageSize() const
|
||||
{
|
||||
return mSourceSize;
|
||||
}
|
||||
|
||||
bool TextureResource::isInitialized() const
|
||||
{
|
||||
return mTextureID != 0;
|
||||
}
|
||||
|
||||
size_t TextureResource::getMemUsage() const
|
||||
{
|
||||
if(!mTextureID || mTextureSize.x() == 0 || mTextureSize.y() == 0)
|
||||
return 0;
|
||||
|
||||
return mTextureSize.x() * mTextureSize.y() * 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t TextureResource::getTotalMemUsage()
|
||||
{
|
||||
size_t total = 0;
|
||||
|
||||
auto it = sTextureList.begin();
|
||||
while(it != sTextureList.end())
|
||||
// Count up all textures that manage their own texture data
|
||||
for (auto tex : sAllTextures)
|
||||
{
|
||||
if((*it).expired())
|
||||
{
|
||||
// remove expired textures from the list
|
||||
it = sTextureList.erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
total += (*it).lock()->getMemUsage();
|
||||
it++;
|
||||
if (tex->mTextureData != nullptr)
|
||||
total += tex->mTextureData->getVRAMUsage();
|
||||
}
|
||||
|
||||
// Now get the committed memory from the manager
|
||||
total += sTextureDataManager.getCommittedSize();
|
||||
// And the size of the loading queue
|
||||
total += sTextureDataManager.getQueueSize();
|
||||
return total;
|
||||
}
|
||||
|
||||
size_t TextureResource::getTotalTextureSize()
|
||||
{
|
||||
size_t total = 0;
|
||||
// Count up all textures that manage their own texture data
|
||||
for (auto tex : sAllTextures)
|
||||
{
|
||||
if (tex->mTextureData != nullptr)
|
||||
total += tex->getSize().x() * tex->getSize().y() * 4;
|
||||
}
|
||||
// Now get the total memory from the manager
|
||||
total += sTextureDataManager.getTotalSize();
|
||||
return total;
|
||||
}
|
||||
|
||||
void TextureResource::unload(std::shared_ptr<ResourceManager>& rm)
|
||||
{
|
||||
// Release the texture's resources
|
||||
std::shared_ptr<TextureData> data;
|
||||
if (mTextureData == nullptr)
|
||||
data = sTextureDataManager.get(this);
|
||||
else
|
||||
data = mTextureData;
|
||||
|
||||
data->releaseVRAM();
|
||||
data->releaseRAM();
|
||||
}
|
||||
|
||||
void TextureResource::reload(std::shared_ptr<ResourceManager>& rm)
|
||||
{
|
||||
// For dynamically loaded textures the texture manager will load them on demand.
|
||||
// For manually loaded textures we have to reload them here
|
||||
if (mTextureData)
|
||||
mTextureData->load();
|
||||
}
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
#include "resources/ResourceManager.h"
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <Eigen/Dense>
|
||||
#include "platform.h"
|
||||
#include "resources/TextureData.h"
|
||||
#include "resources/TextureDataManager.h"
|
||||
#include GLHEADER
|
||||
|
||||
// An OpenGL texture.
|
||||
|
@ -12,40 +16,43 @@
|
|||
class TextureResource : public IReloadable
|
||||
{
|
||||
public:
|
||||
static std::shared_ptr<TextureResource> get(const std::string& path, bool tile = false);
|
||||
static std::shared_ptr<TextureResource> get(const std::string& path, bool tile = false, bool forceLoad = false, bool dynamic = true);
|
||||
void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height);
|
||||
virtual void initFromMemory(const char* file, size_t length);
|
||||
|
||||
// For scalable source images in textures we want to set the resolution to rasterize at
|
||||
void rasterizeAt(size_t width, size_t height);
|
||||
Eigen::Vector2f getSourceImageSize() const;
|
||||
|
||||
virtual ~TextureResource();
|
||||
|
||||
virtual void unload(std::shared_ptr<ResourceManager>& rm) override;
|
||||
virtual void reload(std::shared_ptr<ResourceManager>& rm) override;
|
||||
|
||||
bool isInitialized() const;
|
||||
bool isTiled() const;
|
||||
const Eigen::Vector2i& getSize() const;
|
||||
void bind() const;
|
||||
|
||||
// Warning: will NOT correctly reinitialize when this texture is reloaded (e.g. ES starts/stops playing a game).
|
||||
virtual void initFromMemory(const char* file, size_t length);
|
||||
|
||||
// Warning: will NOT correctly reinitialize when this texture is reloaded (e.g. ES starts/stops playing a game).
|
||||
void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height);
|
||||
const Eigen::Vector2i getSize() const;
|
||||
bool bind();
|
||||
|
||||
size_t getMemUsage() const; // returns an approximation of the VRAM used by this texture (in bytes)
|
||||
static size_t getTotalMemUsage(); // returns an approximation of total VRAM used by textures (in bytes)
|
||||
static size_t getTotalTextureSize(); // returns the number of bytes that would be used if all textures were in memory
|
||||
|
||||
protected:
|
||||
TextureResource(const std::string& path, bool tile);
|
||||
void deinit();
|
||||
|
||||
Eigen::Vector2i mTextureSize;
|
||||
const std::string mPath;
|
||||
const bool mTile;
|
||||
TextureResource(const std::string& path, bool tile, bool dynamic);
|
||||
virtual void unload(std::shared_ptr<ResourceManager>& rm);
|
||||
virtual void reload(std::shared_ptr<ResourceManager>& rm);
|
||||
|
||||
private:
|
||||
GLuint mTextureID;
|
||||
// mTextureData is used for textures that are not loaded from a file - these ones
|
||||
// are permanently allocated and cannot be loaded and unloaded based on resources
|
||||
std::shared_ptr<TextureData> mTextureData;
|
||||
|
||||
// The texture data manager manages loading and unloading of filesystem based textures
|
||||
static TextureDataManager sTextureDataManager;
|
||||
|
||||
Eigen::Vector2i mSize;
|
||||
Eigen::Vector2f mSourceSize;
|
||||
bool mForceLoad;
|
||||
|
||||
typedef std::pair<std::string, bool> TextureKeyType;
|
||||
static std::map< TextureKeyType, std::weak_ptr<TextureResource> > sTextureMap; // map of textures, used to prevent duplicate textures
|
||||
|
||||
static std::list< std::weak_ptr<TextureResource> > sTextureList; // list of all textures, used for memory approximations
|
||||
static std::set<TextureResource*> sAllTextures; // Set of all textures, used for memory management
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue