Starting from 2021-07-01, all LRZ GitLab users will be required to explicitly accept the GitLab Terms of Service. Please see the detailed information at https://doku.lrz.de/display/PUBLIC/GitLab and make sure that your projects conform to the requirements.

texturemanager.cpp 9.03 KB
Newer Older
Jakob Weiss's avatar
Jakob Weiss committed
1 2 3
#include "texturemanager.h"
#include "texture.h"
#include "singleton.h"
Jakob Weiss's avatar
Jakob Weiss committed
4
#include "gltextureformattraits.h"
Jakob Weiss's avatar
Jakob Weiss committed
5

Jakob Weiss's avatar
Jakob Weiss committed
6 7
#include "opengljobprocessor.h"

8
#define ENABLE_TEXTURE_POOL
Jakob Weiss's avatar
Jakob Weiss committed
9

Jakob Weiss's avatar
Jakob Weiss committed
10 11 12 13 14 15 16 17 18 19 20 21 22 23
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");

Jakob Weiss's avatar
Jakob Weiss committed
24
cgt::TextureManager::TextureManager()
Jakob Weiss's avatar
Jakob Weiss committed
25 26 27
    : RunnableWithConditionalWait("Texture Manager")
    , activeAllocatedMemory_(0)
    , texturePoolMemory_(0)
Jakob Weiss's avatar
Jakob Weiss committed
28
{
Jakob Weiss's avatar
Jakob Weiss committed
29 30 31 32
#ifdef ENABLE_TEXTURE_POOL
    // start the garbage collection thread
    start();
#endif ENABLE_TEXTURE_POOl
Jakob Weiss's avatar
Jakob Weiss committed
33 34 35 36
}

cgt::TextureManager::~TextureManager()
{
Jakob Weiss's avatar
Jakob Weiss committed
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
#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_)
    {
Jakob Weiss's avatar
Jakob Weiss committed
60 61 62
        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;
Jakob Weiss's avatar
Jakob Weiss committed
63 64 65
        ss << "\t * [" << size.x << "," << size.y << "," << size.z
            << "] " << textureTypeToStr(textureType) << " " << tft.internalFormatName() << std::endl;

Jakob Weiss's avatar
Jakob Weiss committed
66
        glDeleteTextures(1, &it.second.first);
Jakob Weiss's avatar
Jakob Weiss committed
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

        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)));

Jakob Weiss's avatar
Jakob Weiss committed
83
    dbgOut << texturePoolMemory_ << " Bytes of in texture pool, additionally " << activeAllocatedMemory_ << " Bytes still in use." << std::endl;
Jakob Weiss's avatar
Jakob Weiss committed
84
    size_t releasedBytes = 0;
Jakob Weiss's avatar
Jakob Weiss committed
85 86

    std::stringstream ss;
Jakob Weiss's avatar
Jakob Weiss committed
87 88
    std::vector<GLuint> idsToRelease;
    for (auto it = texturePool_.begin(); it != texturePool_.end(); )
Jakob Weiss's avatar
Jakob Weiss committed
89
    {
Jakob Weiss's avatar
Jakob Weiss committed
90 91 92
        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;
Jakob Weiss's avatar
Jakob Weiss committed
93

Jakob Weiss's avatar
Jakob Weiss committed
94 95 96 97
        // 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;
Jakob Weiss's avatar
Jakob Weiss committed
98

Jakob Weiss's avatar
Jakob Weiss committed
99 100 101 102 103 104 105 106
            releasedBytes += textureByteSize(it->first);
            texturePoolMemory_ -= textureByteSize(it->first);

            idsToRelease.push_back(it->second.first);
            it = texturePool_.erase(it);
        }
        else
            ++it;
Jakob Weiss's avatar
Jakob Weiss committed
107
    }
Jakob Weiss's avatar
Jakob Weiss committed
108

