Files
test/source/blender/nodes/composite/node_composite_tree.cc
Omar Emara 73fcbaf7c4 VSE: Add Compositor modifier
This patch adds a new Compositor modifier that applies a compositing
node group on a sequencer strip. This patch also introduces the concept
of a compositor node tree space subtype, where we now have a Scene and a
Sequencer subtypes. Practically, this just means that node like the
Render Layers node will not be available in the node editor in the
Sequencer subtype.

Future improvements includes:

- The compositor context is recreated on every modifier application,
  while it should ideally be persistent somehow to make use of the
  compositor static cache. This might require work from the compositor
  side by moving the static cache outside of the context and make it
  thread safe if needed. See `Render.compositor` for an example on
  persistent context.
- GPU execution is not supported. This just needs a GPU context to be
  bound before execution, but the tricky part is getting a GPU context.
  See `render::Compositor::execute` for an example on bounding a GPU
  context and why it is less straight forward.
- Node inputs are not exposed on the sequencer modifier interface. An
  approach similar to Geometry Nodes modifier could be used, look at
  `update_input_properties_from_node_tree` for reference, but notice
  that Geometry Nodes naturally exempt the main Geometry socket because
  Geometry inputs can't be exposed, but for the compositor, we will have
  to exempt the main Color and Mask sockets manually. !145971

Co-authored-by: Aras Pranckevicius <aras@nesnausk.org>
Co-authored-by: Falk David <falk@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/139634
2025-09-26 08:40:42 +02:00

278 lines
8.4 KiB
C++

