tornavis/source/blender/sequencer/intern/prefetch.c

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;
}