Jakob Weiss's avatar
Jakob Weiss committed
109 110 111 112 113 114 115 116
    if (!idsToRelease.empty()) {
        dbgOut << "Released " << releasedBytes << " Bytes of texture data:" << std::endl << ss.str();
        std::string debugMessage = dbgOut.str();
        GLJobProc.enqueueJob([=] {
            glDeleteTextures(static_cast<GLsizei>(idsToRelease.size()), idsToRelease.data());
            LDEBUG(debugMessage);
        });
    }
Jakob Weiss's avatar
Jakob Weiss committed
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
}

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
Jakob Weiss's avatar
Jakob Weiss committed
135 136 137 138
}

GLuint cgt::TextureManager::generateId(GLenum type, const cgt::svec3 & dimensions, GLenum internalFormat)
{
Jakob Weiss's avatar
Jakob Weiss committed
139
    cache_key_t cacheKey{ type, dimensions.x, dimensions.y, dimensions.z, internalFormat };
Jakob Weiss's avatar
Jakob Weiss committed
140

Jakob Weiss's avatar
Jakob Weiss committed
141
    cgtAssert(cgt::hmul(dimensions) > 0, "At least one dimension is zero!");
Jakob Weiss's avatar
Jakob Weiss committed
142 143
    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!");
Jakob Weiss's avatar
Jakob Weiss committed
144

Jakob Weiss's avatar
Jakob Weiss committed
145 146 147 148
#ifdef ENABLE_TEXTURE_POOL
    std::lock_guard<std::mutex> scope_lock(accessMutex_);

    GLuint id;
Jakob Weiss's avatar
Jakob Weiss committed
149
    auto it = texturePool_.find(cacheKey);
Jakob Weiss's avatar
Jakob Weiss committed
150
    if (it != texturePool_.end()) {
Jakob Weiss's avatar
Jakob Weiss committed
151
        id = it->second.first;
Jakob Weiss's avatar
Jakob Weiss committed
152
        texturePool_.erase(it);
Jakob Weiss's avatar
Jakob Weiss committed
153 154 155

        activeAllocatedMemory_ += textureByteSize(cacheKey);
        texturePoolMemory_ -= textureByteSize(cacheKey);
Jakob Weiss's avatar
Jakob Weiss committed
156 157 158 159 160 161 162 163
    }
    else {
        glGenTextures(1, &id);

        glBindTexture(type, id);

        switch (type) {
        case GL_TEXTURE_1D:
Jakob Weiss's avatar
Jakob Weiss committed
164
            glTexImage1D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), 0,
Jakob Weiss's avatar
Jakob Weiss committed
165
                Texture::calcMatchingPixelFormat(internalFormat), Texture::calcMatchingPixelDataType(internalFormat), nullptr);
Jakob Weiss's avatar
Jakob Weiss committed
166 167 168 169
            break;

        case GL_TEXTURE_1D_ARRAY: // fallthrough
        case GL_TEXTURE_2D:
Jakob Weiss's avatar
Jakob Weiss committed
170
            glTexImage2D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), static_cast<GLsizei>(dimensions.y), 0,
Jakob Weiss's avatar
Jakob Weiss committed
171
                Texture::calcMatchingPixelFormat(internalFormat), Texture::calcMatchingPixelDataType(internalFormat), nullptr);
Jakob Weiss's avatar
Jakob Weiss committed
172 173 174 175
            break;

        case GL_TEXTURE_2D_ARRAY: // fallthrough
        case GL_TEXTURE_3D:
Jakob Weiss's avatar
Jakob Weiss committed
176
            glTexImage3D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), static_cast<GLsizei>(dimensions.y), static_cast<GLsizei>(dimensions.z), 0,
Jakob Weiss's avatar
Jakob Weiss committed
177
                Texture::calcMatchingPixelFormat(internalFormat), Texture::calcMatchingPixelDataType(internalFormat), nullptr);
Jakob Weiss's avatar
Jakob Weiss committed
178 179 180
            break;
        }
        LGL_ERROR;
