diff --git a/es-app/src/components/RatingComponent.cpp b/es-app/src/components/RatingComponent.cpp index 12f39a309..3dbc36a0a 100644 --- a/es-app/src/components/RatingComponent.cpp +++ b/es-app/src/components/RatingComponent.cpp @@ -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(mFilledTexture.get()); - auto unfilledSVG = dynamic_cast(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(); diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 71ac7272b..68b1a355d 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -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(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); }); diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index 6753b9ae3..c3d030ec4 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -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 diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index 013490aca..c760125b9 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -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(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, diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 1e10c9175..d9093e8e1 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -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()); diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index cf0cfb3ea..f05aec77c 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -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 diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index 337dcaac1..df90ba7b6 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -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); \ No newline at end of file +SETTINGS_GETSET(const std::string&, mStringMap, getString, setString); diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 761df4abc..e402162c3 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -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(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); diff --git a/es-core/src/components/ImageComponent.cpp b/es-core/src/components/ImageComponent.cpp index 2898c6f73..0705e0c28 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -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(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; diff --git a/es-core/src/components/ImageComponent.h b/es-core/src/components/ImageComponent.h index 939c6c7cd..3ff1ffbb2 100644 --- a/es-core/src/components/ImageComponent.h +++ b/es-core/src/components/ImageComponent.h @@ -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 mTexture; + unsigned char mFadeOpacity; + bool mFading; + bool mForceLoad; + bool mDynamic; }; #endif diff --git a/es-core/src/resources/SVGResource.cpp b/es-core/src/resources/SVGResource.cpp deleted file mode 100644 index 1a4ea16fc..000000000 --- a/es-core/src/resources/SVGResource.cpp +++ /dev/null @@ -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& 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; -} diff --git a/es-core/src/resources/SVGResource.h b/es-core/src/resources/SVGResource.h deleted file mode 100644 index 87479c0cf..000000000 --- a/es-core/src/resources/SVGResource.h +++ /dev/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& 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; -}; diff --git a/es-core/src/resources/TextureData.cpp b/es-core/src/resources/TextureData.cpp new file mode 100644 index 000000000..15a3da116 --- /dev/null +++ b/es-core/src/resources/TextureData.cpp @@ -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 + +#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 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 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 lock(mMutex); + if (mDataRGBA) + return true; + } + + std::vector 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 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& 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 lock(mMutex); + if (mDataRGBA || (mTextureID != 0)) + return true; + return false; +} + +bool TextureData::uploadAndBind() +{ + // See if it's already been uploaded + std::unique_lock 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 lock(mMutex); + if (mTextureID != 0) + { + glDeleteTextures(1, &mTextureID); + mTextureID = 0; + } +} + +void TextureData::releaseRAM() +{ + std::unique_lock 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; +} diff --git a/es-core/src/resources/TextureData.h b/es-core/src/resources/TextureData.h new file mode 100644 index 000000000..76ca6a0b4 --- /dev/null +++ b/es-core/src/resources/TextureData.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include "platform.h" +#include +#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; +}; diff --git a/es-core/src/resources/TextureDataManager.cpp b/es-core/src/resources/TextureDataManager.cpp new file mode 100644 index 000000000..55720d14a --- /dev/null +++ b/es-core/src/resources/TextureDataManager.cpp @@ -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(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 TextureDataManager::add(const TextureResource* key, bool tiled) +{ + remove(key); + std::shared_ptr 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 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 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 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 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; + { + // Wait for an event to say there is something in the queue + std::unique_lock 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 lock(mMutex); + if (!mTextureDataQ.empty()) + { + textureData = mTextureDataQ.front(); + mTextureDataQ.pop_front(); + mTextureDataLookup.erase(mTextureDataLookup.find(textureData.get())); + } + } + } +} + +void TextureLoader::load(std::shared_ptr textureData) +{ + // Make sure it's not already loaded + if (!textureData->isLoaded()) + { + std::unique_lock 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) +{ + // Just remove it from the queue so we don't attempt to load it + std::unique_lock 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 lock(mMutex); + for (auto tex : mTextureDataQ) + { + mem += tex->width() * tex->height() * 4; + } + return mem; +} diff --git a/es-core/src/resources/TextureDataManager.h b/es-core/src/resources/TextureDataManager.h new file mode 100644 index 000000000..414804aba --- /dev/null +++ b/es-core/src/resources/TextureDataManager.h @@ -0,0 +1,86 @@ +#pragma once + +#include "resources/ResourceManager.h" +#include "platform.h" +#include "resources/TextureData.h" +#include +#include +#include +#include +#include +#include + +class TextureResource; + +class TextureLoader +{ +public: + TextureLoader(); + ~TextureLoader(); + + void load(std::shared_ptr textureData); + void remove(std::shared_ptr textureData); + + size_t getQueueSize(); + +private: + void processQueue(); + void threadProc(); + + std::list > mTextureDataQ; + std::map >::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 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 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 tex, bool block = false); + +private: + + std::list > mTextures; + std::map >::iterator > mTextureLookup; + std::shared_ptr mBlank; + TextureLoader* mLoader; +}; + diff --git a/es-core/src/resources/TextureResource.cpp b/es-core/src/resources/TextureResource.cpp index e6657dcc2..6400d74b9 100644 --- a/es-core/src/resources/TextureResource.cpp +++ b/es-core/src/resources/TextureResource.cpp @@ -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::sTextureMap; -std::list< std::weak_ptr > TextureResource::sTextureList; +std::set 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 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(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(new TextureData(tile)); + } + sAllTextures.insert(this); } TextureResource::~TextureResource() { - deinit(); -} + if (mTextureData == nullptr) + sTextureDataManager.remove(this); -void TextureResource::unload(std::shared_ptr& rm) -{ - deinit(); -} - -void TextureResource::reload(std::shared_ptr& 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 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 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::get(const std::string& path, bool tile) +std::shared_ptr TextureResource::get(const std::string& path, bool tile, bool forceLoad, bool dynamic) { std::shared_ptr& rm = ResourceManager::getInstance(); const std::string canonicalPath = getCanonicalPath(path); - if(canonicalPath.empty()) { - std::shared_ptr tex(new TextureResource("", tile)); + std::shared_ptr 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::get(const std::string& path, b // need to create it std::shared_ptr tex; + tex = std::shared_ptr(new TextureResource(key.first, tile, dynamic)); + std::shared_ptr data = sTextureDataManager.get(tex.get()); // is it an SVG? - if(key.first.substr(key.first.size() - 4, std::string::npos) == ".svg") + if(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(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(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(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 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& rm) +{ + // Release the texture's resources + std::shared_ptr data; + if (mTextureData == nullptr) + data = sTextureDataManager.get(this); + else + data = mTextureData; + + data->releaseVRAM(); + data->releaseRAM(); +} + +void TextureResource::reload(std::shared_ptr& 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(); +} diff --git a/es-core/src/resources/TextureResource.h b/es-core/src/resources/TextureResource.h index 811e71de3..7623e8b1c 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -3,8 +3,12 @@ #include "resources/ResourceManager.h" #include +#include +#include #include #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 get(const std::string& path, bool tile = false); + static std::shared_ptr 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& rm) override; - virtual void reload(std::shared_ptr& 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& rm); + virtual void reload(std::shared_ptr& 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 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 TextureKeyType; static std::map< TextureKeyType, std::weak_ptr > sTextureMap; // map of textures, used to prevent duplicate textures - - static std::list< std::weak_ptr > sTextureList; // list of all textures, used for memory approximations + static std::set sAllTextures; // Set of all textures, used for memory management };