When effect of adjustment layer strip is moved below the adjustment layer, this causes infinite loop in strip rendering. Same happens when you use multicam strip and set source channel to one of its effects. This is fixed by passing `SeqRenderState` to the effects. If any strip renders "seqbase" pointer of strip is stored in set in the render state struct. If the pointer exists in this set, function returns without rendering anything. In other words, The strip must never render itself. Pull Request: https://projects.blender.org/blender/blender/pulls/146624
2064 lines
67 KiB
C++
2064 lines
67 KiB
C++
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
|
|
* SPDX-FileCopyrightText: 2003-2024 Blender Authors
|
|
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup sequencer
|
|
*/
|
|
|
|
#include <ctime>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_defaults.h"
|
|
#include "DNA_mask_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_sequence_types.h"
|
|
#include "DNA_space_types.h"
|
|
|
|
#include "BLI_linklist.h"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math_geom.h"
|
|
#include "BLI_math_matrix.hh"
|
|
#include "BLI_path_utils.hh"
|
|
#include "BLI_rect.h"
|
|
#include "BLI_task.hh"
|
|
|
|
#include "BKE_anim_data.hh"
|
|
#include "BKE_animsys.h"
|
|
#include "BKE_fcurve.hh"
|
|
#include "BKE_global.hh"
|
|
#include "BKE_image.hh"
|
|
#include "BKE_layer.hh"
|
|
#include "BKE_lib_id.hh"
|
|
#include "BKE_library.hh"
|
|
#include "BKE_main.hh"
|
|
#include "BKE_mask.h"
|
|
#include "BKE_movieclip.h"
|
|
#include "BKE_scene.hh"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
#include "DEG_depsgraph_query.hh"
|
|
|
|
#include "IMB_colormanagement.hh"
|
|
#include "IMB_imbuf.hh"
|
|
#include "IMB_imbuf_types.hh"
|
|
#include "IMB_metadata.hh"
|
|
|
|
#include "MOV_read.hh"
|
|
|
|
#include "RNA_prototypes.hh"
|
|
|
|
#include "RE_engine.h"
|
|
#include "RE_pipeline.h"
|
|
|
|
#include "SEQ_channels.hh"
|
|
#include "SEQ_effects.hh"
|
|
#include "SEQ_iterator.hh"
|
|
#include "SEQ_modifier.hh"
|
|
#include "SEQ_offscreen.hh"
|
|
#include "SEQ_proxy.hh"
|
|
#include "SEQ_relations.hh"
|
|
#include "SEQ_render.hh"
|
|
#include "SEQ_sequencer.hh"
|
|
#include "SEQ_time.hh"
|
|
#include "SEQ_transform.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 "multiview.hh"
|
|
#include "prefetch.hh"
|
|
#include "proxy.hh"
|
|
#include "render.hh"
|
|
#include "utils.hh"
|
|
|
|
#include <algorithm>
|
|
|
|
namespace blender::seq {
|
|
|
|
static ImBuf *seq_render_strip_stack(const RenderData *context,
|
|
SeqRenderState *state,
|
|
ListBase *channels,
|
|
ListBase *seqbasep,
|
|
float timeline_frame,
|
|
int chanshown);
|
|
|
|
static blender::Mutex seq_render_mutex;
|
|
DrawViewFn view3d_fn = nullptr; /* nullptr in background mode */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Color-space utility functions
|
|
* \{ */
|
|
|
|
void seq_imbuf_assign_spaces(const Scene *scene, ImBuf *ibuf)
|
|
{
|
|
#if 0
|
|
/* Byte buffer is supposed to be in sequencer working space already. */
|
|
if (ibuf->rect != nullptr) {
|
|
IMB_colormanagement_assign_byte_colorspace(ibuf, scene->sequencer_colorspace_settings.name);
|
|
}
|
|
#endif
|
|
if (ibuf->float_buffer.data != nullptr) {
|
|
IMB_colormanagement_assign_float_colorspace(ibuf, scene->sequencer_colorspace_settings.name);
|
|
}
|
|
}
|
|
|
|
void seq_imbuf_to_sequencer_space(const Scene *scene, ImBuf *ibuf, bool make_float)
|
|
{
|
|
/* Early output check: if both buffers are nullptr we have nothing to convert. */
|
|
if (ibuf->float_buffer.data == nullptr && ibuf->byte_buffer.data == nullptr) {
|
|
return;
|
|
}
|
|
/* Get common conversion settings. */
|
|
const char *to_colorspace = scene->sequencer_colorspace_settings.name;
|
|
/* Perform actual conversion logic. */
|
|
if (ibuf->float_buffer.data == nullptr) {
|
|
/* We are not requested to give float buffer and byte buffer is already
|
|
* in thee required colorspace. Can skip doing anything here.
|
|
*/
|
|
const char *from_colorspace = IMB_colormanagement_get_rect_colorspace(ibuf);
|
|
if (!make_float && STREQ(from_colorspace, to_colorspace)) {
|
|
return;
|
|
}
|
|
|
|
IMB_alloc_float_pixels(ibuf, 4, false);
|
|
IMB_colormanagement_transform_byte_to_float(ibuf->float_buffer.data,
|
|
ibuf->byte_buffer.data,
|
|
ibuf->x,
|
|
ibuf->y,
|
|
ibuf->channels,
|
|
from_colorspace,
|
|
to_colorspace);
|
|
/* We don't need byte buffer anymore. */
|
|
IMB_free_byte_pixels(ibuf);
|
|
}
|
|
else {
|
|
const char *from_colorspace = IMB_colormanagement_get_float_colorspace(ibuf);
|
|
/* Unknown input color space, can't perform conversion. */
|
|
if (from_colorspace == nullptr || from_colorspace[0] == '\0') {
|
|
return;
|
|
}
|
|
/* We don't want both byte and float buffers around: they'll either run
|
|
* out of sync or conversion of byte buffer will lose precision in there.
|
|
*/
|
|
if (ibuf->byte_buffer.data != nullptr) {
|
|
IMB_free_byte_pixels(ibuf);
|
|
}
|
|
IMB_colormanagement_transform_float(ibuf->float_buffer.data,
|
|
ibuf->x,
|
|
ibuf->y,
|
|
ibuf->channels,
|
|
from_colorspace,
|
|
to_colorspace,
|
|
true);
|
|
}
|
|
seq_imbuf_assign_spaces(scene, ibuf);
|
|
}
|
|
|
|
void render_imbuf_from_sequencer_space(const Scene *scene, ImBuf *ibuf)
|
|
{
|
|
const char *from_colorspace = scene->sequencer_colorspace_settings.name;
|
|
const char *to_colorspace = IMB_colormanagement_role_colorspace_name_get(
|
|
COLOR_ROLE_SCENE_LINEAR);
|
|
|
|
if (!ibuf->float_buffer.data) {
|
|
return;
|
|
}
|
|
|
|
if (to_colorspace && to_colorspace[0] != '\0') {
|
|
IMB_colormanagement_transform_float(ibuf->float_buffer.data,
|
|
ibuf->x,
|
|
ibuf->y,
|
|
ibuf->channels,
|
|
from_colorspace,
|
|
to_colorspace,
|
|
true);
|
|
IMB_colormanagement_assign_float_colorspace(ibuf, to_colorspace);
|
|
}
|
|
}
|
|
|
|
void render_pixel_from_sequencer_space_v4(const Scene *scene, float pixel[4])
|
|
{
|
|
const char *from_colorspace = scene->sequencer_colorspace_settings.name;
|
|
const char *to_colorspace = IMB_colormanagement_role_colorspace_name_get(
|
|
COLOR_ROLE_SCENE_LINEAR);
|
|
|
|
if (to_colorspace && to_colorspace[0] != '\0') {
|
|
IMB_colormanagement_transform_v4(pixel, from_colorspace, to_colorspace);
|
|
}
|
|
else {
|
|
/* if no color management enables fallback to legacy conversion */
|
|
srgb_to_linearrgb_v4(pixel, pixel);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Rendering utility functions
|
|
* \{ */
|
|
|
|
void render_new_render_data(Main *bmain,
|
|
Depsgraph *depsgraph,
|
|
Scene *scene,
|
|
int rectx,
|
|
int recty,
|
|
eSpaceSeq_Proxy_RenderSize preview_render_size,
|
|
int for_render,
|
|
RenderData *r_context)
|
|
{
|
|
r_context->bmain = bmain;
|
|
r_context->depsgraph = depsgraph;
|
|
r_context->scene = scene;
|
|
r_context->rectx = rectx;
|
|
r_context->recty = recty;
|
|
r_context->preview_render_size = preview_render_size;
|
|
r_context->ignore_missing_media = false;
|
|
r_context->for_render = for_render;
|
|
r_context->motion_blur_samples = 0;
|
|
r_context->motion_blur_shutter = 0;
|
|
r_context->skip_cache = false;
|
|
r_context->is_proxy_render = false;
|
|
r_context->view_id = 0;
|
|
r_context->gpu_offscreen = nullptr;
|
|
r_context->gpu_viewport = nullptr;
|
|
r_context->task_id = SEQ_TASK_MAIN_RENDER;
|
|
r_context->is_prefetch_render = false;
|
|
}
|
|
|
|
StripElem *render_give_stripelem(const Scene *scene, const Strip *strip, int timeline_frame)
|
|
{
|
|
StripElem *se = strip->data->stripdata;
|
|
|
|
if (strip->type == STRIP_TYPE_IMAGE) {
|
|
/* only IMAGE strips use the whole array, MOVIE strips use only the first element,
|
|
* all other strips don't use this...
|
|
*/
|
|
|
|
int frame_index = round_fl_to_int(give_frame_index(scene, strip, timeline_frame));
|
|
|
|
if (frame_index == -1 || se == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
se += frame_index + strip->anim_startofs;
|
|
}
|
|
return se;
|
|
}
|
|
|
|
Vector<Strip *> seq_shown_strips_get(const Scene *scene,
|
|
ListBase *channels,
|
|
ListBase *seqbase,
|
|
const int timeline_frame,
|
|
const int chanshown)
|
|
{
|
|
VectorSet strips = query_rendered_strips(scene, channels, seqbase, timeline_frame, chanshown);
|
|
const int strip_count = strips.size();
|
|
|
|
if (UNLIKELY(strip_count > MAX_CHANNELS)) {
|
|
BLI_assert_msg(0, "Too many strips, this shouldn't happen");
|
|
return {};
|
|
}
|
|
|
|
Vector<Strip *> strips_vec = strips.extract_vector();
|
|
/* Sort strips by channel. */
|
|
std::sort(strips_vec.begin(), strips_vec.end(), [](const Strip *a, const Strip *b) {
|
|
return a->channel < b->channel;
|
|
});
|
|
return strips_vec;
|
|
}
|
|
|
|
StripScreenQuad get_strip_screen_quad(const RenderData *context, const Strip *strip)
|
|
{
|
|
Scene *scene = context->scene;
|
|
const int x = context->rectx;
|
|
const int y = context->recty;
|
|
const float2 offset{x * 0.5f, y * 0.5f};
|
|
|
|
Array<float2> quad = image_transform_final_quad_get(scene, strip);
|
|
const float scale = get_render_scale_factor(*context);
|
|
return StripScreenQuad{float2(quad[0] * scale + offset),
|
|
float2(quad[1] * scale + offset),
|
|
float2(quad[2] * scale + offset),
|
|
float2(quad[3] * scale + offset)};
|
|
}
|
|
|
|
/* Is quad `a` fully contained (i.e. covered by) quad `b`? For that to happen,
|
|
* all corners of `a` have to be inside `b`. */
|
|
static bool is_quad_a_inside_b(const StripScreenQuad &a, const StripScreenQuad &b)
|
|
{
|
|
return isect_point_quad_v2(a.v0, b.v0, b.v1, b.v2, b.v3) &&
|
|
isect_point_quad_v2(a.v1, b.v0, b.v1, b.v2, b.v3) &&
|
|
isect_point_quad_v2(a.v2, b.v0, b.v1, b.v2, b.v3) &&
|
|
isect_point_quad_v2(a.v3, b.v0, b.v1, b.v2, b.v3);
|
|
}
|
|
|
|
/* Tracking of "known to be opaque" strip quad coordinates, along with their
|
|
* order index within visible strips during rendering. */
|
|
|
|
struct OpaqueQuad {
|
|
StripScreenQuad quad;
|
|
int order_index;
|
|
};
|
|
|
|
struct OpaqueQuadTracker {
|
|
Vector<OpaqueQuad, 4> opaques;
|
|
|
|
/* Determine if the input strip is completely behind opaque strips that are
|
|
* above it. Current implementation is simple and only checks if strip is
|
|
* completely covered by any other strip. It does not detect case where
|
|
* a strip is not covered by a single strip, but is behind of the union
|
|
* of the strips above. */
|
|
bool is_occluded(const RenderData *context, const Strip *strip, int order_index) const
|
|
{
|
|
StripScreenQuad quad = get_strip_screen_quad(context, strip);
|
|
if (quad.is_empty()) {
|
|
/* Strip size is not initialized/valid, we can't know if it is occluded. */
|
|
return false;
|
|
}
|
|
for (const OpaqueQuad &q : opaques) {
|
|
if (q.order_index > order_index && is_quad_a_inside_b(quad, q.quad)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void add_occluder(const RenderData *context, const Strip *strip, int order_index)
|
|
{
|
|
StripScreenQuad quad = get_strip_screen_quad(context, strip);
|
|
if (!quad.is_empty()) {
|
|
opaques.append({quad, order_index});
|
|
}
|
|
}
|
|
};
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Preprocessing & Effects
|
|
*
|
|
* Input preprocessing for STRIP_TYPE_IMAGE, STRIP_TYPE_MOVIE, STRIP_TYPE_MOVIECLIP and
|
|
* STRIP_TYPE_SCENE.
|
|
*
|
|
* Do all the things you can't really do afterwards using sequence effects
|
|
* (read: before re-scaling to render resolution has been done).
|
|
*
|
|
* Order is important!
|
|
*
|
|
* - De-interlace.
|
|
* - Crop and transform in image source coordinate space.
|
|
* - Flip X + Flip Y (could be done afterwards, backward compatibility).
|
|
* - Promote image to float data (affects pipeline operations afterwards).
|
|
* - Color balance (is most efficient in the byte -> float
|
|
* (future: half -> float should also work fine!)
|
|
* case, if done on load, since we can use lookup tables).
|
|
* - Pre-multiply.
|
|
* \{ */
|
|
|
|
static bool sequencer_use_transform(const Strip *strip)
|
|
{
|
|
const StripTransform *transform = strip->data->transform;
|
|
|
|
if (transform->xofs != 0 || transform->yofs != 0 || transform->scale_x != 1 ||
|
|
transform->scale_y != 1 || transform->rotation != 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool sequencer_use_crop(const Strip *strip)
|
|
{
|
|
const StripCrop *crop = strip->data->crop;
|
|
if (crop->left > 0 || crop->right > 0 || crop->top > 0 || crop->bottom > 0) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool seq_input_have_to_preprocess(const RenderData *context,
|
|
Strip *strip,
|
|
float /*timeline_frame*/)
|
|
{
|
|
float mul;
|
|
|
|
if (context && context->is_proxy_render) {
|
|
return false;
|
|
}
|
|
|
|
if ((strip->flag & (SEQ_FILTERY | SEQ_FLIPX | SEQ_FLIPY | SEQ_MAKE_FLOAT)) ||
|
|
sequencer_use_crop(strip) || sequencer_use_transform(strip))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
mul = strip->mul;
|
|
|
|
if (strip->blend_mode == STRIP_BLEND_REPLACE) {
|
|
mul *= strip->blend_opacity / 100.0f;
|
|
}
|
|
|
|
if (mul != 1.0f) {
|
|
return true;
|
|
}
|
|
|
|
if (strip->sat != 1.0f) {
|
|
return true;
|
|
}
|
|
|
|
if (strip->modifiers.first) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Effect, mask and scene in strip input strips are rendered in preview resolution.
|
|
* They are already down-scaled. #input_preprocess() does not expect this to happen.
|
|
* Other strip types are rendered with original media resolution, unless proxies are
|
|
* enabled for them. With proxies `is_proxy_image` will be set correctly to true.
|
|
*/
|
|
static bool seq_need_scale_to_render_size(const Strip *strip, bool is_proxy_image)
|
|
{
|
|
if (is_proxy_image) {
|
|
return false;
|
|
}
|
|
if (strip->is_effect() || strip->type == STRIP_TYPE_MASK || strip->type == STRIP_TYPE_META ||
|
|
(strip->type == STRIP_TYPE_SCENE && ((strip->flag & SEQ_SCENE_STRIPS) != 0)))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static float3x3 sequencer_image_crop_transform_matrix(const Scene *scene,
|
|
const Strip *strip,
|
|
const ImBuf *in,
|
|
const ImBuf *out,
|
|
const float image_scale_factor,
|
|
const float preview_scale_factor)
|
|
{
|
|
const StripTransform *transform = strip->data->transform;
|
|
|
|
/* This value is intentionally kept as integer. Otherwise images with odd dimensions would
|
|
* be translated to center of canvas by non-integer value, which would cause it to be
|
|
* interpolated. Interpolation with 0 user defined translation is unwanted behavior. */
|
|
const int3 image_center_offs((out->x - in->x) / 2, (out->y - in->y) / 2, 0);
|
|
|
|
const float2 translation(transform->xofs * preview_scale_factor,
|
|
transform->yofs * preview_scale_factor);
|
|
const float rotation = transform->rotation;
|
|
const float2 scale(transform->scale_x * image_scale_factor,
|
|
transform->scale_y * image_scale_factor);
|
|
|
|
const float2 origin = image_transform_origin_get(scene, strip);
|
|
const float2 pivot(in->x * origin[0], in->y * origin[1]);
|
|
|
|
const float3x3 matrix = math::from_loc_rot_scale<float3x3>(
|
|
translation + float2(image_center_offs), rotation, scale);
|
|
const float3x3 mat_pivot = math::from_origin_transform(matrix, pivot);
|
|
return math::invert(mat_pivot);
|
|
}
|
|
|
|
static void sequencer_image_crop_init(const Strip *strip,
|
|
const ImBuf *in,
|
|
float crop_scale_factor,
|
|
rctf *r_crop)
|
|
{
|
|
const StripCrop *c = strip->data->crop;
|
|
const int left = c->left * crop_scale_factor;
|
|
const int right = c->right * crop_scale_factor;
|
|
const int top = c->top * crop_scale_factor;
|
|
const int bottom = c->bottom * crop_scale_factor;
|
|
|
|
BLI_rctf_init(r_crop, left, in->x - right, bottom, in->y - top);
|
|
}
|
|
|
|
static bool is_strip_covering_screen(const RenderData *context, const Strip *strip)
|
|
{
|
|
/* The check is done by checking whether all corners of viewport fit inside
|
|
* of the transformed strip. If they do not, the strip does not cover
|
|
* whole screen. */
|
|
float x0 = 0.0f;
|
|
float y0 = 0.0f;
|
|
float x1 = float(context->rectx);
|
|
float y1 = float(context->recty);
|
|
float x_aspect = context->scene->r.xasp / context->scene->r.yasp;
|
|
if (x_aspect != 1.0f) {
|
|
float xmid = (x0 + x1) * 0.5f;
|
|
x0 = xmid - (xmid - x0) * x_aspect;
|
|
x1 = xmid + (x1 - xmid) * x_aspect;
|
|
}
|
|
StripScreenQuad quad = get_strip_screen_quad(context, strip);
|
|
if (quad.is_empty()) {
|
|
return false; /* Strip is zero size. */
|
|
}
|
|
StripScreenQuad screen{float2(x0, y0), float2(x1, y0), float2(x0, y1), float2(x1, y1)};
|
|
|
|
return is_quad_a_inside_b(screen, quad);
|
|
}
|
|
|
|
/* Automatic filter:
|
|
* - No scale, no rotation and non-fractional position: nearest.
|
|
* - Scale up by more than 2x: cubic mitchell.
|
|
* - Scale down by more than 2x: box.
|
|
* - Otherwise: bilinear. */
|
|
static eIMBInterpolationFilterMode get_auto_filter(const StripTransform *transform)
|
|
{
|
|
const float sx = fabsf(transform->scale_x);
|
|
const float sy = fabsf(transform->scale_y);
|
|
if (sx > 2.0f && sy > 2.0f) {
|
|
return IMB_FILTER_CUBIC_MITCHELL;
|
|
}
|
|
if (sx < 0.5f && sy < 0.5f) {
|
|
return IMB_FILTER_BOX;
|
|
}
|
|
const float px = transform->xofs;
|
|
const float py = transform->yofs;
|
|
const float rot = transform->rotation;
|
|
if (sx == 1.0f && sy == 1.0f && roundf(px) == px && roundf(py) == py && rot == 0.0f) {
|
|
return IMB_FILTER_NEAREST;
|
|
}
|
|
return IMB_FILTER_BILINEAR;
|
|
}
|
|
|
|
static void sequencer_preprocess_transform_crop(
|
|
ImBuf *in, ImBuf *out, const RenderData *context, Strip *strip, const bool is_proxy_image)
|
|
{
|
|
const Scene *scene = context->scene;
|
|
const float preview_scale_factor = get_render_scale_factor(*context);
|
|
const bool do_scale_to_render_size = seq_need_scale_to_render_size(strip, is_proxy_image);
|
|
const float image_scale_factor = do_scale_to_render_size ? preview_scale_factor : 1.0f;
|
|
|
|
float3x3 matrix = sequencer_image_crop_transform_matrix(
|
|
scene, strip, in, out, image_scale_factor, preview_scale_factor);
|
|
|
|
/* Proxy image is smaller, so crop values must be corrected by proxy scale factor.
|
|
* Proxy scale factor always matches preview_scale_factor. */
|
|
rctf source_crop;
|
|
const float crop_scale_factor = do_scale_to_render_size ? preview_scale_factor : 1.0f;
|
|
sequencer_image_crop_init(strip, in, crop_scale_factor, &source_crop);
|
|
|
|
const StripTransform *transform = strip->data->transform;
|
|
eIMBInterpolationFilterMode filter = IMB_FILTER_NEAREST;
|
|
switch (transform->filter) {
|
|
case SEQ_TRANSFORM_FILTER_AUTO:
|
|
filter = get_auto_filter(strip->data->transform);
|
|
break;
|
|
case SEQ_TRANSFORM_FILTER_NEAREST:
|
|
filter = IMB_FILTER_NEAREST;
|
|
break;
|
|
case SEQ_TRANSFORM_FILTER_BILINEAR:
|
|
filter = IMB_FILTER_BILINEAR;
|
|
break;
|
|
case SEQ_TRANSFORM_FILTER_CUBIC_BSPLINE:
|
|
filter = IMB_FILTER_CUBIC_BSPLINE;
|
|
break;
|
|
case SEQ_TRANSFORM_FILTER_CUBIC_MITCHELL:
|
|
filter = IMB_FILTER_CUBIC_MITCHELL;
|
|
break;
|
|
case SEQ_TRANSFORM_FILTER_BOX:
|
|
filter = IMB_FILTER_BOX;
|
|
break;
|
|
}
|
|
|
|
IMB_transform(in, out, IMB_TRANSFORM_MODE_CROP_SRC, filter, matrix, &source_crop);
|
|
|
|
if (is_strip_covering_screen(context, strip)) {
|
|
out->planes = in->planes;
|
|
}
|
|
else {
|
|
/* Strip is not covering full viewport, which means areas with transparency
|
|
* are introduced for sure. */
|
|
out->planes = R_IMF_PLANES_RGBA;
|
|
}
|
|
}
|
|
|
|
static void multiply_ibuf(ImBuf *ibuf, const float fmul, const bool multiply_alpha)
|
|
{
|
|
BLI_assert_msg(ibuf->channels == 0 || ibuf->channels == 4,
|
|
"Sequencer only supports 4 channel images");
|
|
const size_t pixel_count = IMB_get_pixel_count(ibuf);
|
|
if (ibuf->byte_buffer.data != nullptr) {
|
|
threading::parallel_for(IndexRange(pixel_count), 64 * 1024, [&](IndexRange range) {
|
|
uchar *ptr = ibuf->byte_buffer.data + range.first() * 4;
|
|
const int imul = int(256.0f * fmul);
|
|
for ([[maybe_unused]] const int64_t i : range) {
|
|
ptr[0] = min_ii((imul * ptr[0]) >> 8, 255);
|
|
ptr[1] = min_ii((imul * ptr[1]) >> 8, 255);
|
|
ptr[2] = min_ii((imul * ptr[2]) >> 8, 255);
|
|
if (multiply_alpha) {
|
|
ptr[3] = min_ii((imul * ptr[3]) >> 8, 255);
|
|
}
|
|
ptr += 4;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (ibuf->float_buffer.data != nullptr) {
|
|
threading::parallel_for(IndexRange(pixel_count), 64 * 1024, [&](IndexRange range) {
|
|
float *ptr = ibuf->float_buffer.data + range.first() * 4;
|
|
for ([[maybe_unused]] const int64_t i : range) {
|
|
ptr[0] *= fmul;
|
|
ptr[1] *= fmul;
|
|
ptr[2] *= fmul;
|
|
if (multiply_alpha) {
|
|
ptr[3] *= fmul;
|
|
}
|
|
ptr += 4;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
static ImBuf *input_preprocess(const RenderData *context,
|
|
Strip *strip,
|
|
float timeline_frame,
|
|
ImBuf *ibuf,
|
|
const bool is_proxy_image)
|
|
{
|
|
Scene *scene = context->scene;
|
|
ImBuf *preprocessed_ibuf = nullptr;
|
|
|
|
/* Deinterlace. */
|
|
if ((strip->flag & SEQ_FILTERY) && !ELEM(strip->type, STRIP_TYPE_MOVIE, STRIP_TYPE_MOVIECLIP)) {
|
|
/* Change original image pointer to avoid another duplication in SEQ_USE_TRANSFORM. */
|
|
preprocessed_ibuf = IMB_makeSingleUser(ibuf);
|
|
ibuf = preprocessed_ibuf;
|
|
|
|
IMB_filtery(preprocessed_ibuf);
|
|
}
|
|
|
|
if (sequencer_use_crop(strip) || sequencer_use_transform(strip) || context->rectx != ibuf->x ||
|
|
context->recty != ibuf->y)
|
|
{
|
|
const int x = context->rectx;
|
|
const int y = context->recty;
|
|
preprocessed_ibuf = IMB_allocImBuf(
|
|
x, y, 32, ibuf->float_buffer.data ? IB_float_data : IB_byte_data);
|
|
|
|
sequencer_preprocess_transform_crop(ibuf, preprocessed_ibuf, context, strip, is_proxy_image);
|
|
|
|
seq_imbuf_assign_spaces(scene, preprocessed_ibuf);
|
|
IMB_metadata_copy(preprocessed_ibuf, ibuf);
|
|
IMB_freeImBuf(ibuf);
|
|
}
|
|
|
|
/* Duplicate ibuf if we still have original. */
|
|
if (preprocessed_ibuf == nullptr) {
|
|
preprocessed_ibuf = IMB_makeSingleUser(ibuf);
|
|
}
|
|
|
|
if (strip->flag & SEQ_FLIPX) {
|
|
IMB_flipx(preprocessed_ibuf);
|
|
}
|
|
|
|
if (strip->flag & SEQ_FLIPY) {
|
|
IMB_flipy(preprocessed_ibuf);
|
|
}
|
|
|
|
if (strip->sat != 1.0f) {
|
|
IMB_saturation(preprocessed_ibuf, strip->sat);
|
|
}
|
|
|
|
if (strip->flag & SEQ_MAKE_FLOAT) {
|
|
if (!preprocessed_ibuf->float_buffer.data) {
|
|
seq_imbuf_to_sequencer_space(scene, preprocessed_ibuf, true);
|
|
}
|
|
|
|
if (preprocessed_ibuf->byte_buffer.data) {
|
|
IMB_free_byte_pixels(preprocessed_ibuf);
|
|
}
|
|
}
|
|
|
|
float mul = strip->mul;
|
|
if (strip->blend_mode == STRIP_BLEND_REPLACE) {
|
|
mul *= strip->blend_opacity / 100.0f;
|
|
}
|
|
|
|
if (mul != 1.0f) {
|
|
const bool multiply_alpha = (strip->flag & SEQ_MULTIPLY_ALPHA);
|
|
multiply_ibuf(preprocessed_ibuf, mul, multiply_alpha);
|
|
}
|
|
|
|
if (strip->modifiers.first) {
|
|
modifier_apply_stack(context, strip, preprocessed_ibuf, timeline_frame);
|
|
}
|
|
|
|
return preprocessed_ibuf;
|
|
}
|
|
|
|
static ImBuf *seq_render_preprocess_ibuf(const RenderData *context,
|
|
Strip *strip,
|
|
ImBuf *ibuf,
|
|
float timeline_frame,
|
|
bool use_preprocess,
|
|
const bool is_proxy_image)
|
|
{
|
|
if (context->is_proxy_render == false &&
|
|
(ibuf->x != context->rectx || ibuf->y != context->recty))
|
|
{
|
|
use_preprocess = true;
|
|
}
|
|
|
|
/* Proxies and non-generator effect strips are not stored in cache. */
|
|
const bool is_effect_with_inputs = strip->is_effect() &&
|
|
(effect_get_num_inputs(strip->type) != 0 ||
|
|
(strip->type == STRIP_TYPE_ADJUSTMENT));
|
|
if (!is_proxy_image && !is_effect_with_inputs) {
|
|
Scene *orig_scene = prefetch_get_original_scene(context);
|
|
if (orig_scene->ed->cache_flag & SEQ_CACHE_STORE_RAW) {
|
|
source_image_cache_put(context, strip, timeline_frame, ibuf);
|
|
}
|
|
}
|
|
|
|
if (use_preprocess) {
|
|
ibuf = input_preprocess(context, strip, timeline_frame, ibuf, is_proxy_image);
|
|
}
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
static ImBuf *seq_render_effect_strip_impl(const RenderData *context,
|
|
SeqRenderState *state,
|
|
Strip *strip,
|
|
float timeline_frame)
|
|
{
|
|
Scene *scene = context->scene;
|
|
float fac;
|
|
int i;
|
|
EffectHandle sh = strip_effect_handle_get(strip);
|
|
const FCurve *fcu = nullptr;
|
|
ImBuf *ibuf[2];
|
|
Strip *input[2];
|
|
ImBuf *out = nullptr;
|
|
|
|
ibuf[0] = ibuf[1] = nullptr;
|
|
|
|
input[0] = strip->input1;
|
|
input[1] = strip->input2;
|
|
|
|
if (!sh.execute) {
|
|
/* effect not supported in this version... */
|
|
out = IMB_allocImBuf(context->rectx, context->recty, 32, IB_byte_data);
|
|
return out;
|
|
}
|
|
|
|
if (strip->flag & SEQ_USE_EFFECT_DEFAULT_FADE) {
|
|
sh.get_default_fac(scene, strip, timeline_frame, &fac);
|
|
}
|
|
else {
|
|
fcu = id_data_find_fcurve(&scene->id, strip, &RNA_Strip, "effect_fader", 0, nullptr);
|
|
if (fcu) {
|
|
fac = evaluate_fcurve(fcu, timeline_frame);
|
|
}
|
|
else {
|
|
fac = strip->effect_fader;
|
|
}
|
|
}
|
|
|
|
StripEarlyOut early_out = sh.early_out(strip, fac);
|
|
|
|
switch (early_out) {
|
|
case StripEarlyOut::NoInput:
|
|
out = sh.execute(context, state, strip, timeline_frame, fac, nullptr, nullptr);
|
|
break;
|
|
case StripEarlyOut::DoEffect:
|
|
for (i = 0; i < 2; i++) {
|
|
/* Speed effect requires time remapping of `timeline_frame` for input(s). */
|
|
if (input[0] && strip->type == STRIP_TYPE_SPEED) {
|
|
float target_frame = strip_speed_effect_target_frame_get(
|
|
scene, strip, timeline_frame, i);
|
|
|
|
/* Only convert to int when interpolation is not used. */
|
|
SpeedControlVars *s = reinterpret_cast<SpeedControlVars *>(strip->effectdata);
|
|
if ((s->flags & SEQ_SPEED_USE_INTERPOLATION) != 0) {
|
|
target_frame = std::floor(target_frame);
|
|
}
|
|
|
|
ibuf[i] = seq_render_strip(context, state, input[0], target_frame);
|
|
}
|
|
else { /* Other effects. */
|
|
if (input[i]) {
|
|
ibuf[i] = seq_render_strip(context, state, input[i], timeline_frame);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ibuf[0] && (ibuf[1] || effect_get_num_inputs(strip->type) == 1)) {
|
|
out = sh.execute(context, state, strip, timeline_frame, fac, ibuf[0], ibuf[1]);
|
|
}
|
|
break;
|
|
case StripEarlyOut::UseInput1:
|
|
if (input[0]) {
|
|
out = seq_render_strip(context, state, input[0], timeline_frame);
|
|
}
|
|
break;
|
|
case StripEarlyOut::UseInput2:
|
|
if (input[1]) {
|
|
out = seq_render_strip(context, state, input[1], timeline_frame);
|
|
}
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
IMB_freeImBuf(ibuf[i]);
|
|
}
|
|
|
|
if (out == nullptr) {
|
|
out = IMB_allocImBuf(context->rectx, context->recty, 32, IB_byte_data);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Individual strip rendering functions
|
|
* \{ */
|
|
|
|
static void convert_multilayer_ibuf(ImBuf *ibuf)
|
|
{
|
|
/* Load the combined/RGB layer, if this is a multi-layer image. */
|
|
BKE_movieclip_convert_multilayer_ibuf(ibuf);
|
|
|
|
/* Combined layer might be non-4 channels, however the rest
|
|
* of sequencer assumes RGBA everywhere. Convert to 4 channel if needed. */
|
|
if (ibuf->float_buffer.data != nullptr && ibuf->channels != 4) {
|
|
float *dst = MEM_malloc_arrayN<float>(4 * size_t(ibuf->x) * size_t(ibuf->y), __func__);
|
|
IMB_buffer_float_from_float_threaded(dst,
|
|
ibuf->float_buffer.data,
|
|
ibuf->channels,
|
|
IB_PROFILE_LINEAR_RGB,
|
|
IB_PROFILE_LINEAR_RGB,
|
|
false,
|
|
ibuf->x,
|
|
ibuf->y,
|
|
ibuf->x,
|
|
ibuf->x);
|
|
IMB_assign_float_buffer(ibuf, dst, IB_TAKE_OWNERSHIP);
|
|
ibuf->channels = 4;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render individual view for multi-view or single (default view) for mono-view.
|
|
*/
|
|
static ImBuf *seq_render_image_strip_view(const RenderData *context,
|
|
Strip *strip,
|
|
char *filepath,
|
|
char *prefix,
|
|
const char *ext,
|
|
int view_id)
|
|
{
|
|
ImBuf *ibuf = nullptr;
|
|
|
|
int flag = IB_byte_data | IB_metadata | IB_multilayer;
|
|
if (strip->alpha_mode == SEQ_ALPHA_PREMUL) {
|
|
flag |= IB_alphamode_premul;
|
|
}
|
|
|
|
if (prefix[0] == '\0') {
|
|
ibuf = IMB_load_image_from_filepath(filepath, flag, strip->data->colorspace_settings.name);
|
|
}
|
|
else {
|
|
char filepath_view[FILE_MAX];
|
|
BKE_scene_multiview_view_prefix_get(context->scene, filepath, prefix, &ext);
|
|
seq_multiview_name(context->scene, view_id, prefix, ext, filepath_view, FILE_MAX);
|
|
ibuf = IMB_load_image_from_filepath(
|
|
filepath_view, flag, strip->data->colorspace_settings.name);
|
|
}
|
|
|
|
if (ibuf == nullptr) {
|
|
return nullptr;
|
|
}
|
|
convert_multilayer_ibuf(ibuf);
|
|
|
|
/* We don't need both (speed reasons)! */
|
|
if (ibuf->float_buffer.data != nullptr && ibuf->byte_buffer.data != nullptr) {
|
|
IMB_free_byte_pixels(ibuf);
|
|
}
|
|
|
|
/* All sequencer color is done in SRGB space, linear gives odd cross-fades. */
|
|
seq_imbuf_to_sequencer_space(context->scene, ibuf, false);
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
static bool seq_image_strip_is_multiview_render(Scene *scene,
|
|
Strip *strip,
|
|
int totfiles,
|
|
const char *filepath,
|
|
char *r_prefix,
|
|
const char *r_ext)
|
|
{
|
|
if (totfiles > 1) {
|
|
BKE_scene_multiview_view_prefix_get(scene, filepath, r_prefix, &r_ext);
|
|
if (r_prefix[0] == '\0') {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
r_prefix[0] = '\0';
|
|
}
|
|
|
|
return (strip->flag & SEQ_USE_VIEWS) != 0 && (scene->r.scemode & R_MULTIVIEW) != 0;
|
|
}
|
|
|
|
static ImBuf *create_missing_media_image(const RenderData *context, int width, int height)
|
|
{
|
|
if (context->ignore_missing_media) {
|
|
return nullptr;
|
|
}
|
|
if (context->scene == nullptr || context->scene->ed == nullptr ||
|
|
(context->scene->ed->show_missing_media_flag & SEQ_EDIT_SHOW_MISSING_MEDIA) == 0)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
ImBuf *ibuf = IMB_allocImBuf(max_ii(width, 1), max_ii(height, 1), 32, IB_byte_data);
|
|
float col[4] = {0.85f, 0.0f, 0.75f, 1.0f};
|
|
IMB_rectfill(ibuf, col);
|
|
return ibuf;
|
|
}
|
|
|
|
static ImBuf *seq_render_image_strip(const RenderData *context,
|
|
Strip *strip,
|
|
int timeline_frame,
|
|
bool *r_is_proxy_image)
|
|
{
|
|
char filepath[FILE_MAX];
|
|
const char *ext = nullptr;
|
|
char prefix[FILE_MAX];
|
|
ImBuf *ibuf = nullptr;
|
|
|
|
StripElem *s_elem = render_give_stripelem(context->scene, strip, timeline_frame);
|
|
if (s_elem == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
BLI_path_join(filepath, sizeof(filepath), strip->data->dirpath, s_elem->filename);
|
|
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&context->scene->id));
|
|
|
|
/* Try to get a proxy image. */
|
|
ibuf = seq_proxy_fetch(context, strip, timeline_frame);
|
|
if (ibuf != nullptr) {
|
|
*r_is_proxy_image = true;
|
|
return ibuf;
|
|
}
|
|
|
|
/* Proxy not found, render original. */
|
|
const int totfiles = seq_num_files(context->scene, strip->views_format, true);
|
|
bool is_multiview_render = seq_image_strip_is_multiview_render(
|
|
context->scene, strip, totfiles, filepath, prefix, ext);
|
|
|
|
if (is_multiview_render) {
|
|
int totviews = BKE_scene_multiview_num_views_get(&context->scene->r);
|
|
ImBuf **ibufs_arr = MEM_calloc_arrayN<ImBuf *>(totviews, "Sequence Image Views Imbufs");
|
|
|
|
for (int view_id = 0; view_id < totfiles; view_id++) {
|
|
ibufs_arr[view_id] = seq_render_image_strip_view(
|
|
context, strip, filepath, prefix, ext, view_id);
|
|
}
|
|
|
|
if (ibufs_arr[0] == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (strip->views_format == R_IMF_VIEWS_STEREO_3D) {
|
|
IMB_ImBufFromStereo3d(strip->stereo3d_format, ibufs_arr[0], &ibufs_arr[0], &ibufs_arr[1]);
|
|
}
|
|
|
|
for (int view_id = 0; view_id < totviews; view_id++) {
|
|
RenderData localcontext = *context;
|
|
localcontext.view_id = view_id;
|
|
|
|
if (view_id != context->view_id) {
|
|
ibufs_arr[view_id] = seq_render_preprocess_ibuf(
|
|
&localcontext, strip, ibufs_arr[view_id], timeline_frame, true, false);
|
|
}
|
|
}
|
|
|
|
/* Return the original requested ImBuf. */
|
|
ibuf = ibufs_arr[context->view_id];
|
|
|
|
/* Remove the others (decrease their refcount). */
|
|
for (int view_id = 0; view_id < totviews; view_id++) {
|
|
if (ibufs_arr[view_id] != ibuf) {
|
|
IMB_freeImBuf(ibufs_arr[view_id]);
|
|
}
|
|
}
|
|
|
|
MEM_freeN(ibufs_arr);
|
|
}
|
|
else {
|
|
ibuf = seq_render_image_strip_view(context, strip, filepath, prefix, ext, context->view_id);
|
|
}
|
|
|
|
blender::seq::media_presence_set_missing(context->scene, strip, ibuf == nullptr);
|
|
if (ibuf == nullptr) {
|
|
return create_missing_media_image(context, s_elem->orig_width, s_elem->orig_height);
|
|
}
|
|
|
|
s_elem->orig_width = ibuf->x;
|
|
s_elem->orig_height = ibuf->y;
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
static ImBuf *seq_render_movie_strip_custom_file_proxy(const RenderData *context,
|
|
Strip *strip,
|
|
int timeline_frame)
|
|
{
|
|
char filepath[PROXY_MAXFILE];
|
|
StripProxy *proxy = strip->data->proxy;
|
|
|
|
if (proxy->anim == nullptr) {
|
|
if (seq_proxy_get_custom_file_filepath(strip, filepath, context->view_id)) {
|
|
/* Sequencer takes care of colorspace conversion of the result. The input is the best to be
|
|
* kept unchanged for the performance reasons. */
|
|
proxy->anim = openanim(
|
|
filepath, IB_byte_data, 0, true, strip->data->colorspace_settings.name);
|
|
}
|
|
if (proxy->anim == nullptr) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
int frameno = round_fl_to_int(give_frame_index(context->scene, strip, timeline_frame)) +
|
|
strip->anim_startofs;
|
|
return MOV_decode_frame(proxy->anim, frameno, IMB_TC_NONE, IMB_PROXY_NONE);
|
|
}
|
|
|
|
static IMB_Timecode_Type seq_render_movie_strip_timecode_get(Strip *strip)
|
|
{
|
|
bool use_timecodes = (strip->flag & SEQ_USE_PROXY) != 0;
|
|
if (!use_timecodes) {
|
|
return IMB_TC_NONE;
|
|
}
|
|
return IMB_Timecode_Type(strip->data->proxy ? IMB_Timecode_Type(strip->data->proxy->tc) :
|
|
IMB_TC_NONE);
|
|
}
|
|
|
|
/**
|
|
* Render individual view for multi-view or single (default view) for mono-view.
|
|
*/
|
|
static ImBuf *seq_render_movie_strip_view(const RenderData *context,
|
|
Strip *strip,
|
|
float timeline_frame,
|
|
StripAnim *sanim,
|
|
bool *r_is_proxy_image)
|
|
{
|
|
ImBuf *ibuf = nullptr;
|
|
IMB_Proxy_Size psize = rendersize_to_proxysize(context->preview_render_size);
|
|
const int frame_index = round_fl_to_int(give_frame_index(context->scene, strip, timeline_frame));
|
|
|
|
if (can_use_proxy(context, strip, psize)) {
|
|
/* Try to get a proxy image.
|
|
* Movie proxies are handled by ImBuf module with exception of `custom file` setting. */
|
|
if (context->scene->ed->proxy_storage != SEQ_EDIT_PROXY_DIR_STORAGE &&
|
|
strip->data->proxy->storage & SEQ_STORAGE_PROXY_CUSTOM_FILE)
|
|
{
|
|
ibuf = seq_render_movie_strip_custom_file_proxy(context, strip, timeline_frame);
|
|
}
|
|
else {
|
|
ibuf = MOV_decode_frame(sanim->anim,
|
|
frame_index + strip->anim_startofs,
|
|
seq_render_movie_strip_timecode_get(strip),
|
|
psize);
|
|
}
|
|
|
|
if (ibuf != nullptr) {
|
|
*r_is_proxy_image = true;
|
|
}
|
|
}
|
|
|
|
/* Fetching for requested proxy size failed, try fetching the original instead. */
|
|
if (ibuf == nullptr) {
|
|
ibuf = MOV_decode_frame(sanim->anim,
|
|
frame_index + strip->anim_startofs,
|
|
seq_render_movie_strip_timecode_get(strip),
|
|
IMB_PROXY_NONE);
|
|
}
|
|
if (ibuf == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
seq_imbuf_to_sequencer_space(context->scene, ibuf, false);
|
|
|
|
/* We don't need both (speed reasons)! */
|
|
if (ibuf->float_buffer.data != nullptr && ibuf->byte_buffer.data != nullptr) {
|
|
IMB_free_byte_pixels(ibuf);
|
|
}
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
static ImBuf *seq_render_movie_strip(const RenderData *context,
|
|
Strip *strip,
|
|
float timeline_frame,
|
|
bool *r_is_proxy_image)
|
|
{
|
|
/* Load all the videos. */
|
|
strip_open_anim_file(context->scene, strip, false);
|
|
|
|
ImBuf *ibuf = nullptr;
|
|
StripAnim *sanim = static_cast<StripAnim *>(strip->anims.first);
|
|
const int totfiles = seq_num_files(context->scene, strip->views_format, true);
|
|
bool is_multiview_render = (strip->flag & SEQ_USE_VIEWS) != 0 &&
|
|
(context->scene->r.scemode & R_MULTIVIEW) != 0 &&
|
|
BLI_listbase_count_is_equal_to(&strip->anims, totfiles);
|
|
|
|
if (is_multiview_render) {
|
|
ImBuf **ibuf_arr;
|
|
int totviews = BKE_scene_multiview_num_views_get(&context->scene->r);
|
|
ibuf_arr = MEM_calloc_arrayN<ImBuf *>(totviews, "Sequence Image Views Imbufs");
|
|
int ibuf_view_id;
|
|
|
|
for (ibuf_view_id = 0, sanim = static_cast<StripAnim *>(strip->anims.first); sanim;
|
|
sanim = sanim->next, ibuf_view_id++)
|
|
{
|
|
if (sanim->anim) {
|
|
ibuf_arr[ibuf_view_id] = seq_render_movie_strip_view(
|
|
context, strip, timeline_frame, sanim, r_is_proxy_image);
|
|
}
|
|
}
|
|
|
|
if (strip->views_format == R_IMF_VIEWS_STEREO_3D) {
|
|
if (ibuf_arr[0] == nullptr) {
|
|
/* Probably proxy hasn't been created yet. */
|
|
MEM_freeN(ibuf_arr);
|
|
return nullptr;
|
|
}
|
|
|
|
IMB_ImBufFromStereo3d(strip->stereo3d_format, ibuf_arr[0], &ibuf_arr[0], &ibuf_arr[1]);
|
|
}
|
|
|
|
for (int view_id = 0; view_id < totviews; view_id++) {
|
|
RenderData localcontext = *context;
|
|
localcontext.view_id = view_id;
|
|
|
|
if (view_id != context->view_id && ibuf_arr[view_id]) {
|
|
ibuf_arr[view_id] = seq_render_preprocess_ibuf(
|
|
&localcontext, strip, ibuf_arr[view_id], timeline_frame, true, false);
|
|
}
|
|
}
|
|
|
|
/* Return the original requested ImBuf. */
|
|
ibuf = ibuf_arr[context->view_id];
|
|
|
|
/* Remove the others (decrease their refcount). */
|
|
for (int view_id = 0; view_id < totviews; view_id++) {
|
|
if (ibuf_arr[view_id] != ibuf) {
|
|
IMB_freeImBuf(ibuf_arr[view_id]);
|
|
}
|
|
}
|
|
|
|
MEM_freeN(ibuf_arr);
|
|
}
|
|
else {
|
|
ibuf = seq_render_movie_strip_view(context, strip, timeline_frame, sanim, r_is_proxy_image);
|
|
}
|
|
|
|
blender::seq::media_presence_set_missing(context->scene, strip, ibuf == nullptr);
|
|
if (ibuf == nullptr) {
|
|
return create_missing_media_image(
|
|
context, strip->data->stripdata->orig_width, strip->data->stripdata->orig_height);
|
|
}
|
|
|
|
if (*r_is_proxy_image == false) {
|
|
if (sanim && sanim->anim) {
|
|
strip->data->stripdata->orig_fps = MOV_get_fps(sanim->anim);
|
|
}
|
|
strip->data->stripdata->orig_width = ibuf->x;
|
|
strip->data->stripdata->orig_height = ibuf->y;
|
|
}
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
static ImBuf *seq_get_movieclip_ibuf(Strip *strip, MovieClipUser user)
|
|
{
|
|
ImBuf *ibuf = nullptr;
|
|
float tloc[2], tscale, tangle;
|
|
if (strip->clip_flag & SEQ_MOVIECLIP_RENDER_STABILIZED) {
|
|
ibuf = BKE_movieclip_get_stable_ibuf(strip->clip, &user, 0, tloc, &tscale, &tangle);
|
|
}
|
|
else {
|
|
ibuf = BKE_movieclip_get_ibuf_flag(
|
|
strip->clip, &user, strip->clip->flag, MOVIECLIP_CACHE_SKIP);
|
|
}
|
|
return ibuf;
|
|
}
|
|
|
|
static ImBuf *seq_render_movieclip_strip(const RenderData *context,
|
|
Strip *strip,
|
|
float frame_index,
|
|
bool *r_is_proxy_image)
|
|
{
|
|
ImBuf *ibuf = nullptr;
|
|
MovieClipUser user = *DNA_struct_default_get(MovieClipUser);
|
|
IMB_Proxy_Size psize = rendersize_to_proxysize(context->preview_render_size);
|
|
|
|
if (!strip->clip) {
|
|
return nullptr;
|
|
}
|
|
|
|
BKE_movieclip_user_set_frame(&user,
|
|
frame_index + strip->anim_startofs + strip->clip->start_frame);
|
|
|
|
user.render_size = MCLIP_PROXY_RENDER_SIZE_FULL;
|
|
switch (psize) {
|
|
case IMB_PROXY_NONE:
|
|
user.render_size = MCLIP_PROXY_RENDER_SIZE_FULL;
|
|
break;
|
|
case IMB_PROXY_100:
|
|
user.render_size = MCLIP_PROXY_RENDER_SIZE_100;
|
|
break;
|
|
case IMB_PROXY_75:
|
|
user.render_size = MCLIP_PROXY_RENDER_SIZE_75;
|
|
break;
|
|
case IMB_PROXY_50:
|
|
user.render_size = MCLIP_PROXY_RENDER_SIZE_50;
|
|
break;
|
|
case IMB_PROXY_25:
|
|
user.render_size = MCLIP_PROXY_RENDER_SIZE_25;
|
|
break;
|
|
}
|
|
|
|
if (strip->clip_flag & SEQ_MOVIECLIP_RENDER_UNDISTORTED) {
|
|
user.render_flag |= MCLIP_PROXY_RENDER_UNDISTORT;
|
|
}
|
|
|
|
/* Try to get a proxy image. */
|
|
ibuf = seq_get_movieclip_ibuf(strip, user);
|
|
|
|
/* If clip doesn't use proxies, it will fall back to full size render of original file. */
|
|
if (ibuf != nullptr && psize != IMB_PROXY_NONE && BKE_movieclip_proxy_enabled(strip->clip)) {
|
|
*r_is_proxy_image = true;
|
|
}
|
|
|
|
/* If proxy is not found, grab full-size frame. */
|
|
if (ibuf == nullptr) {
|
|
user.render_flag |= MCLIP_PROXY_RENDER_USE_FALLBACK_RENDER;
|
|
ibuf = seq_get_movieclip_ibuf(strip, user);
|
|
}
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
ImBuf *seq_render_mask(const RenderData *context, Mask *mask, float frame_index, bool make_float)
|
|
{
|
|
/* TODO: add option to rasterize to alpha imbuf? */
|
|
ImBuf *ibuf = nullptr;
|
|
float *maskbuf;
|
|
int i;
|
|
|
|
if (!mask) {
|
|
return nullptr;
|
|
}
|
|
|
|
AnimData *adt;
|
|
Mask *mask_temp;
|
|
MaskRasterHandle *mr_handle;
|
|
|
|
mask_temp = (Mask *)BKE_id_copy_ex(
|
|
nullptr, &mask->id, nullptr, LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_NO_ANIMDATA);
|
|
|
|
BKE_mask_evaluate(mask_temp, mask->sfra + frame_index, true);
|
|
|
|
/* anim-data */
|
|
adt = BKE_animdata_from_id(&mask->id);
|
|
const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
|
|
context->depsgraph, mask->sfra + frame_index);
|
|
BKE_animsys_evaluate_animdata(&mask_temp->id, adt, &anim_eval_context, ADT_RECALC_ANIM, false);
|
|
|
|
maskbuf = MEM_malloc_arrayN<float>(size_t(context->rectx) * size_t(context->recty), __func__);
|
|
|
|
mr_handle = BKE_maskrasterize_handle_new();
|
|
|
|
BKE_maskrasterize_handle_init(
|
|
mr_handle, mask_temp, context->rectx, context->recty, true, true, true);
|
|
|
|
BKE_id_free(nullptr, &mask_temp->id);
|
|
|
|
BKE_maskrasterize_buffer(mr_handle, context->rectx, context->recty, maskbuf);
|
|
|
|
BKE_maskrasterize_handle_free(mr_handle);
|
|
|
|
if (make_float) {
|
|
/* pixels */
|
|
const float *fp_src;
|
|
float *fp_dst;
|
|
|
|
ibuf = IMB_allocImBuf(
|
|
context->rectx, context->recty, 32, IB_float_data | IB_uninitialized_pixels);
|
|
|
|
fp_src = maskbuf;
|
|
fp_dst = ibuf->float_buffer.data;
|
|
i = context->rectx * context->recty;
|
|
while (--i) {
|
|
fp_dst[0] = fp_dst[1] = fp_dst[2] = *fp_src;
|
|
fp_dst[3] = 1.0f;
|
|
|
|
fp_src += 1;
|
|
fp_dst += 4;
|
|
}
|
|
}
|
|
else {
|
|
/* pixels */
|
|
const float *fp_src;
|
|
uchar *ub_dst;
|
|
|
|
ibuf = IMB_allocImBuf(
|
|
context->rectx, context->recty, 32, IB_byte_data | IB_uninitialized_pixels);
|
|
|
|
fp_src = maskbuf;
|
|
ub_dst = ibuf->byte_buffer.data;
|
|
i = context->rectx * context->recty;
|
|
while (--i) {
|
|
ub_dst[0] = ub_dst[1] = ub_dst[2] = uchar(*fp_src * 255.0f); /* already clamped */
|
|
ub_dst[3] = 255;
|
|
|
|
fp_src += 1;
|
|
ub_dst += 4;
|
|
}
|
|
}
|
|
|
|
MEM_freeN(maskbuf);
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
static ImBuf *seq_render_mask_strip(const RenderData *context, Strip *strip, float frame_index)
|
|
{
|
|
bool make_float = (strip->flag & SEQ_MAKE_FLOAT) != 0;
|
|
|
|
return seq_render_mask(context, strip->mask, frame_index, make_float);
|
|
}
|
|
|
|
static ImBuf *seq_render_scene_strip(const RenderData *context,
|
|
Strip *strip,
|
|
float frame_index,
|
|
float timeline_frame)
|
|
{
|
|
ImBuf *ibuf = nullptr;
|
|
double frame;
|
|
Object *camera;
|
|
|
|
struct {
|
|
int scemode;
|
|
int timeline_frame;
|
|
float subframe;
|
|
int mode;
|
|
} orig_data;
|
|
|
|
/* Old info:
|
|
* Hack! This function can be called from do_render_seq(), in that case
|
|
* the strip->scene can already have a Render initialized with same name,
|
|
* so we have to use a default name. (compositor uses scene name to
|
|
* find render).
|
|
* However, when called from within the UI (image preview in sequencer)
|
|
* we do want to use scene Render, that way the render result is defined
|
|
* for display in render/image-window
|
|
*
|
|
* Hmm, don't see, why we can't do that all the time,
|
|
* and since G.is_rendering is uhm, gone... (Peter)
|
|
*/
|
|
|
|
/* New info:
|
|
* Using the same name for the renders works just fine as the do_render_seq()
|
|
* render is not used while the scene strips are rendered.
|
|
*
|
|
* However rendering from UI (through sequencer_preview_area_draw) can crash in
|
|
* very many cases since other renders (material preview, an actual render etc.)
|
|
* can be started while this sequence preview render is running. The only proper
|
|
* solution is to make the sequencer preview render a proper job, which can be
|
|
* stopped when needed. This would also give a nice progress bar for the preview
|
|
* space so that users know there's something happening.
|
|
*
|
|
* As a result the active scene now only uses OpenGL rendering for the sequencer
|
|
* preview. This is far from nice, but is the only way to prevent crashes at this
|
|
* time.
|
|
*
|
|
* -jahka
|
|
*/
|
|
|
|
const bool is_rendering = G.is_rendering;
|
|
bool is_preview = !context->for_render && (context->scene->r.seq_prev_type) != OB_RENDER;
|
|
|
|
bool have_comp = false;
|
|
bool use_gpencil = true;
|
|
/* do we need to re-evaluate the frame after rendering? */
|
|
bool is_frame_update = false;
|
|
Scene *scene;
|
|
|
|
/* don't refer to strip->scene above this point!, it can be nullptr */
|
|
if (strip->scene == nullptr) {
|
|
return create_missing_media_image(context, context->rectx, context->recty);
|
|
}
|
|
|
|
/* Prevent rendering scene recursively. */
|
|
if (strip->scene == context->scene) {
|
|
return nullptr;
|
|
}
|
|
|
|
scene = strip->scene;
|
|
frame = double(scene->r.sfra) + double(frame_index) + double(strip->anim_startofs);
|
|
|
|
#if 0 /* UNUSED */
|
|
have_seq = (scene->r.scemode & R_DOSEQ) && scene->ed && scene->ed->seqbase.first;
|
|
#endif
|
|
have_comp = (scene->r.scemode & R_DOCOMP) && scene->compositing_node_group;
|
|
|
|
/* Get view layer for the strip. */
|
|
ViewLayer *view_layer = BKE_view_layer_default_render(scene);
|
|
/* Depsgraph will be nullptr when doing rendering. */
|
|
Depsgraph *depsgraph = nullptr;
|
|
|
|
orig_data.scemode = scene->r.scemode;
|
|
orig_data.timeline_frame = scene->r.cfra;
|
|
orig_data.subframe = scene->r.subframe;
|
|
orig_data.mode = scene->r.mode;
|
|
|
|
BKE_scene_frame_set(scene, frame);
|
|
|
|
if (strip->scene_camera) {
|
|
camera = strip->scene_camera;
|
|
}
|
|
else {
|
|
BKE_scene_camera_switch_update(scene);
|
|
camera = scene->camera;
|
|
}
|
|
|
|
if (have_comp == false && camera == nullptr) {
|
|
goto finally;
|
|
}
|
|
|
|
if (strip->flag & SEQ_SCENE_NO_ANNOTATION) {
|
|
use_gpencil = false;
|
|
}
|
|
|
|
/* Prevent eternal loop. */
|
|
scene->r.scemode &= ~R_DOSEQ;
|
|
|
|
/* Temporarily disable camera switching to enforce using `camera`. */
|
|
scene->r.mode |= R_NO_CAMERA_SWITCH;
|
|
|
|
is_frame_update = (orig_data.timeline_frame != scene->r.cfra) ||
|
|
(orig_data.subframe != scene->r.subframe);
|
|
|
|
if (view3d_fn && is_preview && camera) {
|
|
char err_out[256] = "unknown";
|
|
int width, height;
|
|
BKE_render_resolution(&scene->r, false, &width, &height);
|
|
const char *viewname = BKE_scene_multiview_render_view_name_get(&scene->r, context->view_id);
|
|
|
|
uint draw_flags = V3D_OFSDRAW_NONE;
|
|
draw_flags |= (use_gpencil) ? V3D_OFSDRAW_SHOW_ANNOTATION : 0;
|
|
draw_flags |= (context->scene->r.seq_flag & R_SEQ_OVERRIDE_SCENE_SETTINGS) ?
|
|
V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS :
|
|
0;
|
|
|
|
/* for old scene this can be uninitialized,
|
|
* should probably be added to do_versions at some point if the functionality stays */
|
|
if (context->scene->r.seq_prev_type == 0) {
|
|
context->scene->r.seq_prev_type = 3 /* == OB_SOLID */;
|
|
}
|
|
|
|
/* opengl offscreen render */
|
|
depsgraph = BKE_scene_ensure_depsgraph(context->bmain, scene, view_layer);
|
|
BKE_scene_graph_update_for_newframe(depsgraph);
|
|
Object *camera_eval = DEG_get_evaluated(depsgraph, camera);
|
|
Scene *scene_eval = DEG_get_evaluated_scene(depsgraph);
|
|
ibuf = view3d_fn(
|
|
/* set for OpenGL render (nullptr when scrubbing) */
|
|
depsgraph,
|
|
scene_eval,
|
|
&context->scene->display.shading,
|
|
eDrawType(context->scene->r.seq_prev_type),
|
|
camera_eval,
|
|
width,
|
|
height,
|
|
IB_byte_data,
|
|
eV3DOffscreenDrawFlag(draw_flags),
|
|
scene->r.alphamode,
|
|
viewname,
|
|
context->gpu_offscreen,
|
|
context->gpu_viewport,
|
|
err_out);
|
|
if (ibuf == nullptr) {
|
|
fprintf(stderr, "seq_render_scene_strip failed to get opengl buffer: %s\n", err_out);
|
|
}
|
|
}
|
|
else {
|
|
Render *re = RE_GetSceneRender(scene);
|
|
const int totviews = BKE_scene_multiview_num_views_get(&scene->r);
|
|
ImBuf **ibufs_arr;
|
|
|
|
/*
|
|
* XXX: this if can be removed when sequence preview rendering uses the job system
|
|
*
|
|
* Disable rendered preview for sequencer while rendering - invoked render job will
|
|
* conflict with already running render
|
|
*
|
|
* When rendering from command line renderer is called from main thread, in this
|
|
* case it's always safe to render scene here
|
|
*/
|
|
|
|
if (is_preview && (is_rendering && !G.background)) {
|
|
goto finally;
|
|
}
|
|
|
|
ibufs_arr = MEM_calloc_arrayN<ImBuf *>(totviews, "Sequence Image Views Imbufs");
|
|
|
|
if (re == nullptr) {
|
|
re = RE_NewSceneRender(scene);
|
|
}
|
|
|
|
const float subframe = frame - floorf(frame);
|
|
|
|
RE_RenderFrame(re,
|
|
context->bmain,
|
|
scene,
|
|
have_comp ? nullptr : view_layer,
|
|
camera,
|
|
floorf(frame),
|
|
subframe,
|
|
false);
|
|
|
|
/* restore previous state after it was toggled on & off by RE_RenderFrame */
|
|
G.is_rendering = is_rendering;
|
|
|
|
for (int view_id = 0; view_id < totviews; view_id++) {
|
|
RenderData localcontext = *context;
|
|
RenderResult rres;
|
|
|
|
localcontext.view_id = view_id;
|
|
|
|
RE_AcquireResultImage(re, &rres, view_id);
|
|
|
|
/* TODO: Share the pixel data with the original image buffer from the render result using
|
|
* implicit sharing. */
|
|
if (rres.ibuf && rres.ibuf->float_buffer.data) {
|
|
ibufs_arr[view_id] = IMB_allocImBuf(rres.rectx, rres.recty, 32, IB_float_data);
|
|
memcpy(ibufs_arr[view_id]->float_buffer.data,
|
|
rres.ibuf->float_buffer.data,
|
|
sizeof(float[4]) * rres.rectx * rres.recty);
|
|
|
|
/* float buffers in the sequencer are not linear */
|
|
seq_imbuf_to_sequencer_space(context->scene, ibufs_arr[view_id], false);
|
|
}
|
|
else if (rres.ibuf && rres.ibuf->byte_buffer.data) {
|
|
ibufs_arr[view_id] = IMB_allocImBuf(rres.rectx, rres.recty, 32, IB_byte_data);
|
|
memcpy(ibufs_arr[view_id]->byte_buffer.data,
|
|
rres.ibuf->byte_buffer.data,
|
|
4 * rres.rectx * rres.recty);
|
|
}
|
|
else {
|
|
ibufs_arr[view_id] = IMB_allocImBuf(rres.rectx, rres.recty, 32, IB_byte_data);
|
|
}
|
|
|
|
if (view_id != context->view_id) {
|
|
Scene *orig_scene = prefetch_get_original_scene(context);
|
|
if (orig_scene->ed->cache_flag & SEQ_CACHE_STORE_RAW) {
|
|
source_image_cache_put(&localcontext, strip, timeline_frame, ibufs_arr[view_id]);
|
|
}
|
|
}
|
|
|
|
RE_ReleaseResultImage(re);
|
|
}
|
|
|
|
/* return the original requested ImBuf */
|
|
ibuf = ibufs_arr[context->view_id];
|
|
|
|
/* "remove" the others (decrease their refcount) */
|
|
for (int view_id = 0; view_id < totviews; view_id++) {
|
|
if (ibufs_arr[view_id] != ibuf) {
|
|
IMB_freeImBuf(ibufs_arr[view_id]);
|
|
}
|
|
}
|
|
MEM_freeN(ibufs_arr);
|
|
}
|
|
|
|
finally:
|
|
/* restore */
|
|
scene->r.scemode = orig_data.scemode;
|
|
scene->r.cfra = orig_data.timeline_frame;
|
|
scene->r.subframe = orig_data.subframe;
|
|
|
|
if (is_frame_update && (depsgraph != nullptr)) {
|
|
BKE_scene_graph_update_for_newframe(depsgraph);
|
|
}
|
|
|
|
scene->r.mode &= orig_data.mode | ~R_NO_CAMERA_SWITCH;
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
/**
|
|
* Used for meta-strips & scenes with #SEQ_SCENE_STRIPS flag set.
|
|
*/
|
|
static ImBuf *do_render_strip_seqbase(const RenderData *context,
|
|
SeqRenderState *state,
|
|
Strip *strip,
|
|
float frame_index)
|
|
{
|
|
ImBuf *ibuf = nullptr;
|
|
ListBase *seqbase = nullptr;
|
|
ListBase *channels = nullptr;
|
|
int offset;
|
|
|
|
seqbase = get_seqbase_from_strip(strip, &channels, &offset);
|
|
|
|
if (seqbase && !BLI_listbase_is_empty(seqbase)) {
|
|
|
|
frame_index += offset;
|
|
|
|
if (strip->flag & SEQ_SCENE_STRIPS && strip->scene) {
|
|
BKE_animsys_evaluate_all_animation(context->bmain, context->depsgraph, frame_index);
|
|
}
|
|
|
|
intra_frame_cache_set_cur_frame(
|
|
context->scene, frame_index, context->view_id, context->rectx, context->recty);
|
|
ibuf = seq_render_strip_stack(context,
|
|
state,
|
|
channels,
|
|
seqbase,
|
|
/* scene strips don't have their start taken into account */
|
|
frame_index,
|
|
0);
|
|
}
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Strip Stack Rendering Functions
|
|
* \{ */
|
|
|
|
static ImBuf *do_render_strip_uncached(const RenderData *context,
|
|
SeqRenderState *state,
|
|
Strip *strip,
|
|
float timeline_frame,
|
|
bool *r_is_proxy_image)
|
|
{
|
|
ImBuf *ibuf = nullptr;
|
|
float frame_index = give_frame_index(context->scene, strip, timeline_frame);
|
|
if (strip->type == STRIP_TYPE_META) {
|
|
ibuf = do_render_strip_seqbase(context, state, strip, frame_index);
|
|
}
|
|
else if (strip->type == STRIP_TYPE_SCENE) {
|
|
if (strip->flag & SEQ_SCENE_STRIPS) {
|
|
if (strip->scene && (context->scene != strip->scene)) {
|
|
/* recursive check */
|
|
if (BLI_linklist_index(state->scene_parents, strip->scene) == -1) {
|
|
LinkNode scene_parent{};
|
|
scene_parent.next = state->scene_parents;
|
|
scene_parent.link = strip->scene;
|
|
state->scene_parents = &scene_parent;
|
|
/* end check */
|
|
|
|
/* Use the Scene sequence-strip's scene for the context when rendering the
|
|
* scene's sequences (necessary for multi-cam selector among others). */
|
|
RenderData local_context = *context;
|
|
local_context.scene = strip->scene;
|
|
local_context.skip_cache = true;
|
|
|
|
ibuf = do_render_strip_seqbase(&local_context, state, strip, frame_index);
|
|
|
|
/* step back in the list */
|
|
state->scene_parents = state->scene_parents->next;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* scene can be nullptr after deletions */
|
|
ibuf = seq_render_scene_strip(context, strip, frame_index, timeline_frame);
|
|
}
|
|
}
|
|
else if (strip->is_effect()) {
|
|
ibuf = seq_render_effect_strip_impl(context, state, strip, timeline_frame);
|
|
}
|
|
else if (strip->type == STRIP_TYPE_IMAGE) {
|
|
ibuf = seq_render_image_strip(context, strip, timeline_frame, r_is_proxy_image);
|
|
}
|
|
else if (strip->type == STRIP_TYPE_MOVIE) {
|
|
ibuf = seq_render_movie_strip(context, strip, timeline_frame, r_is_proxy_image);
|
|
}
|
|
else if (strip->type == STRIP_TYPE_MOVIECLIP) {
|
|
ibuf = seq_render_movieclip_strip(
|
|
context, strip, round_fl_to_int(frame_index), r_is_proxy_image);
|
|
|
|
if (ibuf) {
|
|
/* duplicate frame so movie cache wouldn't be confused by sequencer's stuff */
|
|
ImBuf *i = IMB_dupImBuf(ibuf);
|
|
IMB_freeImBuf(ibuf);
|
|
ibuf = i;
|
|
|
|
if (ibuf->float_buffer.data) {
|
|
seq_imbuf_to_sequencer_space(context->scene, ibuf, false);
|
|
}
|
|
}
|
|
}
|
|
else if (strip->type == STRIP_TYPE_MASK) {
|
|
/* ibuf is always new */
|
|
ibuf = seq_render_mask_strip(context, strip, frame_index);
|
|
}
|
|
|
|
if (ibuf) {
|
|
seq_imbuf_assign_spaces(context->scene, ibuf);
|
|
}
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
ImBuf *seq_render_strip(const RenderData *context,
|
|
SeqRenderState *state,
|
|
Strip *strip,
|
|
float timeline_frame)
|
|
{
|
|
bool use_preprocess = false;
|
|
bool is_proxy_image = false;
|
|
|
|
ImBuf *ibuf = intra_frame_cache_get_preprocessed(context->scene, strip);
|
|
if (ibuf != nullptr) {
|
|
return ibuf;
|
|
}
|
|
|
|
/* Proxies are not stored in cache. */
|
|
if (!can_use_proxy(context, strip, rendersize_to_proxysize(context->preview_render_size))) {
|
|
ibuf = seq::source_image_cache_get(context, strip, timeline_frame);
|
|
}
|
|
|
|
if (ibuf == nullptr) {
|
|
ibuf = do_render_strip_uncached(context, state, strip, timeline_frame, &is_proxy_image);
|
|
}
|
|
|
|
if (ibuf) {
|
|
use_preprocess = seq_input_have_to_preprocess(context, strip, timeline_frame);
|
|
ibuf = seq_render_preprocess_ibuf(
|
|
context, strip, ibuf, timeline_frame, use_preprocess, is_proxy_image);
|
|
intra_frame_cache_put_preprocessed(context->scene, strip, ibuf);
|
|
}
|
|
|
|
if (ibuf == nullptr) {
|
|
ibuf = IMB_allocImBuf(context->rectx, context->recty, 32, IB_byte_data);
|
|
seq_imbuf_assign_spaces(context->scene, ibuf);
|
|
}
|
|
|
|
return ibuf;
|
|
}
|
|
|
|
static bool seq_must_swap_input_in_blend_mode(Strip *strip)
|
|
{
|
|
return ELEM(strip->blend_mode, STRIP_BLEND_ALPHAOVER, STRIP_BLEND_ALPHAUNDER);
|
|
}
|
|
|
|
static StripEarlyOut strip_get_early_out_for_blend_mode(Strip *strip)
|
|
{
|
|
EffectHandle sh = strip_blend_mode_handle_get(strip);
|
|
float fac = strip->blend_opacity / 100.0f;
|
|
StripEarlyOut early_out = sh.early_out(strip, fac);
|
|
|
|
if (ELEM(early_out, StripEarlyOut::DoEffect, StripEarlyOut::NoInput)) {
|
|
return early_out;
|
|
}
|
|
|
|
if (seq_must_swap_input_in_blend_mode(strip)) {
|
|
if (early_out == StripEarlyOut::UseInput2) {
|
|
return StripEarlyOut::UseInput1;
|
|
}
|
|
if (early_out == StripEarlyOut::UseInput1) {
|
|
return StripEarlyOut::UseInput2;
|
|
}
|
|
}
|
|
return early_out;
|
|
}
|
|
|
|
static ImBuf *seq_render_strip_stack_apply_effect(
|
|
|
|
const RenderData *context,
|
|
SeqRenderState *state,
|
|
Strip *strip,
|
|
float timeline_frame,
|
|
ImBuf *ibuf1,
|
|
ImBuf *ibuf2)
|
|
{
|
|
ImBuf *out;
|
|
EffectHandle sh = strip_blend_mode_handle_get(strip);
|
|
BLI_assert(sh.execute != nullptr);
|
|
float fac = strip->blend_opacity / 100.0f;
|
|
int swap_input = seq_must_swap_input_in_blend_mode(strip);
|
|
|
|
if (swap_input) {
|
|
out = sh.execute(context, state, strip, timeline_frame, fac, ibuf2, ibuf1);
|
|
}
|
|
else {
|
|
out = sh.execute(context, state, strip, timeline_frame, fac, ibuf1, ibuf2);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
static bool is_opaque_alpha_over(const Strip *strip)
|
|
{
|
|
if (strip->blend_mode != STRIP_BLEND_ALPHAOVER) {
|
|
return false;
|
|
}
|
|
if (strip->blend_opacity < 100.0f) {
|
|
return false;
|
|
}
|
|
if (strip->mul < 1.0f && (strip->flag & SEQ_MULTIPLY_ALPHA) != 0) {
|
|
return false;
|
|
}
|
|
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
|
|
/* Assume result is not opaque if there is an enabled Mask modifier. */
|
|
if ((smd->flag & STRIP_MODIFIER_FLAG_MUTE) == 0 && smd->type == eSeqModifierType_Mask) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static ImBuf *seq_render_strip_stack(const RenderData *context,
|
|
SeqRenderState *state,
|
|
ListBase *channels,
|
|
ListBase *seqbasep,
|
|
float timeline_frame,
|
|
int chanshown)
|
|
{
|
|
Vector<Strip *> strips = seq_shown_strips_get(
|
|
context->scene, channels, seqbasep, timeline_frame, chanshown);
|
|
if (strips.is_empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
OpaqueQuadTracker opaques;
|
|
|
|
int64_t i;
|
|
ImBuf *out = nullptr;
|
|
for (i = strips.size() - 1; i >= 0; i--) {
|
|
Strip *strip = strips[i];
|
|
|
|
out = intra_frame_cache_get_composite(context->scene, strip);
|
|
if (out) {
|
|
break;
|
|
}
|
|
if (strip->blend_mode == STRIP_BLEND_REPLACE) {
|
|
out = seq_render_strip(context, state, strip, timeline_frame);
|
|
break;
|
|
}
|
|
|
|
StripEarlyOut early_out = strip_get_early_out_for_blend_mode(strip);
|
|
|
|
if (early_out == StripEarlyOut::DoEffect && opaques.is_occluded(context, strip, i)) {
|
|
early_out = StripEarlyOut::UseInput1;
|
|
}
|
|
|
|
/* "Alpha over" is default for all strips, and it can be optimized in some cases:
|
|
* - If the whole image has no transparency, there's no need to do actual blending.
|
|
* - Likewise, if we are at the bottom of the stack; the input can be used as-is.
|
|
* - If we are rendering a strip that is known to be opaque, we mark it as an occluder,
|
|
* so that strips below can check if they are completely hidden. */
|
|
if (out == nullptr && early_out == StripEarlyOut::DoEffect && is_opaque_alpha_over(strip)) {
|
|
ImBuf *test = seq_render_strip(context, state, strip, timeline_frame);
|
|
if (ELEM(test->planes, R_IMF_PLANES_BW, R_IMF_PLANES_RGB) || i == 0) {
|
|
early_out = StripEarlyOut::UseInput2;
|
|
}
|
|
else {
|
|
early_out = StripEarlyOut::DoEffect;
|
|
}
|
|
/* Free the image. It is stored in cache, so this doesn't affect performance. */
|
|
IMB_freeImBuf(test);
|
|
|
|
/* Check whether the raw (before preprocessing, which can add alpha) strip content
|
|
* was opaque. */
|
|
ImBuf *ibuf_raw = seq::source_image_cache_get(context, strip, timeline_frame);
|
|
if (ibuf_raw != nullptr) {
|
|
if (ibuf_raw->planes != R_IMF_PLANES_RGBA) {
|
|
opaques.add_occluder(context, strip, i);
|
|
}
|
|
IMB_freeImBuf(ibuf_raw);
|
|
}
|
|
}
|
|
|
|
switch (early_out) {
|
|
case StripEarlyOut::NoInput:
|
|
case StripEarlyOut::UseInput2:
|
|
out = seq_render_strip(context, state, strip, timeline_frame);
|
|
break;
|
|
case StripEarlyOut::UseInput1:
|
|
if (i == 0) {
|
|
out = IMB_allocImBuf(context->rectx, context->recty, 32, IB_byte_data);
|
|
seq_imbuf_assign_spaces(context->scene, out);
|
|
}
|
|
break;
|
|
case StripEarlyOut::DoEffect:
|
|
if (i == 0) {
|
|
/* This is an effect at the bottom of the stack, so one of the inputs does not exist yet:
|
|
* create one that is transparent black. Extra optimization for an alpha over strip at
|
|
* the bottom, we can just return it instead of blending with black. */
|
|
ImBuf *ibuf2 = seq_render_strip(context, state, strip, timeline_frame);
|
|
const bool use_float = ibuf2 && ibuf2->float_buffer.data;
|
|
ImBuf *ibuf1 = IMB_allocImBuf(
|
|
context->rectx, context->recty, 32, use_float ? IB_float_data : IB_byte_data);
|
|
seq_imbuf_assign_spaces(context->scene, ibuf1);
|
|
|
|
out = seq_render_strip_stack_apply_effect(
|
|
context, state, strip, timeline_frame, ibuf1, ibuf2);
|
|
IMB_metadata_copy(out, ibuf2);
|
|
|
|
intra_frame_cache_put_composite(context->scene, strip, out);
|
|
|
|
IMB_freeImBuf(ibuf1);
|
|
IMB_freeImBuf(ibuf2);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (out) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
for (; i < strips.size(); i++) {
|
|
Strip *strip = strips[i];
|
|
|
|
if (opaques.is_occluded(context, strip, i)) {
|
|
continue;
|
|
}
|
|
|
|
if (strip_get_early_out_for_blend_mode(strip) == StripEarlyOut::DoEffect) {
|
|
ImBuf *ibuf1 = out;
|
|
ImBuf *ibuf2 = seq_render_strip(context, state, strip, timeline_frame);
|
|
|
|
out = seq_render_strip_stack_apply_effect(
|
|
context, state, strip, timeline_frame, ibuf1, ibuf2);
|
|
|
|
IMB_freeImBuf(ibuf1);
|
|
IMB_freeImBuf(ibuf2);
|
|
}
|
|
|
|
intra_frame_cache_put_composite(context->scene, strip, out);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
ImBuf *render_give_ibuf(const RenderData *context, float timeline_frame, int chanshown)
|
|
{
|
|
Scene *scene = context->scene;
|
|
Editing *ed = editing_get(scene);
|
|
ListBase *seqbasep;
|
|
ListBase *channels;
|
|
|
|
if (ed == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
if ((chanshown < 0) && !BLI_listbase_is_empty(&ed->metastack)) {
|
|
int count = BLI_listbase_count(&ed->metastack);
|
|
count = max_ii(count + chanshown, 0);
|
|
MetaStack *ms = static_cast<MetaStack *>(BLI_findlink(&ed->metastack, count));
|
|
seqbasep = &ms->old_strip->seqbase;
|
|
channels = &ms->old_strip->channels;
|
|
chanshown = 0;
|
|
}
|
|
else {
|
|
seqbasep = ed->current_strips();
|
|
channels = ed->current_channels();
|
|
}
|
|
|
|
intra_frame_cache_set_cur_frame(
|
|
scene, timeline_frame, context->view_id, context->rectx, context->recty);
|
|
|
|
Scene *orig_scene = prefetch_get_original_scene(context);
|
|
ImBuf *out = nullptr;
|
|
if (!context->skip_cache && !context->is_proxy_render) {
|
|
out = final_image_cache_get(orig_scene, timeline_frame, context->view_id, chanshown);
|
|
}
|
|
|
|
Vector<Strip *> strips = seq_shown_strips_get(
|
|
scene, channels, seqbasep, timeline_frame, chanshown);
|
|
|
|
/* Make sure we only keep the `anim` data for strips that are in view. */
|
|
relations_free_all_anim_ibufs(context->scene, timeline_frame);
|
|
|
|
SeqRenderState state;
|
|
|
|
if (!strips.is_empty() && !out) {
|
|
std::scoped_lock lock(seq_render_mutex);
|
|
/* Try to make space before we add any new frames to the cache if it is full.
|
|
* If we do this after we have added the new cache, we risk removing what we just added. */
|
|
evict_caches_if_full(orig_scene);
|
|
|
|
out = seq_render_strip_stack(context, &state, channels, seqbasep, timeline_frame, chanshown);
|
|
|
|
if (out && (orig_scene->ed->cache_flag & SEQ_CACHE_STORE_FINAL_OUT) && !context->skip_cache &&
|
|
!context->is_proxy_render)
|
|
{
|
|
final_image_cache_put(orig_scene, timeline_frame, context->view_id, chanshown, out);
|
|
}
|
|
}
|
|
|
|
seq_prefetch_start(context, timeline_frame);
|
|
|
|
return out;
|
|
}
|
|
|
|
ImBuf *seq_render_give_ibuf_seqbase(const RenderData *context,
|
|
SeqRenderState *state,
|
|
float timeline_frame,
|
|
int chan_shown,
|
|
ListBase *channels,
|
|
ListBase *seqbasep)
|
|
{
|
|
|
|
return seq_render_strip_stack(context, state, channels, seqbasep, timeline_frame, chan_shown);
|
|
}
|
|
|
|
ImBuf *render_give_ibuf_direct(const RenderData *context, float timeline_frame, Strip *strip)
|
|
{
|
|
SeqRenderState state;
|
|
|
|
intra_frame_cache_set_cur_frame(
|
|
context->scene, timeline_frame, context->view_id, context->rectx, context->recty);
|
|
ImBuf *ibuf = seq_render_strip(context, &state, strip, timeline_frame);
|
|
return ibuf;
|
|
}
|
|
|
|
bool render_is_muted(const ListBase *channels, const Strip *strip)
|
|
{
|
|
SeqTimelineChannel *channel = channel_get_by_index(channels, strip->channel);
|
|
return strip->flag & SEQ_MUTE || channel_is_muted(channel);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
float get_render_scale_factor(eSpaceSeq_Proxy_RenderSize render_size, short scene_render_scale)
|
|
{
|
|
return render_size == SEQ_RENDER_SIZE_SCENE ? scene_render_scale / 100.0f :
|
|
rendersize_to_scale_factor(render_size);
|
|
}
|
|
|
|
float get_render_scale_factor(const RenderData &context)
|
|
{
|
|
return get_render_scale_factor(context.preview_render_size, context.scene->r.size);
|
|
}
|
|
|
|
} // namespace blender::seq
|