Jakob Weiss's avatar
Jakob Weiss committed
181 182

        activeAllocatedMemory_ += textureByteSize(cacheKey);
Jakob Weiss's avatar
Jakob Weiss committed
183 184 185 186 187 188 189 190 191
    }
#else
    GLuint id;
    glGenTextures(1, &id);

    glBindTexture(type, id);

    switch (type) {
    case GL_TEXTURE_1D:
Jakob Weiss's avatar
Jakob Weiss committed
192
        glTexImage1D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), 0,
Jakob Weiss's avatar
Jakob Weiss committed
193
            Texture::calcMatchingPixelFormat(internalFormat), Texture::calcMatchingPixelDataType(internalFormat), nullptr);
Jakob Weiss's avatar
Jakob Weiss committed
194 195 196 197
        break;

    case GL_TEXTURE_1D_ARRAY: // fallthrough
    case GL_TEXTURE_2D:
Jakob Weiss's avatar
Jakob Weiss committed
198
        glTexImage2D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), static_cast<GLsizei>(dimensions.y), 0,
Jakob Weiss's avatar
Jakob Weiss committed
199
            Texture::calcMatchingPixelFormat(internalFormat), Texture::calcMatchingPixelDataType(internalFormat), nullptr);
Jakob Weiss's avatar
Jakob Weiss committed
200 201 202 203
        break;

    case GL_TEXTURE_2D_ARRAY: // fallthrough
    case GL_TEXTURE_3D:
Jakob Weiss's avatar
Jakob Weiss committed
204
        glTexImage3D(type, 0, internalFormat, static_cast<GLsizei>(dimensions.x), static_cast<GLsizei>(dimensions.y), static_cast<GLsizei>(dimensions.z), 0,
Jakob Weiss's avatar
Jakob Weiss committed
205
            Texture::calcMatchingPixelFormat(internalFormat), Texture::calcMatchingPixelDataType(internalFormat), nullptr);
Jakob Weiss's avatar
Jakob Weiss committed
206 207 208
        break;
    }
    LGL_ERROR;
Jakob Weiss's avatar
Jakob Weiss committed
209 210 211

    activeAllocatedMemory_ += textureByteSize(cacheKey);

Jakob Weiss's avatar
Jakob Weiss committed
212 213 214 215 216 217
#endif
    return id;
}

void cgt::TextureManager::releaseId(GLuint id, GLenum type, const cgt::svec3 & dimensions, GLenum internalFormat)
{
Jakob Weiss's avatar
Jakob Weiss committed
218
    cache_key_t cacheKey{ type, dimensions.x, dimensions.y, dimensions.z, internalFormat };
Jakob Weiss's avatar
Jakob Weiss committed
219 220
#ifdef ENABLE_TEXTURE_POOL
    std::lock_guard<std::mutex> scope_lock(accessMutex_);
Jakob Weiss's avatar
Jakob Weiss committed
221
    texturePool_.insert(std::make_pair(cacheKey, std::make_pair(id, 0)));
Jakob Weiss's avatar
Jakob Weiss committed
222 223
    activeAllocatedMemory_ -= textureByteSize(cacheKey);
    texturePoolMemory_ += textureByteSize(cacheKey);
Jakob Weiss's avatar
Jakob Weiss committed
224 225
#else
    glDeleteTextures(1, &id);
Jakob Weiss's avatar
Jakob Weiss committed
226
    activeAllocatedMemory_ -= textureByteSize(cacheKey);
Jakob Weiss's avatar
Jakob Weiss committed
227 228
#endif
}
Jakob Weiss's avatar
Jakob Weiss committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244

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)
{
Jakob Weiss's avatar
Jakob Weiss committed
245
     return key.size_x * key.size_y * key.size_z * GLTextureFormatTraits::get(key.internalFormat).bpp();
Jakob Weiss's avatar
Jakob Weiss committed
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
}

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