Files
test2/source/blender/sequencer/intern/strip_relations.cc
Richard Antalik 8a0622ac68 Fix #148121: Thumbnails are refreshed when adding keyframes to strip
When strip property was animated, all caches were wiped. However, it is
not necessary to wipe raw cache or thumbnail cache.

Pull Request: https://projects.blender.org/blender/blender/pulls/148162
2025-10-15 21:46:30 +02:00

440 lines
13 KiB
C++

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "BLI_ghash.h"
#include "BLI_listbase.h"
#include "BLI_math_base.h"
#include "BLI_session_uid.h"
#include "BKE_main.hh"
#include "BKE_report.hh"
#include "DEG_depsgraph.hh"
#include "MOV_read.hh"
#include "SEQ_iterator.hh"
#include "SEQ_prefetch.hh"
#include "SEQ_preview_cache.hh"
#include "SEQ_relations.hh"
#include "SEQ_sequencer.hh"
#include "SEQ_thumbnail_cache.hh"
#include "SEQ_time.hh"
#include "SEQ_utils.hh"
#include "cache/final_image_cache.hh"
#include "cache/intra_frame_cache.hh"
#include "cache/source_image_cache.hh"
#include "effects/effects.hh"
#include "sequencer.hh"
#include "utils.hh"
namespace blender::seq {
bool relation_is_effect_of_strip(const Strip *effect, const Strip *input)
{
return ELEM(input, effect->input1, effect->input2);
}
void cache_cleanup(Scene *scene)
{
thumbnail_cache_clear(scene);
source_image_cache_clear(scene);
final_image_cache_clear(scene);
intra_frame_cache_invalidate(scene);
preview_cache_invalidate(scene);
}
void cache_cleanup_intra(Scene *scene)
{
intra_frame_cache_invalidate(scene);
}
void cache_cleanup_final(Scene *scene)
{
final_image_cache_clear(scene);
}
void cache_settings_changed(Scene *scene)
{
if (!(scene->ed->cache_flag & SEQ_CACHE_STORE_RAW)) {
/* RAW caches has been disabled, clear them out. */
source_image_cache_clear(scene);
}
if (!(scene->ed->cache_flag & SEQ_CACHE_STORE_FINAL_OUT)) {
/* Final caches has been disabled, clear them out. */
final_image_cache_clear(scene);
}
}
bool is_cache_full(const Scene *scene)
{
size_t cache_limit = size_t(U.memcachelimit) * 1024 * 1024;
return source_image_cache_calc_memory_size(scene) + final_image_cache_calc_memory_size(scene) >
cache_limit;
}
bool evict_caches_if_full(Scene *scene)
{
if (!is_cache_full(scene)) {
/* Cache is not full, we don't have to evict anything. */
return false;
}
/* Cache is full, so we want to remove some images. We always try to remove one final image,
* and some amount of source images for each final image, so that ratio of cached images
* stays the same. Depending on the frame composition complexity, there can be lots of
* source images cached for a single final frame; if we only removed one source image
* we'd eventually have the cache still filled only with source images. */
bool evicted_final = false;
bool evicted_source = false;
do {
const size_t count_final = final_image_cache_get_image_count(scene);
const size_t count_source = source_image_cache_get_image_count(scene);
evicted_final = false;
evicted_source = false;
const bool final_active = scene->ed->cache_flag & SEQ_CACHE_STORE_FINAL_OUT;
/* Evict one final item, and as much from source as needed to maintain ratio. */
if (count_final != 0) {
evicted_final = final_image_cache_evict(scene);
}
/* Only remove source images if there's more of them than final ones. */
if (count_source != 0 && (!final_active || count_source > count_final)) {
evicted_source = source_image_cache_evict(scene);
/* Only try to enforce the ratio when the final cache is active. */
if (evicted_source && final_active) {
const size_t items = divide_ceil_ul(count_source, std::max<size_t>(count_final, 1));
/* Start at "1" to make sure we only try to evict more frames if the ratio is above 1:1. */
for (size_t i = 1; i < items; i++) {
if (!source_image_cache_evict(scene)) {
/* Can't evict any more frames, stop. */
break;
}
}
}
}
} while (is_cache_full(scene) && (evicted_final || evicted_source));
/* Did we evict anything to free up the cache? */
return !(evicted_final || evicted_source);
}
static void invalidate_final_cache_strip_range(Scene *scene, const Strip *strip)
{
const int strip_left = time_left_handle_frame_get(scene, strip);
const int strip_right = time_right_handle_frame_get(scene, strip);
final_image_cache_invalidate_frame_range(scene, strip_left, strip_right);
}
static void invalidate_raw_cache_of_parent_meta(Scene *scene, Strip *strip)
{
Strip *meta = lookup_meta_by_strip(editing_get(scene), strip);
if (meta == nullptr) {
return;
}
relations_invalidate_cache_raw(scene, meta);
}
void relations_invalidate_cache_raw(Scene *scene, Strip *strip)
{
source_image_cache_invalidate_strip(scene, strip);
relations_invalidate_cache(scene, strip);
}
void relations_invalidate_cache(Scene *scene, Strip *strip)
{
if (strip->effectdata && strip->type == STRIP_TYPE_SPEED) {
strip_effect_speed_rebuild_map(scene, strip);
}
media_presence_invalidate_strip(scene, strip);
invalidate_final_cache_strip_range(scene, strip);
intra_frame_cache_invalidate(scene, strip);
preview_cache_invalidate(scene);
invalidate_raw_cache_of_parent_meta(scene, strip);
/* Needed to update VSE sound. */
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
prefetch_stop(scene);
}
void relations_invalidate_scene_strips(const Main *bmain, const Scene *scene_target)
{
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
if (scene->ed != nullptr) {
for (Strip *strip : lookup_strips_by_scene(editing_get(scene), scene_target)) {
relations_invalidate_cache_raw(scene, strip);
}
}
}
}
void relations_invalidate_compositor_modifiers(const Main *bmain, const bNodeTree *node_tree)
{
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
if (scene->ed != nullptr) {
for (Strip *strip : lookup_strips_by_compositor_node_group(editing_get(scene), node_tree)) {
relations_invalidate_cache(scene, strip);
}
}
}
}
static void invalidate_movieclip_strips(Scene *scene, MovieClip *clip_target, ListBase *seqbase)
{
for (Strip *strip = static_cast<Strip *>(seqbase->first); strip != nullptr; strip = strip->next)
{
if (strip->clip == clip_target) {
relations_invalidate_cache_raw(scene, strip);
}
if (strip->seqbase.first != nullptr) {
invalidate_movieclip_strips(scene, clip_target, &strip->seqbase);
}
}
}
void relations_invalidate_movieclip_strips(Main *bmain, MovieClip *clip_target)
{
for (Scene *scene = static_cast<Scene *>(bmain->scenes.first); scene != nullptr;
scene = static_cast<Scene *>(scene->id.next))
{
if (scene->ed != nullptr) {
invalidate_movieclip_strips(scene, clip_target, &scene->ed->seqbase);
}
}
}
void relations_free_imbuf(Scene *scene, ListBase *seqbase, bool for_render)
{
if (scene->ed == nullptr) {
return;
}
cache_cleanup(scene);
prefetch_stop(scene);
LISTBASE_FOREACH (Strip *, strip, seqbase) {
if (for_render && time_strip_intersects_frame(scene, strip, scene->r.cfra)) {
continue;
}
if (strip->data) {
if (strip->type == STRIP_TYPE_MOVIE) {
relations_strip_free_anim(strip);
}
if (strip->type == STRIP_TYPE_SPEED) {
strip_effect_speed_rebuild_map(scene, strip);
}
}
if (strip->type == STRIP_TYPE_META) {
relations_free_imbuf(scene, &strip->seqbase, for_render);
}
if (strip->type == STRIP_TYPE_SCENE) {
/* FIXME: recurse downwards,
* but do recurse protection somehow! */
}
}
}
static void sequencer_all_free_anim_ibufs(const Scene *scene,
ListBase *seqbase,
int timeline_frame,
const int frame_range[2])
{
Editing *ed = editing_get(scene);
for (Strip *strip = static_cast<Strip *>(seqbase->first); strip != nullptr; strip = strip->next)
{
if (!time_strip_intersects_frame(scene, strip, timeline_frame) ||
!((frame_range[0] <= timeline_frame) && (frame_range[1] > timeline_frame)))
{
relations_strip_free_anim(strip);
}
if (strip->type == STRIP_TYPE_META) {
int meta_range[2];
MetaStack *ms = meta_stack_active_get(ed);
if (ms != nullptr && ms->parent_strip == strip) {
meta_range[0] = -MAXFRAME;
meta_range[1] = MAXFRAME;
}
else {
/* Limit frame range to meta strip. */
meta_range[0] = max_ii(frame_range[0], time_left_handle_frame_get(scene, strip));
meta_range[1] = min_ii(frame_range[1], time_right_handle_frame_get(scene, strip));
}
sequencer_all_free_anim_ibufs(scene, &strip->seqbase, timeline_frame, meta_range);
}
}
}
void relations_free_all_anim_ibufs(Scene *scene, int timeline_frame)
{
Editing *ed = editing_get(scene);
if (ed == nullptr) {
return;
}
const int frame_range[2] = {-MAXFRAME, MAXFRAME};
sequencer_all_free_anim_ibufs(scene, &ed->seqbase, timeline_frame, frame_range);
}
static Strip *sequencer_check_scene_recursion(Scene *scene, ListBase *seqbase)
{
LISTBASE_FOREACH (Strip *, strip, seqbase) {
if (strip->type == STRIP_TYPE_SCENE && strip->scene == scene) {
return strip;
}
if (strip->type == STRIP_TYPE_SCENE && (strip->flag & SEQ_SCENE_STRIPS)) {
if (strip->scene && strip->scene->ed &&
sequencer_check_scene_recursion(scene, &strip->scene->ed->seqbase))
{
return strip;
}
}
if (strip->type == STRIP_TYPE_META && sequencer_check_scene_recursion(scene, &strip->seqbase))
{
return strip;
}
}
return nullptr;
}
bool relations_check_scene_recursion(Scene *scene, ReportList *reports)
{
Editing *ed = editing_get(scene);
if (ed == nullptr) {
return false;
}
Strip *recursive_seq = sequencer_check_scene_recursion(scene, &ed->seqbase);
if (recursive_seq != nullptr) {
BKE_reportf(reports,
RPT_WARNING,
"Recursion detected in video sequencer. Strip %s at frame %d will not be rendered",
recursive_seq->name + 2,
time_left_handle_frame_get(scene, recursive_seq));
LISTBASE_FOREACH (Strip *, strip, &ed->seqbase) {
if (strip->type != STRIP_TYPE_SCENE && sequencer_strip_generates_image(strip)) {
/* There are other strips to render, so render them. */
return false;
}
}
/* No other strips to render - cancel operator. */
return true;
}
return false;
}
bool relations_render_loop_check(Strip *strip_main, Strip *strip)
{
if (strip_main == nullptr || strip == nullptr) {
return false;
}
if (strip_main == strip) {
return true;
}
if ((strip_main->input1 && relations_render_loop_check(strip_main->input1, strip)) ||
(strip_main->input2 && relations_render_loop_check(strip_main->input2, strip)))
{
return true;
}
LISTBASE_FOREACH (StripModifierData *, smd, &strip_main->modifiers) {
if (smd->mask_strip && relations_render_loop_check(smd->mask_strip, strip)) {
return true;
}
}
return false;
}
void relations_strip_free_anim(Strip *strip)
{
while (strip->anims.last) {
StripAnim *sanim = static_cast<StripAnim *>(strip->anims.last);
if (sanim->anim) {
MOV_close(sanim->anim);
sanim->anim = nullptr;
}
BLI_freelinkN(&strip->anims, sanim);
}
BLI_listbase_clear(&strip->anims);
}
void relations_session_uid_generate(Strip *strip)
{
strip->runtime.session_uid = BLI_session_uid_generate();
}
static bool get_uids_cb(Strip *strip, void *user_data)
{
GSet *used_uids = (GSet *)user_data;
const SessionUID *session_uid = &strip->runtime.session_uid;
if (!BLI_session_uid_is_generated(session_uid)) {
printf("Sequence %s does not have UID generated.\n", strip->name);
return true;
}
if (BLI_gset_lookup(used_uids, session_uid) != nullptr) {
printf("Sequence %s has duplicate UID generated.\n", strip->name);
return true;
}
BLI_gset_insert(used_uids, (void *)session_uid);
return true;
}
void relations_check_uids_unique_and_report(const Scene *scene)
{
if (scene->ed == nullptr) {
return;
}
GSet *used_uids = BLI_gset_new(
BLI_session_uid_ghash_hash, BLI_session_uid_ghash_compare, "sequencer used uids");
foreach_strip(&scene->ed->seqbase, get_uids_cb, used_uids);
BLI_gset_free(used_uids, nullptr);
}
bool exists_in_seqbase(const Strip *strip, const ListBase *seqbase)
{
LISTBASE_FOREACH (Strip *, strip_test, seqbase) {
if (strip_test->type == STRIP_TYPE_META && exists_in_seqbase(strip, &strip_test->seqbase)) {
return true;
}
if (strip_test == strip) {
return true;
}
}
return false;
}
} // namespace blender::seq