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:
Jools Wills 2017-02-01 21:23:22 +00:00 committed by GitHub
commit bbeb51e43d
18 changed files with 904 additions and 285 deletions

View file

@ -2,7 +2,6 @@
#include "Renderer.h"
#include "Window.h"
#include "Util.h"
#include "resources/SVGResource.h"
RatingComponent::RatingComponent(Window* window) : GuiComponent(window)
{
@ -45,16 +44,13 @@ void RatingComponent::onSizeChanged()
else if(mSize.x() == 0)
mSize[0] = mSize.y() * NUM_RATING_STARS;
auto filledSVG = dynamic_cast<SVGResource*>(mFilledTexture.get());
auto unfilledSVG = dynamic_cast<SVGResource*>(mUnfilledTexture.get());
if(mSize.y() > 0)
{
size_t heightPx = (size_t)round(mSize.y());
if(filledSVG)
filledSVG->rasterizeAt(heightPx, heightPx);
if(unfilledSVG)
unfilledSVG->rasterizeAt(heightPx, heightPx);
if (mFilledTexture)
mFilledTexture->rasterizeAt(heightPx, heightPx);
if(mUnfilledTexture)
mUnfilledTexture->rasterizeAt(heightPx, heightPx);
}
updateVertices();

View file

@ -178,6 +178,12 @@ GuiMenu::GuiMenu(Window* window) : GuiComponent(window), mMenu(window, "MAIN MEN
s->addWithLabel("PARSE GAMESLISTS ONLY", parse_gamelists);
s->addSaveFunc([parse_gamelists] { Settings::getInstance()->setBool("ParseGamelistOnly", parse_gamelists->getState()); });
// maximum vram
auto max_vram = std::make_shared<SliderComponent>(mWindow, 0.f, 1000.f, 10.f, "Mb");
max_vram->setValue((float)(Settings::getInstance()->getInt("MaxVRAM")));
s->addWithLabel("VRAM LIMIT", max_vram);
s->addSaveFunc([max_vram] { Settings::getInstance()->setInt("MaxVRAM", (int)round(max_vram->getValue())); });
mWindow->pushGui(s);
});

View file

