#include "texturemanager.h" #include "texture.h" #include "singleton.h" #include "gltextureformattraits.h" #include "opengljobprocessor.h" #define ENABLE_TEXTURE_POOL namespace { /// private class to format iostream to use points as a thousands separator class PointSeparator : public std::numpunct { public: PointSeparator(std::size_t refs) : std::numpunct(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() { #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(it.first.internalFormat); cgt::svec3 size(it.first.size_x, it.first.size_y, it.first.size_z); GLenum textureType = it.first.type; ss << "\t * [" << size.x << "," << size.y << "," << size.z << "] " << textureTypeToStr(textureType) << " " << tft.internalFormatName() << std::endl; glDeleteTextures(1, &it.second.first); 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." << std::endl; size_t releasedBytes = 0; std::stringstream ss; std::vector idsToRelease; for (auto it = texturePool_.begin(); it != texturePool_.end(); ) { auto tft = GLTextureFormatTraits::get(it->first.internalFormat); cgt::svec3 size(it->first.size_x, it->first.size_y, it->first.size_z); GLenum textureType = it->first.type; // Check if pool entry has expired if (++(it->second.second) >= garbageLifetime) { ss << "\t * [" << size.x << "," << size.y << "," << size.z << "] " << textureTypeToStr(textureType) << " " << tft.internalFormatName() << std::endl; releasedBytes += textureByteSize(it->first); texturePoolMemory_ -= textureByteSize(it->first); idsToRelease.push_back(it->second.first); it = texturePool_.erase(it); } else ++it; } if (!idsToRelease.empty()) { dbgOut << "Released " << releasedBytes << " Bytes of texture data:" << std::endl << ss.str(); std::string debugMessage = dbgOut.str(); GLJobProc.enqueueJob([=] { glDeleteTextures(static_cast(idsToRelease.size()), idsToRelease.data()); LDEBUG(debugMessage); }); } } void cgt::TextureManager::forceGarbageCollection() { #ifdef ENABLE_TEXTURE_POOL std::lock_guard scope_lock(accessMutex_); performGarbageCollection(); #endif ENABLE_TEXTURE_POOL } void cgt::TextureManager::forceClearPool() { #ifdef ENABLE_TEXTURE_POOL std::lock_guard 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 }; cgtAssert(cgt::hmul(dimensions) > 0, "At least one dimension is zero!"); cgtAssert(type != GL_TEXTURE_1D || (dimensions.y == 1 && dimensions.z == 1), "1D texture but higher dimensions are not 1!"); cgtAssert(type != GL_TEXTURE_2D || dimensions.z == 1, "2D texture but z is not 1!"); #ifdef ENABLE_TEXTURE_POOL std::lock_guard scope_lock(accessMutex_); GLuint id; auto it = texturePool_.find(cacheKey); if (it != texturePool_.end()) { id = it->second.first; texturePool_.erase(it); activeAllocatedMemory_ += textureByteSize(cacheKey); texturePoolMemory_ -= textureByteSize(cacheKey); } else { glGenTextures(1, &id); glBindTexture(type, id); switch (type) { case GL_TEXTURE_1D: glTexImage1D(type, 0, internalFormat, static_cast(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(dimensions.x), static_cast(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(dimensions.x), static_cast(dimensions.y), static_cast(dimensions.z), 0, Texture::calcMatchingPixelFormat(internalFormat), Texture::calcMatchingPixelDataType(internalFormat), nullptr); break; } LGL_ERROR; activeAllocatedMemory_ += textureByteSize(cacheKey); } #else GLuint id; glGenTextures(1, &id); glBindTexture(type, id); switch (type) { case GL_TEXTURE_1D: glTexImage1D(type, 0, internalFormat, static_cast(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(dimensions.x), static_cast(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(dimensions.x), static_cast(dimensions.y), static_cast(dimensions.z), 0, Texture::calcMatchingPixelFormat(internalFormat), Texture::calcMatchingPixelDataType(internalFormat), nullptr); break; } LGL_ERROR; activeAllocatedMemory_ += textureByteSize(cacheKey); #endif return id; } void cgt::TextureManager::releaseId(GLuint id, 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 scope_lock(accessMutex_); texturePool_.insert(std::make_pair(cacheKey, std::make_pair(id, 0))); activeAllocatedMemory_ -= textureByteSize(cacheKey); texturePoolMemory_ += textureByteSize(cacheKey); #else glDeleteTextures(1, &id); activeAllocatedMemory_ -= textureByteSize(cacheKey); #endif } void cgt::TextureManager::run() { std::unique_lock scope_lock(accessMutex_); while (!_stopExecution) { _evaluationCondition.wait_for(scope_lock, std::chrono::milliseconds(garbageCollectionDelayMs)); // could have changed in the meantime if(!_stopExecution) performGarbageCollection(); } } size_t cgt::TextureManager::textureByteSize(const cache_key_t & key) { return key.size_x * key.size_y * key.size_z * GLTextureFormatTraits::get(key.internalFormat).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"; } }