diff options
Diffstat (limited to 'source/blender/sequencer/intern/image_cache.c')
-rw-r--r-- | source/blender/sequencer/intern/image_cache.c | 1475 |
1 files changed, 1475 insertions, 0 deletions
diff --git a/source/blender/sequencer/intern/image_cache.c b/source/blender/sequencer/intern/image_cache.c new file mode 100644 index 00000000000..be6a640dd3f --- /dev/null +++ b/source/blender/sequencer/intern/image_cache.c @@ -0,0 +1,1475 @@ +/* + * 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. + * + * Peter Schlaile <peter [at] schlaile [dot] de> 2010 + */ + +/** \file + * \ingroup bke + */ + +#include <memory.h> +#include <stddef.h> +#include <time.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_scene_types.h" +#include "DNA_sequence_types.h" +#include "DNA_space_types.h" /* for FILE_MAX. */ + +#include "IMB_colormanagement.h" +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "BLI_blenlib.h" +#include "BLI_endian_switch.h" +#include "BLI_fileops.h" +#include "BLI_fileops_types.h" +#include "BLI_ghash.h" +#include "BLI_listbase.h" +#include "BLI_mempool.h" +#include "BLI_path_util.h" +#include "BLI_threads.h" + +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_scene.h" + +#include "SEQ_sequencer.h" + +#include "render.h" +#include "sequencer.h" + +/** + * Sequencer Cache Design Notes + * ============================ + * + * Function: + * All images created during rendering are added to cache, even if the cache is already full. + * This is because: + * - one image may be needed multiple times during rendering. + * - keeping the last rendered frame allows us for faster re-render when user edits strip in stack + * - we can decide if we keep frame only when it's completely rendered. Otherwise we risk having + * "holes" in the cache, which can be annoying + * If the cache is full all entries for pending frame will have is_temp_cache set. + * + * Linking: We use links to reduce number of iterations over entries needed to manage cache. + * Entries are linked in order as they are put into cache. + * Only permanent (is_temp_cache = 0) cache entries are linked. + * Putting #SEQ_CACHE_STORE_FINAL_OUT will reset linking + * + * Only entire frame can be freed to release resources for new entries (recycling). + * Once again, this is to reduce number of iterations, but also more controllable than removing + * entries one by one in reverse order to their creation. + * + * User can exclude caching of some images. Such entries will have is_temp_cache set. + * + * + * Disk Cache Design Notes + * ======================= + * + * Disk cache uses directory specified in user preferences + * For each cached non-temp image, image data and supplementary info are written to HDD. + * Multiple(DCACHE_IMAGES_PER_FILE) images share the same file. + * Each of these files contains header DiskCacheHeader followed by image data. + * Zlib compression with user definable level can be used to compress image data(per image) + * Images are written in order in which they are rendered. + * Overwriting of individual entry is not possible. + * Stored images are deleted by invalidation, or when size of all files exceeds maximum + * size specified in user preferences. + * To distinguish 2 blend files with same name, scene->ed->disk_cache_timestamp + * is used as UID. Blend file can still be copied manually which may cause conflict. + * + */ + +/* <cache type>-<resolution X>x<resolution Y>-<rendersize>%(<view_id>)-<frame no>.dcf */ +#define DCACHE_FNAME_FORMAT "%d-%dx%d-%d%%(%d)-%d.dcf" +#define DCACHE_IMAGES_PER_FILE 100 +#define DCACHE_CURRENT_VERSION 1 +#define COLORSPACE_NAME_MAX 64 /* XXX: defined in imb intern */ + +typedef struct DiskCacheHeaderEntry { + unsigned char encoding; + uint64_t frameno; + uint64_t size_compressed; + uint64_t size_raw; + uint64_t offset; + char colorspace_name[COLORSPACE_NAME_MAX]; +} DiskCacheHeaderEntry; + +typedef struct DiskCacheHeader { + DiskCacheHeaderEntry entry[DCACHE_IMAGES_PER_FILE]; +} DiskCacheHeader; + +typedef struct SeqDiskCache { + Main *bmain; + int64_t timestamp; + ListBase files; + ThreadMutex read_write_mutex; + size_t size_total; +} SeqDiskCache; + +typedef struct DiskCacheFile { + struct DiskCacheFile *next, *prev; + char path[FILE_MAX]; + char dir[FILE_MAXDIR]; + char file[FILE_MAX]; + BLI_stat_t fstat; + int cache_type; + int rectx; + int recty; + int render_size; + int view_id; + int start_frame; +} DiskCacheFile; + +typedef struct SeqCache { + Main *bmain; + struct GHash *hash; + ThreadMutex iterator_mutex; + struct BLI_mempool *keys_pool; + struct BLI_mempool *items_pool; + struct SeqCacheKey *last_key; + size_t memory_used; + SeqDiskCache *disk_cache; +} SeqCache; + +typedef struct SeqCacheItem { + struct SeqCache *cache_owner; + struct ImBuf *ibuf; +} SeqCacheItem; + +typedef struct SeqCacheKey { + struct SeqCache *cache_owner; + void *userkey; + struct SeqCacheKey *link_prev; /* Used for linking intermediate items to final frame. */ + struct SeqCacheKey *link_next; /* Used for linking intermediate items to final frame. */ + struct Sequence *seq; + SeqRenderData context; + float frame_index; /* Usually same as timeline_frame. Mapped to media for RAW entries. */ + float timeline_frame; /* Only for reference - used for freeing when cache is full. */ + float cost; /* In short: render time(s) divided by playback frame duration(s) */ + bool is_temp_cache; /* this cache entry will be freed before rendering next frame */ + /* ID of task for asigning temp cache entries to particular task(thread, etc.) */ + eSeqTaskId task_id; + int type; +} SeqCacheKey; + +static ThreadMutex cache_create_lock = BLI_MUTEX_INITIALIZER; +static float seq_cache_timeline_frame_to_frame_index(Sequence *seq, + float timeline_frame, + int type); +static float seq_cache_frame_index_to_timeline_frame(Sequence *seq, float frame_index); + +static char *seq_disk_cache_base_dir(void) +{ + return U.sequencer_disk_cache_dir; +} + +static int seq_disk_cache_compression_level(void) +{ + switch (U.sequencer_disk_cache_compression) { + case USER_SEQ_DISK_CACHE_COMPRESSION_NONE: + return 0; + case USER_SEQ_DISK_CACHE_COMPRESSION_LOW: + return 1; + case USER_SEQ_DISK_CACHE_COMPRESSION_HIGH: + return 9; + } + + return U.sequencer_disk_cache_compression; +} + +static size_t seq_disk_cache_size_limit(void) +{ + return (size_t)U.sequencer_disk_cache_size_limit * (1024 * 1024 * 1024); +} + +static bool seq_disk_cache_is_enabled(Main *bmain) +{ + return (U.sequencer_disk_cache_dir[0] != '\0' && U.sequencer_disk_cache_size_limit != 0 && + (U.sequencer_disk_cache_flag & SEQ_CACHE_DISK_CACHE_ENABLE) != 0 && + bmain->name[0] != '\0'); +} + +static DiskCacheFile *seq_disk_cache_add_file_to_list(SeqDiskCache *disk_cache, const char *path) +{ + + DiskCacheFile *cache_file = MEM_callocN(sizeof(DiskCacheFile), "SeqDiskCacheFile"); + char dir[FILE_MAXDIR], file[FILE_MAX]; + BLI_split_dirfile(path, dir, file, sizeof(dir), sizeof(file)); + BLI_strncpy(cache_file->path, path, sizeof(cache_file->path)); + BLI_strncpy(cache_file->dir, dir, sizeof(cache_file->dir)); + BLI_strncpy(cache_file->file, file, sizeof(cache_file->file)); + sscanf(file, + DCACHE_FNAME_FORMAT, + &cache_file->cache_type, + &cache_file->rectx, + &cache_file->recty, + &cache_file->render_size, + &cache_file->view_id, + &cache_file->start_frame); + cache_file->start_frame *= DCACHE_IMAGES_PER_FILE; + BLI_addtail(&disk_cache->files, cache_file); + return cache_file; +} + +static void seq_disk_cache_get_files(SeqDiskCache *disk_cache, char *path) +{ + struct direntry *filelist, *fl; + uint nbr, i; + disk_cache->size_total = 0; + + i = nbr = BLI_filelist_dir_contents(path, &filelist); + fl = filelist; + while (i--) { + /* Don't follow links. */ + const eFileAttributes file_attrs = BLI_file_attributes(fl->path); + if (file_attrs & FILE_ATTR_ANY_LINK) { + fl++; + continue; + } + + char file[FILE_MAX]; + BLI_split_dirfile(fl->path, NULL, file, 0, sizeof(file)); + + bool is_dir = BLI_is_dir(fl->path); + if (is_dir && !FILENAME_IS_CURRPAR(file)) { + char subpath[FILE_MAX]; + BLI_strncpy(subpath, fl->path, sizeof(subpath)); + BLI_path_slash_ensure(subpath); + seq_disk_cache_get_files(disk_cache, subpath); + } + + if (!is_dir) { + const char *ext = BLI_path_extension(fl->path); + if (ext && ext[1] == 'd' && ext[2] == 'c' && ext[3] == 'f') { + DiskCacheFile *cache_file = seq_disk_cache_add_file_to_list(disk_cache, fl->path); + cache_file->fstat = fl->s; + disk_cache->size_total += cache_file->fstat.st_size; + } + } + fl++; + } + BLI_filelist_free(filelist, nbr); +} + +static DiskCacheFile *seq_disk_cache_get_oldest_file(SeqDiskCache *disk_cache) +{ + DiskCacheFile *oldest_file = disk_cache->files.first; + if (oldest_file == NULL) { + return NULL; + } + for (DiskCacheFile *cache_file = oldest_file->next; cache_file; cache_file = cache_file->next) { + if (cache_file->fstat.st_mtime < oldest_file->fstat.st_mtime) { + oldest_file = cache_file; + } + } + + return oldest_file; +} + +static void seq_disk_cache_delete_file(SeqDiskCache *disk_cache, DiskCacheFile *file) +{ + disk_cache->size_total -= file->fstat.st_size; + BLI_delete(file->path, false, false); + BLI_remlink(&disk_cache->files, file); + MEM_freeN(file); +} + +static bool seq_disk_cache_enforce_limits(SeqDiskCache *disk_cache) +{ + BLI_mutex_lock(&disk_cache->read_write_mutex); + while (disk_cache->size_total > seq_disk_cache_size_limit()) { + DiskCacheFile *oldest_file = seq_disk_cache_get_oldest_file(disk_cache); + + if (!oldest_file) { + /* We shouldn't enforce limits with no files, do re-scan. */ + seq_disk_cache_get_files(disk_cache, seq_disk_cache_base_dir()); + continue; + } + + if (BLI_exists(oldest_file->path) == 0) { + /* File may have been manually deleted during runtime, do re-scan. */ + BLI_freelistN(&disk_cache->files); + seq_disk_cache_get_files(disk_cache, seq_disk_cache_base_dir()); + continue; + } + + seq_disk_cache_delete_file(disk_cache, oldest_file); + } + BLI_mutex_unlock(&disk_cache->read_write_mutex); + + return true; +} + +static DiskCacheFile *seq_disk_cache_get_file_entry_by_path(SeqDiskCache *disk_cache, char *path) +{ + DiskCacheFile *cache_file = disk_cache->files.first; + + for (; cache_file; cache_file = cache_file->next) { + if (BLI_strcasecmp(cache_file->path, path) == 0) { + return cache_file; + } + } + + return NULL; +} + +/* Update file size and timestamp. */ +static void seq_disk_cache_update_file(SeqDiskCache *disk_cache, char *path) +{ + DiskCacheFile *cache_file; + int64_t size_before; + int64_t size_after; + + cache_file = seq_disk_cache_get_file_entry_by_path(disk_cache, path); + size_before = cache_file->fstat.st_size; + + if (BLI_stat(path, &cache_file->fstat) == -1) { + BLI_assert(false); + memset(&cache_file->fstat, 0, sizeof(BLI_stat_t)); + } + + size_after = cache_file->fstat.st_size; + disk_cache->size_total += size_after - size_before; +} + +/* Path format: + * <cache dir>/<project name>/<scene name>-<timestamp>/<seq name>/DCACHE_FNAME_FORMAT + */ + +static void seq_disk_cache_get_project_dir(SeqDiskCache *disk_cache, char *path, size_t path_len) +{ + char main_name[FILE_MAX]; + BLI_split_file_part(BKE_main_blendfile_path(disk_cache->bmain), main_name, sizeof(main_name)); + BLI_strncpy(path, seq_disk_cache_base_dir(), path_len); + BLI_path_append(path, path_len, main_name); +} + +static void seq_disk_cache_get_dir( + SeqDiskCache *disk_cache, Scene *scene, Sequence *seq, char *path, size_t path_len) +{ + char scene_name[MAX_ID_NAME + 22]; /* + -%PRId64 */ + char seq_name[SEQ_NAME_MAXSTR]; + char project_dir[FILE_MAX]; + + seq_disk_cache_get_project_dir(disk_cache, project_dir, sizeof(project_dir)); + sprintf(scene_name, "%s-%" PRId64, scene->id.name, disk_cache->timestamp); + BLI_strncpy(seq_name, seq->name, sizeof(seq_name)); + BLI_filename_make_safe(scene_name); + BLI_filename_make_safe(seq_name); + BLI_strncpy(path, project_dir, path_len); + BLI_path_append(path, path_len, scene_name); + BLI_path_append(path, path_len, seq_name); +} + +static void seq_disk_cache_get_file_path(SeqDiskCache *disk_cache, + SeqCacheKey *key, + char *path, + size_t path_len) +{ + seq_disk_cache_get_dir(disk_cache, key->context.scene, key->seq, path, path_len); + int frameno = (int)key->frame_index / DCACHE_IMAGES_PER_FILE; + char cache_filename[FILE_MAXFILE]; + sprintf(cache_filename, + DCACHE_FNAME_FORMAT, + key->type, + key->context.rectx, + key->context.recty, + key->context.preview_render_size, + key->context.view_id, + frameno); + + BLI_path_append(path, path_len, cache_filename); +} + +static void seq_disk_cache_create_version_file(char *path) +{ + BLI_make_existing_file(path); + + FILE *file = BLI_fopen(path, "w"); + if (file) { + fprintf(file, "%d", DCACHE_CURRENT_VERSION); + fclose(file); + } +} + +static void seq_disk_cache_handle_versioning(SeqDiskCache *disk_cache) +{ + char path[FILE_MAX]; + char path_version_file[FILE_MAX]; + int version = 0; + + seq_disk_cache_get_project_dir(disk_cache, path, sizeof(path)); + BLI_strncpy(path_version_file, path, sizeof(path_version_file)); + BLI_path_append(path_version_file, sizeof(path_version_file), "cache_version"); + + if (BLI_exists(path)) { + FILE *file = BLI_fopen(path_version_file, "r"); + + if (file) { + fscanf(file, "%d", &version); + fclose(file); + } + + if (version != DCACHE_CURRENT_VERSION) { + BLI_delete(path, false, true); + seq_disk_cache_create_version_file(path_version_file); + } + } + else { + seq_disk_cache_create_version_file(path_version_file); + } +} + +static void seq_disk_cache_delete_invalid_files(SeqDiskCache *disk_cache, + Scene *scene, + Sequence *seq, + int invalidate_types, + int range_start, + int range_end) +{ + DiskCacheFile *next_file, *cache_file = disk_cache->files.first; + char cache_dir[FILE_MAX]; + seq_disk_cache_get_dir(disk_cache, scene, seq, cache_dir, sizeof(cache_dir)); + BLI_path_slash_ensure(cache_dir); + + while (cache_file) { + next_file = cache_file->next; + if (cache_file->cache_type & invalidate_types) { + if (STREQ(cache_dir, cache_file->dir)) { + int timeline_frame_start = seq_cache_frame_index_to_timeline_frame( + seq, cache_file->start_frame); + if (timeline_frame_start > range_start && timeline_frame_start <= range_end) { + seq_disk_cache_delete_file(disk_cache, cache_file); + } + } + } + cache_file = next_file; + } +} + +static void seq_disk_cache_invalidate(Scene *scene, + Sequence *seq, + Sequence *seq_changed, + int invalidate_types) +{ + int start; + int end; + SeqDiskCache *disk_cache = scene->ed->cache->disk_cache; + + BLI_mutex_lock(&disk_cache->read_write_mutex); + + start = seq_changed->startdisp - DCACHE_IMAGES_PER_FILE; + end = seq_changed->enddisp; + + seq_disk_cache_delete_invalid_files(disk_cache, scene, seq, invalidate_types, start, end); + + BLI_mutex_unlock(&disk_cache->read_write_mutex); +} + +static size_t deflate_imbuf_to_file(ImBuf *ibuf, + FILE *file, + int level, + DiskCacheHeaderEntry *header_entry) +{ + if (ibuf->rect) { + return BLI_gzip_mem_to_file_at_pos( + ibuf->rect, header_entry->size_raw, file, header_entry->offset, level); + } + + return BLI_gzip_mem_to_file_at_pos( + ibuf->rect_float, header_entry->size_raw, file, header_entry->offset, level); +} + +static size_t inflate_file_to_imbuf(ImBuf *ibuf, FILE *file, DiskCacheHeaderEntry *header_entry) +{ + if (ibuf->rect) { + return BLI_ungzip_file_to_mem_at_pos( + ibuf->rect, header_entry->size_raw, file, header_entry->offset); + } + + return BLI_ungzip_file_to_mem_at_pos( + ibuf->rect_float, header_entry->size_raw, file, header_entry->offset); +} + +static void seq_disk_cache_read_header(FILE *file, DiskCacheHeader *header) +{ + fseek(file, 0, 0); + fread(header, sizeof(*header), 1, file); + + for (int i = 0; i < DCACHE_IMAGES_PER_FILE; i++) { + if ((ENDIAN_ORDER == B_ENDIAN) && header->entry[i].encoding == 0) { + BLI_endian_switch_uint64(&header->entry[i].frameno); + BLI_endian_switch_uint64(&header->entry[i].offset); + BLI_endian_switch_uint64(&header->entry[i].size_compressed); + BLI_endian_switch_uint64(&header->entry[i].size_raw); + } + } +} + +static size_t seq_disk_cache_write_header(FILE *file, DiskCacheHeader *header) +{ + fseek(file, 0, 0); + return fwrite(header, sizeof(*header), 1, file); +} + +static int seq_disk_cache_add_header_entry(SeqCacheKey *key, ImBuf *ibuf, DiskCacheHeader *header) +{ + int i; + uint64_t offset = sizeof(*header); + + /* Lookup free entry, get offset for new data. */ + for (i = 0; i < DCACHE_IMAGES_PER_FILE; i++) { + if (header->entry[i].size_compressed == 0) { + break; + } + } + + /* Attempt to write beyond set entry limit. + * Reset file header and start writing from beginning. + */ + if (i == DCACHE_IMAGES_PER_FILE) { + i = 0; + memset(header, 0, sizeof(*header)); + } + + /* Calculate offset for image data. */ + if (i > 0) { + offset = header->entry[i - 1].offset + header->entry[i - 1].size_compressed; + } + + if (ENDIAN_ORDER == B_ENDIAN) { + header->entry[i].encoding = 255; + } + else { + header->entry[i].encoding = 0; + } + + header->entry[i].offset = offset; + header->entry[i].frameno = key->frame_index; + + /* Store colorspace name of ibuf. */ + const char *colorspace_name; + if (ibuf->rect) { + header->entry[i].size_raw = ibuf->x * ibuf->y * ibuf->channels; + colorspace_name = IMB_colormanagement_get_rect_colorspace(ibuf); + } + else { + header->entry[i].size_raw = ibuf->x * ibuf->y * ibuf->channels * 4; + colorspace_name = IMB_colormanagement_get_float_colorspace(ibuf); + } + BLI_strncpy( + header->entry[i].colorspace_name, colorspace_name, sizeof(header->entry[i].colorspace_name)); + + return i; +} + +static int seq_disk_cache_get_header_entry(SeqCacheKey *key, DiskCacheHeader *header) +{ + for (int i = 0; i < DCACHE_IMAGES_PER_FILE; i++) { + if (header->entry[i].frameno == key->frame_index) { + return i; + } + } + + return -1; +} + +static bool seq_disk_cache_write_file(SeqDiskCache *disk_cache, SeqCacheKey *key, ImBuf *ibuf) +{ + char path[FILE_MAX]; + + seq_disk_cache_get_file_path(disk_cache, key, path, sizeof(path)); + BLI_make_existing_file(path); + + FILE *file = BLI_fopen(path, "rb+"); + if (!file) { + file = BLI_fopen(path, "wb+"); + if (!file) { + return false; + } + seq_disk_cache_add_file_to_list(disk_cache, path); + } + + DiskCacheHeader header; + memset(&header, 0, sizeof(header)); + seq_disk_cache_read_header(file, &header); + int entry_index = seq_disk_cache_add_header_entry(key, ibuf, &header); + size_t bytes_written = deflate_imbuf_to_file( + ibuf, file, seq_disk_cache_compression_level(), &header.entry[entry_index]); + + if (bytes_written != 0) { + /* Last step is writing header, as image data can be overwritten, + * but missing data would cause problems. + */ + header.entry[entry_index].size_compressed = bytes_written; + seq_disk_cache_write_header(file, &header); + seq_disk_cache_update_file(disk_cache, path); + fclose(file); + + return true; + } + + return false; +} + +static ImBuf *seq_disk_cache_read_file(SeqDiskCache *disk_cache, SeqCacheKey *key) +{ + char path[FILE_MAX]; + DiskCacheHeader header; + + seq_disk_cache_get_file_path(disk_cache, key, path, sizeof(path)); + BLI_make_existing_file(path); + + FILE *file = BLI_fopen(path, "rb"); + if (!file) { + return NULL; + } + + seq_disk_cache_read_header(file, &header); + int entry_index = seq_disk_cache_get_header_entry(key, &header); + + /* Item not found. */ + if (entry_index < 0) { + fclose(file); + return NULL; + } + + ImBuf *ibuf; + uint64_t size_char = (uint64_t)key->context.rectx * key->context.recty * 4; + uint64_t size_float = (uint64_t)key->context.rectx * key->context.recty * 16; + size_t expected_size; + + if (header.entry[entry_index].size_raw == size_char) { + expected_size = size_char; + ibuf = IMB_allocImBuf(key->context.rectx, key->context.recty, 32, IB_rect); + IMB_colormanagement_assign_rect_colorspace(ibuf, header.entry[entry_index].colorspace_name); + } + else if (header.entry[entry_index].size_raw == size_float) { + expected_size = size_float; + ibuf = IMB_allocImBuf(key->context.rectx, key->context.recty, 32, IB_rectfloat); + IMB_colormanagement_assign_float_colorspace(ibuf, header.entry[entry_index].colorspace_name); + } + else { + fclose(file); + return NULL; + } + + size_t bytes_read = inflate_file_to_imbuf(ibuf, file, &header.entry[entry_index]); + + /* Sanity check. */ + if (bytes_read != expected_size) { + fclose(file); + IMB_freeImBuf(ibuf); + return NULL; + } + BLI_file_touch(path); + seq_disk_cache_update_file(disk_cache, path); + fclose(file); + + return ibuf; +} + +#undef DCACHE_FNAME_FORMAT +#undef DCACHE_IMAGES_PER_FILE +#undef COLORSPACE_NAME_MAX +#undef DCACHE_CURRENT_VERSION + +static bool seq_cmp_render_data(const SeqRenderData *a, const SeqRenderData *b) +{ + return ((a->preview_render_size != b->preview_render_size) || (a->rectx != b->rectx) || + (a->recty != b->recty) || (a->bmain != b->bmain) || (a->scene != b->scene) || + (a->motion_blur_shutter != b->motion_blur_shutter) || + (a->motion_blur_samples != b->motion_blur_samples) || + (a->scene->r.views_format != b->scene->r.views_format) || (a->view_id != b->view_id)); +} + +static unsigned int seq_hash_render_data(const SeqRenderData *a) +{ + unsigned int rval = a->rectx + a->recty; + + rval ^= a->preview_render_size; + rval ^= ((intptr_t)a->bmain) << 6; + rval ^= ((intptr_t)a->scene) << 6; + rval ^= (int)(a->motion_blur_shutter * 100.0f) << 10; + rval ^= a->motion_blur_samples << 16; + rval ^= ((a->scene->r.views_format * 2) + a->view_id) << 24; + + return rval; +} + +static unsigned int seq_cache_hashhash(const void *key_) +{ + const SeqCacheKey *key = key_; + unsigned int rval = seq_hash_render_data(&key->context); + + rval ^= *(const unsigned int *)&key->frame_index; + rval += key->type; + rval ^= ((intptr_t)key->seq) << 6; + + return rval; +} + +static bool seq_cache_hashcmp(const void *a_, const void *b_) +{ + const SeqCacheKey *a = a_; + const SeqCacheKey *b = b_; + + return ((a->seq != b->seq) || (a->frame_index != b->frame_index) || (a->type != b->type) || + seq_cmp_render_data(&a->context, &b->context)); +} + +static float seq_cache_timeline_frame_to_frame_index(Sequence *seq, float timeline_frame, int type) +{ + /* With raw images, map timeline_frame to strip input media frame range. This means that static + * images or extended frame range of movies will only generate one cache entry. No special + * treatment in converting frame index to timeline_frame is needed. */ + if (type == SEQ_CACHE_STORE_RAW) { + return seq_give_frame_index(seq, timeline_frame); + } + + return timeline_frame - seq->start; +} + +static float seq_cache_frame_index_to_timeline_frame(Sequence *seq, float frame_index) +{ + return frame_index + seq->start; +} + +static SeqCache *seq_cache_get_from_scene(Scene *scene) +{ + if (scene && scene->ed && scene->ed->cache) { + return scene->ed->cache; + } + + return NULL; +} + +static void seq_cache_lock(Scene *scene) +{ + SeqCache *cache = seq_cache_get_from_scene(scene); + + if (cache) { + BLI_mutex_lock(&cache->iterator_mutex); + } +} + +static void seq_cache_unlock(Scene *scene) +{ + SeqCache *cache = seq_cache_get_from_scene(scene); + + if (cache) { + BLI_mutex_unlock(&cache->iterator_mutex); + } +} + +static size_t seq_cache_get_mem_total(void) +{ + return ((size_t)U.memcachelimit) * 1024 * 1024; +} + +static void seq_cache_keyfree(void *val) +{ + SeqCacheKey *key = val; + BLI_mempool_free(key->cache_owner->keys_pool, key); +} + +static void seq_cache_valfree(void *val) +{ + SeqCacheItem *item = (SeqCacheItem *)val; + SeqCache *cache = item->cache_owner; + + if (item->ibuf) { + cache->memory_used -= IMB_get_size_in_memory(item->ibuf); + IMB_freeImBuf(item->ibuf); + } + + BLI_mempool_free(item->cache_owner->items_pool, item); +} + +static void seq_cache_put(SeqCache *cache, SeqCacheKey *key, ImBuf *ibuf) +{ + SeqCacheItem *item; + item = BLI_mempool_alloc(cache->items_pool); + item->cache_owner = cache; + item->ibuf = ibuf; + + if (BLI_ghash_reinsert(cache->hash, key, item, seq_cache_keyfree, seq_cache_valfree)) { + IMB_refImBuf(ibuf); + cache->last_key = key; + cache->memory_used += IMB_get_size_in_memory(ibuf); + } +} + +static ImBuf *seq_cache_get(SeqCache *cache, SeqCacheKey *key) +{ + SeqCacheItem *item = BLI_ghash_lookup(cache->hash, key); + + if (item && item->ibuf) { + IMB_refImBuf(item->ibuf); + + return item->ibuf; + } + + return NULL; +} + +static void seq_cache_relink_keys(SeqCacheKey *link_next, SeqCacheKey *link_prev) +{ + if (link_next) { + link_next->link_prev = link_prev; + } + if (link_prev) { + link_prev->link_next = link_next; + } +} + +/* Choose a key out of 2 candidates(leftmost and rightmost items) + * to recycle based on currently used strategy */ +static SeqCacheKey *seq_cache_choose_key(Scene *scene, SeqCacheKey *lkey, SeqCacheKey *rkey) +{ + SeqCacheKey *finalkey = NULL; + + /* Ideally, cache would not need to check the state of prefetching task + * that is tricky to do however, because prefetch would need to know, + * if a key, that is about to be created would be removed by itself. + * + * This can happen because only FINAL_OUT item insertion will trigger recycling + * but that is also the point, where prefetch can be suspended. + * + * We could use temp cache as a shield and later make it a non-temporary entry, + * but it is not worth of increasing system complexity. + */ + if (scene->ed->cache_flag & SEQ_CACHE_PREFETCH_ENABLE && + BKE_sequencer_prefetch_job_is_running(scene)) { + int pfjob_start, pfjob_end; + BKE_sequencer_prefetch_get_time_range(scene, &pfjob_start, &pfjob_end); + + if (lkey) { + if (lkey->timeline_frame < pfjob_start || lkey->timeline_frame > pfjob_end) { + return lkey; + } + } + + if (rkey) { + if (rkey->timeline_frame < pfjob_start || rkey->timeline_frame > pfjob_end) { + return rkey; + } + } + + return NULL; + } + + if (rkey && lkey) { + if (lkey->timeline_frame > rkey->timeline_frame) { + SeqCacheKey *swapkey = lkey; + lkey = rkey; + rkey = swapkey; + } + + int l_diff = scene->r.cfra - lkey->timeline_frame; + int r_diff = rkey->timeline_frame - scene->r.cfra; + + if (l_diff > r_diff) { + finalkey = lkey; + } + else { + finalkey = rkey; + } + } + else { + if (lkey) { + finalkey = lkey; + } + else { + finalkey = rkey; + } + } + return finalkey; +} + +static void seq_cache_recycle_linked(Scene *scene, SeqCacheKey *base) +{ + SeqCache *cache = seq_cache_get_from_scene(scene); + if (!cache) { + return; + } + + SeqCacheKey *next = base->link_next; + + while (base) { + SeqCacheKey *prev = base->link_prev; + BLI_ghash_remove(cache->hash, base, seq_cache_keyfree, seq_cache_valfree); + base = prev; + } + + base = next; + while (base) { + next = base->link_next; + BLI_ghash_remove(cache->hash, base, seq_cache_keyfree, seq_cache_valfree); + base = next; + } +} + +static SeqCacheKey *seq_cache_get_item_for_removal(Scene *scene) +{ + SeqCache *cache = seq_cache_get_from_scene(scene); + SeqCacheKey *finalkey = NULL; + /* Leftmost key. */ + SeqCacheKey *lkey = NULL; + /* Rightmost key. */ + SeqCacheKey *rkey = NULL; + SeqCacheKey *key = NULL; + + GHashIterator gh_iter; + BLI_ghashIterator_init(&gh_iter, cache->hash); + int total_count = 0; + int cheap_count = 0; + + while (!BLI_ghashIterator_done(&gh_iter)) { + key = BLI_ghashIterator_getKey(&gh_iter); + SeqCacheItem *item = BLI_ghashIterator_getValue(&gh_iter); + BLI_ghashIterator_step(&gh_iter); + + /* This shouldn't happen, but better be safe than sorry. */ + if (!item->ibuf) { + seq_cache_recycle_linked(scene, key); + /* Can not continue iterating after linked remove. */ + BLI_ghashIterator_init(&gh_iter, cache->hash); + continue; + } + + if (key->is_temp_cache || key->link_next != NULL) { + continue; + } + + total_count++; + + if (key->cost <= scene->ed->recycle_max_cost) { + cheap_count++; + if (lkey) { + if (key->timeline_frame < lkey->timeline_frame) { + lkey = key; + } + } + else { + lkey = key; + } + if (rkey) { + if (key->timeline_frame > rkey->timeline_frame) { + rkey = key; + } + } + else { + rkey = key; + } + } + } + + finalkey = seq_cache_choose_key(scene, lkey, rkey); + + return finalkey; +} + +/* Find only "base" keys. + * Sources(other types) for a frame must be freed all at once. + */ +bool BKE_sequencer_cache_recycle_item(Scene *scene) +{ + size_t memory_total = seq_cache_get_mem_total(); + SeqCache *cache = seq_cache_get_from_scene(scene); + if (!cache) { + return false; + } + + seq_cache_lock(scene); + + while (cache->memory_used > memory_total) { + SeqCacheKey *finalkey = seq_cache_get_item_for_removal(scene); + + if (finalkey) { + seq_cache_recycle_linked(scene, finalkey); + } + else { + seq_cache_unlock(scene); + return false; + } + } + seq_cache_unlock(scene); + return true; +} + +static void seq_cache_set_temp_cache_linked(Scene *scene, SeqCacheKey *base) +{ + SeqCache *cache = seq_cache_get_from_scene(scene); + + if (!cache || !base) { + return; + } + + SeqCacheKey *next = base->link_next; + + while (base) { + SeqCacheKey *prev = base->link_prev; + base->is_temp_cache = true; + base = prev; + } + + base = next; + while (base) { + next = base->link_next; + base->is_temp_cache = true; + base = next; + } +} + +static void seq_disk_cache_create(Main *bmain, Scene *scene) +{ + BLI_mutex_lock(&cache_create_lock); + SeqCache *cache = seq_cache_get_from_scene(scene); + + if (cache == NULL) { + return; + } + + if (cache->disk_cache != NULL) { + return; + } + + cache->disk_cache = MEM_callocN(sizeof(SeqDiskCache), "SeqDiskCache"); + cache->disk_cache->bmain = bmain; + BLI_mutex_init(&cache->disk_cache->read_write_mutex); + seq_disk_cache_handle_versioning(cache->disk_cache); + seq_disk_cache_get_files(cache->disk_cache, seq_disk_cache_base_dir()); + cache->disk_cache->timestamp = scene->ed->disk_cache_timestamp; + BLI_mutex_unlock(&cache_create_lock); +} + +static void seq_cache_create(Main *bmain, Scene *scene) +{ + BLI_mutex_lock(&cache_create_lock); + if (scene->ed->cache == NULL) { + SeqCache *cache = MEM_callocN(sizeof(SeqCache), "SeqCache"); + cache->keys_pool = BLI_mempool_create(sizeof(SeqCacheKey), 0, 64, BLI_MEMPOOL_NOP); + cache->items_pool = BLI_mempool_create(sizeof(SeqCacheItem), 0, 64, BLI_MEMPOOL_NOP); + cache->hash = BLI_ghash_new(seq_cache_hashhash, seq_cache_hashcmp, "SeqCache hash"); + cache->last_key = NULL; + cache->bmain = bmain; + BLI_mutex_init(&cache->iterator_mutex); + scene->ed->cache = cache; + + if (scene->ed->disk_cache_timestamp == 0) { + scene->ed->disk_cache_timestamp = time(NULL); + } + } + BLI_mutex_unlock(&cache_create_lock); +} + +/* ***************************** API ****************************** */ + +void BKE_sequencer_cache_free_temp_cache(Scene *scene, short id, int timeline_frame) +{ + SeqCache *cache = seq_cache_get_from_scene(scene); + if (!cache) { + return; + } + + seq_cache_lock(scene); + + GHashIterator gh_iter; + BLI_ghashIterator_init(&gh_iter, cache->hash); + while (!BLI_ghashIterator_done(&gh_iter)) { + SeqCacheKey *key = BLI_ghashIterator_getKey(&gh_iter); + BLI_ghashIterator_step(&gh_iter); + + if (key->is_temp_cache && key->task_id == id) { + /* Use frame_index here to avoid freeing raw images if they are used for multiple frames. */ + float frame_index = seq_cache_timeline_frame_to_frame_index( + key->seq, timeline_frame, key->type); + if (frame_index != key->frame_index || timeline_frame > key->seq->enddisp || + timeline_frame < key->seq->startdisp) { + BLI_ghash_remove(cache->hash, key, seq_cache_keyfree, seq_cache_valfree); + } + } + } + seq_cache_unlock(scene); +} + +void BKE_sequencer_cache_destruct(Scene *scene) +{ + SeqCache *cache = seq_cache_get_from_scene(scene); + if (!cache) { + return; + } + + BLI_ghash_free(cache->hash, seq_cache_keyfree, seq_cache_valfree); + BLI_mempool_destroy(cache->keys_pool); + BLI_mempool_destroy(cache->items_pool); + BLI_mutex_end(&cache->iterator_mutex); + + if (cache->disk_cache != NULL) { + BLI_freelistN(&cache->disk_cache->files); + BLI_mutex_end(&cache->disk_cache->read_write_mutex); + MEM_freeN(cache->disk_cache); + } + + MEM_freeN(cache); + scene->ed->cache = NULL; +} + +void BKE_sequencer_cache_cleanup_all(Main *bmain) +{ + for (Scene *scene = bmain->scenes.first; scene != NULL; scene = scene->id.next) { + BKE_sequencer_cache_cleanup(scene); + } +} +void BKE_sequencer_cache_cleanup(Scene *scene) +{ + BKE_sequencer_prefetch_stop(scene); + + SeqCache *cache = seq_cache_get_from_scene(scene); + if (!cache) { + return; + } + + seq_cache_lock(scene); + + GHashIterator gh_iter; + BLI_ghashIterator_init(&gh_iter, cache->hash); + while (!BLI_ghashIterator_done(&gh_iter)) { + SeqCacheKey *key = BLI_ghashIterator_getKey(&gh_iter); + + BLI_ghashIterator_step(&gh_iter); + BLI_ghash_remove(cache->hash, key, seq_cache_keyfree, seq_cache_valfree); + } + cache->last_key = NULL; + seq_cache_unlock(scene); +} + +void BKE_sequencer_cache_cleanup_sequence(Scene *scene, + Sequence *seq, + Sequence *seq_changed, + int invalidate_types, + bool force_seq_changed_range) +{ + SeqCache *cache = seq_cache_get_from_scene(scene); + if (!cache) { + return; + } + + if (seq_disk_cache_is_enabled(cache->bmain) && cache->disk_cache != NULL) { + seq_disk_cache_invalidate(scene, seq, seq_changed, invalidate_types); + } + + seq_cache_lock(scene); + + int range_start = seq_changed->startdisp; + int range_end = seq_changed->enddisp; + + if (!force_seq_changed_range) { + if (seq->startdisp > range_start) { + range_start = seq->startdisp; + } + + if (seq->enddisp < range_end) { + range_end = seq->enddisp; + } + } + + int invalidate_composite = invalidate_types & SEQ_CACHE_STORE_FINAL_OUT; + int invalidate_source = invalidate_types & (SEQ_CACHE_STORE_RAW | SEQ_CACHE_STORE_PREPROCESSED | + SEQ_CACHE_STORE_COMPOSITE); + + GHashIterator gh_iter; + BLI_ghashIterator_init(&gh_iter, cache->hash); + while (!BLI_ghashIterator_done(&gh_iter)) { + SeqCacheKey *key = BLI_ghashIterator_getKey(&gh_iter); + BLI_ghashIterator_step(&gh_iter); + + /* Clean all final and composite in intersection of seq and seq_changed. */ + if (key->type & invalidate_composite && key->timeline_frame >= range_start && + key->timeline_frame <= range_end) { + if (key->link_next || key->link_prev) { + seq_cache_relink_keys(key->link_next, key->link_prev); + } + + BLI_ghash_remove(cache->hash, key, seq_cache_keyfree, seq_cache_valfree); + } + + if (key->type & invalidate_source && key->seq == seq && + key->timeline_frame >= seq_changed->startdisp && + key->timeline_frame <= seq_changed->enddisp) { + if (key->link_next || key->link_prev) { + seq_cache_relink_keys(key->link_next, key->link_prev); + } + + BLI_ghash_remove(cache->hash, key, seq_cache_keyfree, seq_cache_valfree); + } + } + cache->last_key = NULL; + seq_cache_unlock(scene); +} + +struct ImBuf *BKE_sequencer_cache_get(const SeqRenderData *context, + Sequence *seq, + float timeline_frame, + int type, + bool skip_disk_cache) +{ + + if (context->skip_cache || context->is_proxy_render || !seq) { + return NULL; + } + + Scene *scene = context->scene; + + if (context->is_prefetch_render) { + context = BKE_sequencer_prefetch_get_original_context(context); + scene = context->scene; + seq = BKE_sequencer_prefetch_get_original_sequence(seq, scene); + } + + if (!seq) { + return NULL; + } + + if (!scene->ed->cache) { + seq_cache_create(context->bmain, scene); + } + + seq_cache_lock(scene); + SeqCache *cache = seq_cache_get_from_scene(scene); + ImBuf *ibuf = NULL; + SeqCacheKey key; + + /* Try RAM cache: */ + if (cache && seq) { + key.seq = seq; + key.context = *context; + key.frame_index = seq_cache_timeline_frame_to_frame_index(seq, timeline_frame, type); + key.type = type; + + ibuf = seq_cache_get(cache, &key); + } + seq_cache_unlock(scene); + + if (ibuf) { + return ibuf; + } + + /* Try disk cache: */ + if (!skip_disk_cache && seq_disk_cache_is_enabled(context->bmain)) { + if (cache->disk_cache == NULL) { + seq_disk_cache_create(context->bmain, context->scene); + } + + BLI_mutex_lock(&cache->disk_cache->read_write_mutex); + ibuf = seq_disk_cache_read_file(cache->disk_cache, &key); + BLI_mutex_unlock(&cache->disk_cache->read_write_mutex); + if (ibuf) { + if (key.type == SEQ_CACHE_STORE_FINAL_OUT) { + BKE_sequencer_cache_put_if_possible(context, seq, timeline_frame, type, ibuf, 0.0f, true); + } + else { + BKE_sequencer_cache_put(context, seq, timeline_frame, type, ibuf, 0.0f, true); + } + } + } + + return ibuf; +} + +bool BKE_sequencer_cache_put_if_possible(const SeqRenderData *context, + Sequence *seq, + float timeline_frame, + int type, + ImBuf *ibuf, + float cost, + bool skip_disk_cache) +{ + Scene *scene = context->scene; + + if (context->is_prefetch_render) { + context = BKE_sequencer_prefetch_get_original_context(context); + scene = context->scene; + seq = BKE_sequencer_prefetch_get_original_sequence(seq, scene); + } + + if (!seq) { + return false; + } + + if (BKE_sequencer_cache_recycle_item(scene)) { + BKE_sequencer_cache_put(context, seq, timeline_frame, type, ibuf, cost, skip_disk_cache); + return true; + } + + seq_cache_set_temp_cache_linked(scene, scene->ed->cache->last_key); + scene->ed->cache->last_key = NULL; + return false; +} + +void BKE_sequencer_cache_put(const SeqRenderData *context, + Sequence *seq, + float timeline_frame, + int type, + ImBuf *i, + float cost, + bool skip_disk_cache) +{ + if (i == NULL || context->skip_cache || context->is_proxy_render || !seq) { + return; + } + + Scene *scene = context->scene; + + if (context->is_prefetch_render) { + context = BKE_sequencer_prefetch_get_original_context(context); + scene = context->scene; + seq = BKE_sequencer_prefetch_get_original_sequence(seq, scene); + BLI_assert(seq != NULL); + } + + /* Prevent reinserting, it breaks cache key linking. */ + ImBuf *test = BKE_sequencer_cache_get(context, seq, timeline_frame, type, true); + if (test) { + IMB_freeImBuf(test); + return; + } + + if (!scene->ed->cache) { + seq_cache_create(context->bmain, scene); + } + + seq_cache_lock(scene); + + SeqCache *cache = seq_cache_get_from_scene(scene); + int flag; + + if (seq->cache_flag & SEQ_CACHE_OVERRIDE) { + flag = seq->cache_flag; + /* Final_out is invalid in context of sequence override. */ + flag -= seq->cache_flag & SEQ_CACHE_STORE_FINAL_OUT; + /* If global setting is enabled however, use it. */ + flag |= scene->ed->cache_flag & SEQ_CACHE_STORE_FINAL_OUT; + } + else { + flag = scene->ed->cache_flag; + } + + if (cost > SEQ_CACHE_COST_MAX) { + cost = SEQ_CACHE_COST_MAX; + } + + SeqCacheKey *key; + key = BLI_mempool_alloc(cache->keys_pool); + key->cache_owner = cache; + key->seq = seq; + key->context = *context; + key->frame_index = seq_cache_timeline_frame_to_frame_index(seq, timeline_frame, type); + key->timeline_frame = timeline_frame; + key->type = type; + key->cost = cost; + key->link_prev = NULL; + key->link_next = NULL; + key->is_temp_cache = true; + key->task_id = context->task_id; + + /* Item stored for later use */ + if (flag & type) { + key->is_temp_cache = false; + key->link_prev = cache->last_key; + } + + SeqCacheKey *temp_last_key = cache->last_key; + seq_cache_put(cache, key, i); + + /* Restore pointer to previous item as this one will be freed when stack is rendered. */ + if (key->is_temp_cache) { + cache->last_key = temp_last_key; + } + + /* Set last_key's reference to this key so we can look up chain backwards. + * Item is already put in cache, so cache->last_key points to current key. + */ + if (flag & type && temp_last_key) { + temp_last_key->link_next = cache->last_key; + } + + /* Reset linking. */ + if (key->type == SEQ_CACHE_STORE_FINAL_OUT) { + cache->last_key = NULL; + } + + seq_cache_unlock(scene); + + if (!key->is_temp_cache && !skip_disk_cache) { + if (seq_disk_cache_is_enabled(context->bmain)) { + if (cache->disk_cache == NULL) { + seq_disk_cache_create(context->bmain, context->scene); + } + + BLI_mutex_lock(&cache->disk_cache->read_write_mutex); + seq_disk_cache_write_file(cache->disk_cache, key, i); + BLI_mutex_unlock(&cache->disk_cache->read_write_mutex); + seq_disk_cache_enforce_limits(cache->disk_cache); + } + } +} + +void BKE_sequencer_cache_iterate(struct Scene *scene, + void *userdata, + bool callback_init(void *userdata, size_t item_count), + bool callback_iter(void *userdata, + struct Sequence *seq, + int timeline_frame, + int cache_type, + float cost)) +{ + SeqCache *cache = seq_cache_get_from_scene(scene); + if (!cache) { + return; + } + + seq_cache_lock(scene); + bool interrupt = callback_init(userdata, BLI_ghash_len(cache->hash)); + + GHashIterator gh_iter; + BLI_ghashIterator_init(&gh_iter, cache->hash); + + while (!BLI_ghashIterator_done(&gh_iter) && !interrupt) { + SeqCacheKey *key = BLI_ghashIterator_getKey(&gh_iter); + BLI_ghashIterator_step(&gh_iter); + + interrupt = callback_iter(userdata, key->seq, key->timeline_frame, key->type, key->cost); + } + + cache->last_key = NULL; + seq_cache_unlock(scene); +} + +bool BKE_sequencer_cache_is_full(Scene *scene) +{ + size_t memory_total = seq_cache_get_mem_total(); + SeqCache *cache = seq_cache_get_from_scene(scene); + if (!cache) { + return false; + } + + return memory_total < cache->memory_used; +} |