This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/imbuf/intern/moviecache.c
Campbell Barton de13d0a80c doxygen: add newline after \file
While \file doesn't need an argument, it can't have another doxy
command after it.
2019-02-18 08:22:12 +11:00

606 lines
14 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2011 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup bke
*/
#undef DEBUG_MESSAGES
#include <stdlib.h> /* for qsort */
#include <memory.h>
#include "MEM_guardedalloc.h"
#include "MEM_CacheLimiterC-Api.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BLI_ghash.h"
#include "BLI_mempool.h"
#include "BLI_threads.h"
#include "IMB_moviecache.h"
#include "IMB_imbuf_types.h"
#include "IMB_imbuf.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 = NULL;
static pthread_mutex_t limitor_lock = BLI_MUTEX_INITIALIZER;
typedef struct MovieCache {
char name[64];
GHash *hash;
GHashHashFP hashfp;
GHashCmpFP cmpfp;
MovieCacheGetKeyDataFP getdatafp;
MovieCacheGetPriorityDataFP getprioritydatafp;
MovieCacheGetItemPriorityFP getitempriorityfp;
MovieCachePriorityDeleterFP prioritydeleterfp;
struct BLI_mempool *keys_pool;
struct BLI_mempool *items_pool;
struct BLI_mempool *userkeys_pool;
int keysize;
void *last_userkey;
int totseg, *points, proxy, render_flags; /* for visual statistics optimization */
int pad;
} MovieCache;
typedef struct MovieCacheKey {
MovieCache *cache_owner;
void *userkey;
} MovieCacheKey;
typedef struct MovieCacheItem {
MovieCache *cache_owner;
ImBuf *ibuf;
MEM_CacheLimiterHandleC *c_handle;
void *priority_data;
} MovieCacheItem;
static unsigned int moviecache_hashhash(const void *keyv)
{
const MovieCacheKey *key = keyv;
return key->cache_owner->hashfp(key->userkey);
}
static bool moviecache_hashcmp(const void *av, const void *bv)
{
const MovieCacheKey *a = av;
const MovieCacheKey *b = bv;
return a->cache_owner->cmpfp(a->userkey, b->userkey);
}
static void moviecache_keyfree(void *val)
{
MovieCacheKey *key = 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->ibuf) {
MEM_CacheLimiter_unmanage(item->c_handle);
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 = BLI_ghashIterator_getKey(&gh_iter);
const MovieCacheItem *item = BLI_ghashIterator_getValue(&gh_iter);
bool remove;
BLI_ghashIterator_step(&gh_iter);
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 = av;
const int *b = bv;
return *a - *b;
}
static void IMB_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 = NULL;
item->c_handle = NULL;
/* force cached segments to be updated */
if (cache->points) {
MEM_freeN(cache->points);
cache->points = NULL;
}
}
}
/* approximate size of ImBuf in memory */
static size_t IMB_get_size_in_memory(ImBuf *ibuf)
{
int a;
size_t size = 0, channel_size = 0;
/* Persistent images should have no affect on how "normal"
* images are cached.
*
* This is a bit arbitrary, but would make it so only movies
* and sequences are memory limited, keeping textures in the
* memory in order to avoid constant file reload on viewport
* update.
*/
if (ibuf->userflags & IB_PERSISTENT) {
return 0;
}
size += sizeof(ImBuf);
if (ibuf->rect)
channel_size += sizeof(char);
if (ibuf->rect_float)
channel_size += sizeof(float);
size += channel_size * ibuf->x * ibuf->y * ibuf->channels;
if (ibuf->miptot) {
for (a = 0; a < ibuf->miptot; a++) {
if (ibuf->mipmap[a])
size += IMB_get_size_in_memory(ibuf->mipmap[a]);
}
}
if (ibuf->tiles) {
size += sizeof(unsigned int) * ibuf->ytiles * ibuf->xtiles;
}
return size;
}
static size_t get_item_size(void *p)
{
size_t size = sizeof(MovieCacheItem);
MovieCacheItem *item = (MovieCacheItem *) p;
if (item->ibuf)
size += IMB_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;
/* 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(void)
{
limitor = new_MEM_CacheLimiter(IMB_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(void)
{
if (limitor)
delete_MEM_CacheLimiter(limitor);
}
MovieCache *IMB_moviecache_create(const char *name, int keysize, GHashHashFP hashfp, GHashCmpFP cmpfp)
{
MovieCache *cache;
PRINT("%s: cache '%s' create\n", __func__, name);
cache = MEM_callocN(sizeof(MovieCache), "MovieCache");
BLI_strncpy(cache->name, name, sizeof(cache->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(struct 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();
IMB_refImBuf(ibuf);
key = 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 = 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 = NULL;
item->priority_data = NULL;
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)
BLI_mutex_lock(&limitor_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)
BLI_mutex_unlock(&limitor_lock);
/* cache limiter can't remove unused keys which points to destroyed values */
check_unused_keys(cache);
if (cache->points) {
MEM_freeN(cache->points);
cache->points = NULL;
}
}
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 = IMB_get_size_in_memory(ibuf);
mem_limit = MEM_CacheLimiter_get_maximum();
BLI_mutex_lock(&limitor_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;
}
BLI_mutex_unlock(&limitor_lock);
return result;
}
ImBuf *IMB_moviecache_get(MovieCache *cache, void *userkey)
{
MovieCacheKey key;
MovieCacheItem *item;
key.cache_owner = cache;
key.userkey = userkey;
item = (MovieCacheItem *)BLI_ghash_lookup(cache->hash, &key);
if (item) {
if (item->ibuf) {
BLI_mutex_lock(&limitor_lock);
MEM_CacheLimiter_touch(item->c_handle);
BLI_mutex_unlock(&limitor_lock);
IMB_refImBuf(item->ibuf);
return item->ibuf;
}
}
return NULL;
}
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 != NULL;
}
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 = BLI_ghashIterator_getKey(&gh_iter);
MovieCacheItem *item = 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);
}
}
}
/* get segments of cached frames. useful for debugging cache policies */
void IMB_moviecache_get_cache_segments(MovieCache *cache, int proxy, int render_flags, int *totseg_r, int **points_r)
{
*totseg_r = 0;
*points_r = NULL;
if (!cache->getdatafp)
return;
if (cache->proxy != proxy || cache->render_flags != render_flags) {
if (cache->points)
MEM_freeN(cache->points);
cache->points = NULL;
}
if (cache->points) {
*totseg_r = cache->totseg;
*points_r = cache->points;
}
else {
int totframe = BLI_ghash_len(cache->hash);
int *frames = 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 = BLI_ghashIterator_getKey(&gh_iter);
MovieCacheItem *item = 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 = MEM_callocN(2 * sizeof(int) * 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];
}
*totseg_r = totseg;
*points_r = points;
cache->totseg = totseg;
cache->points = points;
cache->proxy = proxy;
cache->render_flags = render_flags;
}
MEM_freeN(frames);
}
}
struct MovieCacheIter *IMB_moviecacheIter_new(MovieCache *cache)
{
GHashIterator *iter;
check_unused_keys(cache);
iter = BLI_ghashIterator_new(cache->hash);
return (struct MovieCacheIter *) iter;
}
void IMB_moviecacheIter_free(struct MovieCacheIter *iter)
{
BLI_ghashIterator_free((GHashIterator *) iter);
}
bool IMB_moviecacheIter_done(struct MovieCacheIter *iter)
{
return BLI_ghashIterator_done((GHashIterator *) iter);
}
void IMB_moviecacheIter_step(struct MovieCacheIter *iter)
{
BLI_ghashIterator_step((GHashIterator *) iter);
}
ImBuf *IMB_moviecacheIter_getImBuf(struct MovieCacheIter *iter)
{
MovieCacheItem *item = BLI_ghashIterator_getValue((GHashIterator *) iter);
return item->ibuf;
}
void *IMB_moviecacheIter_getUserKey(struct MovieCacheIter *iter)
{
MovieCacheKey *key = BLI_ghashIterator_getKey((GHashIterator *) iter);
return key->userkey;
}