Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
612 lines
15 KiB
C++
612 lines
15 KiB
C++
/* SPDX-FileCopyrightText: 2011 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#undef DEBUG_MESSAGES
|
|
|
|
#include <cstdlib> /* for qsort */
|
|
#include <memory.h>
|
|
#include <mutex>
|
|
|
|
#include "MEM_CacheLimiterC-Api.h"
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_mempool.h"
|
|
#include "BLI_string.h"
|
|
#include "BLI_threads.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "IMB_moviecache.h"
|
|
|
|
#include "IMB_imbuf.h"
|
|
#include "IMB_imbuf_types.h"
|
|
|
|
#ifdef DEBUG_MESSAGES
|
|
# if defined __GNUC__
|
|
# define PRINT(format, args...) printf(format, ##args)
|
|
# else
|
|
# define PRINT(format, ...) printf(__VA_ARGS__)
|
|
# endif
|
|
#else
|
|
# define PRINT(format, ...)
|
|
#endif
|
|
|
|
static MEM_CacheLimiterC *limitor = nullptr;
|
|
|
|
/* Image buffers managed by a moviecache might be using their own movie caches (used by color
|
|
* management). In practice this means that, for example, freeing MovieCache used by MovieClip
|
|
* will request freeing MovieCache owned by ImBuf. Freeing MovieCache needs to be thread-safe,
|
|
* so regular mutex will not work here, hence the recursive lock. */
|
|
static std::recursive_mutex limitor_lock;
|
|
|
|
struct MovieCache {
|
|
char name[64];
|
|
|
|
GHash *hash;
|
|
GHashHashFP hashfp;
|
|
GHashCmpFP cmpfp;
|
|
MovieCacheGetKeyDataFP getdatafp;
|
|
|
|
MovieCacheGetPriorityDataFP getprioritydatafp;
|
|
MovieCacheGetItemPriorityFP getitempriorityfp;
|
|
MovieCachePriorityDeleterFP prioritydeleterfp;
|
|
|
|
BLI_mempool *keys_pool;
|
|
BLI_mempool *items_pool;
|
|
BLI_mempool *userkeys_pool;
|
|
|
|
int keysize;
|
|
|
|
void *last_userkey;
|
|
|
|
int totseg, *points, proxy, render_flags; /* for visual statistics optimization */
|
|
int pad;
|
|
};
|
|
|
|
struct MovieCacheKey {
|
|
MovieCache *cache_owner;
|
|
void *userkey;
|
|
};
|
|
|
|
struct MovieCacheItem {
|
|
MovieCache *cache_owner;
|
|
ImBuf *ibuf;
|
|
MEM_CacheLimiterHandleC *c_handle;
|
|
void *priority_data;
|
|
/* Indicates that #ibuf is null, because there was an error during load. */
|
|
bool added_empty;
|
|
};
|
|
|
|
static uint moviecache_hashhash(const void *keyv)
|
|
{
|
|
const MovieCacheKey *key = (const MovieCacheKey *)keyv;
|
|
|
|
return key->cache_owner->hashfp(key->userkey);
|
|
}
|
|
|
|
static bool moviecache_hashcmp(const void *av, const void *bv)
|
|
{
|
|
const MovieCacheKey *a = (const MovieCacheKey *)av;
|
|
const MovieCacheKey *b = (const MovieCacheKey *)bv;
|
|
|
|
return a->cache_owner->cmpfp(a->userkey, b->userkey);
|
|
}
|
|
|
|
static void moviecache_keyfree(void *val)
|
|
{
|
|
MovieCacheKey *key = (MovieCacheKey *)val;
|
|
|
|
BLI_mempool_free(key->cache_owner->userkeys_pool, key->userkey);
|
|
|
|
BLI_mempool_free(key->cache_owner->keys_pool, key);
|
|
}
|
|
|
|
static void moviecache_valfree(void *val)
|
|
{
|
|
MovieCacheItem *item = (MovieCacheItem *)val;
|
|
MovieCache *cache = item->cache_owner;
|
|
|
|
PRINT("%s: cache '%s' free item %p buffer %p\n", __func__, cache->name, item, item->ibuf);
|
|
|
|
if (item->c_handle) {
|
|
limitor_lock.lock();
|
|
MEM_CacheLimiter_unmanage(item->c_handle);
|
|
limitor_lock.unlock();
|
|
}
|
|
|
|
if (item->ibuf) {
|
|
IMB_freeImBuf(item->ibuf);
|
|
}
|
|
|
|
if (item->priority_data && cache->prioritydeleterfp) {
|
|
cache->prioritydeleterfp(item->priority_data);
|
|
}
|
|
|
|
BLI_mempool_free(item->cache_owner->items_pool, item);
|
|
}
|
|
|
|
static void check_unused_keys(MovieCache *cache)
|
|
{
|
|
GHashIterator gh_iter;
|
|
|
|
BLI_ghashIterator_init(&gh_iter, cache->hash);
|
|
|
|
while (!BLI_ghashIterator_done(&gh_iter)) {
|
|
const MovieCacheKey *key = (const MovieCacheKey *)BLI_ghashIterator_getKey(&gh_iter);
|
|
const MovieCacheItem *item = (const MovieCacheItem *)BLI_ghashIterator_getValue(&gh_iter);
|
|
|
|
BLI_ghashIterator_step(&gh_iter);
|
|
|
|
if (item->added_empty) {
|
|
/* Don't remove entries that have been added empty. Those indicate that the image couldn't be
|
|
* loaded correctly. */
|
|
continue;
|
|
}
|
|
|
|
bool remove = !item->ibuf;
|
|
|
|
if (remove) {
|
|
PRINT("%s: cache '%s' remove item %p without buffer\n", __func__, cache->name, item);
|
|
}
|
|
|
|
if (remove) {
|
|
BLI_ghash_remove(cache->hash, key, moviecache_keyfree, moviecache_valfree);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int compare_int(const void *av, const void *bv)
|
|
{
|
|
const int *a = (int *)av;
|
|
const int *b = (int *)bv;
|
|
return *a - *b;
|
|
}
|
|
|
|
static void moviecache_destructor(void *p)
|
|
{
|
|
MovieCacheItem *item = (MovieCacheItem *)p;
|
|
|
|
if (item && item->ibuf) {
|
|
MovieCache *cache = item->cache_owner;
|
|
|
|
PRINT("%s: cache '%s' destroy item %p buffer %p\n", __func__, cache->name, item, item->ibuf);
|
|
|
|
IMB_freeImBuf(item->ibuf);
|
|
|
|
item->ibuf = nullptr;
|
|
item->c_handle = nullptr;
|
|
|
|
/* force cached segments to be updated */
|
|
MEM_SAFE_FREE(cache->points);
|
|
}
|
|
}
|
|
|
|
static size_t get_size_in_memory(ImBuf *ibuf)
|
|
{
|
|
/* Keep textures in the memory to avoid constant file reload on viewport update. */
|
|
if (ibuf->userflags & IB_PERSISTENT) {
|
|
return 0;
|
|
}
|
|
|
|
return IMB_get_size_in_memory(ibuf);
|
|
}
|
|
static size_t get_item_size(void *p)
|
|
{
|
|
size_t size = sizeof(MovieCacheItem);
|
|
MovieCacheItem *item = (MovieCacheItem *)p;
|
|
|
|
if (item->ibuf) {
|
|
size += get_size_in_memory(item->ibuf);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static int get_item_priority(void *item_v, int default_priority)
|
|
{
|
|
MovieCacheItem *item = (MovieCacheItem *)item_v;
|
|
MovieCache *cache = item->cache_owner;
|
|
int priority;
|
|
|
|
if (!cache->getitempriorityfp) {
|
|
PRINT("%s: cache '%s' item %p use default priority %d\n",
|
|
__func__,
|
|
cache->name,
|
|
item,
|
|
default_priority);
|
|
|
|
return default_priority;
|
|
}
|
|
|
|
priority = cache->getitempriorityfp(cache->last_userkey, item->priority_data);
|
|
|
|
PRINT("%s: cache '%s' item %p priority %d\n", __func__, cache->name, item, priority);
|
|
|
|
return priority;
|
|
}
|
|
|
|
static bool get_item_destroyable(void *item_v)
|
|
{
|
|
MovieCacheItem *item = (MovieCacheItem *)item_v;
|
|
if (item->ibuf == nullptr) {
|
|
return true;
|
|
}
|
|
/* IB_BITMAPDIRTY means image was modified from inside blender and
|
|
* changes are not saved to disk.
|
|
*
|
|
* Such buffers are never to be freed.
|
|
*/
|
|
if ((item->ibuf->userflags & IB_BITMAPDIRTY) || (item->ibuf->userflags & IB_PERSISTENT)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void IMB_moviecache_init()
|
|
{
|
|
limitor = new_MEM_CacheLimiter(moviecache_destructor, get_item_size);
|
|
|
|
MEM_CacheLimiter_ItemPriority_Func_set(limitor, get_item_priority);
|
|
MEM_CacheLimiter_ItemDestroyable_Func_set(limitor, get_item_destroyable);
|
|
}
|
|
|
|
void IMB_moviecache_destruct()
|
|
{
|
|
if (limitor) {
|
|
delete_MEM_CacheLimiter(limitor);
|
|
limitor = nullptr;
|
|
}
|
|
}
|
|
|
|
MovieCache *IMB_moviecache_create(const char *name,
|
|
int keysize,
|
|
GHashHashFP hashfp,
|
|
GHashCmpFP cmpfp)
|
|
{
|
|
MovieCache *cache;
|
|
|
|
PRINT("%s: cache '%s' create\n", __func__, name);
|
|
|
|
cache = (MovieCache *)MEM_callocN(sizeof(MovieCache), "MovieCache");
|
|
|
|
STRNCPY(cache->name, name);
|
|
|
|
cache->keys_pool = BLI_mempool_create(sizeof(MovieCacheKey), 0, 64, BLI_MEMPOOL_NOP);
|
|
cache->items_pool = BLI_mempool_create(sizeof(MovieCacheItem), 0, 64, BLI_MEMPOOL_NOP);
|
|
cache->userkeys_pool = BLI_mempool_create(keysize, 0, 64, BLI_MEMPOOL_NOP);
|
|
cache->hash = BLI_ghash_new(
|
|
moviecache_hashhash, moviecache_hashcmp, "MovieClip ImBuf cache hash");
|
|
|
|
cache->keysize = keysize;
|
|
cache->hashfp = hashfp;
|
|
cache->cmpfp = cmpfp;
|
|
cache->proxy = -1;
|
|
|
|
return cache;
|
|
}
|
|
|
|
void IMB_moviecache_set_getdata_callback(MovieCache *cache, MovieCacheGetKeyDataFP getdatafp)
|
|
{
|
|
cache->getdatafp = getdatafp;
|
|
}
|
|
|
|
void IMB_moviecache_set_priority_callback(MovieCache *cache,
|
|
MovieCacheGetPriorityDataFP getprioritydatafp,
|
|
MovieCacheGetItemPriorityFP getitempriorityfp,
|
|
MovieCachePriorityDeleterFP prioritydeleterfp)
|
|
{
|
|
cache->last_userkey = MEM_mallocN(cache->keysize, "movie cache last user key");
|
|
|
|
cache->getprioritydatafp = getprioritydatafp;
|
|
cache->getitempriorityfp = getitempriorityfp;
|
|
cache->prioritydeleterfp = prioritydeleterfp;
|
|
}
|
|
|
|
static void do_moviecache_put(MovieCache *cache, void *userkey, ImBuf *ibuf, bool need_lock)
|
|
{
|
|
MovieCacheKey *key;
|
|
MovieCacheItem *item;
|
|
|
|
if (!limitor) {
|
|
IMB_moviecache_init();
|
|
}
|
|
|
|
if (ibuf != nullptr) {
|
|
IMB_refImBuf(ibuf);
|
|
}
|
|
|
|
key = (MovieCacheKey *)BLI_mempool_alloc(cache->keys_pool);
|
|
key->cache_owner = cache;
|
|
key->userkey = BLI_mempool_alloc(cache->userkeys_pool);
|
|
memcpy(key->userkey, userkey, cache->keysize);
|
|
|
|
item = (MovieCacheItem *)BLI_mempool_alloc(cache->items_pool);
|
|
|
|
PRINT("%s: cache '%s' put %p, item %p\n", __func__, cache->name, ibuf, item);
|
|
|
|
item->ibuf = ibuf;
|
|
item->cache_owner = cache;
|
|
item->c_handle = nullptr;
|
|
item->priority_data = nullptr;
|
|
item->added_empty = ibuf == nullptr;
|
|
|
|
if (cache->getprioritydatafp) {
|
|
item->priority_data = cache->getprioritydatafp(userkey);
|
|
}
|
|
|
|
BLI_ghash_reinsert(cache->hash, key, item, moviecache_keyfree, moviecache_valfree);
|
|
|
|
if (cache->last_userkey) {
|
|
memcpy(cache->last_userkey, userkey, cache->keysize);
|
|
}
|
|
|
|
if (need_lock) {
|
|
limitor_lock.lock();
|
|
}
|
|
|
|
item->c_handle = MEM_CacheLimiter_insert(limitor, item);
|
|
|
|
MEM_CacheLimiter_ref(item->c_handle);
|
|
MEM_CacheLimiter_enforce_limits(limitor);
|
|
MEM_CacheLimiter_unref(item->c_handle);
|
|
|
|
if (need_lock) {
|
|
limitor_lock.unlock();
|
|
}
|
|
|
|
/* cache limiter can't remove unused keys which points to destroyed values */
|
|
check_unused_keys(cache);
|
|
|
|
MEM_SAFE_FREE(cache->points);
|
|
}
|
|
|
|
void IMB_moviecache_put(MovieCache *cache, void *userkey, ImBuf *ibuf)
|
|
{
|
|
do_moviecache_put(cache, userkey, ibuf, true);
|
|
}
|
|
|
|
bool IMB_moviecache_put_if_possible(MovieCache *cache, void *userkey, ImBuf *ibuf)
|
|
{
|
|
size_t mem_in_use, mem_limit, elem_size;
|
|
bool result = false;
|
|
|
|
elem_size = (ibuf == nullptr) ? 0 : get_size_in_memory(ibuf);
|
|
mem_limit = MEM_CacheLimiter_get_maximum();
|
|
|
|
limitor_lock.lock();
|
|
mem_in_use = MEM_CacheLimiter_get_memory_in_use(limitor);
|
|
|
|
if (mem_in_use + elem_size <= mem_limit) {
|
|
do_moviecache_put(cache, userkey, ibuf, false);
|
|
result = true;
|
|
}
|
|
|
|
limitor_lock.unlock();
|
|
|
|
return result;
|
|
}
|
|
|
|
void IMB_moviecache_remove(MovieCache *cache, void *userkey)
|
|
{
|
|
MovieCacheKey key;
|
|
key.cache_owner = cache;
|
|
key.userkey = userkey;
|
|
BLI_ghash_remove(cache->hash, &key, moviecache_keyfree, moviecache_valfree);
|
|
}
|
|
|
|
ImBuf *IMB_moviecache_get(MovieCache *cache, void *userkey, bool *r_is_cached_empty)
|
|
{
|
|
MovieCacheKey key;
|
|
MovieCacheItem *item;
|
|
|
|
key.cache_owner = cache;
|
|
key.userkey = userkey;
|
|
item = (MovieCacheItem *)BLI_ghash_lookup(cache->hash, &key);
|
|
|
|
if (r_is_cached_empty) {
|
|
*r_is_cached_empty = false;
|
|
}
|
|
|
|
if (item) {
|
|
if (item->ibuf) {
|
|
limitor_lock.lock();
|
|
MEM_CacheLimiter_touch(item->c_handle);
|
|
limitor_lock.unlock();
|
|
|
|
IMB_refImBuf(item->ibuf);
|
|
|
|
return item->ibuf;
|
|
}
|
|
if (r_is_cached_empty) {
|
|
*r_is_cached_empty = true;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool IMB_moviecache_has_frame(MovieCache *cache, void *userkey)
|
|
{
|
|
MovieCacheKey key;
|
|
MovieCacheItem *item;
|
|
|
|
key.cache_owner = cache;
|
|
key.userkey = userkey;
|
|
item = (MovieCacheItem *)BLI_ghash_lookup(cache->hash, &key);
|
|
|
|
return item != nullptr;
|
|
}
|
|
|
|
void IMB_moviecache_free(MovieCache *cache)
|
|
{
|
|
PRINT("%s: cache '%s' free\n", __func__, cache->name);
|
|
|
|
BLI_ghash_free(cache->hash, moviecache_keyfree, moviecache_valfree);
|
|
|
|
BLI_mempool_destroy(cache->keys_pool);
|
|
BLI_mempool_destroy(cache->items_pool);
|
|
BLI_mempool_destroy(cache->userkeys_pool);
|
|
|
|
if (cache->points) {
|
|
MEM_freeN(cache->points);
|
|
}
|
|
|
|
if (cache->last_userkey) {
|
|
MEM_freeN(cache->last_userkey);
|
|
}
|
|
|
|
MEM_freeN(cache);
|
|
}
|
|
|
|
void IMB_moviecache_cleanup(MovieCache *cache,
|
|
bool(cleanup_check_cb)(ImBuf *ibuf, void *userkey, void *userdata),
|
|
void *userdata)
|
|
{
|
|
GHashIterator gh_iter;
|
|
|
|
check_unused_keys(cache);
|
|
|
|
BLI_ghashIterator_init(&gh_iter, cache->hash);
|
|
|
|
while (!BLI_ghashIterator_done(&gh_iter)) {
|
|
MovieCacheKey *key = (MovieCacheKey *)BLI_ghashIterator_getKey(&gh_iter);
|
|
MovieCacheItem *item = (MovieCacheItem *)BLI_ghashIterator_getValue(&gh_iter);
|
|
|
|
BLI_ghashIterator_step(&gh_iter);
|
|
|
|
if (cleanup_check_cb(item->ibuf, key->userkey, userdata)) {
|
|
PRINT("%s: cache '%s' remove item %p\n", __func__, cache->name, item);
|
|
|
|
BLI_ghash_remove(cache->hash, key, moviecache_keyfree, moviecache_valfree);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IMB_moviecache_get_cache_segments(
|
|
MovieCache *cache, int proxy, int render_flags, int *r_totseg, int **r_points)
|
|
{
|
|
*r_totseg = 0;
|
|
*r_points = nullptr;
|
|
|
|
if (!cache->getdatafp) {
|
|
return;
|
|
}
|
|
|
|
if (cache->proxy != proxy || cache->render_flags != render_flags) {
|
|
MEM_SAFE_FREE(cache->points);
|
|
}
|
|
|
|
if (cache->points) {
|
|
*r_totseg = cache->totseg;
|
|
*r_points = cache->points;
|
|
}
|
|
else {
|
|
int totframe = BLI_ghash_len(cache->hash);
|
|
int *frames = (int *)MEM_callocN(totframe * sizeof(int), "movieclip cache frames");
|
|
int a, totseg = 0;
|
|
GHashIterator gh_iter;
|
|
|
|
a = 0;
|
|
GHASH_ITER (gh_iter, cache->hash) {
|
|
MovieCacheKey *key = (MovieCacheKey *)BLI_ghashIterator_getKey(&gh_iter);
|
|
MovieCacheItem *item = (MovieCacheItem *)BLI_ghashIterator_getValue(&gh_iter);
|
|
int framenr, curproxy, curflags;
|
|
|
|
if (item->ibuf) {
|
|
cache->getdatafp(key->userkey, &framenr, &curproxy, &curflags);
|
|
|
|
if (curproxy == proxy && curflags == render_flags) {
|
|
frames[a++] = framenr;
|
|
}
|
|
}
|
|
}
|
|
|
|
qsort(frames, totframe, sizeof(int), compare_int);
|
|
|
|
/* count */
|
|
for (a = 0; a < totframe; a++) {
|
|
if (a && frames[a] - frames[a - 1] != 1) {
|
|
totseg++;
|
|
}
|
|
|
|
if (a == totframe - 1) {
|
|
totseg++;
|
|
}
|
|
}
|
|
|
|
if (totseg) {
|
|
int b, *points;
|
|
|
|
points = (int *)MEM_callocN(sizeof(int[2]) * totseg, "movieclip cache segments");
|
|
|
|
/* fill */
|
|
for (a = 0, b = 0; a < totframe; a++) {
|
|
if (a == 0) {
|
|
points[b++] = frames[a];
|
|
}
|
|
|
|
if (a && frames[a] - frames[a - 1] != 1) {
|
|
points[b++] = frames[a - 1];
|
|
points[b++] = frames[a];
|
|
}
|
|
|
|
if (a == totframe - 1) {
|
|
points[b++] = frames[a];
|
|
}
|
|
}
|
|
|
|
*r_totseg = totseg;
|
|
*r_points = points;
|
|
|
|
cache->totseg = totseg;
|
|
cache->points = points;
|
|
cache->proxy = proxy;
|
|
cache->render_flags = render_flags;
|
|
}
|
|
|
|
MEM_freeN(frames);
|
|
}
|
|
}
|
|
|
|
MovieCacheIter *IMB_moviecacheIter_new(MovieCache *cache)
|
|
{
|
|
GHashIterator *iter;
|
|
|
|
check_unused_keys(cache);
|
|
iter = BLI_ghashIterator_new(cache->hash);
|
|
|
|
return (MovieCacheIter *)iter;
|
|
}
|
|
|
|
void IMB_moviecacheIter_free(MovieCacheIter *iter)
|
|
{
|
|
BLI_ghashIterator_free((GHashIterator *)iter);
|
|
}
|
|
|
|
bool IMB_moviecacheIter_done(MovieCacheIter *iter)
|
|
{
|
|
return BLI_ghashIterator_done((GHashIterator *)iter);
|
|
}
|
|
|
|
void IMB_moviecacheIter_step(MovieCacheIter *iter)
|
|
{
|
|
BLI_ghashIterator_step((GHashIterator *)iter);
|
|
}
|
|
|
|
ImBuf *IMB_moviecacheIter_getImBuf(MovieCacheIter *iter)
|
|
{
|
|
MovieCacheItem *item = (MovieCacheItem *)BLI_ghashIterator_getValue((GHashIterator *)iter);
|
|
return item->ibuf;
|
|
}
|
|
|
|
void *IMB_moviecacheIter_getUserKey(MovieCacheIter *iter)
|
|
{
|
|
MovieCacheKey *key = (MovieCacheKey *)BLI_ghashIterator_getKey((GHashIterator *)iter);
|
|
return key->userkey;
|
|
}
|