Implement the texture freelist in ResourcePool.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 21 Jan 2014 21:26:12 +0000 (22:26 +0100)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Tue, 21 Jan 2014 21:26:12 +0000 (22:26 +0100)
resource_pool.cpp
resource_pool.h
ycbcr_input.cpp

index e5f8988..e9241fc 100644 (file)
 
 using namespace std;
 
-ResourcePool::ResourcePool(size_t program_freelist_max_length)
-       : program_freelist_max_length(program_freelist_max_length) {
+ResourcePool::ResourcePool(size_t program_freelist_max_length,
+                           size_t texture_freelist_max_bytes)
+       : program_freelist_max_length(program_freelist_max_length),
+         texture_freelist_max_bytes(texture_freelist_max_bytes),
+         texture_freelist_bytes(0) {
        pthread_mutex_init(&lock, NULL);
 }
 
@@ -29,6 +32,20 @@ ResourcePool::~ResourcePool()
        }
        assert(programs.empty());
        assert(program_shaders.empty());
+
+       for (list<GLuint>::const_iterator freelist_it = texture_freelist.begin();
+            freelist_it != texture_freelist.end();
+            ++freelist_it) {
+               GLuint free_texture_num = program_freelist.front();
+               program_freelist.pop_front();
+               assert(texture_formats.count(free_texture_num) != 0);
+               texture_freelist_bytes -= estimate_texture_size(texture_formats[free_texture_num]);
+               texture_formats.erase(free_texture_num);
+               glDeleteTextures(1, &free_texture_num);
+               check_error();
+       }
+       assert(texture_formats.empty());
+       assert(texture_freelist_bytes == 0);
 }
 
 void ResourcePool::delete_program(GLuint glsl_program_num)
