Notice: If you are member of any public project or group, please make sure that your GitLab username is not the same as the LRZ identifier/Kennung (see https://gitlab.lrz.de/profile/account). Please change your username if necessary. For more information see the section "Public projects / Öffentliche Projekte" at https://doku.lrz.de/display/PUBLIC/GitLab . Thank you!

Commit ff4c2359 authored by Jakob Weiss's avatar Jakob Weiss

Enhanced Texture Manager

Now spawns a garbage collection thread that periodically clears the cache. Still not optimal but at least gets rid of unused memory at some point. For next iteration, a "smart" garbage collection (time- or query based heuristic)
parent 346023e4
#include "texturemanager.h"
#include "texture.h"
#include "singleton.h"
#include "gltextureformattraits.h"
namespace {
/// private class to format iostream to use points as a thousands separator
class PointSeparator : public std::numpunct<char>
{
public:
PointSeparator(std::size_t refs) : std::numpunct<char>(refs) {}
protected:
char do_thousands_sep() const { return '.'; }
std::string do_grouping() const { return "\03"; }
};
}
const std::string cgt::TextureManager::loggerCat_("cgt.TextureManager");
cgt::TextureManager::TextureManager()
: RunnableWithConditionalWait("Texture Manager")
, activeAllocatedMemory_(0)
, texturePoolMemory_(0)
{
#ifdef ENABLE_TEXTURE_POOL
// start the garbage collection thread
start();
#endif ENABLE_TEXTURE_POOl
}
cgt::TextureManager::~TextureManager()
{
std::lock_guard<std::mutex> scope_lock(accessMutex_);
std::cout << "[TextureManager] releasing texture pool with " << texturePool_.size() << " textures..." << std::endl;
for (auto& it : texturePool_) {
std::cout << "Deleting texture of size [" << std::get<1>(it.first) << "," << std::get<2>(it.first) << "," << std::get<3>(it.first)
<< "] of type " << std::get<0>(it.first) << " " << std::get<3>(it.first) << std::endl;
#ifdef ENABLE_TEXTURE_POOL
// stop the GC thread and clear the pool
stop();
forceClearPool();
#endif ENABLE_TEXTURE_POOL
cgtAssert(texturePool_.empty(), "Texture pool not empty at the end of texture manager's life!");
cgtAssert(activeAllocatedMemory_ == 0, "There are still Textures in use after the Texture Manager is destroyed! This most likely means you are leaking cgt::Texture instances!");
}
void cgt::TextureManager::clearPool()
{
std::stringstream dbgOut;
dbgOut.imbue(std::locale(std::cout.getloc(), new PointSeparator(1)));
dbgOut << texturePoolMemory_ << " Bytes of in texture pool, additionally " << activeAllocatedMemory_ << " Bytes still in use.";
LDEBUG(dbgOut.str());
LDEBUG("releasing texture pool with " << texturePool_.size() << " textures...");
size_t totalBytes = 0;
std::stringstream ss;
for (auto& it : texturePool_)
{
auto tft = GLTextureFormatTraits::get(std::get<INTERNAL_FORMAT>(it.first));
cgt::svec3 size(std::get<SIZE_X>(it.first), std::get<SIZE_Y>(it.first), std::get<SIZE_Z>(it.first));
GLenum textureType = std::get<TEX_TYPE>(it.first);
ss << "\t * [" << size.x << "," << size.y << "," << size.z
<< "] " << textureTypeToStr(textureType) << " " << tft.internalFormatName() << std::endl;
glDeleteTextures(1, &it.second);
totalBytes += textureByteSize(it.first);
texturePoolMemory_ -= textureByteSize(it.first);
}
texturePool_.clear();
std::stringstream ss3;
ss3 << "Released " << totalBytes << " Bytes of texture data:" << std::endl << ss.str();
LDEBUG(ss3.str());
}
void cgt::TextureManager::performGarbageCollection()
{
std::stringstream dbgOut;
dbgOut.imbue(std::locale(std::cout.getloc(), new PointSeparator(1)));
dbgOut << texturePoolMemory_ << " Bytes of in texture pool, additionally " << activeAllocatedMemory_ << " Bytes still in use.";
LDEBUG(dbgOut.str());
LDEBUG("releasing texture pool with " << texturePool_.size() << " textures...");
size_t totalBytes = 0;
std::stringstream ss;
for (auto& it : texturePool_)
{
auto tft = GLTextureFormatTraits::get(std::get<INTERNAL_FORMAT>(it.first));
cgt::svec3 size(std::get<SIZE_X>(it.first), std::get<SIZE_Y>(it.first), std::get<SIZE_Z>(it.first));
GLenum textureType = std::get<TEX_TYPE>(it.first);
ss << "\t * [" << size.x << "," << size.y << "," << size.z
<< "] " << textureTypeToStr(textureType) << " " << tft.internalFormatName() << std::endl;
glDeleteTextures(1, &it.second);
totalBytes += textureByteSize(it.first);
texturePoolMemory_ -= textureByteSize(it.first);
}
texturePool_.clear();
std::stringstream ss3;
ss3 << "Released " << totalBytes << " Bytes of texture data:" << std::endl << ss.str();
LDEBUG(ss3.str());
}
void cgt::TextureManager::forceGarbageCollection()
{
#ifdef ENABLE_TEXTURE_POOL
std::lock_guard<std::mutex> scope_lock(accessMutex_);
performGarbageCollection();
#endif ENABLE_TEXTURE_POOL
}
void cgt::TextureManager::forceClearPool()
{
#ifdef ENABLE_TEXTURE_POOL
std::lock_guard<std::mutex> scope_lock(accessMutex_);
clearPool();
#endif ENABLE_TEXTURE_POOL
}
GLuint cgt::TextureManager::generateId(GLenum type, const cgt::svec3 & dimensions, GLenum internalFormat)
{
cache_key_t cacheKey(type, dimensions.x, dimensions.y, dimensions.z, internalFormat);
#ifdef ENABLE_TEXTURE_POOL
std::lock_guard<std::mutex> scope_lock(accessMutex_);
GLuint id;
auto it = texturePool_.find(std::make_tuple(type, dimensions.x, dimensions.y, dimensions.z, internalFormat));
auto it = texturePool_.find(cacheKey);
if (it != texturePool_.end()) {
id = it->second;
texturePool_.erase(it);
activeAllocatedMemory_ += textureByteSize(cacheKey);
texturePoolMemory_ -= textureByteSize(cacheKey);
}
else {
glGenTextures(1, &id);
......@@ -54,6 +161,8 @@ GLuint cgt::TextureManager::generateId(GLenum type, const cgt::svec3 & dimension
break;
}
LGL_ERROR;
activeAllocatedMemory_ += textureByteSize(cacheKey);
}
#else
GLuint id;
......@@ -63,23 +172,26 @@ GLuint cgt::TextureManager::generateId(GLenum type, const cgt::svec3 & dimension
switch (type) {
case GL_TEXTURE_1D:
glTexImage1D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), 0,
glTexImage1D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), 0,
Texture::calcMatchingPixelFormat(internalFormat), Texture::calcMatchingPixelDataType(internalFormat), nullptr);
break;
case GL_TEXTURE_1D_ARRAY: // fallthrough
case GL_TEXTURE_2D:
glTexImage2D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), static_cast<GLsizei>(dimensions.y), 0,
glTexImage2D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), static_cast<GLsizei>(dimensions.y), 0,
Texture::calcMatchingPixelFormat(internalFormat), Texture::calcMatchingPixelDataType(internalFormat), nullptr);
break;
case GL_TEXTURE_2D_ARRAY: // fallthrough
case GL_TEXTURE_3D:
glTexImage3D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), static_cast<GLsizei>(dimensions.y), static_cast<GLsizei>(dimensions.z), 0,
glTexImage3D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), static_cast<GLsizei>(dimensions.y), static_cast<GLsizei>(dimensions.z), 0,
Texture::calcMatchingPixelFormat(internalFormat), Texture::calcMatchingPixelDataType(internalFormat), nullptr);
break;
}
LGL_ERROR;
activeAllocatedMemory_ += textureByteSize(cacheKey);
#endif
return id;
}
......@@ -88,8 +200,48 @@ void cgt::TextureManager::releaseId(GLuint id, GLenum type, const cgt::svec3 & d
{
#ifdef ENABLE_TEXTURE_POOL
std::lock_guard<std::mutex> scope_lock(accessMutex_);
texturePool_.insert(std::make_pair(std::make_tuple(type, dimensions.x, dimensions.y, dimensions.z, internalFormat), id));
cache_key_t cacheKey(type, dimensions.x, dimensions.y, dimensions.z, internalFormat);
texturePool_.insert(std::make_pair(cacheKey, id));
activeAllocatedMemory_ -= textureByteSize(cacheKey);
texturePoolMemory_ += textureByteSize(cacheKey);
#else
glDeleteTextures(1, &id);
activeAllocatedMemory_ -= textureByteSize(cacheKey);
#endif
}
void cgt::TextureManager::run()
{
std::unique_lock<std::mutex> scope_lock(accessMutex_);
while (!_stopExecution) {
_evaluationCondition.wait_for(scope_lock, std::chrono::milliseconds(garbageCollectionDelayMs));
// could have changed in the meantime
if(!_stopExecution)
performGarbageCollection();
}
}
constexpr size_t cgt::TextureManager::textureByteSize(const cache_key_t & key)
{
return std::get<SIZE_X>(key) * std::get<SIZE_Y>(key) * std::get<SIZE_Z>(key) * GLTextureFormatTraits::get(std::get<INTERNAL_FORMAT>(key)).bpp();
}
std::string cgt::TextureManager::textureTypeToStr(GLenum tt)
{
switch (tt) {
case GL_TEXTURE_1D:
return "GL_TEXTURE_1D";
case GL_TEXTURE_1D_ARRAY:
return "GL_TEXTURE_1D_ARRAY";
case GL_TEXTURE_2D:
return "GL_TEXTURE_2D";
case GL_TEXTURE_2D_ARRAY:
return "GL_TEXTURE_2D_ARRAY";
case GL_TEXTURE_3D:
return "GL_TEXTURE_3D";
default:
return "Unknown";
}
}
......@@ -2,30 +2,81 @@
#define TEXTUREMANAGER_H__
#include "cgt/cgt_gl.h"
#include "cgt/runnable.h"
#include "cgt/singleton.h"
#include "cgt/vector.h"
#include <mutex>
#include <map>
#include <mutex>
#include <tuple>
namespace cgt {
class TextureManager : public Singleton<TextureManager> {
class TextureManager : public Singleton<TextureManager>, public RunnableWithConditionalWait {
public:
TextureManager();
~TextureManager();
/**
* Forces the manager to perform a (synchronous) garbage collection. Can be called manually by user code if
* GPU memory seems low. This method is threadsafe.
*/
void forceGarbageCollection();
/**
* Forces the texture pool to be cleared synchronously. This method is threadsafe.
*/
void forceClearPool();
GLuint generateId(GLenum type, const cgt::svec3& dimensions, GLenum internalFormat);
void releaseId(GLuint id, GLenum type, const cgt::svec3& dimensions, GLenum internalFormat);
/// \see Runnable::run()
void run() override;
private:
std::mutex accessMutex_;
static const std::string loggerCat_;
using cache_key_t = std::tuple<GLenum, size_t, size_t, size_t, GLenum>;
enum CacheEntryTupleIndex {
TEX_TYPE = 0,
SIZE_X = 1,
SIZE_Y = 2,
SIZE_Z = 3,
INTERNAL_FORMAT = 4
};
/// GC will be performed regularly at this interval, in [ms]
const size_t garbageCollectionDelayMs = 15000;
std::multimap<std::tuple<GLenum, size_t, size_t, size_t, GLenum>, GLuint> texturePool_;
/// Compute the memory size from the cache entry key
static constexpr size_t textureByteSize(const cache_key_t& key);
/// Convert the texture type to a humanly readable string
static std::string textureTypeToStr(GLenum tt);
/**
* Performs a garbage collection event. This method is NOT threadsafe.
*/
void performGarbageCollection();
/**
* Clears the current texture pool. This method is NOT threadsafe.
*/
void clearPool();
/// the actual texture pool containing all textures not in use
std::multimap<cache_key_t, GLuint> texturePool_;
/// Currently allocated GPU memory from textures still in use
size_t activeAllocatedMemory_;
/// Allocated GPU memory from textures in the texture pool
size_t texturePoolMemory_;
/// Mutex ensuring thread safety
std::mutex accessMutex_;
};
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment