mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-12-01 18:45:39 +00:00
Merge pull request #88 from fieldofcows/wsod_fix_pr
Fix WSOD by loading textures on demand in a separate thread when a us…
This commit is contained in:
commit
bbeb51e43d
|
@ -2,7 +2,6 @@
|
||||||
#include "Renderer.h"
|
#include "Renderer.h"
|
||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
#include "Util.h"
|
#include "Util.h"
|
||||||
#include "resources/SVGResource.h"
|
|
||||||
|
|
||||||
RatingComponent::RatingComponent(Window* window) : GuiComponent(window)
|
RatingComponent::RatingComponent(Window* window) : GuiComponent(window)
|
||||||
{
|
{
|
||||||
|
@ -45,16 +44,13 @@ void RatingComponent::onSizeChanged()
|
||||||
else if(mSize.x() == 0)
|
else if(mSize.x() == 0)
|
||||||
mSize[0] = mSize.y() * NUM_RATING_STARS;
|
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)
|
if(mSize.y() > 0)
|
||||||
{
|
{
|
||||||
size_t heightPx = (size_t)round(mSize.y());
|
size_t heightPx = (size_t)round(mSize.y());
|
||||||
if(filledSVG)
|
if (mFilledTexture)
|
||||||
filledSVG->rasterizeAt(heightPx, heightPx);
|
mFilledTexture->rasterizeAt(heightPx, heightPx);
|
||||||
if(unfilledSVG)
|
if(mUnfilledTexture)
|
||||||
unfilledSVG->rasterizeAt(heightPx, heightPx);
|
mUnfilledTexture->rasterizeAt(heightPx, heightPx);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateVertices();
|
updateVertices();
|
||||||
|
|
|
@ -178,6 +178,12 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN
|
||||||
s->addWithLabel("PARSE GAMESLISTS ONLY", parse_gamelists);
|
s->addWithLabel("PARSE GAMESLISTS ONLY", parse_gamelists);
|
||||||
s->addSaveFunc([parse_gamelists] { Settings::getInstance()->setBool("ParseGamelistOnly", parse_gamelists->getState()); });
|
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);
|
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)
|
}else if(strcmp(argv[i], "--scrape") == 0)
|
||||||
{
|
{
|
||||||
scrape_cmdline = true;
|
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)
|
}else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
|
||||||
{
|
{
|
||||||
#ifdef WIN32
|
#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"
|
"--scrape scrape using command line interface\n"
|
||||||
"--windowed not fullscreen, should be used with --resolution\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"
|
"--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"
|
"--help, -h summon a sentient, angry tuba\n\n"
|
||||||
"More information available in README.md.\n";
|
"More information available in README.md.\n";
|
||||||
return false; //exit after printing help
|
return false; //exit after printing help
|
||||||
|
|
|
@ -43,13 +43,13 @@ void SystemView::populate()
|
||||||
// make logo
|
// make logo
|
||||||
if(theme->getElement("system", "logo", "image"))
|
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->setMaxSize(Eigen::Vector2f(logoSize().x(), logoSize().y()));
|
||||||
logo->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
|
logo->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
|
||||||
logo->setPosition((logoSize().x() - logo->getSize().x()) / 2, (logoSize().y() - logo->getSize().y()) / 2); // center
|
logo->setPosition((logoSize().x() - logo->getSize().x()) / 2, (logoSize().y() - logo->getSize().y()) / 2); // center
|
||||||
e.data.logo = std::shared_ptr<GuiComponent>(logo);
|
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->setMaxSize(Eigen::Vector2f(logoSize().x() * SELECTED_SCALE, logoSize().y() * SELECTED_SCALE * 0.70f));
|
||||||
logoSelected->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
|
logoSelected->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
|
||||||
logoSelected->setPosition((logoSize().x() - logoSelected->getSize().x()) / 2,
|
logoSelected->setPosition((logoSize().x() - logoSelected->getSize().x()) / 2,
|
||||||
|
|
|
@ -406,7 +406,9 @@ void ViewController::reloadAll()
|
||||||
mCurrentView = getGameListView(mState.getSystem());
|
mCurrentView = getGameListView(mState.getSystem());
|
||||||
}else if(mState.viewing == SYSTEM_SELECT)
|
}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;
|
mCurrentView = mSystemListView;
|
||||||
}else{
|
}else{
|
||||||
goToSystemView(SystemData::sSystemVector.front());
|
goToSystemView(SystemData::sSystemVector.front());
|
||||||
|
|
|
@ -53,8 +53,9 @@ set(CORE_HEADERS
|
||||||
# Resources
|
# Resources
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.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/TextureResource.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.h
|
||||||
|
|
||||||
# Embedded assets (needed by ResourceManager)
|
# Embedded assets (needed by ResourceManager)
|
||||||
${emulationstation-all_SOURCE_DIR}/data/Resources.h
|
${emulationstation-all_SOURCE_DIR}/data/Resources.h
|
||||||
|
@ -108,8 +109,9 @@ set(CORE_SOURCES
|
||||||
# Resources
|
# Resources
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.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/TextureResource.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(EMBEDDED_ASSET_SOURCES
|
set(EMBEDDED_ASSET_SOURCES
|
||||||
|
|
|
@ -69,6 +69,7 @@ void Settings::setDefaults()
|
||||||
mIntMap["ScreenSaverTime"] = 5*60*1000; // 5 minutes
|
mIntMap["ScreenSaverTime"] = 5*60*1000; // 5 minutes
|
||||||
mIntMap["ScraperResizeWidth"] = 400;
|
mIntMap["ScraperResizeWidth"] = 400;
|
||||||
mIntMap["ScraperResizeHeight"] = 0;
|
mIntMap["ScraperResizeHeight"] = 0;
|
||||||
|
mIntMap["MaxVRAM"] = 100;
|
||||||
|
|
||||||
mStringMap["TransitionStyle"] = "fade";
|
mStringMap["TransitionStyle"] = "fade";
|
||||||
mStringMap["ThemeSet"] = "";
|
mStringMap["ThemeSet"] = "";
|
||||||
|
|
|
@ -160,11 +160,12 @@ void Window::update(int deltaTime)
|
||||||
ss << std::fixed << std::setprecision(2) << ((float)mFrameTimeElapsed / (float)mFrameCountElapsed) << "ms";
|
ss << std::fixed << std::setprecision(2) << ((float)mFrameTimeElapsed / (float)mFrameCountElapsed) << "ms";
|
||||||
|
|
||||||
// vram
|
// 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 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));
|
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::setMatrix(trans);
|
||||||
Renderer::drawRect(0, 0, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0xFFFFFFFF);
|
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.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f);
|
||||||
splash.setImage(":/splash.svg");
|
splash.setImage(":/splash.svg");
|
||||||
splash.setPosition((Renderer::getScreenWidth() - splash.getSize().x()) / 2, (Renderer::getScreenHeight() - splash.getSize().y()) / 2 * 0.6f);
|
splash.setPosition((Renderer::getScreenWidth() - splash.getSize().x()) / 2, (Renderer::getScreenHeight() - splash.getSize().y()) / 2 * 0.6f);
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include "Renderer.h"
|
#include "Renderer.h"
|
||||||
#include "ThemeData.h"
|
#include "ThemeData.h"
|
||||||
#include "Util.h"
|
#include "Util.h"
|
||||||
#include "resources/SVGResource.h"
|
|
||||||
|
|
||||||
Eigen::Vector2i ImageComponent::getTextureSize() const
|
Eigen::Vector2i ImageComponent::getTextureSize() const
|
||||||
{
|
{
|
||||||
|
@ -22,8 +21,9 @@ Eigen::Vector2f ImageComponent::getCenter() const
|
||||||
mPosition.y() - (getSize().y() * mOrigin.y()) + getSize().y() / 2);
|
mPosition.y() - (getSize().y() * mOrigin.y()) + getSize().y() / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageComponent::ImageComponent(Window* window) : GuiComponent(window),
|
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)
|
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();
|
updateColors();
|
||||||
}
|
}
|
||||||
|
@ -37,9 +37,7 @@ void ImageComponent::resize()
|
||||||
if(!mTexture)
|
if(!mTexture)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SVGResource* svg = dynamic_cast<SVGResource*>(mTexture.get());
|
const Eigen::Vector2f textureSize = mTexture->getSourceImageSize();
|
||||||
|
|
||||||
const Eigen::Vector2f textureSize = svg ? svg->getSourceImageSize() : Eigen::Vector2f((float)mTexture->getSize().x(), (float)mTexture->getSize().y());
|
|
||||||
if(textureSize.isZero())
|
if(textureSize.isZero())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -90,12 +88,8 @@ void ImageComponent::resize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(svg)
|
|
||||||
{
|
|
||||||
// mSize.y() should already be rounded
|
// mSize.y() should already be rounded
|
||||||
svg->rasterizeAt((int)round(mSize.x()), (int)round(mSize.y()));
|
mTexture->rasterizeAt((int)round(mSize.x()), (int)round(mSize.y()));
|
||||||
}
|
|
||||||
|
|
||||||
onSizeChanged();
|
onSizeChanged();
|
||||||
}
|
}
|
||||||
|
@ -110,7 +104,7 @@ void ImageComponent::setImage(std::string path, bool tile)
|
||||||
if(path.empty() || !ResourceManager::getInstance()->fileExists(path))
|
if(path.empty() || !ResourceManager::getInstance()->fileExists(path))
|
||||||
mTexture.reset();
|
mTexture.reset();
|
||||||
else
|
else
|
||||||
mTexture = TextureResource::get(path, tile);
|
mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic);
|
||||||
|
|
||||||
resize();
|
resize();
|
||||||
}
|
}
|
||||||
|
@ -166,6 +160,9 @@ void ImageComponent::setFlipY(bool flip)
|
||||||
void ImageComponent::setColorShift(unsigned int color)
|
void ImageComponent::setColorShift(unsigned int color)
|
||||||
{
|
{
|
||||||
mColorShift = 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();
|
updateColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,7 +244,10 @@ void ImageComponent::render(const Eigen::Affine3f& parentTrans)
|
||||||
if(mTexture->isInitialized())
|
if(mTexture->isInitialized())
|
||||||
{
|
{
|
||||||
// actually draw the image
|
// 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_TEXTURE_2D);
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
|
@ -278,6 +278,47 @@ void ImageComponent::render(const Eigen::Affine3f& parentTrans)
|
||||||
GuiComponent::renderChildren(trans);
|
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()
|
bool ImageComponent::hasImage()
|
||||||
{
|
{
|
||||||
return (bool)mTexture;
|
return (bool)mTexture;
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
class ImageComponent : public GuiComponent
|
class ImageComponent : public GuiComponent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ImageComponent(Window* window);
|
ImageComponent(Window* window, bool forceLoad = false, bool dynamic = true);
|
||||||
virtual ~ImageComponent();
|
virtual ~ImageComponent();
|
||||||
|
|
||||||
//Loads the image at the given filepath. Will tile if tile is true (retrieves texture as tiling, creates vertices accordingly).
|
//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 updateVertices();
|
||||||
void updateColors();
|
void updateColors();
|
||||||
|
void fadeIn(bool textureLoaded);
|
||||||
|
|
||||||
unsigned int mColorShift;
|
unsigned int mColorShift;
|
||||||
|
|
||||||
std::shared_ptr<TextureResource> mTexture;
|
std::shared_ptr<TextureResource> mTexture;
|
||||||
|
unsigned char mFadeOpacity;
|
||||||
|
bool mFading;
|
||||||
|
bool mForceLoad;
|
||||||
|
bool mDynamic;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#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 "ImageIO.h"
|
||||||
#include "Renderer.h"
|
#include "Renderer.h"
|
||||||
#include "Util.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::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) :
|
TextureResource::TextureResource(const std::string& path, bool tile, bool dynamic) : mTextureData(nullptr), mForceLoad(false)
|
||||||
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<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()
|
TextureResource::~TextureResource()
|
||||||
{
|
{
|
||||||
deinit();
|
if (mTextureData == nullptr)
|
||||||
}
|
sTextureDataManager.remove(this);
|
||||||
|
|
||||||
void TextureResource::unload(std::shared_ptr<ResourceManager>& rm)
|
sAllTextures.erase(sAllTextures.find(this));
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureResource::initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height)
|
void TextureResource::initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height)
|
||||||
{
|
{
|
||||||
deinit();
|
// This is only valid if we have a local texture data object
|
||||||
|
assert(mTextureData != nullptr);
|
||||||
assert(width > 0 && height > 0);
|
mTextureData->releaseVRAM();
|
||||||
|
mTextureData->releaseRAM();
|
||||||
//now for the openGL texture stuff
|
mTextureData->initFromRGBA(dataRGBA, width, height);
|
||||||
glGenTextures(1, &mTextureID);
|
// Cache the image dimensions
|
||||||
glBindTexture(GL_TEXTURE_2D, mTextureID);
|
mSize << width, height;
|
||||||
|
mSourceSize << mTextureData->sourceWidth(), mTextureData->sourceHeight();
|
||||||
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)
|
void TextureResource::initFromMemory(const char* data, size_t length)
|
||||||
{
|
{
|
||||||
size_t width, height;
|
// This is only valid if we have a local texture data object
|
||||||
std::vector<unsigned char> imageRGBA = ImageIO::loadFromMemoryRGBA32((const unsigned char*)(data), length, width, height);
|
assert(mTextureData != nullptr);
|
||||||
|
mTextureData->releaseVRAM();
|
||||||
if(imageRGBA.size() == 0)
|
mTextureData->releaseRAM();
|
||||||
{
|
mTextureData->initImageFromMemory((const unsigned char*)data, length);
|
||||||
LOG(LogError) << "Could not initialize texture from memory, invalid data! (file path: " << mPath << ", data ptr: " << (size_t)data << ", reported size: " << length << ")";
|
// Get the size from the texture data
|
||||||
return;
|
mSize << mTextureData->width(), mTextureData->height();
|
||||||
}
|
mSourceSize << mTextureData->sourceWidth(), mTextureData->sourceHeight();
|
||||||
|
|
||||||
initFromPixels(imageRGBA.data(), width, height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureResource::deinit()
|
const Eigen::Vector2i TextureResource::getSize() const
|
||||||
{
|
{
|
||||||
if(mTextureID != 0)
|
return mSize;
|
||||||
{
|
|
||||||
glDeleteTextures(1, &mTextureID);
|
|
||||||
mTextureID = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Eigen::Vector2i& TextureResource::getSize() const
|
|
||||||
{
|
|
||||||
return mTextureSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TextureResource::isTiled() const
|
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)
|
if (mTextureData != nullptr)
|
||||||
glBindTexture(GL_TEXTURE_2D, mTextureID);
|
{
|
||||||
|
mTextureData->uploadAndBind();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
LOG(LogError) << "Tried to bind uninitialized texture!";
|
{
|
||||||
|
return sTextureDataManager.bind(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<TextureResource> TextureResource::get(const std::string& path, bool tile, bool forceLoad, bool dynamic)
|
||||||
std::shared_ptr<TextureResource> TextureResource::get(const std::string& path, bool tile)
|
|
||||||
{
|
{
|
||||||
std::shared_ptr<ResourceManager>& rm = ResourceManager::getInstance();
|
std::shared_ptr<ResourceManager>& rm = ResourceManager::getInstance();
|
||||||
|
|
||||||
const std::string canonicalPath = getCanonicalPath(path);
|
const std::string canonicalPath = getCanonicalPath(path);
|
||||||
|
|
||||||
if(canonicalPath.empty())
|
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
|
rm->addReloadable(tex); //make sure we get properly deinitialized even though we do nothing on reinitialization
|
||||||
return tex;
|
return tex;
|
||||||
}
|
}
|
||||||
|
@ -121,58 +126,100 @@ std::shared_ptr<TextureResource> TextureResource::get(const std::string& path, b
|
||||||
|
|
||||||
// need to create it
|
// need to create it
|
||||||
std::shared_ptr<TextureResource> tex;
|
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?
|
// 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
|
// Probably not. Add it to our map. We don't add SVGs because 2 svgs might be rasterized at different sizes
|
||||||
// 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));
|
|
||||||
sTextureMap[key] = std::weak_ptr<TextureResource>(tex);
|
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
|
bool TextureResource::isInitialized() const
|
||||||
{
|
{
|
||||||
return mTextureID != 0;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
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 TextureResource::getTotalMemUsage()
|
||||||
{
|
{
|
||||||
size_t total = 0;
|
size_t total = 0;
|
||||||
|
// Count up all textures that manage their own texture data
|
||||||
auto it = sTextureList.begin();
|
for (auto tex : sAllTextures)
|
||||||
while(it != sTextureList.end())
|
|
||||||
{
|
{
|
||||||
if((*it).expired())
|
if (tex->mTextureData != nullptr)
|
||||||
{
|
total += tex->mTextureData->getVRAMUsage();
|
||||||
// remove expired textures from the list
|
|
||||||
it = sTextureList.erase(it);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
// Now get the committed memory from the manager
|
||||||
total += (*it).lock()->getMemUsage();
|
total += sTextureDataManager.getCommittedSize();
|
||||||
it++;
|
// And the size of the loading queue
|
||||||
}
|
total += sTextureDataManager.getQueueSize();
|
||||||
|
|
||||||
return total;
|
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 "resources/ResourceManager.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
#include <list>
|
||||||
#include <Eigen/Dense>
|
#include <Eigen/Dense>
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
#include "resources/TextureData.h"
|
||||||
|
#include "resources/TextureDataManager.h"
|
||||||
#include GLHEADER
|
#include GLHEADER
|
||||||
|
|
||||||
// An OpenGL texture.
|
// An OpenGL texture.
|
||||||
|
@ -12,40 +16,43 @@
|
||||||
class TextureResource : public IReloadable
|
class TextureResource : public IReloadable
|
||||||
{
|
{
|
||||||
public:
|
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 ~TextureResource();
|
||||||
|
|
||||||
virtual void unload(std::shared_ptr<ResourceManager>& rm) override;
|
|
||||||
virtual void reload(std::shared_ptr<ResourceManager>& rm) override;
|
|
||||||
|
|
||||||
bool isInitialized() const;
|
bool isInitialized() const;
|
||||||
bool isTiled() 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).
|
const Eigen::Vector2i getSize() const;
|
||||||
virtual void initFromMemory(const char* file, size_t length);
|
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 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:
|
protected:
|
||||||
TextureResource(const std::string& path, bool tile);
|
TextureResource(const std::string& path, bool tile, bool dynamic);
|
||||||
void deinit();
|
virtual void unload(std::shared_ptr<ResourceManager>& rm);
|
||||||
|
virtual void reload(std::shared_ptr<ResourceManager>& rm);
|
||||||
Eigen::Vector2i mTextureSize;
|
|
||||||
const std::string mPath;
|
|
||||||
const bool mTile;
|
|
||||||
|
|
||||||
private:
|
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;
|
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::map< TextureKeyType, std::weak_ptr<TextureResource> > sTextureMap; // map of textures, used to prevent duplicate textures
|
||||||
|
static std::set<TextureResource*> sAllTextures; // Set of all textures, used for memory management
|
||||||
static std::list< std::weak_ptr<TextureResource> > sTextureList; // list of all textures, used for memory approximations
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue