Files
test2/source/blender/sequencer/intern/cache/source_image_cache.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

288 lines
7.9 KiB
C++
Raw Normal View History

/* 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