VSE: Compositor based effects/transitions

Implements compositor-based Effects in VSE (#150636). Compositor
effects can be of all currently existing effect kinds:

Two input effects (transitions):
- Similar built-in effects: Cross fade, Wipe.
- Created on two selected strips.
- When creating a node group for them, the group input has
  "Input 1", "Input 2" and a float "Effect Fader" (0..1 over strip
  range) inputs.
- Expected use case: transitions or fancy combination of two strips.

Single input effects:
- Similar built-in effects: Blur, Glow.
- Created on one selected strip.
- When creating a node group for them, the group input has "Input"
  and a float "Effect Fader" (0..1 over strip range) inputs.
- Expected use case: post-processing of input in screen-space
  (different from strip modifiers, which operate in local space
  before strip transform is applied).

Zero-input effects:
- Similar built-in effects: Solid Color, Text.
- Created when no strips are selected.
- When creating a node group for them, the group input only has a
  float "Effect Fader" (0..1 over strip range) input.
- Expected use case: gradients, procedural textures.

General notes:

Menu item to create a Compositor strip is in top level menu; next to
Adjustment strip. It is not under "effect" or "transition" since
compositor strip can be 1 or 0 inputs.

Properties inspector for compositor strips displays their inputs (as
for any other effect), and the compositor node tree selector.

Note that this PR does not change anything in how effect inputs are
selected within VSE - the new Compositor effect works just like any
other effect that already exists. You create a new strip with none,
one or two strips selected, and that are the inputs of the effect.
These can be swapped (for two input effects) or reassigned later with
the usual operators.

Implementation notes:

The actual compositor effect code is very small; most of the work
inside the PR was:
- Changes across VSE codebase to allow non-fixed amount of inputs.
  Since Compositor strip type can have either 0, 1 or 2 inputs now.
- Various places related to depsgraph & co, where it was handling
  strip Compositor Modifiers; very similar code has been added to
  also handle Compositor Effects.
- Just like compositor modifier, the compositor effect right now
  only works on the CPU. Getting both to use GPU is future task.

Pull Request: https://projects.blender.org/blender/blender/pulls/150694
This commit is contained in:
Aras Pranckevicius 2026-02-17 19:19:34 +01:00 • committed by Aras Pranckevicius
parent a66c520538
commit 04aea5a4e9
52 changed files with 784 additions and 289 deletions

View file

@ -62,7 +62,7 @@ class STRIP_PT_strip(StripButtonsPanel, Panel):
}:
icon_header = 'SHADERFX'
elif strip_type in {
'CROSS', 'GAMMA_CROSS', 'WIPE',
'CROSS', 'GAMMA_CROSS', 'WIPE', 'COMPOSITOR',
}:
icon_header = 'ARROW_LEFTRIGHT'
elif strip_type == 'SCENE':
@ -146,6 +146,7 @@ class STRIP_PT_effect(StripButtonsPanel, Panel):
'ALPHA_UNDER',
'CROSS',
'GAMMA_CROSS',
'COMPOSITOR',
'MULTIPLY',
'WIPE',
'GLOW',
@ -165,6 +166,11 @@ class STRIP_PT_effect(StripButtonsPanel, Panel):
layout.active = not strip.mute
strip_type = strip.type
if strip_type == 'COMPOSITOR':
layout.template_ID(strip, "node_group", new="node.new_compositor_sequencer_node_group")
if strip.input_count > 0:
col = layout.column()
row = col.row()
@ -176,8 +182,6 @@ class STRIP_PT_effect(StripButtonsPanel, Panel):
row.prop(strip, "input_2")
row.operator("sequencer.swap_inputs", text="", icon='SORT_DESC')
strip_type = strip.type
if strip_type == 'COLOR':
layout.template_color_picker(strip, "color", value_slider=True, cubic=True)
layout.prop(strip, "color", text="")
@ -261,7 +265,7 @@ class STRIP_PT_effect(StripButtonsPanel, Panel):
layout.prop(strip, "wrap_width", text="Wrap Width")
col = layout.column(align=True)
if strip_type in {'CROSS', 'GAMMA_CROSS', 'WIPE', 'ALPHA_OVER', 'ALPHA_UNDER'}:
if strip_type in {'CROSS', 'GAMMA_CROSS', 'WIPE', 'ALPHA_OVER', 'ALPHA_UNDER', 'COMPOSITOR'}:
col.prop(strip, "use_default_fade", text="Default Fade")
if not strip.use_default_fade:
col.prop(strip, "effect_fader", text="Effect Fader")
@ -965,7 +969,7 @@ class STRIP_PT_adjust_video(StripButtonsPanel, Panel):
return strip.type in {
'MOVIE', 'IMAGE', 'SCENE', 'MOVIECLIP', 'MASK',
'META', 'ADD', 'SUBTRACT', 'ALPHA_OVER',
'ALPHA_UNDER', 'CROSS', 'GAMMA_CROSS', 'MULTIPLY',
'ALPHA_UNDER', 'CROSS', 'GAMMA_CROSS', 'MULTIPLY', 'COMPOSITOR',
'WIPE', 'GLOW', 'COLOR', 'MULTICAM', 'SPEED', 'ADJUSTMENT', 'COLORMIX',
}
@ -997,7 +1001,7 @@ class STRIP_PT_adjust_color(StripButtonsPanel, Panel):
return strip.type in {
'MOVIE', 'IMAGE', 'SCENE', 'MOVIECLIP', 'MASK',
'META', 'ADD', 'SUBTRACT', 'ALPHA_OVER',
'ALPHA_UNDER', 'CROSS', 'GAMMA_CROSS', 'MULTIPLY',
'ALPHA_UNDER', 'CROSS', 'GAMMA_CROSS', 'MULTIPLY', 'COMPOSITOR',
'WIPE', 'GLOW', 'COLOR', 'MULTICAM', 'SPEED', 'ADJUSTMENT', 'COLORMIX',
}

View file

