Rework internals of how VSE caching is done. Primarily to make all the caching logic more understandable from development point of view, but also has several user visible implications (more details in the PR): - Simpler and fewer caching UI options, - Disk cache is gone (primary reason: proxies are kinda the same thing), - VSE cache size set in preferences is actual size used for VSE caches now (previously caching stopped as soon as whole Blender used that much memory, even if some memory usage was not about VSE at all), - Certain scenarios of cache invalidation are faster now. Pull Request: https://projects.blender.org/blender/blender/pulls/137926
288 lines
7.9 KiB
C++
288 lines
7.9 KiB
C++
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup sequencer
|
|
*/
|
|
|
|
#include "BLI_map.hh"
|
|
#include "BLI_mutex.hh"
|
|
#include "BLI_vector.hh"
|
|
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_sequence_types.h"
|
|
|
|
#include "IMB_imbuf.hh"
|
|
|
|
#include "SEQ_relations.hh"
|
|
#include "SEQ_render.hh"
|
|
#include "SEQ_time.hh"
|
|
|
|
#include "prefetch.hh"
|
|
#include "source_image_cache.hh"
|
|
|
|
namespace blender::seq {
|
|
|
|
static Mutex source_image_cache_mutex;
|
|
|
|
struct SourceImageCache {
|
|
struct StripEntry {
|
|
/* Map key is {source media frame index (i.e. movie frame), view ID}. */
|
|
Map<std::pair<int, int>, ImBuf *> frames;
|
|
};
|
|
|
|
Map<const Strip *, StripEntry> map_;
|
|
|
|
~SourceImageCache()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
for (const auto &item : map_.items()) {
|
|
for (ImBuf *image : item.value.frames.values()) {
|
|
IMB_freeImBuf(image);
|
|
}
|
|
}
|
|
map_.clear();
|
|
}
|
|
|
|
void remove_entry(const Strip *strip)
|
|
{
|
|
StripEntry *entry = map_.lookup_ptr(strip);
|
|
if (entry == nullptr) {
|
|
return;
|
|
}
|
|
for (ImBuf *image : entry->frames.values()) {
|
|
IMB_freeImBuf(image);
|
|
}
|
|
map_.remove_contained(strip);
|
|
}
|
|
};
|
|
|
|
static SourceImageCache *ensure_source_image_cache(Scene *scene)
|
|
{
|
|
SourceImageCache **cache = &scene->ed->runtime.source_image_cache;
|
|
if (*cache == nullptr) {
|
|
*cache = MEM_new<SourceImageCache>(__func__);
|
|
}
|
|
return *cache;
|
|
}
|
|
|
|
static SourceImageCache *query_source_image_cache(const Scene *scene)
|
|
{
|
|
if (scene == nullptr || scene->ed == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return scene->ed->runtime.source_image_cache;
|
|
}
|
|
|
|
ImBuf *source_image_cache_get(const RenderData *context, const Strip *strip, float timeline_frame)
|
|
{
|
|
if (context->skip_cache || context->is_proxy_render || strip == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
Scene *scene = prefetch_get_original_scene_and_strip(context, strip);
|
|
timeline_frame = math::round(timeline_frame);
|
|
int frame_index = give_frame_index(scene, strip, timeline_frame);
|
|
if (strip->type == STRIP_TYPE_MOVIE) {
|
|
frame_index += strip->anim_startofs;
|
|
}
|
|
const int view_id = context->view_id;
|
|
|
|
ImBuf *res = nullptr;
|
|
{
|
|
std::lock_guard lock(source_image_cache_mutex);
|
|
SourceImageCache *cache = query_source_image_cache(scene);
|
|
if (cache == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
SourceImageCache::StripEntry *val = cache->map_.lookup_ptr(strip);
|
|
if (val == nullptr) {
|
|
/* Nothing in cache for this strip yet. */
|
|
return nullptr;
|
|
}
|
|
/* Search entries for the frame we want. */
|
|
res = val->frames.lookup_default({frame_index, view_id}, nullptr);
|
|
}
|
|
|
|
if (res) {
|
|
IMB_refImBuf(res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void source_image_cache_put(const RenderData *context,
|
|
const Strip *strip,
|
|
float timeline_frame,
|
|
ImBuf *image)
|
|
{
|
|
if (context->skip_cache || context->is_proxy_render || strip == nullptr || image == nullptr) {
|
|
return;
|
|
}
|
|
|
|
Scene *scene = prefetch_get_original_scene_and_strip(context, strip);
|
|
timeline_frame = math::round(timeline_frame);
|
|
|
|
int frame_index = give_frame_index(scene, strip, timeline_frame);
|
|
if (strip->type == STRIP_TYPE_MOVIE) {
|
|
frame_index += strip->anim_startofs;
|
|
}
|
|
const int view_id = context->view_id;
|
|
|
|
IMB_refImBuf(image);
|
|
|
|
std::lock_guard lock(source_image_cache_mutex);
|
|
SourceImageCache *cache = ensure_source_image_cache(scene);
|
|
|
|
SourceImageCache::StripEntry *val = cache->map_.lookup_ptr(strip);
|
|
|
|
if (val == nullptr) {
|
|
/* Nothing in cache for this strip yet. */
|
|
cache->map_.add_new(strip, {});
|
|
val = cache->map_.lookup_ptr(strip);
|
|
}
|
|
BLI_assert_msg(val != nullptr, "Source image cache value should never be null here");
|
|
|
|
ImBuf *&item = val->frames.lookup_or_add_default({frame_index, view_id});
|
|
if (item != nullptr) {
|
|
IMB_freeImBuf(item);
|
|
}
|
|
item = image;
|
|
}
|
|
|
|
void source_image_cache_invalidate_strip(Scene *scene, const Strip *strip)
|
|
{
|
|
std::lock_guard lock(source_image_cache_mutex);
|
|
SourceImageCache *cache = query_source_image_cache(scene);
|
|
if (cache != nullptr) {
|
|
cache->remove_entry(strip);
|
|
}
|
|
}
|
|
|
|
void source_image_cache_clear(Scene *scene)
|
|
{
|
|
std::lock_guard lock(source_image_cache_mutex);
|
|
SourceImageCache *cache = query_source_image_cache(scene);
|
|
if (cache != nullptr) {
|
|
scene->ed->runtime.source_image_cache->clear();
|
|
}
|
|
}
|
|
|
|
void source_image_cache_destroy(Scene *scene)
|
|
{
|
|
std::lock_guard lock(source_image_cache_mutex);
|
|
SourceImageCache *cache = query_source_image_cache(scene);
|
|
if (cache != nullptr) {
|
|
BLI_assert(cache == scene->ed->runtime.source_image_cache);
|
|
MEM_delete(scene->ed->runtime.source_image_cache);
|
|
scene->ed->runtime.source_image_cache = nullptr;
|
|
}
|
|
}
|
|
|
|
void source_image_cache_iterate(Scene *scene,
|
|
void *userdata,
|
|
void callback_iter(void *userdata,
|
|
const Strip *strip,
|
|
int timeline_frame))
|
|
{
|
|
std::lock_guard lock(source_image_cache_mutex);
|
|
SourceImageCache *cache = query_source_image_cache(scene);
|
|
if (cache == nullptr) {
|
|
return;
|
|
}
|
|
|
|
const float scene_fps = float(scene->r.frs_sec) / float(scene->r.frs_sec_base);
|
|
|
|
for (const auto &[key, value] : cache->map_.items()) {
|
|
for (std::pair<int, int> frame_view : value.frames.keys()) {
|
|
/* We have frame index of source media, try to guesstimate the timeline frame.
|
|
* Note that this will be not correct when retiming, strobing etc. are used.
|
|
* However, factor in playback rate difference. */
|
|
float frame_fl = frame_view.first / time_media_playback_rate_factor_get(key, scene_fps);
|
|
float timeline_frame = frame_fl + time_start_frame_get(key);
|
|
|
|
callback_iter(userdata, key, int(timeline_frame));
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t source_image_cache_calc_memory_size(const Scene *scene)
|
|
{
|
|
std::lock_guard lock(source_image_cache_mutex);
|
|
SourceImageCache *cache = query_source_image_cache(scene);
|
|
if (cache == nullptr) {
|
|
return 0;
|
|
}
|
|
size_t size = 0;
|
|
for (const SourceImageCache::StripEntry &entry : cache->map_.values()) {
|
|
for (const ImBuf *image : entry.frames.values()) {
|
|
size += IMB_get_size_in_memory(image);
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
|
|
size_t source_image_cache_get_image_count(const Scene *scene)
|
|
{
|
|
std::lock_guard lock(source_image_cache_mutex);
|
|
SourceImageCache *cache = query_source_image_cache(scene);
|
|
if (cache == nullptr) {
|
|
return 0;
|
|
}
|
|
size_t count = 0;
|
|
for (const SourceImageCache::StripEntry &entry : cache->map_.values()) {
|
|
count += entry.frames.size();
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool source_image_cache_evict(Scene *scene)
|
|
{
|
|
std::lock_guard lock(source_image_cache_mutex);
|
|
SourceImageCache *cache = query_source_image_cache(scene);
|
|
if (cache == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
/* Find which entry to remove -- we pick the one that is furthest from the current frame,
|
|
* biasing the ones that are behind the current frame. */
|
|
const int cur_frame = scene->r.cfra;
|
|
SourceImageCache::StripEntry *best_strip = nullptr;
|
|
std::pair<int, int> best_key = {};
|
|
int best_score = 0;
|
|
for (const auto &strip : cache->map_.items()) {
|
|
for (const auto &entry : strip.value.frames.items()) {
|
|
const int item_frame = entry.key.first;
|
|
/* Score for removal is distance to current frame; 2x that if behind current frame. */
|
|
int score = 0;
|
|
if (item_frame < cur_frame) {
|
|
score = (cur_frame - item_frame) * 2;
|
|
}
|
|
else if (item_frame > cur_frame) {
|
|
score = item_frame - cur_frame;
|
|
}
|
|
if (score > best_score) {
|
|
best_strip = &strip.value;
|
|
best_key = entry.key;
|
|
best_score = score;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Remove if we found one. */
|
|
if (best_strip != nullptr) {
|
|
IMB_freeImBuf(best_strip->frames.lookup(best_key));
|
|
best_strip->frames.remove(best_key);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace blender::seq
|