@@ -130,6 +147,24 @@ void ResourcePool::release_glsl_program(GLuint glsl_program_num)
 
 GLuint ResourcePool::create_2d_texture(GLint internal_format, GLsizei width, GLsizei height)
 {
+       pthread_mutex_lock(&lock);
+       // See if there's a texture on the freelist we can use.
+       for (list<GLuint>::iterator freelist_it = texture_freelist.begin();
+            freelist_it != texture_freelist.end();
+            ++freelist_it) {
+               GLuint texture_num = *freelist_it;
+               map<GLuint, Texture2D>::const_iterator format_it = texture_formats.find(texture_num);
+               assert(format_it != texture_formats.end());
+               if (format_it->second.internal_format == internal_format &&
+                   format_it->second.width == width &&
+                   format_it->second.height == height) {
+                       texture_freelist_bytes -= estimate_texture_size(format_it->second);
+                       texture_freelist.erase(freelist_it);
+                       pthread_mutex_unlock(&lock);
+                       return texture_num;
+               }
+       }
+
        // Find any reasonable format given the internal format; OpenGL validates it
        // even though we give NULL as pointer.
        GLenum format;
@@ -162,11 +197,64 @@ GLuint ResourcePool::create_2d_texture(GLint internal_format, GLsizei width, GLs
        glBindTexture(GL_TEXTURE_2D, 0);
        check_error();
 
+       Texture2D texture_format;
+       texture_format.internal_format = internal_format;
+       texture_format.width = width;
+       texture_format.height = height;
+       assert(texture_formats.count(texture_num) == 0);
+       texture_formats.insert(make_pair(texture_num, texture_format));
+
+       pthread_mutex_unlock(&lock);
        return texture_num;
 }
 
 void ResourcePool::release_2d_texture(GLuint texture_num)
 {
-       glDeleteTextures(1, &texture_num);
-       check_error();
+       pthread_mutex_lock(&lock);
+       texture_freelist.push_front(texture_num);
+       assert(texture_formats.count(texture_num) != 0);
+       texture_freelist_bytes += estimate_texture_size(texture_formats[texture_num]);
+
+       while (texture_freelist_bytes > texture_freelist_max_bytes) {
+               GLuint free_texture_num = texture_freelist.front();
+               texture_freelist.pop_front();
+               assert(texture_formats.count(free_texture_num) != 0);
+               texture_freelist_bytes -= estimate_texture_size(texture_formats[free_texture_num]);
+               texture_formats.erase(free_texture_num);
+               glDeleteTextures(1, &free_texture_num);
+               check_error();
+       }
+       pthread_mutex_unlock(&lock);
+}
+
+size_t ResourcePool::estimate_texture_size(const Texture2D &texture_format)
+{
+       size_t bytes_per_pixel;
+
+       switch (texture_format.internal_format) {
+       case GL_RGBA32F_ARB:
+               bytes_per_pixel = 16;
+               break;
+       case GL_RGBA16F_ARB:
+               bytes_per_pixel = 8;
+               break;
+       case GL_RGBA8:
+       case GL_SRGB8_ALPHA8:
+               bytes_per_pixel = 4;
+               break;
+       case GL_RG32F:
+               bytes_per_pixel = 8;
+               break;
+       case GL_RG16F:
+               bytes_per_pixel = 4;
+               break;
+       case GL_LUMINANCE8:
+               bytes_per_pixel = 1;
+               break;
+       default:
+               // TODO: Add more here as needed.
+               assert(false);
+       }
+
+       return texture_format.width * texture_format.height * bytes_per_pixel;
 }
index 6f4d22d..50e0da2 100644 (file)
@@ -29,7 +29,15 @@ public:
        // around after they are no longer in use (in case another EffectChain
        // wants that exact program later). Shaders are expensive to compile and do not
        // need a lot of resources to keep around, so this should be a reasonable number.
-       ResourcePool(size_t program_freelist_max_length = 100);
+       //
+       // texture_freelist_max_bytes is how many bytes of unused textures to keep around
+       // after they are no longer in use (in case a new texture of the same dimensions
+       // and format is needed). Note that the size estimate is very coarse; it does not
+       // take into account padding, metadata, and most importantly mipmapping.
+       // This means you should be prepared for actual memory usage of the freelist being
+       // twice this estimate or more.
+       ResourcePool(size_t program_freelist_max_length = 100,
+                    size_t texture_freelist_max_bytes = 100 << 20);  // 100 MB.
        ~ResourcePool();
 
        // All remaining functions are intended for calls from EffectChain only.
@@ -57,7 +65,7 @@ private:
        // Protects all the other elements in the class.
        pthread_mutex_t lock;
 
-       size_t program_freelist_max_length;
+       size_t program_freelist_max_length, texture_freelist_max_bytes;
                
        // A mapping from vertex/fragment shader source strings to compiled program number.
        std::map<std::pair<std::string, std::string>, GLuint> programs;
@@ -74,6 +82,27 @@ private:
        // Once this reaches <program_freelist_max_length>, the last element
        // will be deleted.
        std::list<GLuint> program_freelist;
+
+       struct Texture2D {
+               GLint internal_format;
+               GLsizei width, height;
+       };
+
+       // A mapping from texture number to format details. This is filled if the
+       // texture is given out to a client or on the freelist, but not if it is
+       // deleted from the freelist.
+       std::map<GLuint, Texture2D> texture_formats;
+
+       // A list of all textures that are release but not freed (most recently freed
+       // first), and an estimate of their current memory usage. Once
+       // <texture_freelist_bytes> goes above <texture_freelist_max_bytes>,
+       // elements are deleted off the end of the list until we are under the limit
+       // again.
+       std::list<GLuint> texture_freelist;
+       size_t texture_freelist_bytes;
+
+       // See the caveats at the constructor.
+       static size_t estimate_texture_size(const Texture2D &texture_format);
 };
 
 #endif  // !defined(_MOVIT_RESOURCE_POOL_H)
index 87140be..eebb864 100644 (file)
@@ -66,7 +66,8 @@ YCbCrInput::YCbCrInput(const ImageFormat &image_format,
          finalized(false),
          needs_mipmaps(false),
          width(width),
-         height(height)
+         height(height),
+         resource_pool(NULL)
 {
        pbos[0] = pbos[1] = pbos[2] = 0;
        texture_num[0] = texture_num[1] = texture_num[2] = 0;