]> git.sesse.net Git - movit/blob - resource_pool.cpp
Implement the texture freelist in ResourcePool.
[movit] / resource_pool.cpp
1 #include "resource_pool.h"
2
3 #include <stdio.h>
4 #include <pthread.h>
5
6 #include <algorithm>
7 #include <map>
8 #include <string>
9 #include <utility>
10
11 #include "init.h"
12 #include "util.h"
13
14 using namespace std;
15
16 ResourcePool::ResourcePool(size_t program_freelist_max_length,
17                            size_t texture_freelist_max_bytes)
18         : program_freelist_max_length(program_freelist_max_length),
19           texture_freelist_max_bytes(texture_freelist_max_bytes),
20           texture_freelist_bytes(0) {
21         pthread_mutex_init(&lock, NULL);
22 }
23
24 ResourcePool::~ResourcePool()
25 {
26         assert(program_refcount.empty());
27
28         for (list<GLuint>::const_iterator freelist_it = program_freelist.begin();
29              freelist_it != program_freelist.end();
30              ++freelist_it) {
31                 delete_program(*freelist_it);
32         }
33         assert(programs.empty());
34         assert(program_shaders.empty());
35
36         for (list<GLuint>::const_iterator freelist_it = texture_freelist.begin();
37              freelist_it != texture_freelist.end();
38              ++freelist_it) {
39                 GLuint free_texture_num = program_freelist.front();
40                 program_freelist.pop_front();
41                 assert(texture_formats.count(free_texture_num) != 0);
42                 texture_freelist_bytes -= estimate_texture_size(texture_formats[free_texture_num]);
43                 texture_formats.erase(free_texture_num);
44                 glDeleteTextures(1, &free_texture_num);
45                 check_error();
46         }
47         assert(texture_formats.empty());
48         assert(texture_freelist_bytes == 0);
49 }
50
51 void ResourcePool::delete_program(GLuint glsl_program_num)
52 {
53         bool found_program = false;
54         for (std::map<std::pair<std::string, std::string>, GLuint>::iterator program_it = programs.begin();
55              program_it != programs.end();
56              ++program_it) {
57                 if (program_it->second == glsl_program_num) {
58                         programs.erase(program_it);
59                         found_program = true;
60                         break;
61                 }
62         }
63         assert(found_program);
64         glDeleteProgram(glsl_program_num);
65
66         std::map<GLuint, std::pair<GLuint, GLuint> >::iterator shader_it =
67                 program_shaders.find(glsl_program_num);
68         assert(shader_it != program_shaders.end());
69
70         glDeleteShader(shader_it->second.first);
71         glDeleteShader(shader_it->second.second);
72         program_shaders.erase(shader_it);
73 }
74
75 GLuint ResourcePool::compile_glsl_program(const string& vertex_shader, const string& fragment_shader)
76 {
77         GLuint glsl_program_num;
78         pthread_mutex_lock(&lock);
79         const pair<string, string> key(vertex_shader, fragment_shader);
80         if (programs.count(key)) {
81                 // Already in the cache. Increment the refcount, or take it off the freelist
82                 // if it's zero.
83                 glsl_program_num = programs[key];
84                 map<GLuint, int>::iterator refcount_it = program_refcount.find(glsl_program_num);
85                 if (refcount_it != program_refcount.end()) {
86                         ++refcount_it->second;
87                 } else {
88                         list<GLuint>::iterator freelist_it =
89                                 find(program_freelist.begin(), program_freelist.end(), glsl_program_num);
90                         assert(freelist_it != program_freelist.end());
91                         program_freelist.erase(freelist_it);
92                         program_refcount.insert(make_pair(glsl_program_num, 1));
93                 }
94         } else {
95                 // Not in the cache. Compile the shaders.
96                 glsl_program_num = glCreateProgram();
97                 GLuint vs_obj = compile_shader(vertex_shader, GL_VERTEX_SHADER);
98                 GLuint fs_obj = compile_shader(fragment_shader, GL_FRAGMENT_SHADER);
99                 glAttachShader(glsl_program_num, vs_obj);
100                 check_error();
101                 glAttachShader(glsl_program_num, fs_obj);
102                 check_error();
103                 glLinkProgram(glsl_program_num);
104                 check_error();
105
106                 if (movit_debug_level == MOVIT_DEBUG_ON) {
107                         // Output shader to a temporary file, for easier debugging.
108                         static int compiled_shader_num = 0;
109                         char filename[256];
110                         sprintf(filename, "chain-%03d.frag", compiled_shader_num++);
111                         FILE *fp = fopen(filename, "w");
112                         if (fp == NULL) {
113                                 perror(filename);
114                                 exit(1);
115                         }
116                         fprintf(fp, "%s\n", fragment_shader.c_str());
117                         fclose(fp);
118                 }
119
120                 programs.insert(make_pair(key, glsl_program_num));
121                 program_refcount.insert(make_pair(glsl_program_num, 1));
122                 program_shaders.insert(make_pair(glsl_program_num, make_pair(vs_obj, fs_obj)));
123         }
124         pthread_mutex_unlock(&lock);
125         return glsl_program_num;
126 }
127
128 void ResourcePool::release_glsl_program(GLuint glsl_program_num)
129 {
130         pthread_mutex_lock(&lock);
131         map<GLuint, int>::iterator refcount_it = program_refcount.find(glsl_program_num);
132         assert(refcount_it != program_refcount.end());
133
134         if (--refcount_it->second == 0) {
135                 program_refcount.erase(refcount_it);
136                 assert(find(program_freelist.begin(), program_freelist.end(), glsl_program_num)
137                         == program_freelist.end());
138                 program_freelist.push_front(glsl_program_num);
139                 if (program_freelist.size() > program_freelist_max_length) {
140                         delete_program(program_freelist.back());
141                         program_freelist.pop_back();
142                 }
143         }
144
145         pthread_mutex_unlock(&lock);
146 }
147
148 GLuint ResourcePool::create_2d_texture(GLint internal_format, GLsizei width, GLsizei height)
149 {
150         pthread_mutex_lock(&lock);
151         // See if there's a texture on the freelist we can use.
152         for (list<GLuint>::iterator freelist_it = texture_freelist.begin();
153              freelist_it != texture_freelist.end();
154              ++freelist_it) {
155                 GLuint texture_num = *freelist_it;
156                 map<GLuint, Texture2D>::const_iterator format_it = texture_formats.find(texture_num);
157                 assert(format_it != texture_formats.end());
158                 if (format_it->second.internal_format == internal_format &&
159                     format_it->second.width == width &&
160                     format_it->second.height == height) {
161                         texture_freelist_bytes -= estimate_texture_size(format_it->second);
162                         texture_freelist.erase(freelist_it);
163                         pthread_mutex_unlock(&lock);
164                         return texture_num;
165                 }
166         }
167
168         // Find any reasonable format given the internal format; OpenGL validates it
169         // even though we give NULL as pointer.
170         GLenum format;
171         switch (internal_format) {
172         case GL_RGBA32F_ARB:
173         case GL_RGBA16F_ARB:
174         case GL_RGBA8:
175         case GL_SRGB8_ALPHA8:
176                 format = GL_RGBA;
177                 break;
178         case GL_RG32F:
179         case GL_RG16F:
180                 format = GL_RG;
181                 break;
182         case GL_LUMINANCE8:
183                 format = GL_LUMINANCE;
184                 break;
185         default:
186                 // TODO: Add more here as needed.
187                 assert(false);
188         }
189
190         GLuint texture_num;
191         glGenTextures(1, &texture_num);
192         check_error();
193         glBindTexture(GL_TEXTURE_2D, texture_num);
194         check_error();
195         glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
196         check_error();
197         glBindTexture(GL_TEXTURE_2D, 0);
198         check_error();
199
200         Texture2D texture_format;
201         texture_format.internal_format = internal_format;
202         texture_format.width = width;
203         texture_format.height = height;
204         assert(texture_formats.count(texture_num) == 0);
205         texture_formats.insert(make_pair(texture_num, texture_format));
206
207         pthread_mutex_unlock(&lock);
208         return texture_num;
209 }
210
211 void ResourcePool::release_2d_texture(GLuint texture_num)
212 {
213         pthread_mutex_lock(&lock);
214         texture_freelist.push_front(texture_num);
215         assert(texture_formats.count(texture_num) != 0);
216         texture_freelist_bytes += estimate_texture_size(texture_formats[texture_num]);
217
218         while (texture_freelist_bytes > texture_freelist_max_bytes) {
219                 GLuint free_texture_num = texture_freelist.front();
220                 texture_freelist.pop_front();
221                 assert(texture_formats.count(free_texture_num) != 0);
222                 texture_freelist_bytes -= estimate_texture_size(texture_formats[free_texture_num]);
223                 texture_formats.erase(free_texture_num);
224                 glDeleteTextures(1, &free_texture_num);
225                 check_error();
226         }
227         pthread_mutex_unlock(&lock);
228 }
229
230 size_t ResourcePool::estimate_texture_size(const Texture2D &texture_format)
231 {
232         size_t bytes_per_pixel;
233
234         switch (texture_format.internal_format) {
235         case GL_RGBA32F_ARB:
236                 bytes_per_pixel = 16;
237                 break;
238         case GL_RGBA16F_ARB:
239                 bytes_per_pixel = 8;
240                 break;
241         case GL_RGBA8:
242         case GL_SRGB8_ALPHA8:
243                 bytes_per_pixel = 4;
244                 break;
245         case GL_RG32F:
246                 bytes_per_pixel = 8;
247                 break;
248         case GL_RG16F:
249                 bytes_per_pixel = 4;
250                 break;
251         case GL_LUMINANCE8:
252                 bytes_per_pixel = 1;
253                 break;
254         default:
255                 // TODO: Add more here as needed.
256                 assert(false);
257         }
258
259         return texture_format.width * texture_format.height * bytes_per_pixel;
260 }