/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup nodes
*/
#include "DNA_node_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "BLI_listbase.h"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_image.hh"
#include "BKE_main.hh"
#include "BKE_node.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_tracking.h"
#include "UI_resources.hh"
#include "SEQ_modifier.hh"
#include "SEQ_select.hh"
#include "SEQ_sequencer.hh"
#include "node_common.h"
#include "RNA_prototypes.hh"
#include "NOD_composite.hh"
#include "node_composite_util.hh"
static void composite_get_from_context(const bContext *C,
blender::bke::bNodeTreeType * /*treetype*/,
bNodeTree **r_ntree,
ID **r_id,
ID **r_from)
{
using namespace blender;
const SpaceNode *snode = CTX_wm_space_node(C);
if (snode->node_tree_sub_type == SNODE_COMPOSITOR_SEQUENCER) {
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;
}
if (smd->type != eSeqModifierType_Compositor) {
*r_ntree = nullptr;
return;
}
SequencerCompositorModifierData *scmd = reinterpret_cast<SequencerCompositorModifierData *>(
smd);
*r_from = nullptr;
*r_id = &sequencer_scene->id;
*r_ntree = scmd->node_group;
return;
}
Scene *scene = CTX_data_scene(C);
*r_from = nullptr;
*r_id = &scene->id;
*r_ntree = scene->compositing_node_group;
}
static void foreach_nodeclass(void *calldata, blender::bke::bNodeClassCallback func)
{
func(calldata, NODE_CLASS_INPUT, N_("Input"));
func(calldata, NODE_CLASS_OUTPUT, N_("Output"));
func(calldata, NODE_CLASS_OP_COLOR, N_("Color"));
func(calldata, NODE_CLASS_OP_VECTOR, N_("Vector"));
func(calldata, NODE_CLASS_OP_FILTER, N_("Filter"));
func(calldata, NODE_CLASS_CONVERTER, N_("Converter"));
func(calldata, NODE_CLASS_MATTE, N_("Matte"));
func(calldata, NODE_CLASS_DISTORT, N_("Distort"));
func(calldata, NODE_CLASS_GROUP, N_("Group"));
func(calldata, NODE_CLASS_INTERFACE, N_("Interface"));
func(calldata, NODE_CLASS_LAYOUT, N_("Layout"));
}
/* local tree then owns all compbufs */
static void localize(bNodeTree *localtree, bNodeTree *ntree)
{
bNode *node = (bNode *)ntree->nodes.first;
bNode *local_node = (bNode *)localtree->nodes.first;
while (node != nullptr) {
/* Ensure new user input gets handled ok. */
node->runtime->need_exec = 0;
local_node->runtime->original = node;
/* move over the compbufs */
/* right after #blender::bke::node_tree_copy_tree() `oldsock` pointers are valid */
node = node->next;
local_node = local_node->next;
}
}
static void local_merge(Main * /*bmain*/, bNodeTree *localtree, bNodeTree *ntree)
{
/* move over the compbufs and previews */
blender::bke::node_preview_merge_tree(ntree, localtree, true);
LISTBASE_FOREACH (bNode *, lnode, &localtree->nodes) {
if (bNode *orig_node = blender::bke::node_find_node_by_name(*ntree, lnode->name)) {
if (lnode->type_legacy == CMP_NODE_MOVIEDISTORTION) {
/* special case for distortion node: distortion context is allocating in exec function
* and to achieve much better performance on further calls this context should be
* copied back to original node */
if (lnode->storage) {
if (orig_node->storage) {
BKE_tracking_distortion_free((MovieDistortion *)orig_node->storage);
}
orig_node->storage = BKE_tracking_distortion_copy((MovieDistortion *)lnode->storage);
}
}
}
}
}
static void update(bNodeTree *ntree)
{
blender::bke::node_tree_set_output(*ntree);
ntree_update_reroute_nodes(ntree);
}
static void composite_node_add_init(bNodeTree * /*bnodetree*/, bNode *bnode)
{
/* Composite node will only show previews for input classes
* by default, other will be hidden
* but can be made visible with the show_preview option */
if (bnode->typeinfo->nclass != NODE_CLASS_INPUT) {
bnode->flag &= ~NODE_PREVIEW;
}
}
static bool composite_node_tree_socket_type_valid(blender::bke::bNodeTreeType * /*ntreetype*/,
blender::bke::bNodeSocketType *socket_type)
{
return blender::bke::node_is_static_socket_type(*socket_type) && ELEM(socket_type->type,
SOCK_FLOAT,
SOCK_INT,
SOCK_BOOLEAN,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_MENU,
SOCK_STRING);
}
/**
* Keep consistent with the #is_conversion_supported function in #compositor::ConversionOperation
* on the compositor side.
*/
static bool composite_validate_link(eNodeSocketDatatype from_type, eNodeSocketDatatype to_type)
{
/* Basic math types can be implicitly converted to each other. */
if (ELEM(from_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT) &&
ELEM(to_type, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_BOOLEAN, SOCK_INT))
{
return true;
}
return from_type == to_type;
}
blender::bke::bNodeTreeType *ntreeType_Composite;
void register_node_tree_type_cmp()
{
blender::bke::bNodeTreeType *tt = ntreeType_Composite = MEM_new<blender::bke::bNodeTreeType>(
__func__);
tt->type = NTREE_COMPOSIT;
tt->idname = "CompositorNodeTree";
tt->group_idname = "CompositorNodeGroup";
tt->ui_name = N_("Compositor");
tt->ui_icon = ICON_NODE_COMPOSITING;
tt->ui_description = N_("Compositing nodes");
tt->foreach_nodeclass = foreach_nodeclass;
tt->localize = localize;
tt->local_merge = local_merge;
tt->update = update;
tt->get_from_context = composite_get_from_context;
tt->node_add_init = composite_node_add_init;
tt->validate_link = composite_validate_link;
tt->valid_socket_type = composite_node_tree_socket_type_valid;
tt->rna_ext.srna = &RNA_CompositorNodeTree;
blender::bke::node_tree_type_add(*tt);
}
/* *********************************************** */
void ntreeCompositUpdateRLayers(bNodeTree *ntree)
{
if (ntree == nullptr) {
return;
}
for (bNode *node : ntree->all_nodes()) {
if (node->type_legacy == CMP_NODE_R_LAYERS) {
node_cmp_rlayers_outputs(ntree, node);
}
else if (node->type_legacy == CMP_NODE_CRYPTOMATTE &&
node->custom1 == CMP_NODE_CRYPTOMATTE_SOURCE_RENDER)
{
node->typeinfo->updatefunc(ntree, node);
}
}
}
void ntreeCompositTagRender(Scene *scene)
{
/* XXX Think using G_MAIN here is valid, since you want to update current file's scene nodes,
* not the ones in temp main generated for rendering?
* This is still rather weak though,
* ideally render struct would store its own main AND original G_MAIN. */
for (Scene *sce_iter = (Scene *)G_MAIN->scenes.first; sce_iter;
sce_iter = (Scene *)sce_iter->id.next)
{
if (sce_iter->compositing_node_group) {
for (bNode *node : sce_iter->compositing_node_group->all_nodes()) {
if (node->id == (ID *)scene) {
BKE_ntree_update_tag_node_property(sce_iter->compositing_node_group, node);
}
}
}
}
BKE_ntree_update(*G_MAIN);
}
void ntreeCompositClearTags(bNodeTree *ntree)
{
/* XXX: after render animation system gets a refresh, this call allows composite to end clean. */
if (ntree == nullptr) {
return;
}
for (bNode *node : ntree->all_nodes()) {
node->runtime->need_exec = 0;
if (node->is_group()) {
ntreeCompositClearTags((bNodeTree *)node->id);
}
}
}
void ntreeCompositTagNeedExec(bNode *node)
{
node->runtime->need_exec = true;
}