The expiration time for new job artifacts in CI/CD pipelines is now 30 days (GitLab default). Previously generated artifacts in already completed jobs will not be affected by the change. The latest artifacts for all jobs in the latest successful pipelines will be kept. More information: https://gitlab.lrz.de/help/user/admin_area/settings/continuous_integration.html#default-artifacts-expiration

Commit ff4c2359 authored by Jakob Weiss's avatar Jakob Weiss
Browse files

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