Files
test/source/blender/sequencer/intern/modifiers/MOD_compositor.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

243 lines
6.9 KiB
C++

/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "BLI_math_base.h"
#include "BLI_rect.h"
#include "BLT_translation.hh"
#include "COM_context.hh"
#include "COM_domain.hh"
#include "COM_evaluator.hh"
#include "DNA_sequence_types.h"
#include "DEG_depsgraph_query.hh"
#include "IMB_colormanagement.hh"
#include "SEQ_modifier.hh"
#include "SEQ_modifiertypes.hh"
#include "SEQ_render.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "RNA_access.hh"
#include "modifier.hh"
#include "render.hh"
namespace blender::seq {
class CompositorContext : public compositor::Context {
private:
const RenderData &render_data_;
const SequencerCompositorModifierData *modifier_data_;
ImBuf *image_buffer_;
ImBuf *mask_buffer_;
public:
CompositorContext(const RenderData &render_data,
const SequencerCompositorModifierData *modifier_data,
ImBuf *image_buffer,
ImBuf *mask_buffer)
: compositor::Context(),
render_data_(render_data),
modifier_data_(modifier_data),
image_buffer_(image_buffer),
mask_buffer_(mask_buffer)
{
}
const Scene &get_scene() const override
{
return *render_data_.scene;
}
const bNodeTree &get_node_tree() const override
{
return *DEG_get_evaluated<bNodeTree>(render_data_.depsgraph, modifier_data_->node_group);
}
compositor::OutputTypes needed_outputs() const override
{
compositor::OutputTypes needed_outputs = compositor::OutputTypes::Composite;
if (!render_data_.for_render) {
needed_outputs |= compositor::OutputTypes::Viewer;
}
return needed_outputs;
}
bool treat_viewer_as_compositor_output() const override
{
return true;
}
Bounds<int2> get_compositing_region() const override
{
return Bounds<int2>(int2(0), int2(image_buffer_->x, image_buffer_->y));
}
compositor::Result get_output() override
{
compositor::Result result = this->create_result(compositor::ResultType::Color);
result.wrap_external(image_buffer_->float_buffer.data,
int2(image_buffer_->x, image_buffer_->y));
return result;
}
compositor::Result get_viewer_output(compositor::Domain /*domain*/,
bool /*is_data*/,
compositor::ResultPrecision /*precision*/) override
{
compositor::Result result = this->create_result(compositor::ResultType::Color);
result.wrap_external(image_buffer_->float_buffer.data,
int2(image_buffer_->x, image_buffer_->y));
return result;
}
compositor::Result get_input(const Scene * /*scene*/,
int /*view_layer_id*/,
const char *pass_name) override
{
compositor::Result result = this->create_result(compositor::ResultType::Color);
if (StringRef(pass_name) == "Image") {
result.wrap_external(image_buffer_->float_buffer.data,
int2(image_buffer_->x, image_buffer_->y));
}
else if (StringRef(pass_name) == "Mask" && mask_buffer_) {
result.wrap_external(mask_buffer_->float_buffer.data,
int2(mask_buffer_->x, mask_buffer_->y));
}
return result;
}
bool use_gpu() const override
{
return false;
}
};
static void compositor_modifier_init_data(StripModifierData *strip_modifier_data)
{
SequencerCompositorModifierData *modifier_data =
reinterpret_cast<SequencerCompositorModifierData *>(strip_modifier_data);
modifier_data->node_group = nullptr;
}
static bool ensure_linear_float_buffer(ImBuf *ibuf)
{
if (!ibuf) {
return false;
}
/* Already have scene linear float pixels, nothing to do. */
if (ibuf->float_buffer.data &&
IMB_colormanagement_space_is_scene_linear(ibuf->float_buffer.colorspace))
{
return true;
}
if (ibuf->float_buffer.data == nullptr) {
IMB_float_from_byte(ibuf);
}
else {
const char *from_colorspace = IMB_colormanagement_get_float_colorspace(ibuf);
const char *to_colorspace = IMB_colormanagement_role_colorspace_name_get(
COLOR_ROLE_SCENE_LINEAR);
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);
}
return false;
}
static void compositor_modifier_apply(const RenderData *render_data,
const StripScreenQuad & /*quad*/,
StripModifierData *strip_modifier_data,
ImBuf *image_buffer,
ImBuf *mask)
{
const SequencerCompositorModifierData *modifier_data =
reinterpret_cast<SequencerCompositorModifierData *>(strip_modifier_data);
if (!modifier_data->node_group) {
return;
}
ensure_linear_float_buffer(mask);
const bool was_float_linear = ensure_linear_float_buffer(image_buffer);
const bool was_byte = image_buffer->float_buffer.data == nullptr;
CompositorContext context(*render_data, modifier_data, image_buffer, mask);
compositor::Evaluator evaluator(context);
evaluator.evaluate();
if (was_float_linear) {
return;
}
if (was_byte) {
IMB_byte_from_float(image_buffer);
IMB_free_float_pixels(image_buffer);
}
else {
seq_imbuf_to_sequencer_space(render_data->scene, image_buffer, true);
}
}
static void compositor_modifier_panel_draw(const bContext *C, Panel *panel)
{
uiLayout *layout = panel->layout;
PointerRNA *ptr = UI_panel_custom_data_get(panel);
layout->use_property_split_set(true);
uiTemplateID(layout,
C,
ptr,
"node_group",
"NODE_OT_new_compositor_sequencer_node_group",
nullptr,
nullptr);
if (uiLayout *mask_input_layout = layout->panel_prop(
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
{
draw_mask_input_type_settings(C, mask_input_layout, ptr);
}
}
static void compositor_modifier_register(ARegionType *region_type)
{
modifier_panel_register(
region_type, eSeqModifierType_Compositor, compositor_modifier_panel_draw);
}
StripModifierTypeInfo seqModifierType_Compositor = {
/*idname*/ "Compositor",
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Compositor"),
/*struct_name*/ "SequencerCompositorModifierData",
/*struct_size*/ sizeof(SequencerCompositorModifierData),
/*init_data*/ compositor_modifier_init_data,
/*free_data*/ nullptr,
/*copy_data*/ nullptr,
/*apply*/ compositor_modifier_apply,
/*panel_register*/ compositor_modifier_register,
};
}; // namespace blender::seq