diff --git a/es-app/src/components/RatingComponent.cpp b/es-app/src/components/RatingComponent.cpp index 3dbc36a0a..12f39a309 100644 --- a/es-app/src/components/RatingComponent.cpp +++ b/es-app/src/components/RatingComponent.cpp @@ -2,6 +2,7 @@ #include "Renderer.h" #include "Window.h" #include "Util.h" +#include "resources/SVGResource.h" RatingComponent::RatingComponent(Window* window) : GuiComponent(window) { @@ -44,13 +45,16 @@ 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 (mFilledTexture) - mFilledTexture->rasterizeAt(heightPx, heightPx); - if(mUnfilledTexture) - mUnfilledTexture->rasterizeAt(heightPx, heightPx); + if(filledSVG) + filledSVG->rasterizeAt(heightPx, heightPx); + if(unfilledSVG) + unfilledSVG->rasterizeAt(heightPx, heightPx); } updateVertices(); diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index 68b1a355d..71ac7272b 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -178,12 +178,6 @@ 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 c3d030ec4..6753b9ae3 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -74,10 +74,6 @@ 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 @@ -103,7 +99,6 @@ 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 c760125b9..013490aca 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, false, false); + ImageComponent* logo = new ImageComponent(mWindow); 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, false, false); + ImageComponent* logoSelected = new ImageComponent(mWindow); 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 d9093e8e1..1e10c9175 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -406,9 +406,7 @@ void ViewController::reloadAll() mCurrentView = getGameListView(mState.getSystem()); }else if(mState.viewing == SYSTEM_SELECT) { - SystemData* system = mState.getSystem(); - goToSystemView(SystemData::sSystemVector.front()); - mSystemListView->goToSystem(system, false); + mSystemListView->goToSystem(mState.getSystem(), false); mCurrentView = mSystemListView; }else{ goToSystemView(SystemData::sSystemVector.front()); diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index f05aec77c..cf0cfb3ea 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -53,9 +53,8 @@ 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 @@ -109,9 +108,8 @@ 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 df90ba7b6..337dcaac1 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -69,7 +69,6 @@ void Settings::setDefaults() mIntMap["ScreenSaverTime"] = 5*60*1000; // 5 minutes mIntMap["ScraperResizeWidth"] = 400; mIntMap["ScraperResizeHeight"] = 0; - mIntMap["MaxVRAM"] = 100; mStringMap["TransitionStyle"] = "fade"; mStringMap["ThemeSet"] = ""; @@ -155,4 +154,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); \ No newline at end of file diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index e402162c3..761df4abc 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -160,12 +160,11 @@ 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 textureTotalUsageMb = TextureResource::getTotalTextureSize() / 1000.0f / 1000.0f; + float textureVramUsageMb = TextureResource::getTotalMemUsage() / 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)); } @@ -243,7 +242,7 @@ void Window::renderLoadingScreen() Renderer::setMatrix(trans); Renderer::drawRect(0, 0, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0xFFFFFFFF); - ImageComponent splash(this, true); + ImageComponent splash(this); 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 1ae62314d..2898c6f73 100644 --- a/es-core/src/components/ImageComponent.cpp +++ b/es-core/src/components/ImageComponent.cpp @@ -6,6 +6,7 @@ #include "Renderer.h" #include "ThemeData.h" #include "Util.h" +#include "resources/SVGResource.h" Eigen::Vector2i ImageComponent::getTextureSize() const { @@ -21,9 +22,8 @@ Eigen::Vector2f ImageComponent::getCenter() const mPosition.y() - (getSize().y() * mOrigin.y()) + getSize().y() / 2); } -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) +ImageComponent::ImageComponent(Window* window) : GuiComponent(window), + mTargetIsMax(false), mFlipX(false), mFlipY(false), mOrigin(0.0, 0.0), mTargetSize(0, 0), mColorShift(0xFFFFFFFF) { updateColors(); } @@ -37,7 +37,9 @@ void ImageComponent::resize() if(!mTexture) return; - const Eigen::Vector2f textureSize = mTexture->getSourceImageSize(); + SVGResource* svg = dynamic_cast(mTexture.get()); + + const Eigen::Vector2f textureSize = svg ? svg->getSourceImageSize() : Eigen::Vector2f((float)mTexture->getSize().x(), (float)mTexture->getSize().y()); if(textureSize.isZero()) return; @@ -88,8 +90,12 @@ void ImageComponent::resize() } } } - // mSize.y() should already be rounded - mTexture->rasterizeAt((int)round(mSize.x()), (int)round(mSize.y())); + + if(svg) + { + // mSize.y() should already be rounded + svg->rasterizeAt((int)round(mSize.x()), (int)round(mSize.y())); + } onSizeChanged(); } @@ -104,7 +110,7 @@ void ImageComponent::setImage(std::string path, bool tile) if(path.empty() || !ResourceManager::getInstance()->fileExists(path)) mTexture.reset(); else - mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic); + mTexture = TextureResource::get(path, tile); resize(); } @@ -241,10 +247,7 @@ void ImageComponent::render(const Eigen::Affine3f& parentTrans) if(mTexture->isInitialized()) { // actually draw the image - // 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()); + mTexture->bind(); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); @@ -275,47 +278,6 @@ 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 3ff1ffbb2..939c6c7cd 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, bool forceLoad = false, bool dynamic = true); + ImageComponent(Window* window); virtual ~ImageComponent(); //Loads the image at the given filepath. Will tile if tile is true (retrieves texture as tiling, creates vertices accordingly). @@ -81,15 +81,10 @@ 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 new file mode 100644 index 000000000..1a4ea16fc --- /dev/null +++ b/es-core/src/resources/SVGResource.cpp @@ -0,0 +1,101 @@ +#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 new file mode 100644 index 000000000..87479c0cf --- /dev/null +++ b/es-core/src/resources/SVGResource.h @@ -0,0 +1,27 @@ +#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 deleted file mode 100644 index 15a3da116..000000000 --- a/es-core/src/resources/TextureData.cpp +++ /dev/null @@ -1,259 +0,0 @@ -#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 deleted file mode 100644 index 76ca6a0b4..000000000 --- a/es-core/src/resources/TextureData.h +++ /dev/null @@ -1,63 +0,0 @@ -#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 deleted file mode 100644 index 55720d14a..000000000 --- a/es-core/src/resources/TextureDataManager.cpp +++ /dev/null @@ -1,226 +0,0 @@ -#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 deleted file mode 100644 index 414804aba..000000000 --- a/es-core/src/resources/TextureDataManager.h +++ /dev/null @@ -1,86 +0,0 @@ -#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 6400d74b9..e6657dcc2 100644 --- a/es-core/src/resources/TextureResource.cpp +++ b/es-core/src/resources/TextureResource.cpp @@ -5,113 +5,108 @@ #include "ImageIO.h" #include "Renderer.h" #include "Util.h" -#include "Settings.h" +#include "resources/SVGResource.h" -TextureDataManager TextureResource::sTextureDataManager; std::map< TextureResource::TextureKeyType, std::weak_ptr > TextureResource::sTextureMap; -std::set TextureResource::sAllTextures; +std::list< std::weak_ptr > TextureResource::sTextureList; -TextureResource::TextureResource(const std::string& path, bool tile, bool dynamic) : mTextureData(nullptr), mForceLoad(false) +TextureResource::TextureResource(const std::string& path, bool tile) : + mTextureID(0), mPath(path), mTextureSize(Eigen::Vector2i::Zero()), mTile(tile) { - // 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() { - if (mTextureData == nullptr) - sTextureDataManager.remove(this); + deinit(); +} - sAllTextures.erase(sAllTextures.find(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); + } } void TextureResource::initFromPixels(const unsigned char* dataRGBA, size_t width, size_t 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(); + 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; } void TextureResource::initFromMemory(const char* data, size_t length) { - // 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(); + 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); } -const Eigen::Vector2i TextureResource::getSize() const +void TextureResource::deinit() { - return mSize; + if(mTextureID != 0) + { + glDeleteTextures(1, &mTextureID); + mTextureID = 0; + } +} + +const Eigen::Vector2i& TextureResource::getSize() const +{ + return mTextureSize; } bool TextureResource::isTiled() const { - if (mTextureData != nullptr) - return mTextureData->tiled(); - std::shared_ptr data = sTextureDataManager.get(this); - return data->tiled(); + return mTile; } -bool TextureResource::bind() +void TextureResource::bind() const { - if (mTextureData != nullptr) - { - mTextureData->uploadAndBind(); - return true; - } + if(mTextureID != 0) + glBindTexture(GL_TEXTURE_2D, mTextureID); else - { - return sTextureDataManager.bind(this); - } + LOG(LogError) << "Tried to bind uninitialized texture!"; } -std::shared_ptr TextureResource::get(const std::string& path, bool tile, bool forceLoad, bool dynamic) + +std::shared_ptr TextureResource::get(const std::string& path, bool tile) { std::shared_ptr& rm = ResourceManager::getInstance(); const std::string canonicalPath = getCanonicalPath(path); + if(canonicalPath.empty()) { - std::shared_ptr tex(new TextureResource("", tile, false)); + std::shared_ptr tex(new TextureResource("", tile)); rm->addReloadable(tex); //make sure we get properly deinitialized even though we do nothing on reinitialization return tex; } @@ -126,100 +121,58 @@ 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 not. Add it to our map. We don't add SVGs because 2 svgs might be rasterized at different sizes + // 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)); 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 true; + return mTextureID != 0; +} + +size_t TextureResource::getMemUsage() const +{ + if(!mTextureID || mTextureSize.x() == 0 || mTextureSize.y() == 0) + return 0; + + return mTextureSize.x() * mTextureSize.y() * 4; } size_t TextureResource::getTotalMemUsage() { size_t total = 0; - // Count up all textures that manage their own texture data - for (auto tex : sAllTextures) + + auto it = sTextureList.begin(); + while(it != sTextureList.end()) { - if (tex->mTextureData != nullptr) - total += tex->mTextureData->getVRAMUsage(); + if((*it).expired()) + { + // remove expired textures from the list + it = sTextureList.erase(it); + continue; + } + + total += (*it).lock()->getMemUsage(); + it++; } - // 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 7623e8b1c..811e71de3 100644 --- a/es-core/src/resources/TextureResource.h +++ b/es-core/src/resources/TextureResource.h @@ -3,12 +3,8 @@ #include "resources/ResourceManager.h" #include -#include -#include #include #include "platform.h" -#include "resources/TextureData.h" -#include "resources/TextureDataManager.h" #include GLHEADER // An OpenGL texture. @@ -16,43 +12,40 @@ class TextureResource : public IReloadable { public: - 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; + static std::shared_ptr get(const std::string& path, bool tile = false); 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); - const Eigen::Vector2i getSize() const; - bool bind(); + // 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); + 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, bool dynamic); - virtual void unload(std::shared_ptr& rm); - virtual void reload(std::shared_ptr& rm); + TextureResource(const std::string& path, bool tile); + void deinit(); + + Eigen::Vector2i mTextureSize; + const std::string mPath; + const bool mTile; private: - // 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; + GLuint mTextureID; typedef std::pair TextureKeyType; static std::map< TextureKeyType, std::weak_ptr > sTextureMap; // map of textures, used to prevent duplicate textures - static std::set sAllTextures; // Set of all textures, used for memory management + + static std::list< std::weak_ptr > sTextureList; // list of all textures, used for memory approximations };