Files
test2/source/blender/sequencer/intern/prefetch.c
Sergey Sharybin c1bc70b711 Cleanup: Add a copyright notice to files and use SPDX format
A lot of files were missing copyright field in the header and
the Blender Foundation contributed to them in a sense of bug
fixing and general maintenance.

This change makes it explicit that those files are at least
partially copyrighted by the Blender Foundation.

Note that this does not make it so the Blender Foundation is
the only holder of the copyright in those files, and developers
who do not have a signed contract with the foundation still
hold the copyright as well.

Another aspect of this change is using SPDX format for the
header. We already used it for the license specification,
and now we state it for the copyright as well, following the
FAQ:

    https://reuse.software/faq/
2023-05-31 16:19:06 +02:00

608 lines
17 KiB
C

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "MEM_guardedalloc.h"
#include "DNA_anim_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_sequence_types.h"
#include "DNA_windowmanager_types.h"
#include "BLI_listbase.h"
#include "BLI_threads.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
#include "BKE_anim_data.h"
#include "BKE_animsys.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_scene.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
#include "DEG_depsgraph_debug.h"
#include "DEG_depsgraph_query.h"
#include "SEQ_channels.h"
#include "SEQ_iterator.h"
#include "SEQ_prefetch.h"
#include "SEQ_relations.h"
#include "SEQ_render.h"
#include "SEQ_sequencer.h"
#include "image_cache.h"
#include "prefetch.h"
#include "render.h"
typedef struct PrefetchJob {
struct PrefetchJob *next, *prev;
struct Main *bmain;
struct Main *bmain_eval;
struct Scene *scene;
struct Scene *scene_eval;
struct Depsgraph *depsgraph;
ThreadMutex prefetch_suspend_mutex;
ThreadCondition prefetch_suspend_cond;
ListBase threads;
/* context */
struct SeqRenderData context;
struct SeqRenderData context_cpy;
struct ListBase *seqbasep;
struct ListBase *seqbasep_cpy;
/* prefetch area */
float cfra;
int num_frames_prefetched;
/* control */
bool running;
bool waiting;
bool stop;
} PrefetchJob;
static bool seq_prefetch_is_playing(const Main *bmain)
{
for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
if (screen->animtimer) {
return true;
}
}
return false;
}
static bool seq_prefetch_is_scrubbing(const Main *bmain)
{
for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
if (screen->scrubbing) {
return true;
}
}
return false;
}
static PrefetchJob *seq_prefetch_job_get(Scene *scene)
{
if (scene && scene->ed) {
return scene->ed->prefetch_job;
}
return NULL;
}
bool seq_prefetch_job_is_running(Scene *scene)
{
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
if (!pfjob) {
return false;
}
return pfjob->running;
}
static bool seq_prefetch_job_is_waiting(Scene *scene)
{
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
if (!pfjob) {
return false;
}
return pfjob->waiting;
}
static Sequence *sequencer_prefetch_get_original_sequence(Sequence *seq, ListBase *seqbase)
{
LISTBASE_FOREACH (Sequence *, seq_orig, seqbase) {
if (STREQ(seq->name, seq_orig->name)) {
return seq_orig;
}
if (seq_orig->type == SEQ_TYPE_META) {
Sequence *match = sequencer_prefetch_get_original_sequence(seq, &seq_orig->seqbase);
if (match != NULL) {
return match;
}
}
}
return NULL;
}
Sequence *seq_prefetch_get_original_sequence(Sequence *seq, Scene *scene)
{
Editing *ed = scene->ed;
return sequencer_prefetch_get_original_sequence(seq, &ed->seqbase);
}
SeqRenderData *seq_prefetch_get_original_context(const SeqRenderData *context)
{
PrefetchJob *pfjob = seq_prefetch_job_get(context->scene);
return &pfjob->context;
}
static bool seq_prefetch_is_cache_full(Scene *scene)
{
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
if (!seq_cache_is_full()) {
return false;
}
return seq_cache_recycle_item(pfjob->scene) == false;
}
static float seq_prefetch_cfra(PrefetchJob *pfjob)
{
return pfjob->cfra + pfjob->num_frames_prefetched;
}
static AnimationEvalContext seq_prefetch_anim_eval_context(PrefetchJob *pfjob)
{
return BKE_animsys_eval_context_construct(pfjob->depsgraph, seq_prefetch_cfra(pfjob));
}
void seq_prefetch_get_time_range(Scene *scene, int *start, int *end)
{
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
*start = pfjob->cfra;
*end = seq_prefetch_cfra(pfjob);
}
static void seq_prefetch_free_depsgraph(PrefetchJob *pfjob)
{
if (pfjob->depsgraph != NULL) {
DEG_graph_free(pfjob->depsgraph);
}
pfjob->depsgraph = NULL;
pfjob->scene_eval = NULL;
}
static void seq_prefetch_update_depsgraph(PrefetchJob *pfjob)
{
DEG_evaluate_on_framechange(pfjob->depsgraph, seq_prefetch_cfra(pfjob));
}
static void seq_prefetch_init_depsgraph(PrefetchJob *pfjob)
{
Main *bmain = pfjob->bmain_eval;
Scene *scene = pfjob->scene;
ViewLayer *view_layer = BKE_view_layer_default_render(scene);
pfjob->depsgraph = DEG_graph_new(bmain, scene, view_layer, DAG_EVAL_RENDER);
DEG_debug_name_set(pfjob->depsgraph, "SEQUENCER PREFETCH");
/* Make sure there is a correct evaluated scene pointer. */
DEG_graph_build_for_render_pipeline(pfjob->depsgraph);
/* Update immediately so we have proper evaluated scene. */
seq_prefetch_update_depsgraph(pfjob);
pfjob->scene_eval = DEG_get_evaluated_scene(pfjob->depsgraph);
pfjob->scene_eval->ed->cache_flag = 0;
}
static void seq_prefetch_update_area(PrefetchJob *pfjob)
{
int cfra = pfjob->scene->r.cfra;
/* rebase */
if (cfra > pfjob->cfra) {
int delta = cfra - pfjob->cfra;
pfjob->cfra = cfra;
pfjob->num_frames_prefetched -= delta;
if (pfjob->num_frames_prefetched <= 1) {
pfjob->num_frames_prefetched = 1;
}
}
/* reset */
if (cfra < pfjob->cfra) {
pfjob->cfra = cfra;
pfjob->num_frames_prefetched = 1;
}
}
void SEQ_prefetch_stop_all(void)
{
/* TODO(Richard): Use wm_jobs for prefetch, or pass main. */
for (Scene *scene = G.main->scenes.first; scene; scene = scene->id.next) {
SEQ_prefetch_stop(scene);
}
}
void SEQ_prefetch_stop(Scene *scene)
{
PrefetchJob *pfjob;
pfjob = seq_prefetch_job_get(scene);
if (!pfjob) {
return;
}
pfjob->stop = true;
while (pfjob->running) {
BLI_condition_notify_one(&pfjob->prefetch_suspend_cond);
}
}
static void seq_prefetch_update_context(const SeqRenderData *context)
{
PrefetchJob *pfjob;
pfjob = seq_prefetch_job_get(context->scene);
SEQ_render_new_render_data(pfjob->bmain_eval,
pfjob->depsgraph,
pfjob->scene_eval,
context->rectx,
context->recty,
context->preview_render_size,
false,
&pfjob->context_cpy);
pfjob->context_cpy.is_prefetch_render = true;
pfjob->context_cpy.task_id = SEQ_TASK_PREFETCH_RENDER;
SEQ_render_new_render_data(pfjob->bmain,
pfjob->depsgraph,
pfjob->scene,
context->rectx,
context->recty,
context->preview_render_size,
false,
&pfjob->context);
pfjob->context.is_prefetch_render = false;
/* Same ID as prefetch context, because context will be swapped, but we still
* want to assign this ID to cache entries created in this thread.
* This is to allow "temp cache" work correctly for both threads.
*/
pfjob->context.task_id = SEQ_TASK_PREFETCH_RENDER;
}
static void seq_prefetch_update_scene(Scene *scene)
{
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
if (!pfjob) {
return;
}
pfjob->scene = scene;
seq_prefetch_free_depsgraph(pfjob);
seq_prefetch_init_depsgraph(pfjob);
}
static void seq_prefetch_update_active_seqbase(PrefetchJob *pfjob)
{
MetaStack *ms_orig = SEQ_meta_stack_active_get(SEQ_editing_get(pfjob->scene));
Editing *ed_eval = SEQ_editing_get(pfjob->scene_eval);
if (ms_orig != NULL) {
Sequence *meta_eval = seq_prefetch_get_original_sequence(ms_orig->parseq, pfjob->scene_eval);
SEQ_seqbase_active_set(ed_eval, &meta_eval->seqbase);
}
else {
SEQ_seqbase_active_set(ed_eval, &ed_eval->seqbase);
}
}
static void seq_prefetch_resume(Scene *scene)
{
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
if (pfjob && pfjob->waiting) {
BLI_condition_notify_one(&pfjob->prefetch_suspend_cond);
}
}
void seq_prefetch_free(Scene *scene)
{
PrefetchJob *pfjob = seq_prefetch_job_get(scene);
if (!pfjob) {
return;
}
SEQ_prefetch_stop(scene);
BLI_threadpool_remove(&pfjob->threads, pfjob);
BLI_threadpool_end(&pfjob->threads);
BLI_mutex_end(&pfjob->prefetch_suspend_mutex);
BLI_condition_end(&pfjob->prefetch_suspend_cond);
seq_prefetch_free_depsgraph(pfjob);
BKE_main_free(pfjob->bmain_eval);
MEM_freeN(pfjob);
scene->ed->prefetch_job = NULL;
}
static bool seq_prefetch_seq_has_disk_cache(PrefetchJob *pfjob,
Sequence *seq,
bool can_have_final_image)
{
SeqRenderData *ctx = &pfjob->context_cpy;
float cfra = seq_prefetch_cfra(pfjob);
ImBuf *ibuf = seq_cache_get(ctx, seq, cfra, SEQ_CACHE_STORE_PREPROCESSED);
if (ibuf != NULL) {
IMB_freeImBuf(ibuf);
return true;
}
ibuf = seq_cache_get(ctx, seq, cfra, SEQ_CACHE_STORE_RAW);
if (ibuf != NULL) {
IMB_freeImBuf(ibuf);
return true;
}
if (!can_have_final_image) {
return false;
}
ibuf = seq_cache_get(ctx, seq, cfra, SEQ_CACHE_STORE_FINAL_OUT);
if (ibuf != NULL) {
IMB_freeImBuf(ibuf);
return true;
}
return false;
}
static bool seq_prefetch_scene_strip_is_rendered(PrefetchJob *pfjob,
ListBase *channels,
ListBase *seqbase,
SeqCollection *scene_strips,
bool is_recursive_check)
{
float cfra = seq_prefetch_cfra(pfjob);
Sequence *seq_arr[MAXSEQ + 1];
int count = seq_get_shown_sequences(pfjob->scene_eval, channels, seqbase, cfra, 0, seq_arr);
/* Iterate over rendered strips. */
for (int i = 0; i < count; i++) {
Sequence *seq = seq_arr[i];
if (seq->type == SEQ_TYPE_META &&
seq_prefetch_scene_strip_is_rendered(pfjob, channels, &seq->seqbase, scene_strips, true))
{
return true;
}
/* Disable prefetching 3D scene strips, but check for disk cache. */
if (seq->type == SEQ_TYPE_SCENE && (seq->flag & SEQ_SCENE_STRIPS) == 0 &&
!seq_prefetch_seq_has_disk_cache(pfjob, seq, !is_recursive_check))
{
return true;
}
/* Check if strip is effect of scene strip or uses it as modifier. This is recursive check. */
Sequence *seq_scene;
SEQ_ITERATOR_FOREACH (seq_scene, scene_strips) {
if (SEQ_relations_render_loop_check(seq, seq_scene)) {
return true;
}
}
}
return false;
}
static SeqCollection *query_scene_strips(ListBase *seqbase)
{
SeqCollection *collection = SEQ_query_all_strips_recursive(seqbase);
LISTBASE_FOREACH (Sequence *, seq, seqbase) {
if (seq->type != SEQ_TYPE_SCENE || (seq->flag & SEQ_SCENE_STRIPS) != 0) {
SEQ_collection_remove_strip(seq, collection);
}
}
return collection;
}
/* Prefetch must avoid rendering scene strips, because rendering in background locks UI and can
* make it unresponsive for long time periods. */
static bool seq_prefetch_must_skip_frame(PrefetchJob *pfjob, ListBase *channels, ListBase *seqbase)
{
SeqCollection *scene_strips = query_scene_strips(seqbase);
if (seq_prefetch_scene_strip_is_rendered(pfjob, channels, seqbase, scene_strips, false)) {
SEQ_collection_free(scene_strips);
return true;
}
SEQ_collection_free(scene_strips);
return false;
}
static bool seq_prefetch_need_suspend(PrefetchJob *pfjob)
{
return seq_prefetch_is_cache_full(pfjob->scene) || seq_prefetch_is_scrubbing(pfjob->bmain) ||
(seq_prefetch_cfra(pfjob) >= pfjob->scene->r.efra);
}
static void seq_prefetch_do_suspend(PrefetchJob *pfjob)
{
BLI_mutex_lock(&pfjob->prefetch_suspend_mutex);
while (seq_prefetch_need_suspend(pfjob) &&
(pfjob->scene->ed->cache_flag & SEQ_CACHE_PREFETCH_ENABLE) && !pfjob->stop)
{
pfjob->waiting = true;
BLI_condition_wait(&pfjob->prefetch_suspend_cond, &pfjob->prefetch_suspend_mutex);
seq_prefetch_update_area(pfjob);
}
pfjob->waiting = false;
BLI_mutex_unlock(&pfjob->prefetch_suspend_mutex);
}
static void *seq_prefetch_frames(void *job)
{
PrefetchJob *pfjob = (PrefetchJob *)job;
while (seq_prefetch_cfra(pfjob) <= pfjob->scene->r.efra) {
pfjob->scene_eval->ed->prefetch_job = NULL;
seq_prefetch_update_depsgraph(pfjob);
AnimData *adt = BKE_animdata_from_id(&pfjob->context_cpy.scene->id);
AnimationEvalContext anim_eval_context = seq_prefetch_anim_eval_context(pfjob);
BKE_animsys_evaluate_animdata(
&pfjob->context_cpy.scene->id, adt, &anim_eval_context, ADT_RECALC_ALL, false);
/* This is quite hacky solution:
* We need cross-reference original scene with copy for cache.
* However depsgraph must not have this data, because it will try to kill this job.
* Scene copy don't reference original scene. Perhaps, this could be done by depsgraph.
* Set to NULL before return!
*/
pfjob->scene_eval->ed->prefetch_job = pfjob;
ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(pfjob->scene_eval));
ListBase *channels = SEQ_channels_displayed_get(SEQ_editing_get(pfjob->scene_eval));
if (seq_prefetch_must_skip_frame(pfjob, channels, seqbase)) {
pfjob->num_frames_prefetched++;
continue;
}
ImBuf *ibuf = SEQ_render_give_ibuf(&pfjob->context_cpy, seq_prefetch_cfra(pfjob), 0);
seq_cache_free_temp_cache(pfjob->scene, pfjob->context.task_id, seq_prefetch_cfra(pfjob));
IMB_freeImBuf(ibuf);
/* Suspend thread if there is nothing to be prefetched. */
seq_prefetch_do_suspend(pfjob);
/* Avoid "collision" with main thread, but make sure to fetch at least few frames */
if (pfjob->num_frames_prefetched > 5 && (seq_prefetch_cfra(pfjob) - pfjob->scene->r.cfra) < 2)
{
break;
}
if (!(pfjob->scene->ed->cache_flag & SEQ_CACHE_PREFETCH_ENABLE) || pfjob->stop) {
break;
}
seq_prefetch_update_area(pfjob);
pfjob->num_frames_prefetched++;
}
seq_cache_free_temp_cache(pfjob->scene, pfjob->context.task_id, seq_prefetch_cfra(pfjob));
pfjob->running = false;
pfjob->scene_eval->ed->prefetch_job = NULL;
return NULL;
}
static PrefetchJob *seq_prefetch_start_ex(const SeqRenderData *context, float cfra)
{
PrefetchJob *pfjob = seq_prefetch_job_get(context->scene);
if (!pfjob) {
if (context->scene->ed) {
pfjob = (PrefetchJob *)MEM_callocN(sizeof(PrefetchJob), "PrefetchJob");
context->scene->ed->prefetch_job = pfjob;
BLI_threadpool_init(&pfjob->threads, seq_prefetch_frames, 1);
BLI_mutex_init(&pfjob->prefetch_suspend_mutex);
BLI_condition_init(&pfjob->prefetch_suspend_cond);
pfjob->bmain_eval = BKE_main_new();
pfjob->scene = context->scene;
seq_prefetch_init_depsgraph(pfjob);
}
}
pfjob->bmain = context->bmain;
pfjob->cfra = cfra;
pfjob->num_frames_prefetched = 1;
pfjob->waiting = false;
pfjob->stop = false;
pfjob->running = true;
seq_prefetch_update_scene(context->scene);
seq_prefetch_update_context(context);
seq_prefetch_update_active_seqbase(pfjob);
BLI_threadpool_remove(&pfjob->threads, pfjob);
BLI_threadpool_insert(&pfjob->threads, pfjob);
return pfjob;
}
void seq_prefetch_start(const SeqRenderData *context, float timeline_frame)
{
Scene *scene = context->scene;
Editing *ed = scene->ed;
bool has_strips = (bool)ed->seqbasep->first;
if (!context->is_prefetch_render && !context->is_proxy_render) {
bool playing = seq_prefetch_is_playing(context->bmain);
bool scrubbing = seq_prefetch_is_scrubbing(context->bmain);
bool running = seq_prefetch_job_is_running(scene);
seq_prefetch_resume(scene);
/* conditions to start:
* prefetch enabled, prefetch not running, not scrubbing, not playing,
* cache storage enabled, has strips to render, not rendering, not doing modal transform -
* important, see D7820.
*/
if ((ed->cache_flag & SEQ_CACHE_PREFETCH_ENABLE) && !running && !scrubbing && !playing &&
ed->cache_flag & SEQ_CACHE_ALL_TYPES && has_strips && !G.is_rendering && !G.moving)
{
seq_prefetch_start_ex(context, timeline_frame);
}
}
}
bool SEQ_prefetch_need_redraw(Main *bmain, Scene *scene)
{
bool playing = seq_prefetch_is_playing(bmain);
bool scrubbing = seq_prefetch_is_scrubbing(bmain);
bool running = seq_prefetch_job_is_running(scene);
bool suspended = seq_prefetch_job_is_waiting(scene);
/* force redraw, when prefetching and using cache view. */
if (running && !playing && !suspended && scene->ed->cache_flag & SEQ_CACHE_VIEW_ENABLE) {
return true;
}
/* Sometimes scrubbing flag is set when not scrubbing. In that case I want to catch "event" of
* stopping scrubbing */
if (scrubbing) {
return true;
}
return false;
}