@ -74,6 +74,10 @@ bool parseArgs(int argc, char* argv[], unsigned int* width, unsigned int* height
}else if(strcmp(argv[i], "--scrape") == 0)
{
scrape_cmdline = true;
}else if(strcmp(argv[i], "--max-vram") == 0)
{
int maxVRAM = atoi(argv[i + 1]);
Settings::getInstance()->setInt("MaxVRAM", maxVRAM);
}else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
{
#ifdef WIN32
@ -99,6 +103,7 @@ bool parseArgs(int argc, char* argv[], unsigned int* width, unsigned int* height
"--scrape scrape using command line interface\n"
"--windowed not fullscreen, should be used with --resolution\n"
"--vsync [1/on or 0/off] turn vsync on or off (default is on)\n"
"--max-vram [size] Max VRAM to use in Mb before swapping. 0 for unlimited\n"
"--help, -h summon a sentient, angry tuba\n\n"
"More information available in README.md.\n";
return false; //exit after printing help

View file

@ -43,13 +43,13 @@ void SystemView::populate()
// make logo
if(theme->getElement("system", "logo", "image"))
{
ImageComponent* logo = new ImageComponent(mWindow);
ImageComponent* logo = new ImageComponent(mWindow, false, false);
logo->setMaxSize(Eigen::Vector2f(logoSize().x(), logoSize().y()));
logo->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
logo->setPosition((logoSize().x() - logo->getSize().x()) / 2, (logoSize().y() - logo->getSize().y()) / 2); // center
e.data.logo = std::shared_ptr<GuiComponent>(logo);
ImageComponent* logoSelected = new ImageComponent(mWindow);
ImageComponent* logoSelected = new ImageComponent(mWindow, false, false);
logoSelected->setMaxSize(Eigen::Vector2f(logoSize().x() * SELECTED_SCALE, logoSize().y() * SELECTED_SCALE * 0.70f));
logoSelected->applyTheme((*it)->getTheme(), "system", "logo", ThemeFlags::PATH);
logoSelected->setPosition((logoSize().x() - logoSelected->getSize().x()) / 2,

View file

@ -406,7 +406,9 @@ void ViewController::reloadAll()
mCurrentView = getGameListView(mState.getSystem());
}else if(mState.viewing == SYSTEM_SELECT)
{
mSystemListView->goToSystem(mState.getSystem(), false);
SystemData* system = mState.getSystem();
goToSystemView(SystemData::sSystemVector.front());
mSystemListView->goToSystem(system, false);
mCurrentView = mSystemListView;
}else{
goToSystemView(SystemData::sSystemVector.front());

View file

@ -53,8 +53,9 @@ set(CORE_HEADERS
# Resources
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.h
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.h
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/SVGResource.h
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.h
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.h
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.h
# Embedded assets (needed by ResourceManager)
${emulationstation-all_SOURCE_DIR}/data/Resources.h
@ -108,8 +109,9 @@ set(CORE_SOURCES
# Resources
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/Font.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ResourceManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/SVGResource.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.cpp
)
set(EMBEDDED_ASSET_SOURCES

View file

@ -69,6 +69,7 @@ void Settings::setDefaults()
mIntMap["ScreenSaverTime"] = 5*60*1000; // 5 minutes
mIntMap["ScraperResizeWidth"] = 400;
mIntMap["ScraperResizeHeight"] = 0;
mIntMap["MaxVRAM"] = 100;
mStringMap["TransitionStyle"] = "fade";
mStringMap["ThemeSet"] = "";
@ -154,4 +155,4 @@ void Settings::setMethodName(const std::string& name, type value) \
SETTINGS_GETSET(bool, mBoolMap, getBool, setBool);
SETTINGS_GETSET(int, mIntMap, getInt, setInt);
SETTINGS_GETSET(float, mFloatMap, getFloat, setFloat);
SETTINGS_GETSET(const std::string&, mStringMap, getString, setString);
SETTINGS_GETSET(const std::string&, mStringMap, getString, setString);

View file

@ -160,11 +160,12 @@ void Window::update(int deltaTime)
ss << std::fixed << std::setprecision(2) << ((float)mFrameTimeElapsed / (float)mFrameCountElapsed) << "ms";
// vram
float textureVramUsageMb = TextureResource::getTotalMemUsage() / 1000.0f / 1000.0f;;
float textureVramUsageMb = TextureResource::getTotalMemUsage() / 1000.0f / 1000.0f;
float textureTotalUsageMb = TextureResource::getTotalTextureSize() / 1000.0f / 1000.0f;
float fontVramUsageMb = Font::getTotalMemUsage() / 1000.0f / 1000.0f;;
float totalVramUsageMb = textureVramUsageMb + fontVramUsageMb;
ss << "\nVRAM: " << totalVramUsageMb << "mb (texs: " << textureVramUsageMb << "mb, fonts: " << fontVramUsageMb << "mb)";
ss << "\nFont VRAM: " << fontVramUsageMb << " Tex VRAM: " << textureVramUsageMb <<
" Tex Max: " << textureTotalUsageMb;
mFrameDataText = std::unique_ptr<TextCache>(mDefaultFonts.at(1)->buildTextCache(ss.str(), 50.f, 50.f, 0xFF00FFFF));
}
@ -242,7 +243,7 @@ void Window::renderLoadingScreen()
Renderer::setMatrix(trans);
Renderer::drawRect(0, 0, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0xFFFFFFFF);
ImageComponent splash(this);
ImageComponent splash(this, true);
splash.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f);
splash.setImage(":/splash.svg");
splash.setPosition((Renderer::getScreenWidth() - splash.getSize().x()) / 2, (Renderer::getScreenHeight() - splash.getSize().y()) / 2 * 0.6f);

View file

@ -6,7 +6,6 @@
#include "Renderer.h"
#include "ThemeData.h"
#include "Util.h"
#include "resources/SVGResource.h"
Eigen::Vector2i ImageComponent::getTextureSize() const
{
@ -22,8 +21,9 @@ Eigen::Vector2f ImageComponent::getCenter() const
mPosition.y() - (getSize().y() * mOrigin.y()) + getSize().y() / 2);
}
ImageComponent::ImageComponent(Window* window) : GuiComponent(window),
mTargetIsMax(false), mFlipX(false), mFlipY(false), mOrigin(0.0, 0.0), mTargetSize(0, 0), mColorShift(0xFFFFFFFF)
ImageComponent::ImageComponent(Window* window, bool forceLoad, bool dynamic) : GuiComponent(window),
mTargetIsMax(false), mFlipX(false), mFlipY(false), mOrigin(0.0, 0.0), mTargetSize(0, 0), mColorShift(0xFFFFFFFF),
mForceLoad(forceLoad), mDynamic(dynamic), mFadeOpacity(0.0f), mFading(false)
{
updateColors();
}
@ -37,9 +37,7 @@ void ImageComponent::resize()
if(!mTexture)
return;
SVGResource* svg = dynamic_cast<SVGResource*>(mTexture.get());
const Eigen::Vector2f textureSize = svg ? svg->getSourceImageSize() : Eigen::Vector2f((float)mTexture->getSize().x(), (float)mTexture->getSize().y());
const Eigen::Vector2f textureSize = mTexture->getSourceImageSize();
if(textureSize.isZero())
return;
@ -90,12 +88,8 @@ void ImageComponent::resize()
}
}
}
if(svg)
{
// mSize.y() should already be rounded
svg->rasterizeAt((int)round(mSize.x()), (int)round(mSize.y()));
}
// mSize.y() should already be rounded
mTexture->rasterizeAt((int)round(mSize.x()), (int)round(mSize.y()));
onSizeChanged();
}
@ -110,7 +104,7 @@ void ImageComponent::setImage(std::string path, bool tile)
if(path.empty() || !ResourceManager::getInstance()->fileExists(path))
mTexture.reset();
else
mTexture = TextureResource::get(path, tile);
mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic);
resize();
}
@ -166,6 +160,9 @@ void ImageComponent::setFlipY(bool flip)
void ImageComponent::setColorShift(unsigned int color)
{
mColorShift = color;
// Grab the opacity from the color shift because we may need to apply it if
// fading textures in
mOpacity = color & 0xff;
updateColors();
}
@ -247,7 +244,10 @@ void ImageComponent::render(const Eigen::Affine3f& parentTrans)
if(mTexture->isInitialized())
{
// actually draw the image
mTexture->bind();
// The bind() function returns false if the texture is not currently loaded. A blank
// texture is bound in this case but we want to handle a fade so it doesn't just 'jump' in
// when it finally loads
fadeIn(mTexture->bind());
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
@ -278,6 +278,47 @@ void ImageComponent::render(const Eigen::Affine3f& parentTrans)
GuiComponent::renderChildren(trans);
}
void ImageComponent::fadeIn(bool textureLoaded)
{
if (!mForceLoad)
{
if (!textureLoaded)
{
// Start the fade if this is the first time we've encountered the unloaded texture
if (!mFading)
{
// Start with a zero opacity and flag it as fading
mFadeOpacity = 0;
mFading = true;
// Set the colours to be translucent
mColorShift = (mColorShift >> 8 << 8) | 0;
updateColors();
}
}
else if (mFading)
{
// The texture is loaded and we need to fade it in. The fade is based on the frame rate
// and is 1/4 second if running at 60 frames per second although the actual value is not
// that important
int opacity = mFadeOpacity + 255 / 15;
// See if we've finished fading
if (opacity >= 255)
{
mFadeOpacity = 255;
mFading = false;
}
else
{
mFadeOpacity = (unsigned char)opacity;
}
// Apply the combination of the target opacity and current fade
float newOpacity = (float)mOpacity * ((float)mFadeOpacity / 255.0f);
mColorShift = (mColorShift >> 8 << 8) | (unsigned char)newOpacity;
updateColors();
}
}
}
bool ImageComponent::hasImage()
{
return (bool)mTexture;

View file

@ -12,7 +12,7 @@
class ImageComponent : public GuiComponent
{
public:
ImageComponent(Window* window);
ImageComponent(Window* window, bool forceLoad = false, bool dynamic = true);
virtual ~ImageComponent();
//Loads the image at the given filepath. Will tile if tile is true (retrieves texture as tiling, creates vertices accordingly).
@ -81,10 +81,15 @@ private:
void updateVertices();
void updateColors();
void fadeIn(bool textureLoaded);
unsigned int mColorShift;
std::shared_ptr<TextureResource> mTexture;
unsigned char mFadeOpacity;
bool mFading;
bool mForceLoad;
bool mDynamic;
};
#endif

View file

@ -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;
}

