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
Loading
Loading
Loading
Loading
+163 −11
Original line number Diff line number Diff line
#include "texturemanager.h"
#include "texture.h"
#include "singleton.h"
#include "gltextureformattraits.h"


cgt::TextureManager::TextureManager()
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;
@@ -80,6 +189,9 @@ GLuint cgt::TextureManager::generateId(GLenum type, const cgt::svec3 & dimension
        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";
    }
}
+56 −5
Original line number Diff line number Diff line
@@ -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_;
    };

}