1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2011 Blender Foundation.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup bke
22  */
23 
24 #undef DEBUG_MESSAGES
25 
26 #include <memory.h>
27 #include <stdlib.h> /* for qsort */
28 
29 #include "MEM_CacheLimiterC-Api.h"
30 #include "MEM_guardedalloc.h"
31 
32 #include "BLI_ghash.h"
33 #include "BLI_mempool.h"
34 #include "BLI_string.h"
35 #include "BLI_threads.h"
36 #include "BLI_utildefines.h"
37 
38 #include "IMB_moviecache.h"
39 
40 #include "IMB_imbuf.h"
41 #include "IMB_imbuf_types.h"
42 
43 #ifdef DEBUG_MESSAGES
44 #  if defined __GNUC__
45 #    define PRINT(format, args...) printf(format, ##args)
46 #  else
47 #    define PRINT(format, ...) printf(__VA_ARGS__)
48 #  endif
49 #else
50 #  define PRINT(format, ...)
51 #endif
52 
53 static MEM_CacheLimiterC *limitor = NULL;
54 static pthread_mutex_t limitor_lock = BLI_MUTEX_INITIALIZER;
55 
56 typedef struct MovieCache {
57   char name[64];
58 
59   GHash *hash;
60   GHashHashFP hashfp;
61   GHashCmpFP cmpfp;
62   MovieCacheGetKeyDataFP getdatafp;
63 
64   MovieCacheGetPriorityDataFP getprioritydatafp;
65   MovieCacheGetItemPriorityFP getitempriorityfp;
66   MovieCachePriorityDeleterFP prioritydeleterfp;
67 
68   struct BLI_mempool *keys_pool;
69   struct BLI_mempool *items_pool;
70   struct BLI_mempool *userkeys_pool;
71 
72   int keysize;
73 
74   void *last_userkey;
75 
76   int totseg, *points, proxy, render_flags; /* for visual statistics optimization */
77   int pad;
78 } MovieCache;
79 
80 typedef struct MovieCacheKey {
81   MovieCache *cache_owner;
82   void *userkey;
83 } MovieCacheKey;
84 
85 typedef struct MovieCacheItem {
86   MovieCache *cache_owner;
87   ImBuf *ibuf;
88   MEM_CacheLimiterHandleC *c_handle;
89   void *priority_data;
90 } MovieCacheItem;
91 
moviecache_hashhash(const void * keyv)92 static unsigned int moviecache_hashhash(const void *keyv)
93 {
94   const MovieCacheKey *key = keyv;
95 
96   return key->cache_owner->hashfp(key->userkey);
97 }
98 
moviecache_hashcmp(const void * av,const void * bv)99 static bool moviecache_hashcmp(const void *av, const void *bv)
100 {
101   const MovieCacheKey *a = av;
102   const MovieCacheKey *b = bv;
103 
104   return a->cache_owner->cmpfp(a->userkey, b->userkey);
105 }
106 
moviecache_keyfree(void * val)107 static void moviecache_keyfree(void *val)
108 {
109   MovieCacheKey *key = val;
110 
111   BLI_mempool_free(key->cache_owner->userkeys_pool, key->userkey);
112 
113   BLI_mempool_free(key->cache_owner->keys_pool, key);
114 }
115 
moviecache_valfree(void * val)116 static void moviecache_valfree(void *val)
117 {
118   MovieCacheItem *item = (MovieCacheItem *)val;
119   MovieCache *cache = item->cache_owner;
120 
121   PRINT("%s: cache '%s' free item %p buffer %p\n", __func__, cache->name, item, item->ibuf);
122 
123   if (item->ibuf) {
124     MEM_CacheLimiter_unmanage(item->c_handle);
125     IMB_freeImBuf(item->ibuf);
126   }
127 
128   if (item->priority_data && cache->prioritydeleterfp) {
129     cache->prioritydeleterfp(item->priority_data);
130   }
131 
132   BLI_mempool_free(item->cache_owner->items_pool, item);
133 }
134 
check_unused_keys(MovieCache * cache)135 static void check_unused_keys(MovieCache *cache)
136 {
137   GHashIterator gh_iter;
138 
139   BLI_ghashIterator_init(&gh_iter, cache->hash);
140 
141   while (!BLI_ghashIterator_done(&gh_iter)) {
142     const MovieCacheKey *key = BLI_ghashIterator_getKey(&gh_iter);
143     const MovieCacheItem *item = BLI_ghashIterator_getValue(&gh_iter);
144     bool remove;
145 
146     BLI_ghashIterator_step(&gh_iter);
147 
148     remove = !item->ibuf;
149 
150     if (remove) {
151       PRINT("%s: cache '%s' remove item %p without buffer\n", __func__, cache->name, item);
152     }
153 
154     if (remove) {
155       BLI_ghash_remove(cache->hash, key, moviecache_keyfree, moviecache_valfree);
156     }
157   }
158 }
159 
compare_int(const void * av,const void * bv)160 static int compare_int(const void *av, const void *bv)
161 {
162   const int *a = av;
163   const int *b = bv;
164   return *a - *b;
165 }
166 
IMB_moviecache_destructor(void * p)167 static void IMB_moviecache_destructor(void *p)
168 {
169   MovieCacheItem *item = (MovieCacheItem *)p;
170 
171   if (item && item->ibuf) {
172     MovieCache *cache = item->cache_owner;
173 
174     PRINT("%s: cache '%s' destroy item %p buffer %p\n", __func__, cache->name, item, item->ibuf);
175 
176     IMB_freeImBuf(item->ibuf);
177 
178     item->ibuf = NULL;
179     item->c_handle = NULL;
180 
181     /* force cached segments to be updated */
182     if (cache->points) {
183       MEM_freeN(cache->points);
184       cache->points = NULL;
185     }
186   }
187 }
188 
get_size_in_memory(ImBuf * ibuf)189 static size_t get_size_in_memory(ImBuf *ibuf)
190 {
191   /* Keep textures in the memory to avoid constant file reload on viewport update. */
192   if (ibuf->userflags & IB_PERSISTENT) {
193     return 0;
194   }
195 
196   return IMB_get_size_in_memory(ibuf);
197 }
get_item_size(void * p)198 static size_t get_item_size(void *p)
199 {
200   size_t size = sizeof(MovieCacheItem);
201   MovieCacheItem *item = (MovieCacheItem *)p;
202 
203   if (item->ibuf) {
204     size += get_size_in_memory(item->ibuf);
205   }
206 
207   return size;
208 }
209 
get_item_priority(void * item_v,int default_priority)210 static int get_item_priority(void *item_v, int default_priority)
211 {
212   MovieCacheItem *item = (MovieCacheItem *)item_v;
213   MovieCache *cache = item->cache_owner;
214   int priority;
215 
216   if (!cache->getitempriorityfp) {
217     PRINT("%s: cache '%s' item %p use default priority %d\n",
218           __func__,
219           cache->name,
220           item,
221           default_priority);
222 
223     return default_priority;
224   }
225 
226   priority = cache->getitempriorityfp(cache->last_userkey, item->priority_data);
227 
228   PRINT("%s: cache '%s' item %p priority %d\n", __func__, cache->name, item, priority);
229 
230   return priority;
231 }
232 
get_item_destroyable(void * item_v)233 static bool get_item_destroyable(void *item_v)
234 {
235   MovieCacheItem *item = (MovieCacheItem *)item_v;
236   /* IB_BITMAPDIRTY means image was modified from inside blender and
237    * changes are not saved to disk.
238    *
239    * Such buffers are never to be freed.
240    */
241   if ((item->ibuf->userflags & IB_BITMAPDIRTY) || (item->ibuf->userflags & IB_PERSISTENT)) {
242     return false;
243   }
244   return true;
245 }
246 
IMB_moviecache_init(void)247 void IMB_moviecache_init(void)
248 {
249   limitor = new_MEM_CacheLimiter(IMB_moviecache_destructor, get_item_size);
250 
251   MEM_CacheLimiter_ItemPriority_Func_set(limitor, get_item_priority);
252   MEM_CacheLimiter_ItemDestroyable_Func_set(limitor, get_item_destroyable);
253 }
254 
IMB_moviecache_destruct(void)255 void IMB_moviecache_destruct(void)
256 {
257   if (limitor) {
258     delete_MEM_CacheLimiter(limitor);
259   }
260 }
261 
IMB_moviecache_create(const char * name,int keysize,GHashHashFP hashfp,GHashCmpFP cmpfp)262 MovieCache *IMB_moviecache_create(const char *name,
263                                   int keysize,
264                                   GHashHashFP hashfp,
265                                   GHashCmpFP cmpfp)
266 {
267   MovieCache *cache;
268 
269   PRINT("%s: cache '%s' create\n", __func__, name);
270 
271   cache = MEM_callocN(sizeof(MovieCache), "MovieCache");
272 
273   BLI_strncpy(cache->name, name, sizeof(cache->name));
274 
275   cache->keys_pool = BLI_mempool_create(sizeof(MovieCacheKey), 0, 64, BLI_MEMPOOL_NOP);
276   cache->items_pool = BLI_mempool_create(sizeof(MovieCacheItem), 0, 64, BLI_MEMPOOL_NOP);
277   cache->userkeys_pool = BLI_mempool_create(keysize, 0, 64, BLI_MEMPOOL_NOP);
278   cache->hash = BLI_ghash_new(
279       moviecache_hashhash, moviecache_hashcmp, "MovieClip ImBuf cache hash");
280 
281   cache->keysize = keysize;
282   cache->hashfp = hashfp;
283   cache->cmpfp = cmpfp;
284   cache->proxy = -1;
285 
286   return cache;
287 }
288 
IMB_moviecache_set_getdata_callback(MovieCache * cache,MovieCacheGetKeyDataFP getdatafp)289 void IMB_moviecache_set_getdata_callback(MovieCache *cache, MovieCacheGetKeyDataFP getdatafp)
290 {
291   cache->getdatafp = getdatafp;
292 }
293 
IMB_moviecache_set_priority_callback(struct MovieCache * cache,MovieCacheGetPriorityDataFP getprioritydatafp,MovieCacheGetItemPriorityFP getitempriorityfp,MovieCachePriorityDeleterFP prioritydeleterfp)294 void IMB_moviecache_set_priority_callback(struct MovieCache *cache,
295                                           MovieCacheGetPriorityDataFP getprioritydatafp,
296                                           MovieCacheGetItemPriorityFP getitempriorityfp,
297                                           MovieCachePriorityDeleterFP prioritydeleterfp)
298 {
299   cache->last_userkey = MEM_mallocN(cache->keysize, "movie cache last user key");
300 
301   cache->getprioritydatafp = getprioritydatafp;
302   cache->getitempriorityfp = getitempriorityfp;
303   cache->prioritydeleterfp = prioritydeleterfp;
304 }
305 
do_moviecache_put(MovieCache * cache,void * userkey,ImBuf * ibuf,bool need_lock)306 static void do_moviecache_put(MovieCache *cache, void *userkey, ImBuf *ibuf, bool need_lock)
307 {
308   MovieCacheKey *key;
309   MovieCacheItem *item;
310 
311   if (!limitor) {
312     IMB_moviecache_init();
313   }
314 
315   IMB_refImBuf(ibuf);
316 
317   key = BLI_mempool_alloc(cache->keys_pool);
318   key->cache_owner = cache;
319   key->userkey = BLI_mempool_alloc(cache->userkeys_pool);
320   memcpy(key->userkey, userkey, cache->keysize);
321 
322   item = BLI_mempool_alloc(cache->items_pool);
323 
324   PRINT("%s: cache '%s' put %p, item %p\n", __func__, cache->name, ibuf, item);
325 
326   item->ibuf = ibuf;
327   item->cache_owner = cache;
328   item->c_handle = NULL;
329   item->priority_data = NULL;
330 
331   if (cache->getprioritydatafp) {
332     item->priority_data = cache->getprioritydatafp(userkey);
333   }
334 
335   BLI_ghash_reinsert(cache->hash, key, item, moviecache_keyfree, moviecache_valfree);
336 
337   if (cache->last_userkey) {
338     memcpy(cache->last_userkey, userkey, cache->keysize);
339   }
340 
341   if (need_lock) {
342     BLI_mutex_lock(&limitor_lock);
343   }
344 
345   item->c_handle = MEM_CacheLimiter_insert(limitor, item);
346 
347   MEM_CacheLimiter_ref(item->c_handle);
348   MEM_CacheLimiter_enforce_limits(limitor);
349   MEM_CacheLimiter_unref(item->c_handle);
350 
351   if (need_lock) {
352     BLI_mutex_unlock(&limitor_lock);
353   }
354 
355   /* cache limiter can't remove unused keys which points to destroyed values */
356   check_unused_keys(cache);
357 
358   if (cache->points) {
359     MEM_freeN(cache->points);
360     cache->points = NULL;
361   }
362 }
363 
IMB_moviecache_put(MovieCache * cache,void * userkey,ImBuf * ibuf)364 void IMB_moviecache_put(MovieCache *cache, void *userkey, ImBuf *ibuf)
365 {
366   do_moviecache_put(cache, userkey, ibuf, true);
367 }
368 
IMB_moviecache_put_if_possible(MovieCache * cache,void * userkey,ImBuf * ibuf)369 bool IMB_moviecache_put_if_possible(MovieCache *cache, void *userkey, ImBuf *ibuf)
370 {
371   size_t mem_in_use, mem_limit, elem_size;
372   bool result = false;
373 
374   elem_size = get_size_in_memory(ibuf);
375   mem_limit = MEM_CacheLimiter_get_maximum();
376 
377   BLI_mutex_lock(&limitor_lock);
378   mem_in_use = MEM_CacheLimiter_get_memory_in_use(limitor);
379 
380   if (mem_in_use + elem_size <= mem_limit) {
381     do_moviecache_put(cache, userkey, ibuf, false);
382     result = true;
383   }
384 
385   BLI_mutex_unlock(&limitor_lock);
386 
387   return result;
388 }
389 
IMB_moviecache_remove(MovieCache * cache,void * userkey)390 void IMB_moviecache_remove(MovieCache *cache, void *userkey)
391 {
392   MovieCacheKey key;
393   key.cache_owner = cache;
394   key.userkey = userkey;
395   BLI_ghash_remove(cache->hash, &key, moviecache_keyfree, moviecache_valfree);
396 }
397 
IMB_moviecache_get(MovieCache * cache,void * userkey)398 ImBuf *IMB_moviecache_get(MovieCache *cache, void *userkey)
399 {
400   MovieCacheKey key;
401   MovieCacheItem *item;
402 
403   key.cache_owner = cache;
404   key.userkey = userkey;
405   item = (MovieCacheItem *)BLI_ghash_lookup(cache->hash, &key);
406 
407   if (item) {
408     if (item->ibuf) {
409       BLI_mutex_lock(&limitor_lock);
410       MEM_CacheLimiter_touch(item->c_handle);
411       BLI_mutex_unlock(&limitor_lock);
412 
413       IMB_refImBuf(item->ibuf);
414 
415       return item->ibuf;
416     }
417   }
418 
419   return NULL;
420 }
421 
IMB_moviecache_has_frame(MovieCache * cache,void * userkey)422 bool IMB_moviecache_has_frame(MovieCache *cache, void *userkey)
423 {
424   MovieCacheKey key;
425   MovieCacheItem *item;
426 
427   key.cache_owner = cache;
428   key.userkey = userkey;
429   item = (MovieCacheItem *)BLI_ghash_lookup(cache->hash, &key);
430 
431   return item != NULL;
432 }
433 
IMB_moviecache_free(MovieCache * cache)434 void IMB_moviecache_free(MovieCache *cache)
435 {
436   PRINT("%s: cache '%s' free\n", __func__, cache->name);
437 
438   BLI_ghash_free(cache->hash, moviecache_keyfree, moviecache_valfree);
439 
440   BLI_mempool_destroy(cache->keys_pool);
441   BLI_mempool_destroy(cache->items_pool);
442   BLI_mempool_destroy(cache->userkeys_pool);
443 
444   if (cache->points) {
445     MEM_freeN(cache->points);
446   }
447 
448   if (cache->last_userkey) {
449     MEM_freeN(cache->last_userkey);
450   }
451 
452   MEM_freeN(cache);
453 }
454 
IMB_moviecache_cleanup(MovieCache * cache,bool (cleanup_check_cb)(ImBuf * ibuf,void * userkey,void * userdata),void * userdata)455 void IMB_moviecache_cleanup(MovieCache *cache,
456                             bool(cleanup_check_cb)(ImBuf *ibuf, void *userkey, void *userdata),
457                             void *userdata)
458 {
459   GHashIterator gh_iter;
460 
461   check_unused_keys(cache);
462 
463   BLI_ghashIterator_init(&gh_iter, cache->hash);
464 
465   while (!BLI_ghashIterator_done(&gh_iter)) {
466     MovieCacheKey *key = BLI_ghashIterator_getKey(&gh_iter);
467     MovieCacheItem *item = BLI_ghashIterator_getValue(&gh_iter);
468 
469     BLI_ghashIterator_step(&gh_iter);
470 
471     if (cleanup_check_cb(item->ibuf, key->userkey, userdata)) {
472       PRINT("%s: cache '%s' remove item %p\n", __func__, cache->name, item);
473 
474       BLI_ghash_remove(cache->hash, key, moviecache_keyfree, moviecache_valfree);
475     }
476   }
477 }
478 
479 /* get segments of cached frames. useful for debugging cache policies */
IMB_moviecache_get_cache_segments(MovieCache * cache,int proxy,int render_flags,int * r_totseg,int ** r_points)480 void IMB_moviecache_get_cache_segments(
481     MovieCache *cache, int proxy, int render_flags, int *r_totseg, int **r_points)
482 {
483   *r_totseg = 0;
484   *r_points = NULL;
485 
486   if (!cache->getdatafp) {
487     return;
488   }
489 
490   if (cache->proxy != proxy || cache->render_flags != render_flags) {
491     if (cache->points) {
492       MEM_freeN(cache->points);
493     }
494 
495     cache->points = NULL;
496   }
497 
498   if (cache->points) {
499     *r_totseg = cache->totseg;
500     *r_points = cache->points;
501   }
502   else {
503     int totframe = BLI_ghash_len(cache->hash);
504     int *frames = MEM_callocN(totframe * sizeof(int), "movieclip cache frames");
505     int a, totseg = 0;
506     GHashIterator gh_iter;
507 
508     a = 0;
509     GHASH_ITER (gh_iter, cache->hash) {
510       MovieCacheKey *key = BLI_ghashIterator_getKey(&gh_iter);
511       MovieCacheItem *item = BLI_ghashIterator_getValue(&gh_iter);
512       int framenr, curproxy, curflags;
513 
514       if (item->ibuf) {
515         cache->getdatafp(key->userkey, &framenr, &curproxy, &curflags);
516 
517         if (curproxy == proxy && curflags == render_flags) {
518           frames[a++] = framenr;
519         }
520       }
521     }
522 
523     qsort(frames, totframe, sizeof(int), compare_int);
524 
525     /* count */
526     for (a = 0; a < totframe; a++) {
527       if (a && frames[a] - frames[a - 1] != 1) {
528         totseg++;
529       }
530 
531       if (a == totframe - 1) {
532         totseg++;
533       }
534     }
535 
536     if (totseg) {
537       int b, *points;
538 
539       points = MEM_callocN(sizeof(int[2]) * totseg, "movieclip cache segments");
540 
541       /* fill */
542       for (a = 0, b = 0; a < totframe; a++) {
543         if (a == 0) {
544           points[b++] = frames[a];
545         }
546 
547         if (a && frames[a] - frames[a - 1] != 1) {
548           points[b++] = frames[a - 1];
549           points[b++] = frames[a];
550         }
551 
552         if (a == totframe - 1) {
553           points[b++] = frames[a];
554         }
555       }
556 
557       *r_totseg = totseg;
558       *r_points = points;
559 
560       cache->totseg = totseg;
561       cache->points = points;
562       cache->proxy = proxy;
563       cache->render_flags = render_flags;
564     }
565 
566     MEM_freeN(frames);
567   }
568 }
569 
IMB_moviecacheIter_new(MovieCache * cache)570 struct MovieCacheIter *IMB_moviecacheIter_new(MovieCache *cache)
571 {
572   GHashIterator *iter;
573 
574   check_unused_keys(cache);
575   iter = BLI_ghashIterator_new(cache->hash);
576 
577   return (struct MovieCacheIter *)iter;
578 }
579 
IMB_moviecacheIter_free(struct MovieCacheIter * iter)580 void IMB_moviecacheIter_free(struct MovieCacheIter *iter)
581 {
582   BLI_ghashIterator_free((GHashIterator *)iter);
583 }
584 
IMB_moviecacheIter_done(struct MovieCacheIter * iter)585 bool IMB_moviecacheIter_done(struct MovieCacheIter *iter)
586 {
587   return BLI_ghashIterator_done((GHashIterator *)iter);
588 }
589 
IMB_moviecacheIter_step(struct MovieCacheIter * iter)590 void IMB_moviecacheIter_step(struct MovieCacheIter *iter)
591 {
592   BLI_ghashIterator_step((GHashIterator *)iter);
593 }
594 
IMB_moviecacheIter_getImBuf(struct MovieCacheIter * iter)595 ImBuf *IMB_moviecacheIter_getImBuf(struct MovieCacheIter *iter)
596 {
597   MovieCacheItem *item = BLI_ghashIterator_getValue((GHashIterator *)iter);
598   return item->ibuf;
599 }
600 
IMB_moviecacheIter_getUserKey(struct MovieCacheIter * iter)601 void *IMB_moviecacheIter_getUserKey(struct MovieCacheIter *iter)
602 {
603   MovieCacheKey *key = BLI_ghashIterator_getKey((GHashIterator *)iter);
604   return key->userkey;
605 }
606