View file

@ -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;
};

View 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;
}

View 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;
};

View 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;
}

View 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;
};

View file

@ -5,108 +5,113 @@
#include "ImageIO.h"
#include "Renderer.h"
#include "Util.h"
#include "resources/SVGResource.h"
#include "Settings.h"
TextureDataManager TextureResource::sTextureDataManager;
std::map< TextureResource::TextureKeyType, std::weak_ptr<TextureResource> > TextureResource::sTextureMap;
std::list< std::weak_ptr<TextureResource> > TextureResource::sTextureList;
std::set<TextureResource*> TextureResource::sAllTextures;
TextureResource::TextureResource(const std::string& path, bool tile) :
mTextureID(0), mPath(path), mTextureSize(Eigen::Vector2i::Zero()), mTile(tile)
TextureResource::TextureResource(const std::string& path, bool tile, bool dynamic) : mTextureData(nullptr), mForceLoad(false)
{
// Create a texture data object for this texture
if (!path.empty())
{
// If there is a path then the 'dynamic' flag tells us whether to use the texture
// data manager to manage loading/unloading of this texture
std::shared_ptr<TextureData> data;
if (dynamic)
{
data = sTextureDataManager.add(this, tile);
data->initFromPath(path);
// Force the texture manager to load it using a blocking load
sTextureDataManager.load(data, true);
}
else
{
mTextureData = std::shared_ptr<TextureData>(new TextureData(tile));
data = mTextureData;
data->initFromPath(path);
// Load it so we can read the width/height
data->load();
}
mSize << data->width(), data->height();
mSourceSize << data->sourceWidth(), data->sourceHeight();
}
else
{
// Create a texture managed by this class because it cannot be dynamically loaded and unloaded
mTextureData = std::shared_ptr<TextureData>(new TextureData(tile));
}
sAllTextures.insert(this);
}
TextureResource::~TextureResource()
{
deinit();
}
if (mTextureData == nullptr)
sTextureDataManager.remove(this);
void TextureResource::unload(std::shared_ptr<ResourceManager>& rm)
{
deinit();
}
void TextureResource::reload(std::shared_ptr<ResourceManager>& rm)
{
if(!mPath.empty())
{
const ResourceData& data = rm->getFileData(mPath);
initFromMemory((const char*)data.ptr.get(), data.length);
}
sAllTextures.erase(sAllTextures.find(this));
}
void TextureResource::initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height)
{
deinit();
assert(width > 0 && height > 0);
//now for the openGL texture stuff
glGenTextures(1, &mTextureID);
glBindTexture(GL_TEXTURE_2D, mTextureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, dataRGBA);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
const GLint wrapMode = mTile ? GL_REPEAT : GL_CLAMP_TO_EDGE;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
mTextureSize << width, height;
// This is only valid if we have a local texture data object
assert(mTextureData != nullptr);
mTextureData->releaseVRAM();
mTextureData->releaseRAM();
mTextureData->initFromRGBA(dataRGBA, width, height);
// Cache the image dimensions
mSize << width, height;
mSourceSize << mTextureData->sourceWidth(), mTextureData->sourceHeight();
}
void TextureResource::initFromMemory(const char* data, size_t length)
{
size_t width, height;
std::vector<unsigned char> imageRGBA = ImageIO::loadFromMemoryRGBA32((const unsigned char*)(data), length, width, height);
if(imageRGBA.size() == 0)
{
LOG(LogError) << "Could not initialize texture from memory, invalid data! (file path: " << mPath << ", data ptr: " << (size_t)data << ", reported size: " << length << ")";
return;
}
initFromPixels(imageRGBA.data(), width, height);
// This is only valid if we have a local texture data object
assert(mTextureData != nullptr);
mTextureData->releaseVRAM();
mTextureData->releaseRAM();
mTextureData->initImageFromMemory((const unsigned char*)data, length);
// Get the size from the texture data
mSize << mTextureData->width(), mTextureData->height();
mSourceSize << mTextureData->sourceWidth(), mTextureData->sourceHeight();
}
void TextureResource::deinit()
const Eigen::Vector2i TextureResource::getSize() const
{
if(mTextureID != 0)
{
glDeleteTextures(1, &mTextureID);
mTextureID = 0;
}
}
const Eigen::Vector2i& TextureResource::getSize() const
{
return mTextureSize;
return mSize;
}
bool TextureResource::isTiled() const
{
return mTile;
if (mTextureData != nullptr)
return mTextureData->tiled();
std::shared_ptr<TextureData> data = sTextureDataManager.get(this);
return data->tiled();
}
void TextureResource::bind() const
bool TextureResource::bind()
{
if(mTextureID != 0)
glBindTexture(GL_TEXTURE_2D, mTextureID);
if (mTextureData != nullptr)
{
mTextureData->uploadAndBind();
return true;
}
else
LOG(LogError) << "Tried to bind uninitialized texture!";
{
return sTextureDataManager.bind(this);
}
}
std::shared_ptr<TextureResource> TextureResource::get(const std::string& path, bool tile)
std::shared_ptr<TextureResource> TextureResource::get(const std::string& path, bool tile, bool forceLoad, bool dynamic)
{
std::shared_ptr<ResourceManager>& rm = ResourceManager::getInstance();
const std::string canonicalPath = getCanonicalPath(path);
if(canonicalPath.empty())
{
std::shared_ptr<TextureResource> tex(new TextureResource("", tile));
std::shared_ptr<TextureResource> tex(new TextureResource("", tile, false));
rm->addReloadable(tex); //make sure we get properly deinitialized even though we do nothing on reinitialization
return tex;
}
@ -121,58 +126,100 @@ std::shared_ptr<TextureResource> TextureResource::get(const std::string& path, b
// need to create it
std::shared_ptr<TextureResource> tex;
tex = std::shared_ptr<TextureResource>(new TextureResource(key.first, tile, dynamic));
std::shared_ptr<TextureData> data = sTextureDataManager.get(tex.get());
// is it an SVG?
if(key.first.substr(key.first.size() - 4, std::string::npos) == ".svg")
if(key.first.substr(key.first.size() - 4, std::string::npos) != ".svg")
{
// probably
// don't add it to our map because 2 svgs might be rasterized at different sizes
tex = std::shared_ptr<SVGResource>(new SVGResource(key.first, tile));
sTextureList.push_back(tex); // add it to our list though
rm->addReloadable(tex);
tex->reload(rm);
return tex;
}else{
// normal texture
tex = std::shared_ptr<TextureResource>(new TextureResource(key.first, tile));
// Probably not. Add it to our map. We don't add SVGs because 2 svgs might be rasterized at different sizes
sTextureMap[key] = std::weak_ptr<TextureResource>(tex);
sTextureList.push_back(tex);
rm->addReloadable(tex);
tex->reload(ResourceManager::getInstance());
return tex;
}
// Add it to the reloadable list
rm->addReloadable(tex);
// Force load it if necessary. Note that it may get dumped from VRAM if we run low
if (forceLoad)
{
tex->mForceLoad = forceLoad;
data->load();
}
return tex;
}
// For scalable source images in textures we want to set the resolution to rasterize at
void TextureResource::rasterizeAt(size_t width, size_t height)
{
std::shared_ptr<TextureData> data;
if (mTextureData != nullptr)
data = mTextureData;
else
data = sTextureDataManager.get(this);
mSourceSize << (float)width, (float)height;
data->setSourceSize((float)width, (float)height);
if (mForceLoad || (mTextureData != nullptr))
data->load();
}
Eigen::Vector2f TextureResource::getSourceImageSize() const
{
return mSourceSize;
}
bool TextureResource::isInitialized() const
{
return mTextureID != 0;
}
size_t TextureResource::getMemUsage() const
{
if(!mTextureID || mTextureSize.x() == 0 || mTextureSize.y() == 0)
return 0;
return mTextureSize.x() * mTextureSize.y() * 4;
return true;
}
size_t TextureResource::getTotalMemUsage()
{
size_t total = 0;
auto it = sTextureList.begin();
while(it != sTextureList.end())
// Count up all textures that manage their own texture data
for (auto tex : sAllTextures)
{
if((*it).expired())
{
// remove expired textures from the list
it = sTextureList.erase(it);
continue;
}
total += (*it).lock()->getMemUsage();
it++;
if (tex->mTextureData != nullptr)
total += tex->mTextureData->getVRAMUsage();
}
// Now get the committed memory from the manager
total += sTextureDataManager.getCommittedSize();
// And the size of the loading queue
total += sTextureDataManager.getQueueSize();
return total;
}
size_t TextureResource::getTotalTextureSize()
{
size_t total = 0;
// Count up all textures that manage their own texture data
for (auto tex : sAllTextures)
{
if (tex->mTextureData != nullptr)
total += tex->getSize().x() * tex->getSize().y() * 4;
}
// Now get the total memory from the manager
total += sTextureDataManager.getTotalSize();
return total;
}
void TextureResource::unload(std::shared_ptr<ResourceManager>& rm)
{
// Release the texture's resources
std::shared_ptr<TextureData> data;
if (mTextureData == nullptr)
data = sTextureDataManager.get(this);
else
data = mTextureData;
data->releaseVRAM();
data->releaseRAM();
}
void TextureResource::reload(std::shared_ptr<ResourceManager>& rm)
{
// For dynamically loaded textures the texture manager will load them on demand.
// For manually loaded textures we have to reload them here
if (mTextureData)
mTextureData->load();
}

View file

@ -3,8 +3,12 @@
#include "resources/ResourceManager.h"
#include <string>
#include <set>
#include <list>
#include <Eigen/Dense>
#include "platform.h"
#include "resources/TextureData.h"
#include "resources/TextureDataManager.h"
#include GLHEADER
// An OpenGL texture.
@ -12,40 +16,43 @@
class TextureResource : public IReloadable
{
public:
static std::shared_ptr<TextureResource> get(const std::string& path, bool tile = false);
static std::shared_ptr<TextureResource> get(const std::string& path, bool tile = false, bool forceLoad = false, bool dynamic = true);
void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height);
virtual void initFromMemory(const char* file, size_t length);
// For scalable source images in textures we want to set the resolution to rasterize at
void rasterizeAt(size_t width, size_t height);
Eigen::Vector2f getSourceImageSize() const;
virtual ~TextureResource();
virtual void unload(std::shared_ptr<ResourceManager>& rm) override;
virtual void reload(std::shared_ptr<ResourceManager>& rm) override;
bool isInitialized() const;
bool isTiled() const;
const Eigen::Vector2i& getSize() const;
void bind() const;
// Warning: will NOT correctly reinitialize when this texture is reloaded (e.g. ES starts/stops playing a game).
virtual void initFromMemory(const char* file, size_t length);
// Warning: will NOT correctly reinitialize when this texture is reloaded (e.g. ES starts/stops playing a game).
void initFromPixels(const unsigned char* dataRGBA, size_t width, size_t height);
const Eigen::Vector2i getSize() const;
bool bind();
size_t getMemUsage() const; // returns an approximation of the VRAM used by this texture (in bytes)
static size_t getTotalMemUsage(); // returns an approximation of total VRAM used by textures (in bytes)
static size_t getTotalTextureSize(); // returns the number of bytes that would be used if all textures were in memory
protected:
TextureResource(const std::string& path, bool tile);
void deinit();
Eigen::Vector2i mTextureSize;
const std::string mPath;
const bool mTile;
TextureResource(const std::string& path, bool tile, bool dynamic);
virtual void unload(std::shared_ptr<ResourceManager>& rm);
virtual void reload(std::shared_ptr<ResourceManager>& rm);
private:
GLuint mTextureID;
// mTextureData is used for textures that are not loaded from a file - these ones
// are permanently allocated and cannot be loaded and unloaded based on resources
std::shared_ptr<TextureData> mTextureData;
// The texture data manager manages loading and unloading of filesystem based textures
static TextureDataManager sTextureDataManager;
Eigen::Vector2i mSize;
Eigen::Vector2f mSourceSize;
bool mForceLoad;
typedef std::pair<std::string, bool> TextureKeyType;
static std::map< TextureKeyType, std::weak_ptr<TextureResource> > sTextureMap; // map of textures, used to prevent duplicate textures
static std::list< std::weak_ptr<TextureResource> > sTextureList; // list of all textures, used for memory approximations
static std::set<TextureResource*> sAllTextures; // Set of all textures, used for memory management
};