@ -158,20 +158,27 @@ class NODE_HT_header(Header):
sequencer_scene = context.workspace.sequencer_scene
sequencer_editor = sequencer_scene.sequence_editor if sequencer_scene else None
active_strip = sequencer_editor.active_strip if sequencer_editor else None
active_modifier = active_strip.modifiers.active if active_strip else None
is_compositor_modifier_active = active_modifier and active_modifier.type == 'COMPOSITOR'
if is_compositor_modifier_active and not snode.pin:
if active_modifier.node_group:
row.template_ID(active_modifier,
"node_group",
new="node.duplicate_compositing_modifier_node_group")
else:
row.template_ID(
active_modifier,
if active_strip:
active_modifier = active_strip.modifiers.active
is_compositor_modifier_active = active_modifier and active_modifier.type == 'COMPOSITOR'
is_compositor_effect_active = active_strip.type == 'COMPOSITOR'
if is_compositor_effect_active and not snode.pin:
row.template_ID( # @TODO: duplicate operator
active_strip,
"node_group",
new="node.new_compositor_sequencer_node_group")
elif active_strip and active_strip.type != 'SOUND':
row.template_ID(snode, "node_tree", new="node.new_compositor_sequencer_node_group")
elif is_compositor_modifier_active and not snode.pin:
if active_modifier.node_group:
row.template_ID(active_modifier,
"node_group",
new="node.duplicate_compositing_modifier_node_group")
else:
row.template_ID(
active_modifier,
"node_group",
new="node.new_compositor_sequencer_node_group")
elif active_strip.type != 'SOUND':
row.template_ID(snode, "node_tree", new="node.new_compositor_sequencer_node_group")
elif snode.tree_type == 'GeometryNodeTree':
layout.prop(snode, "node_tree_sub_type", text="")

View file

@ -628,7 +628,7 @@ class SEQUENCER_MT_change(Menu):
layout.operator_context = 'INVOKE_DEFAULT'
if strip and strip.type in {
'CROSS', 'ADD', 'SUBTRACT', 'ALPHA_OVER', 'ALPHA_UNDER',
'GAMMA_CROSS', 'MULTIPLY', 'WIPE', 'GLOW',
'GAMMA_CROSS', 'COMPOSITOR', 'MULTIPLY', 'WIPE', 'GLOW',
'SPEED', 'MULTICAM', 'ADJUSTMENT', 'GAUSSIAN_BLUR',
}:
layout.menu("SEQUENCER_MT_strip_effect_change")
@ -727,14 +727,16 @@ class SEQUENCER_MT_add(Menu):
layout.operator("sequencer.effect_strip_add", text="Text", icon='FONT_DATA').type = 'TEXT'
layout.separator()
total, nonsound = selected_strips_count(context)
layout.operator("sequencer.effect_strip_add", text="Adjustment Layer", icon='COLOR').type = 'ADJUSTMENT'
col = layout.column()
col.operator("sequencer.effect_strip_add", text="Compositor", icon='NODE_COMPOSITING').type = 'COMPOSITOR'
col.enabled = nonsound < 3
layout.operator_context = 'INVOKE_DEFAULT'
layout.menu("SEQUENCER_MT_add_effect", icon='SHADERFX')
total, nonsound = selected_strips_count(context)
col = layout.column()
col.menu("SEQUENCER_MT_add_transitions", icon='ARROW_LEFTRIGHT')
# Enable for video transitions or sound cross-fade.
@ -1023,6 +1025,10 @@ class SEQUENCER_MT_strip_effect_change(Menu):
strip = context.active_strip
col = layout.column()
col.operator("sequencer.change_effect_type", text="Compositor").type = 'COMPOSITOR'
layout.separator()
col = layout.column()
col.operator("sequencer.change_effect_type", text="Adjustment Layer").type = 'ADJUSTMENT'
col.operator("sequencer.change_effect_type", text="Multicam Selector").type = 'MULTICAM'
@ -1163,7 +1169,7 @@ class SEQUENCER_MT_strip(Menu):
if strip_type in {
'CROSS', 'ADD', 'SUBTRACT', 'ALPHA_OVER', 'ALPHA_UNDER',
'GAMMA_CROSS', 'MULTIPLY', 'WIPE', 'GLOW',
'GAMMA_CROSS', 'COMPOSITOR', 'MULTIPLY', 'WIPE', 'GLOW',
'SPEED', 'MULTICAM', 'ADJUSTMENT', 'GAUSSIAN_BLUR',
}:
layout.separator()
@ -1346,7 +1352,7 @@ class SEQUENCER_MT_context_menu(Menu):
if strip_type in {
'CROSS', 'ADD', 'SUBTRACT', 'ALPHA_OVER', 'ALPHA_UNDER',
'GAMMA_CROSS', 'MULTIPLY', 'WIPE', 'GLOW',
'GAMMA_CROSS', 'COMPOSITOR', 'MULTIPLY', 'WIPE', 'GLOW',
'SPEED', 'MULTICAM', 'ADJUSTMENT', 'GAUSSIAN_BLUR',
}:
layout.separator()

View file

@ -829,6 +829,10 @@ static bool strip_foreach_member_id_cb(Strip *strip, void *user_data)
IDP_foreach_property(strip->system_properties, IDP_TYPE_FILTER_ID, [&](IDProperty *prop) {
BKE_lib_query_idpropertiesForeachIDLink_callback(prop, data);
});
if (strip->type == STRIP_TYPE_COMPOSITOR && strip->effectdata) {
CompositorEffectVars *comp_data = static_cast<CompositorEffectVars *>(strip->effectdata);
FOREACHID_PROCESS_IDSUPER(data, comp_data->node_group, IDWALK_CB_USER);
}
/* TODO: This could use `seq::foreach_strip_modifier_id`, but because `FOREACHID_PROCESS_IDSUPER`
* doesn't take IDs but "ID supers", it makes it a bit more cumbersome. */
for (StripModifierData &smd : strip->modifiers) {

View file

@ -96,7 +96,6 @@
#include "readfile.hh"
#include "SEQ_channels.hh"
#include "SEQ_effects.hh"
#include "SEQ_iterator.hh"
#include "SEQ_retiming.hh"
#include "SEQ_sequencer.hh"
@ -1765,9 +1764,7 @@ static bool version_merge_still_offsets(Strip *strip, void * /*user_data*/)
static bool version_set_seq_single_frame_content(Strip *strip, void * /*user_data*/)
{
if ((strip->len == 1) && (strip->type == STRIP_TYPE_IMAGE ||
(strip->is_effect() && seq::effect_get_num_inputs(strip->type) == 0)))
{
if ((strip->len == 1) && (strip->type == STRIP_TYPE_IMAGE || !strip->is_effect_with_inputs())) {
strip->flag |= SEQ_SINGLE_FRAME_CONTENT;
}
return true;

View file

@ -2354,6 +2354,13 @@ static bool strip_node_build_cb(Strip *strip, void *user_data)
ViewLayer *sequence_view_layer = BKE_view_layer_default_render(strip->scene);
nb->build_scene_speakers(strip->scene, sequence_view_layer);
}
if (strip->type == STRIP_TYPE_COMPOSITOR && strip->effectdata) {
CompositorEffectVars *comp_data = static_cast<CompositorEffectVars *>(strip->effectdata);
if (comp_data->node_group) {
nb->build_nodetree(comp_data->node_group);
}
}
for (StripModifierData &modifier : strip->modifiers) {
if (modifier.type != eSeqModifierType_Compositor) {
continue;

View file

@ -3467,6 +3467,15 @@ static bool strip_build_prop_cb(Strip *strip, void *user_data)
ViewLayer *sequence_view_layer = BKE_view_layer_default_render(strip->scene);
cd->builder->build_scene_speakers(strip->scene, sequence_view_layer);
}
if (strip->type == STRIP_TYPE_COMPOSITOR && strip->effectdata) {
const CompositorEffectVars *comp_data = static_cast<CompositorEffectVars *>(strip->effectdata);
if (comp_data->node_group) {
cd->builder->build_nodetree(comp_data->node_group);
OperationKey node_tree_key(
&comp_data->node_group->id, NodeType::NTREE_OUTPUT, OperationCode::NTREE_OUTPUT);
cd->builder->add_relation(node_tree_key, cd->sequencer_key, "Effect's Node Group");
}
}
for (StripModifierData &modifier : strip->modifiers) {
if (modifier.type != eSeqModifierType_Compositor) {
continue;

View file

@ -3447,10 +3447,16 @@ static size_t animdata_filter_dopesheet_scene(bAnimContext *ac,
ac, &tmp_data, reinterpret_cast<ID *>(sce), ntree, filter_mode);
}
/* Strip modifier node trees. */
/* VSE strip node trees. */
if (ed && !(ac->filters.flag & ADS_FILTER_NONTREE)) {
VectorSet<ID *> node_trees;
seq::foreach_strip(&ed->seqbase, [&](Strip *strip) {
if (strip->type == STRIP_TYPE_COMPOSITOR && strip->effectdata) {
CompositorEffectVars *comp_data = static_cast<CompositorEffectVars *>(strip->effectdata);
if (comp_data->node_group) {
node_trees.add(reinterpret_cast<ID *>(comp_data->node_group));
}
}
seq::foreach_strip_modifier_id(strip, [&](ID *id) {
if (GS(id->name) == ID_NT) {
node_trees.add(id);

View file

@ -327,11 +327,11 @@ static void update_sequencer(const DEGEditorUpdateContext *update_ctx, Main *bma
}
}
/* Invalidate cache for strips that use this compositing tree as a modifier. */
/* Invalidate cache for strips that use this compositing tree. */
if (GS(id->name) == ID_NT) {
const bNodeTree *node_tree = reinterpret_cast<const bNodeTree *>(id);
if (node_tree->type == NTREE_COMPOSIT) {
seq::relations_invalidate_compositor_modifiers(bmain, node_tree);
seq::relations_invalidate_compositor_users(bmain, node_tree);
}
}
}

View file

@ -1850,15 +1850,36 @@ void NODE_OT_duplicate_compositing_modifier_node_group(wmOperatorType *ot)
/** \name New Compositor Sequencer Node Group Operator
* \{ */
static void initialize_compositor_sequencer_node_group(const bContext *C, bNodeTree &ntree)
static void initialize_compositor_sequencer_node_group(const bContext *C,
bNodeTree &ntree,
bool for_effect,
int effect_input_count)
{
BLI_assert(ntree.type == NTREE_COMPOSIT);
BLI_assert(BLI_listbase_count(&ntree.nodes) == 0);
ntree.tree_interface.add_socket(
"Image", "", "NodeSocketColor", NODE_INTERFACE_SOCKET_INPUT, nullptr);
ntree.tree_interface.add_socket(
"Mask", "", "NodeSocketColor", NODE_INTERFACE_SOCKET_INPUT, nullptr);
if (for_effect) {
/* Effect: Input 1, Input 2, Fader depending on input count. */
if (effect_input_count == 2) {
ntree.tree_interface.add_socket(
"Input 1", "", "NodeSocketColor", NODE_INTERFACE_SOCKET_INPUT, nullptr);
ntree.tree_interface.add_socket(
"Input 2", "", "NodeSocketColor", NODE_INTERFACE_SOCKET_INPUT, nullptr);
}
else if (effect_input_count == 1) {
ntree.tree_interface.add_socket(
"Input", "", "NodeSocketColor", NODE_INTERFACE_SOCKET_INPUT, nullptr);
}
ntree.tree_interface.add_socket(
"Effect Fader", "", "NodeSocketFloat", NODE_INTERFACE_SOCKET_INPUT, nullptr);
}
else {
/* Modifier: Image, Mask. */
ntree.tree_interface.add_socket(
"Image", "", "NodeSocketColor", NODE_INTERFACE_SOCKET_INPUT, nullptr);
ntree.tree_interface.add_socket(
"Mask", "", "NodeSocketColor", NODE_INTERFACE_SOCKET_INPUT, nullptr);
}
ntree.tree_interface.add_socket(
"Image", "", "NodeSocketColor", NODE_INTERFACE_SOCKET_OUTPUT, nullptr);
@ -1908,23 +1929,40 @@ static wmOperatorStatus new_compositor_sequencer_node_group_exec(bContext *C, wm
char tree_name[MAX_ID_NAME - 2];
RNA_string_get(op->ptr, "name", tree_name);
bNodeTree *ntree = new_node_tree_impl(C, tree_name, "CompositorNodeTree");
initialize_compositor_sequencer_node_group(C, *ntree);
Strip *strip = seq::select_active_get(scene);
const bool is_effect_active = strip != nullptr && strip->type == STRIP_TYPE_COMPOSITOR;
int effect_input_count = 0;
if (is_effect_active) {
effect_input_count = (strip->input1 && strip->input2) ? 2 : (strip->input1 ? 1 : 0);
}
bNodeTree *ntree = new_node_tree_impl(C, tree_name, "CompositorNodeTree");
initialize_compositor_sequencer_node_group(C, *ntree, is_effect_active, effect_input_count);
/* Add modifier and assign node tree when the strip has no active compositor modifier. */
if (strip != nullptr && strip->type != STRIP_TYPE_SOUND) {
bool assigned_node_tree = false;
/* If strip is a compositor effect: assign the node tree. */
if (strip->type == STRIP_TYPE_COMPOSITOR && strip->effectdata) {
CompositorEffectVars *comp_data = static_cast<CompositorEffectVars *>(strip->effectdata);
comp_data->node_group = ntree;
assigned_node_tree = true;
}
/* Otherwise, if there's no active compositor modifier: create one and assign the node tree. */
StripModifierData *active_smd = seq::modifier_get_active(strip);
if (!active_smd || active_smd->type != eSeqModifierType_Compositor) {
if (!assigned_node_tree && (!active_smd || active_smd->type != eSeqModifierType_Compositor)) {
StripModifierData *smd = seq::modifier_new(strip, nullptr, eSeqModifierType_Compositor);
seq::modifier_persistent_uid_init(*strip, *smd);
SequencerCompositorModifierData *modifier_data =
reinterpret_cast<SequencerCompositorModifierData *>(smd);
modifier_data->node_group = ntree;
seq::relations_invalidate_cache(scene, strip);
assigned_node_tree = true;
}
if (assigned_node_tree) {
seq::relations_invalidate_cache(scene, strip);
/* Tag depsgraph relations for an update since the modifier should now be referencing a
* different node tree. */
DEG_relations_tag_update(bmain);

View file

@ -169,52 +169,44 @@ static void get_context_path_node_compositor(const bContext &C,
SpaceNode &snode,
Vector<ui::ContextPathItem> &path)
{
bool skip_base = false;
if (snode.flag & SNODE_PIN) {
context_path_add_node_tree_and_node_groups(snode, path);
/* Pinned: nothing extra; will be handled below. */
}
else if (snode.node_tree_sub_type == SNODE_COMPOSITOR_SEQUENCER) {
Scene *sequencer_scene = CTX_data_sequencer_scene(&C);
if (sequencer_scene) {
ui::context_path_add_generic(path, *RNA_Scene, sequencer_scene, ICON_SCENE);
Strip *strip = seq::select_active_get(sequencer_scene);
if (strip) {
ui::context_path_add_generic(path, *RNA_Strip, strip, ICON_SEQ_STRIP_DUPLICATE);
bNodeTree *node_group = nullptr;
if (strip->type == STRIP_TYPE_COMPOSITOR && strip->effectdata) {
CompositorEffectVars *comp_data = static_cast<CompositorEffectVars *>(strip->effectdata);
node_group = comp_data->node_group;
}
else {
StripModifierData *smd = seq::modifier_get_active(strip);
if (smd && smd->type == eSeqModifierType_Compositor) {
SequencerCompositorModifierData *scmd =
reinterpret_cast<SequencerCompositorModifierData *>(smd);
node_group = scmd->node_group;
}
}
if (node_group != nullptr) {
context_path_add_top_level_shader_node_tree(snode, path, *RNA_NodeTree, node_group);
skip_base = true;
}
}
}
}
else {
if (snode.node_tree_sub_type == SNODE_COMPOSITOR_SEQUENCER) {
Scene *sequencer_scene = CTX_data_sequencer_scene(&C);
if (!sequencer_scene) {
context_path_add_node_tree_and_node_groups(snode, path);
return;
}
ui::context_path_add_generic(path, *RNA_Scene, sequencer_scene, ICON_SCENE);
Editing *ed = seq::editing_get(sequencer_scene);
if (!ed) {
context_path_add_node_tree_and_node_groups(snode, path);
return;
}
Strip *strip = seq::select_active_get(sequencer_scene);
if (!strip) {
context_path_add_node_tree_and_node_groups(snode, path);
return;
}
ui::context_path_add_generic(path, *RNA_Strip, strip, ICON_SEQ_STRIP_DUPLICATE);
StripModifierData *smd = seq::modifier_get_active(strip);
if (!smd) {
context_path_add_node_tree_and_node_groups(snode, path);
return;
}
if (smd->type != eSeqModifierType_Compositor) {
context_path_add_node_tree_and_node_groups(snode, path);
return;
}
SequencerCompositorModifierData *scmd = reinterpret_cast<SequencerCompositorModifierData *>(
smd);
if (scmd->node_group == nullptr) {
context_path_add_node_tree_and_node_groups(snode, path);
return;
}
context_path_add_top_level_shader_node_tree(snode, path, *RNA_NodeTree, scmd->node_group);
context_path_add_node_tree_and_node_groups(snode, path, true);
}
else {
Scene *scene = CTX_data_scene(&C);
ui::context_path_add_generic(path, *RNA_Scene, scene);
context_path_add_node_tree_and_node_groups(snode, path);
}
Scene *scene = CTX_data_scene(&C);
ui::context_path_add_generic(path, *RNA_Scene, scene);
}
context_path_add_node_tree_and_node_groups(snode, path, skip_base);
}
static void get_context_path_node_geometry(const bContext &C,

View file

@ -2815,6 +2815,7 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te)
case STRIP_TYPE_CROSS:
case STRIP_TYPE_GAMCROSS:
case STRIP_TYPE_WIPE:
case STRIP_TYPE_COMPOSITOR:
data.icon = ICON_ARROW_LEFTRIGHT;
break;
case STRIP_TYPE_META:

View file

@ -1969,14 +1969,16 @@ static wmOperatorStatus sequencer_add_effect_strip_exec(bContext *C, wmOperator
Editing *ed = seq::editing_ensure(scene);
StripType effect_type = StripType(RNA_enum_get(op->ptr, "type"));
const int num_inputs = seq::effect_get_num_inputs(effect_type);
const int min_inputs = seq::effect_type_get_min_num_inputs(effect_type);
VectorSet<Strip *> inputs = strip_effect_get_new_inputs(scene, num_inputs);
StringRef error_msg = effect_inputs_validate(inputs, num_inputs);
if (!error_msg.is_empty()) {
BKE_report(op->reports, RPT_ERROR, error_msg.data());
return OPERATOR_CANCELLED;
VectorSet<Strip *> inputs = strip_effect_get_new_inputs(
scene, effect_type == STRIP_TYPE_COMPOSITOR ? 2 : min_inputs);
if (effect_type != STRIP_TYPE_COMPOSITOR) {
const char *error_msg = effect_inputs_validate(inputs.size(), min_inputs);
if (error_msg != nullptr) {
BKE_report(op->reports, RPT_ERROR, error_msg);
return OPERATOR_CANCELLED;
}
}
seq::LoadData load_data;
@ -2011,12 +2013,9 @@ static wmOperatorStatus sequencer_add_effect_strip_exec(bContext *C, wmOperator
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
sequencer_select_do_updates(C, scene);
/* It's reasonable to add effects with inputs directly above the input. */
if (ELEM(load_data.effect.type,
STRIP_TYPE_COLOR,
STRIP_TYPE_TEXT,
STRIP_TYPE_ADJUSTMENT,
STRIP_TYPE_MULTICAM))
/* Place generator effects; others are automatically placed above their inputs. */
if (seq::effect_type_get_min_num_inputs(load_data.effect.type) == 0 &&
(load_data.effect.type != STRIP_TYPE_COMPOSITOR || input1 == nullptr))
{
move_strips(C, op);
}
@ -2024,23 +2023,34 @@ static wmOperatorStatus sequencer_add_effect_strip_exec(bContext *C, wmOperator
return OPERATOR_FINISHED;
}
static bool is_op_for_effect_with_inputs(const Scene *scene, StripType type)
{
if (seq::effect_type_get_min_num_inputs(type) != 0) {
return true;
}
/* Adding compositor effect with any selected strip; assuming it will be for that strip. */
if (type == STRIP_TYPE_COMPOSITOR) {
if (seq::select_has_any(scene)) {
return true;
}
}
return false;
}
static wmOperatorStatus sequencer_add_effect_strip_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
bool is_type_set = RNA_struct_property_is_set(op->ptr, "type");
int type = -1;
int prop_flag = SEQPROP_LENGTH;
if (!is_type_set) {
BKE_report(op->reports, RPT_ERROR_INVALID_INPUT, "Strip type is not set.");
return OPERATOR_CANCELLED;
}
type = RNA_enum_get(op->ptr, "type");
int prop_flag = SEQPROP_LENGTH;
/* When invoking an effect strip which uses inputs, skip guessing of the channel. */
if (seq::effect_get_num_inputs(type) != 0) {
StripType type = StripType(RNA_enum_get(op->ptr, "type"));
if (is_op_for_effect_with_inputs(CTX_data_sequencer_scene(C), type)) {
prop_flag |= SEQPROP_NOCHAN;
}
@ -2049,15 +2059,15 @@ static wmOperatorStatus sequencer_add_effect_strip_invoke(bContext *C,
return sequencer_add_effect_strip_exec(C, op);
}
static bool sequencer_add_effect_strip_poll_property(const bContext * /*C*/,
static bool sequencer_add_effect_strip_poll_property(const bContext *C,
wmOperator *op,
const PropertyRNA *prop)
{
const char *prop_id = RNA_property_identifier(prop);
int type = RNA_enum_get(op->ptr, "type");
StripType type = StripType(RNA_enum_get(op->ptr, "type"));
/* Hide start frame and length for effect strips that are locked to their parents' location. */
if (seq::effect_get_num_inputs(type) != 0) {
if (is_op_for_effect_with_inputs(CTX_data_sequencer_scene(C), type)) {
if (STR_ELEM(prop_id, "frame_start", "length")) {
return false;
}
@ -2114,6 +2124,8 @@ static std::string sequencer_add_effect_strip_get_description(bContext * /*C*/,
return TIP_("Add a text strip to the sequencer");
case STRIP_TYPE_COLORMIX:
return TIP_("Add a color mix effect strip to the sequencer");
case STRIP_TYPE_COMPOSITOR:
return TIP_("Add a compositor based effect strip for zero, one, or two selected inputs");
default:
break;
}

View file

@ -311,8 +311,7 @@ static bool sequencer_swap_inputs_poll(bContext *C)
}
Scene *scene = CTX_data_sequencer_scene(C);
Strip *active_strip = seq::select_active_get(scene);
if (sequencer_effect_poll(C) && seq::effect_get_num_inputs(active_strip->type) == 2) {
if (active_strip && active_strip->effect_num_inputs_get() == 2) {
return true;
}
@ -1607,18 +1606,18 @@ void SEQUENCER_OT_refresh_all(wmOperatorType *ot)
/** \name Reassign Inputs Operator
* \{ */
StringRef effect_inputs_validate(const VectorSet<Strip *> &inputs, int num_inputs)
const char *effect_inputs_validate(int have_inputs, int num_inputs)
{
if (inputs.size() > 2) {
if (have_inputs > 2) {
return "Cannot apply effect to more than 2 strips with video content";
}
if (num_inputs == 2 && inputs.size() != 2) {
if (num_inputs == 2 && have_inputs != 2) {
return "Exactly 2 selected strips with video content are needed";
}
if (num_inputs == 1 && inputs.size() != 1) {
if (num_inputs == 1 && have_inputs != 1) {
return "Exactly one selected strip with video content is needed";
}
return "";
return nullptr;
}
VectorSet<Strip *> strip_effect_get_new_inputs(const Scene *scene,
@ -1657,7 +1656,7 @@ static wmOperatorStatus sequencer_reassign_inputs_exec(bContext *C, wmOperator *
{
Scene *scene = CTX_data_sequencer_scene(C);
Strip *active_strip = seq::select_active_get(scene);
const int num_inputs = seq::effect_get_num_inputs(active_strip->type);
const int num_inputs = active_strip->effect_num_inputs_get();
if (num_inputs == 0) {
BKE_report(op->reports, RPT_ERROR, "Cannot reassign inputs: strip has no inputs");
@ -1665,7 +1664,7 @@ static wmOperatorStatus sequencer_reassign_inputs_exec(bContext *C, wmOperator *
}
VectorSet<Strip *> inputs = strip_effect_get_new_inputs(scene, num_inputs, true);
StringRef error_msg = effect_inputs_validate(inputs, num_inputs);
StringRef error_msg = effect_inputs_validate(inputs.size(), num_inputs);
if (!error_msg.is_empty()) {
BKE_report(op->reports, RPT_ERROR, error_msg.data());
@ -1739,7 +1738,7 @@ static wmOperatorStatus sequencer_swap_inputs_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (seq::effect_get_num_inputs(active_strip->type) != 2 || active_strip->input1 == nullptr ||
if (active_strip->effect_num_inputs_get() != 2 || active_strip->input1 == nullptr ||
active_strip->input2 == nullptr)
{
BKE_report(op->reports, RPT_ERROR, "Strip needs two inputs to swap");
@ -3125,12 +3124,10 @@ static wmOperatorStatus sequencer_swap_exec(bContext *C, wmOperator *op)
if (strip) {
/* Disallow effect strips. */
if (seq::effect_get_num_inputs(strip->type) >= 1 &&
(strip->effectdata || strip->input1 || strip->input2))
{
if (strip->is_effect_with_inputs() && (strip->effectdata || strip->input1 || strip->input2)) {
return OPERATOR_CANCELLED;
}
if ((seq::effect_get_num_inputs(active_strip->type) >= 1) &&
if (active_strip->is_effect_with_inputs() &&
(active_strip->effectdata || active_strip->input1 || active_strip->input2))
{
return OPERATOR_CANCELLED;
@ -3393,6 +3390,7 @@ const EnumPropertyItem sequencer_prop_effect_types[] = {
{STRIP_TYPE_GAUSSIAN_BLUR, "GAUSSIAN_BLUR", 0, "Gaussian Blur", "Soften details along axes"},
{STRIP_TYPE_TEXT, "TEXT", 0, "Text", "Add a simple text strip"},
{STRIP_TYPE_COLORMIX, "COLORMIX", 0, "Color Mix", "Combine two strips using blend modes"},
{STRIP_TYPE_COMPOSITOR, "COMPOSITOR", 0, "Compositor", "Compositor based effect"},
{0, nullptr, 0, nullptr, nullptr},
};
@ -3400,16 +3398,20 @@ static wmOperatorStatus sequencer_change_effect_type_exec(bContext *C, wmOperato
{
Scene *scene = CTX_data_sequencer_scene(C);
Strip *strip = seq::select_active_get(scene);
const int old_type = strip->type;
const int new_type = RNA_enum_get(op->ptr, "type");
const StripType old_type = StripType(strip->type);
const int have_inputs = strip->effect_num_inputs_get();
const StripType new_type = StripType(RNA_enum_get(op->ptr, "type"));
if (!strip->is_effect()) {
return OPERATOR_CANCELLED;
}
if (seq::effect_get_num_inputs(strip->type) != seq::effect_get_num_inputs(new_type)) {
BKE_report(op->reports, RPT_ERROR, "New effect takes less or more inputs");
return OPERATOR_CANCELLED;
/* Note: allow changing to compositor effect; it can have any number of inputs. */
if (new_type != STRIP_TYPE_COMPOSITOR) {
if (have_inputs != seq::effect_type_get_min_num_inputs(new_type)) {
BKE_report(op->reports, RPT_ERROR, "New effect takes less or more inputs");
return OPERATOR_CANCELLED;
}
}
/* Free previous effect. */

View file

@ -179,7 +179,7 @@ void slip_modal_keymap(wmKeyConfig *keyconf);
VectorSet<Strip *> strip_effect_get_new_inputs(const Scene *scene,
int num_inputs,
bool ignore_active = false);
StringRef effect_inputs_validate(const VectorSet<Strip *> &inputs, int num_inputs);
const char *effect_inputs_validate(int have_inputs, int num_inputs);
/* Operator helpers. */
bool sequencer_edit_poll(bContext *C);

View file

@ -38,7 +38,6 @@
#include "SEQ_channels.hh"
#include "SEQ_connect.hh"
#include "SEQ_effects.hh"
#include "SEQ_iterator.hh"
#include "SEQ_relations.hh"
#include "SEQ_retiming.hh"
@ -969,7 +968,7 @@ static float inner_clickable_handle_size_get(const Scene *scene,
bool can_select_handle(const Scene *scene, const Strip *strip, const View2D *v2d)
{
if (seq::effect_get_num_inputs(strip->type) > 0) {
if (strip->is_effect_with_inputs()) {
return false;
}

View file

@ -344,6 +344,7 @@ static void color3ubv_from_seq(const Scene *curscene,
case STRIP_TYPE_CROSS:
case STRIP_TYPE_GAMCROSS:
case STRIP_TYPE_WIPE:
case STRIP_TYPE_COMPOSITOR:
ui::theme::get_color_3ubv(TH_SEQ_TRANSITION, r_col);
/* Slightly offset hue to distinguish different transition types. */
@ -353,6 +354,9 @@ static void color3ubv_from_seq(const Scene *curscene,
else if (strip->type == STRIP_TYPE_WIPE) {
rgb_byte_set_hue_float_offset(r_col, 0.06);
}
else if (strip->type == STRIP_TYPE_COMPOSITOR) {
rgb_byte_set_hue_float_offset(r_col, -0.03f);
}
break;
/* Effects. */
@ -1323,7 +1327,8 @@ static void draw_strips_background(const TimelineDrawContext &ctx,
/* Transition state. */
if (show_overlay && strip.can_draw_strip_content &&
seq::effect_is_transition(StripType(strip.strip->type)))
seq::effect_is_transition(StripType(strip.strip->type)) && strip.strip->input1 &&
strip.strip->input2)
{
data.flags |= GPU_SEQ_FLAG_TRANSITION;

View file

@ -24,7 +24,6 @@
#include "SEQ_animation.hh"
#include "SEQ_channels.hh"
#include "SEQ_edit.hh"
#include "SEQ_effects.hh"
#include "SEQ_iterator.hh"
#include "SEQ_relations.hh"
#include "SEQ_sequencer.hh"
@ -480,10 +479,10 @@ static void create_trans_seq_clamp_data(TransInfo *t, const Scene *scene)
VectorSet<Strip *> strips = seq::query_selected_strips(seq::active_seqbase_get(ed));
for (Strip *strip : strips) {
if (!strip->is_effect() || seq::effect_get_num_inputs(strip->type) == 0) {
if (!strip->is_effect_with_inputs()) {
continue;
}
/* If there is an effect strip with no inputs selected, prevent any x-direction movement,
/* If there is an effect strip without its inputs selected, prevent any x-direction movement,
* since these strips are tied to their inputs and can only move up and down. */
if (!(strip->input1->flag & SEQ_SELECT) &&
(!strip->input2 || !(strip->input2->flag & SEQ_SELECT)))

View file

@ -214,9 +214,8 @@ static VectorSet<Strip *> query_snap_targets_timeline(Scene *scene,
* have to be selected. Remove such strips from `snap_targets` collection. */
VectorSet effects_of_snap_sources = snap_sources;
seq::iterator_set_expand(scene, seqbase, effects_of_snap_sources, query_strip_effects_fn);
effects_of_snap_sources.remove_if([&](Strip *strip) {
return strip->is_effect() && seq::effect_get_num_inputs(strip->type) == 0;
});
effects_of_snap_sources.remove_if(
[&](Strip *strip) { return strip->is_effect() && !strip->is_effect_with_inputs(); });
VectorSet<Strip *> snap_targets;
for (Strip &strip : *seqbase) {

View file

@ -147,6 +147,7 @@ enum StripType {
STRIP_TYPE_MUL = 14,
/* Removed (behavior was the same as alpha-over), only used when reading old files. */
STRIP_TYPE_OVERDROP_REMOVED = 15,
STRIP_TYPE_COMPOSITOR = 16,
/* STRIP_TYPE_PLUGIN = 24, */ /* Removed. */
STRIP_TYPE_WIPE = 25,
STRIP_TYPE_GLOW = 26,
@ -459,6 +460,11 @@ struct Strip {
#ifdef __cplusplus
bool is_effect() const;
int effect_num_inputs_get() const;
bool is_effect_with_inputs() const
{
return this->effect_num_inputs_get() != 0;
}
/**
* Get timeline frame where strip content starts.
@ -846,6 +852,10 @@ struct ColorMixVars {
float factor = 0;
};
struct CompositorEffectVars {
struct bNodeTree *node_group = nullptr;
};
/** \} */
/* -------------------------------------------------------------------- */

View file

@ -704,7 +704,7 @@ static int rna_Strip_time_editable(const PointerRNA *ptr, const char ** /*r_info
{
Strip *strip = static_cast<Strip *>(ptr->data);
/* Effect strips' start frame and length must be readonly! */
return (seq::effect_get_num_inputs(strip->type)) ? PropertyFlag(0) : PROP_EDITABLE;
return strip->is_effect_with_inputs() ? PropertyFlag(0) : PROP_EDITABLE;
}
static void rna_Strip_channel_set(PointerRNA *ptr, int value)
@ -970,6 +970,8 @@ static StructRNA *rna_Strip_refine(PointerRNA *ptr)
return RNA_AlphaUnderStrip;
case STRIP_TYPE_GAMCROSS:
return RNA_GammaCrossStrip;
case STRIP_TYPE_COMPOSITOR:
return RNA_CompositorStrip;
case STRIP_TYPE_MUL:
return RNA_MultiplyStrip;
case STRIP_TYPE_MULTICAM:
@ -1146,8 +1148,7 @@ static void rna_Strip_pan_range(
static int rna_Strip_input_count_get(PointerRNA *ptr)
{
Strip *strip = static_cast<Strip *>(ptr->data);
return seq::effect_get_num_inputs(strip->type);
return strip->effect_num_inputs_get();
}
static void rna_Strip_input_set(PointerRNA *ptr,
@ -1856,7 +1857,7 @@ static void rna_Strip_SoundEqualizer_Curve_clear(SoundEqualizerModifierData *sem
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, NULL);
}
static bool rna_CompositorModifier_node_group_poll(PointerRNA * /*ptr*/, PointerRNA value)
static bool rna_Compositor_node_group_poll(PointerRNA * /*ptr*/, PointerRNA value)
{
const bNodeTree *node_tree = value.data_as<bNodeTree>();
if (node_tree->type != NTREE_COMPOSIT) {
@ -1865,11 +1866,9 @@ static bool rna_CompositorModifier_node_group_poll(PointerRNA * /*ptr*/, Pointer
return true;
}
static void rna_CompositorModifier_node_group_update(Main *bmain, Scene *scene, PointerRNA *ptr)
static void strip_compositor_node_group_update(Main *bmain, PointerRNA *ptr)
{
rna_StripModifier_update(bmain, scene, ptr);
/* Tag depsgraph relations for an update since the modifier could now be referencing a different
/* Tag depsgraph relations for an update since the strip could now be referencing a different
* node tree. */
DEG_relations_tag_update(bmain);
@ -1878,11 +1877,23 @@ static void rna_CompositorModifier_node_group_update(Main *bmain, Scene *scene,
Scene *strip_scene = reinterpret_cast<Scene *>(ptr->owner_id);
Editing *ed = seq::editing_get(strip_scene);
/* The sequencer stores a cached mapping between compositor node trees and strips that use them
* as a modifier, so we need to invalidate the cache since the node tree changed. */
/* The sequencer stores a cached mapping between compositor node trees and strips that use them,
* so we need to invalidate the cache since the node tree changed. */
seq::strip_lookup_invalidate(ed);
}
static void rna_CompositorEffect_node_group_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
rna_Strip_invalidate_raw_update(bmain, scene, ptr);
strip_compositor_node_group_update(bmain, ptr);
}
static void rna_CompositorModifier_node_group_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
rna_StripModifier_update(bmain, scene, ptr);
strip_compositor_node_group_update(bmain, ptr);
}
} // namespace blender
#else
@ -2391,6 +2402,7 @@ static void rna_def_strip(BlenderRNA *brna)
{STRIP_TYPE_ALPHAOVER, "ALPHA_OVER", 0, "Alpha Over", ""},
{STRIP_TYPE_ALPHAUNDER, "ALPHA_UNDER", 0, "Alpha Under", ""},
{STRIP_TYPE_GAMCROSS, "GAMMA_CROSS", 0, "Gamma Crossfade", ""},
{STRIP_TYPE_COMPOSITOR, "COMPOSITOR", 0, "Compositor", ""},
{STRIP_TYPE_MUL, "MULTIPLY", 0, "Multiply", ""},
{STRIP_TYPE_WIPE, "WIPE", 0, "Wipe", ""},
{STRIP_TYPE_GLOW, "GLOW", 0, "Glow", ""},
@ -3613,6 +3625,17 @@ static void rna_def_glow(StructRNA *srna)
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Strip_invalidate_raw_update");
}
static void rna_def_compositor_effect(StructRNA *srna)
{
RNA_def_struct_sdna_from(srna, "CompositorEffectVars", "effectdata");
PropertyRNA *prop = RNA_def_property(srna, "node_group", PROP_POINTER, PROP_NONE);
RNA_def_property_ui_text(prop, "Node Group", "Node group that controls what this effect does");
RNA_def_property_pointer_funcs(
prop, nullptr, nullptr, nullptr, "rna_Compositor_node_group_poll");
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_CompositorEffect_node_group_update");
}
static void rna_def_solid_color(StructRNA *srna)
{
PropertyRNA *prop;
@ -3941,6 +3964,7 @@ static EffectInfo def_effects[] = {
0},
{"CrossStrip", "Crossfade Strip", "Crossfade Strip", nullptr, 2},
{"GammaCrossStrip", "Gamma Crossfade Strip", "Gamma Crossfade Strip", nullptr, 2},
{"CompositorStrip", "Compositor Strip", "Compositor Strip", rna_def_compositor_effect, 2},
{"GlowStrip", "Glow Strip", "Sequence strip creating a glow effect", rna_def_glow, 1},
{"MulticamStrip",
"Multicam Select Strip",
@ -4288,7 +4312,7 @@ static void rna_def_compositor_modifier(BlenderRNA *brna)
PropertyRNA *prop = RNA_def_property(srna, "node_group", PROP_POINTER, PROP_NONE);
RNA_def_property_ui_text(prop, "Node Group", "Node group that controls what this modifier does");
RNA_def_property_pointer_funcs(
prop, nullptr, nullptr, nullptr, "rna_CompositorModifier_node_group_poll");
prop, nullptr, nullptr, nullptr, "rna_Compositor_node_group_poll");
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_update(
prop, NC_SCENE | ND_SEQUENCER, "rna_CompositorModifier_node_group_update");

View file

@ -489,13 +489,11 @@ static Strip *rna_Strips_new_effect(ID *id,
Strip *input1,
Strip *input2)
{
Scene *scene = id_cast<Scene *>(id);
Strip *strip;
const int num_inputs = seq::effect_get_num_inputs(type);
switch (num_inputs) {
const int min_inputs = blender::seq::effect_type_get_min_num_inputs(StripType(type));
const bool compositor_with_inputs = type == STRIP_TYPE_COMPOSITOR && input1 != nullptr;
switch (min_inputs) {
case 0:
if (length <= 0) {
if (length <= 0 && !compositor_with_inputs) {
BKE_report(reports, RPT_ERROR, "Strips.new_effect: invalid length");
return nullptr;
}
@ -517,17 +515,17 @@ static Strip *rna_Strips_new_effect(ID *id,
reports,
RPT_ERROR,
"Strips.new_effect: effect expects more than 2 inputs (%d, should never happen!)",
num_inputs);
min_inputs);
return nullptr;
}
seq::LoadData load_data;
seq::add_load_data_init(&load_data, name, nullptr, frame_start, channel);
load_data.effect.length = length;
load_data.effect.type = StripType(type);
load_data.effect.input1 = input1;
load_data.effect.input2 = input2;
strip = seq::add_effect_strip(scene, seqbase, &load_data);
Scene *scene = id_cast<Scene *>(id);
Strip *strip = seq::add_effect_strip(scene, seqbase, &load_data);
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS);
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, scene);
@ -842,6 +840,7 @@ void RNA_api_strips(StructRNA *srna, const bool metastrip)
0,
"Gamma Crossfade",
"Crossfade with color correction"},
{STRIP_TYPE_COMPOSITOR, "COMPOSITOR", 0, "Compositor", "Compositor based effect"},
{STRIP_TYPE_MUL, "MULTIPLY", 0, "Multiply", "Multiply color channels from two videos"},
{STRIP_TYPE_WIPE, "WIPE", 0, "Wipe", "Sweep a transition line across the frame"},
{STRIP_TYPE_GLOW, "GLOW", 0, "Glow", "Add blur and brightness to light areas"},

View file

@ -44,35 +44,35 @@ static void composite_get_from_context(const bContext *C,
{
const SpaceNode *snode = CTX_wm_space_node(C);
if (snode->node_tree_sub_type == SNODE_COMPOSITOR_SEQUENCER) {
*r_ntree = nullptr;
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
if (!sequencer_scene) {
*r_ntree = nullptr;
return;
}
Editing *ed = seq::editing_get(sequencer_scene);
if (!ed) {
*r_ntree = nullptr;
return;
}
Strip *strip = seq::select_active_get(sequencer_scene);
if (!strip) {
*r_ntree = nullptr;
return;
}
StripModifierData *smd = seq::modifier_get_active(strip);
if (!smd) {
*r_ntree = nullptr;
return;
bNodeTree *node_group = nullptr;
if (strip->type == STRIP_TYPE_COMPOSITOR && strip->effectdata) {
CompositorEffectVars *comp_data = static_cast<CompositorEffectVars *>(strip->effectdata);
node_group = comp_data->node_group;
}
if (smd->type != eSeqModifierType_Compositor) {
*r_ntree = nullptr;
return;
else {
StripModifierData *smd = seq::modifier_get_active(strip);
if (smd && smd->type == eSeqModifierType_Compositor) {
SequencerCompositorModifierData *scmd =
reinterpret_cast<SequencerCompositorModifierData *>(smd);
node_group = scmd->node_group;
}
}
if (node_group) {
*r_from = nullptr;
*r_id = &sequencer_scene->id;
*r_ntree = node_group;
}
SequencerCompositorModifierData *scmd = reinterpret_cast<SequencerCompositorModifierData *>(
smd);
*r_from = nullptr;
*r_id = &sequencer_scene->id;
*r_ntree = scmd->node_group;
return;
}

View file

@ -947,41 +947,44 @@ static void node_group_input_extra_info(nodes::NodeExtraInfoParams &parameters)
return;
}
blender::Span<const bNodeSocket *> group_inputs = parameters.node.output_sockets().drop_back(1);
bool added_warning_for_unsupported_inputs = false;
Span<const bNodeSocket *> group_inputs = parameters.node.output_sockets().drop_back(1);
int color_count = 0;
int float_count = 0;
int other_count = 0;
for (const bNodeSocket *input : group_inputs) {
if (StringRef(input->name) == "Image") {
if (input->type != SOCK_RGBA) {
blender::nodes::NodeExtraInfoRow row;
row.text = IFACE_("Wrong Image Input Type");
row.icon = ICON_ERROR;
row.tooltip = TIP_("Node group's main Image input should be of type Color");
parameters.rows.append(std::move(row));
}
if (input->type == SOCK_RGBA) {
color_count++;
}
else if (StringRef(input->name) == "Mask") {
if (input->type != SOCK_RGBA) {
blender::nodes::NodeExtraInfoRow row;
row.text = IFACE_("Wrong Mask Input Type");
row.icon = ICON_ERROR;
row.tooltip = TIP_("Node group's Mask input should be of type Color");
parameters.rows.append(std::move(row));
}
else if (input->type == SOCK_FLOAT) {
float_count++;
}
else {
if (added_warning_for_unsupported_inputs) {
continue;
}
blender::nodes::NodeExtraInfoRow row;
row.text = IFACE_("Unsupported Inputs");
row.icon = ICON_WARNING_LARGE;
row.tooltip = TIP_(
"Only a main Image and Mask inputs are supported, the rest are unsupported and will "
"return zero");
parameters.rows.append(std::move(row));
added_warning_for_unsupported_inputs = true;
other_count++;
}
}
if (color_count > 2) {
nodes::NodeExtraInfoRow row;
row.text = IFACE_("Unsupported Inputs");
row.icon = ICON_WARNING_LARGE;
row.tooltip = TIP_("Sequencer supports up to two Image inputs, the rest will return zero");
parameters.rows.append(std::move(row));
}
if (float_count > 1) {
nodes::NodeExtraInfoRow row;
row.text = IFACE_("Unsupported Inputs");
row.icon = ICON_WARNING_LARGE;
row.tooltip = TIP_("Sequencer supports one Float input, the rest will return zero");
parameters.rows.append(std::move(row));
}
if (other_count > 0) {
nodes::NodeExtraInfoRow row;
row.text = IFACE_("Unsupported Inputs");
row.icon = ICON_WARNING_LARGE;
row.tooltip = TIP_(
"Sequencer supports only Color and Float inputs, the rest will return zero");
parameters.rows.append(std::move(row));
}
}
void register_node_type_group_input()

View file

@ -59,6 +59,7 @@ set(SRC
intern/effects/vse_effect_add_sub_mul.cc
intern/effects/vse_effect_adjustment.cc
intern/effects/vse_effect_blend.cc
intern/effects/vse_effect_compositor.cc
intern/effects/vse_effect_cross.cc
intern/effects/vse_effect_gaussian_blur.cc
intern/effects/vse_effect_glow.cc

View file

@ -23,7 +23,12 @@ namespace seq {
void effect_ensure_initialized(Strip *strip);
void effect_free(Strip *strip);
int effect_get_num_inputs(int strip_type);
/* Returns the minimum number of inputs needed by the effect type.
* Note: some effects (compositor) will return zero; they can
* take variable number of inputs. */
int effect_type_get_min_num_inputs(StripType type);
bool strip_type_is_effect(StripType type);
bool effect_is_transition(StripType type);
void effect_text_font_set(Strip *strip, VFont *font);
bool effects_can_render_text(const Strip *strip);

View file

@ -60,9 +60,9 @@ void relations_invalidate_cache_raw(Scene *scene, Strip *strip);
void relations_invalidate_scene_strips(const Main *bmain, const Scene *scene_target);
/**
* Invalidates the cache for all strips that uses the given node tree as a compositor modifier.
* Invalidates the cache for all strips that uses the given compositor node tree.
*/
void relations_invalidate_compositor_modifiers(const Main *bmain, const bNodeTree *node_tree);
void relations_invalidate_compositor_users(const Main *bmain, const bNodeTree *node_tree);
void relations_invalidate_movieclip_strips(Main *bmain, MovieClip *clip_target);
/**

View file

@ -18,6 +18,7 @@ namespace seq {
Strip *select_active_get(const Scene *scene);
bool select_active_get_pair(Scene *scene, Strip **r_strip_act, Strip **r_strip_other);
void select_active_set(Scene *scene, Strip *strip);
bool select_has_any(const Scene *scene);
} // namespace seq
} // namespace blender

View file

@ -198,7 +198,7 @@ Span<Strip *> lookup_strips_by_scene(Editing *ed, const Scene *key);
Map<const Scene *, VectorSet<Strip *>> &lookup_strips_by_scene_map_get(Editing *ed);
/**
* Find all strips using provided compositor node tree as a modifier
* Find all strips using provided compositor node tree
*
* \param ed: Editing that owns lookup hash
* \param key: Node tree pointer

View file

@ -103,11 +103,6 @@ Array<float> make_gaussian_blur_kernel(float rad, int size)
static void init_noop(Strip * /*strip*/) {}
static int num_inputs_default()
{
return 2;
}
static void copy_effect_default(Strip *dst, const Strip *src, const int /*flag*/)
{
dst->effectdata = MEM_dupalloc_void(src->effectdata);
@ -169,7 +164,6 @@ EffectHandle effect_handle_get(StripType strip_type)
EffectHandle rval;
rval.init = init_noop;
rval.num_inputs = num_inputs_default;
rval.free = nullptr;
rval.early_out = early_out_noop;
rval.execute = nullptr;
@ -182,6 +176,9 @@ EffectHandle effect_handle_get(StripType strip_type)
case STRIP_TYPE_GAMCROSS:
gamma_cross_effect_get_handle(rval);
break;
case STRIP_TYPE_COMPOSITOR:
compositor_effect_get_handle(rval);
break;
case STRIP_TYPE_ADD:
add_effect_get_handle(rval);
break;
@ -236,7 +233,6 @@ static EffectHandle effect_handle_for_blend_mode_get(StripBlendMode blend)
EffectHandle rval;
rval.init = init_noop;
rval.num_inputs = num_inputs_default;
rval.free = nullptr;
rval.early_out = early_out_noop;
rval.execute = nullptr;
@ -312,7 +308,15 @@ EffectHandle strip_blend_mode_handle_get(Strip *strip)
static float transition_fader_calc(const Scene *scene, const Strip *strip, float timeline_frame)
{
float fac = float(timeline_frame - strip->left_handle());
fac /= strip->length(scene);
/* Compositor with no inputs can have strip->len not be updated,
* since most of existing editing code assumes no-input effects never need the length.
* So for the fader, just calculated it here directly. */
if (strip->type == STRIP_TYPE_COMPOSITOR) {
fac /= strip->enddisp - strip->startdisp;
}
else {
fac /= strip->length(scene);
}
fac = math::clamp(fac, 0.0f, 1.0f);
return fac;
}
@ -334,18 +338,43 @@ float effect_fader_calc(Scene *scene, Strip *strip, float timeline_frame)
return strip->effect_fader;
}
int effect_get_num_inputs(int strip_type)
int effect_type_get_min_num_inputs(StripType type)
{
EffectHandle rval = effect_handle_get(StripType(strip_type));
if (rval.execute == nullptr) {
if (!strip_type_is_effect(type)) {
return 0;
}
return rval.num_inputs();
/* Zero input effects. Note: compositor is here too, but it supports
* any input count. */
if (ELEM(type,
STRIP_TYPE_ADJUSTMENT,
STRIP_TYPE_MULTICAM,
STRIP_TYPE_COLOR,
STRIP_TYPE_TEXT,
STRIP_TYPE_COMPOSITOR))
{
return 0;
}
/* One input effects. */
if (ELEM(type, STRIP_TYPE_GAUSSIAN_BLUR, STRIP_TYPE_GLOW, STRIP_TYPE_SPEED)) {
return 1;
}
/* Others are two inputs. */
return 2;
}
bool strip_type_is_effect(StripType type)
{
return (type >= STRIP_TYPE_CROSS && type <= STRIP_TYPE_COMPOSITOR) ||
(type >= STRIP_TYPE_WIPE && type <= STRIP_TYPE_ADJUSTMENT) ||
(type >= STRIP_TYPE_GAUSSIAN_BLUR && type <= STRIP_TYPE_COLORMIX);
}
bool effect_is_transition(StripType type)
{
return ELEM(type, STRIP_TYPE_CROSS, STRIP_TYPE_GAMCROSS, STRIP_TYPE_WIPE);
return ELEM(type, STRIP_TYPE_CROSS, STRIP_TYPE_GAMCROSS, STRIP_TYPE_WIPE, STRIP_TYPE_COMPOSITOR);
}
} // namespace blender::seq

View file

@ -41,9 +41,6 @@ struct EffectHandle {
/* #init is only called on first creation, or when changing effect type. */
void (*init)(Strip *strip);
/* Number of input strips needed for this effect. */
int (*num_inputs)();
/* duplicate */
void (*copy)(Strip *dst, const Strip *src, int flag);
@ -123,6 +120,7 @@ void alpha_over_effect_get_handle(EffectHandle &rval);
void alpha_under_effect_get_handle(EffectHandle &rval);
void blend_mode_effect_get_handle(EffectHandle &rval);
void color_mix_effect_get_handle(EffectHandle &rval);
void compositor_effect_get_handle(EffectHandle &rval);
void cross_effect_get_handle(EffectHandle &rval);
void gamma_cross_effect_get_handle(EffectHandle &rval);
void gaussian_blur_effect_get_handle(EffectHandle &rval);

View file

@ -18,12 +18,6 @@
namespace blender::seq {
/* No effect inputs for adjustment, we use #give_ibuf_seq. */
static int num_inputs_adjustment()
{
return 0;
}
static StripEarlyOut early_out_adjustment(const Strip * /*strip*/, float /*fac*/)
{
return StripEarlyOut::NoInput;
@ -85,7 +79,6 @@ static ImBuf *do_adjustment(const RenderData *context,
void adjustment_effect_get_handle(EffectHandle &rval)
{
rval.num_inputs = num_inputs_adjustment;
rval.early_out = early_out_adjustment;
rval.execute = do_adjustment;
}

View file

@ -0,0 +1,342 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "BKE_node_runtime.hh"
#include "COM_context.hh"
#include "COM_domain.hh"
#include "COM_node_group_operation.hh"
#include "COM_realize_on_domain_operation.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_sequence_types.h"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "SEQ_render.hh"
#include "effects.hh"
#include "render.hh"
namespace blender::seq {
class CompositorEffectContext : public compositor::Context {
const RenderData &render_data_;
bNodeTree *node_group_;
ImBuf *input_1_;
ImBuf *input_2_;
ImBuf *output_;
float factor_;
float2 result_translation_ = float2(0, 0);
const Strip *strip_;
/* Identifies if the output of the viewer was written. */
bool viewer_was_written_ = false;
public:
CompositorEffectContext(compositor::StaticCacheManager &cache_manager,
const RenderData &render_data,
bNodeTree *node_tree,
ImBuf *input_1,
ImBuf *input_2,
ImBuf *output,
float factor,
const Strip &strip)
: compositor::Context(cache_manager),
render_data_(render_data),
node_group_(node_tree),
input_1_(input_1),
input_2_(input_2),
output_(output),
factor_(factor),
strip_(&strip)
{
}
float2 get_result_translation() const
{
return result_translation_;
}
const Scene &get_scene() const override
{
return *render_data_.scene;
}
bool treat_viewer_as_group_output() const override
{
return true;
}
compositor::Domain get_compositing_domain() const override
{
return compositor::Domain(int2(this->output_->x, this->output_->y));
}
void write_output(const compositor::Result &result)
{
/* Do not write the output if the viewer output was already written. */
if (viewer_was_written_) {
return;
}
if (result.is_single_value()) {
IMB_rectfill(this->output_, result.get_single_value<compositor::Color>());
return;
}
result_translation_ = result.domain().transformation.location();
std::memcpy(this->output_->float_buffer.data,
result.cpu_data().data(),
IMB_get_pixel_count(this->output_) * sizeof(float) * 4);
}
void write_viewer(compositor::Result &result) override
{
/* Within compositor effect, output and viewer output function the same. */
this->write_output(result);
viewer_was_written_ = true;
}
const Strip *get_strip() const override
{
return strip_;
}
bool use_gpu() const override
{
return false;
}
compositor::NodeGroupOutputTypes needed_outputs() const
{
compositor::NodeGroupOutputTypes needed_outputs =
compositor::NodeGroupOutputTypes::GroupOutputNode;
if (!render_data_.render) {
needed_outputs |= compositor::NodeGroupOutputTypes::ViewerNode;
}
return needed_outputs;
}
void evaluate()
{
using namespace compositor;
const bNodeTree &node_group = *DEG_get_evaluated<bNodeTree>(render_data_.depsgraph,
node_group_);
NodeGroupOperation node_group_operation(*this,
node_group,
this->needed_outputs(),
nullptr,
node_group.active_viewer_key,
bke::NODE_INSTANCE_KEY_BASE);
/* Set the reference count for the outputs, only the first color output is actually needed,
* while the rest are ignored. */
node_group.ensure_interface_cache();
for (const bNodeTreeInterfaceSocket *output_socket : node_group.interface_outputs()) {
const bool is_first_output = output_socket == node_group.interface_outputs().first();
Result &output_result = node_group_operation.get_result(output_socket->identifier);
const bool is_color = output_result.type() == ResultType::Color;
output_result.set_reference_count(is_first_output && is_color ? 1 : 0);
}
/* Map the inputs to the operation. */
Vector<std::unique_ptr<Result>> inputs;
int float_counter = 0;
int color_counter = 0;
for (const bNodeTreeInterfaceSocket *input_socket : node_group.interface_inputs()) {
const bke::bNodeSocketType *typeinfo = input_socket->socket_typeinfo();
Result *input_result = nullptr;
if (typeinfo && typeinfo->type == SOCK_FLOAT && float_counter == 0) {
/* First float input is factor. */
input_result = new Result(this->create_result(ResultType::Float, ResultPrecision::Full));
input_result->allocate_single_value();
input_result->set_single_value(this->factor_);
float_counter++;
}
else if (color_counter == 0 && this->input_1_) {
/* First input image. */
input_result = new Result(this->create_result(ResultType::Color, ResultPrecision::Full));
input_result->wrap_external(this->input_1_->float_buffer.data,
int2(this->input_1_->x, this->input_1_->y));
color_counter++;
}
else if (color_counter == 1 && this->input_2_) {
/* Second input image. */
input_result = new Result(this->create_result(ResultType::Color, ResultPrecision::Full));
input_result->wrap_external(this->input_2_->float_buffer.data,
int2(this->input_2_->x, this->input_2_->y));
color_counter++;
}
else {
/* Unsupported sockets. */
input_result = new Result(this->create_result(ResultType::Color, ResultPrecision::Full));
input_result->allocate_invalid();
}
node_group_operation.map_input_to_result(input_socket->identifier, input_result);
inputs.append(std::unique_ptr<Result>(input_result));
}
node_group_operation.evaluate();
/* Write the outputs of the operation. */
for (const bNodeTreeInterfaceSocket *output_socket : node_group.interface_outputs()) {
Result &output_result = node_group_operation.get_result(output_socket->identifier);
if (!output_result.should_compute()) {
continue;
}
/* Realize the output transforms if needed. */
const InputDescriptor input_descriptor = {ResultType::Color,
InputRealizationMode::OperationDomain};
SimpleOperation *realization_operation = RealizeOnDomainOperation::construct_if_needed(
*this, output_result, input_descriptor, output_result.domain());
if (realization_operation) {
realization_operation->map_input_to_result(&output_result);
realization_operation->evaluate();
Result &realized_output_result = realization_operation->get_result();
this->write_output(realized_output_result);
realized_output_result.release();
delete realization_operation;
continue;
}
this->write_output(output_result);
output_result.release();
}
}
};
static bool is_linear_float_buffer(const ImBuf *image)
{
return image->float_buffer.data &&
IMB_colormanagement_space_is_scene_linear(image->float_buffer.colorspace);
}
static ImBuf *make_linear_float_buffer(ImBuf *src)
{
if (!src) {
return nullptr;
}
/* Already have scene linear float pixels, return same buffer. */
if (is_linear_float_buffer(src)) {
return src;
}
ImBuf *dst = IMB_allocImBuf(
src->x, src->y, src->planes, IB_float_data | IB_uninitialized_pixels);
const char *to_colorspace = IMB_colormanagement_role_colorspace_name_get(
COLOR_ROLE_SCENE_LINEAR);
if (src->float_buffer.data == nullptr) {
const char *from_colorspace = IMB_colormanagement_get_rect_colorspace(src);
IMB_colormanagement_transform_byte_to_float(dst->float_buffer.data,
src->byte_buffer.data,
src->x,
src->y,
src->channels,
from_colorspace,
to_colorspace);
}
else {
const char *from_colorspace = IMB_colormanagement_get_float_colorspace(src);
//@TODO: src->dst transform would be faster instead of copy + transform in-place
memcpy(dst->float_buffer.data,
src->float_buffer.data,
IMB_get_pixel_count(src) * src->channels * sizeof(float));
IMB_colormanagement_transform_float(dst->float_buffer.data,
dst->x,
dst->y,
dst->channels,
from_colorspace,
to_colorspace,
true);
}
IMB_colormanagement_assign_float_colorspace(dst, to_colorspace);
return dst;
}
static ImBuf *do_compositor_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip *strip,
float /*timeline_frame*/,
float fac,
ImBuf *src1,
ImBuf *src2)
{
const int x = context->rectx;
const int y = context->recty;
ImBuf *out = IMB_allocImBuf(x, y, 32, IB_float_data | IB_uninitialized_pixels);
IMB_colormanagement_assign_float_colorspace(
out, IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_SCENE_LINEAR));
CompositorEffectVars *data = static_cast<CompositorEffectVars *>(strip->effectdata);
if (!data || !data->node_group) {
IMB_rectfill(out, float4(0, 0, 0, 1));
}
else {
ImBuf *linear_src1 = make_linear_float_buffer(src1);
ImBuf *linear_src2 = make_linear_float_buffer(src2);
/* TODO: Should be persistent across evaluations. */
compositor::StaticCacheManager cache_manager;
CompositorEffectContext com_context(
cache_manager, *context, data->node_group, linear_src1, linear_src2, out, fac, *strip);
com_context.evaluate();
com_context.cache_manager().reset();
// context.result_translation += com_context.get_result_translation(); //@TODO?
if (linear_src1 != src1) {
IMB_freeImBuf(linear_src1);
}
if (linear_src2 != src2) {
IMB_freeImBuf(linear_src2);
}
seq_imbuf_to_sequencer_space(context->scene, out, true);
}
return out;
}
static void init_compositor_effect(Strip *strip)
{
CompositorEffectVars *data = MEM_new<CompositorEffectVars>(__func__);
strip->effectdata = data;
}
static void free_compositor_effect(Strip *strip, const bool /*do_id_user*/)
{
if (strip->effectdata) {
CompositorEffectVars *data = static_cast<CompositorEffectVars *>(strip->effectdata);
MEM_delete(data);
strip->effectdata = nullptr;
}
}
static StripEarlyOut early_out_compositor(const Strip *strip, float /*fac*/)
{
/* No inputs: compositor generates the result. */
if (strip->input1 == nullptr) {
return StripEarlyOut::NoInput;
}
/* One or two inputs: do the effect. */
return StripEarlyOut::DoEffect;
}
void compositor_effect_get_handle(EffectHandle &rval)
{
rval.init = init_compositor_effect;
rval.free = free_compositor_effect;
rval.execute = do_compositor_effect;
rval.early_out = early_out_compositor;
}
} // namespace blender::seq

View file

@ -36,11 +36,6 @@ static void free_gaussian_blur_effect(Strip *strip, const bool /*do_id_user*/)
}
}
static int num_inputs_gaussian_blur()
{
return 1;
}
static StripEarlyOut early_out_gaussian_blur(const Strip *strip, float /*fac*/)
{
GaussianBlurVars *data = static_cast<GaussianBlurVars *>(strip->effectdata);
@ -221,7 +216,6 @@ static ImBuf *do_gaussian_blur_effect(const RenderData *context,
void gaussian_blur_effect_get_handle(EffectHandle &rval)
{
rval.init = init_gaussian_blur_effect;
rval.num_inputs = num_inputs_gaussian_blur;
rval.free = free_gaussian_blur_effect;
rval.early_out = early_out_gaussian_blur;
rval.execute = do_gaussian_blur_effect;

View file

@ -142,11 +142,6 @@ static void free_glow_effect(Strip *strip, const bool /*do_id_user*/)
}
}
static int num_inputs_glow()
{
return 1;
}
static void do_glow_effect_byte(Strip *strip,
int render_size,
float fac,
@ -250,7 +245,6 @@ static ImBuf *do_glow_effect(const RenderData *context,
void glow_effect_get_handle(EffectHandle &rval)
{
rval.init = init_glow_effect;
rval.num_inputs = num_inputs_glow;
rval.free = free_glow_effect;
rval.execute = do_glow_effect;
}

View file

@ -18,12 +18,6 @@
namespace blender::seq {
/* No effect inputs for multi-camera, we use #give_ibuf_seq. */
static int num_inputs_multicam()
{
return 0;
}
static StripEarlyOut early_out_multicam(const Strip * /*strip*/, float /*fac*/)
{
return StripEarlyOut::NoInput;
@ -63,7 +57,6 @@ static ImBuf *do_multicam(const RenderData *context,
void multi_camera_effect_get_handle(EffectHandle &rval)
{
rval.num_inputs = num_inputs_multicam;
rval.early_out = early_out_multicam;
rval.execute = do_multicam;
}

View file

@ -31,11 +31,6 @@ static void free_solid_color(Strip *strip, const bool /*do_id_user*/)
}
}
static int num_inputs_color()
{
return 0;
}
static StripEarlyOut early_out_color(const Strip * /*strip*/, float /*fac*/)
{
return StripEarlyOut::NoInput;
@ -92,7 +87,6 @@ static ImBuf *do_solid_color(const RenderData *context,
void solid_color_effect_get_handle(EffectHandle &rval)
{
rval.init = init_solid_color;
rval.num_inputs = num_inputs_color;
rval.free = free_solid_color;
rval.early_out = early_out_color;
rval.execute = do_solid_color;

View file

@ -45,11 +45,6 @@ static void free_speed_effect(Strip *strip, const bool /*do_id_user*/)
}
}
static int num_inputs_speed()
{
return 1;
}
static void copy_speed_effect(Strip *dst, const Strip *src, const int /*flag*/)
{
SpeedControlVars *v = MEM_dupalloc(static_cast<SpeedControlVars *>(src->effectdata));
@ -202,7 +197,6 @@ static ImBuf *do_speed_effect(const RenderData *context,
void speed_effect_get_handle(EffectHandle &rval)
{
rval.init = init_speed_effect;
rval.num_inputs = num_inputs_speed;
rval.free = free_speed_effect;
rval.copy = copy_speed_effect;
rval.execute = do_speed_effect;

View file

@ -299,11 +299,6 @@ static void copy_text_effect(Strip *dst, const Strip *src, const int flag)
dst->effectdata = data;
}
static int num_inputs_text()
{
return 0;
}
static StripEarlyOut early_out_text(const Strip *strip, float /*fac*/)
{
if (!effects_can_render_text(strip)) {
@ -1106,7 +1101,6 @@ static ImBuf *do_text_effect(const RenderData *context,
void text_effect_get_handle(EffectHandle &rval)
{
rval.num_inputs = num_inputs_text;
rval.init = init_text_effect;
rval.free = free_text_effect;
rval.copy = copy_text_effect;

View file

@ -169,11 +169,6 @@ static void free_wipe_effect(Strip *strip, const bool /*do_id_user*/)
}
}
static int num_inputs_wipe()
{
return 2;
}
template<typename T>
static void do_wipe_effect(
const Strip *strip, float fac, int width, int height, const T *rect1, const T *rect2, T *out)
@ -246,7 +241,6 @@ void wipe_effect_get_handle(EffectHandle &rval)
{
rval.init = init_wipe_effect;
rval.free = free_wipe_effect;
rval.num_inputs = num_inputs_wipe;
rval.early_out = early_out_fade;
rval.execute = do_wipe_effect;
}

View file

@ -15,7 +15,6 @@
#include "BLI_listbase.h"
#include "SEQ_connect.hh"
#include "SEQ_effects.hh"
#include "SEQ_iterator.hh"
#include "SEQ_relations.hh"
#include "SEQ_render.hh"
@ -200,7 +199,7 @@ bool must_render_strip(const VectorSet<Strip *> &strip_stack, Strip *target_stri
}
/* All effects with inputs are rendered assuming they pass the above checks. */
if (target_strip->is_effect() && effect_get_num_inputs(target_strip->type) != 0) {
if (target_strip->is_effect_with_inputs()) {
return true;
}

View file

@ -711,9 +711,8 @@ static ImBuf *seq_render_preprocess_ibuf(const RenderData *context,
}
/* 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));
const bool is_effect_with_inputs = strip->is_effect_with_inputs() ||
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) {
@ -783,7 +782,7 @@ static ImBuf *seq_render_effect_strip_impl(const RenderData *context,
}
}
if (ibuf[0] && (ibuf[1] || effect_get_num_inputs(strip->type) == 1)) {
if (ibuf[0] && (ibuf[1] || strip->effect_num_inputs_get() == 1)) {
out = sh.execute(context, state, strip, timeline_frame, fac, ibuf[0], ibuf[1]);
}
break;

View file

@ -900,6 +900,9 @@ static bool strip_write_data_cb(Strip *strip, void *userdata)
case STRIP_TYPE_COLORMIX:
writer->write_struct_cast<ColorMixVars>(strip->effectdata);
break;
case STRIP_TYPE_COMPOSITOR:
writer->write_struct_cast<CompositorEffectVars>(strip->effectdata);
break;
}
}
@ -999,6 +1002,9 @@ static bool strip_read_data_cb(Strip *strip, void *user_data)
case STRIP_TYPE_COLORMIX:
BLO_read_struct(reader, ColorMixVars, &strip->effectdata);
break;
case STRIP_TYPE_COMPOSITOR:
BLO_read_struct(reader, CompositorEffectVars, &strip->effectdata);
break;
default:
BLI_assert_unreachable();
strip->effectdata = nullptr;
@ -1290,9 +1296,16 @@ ListBaseT<SeqTimelineChannel> *Editing::current_channels() const
bool Strip::is_effect() const
{
return (this->type >= STRIP_TYPE_CROSS && this->type <= STRIP_TYPE_OVERDROP_REMOVED) ||
(this->type >= STRIP_TYPE_WIPE && this->type <= STRIP_TYPE_ADJUSTMENT) ||
(this->type >= STRIP_TYPE_GAUSSIAN_BLUR && this->type <= STRIP_TYPE_COLORMIX);
return blender::seq::strip_type_is_effect(StripType(this->type));
}
int Strip::effect_num_inputs_get() const
{
/* Compositor can have varying amount of inputs; return based on assigned inputs. */
if (this->type == STRIP_TYPE_COMPOSITOR) {
return this->input1 && this->input2 ? 2 : this->input1 ? 1 : 0;
}
return blender::seq::effect_type_get_min_num_inputs(StripType(this->type));
}
} // namespace blender

View file

@ -168,12 +168,13 @@ Strip *add_effect_strip(Scene *scene, ListBaseT<Strip> *seqbase, LoadData *load_
strip->flag |= SEQ_USE_EFFECT_DEFAULT_FADE;
effect_ensure_initialized(strip);
if (effect_get_num_inputs(strip->type) != 0) {
const int min_inputs = effect_type_get_min_num_inputs(load_data->effect.type);
if (min_inputs != 0 || load_data->effect.type == STRIP_TYPE_COMPOSITOR) {
strip->input1 = load_data->effect.input1;
strip->input2 = load_data->effect.input2;
}
if (effect_get_num_inputs(strip->type) == 1) {
if (min_inputs == 1) {
strip->blend_mode = strip->input1->blend_mode;
strip->blend_opacity = strip->input1->blend_opacity;
}

View file

@ -64,7 +64,7 @@ bool edit_strip_swap(Scene *scene, Strip *strip_a, Strip *strip_b, const char **
}
if (strip_a->is_effect() && strip_b->is_effect()) {
if (effect_get_num_inputs(strip_a->type) != effect_get_num_inputs(strip_b->type)) {
if (strip_a->effect_num_inputs_get() != strip_b->effect_num_inputs_get()) {
*r_error_str = N_("Strips must have the same number of inputs");
return false;
}
@ -391,7 +391,7 @@ static bool seq_edit_split_operation_permitted_check(const Scene *scene,
if (!seq_edit_split_intersect_check(scene, strip, timeline_frame)) {
continue;
}
if (effect_get_num_inputs(strip->type) <= 1) {
if (strip->effect_num_inputs_get() <= 1) {
continue;
}
if (effect_is_transition(StripType(strip->type))) {

View file

@ -57,6 +57,15 @@ static void strip_by_scene_lookup_build(Strip *strip, StripLookup *lookup)
static void strip_by_compositor_node_group_lookup_build(Strip *strip, StripLookup *lookup)
{
if (strip->type == STRIP_TYPE_COMPOSITOR && strip->effectdata) {
const CompositorEffectVars *comp_data = static_cast<CompositorEffectVars *>(strip->effectdata);
if (comp_data->node_group) {
VectorSet<Strip *> &strips = lookup->strips_by_compositor_node_group.lookup_or_add_default(
comp_data->node_group);
strips.add(strip);
}
}
for (StripModifierData &modifier : strip->modifiers) {
if (modifier.type != eSeqModifierType_Compositor) {
continue;

View file

@ -166,6 +166,11 @@ void relations_invalidate_cache(Scene *scene, Strip *strip)
strip_effect_speed_rebuild_map(scene, strip);
}
/* Zero-input compositor effect source caches also need to be invalidated. */
if (strip->type == STRIP_TYPE_COMPOSITOR && !strip->is_effect_with_inputs()) {
source_image_cache_invalidate_strip(scene, strip);
}
invalidate_final_cache_strip_range(scene, strip);
intra_frame_cache_invalidate(scene, strip);
preview_cache_invalidate(scene);
@ -187,7 +192,7 @@ void relations_invalidate_scene_strips(const Main *bmain, const Scene *scene_tar
}
}
void relations_invalidate_compositor_modifiers(const Main *bmain, const bNodeTree *node_tree)
void relations_invalidate_compositor_users(const Main *bmain, const bNodeTree *node_tree)
{
for (Scene &scene : bmain->scenes) {
if (scene.ed != nullptr) {

View file

@ -65,4 +65,17 @@ bool select_active_get_pair(Scene *scene, Strip **r_strip_act, Strip **r_strip_o
return (*r_strip_other != nullptr);
}
bool select_has_any(const Scene *scene)
{
Editing *ed = editing_get(scene);
if (ed != nullptr) {
for (Strip &strip : *ed->current_strips()) {
if (strip.flag & SEQ_SELECT) {
return true;
}
}
}
return false;
}
} // namespace blender::seq

View file

@ -25,7 +25,6 @@
#include "SEQ_animation.hh"
#include "SEQ_channels.hh"
#include "SEQ_edit.hh"
#include "SEQ_effects.hh"
#include "SEQ_iterator.hh"
#include "SEQ_relations.hh"
#include "SEQ_sequencer.hh"
@ -45,7 +44,7 @@ bool transform_single_image_check(const Strip *strip)
bool transform_strip_can_be_translated(const Strip *strip)
{
return !strip->is_effect() || (effect_get_num_inputs(strip->type) == 0);
return !strip->is_effect_with_inputs();
}
bool transform_test_overlap(const Scene *scene, Strip *strip1, Strip *strip2)
@ -433,7 +432,7 @@ static void strip_transform_handle_overwrite_trim(Scene *scene,
/* Trim all non effects, that have influence on effect length which is overlapping. */
for (Strip *strip : targets) {
if (strip->is_effect() && effect_get_num_inputs(strip->type) > 0) {
if (strip->is_effect_with_inputs()) {
continue;
}
if (overlap == STRIP_OVERLAP_LEFT_SIDE) {

View file

@ -127,6 +127,8 @@ const char *get_default_stripname_by_type(int type)
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Crossfade");
case STRIP_TYPE_GAMCROSS:
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Gamma Crossfade");
case STRIP_TYPE_COMPOSITOR:
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Compositor");
case STRIP_TYPE_ADD:
return CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, "Add");
case STRIP_TYPE_SUB:

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d5f7836d7cb43277b4e29291eece9fd59abb00b43eb701605012a230f77bc9d6
size 85549

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:13dd3ff3f251a23b1cdeced0a1799dbcba6d86e441da1f0f119840d90341b93a
size 67267