From 865204fef06b1f4e73a3ad82202fe8221d1efae5 Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Wed, 10 Aug 2022 09:45:28 +0200 Subject: Realtime Compositor: Add basic input nodes This patch implements the following nodes for the realtime compositor: - Image node. - Movie clip node. - Render layers node. - RGB node. - Scene time node. - Value node. Differential Revision: https://developer.blender.org/D15227 Reviewed By: Clement Foucault --- .../nodes/composite/nodes/node_composite_image.cc | 275 ++++++++++++++++++++- .../composite/nodes/node_composite_movieclip.cc | 183 ++++++++++++++ .../nodes/composite/nodes/node_composite_rgb.cc | 30 +++ .../composite/nodes/node_composite_scene_time.cc | 36 +++ .../nodes/composite/nodes/node_composite_value.cc | 26 ++ 5 files changed, 547 insertions(+), 3 deletions(-) (limited to 'source/blender/nodes') diff --git a/source/blender/nodes/composite/nodes/node_composite_image.cc b/source/blender/nodes/composite/nodes/node_composite_image.cc index d75aa575395..d8852e9333f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_image.cc +++ b/source/blender/nodes/composite/nodes/node_composite_image.cc @@ -8,6 +8,7 @@ #include "node_composite_util.hh" #include "BLI_linklist.h" +#include "BLI_math_vec_types.hh" #include "BLI_utildefines.h" #include "BKE_context.h" @@ -17,6 +18,8 @@ #include "BKE_main.h" #include "BKE_scene.h" +#include "DEG_depsgraph_query.h" + #include "DNA_scene_types.h" #include "RE_engine.h" @@ -27,6 +30,12 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_shader.h" +#include "GPU_texture.h" + +#include "COM_node_operation.hh" +#include "COM_utilities.hh" + /* **************** IMAGE (and RenderResult, multilayer image) ******************** */ static bNodeSocketTemplate cmp_node_rlayers_out[] = { @@ -433,6 +442,215 @@ static void node_composit_copy_image(bNodeTree *UNUSED(dest_ntree), } } +using namespace blender::realtime_compositor; + +class ImageOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + if (!is_valid()) { + allocate_invalid(); + return; + } + + update_image_frame_number(); + + for (const OutputSocketRef *output : node()->outputs()) { + compute_output(output->identifier()); + } + } + + /* Returns true if the node results can be computed, otherwise, returns false. */ + bool is_valid() + { + Image *image = get_image(); + ImageUser *image_user = get_image_user(); + if (!image || !image_user) { + return false; + } + + if (BKE_image_is_multilayer(image)) { + if (!image->rr) { + return false; + } + + RenderLayer *render_layer = get_render_layer(); + if (!render_layer) { + return false; + } + } + + return true; + } + + /* Allocate all needed outputs as invalid. This should be called when is_valid returns false. */ + void allocate_invalid() + { + for (const OutputSocketRef *output : node()->outputs()) { + if (!should_compute_output(output->identifier())) { + continue; + } + + Result &result = get_result(output->identifier()); + result.allocate_invalid(); + } + } + + /* Compute the effective frame number of the image if it was animated and invalidate the cached + * GPU texture if the computed frame number is different. */ + void update_image_frame_number() + { + BKE_image_user_frame_calc(get_image(), get_image_user(), context().get_frame_number()); + } + + void compute_output(StringRef identifier) + { + if (!should_compute_output(identifier)) { + return; + } + + ImageUser image_user = compute_image_user_for_output(identifier); + GPUTexture *image_texture = BKE_image_get_gpu_texture(get_image(), &image_user, nullptr); + + const int2 size = int2(GPU_texture_width(image_texture), GPU_texture_height(image_texture)); + Result &result = get_result(identifier); + result.allocate_texture(Domain(size)); + + GPUShader *shader = shader_manager().get(get_shader_name(identifier)); + GPU_shader_bind(shader); + + const int input_unit = GPU_shader_get_texture_binding(shader, "input_tx"); + GPU_texture_bind(image_texture, input_unit); + + result.bind_as_image(shader, "output_img"); + + compute_dispatch_threads_at_least(shader, size); + + GPU_shader_unbind(); + GPU_texture_unbind(image_texture); + result.unbind_as_image(); + } + + /* Get a copy of the image user that is appropriate to retrieve the image buffer for the output + * with the given identifier. This essentially sets the appropriate pass and view indices that + * corresponds to the output. */ + ImageUser compute_image_user_for_output(StringRef identifier) + { + ImageUser image_user = *get_image_user(); + + /* Set the needed view. */ + image_user.view = get_view_index(); + + /* Set the needed pass. */ + if (BKE_image_is_multilayer(get_image())) { + image_user.pass = get_pass_index(get_pass_name(identifier)); + BKE_image_multilayer_index(get_image()->rr, &image_user); + } + else { + BKE_image_multiview_index(get_image(), &image_user); + } + + return image_user; + } + + /* Get the shader that should be used to compute the output with the given identifier. The + * shaders just copy the retrieved image textures into the results except for the alpha output, + * which extracts the alpha and writes it to the result instead. Note that a call to a host + * texture copy doesn't work because results are stored in a different half float formats. */ + const char *get_shader_name(StringRef identifier) + { + if (identifier == "Alpha") { + return "compositor_extract_alpha_from_color"; + } + else if (get_result(identifier).type() == ResultType::Color) { + return "compositor_convert_color_to_half_color"; + } + else { + return "compositor_convert_float_to_half_float"; + } + } + + Image *get_image() + { + return (Image *)bnode().id; + } + + ImageUser *get_image_user() + { + return static_cast(bnode().storage); + } + + /* Get the render layer selected in the node assuming the image is a multilayer image. */ + RenderLayer *get_render_layer() + { + const ListBase *layers = &get_image()->rr->layers; + return static_cast(BLI_findlink(layers, get_image_user()->layer)); + } + + /* Get the name of the pass corresponding to the output with the given identifier assuming the + * image is a multilayer image. */ + const char *get_pass_name(StringRef identifier) + { + DOutputSocket output = node().output_by_identifier(identifier); + return static_cast(output->bsocket()->storage)->pass_name; + } + + /* Get the index of the pass with the given name in the selected render layer's passes list + * assuming the image is a multilayer image. */ + int get_pass_index(const char *name) + { + return BLI_findstringindex(&get_render_layer()->passes, name, offsetof(RenderPass, name)); + } + + /* Get the index of the view selected in the node. If the image is not a multi-view image or only + * has a single view, then zero is returned. Otherwise, if the image is a multi-view image, the + * index of the selected view is returned. However, note that the value of the view member of the + * image user is not the actual index of the view. More specifically, the index 0 is reserved to + * denote the special mode of operation "All", which dynamically selects the view whose name + * matches the view currently being rendered. It follows that the views are then indexed starting + * from 1. So for non zero view values, the actual index of the view is the value of the view + * member of the image user minus 1. */ + int get_view_index() + { + /* The image is not a multi-view image, so just return zero. */ + if (!BKE_image_is_multiview(get_image())) { + return 0; + } + + const ListBase *views = &get_image()->rr->views; + /* There is only one view and its index is 0. */ + if (BLI_listbase_count_at_most(views, 2) < 2) { + return 0; + } + + const int view = get_image_user()->view; + /* The view is not zero, which means it is manually specified and the actual index is then the + * view value minus 1. */ + if (view != 0) { + return view - 1; + } + + /* Otherwise, the view value is zero, denoting the special mode of operation "All", which finds + * the index of the view whose name matches the view currently being rendered. */ + const char *view_name = context().get_view_name().data(); + const int matched_view = BLI_findstringindex(views, view_name, offsetof(RenderView, name)); + + /* No view matches the view currently being rendered, so fallback to the first view. */ + if (matched_view == -1) { + return 0; + } + + return matched_view; + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new ImageOperation(context, node); +} + } // namespace blender::nodes::node_composite_image_cc void register_node_type_cmp_image() @@ -446,6 +664,7 @@ void register_node_type_cmp_image() node_type_storage( &ntype, "ImageUser", file_ns::node_composit_free_image, file_ns::node_composit_copy_image); node_type_update(&ntype, file_ns::cmp_node_image_update); + ntype.get_compositor_operation = file_ns::get_compositor_operation; ntype.labelfunc = node_image_label; ntype.flag |= NODE_PREVIEW; @@ -469,7 +688,7 @@ const char *node_cmp_rlayers_sock_to_pass(int sock_index) return (STREQ(name, "Alpha")) ? RE_PASSNAME_COMBINED : name; } -namespace blender::nodes::node_composite_image_cc { +namespace blender::nodes::node_composite_render_layer_cc { static void node_composit_init_rlayers(const bContext *C, PointerRNA *ptr) { @@ -595,11 +814,60 @@ static void node_composit_buts_viewlayers(uiLayout *layout, bContext *C, Pointer RNA_string_set(&op_ptr, "scene", scene_name); } -} // namespace blender::nodes::node_composite_image_cc +using namespace blender::realtime_compositor; + +class RenderLayerOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + const int view_layer = bnode().custom1; + GPUTexture *pass_texture = context().get_input_texture(view_layer, SCE_PASS_COMBINED); + const int2 size = int2(GPU_texture_width(pass_texture), GPU_texture_height(pass_texture)); + + /* Compute image output. */ + Result &image_result = get_result("Image"); + image_result.allocate_texture(Domain(size)); + GPU_texture_copy(image_result.texture(), pass_texture); + + /* Compute alpha output. */ + Result &alpha_result = get_result("Alpha"); + alpha_result.allocate_texture(Domain(size)); + + GPUShader *shader = shader_manager().get("compositor_extract_alpha_from_color"); + GPU_shader_bind(shader); + + const int input_unit = GPU_shader_get_texture_binding(shader, "input_tx"); + GPU_texture_bind(pass_texture, input_unit); + + alpha_result.bind_as_image(shader, "output_img"); + + compute_dispatch_threads_at_least(shader, size); + + GPU_shader_unbind(); + GPU_texture_unbind(pass_texture); + alpha_result.unbind_as_image(); + + /* Other output passes are not supported for now, so allocate them as invalid. */ + for (const OutputSocketRef *output : node()->outputs()) { + if (output->identifier() != "Image" && output->identifier() != "Alpha") { + get_result(output->identifier()).allocate_invalid(); + } + } + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new RenderLayerOperation(context, node); +} + +} // namespace blender::nodes::node_composite_render_layer_cc void register_node_type_cmp_rlayers() { - namespace file_ns = blender::nodes::node_composite_image_cc; + namespace file_ns = blender::nodes::node_composite_render_layer_cc; static bNodeType ntype; @@ -608,6 +876,7 @@ void register_node_type_cmp_rlayers() ntype.draw_buttons = file_ns::node_composit_buts_viewlayers; ntype.initfunc_api = file_ns::node_composit_init_rlayers; ntype.poll = file_ns::node_composit_poll_rlayers; + ntype.get_compositor_operation = file_ns::get_compositor_operation; ntype.flag |= NODE_PREVIEW; node_type_storage( &ntype, nullptr, file_ns::node_composit_free_rlayers, file_ns::node_composit_copy_rlayers); diff --git a/source/blender/nodes/composite/nodes/node_composite_movieclip.cc b/source/blender/nodes/composite/nodes/node_composite_movieclip.cc index a4d5f294fe0..ec95de3da18 100644 --- a/source/blender/nodes/composite/nodes/node_composite_movieclip.cc +++ b/source/blender/nodes/composite/nodes/node_composite_movieclip.cc @@ -5,8 +5,13 @@ * \ingroup cmpnodes */ +#include "BLI_math_vec_types.hh" + #include "BKE_context.h" #include "BKE_lib_id.h" +#include "BKE_movieclip.h" +#include "BKE_tracking.h" + #include "DNA_defaults.h" #include "RNA_access.h" @@ -14,6 +19,12 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_shader.h" +#include "GPU_texture.h" + +#include "COM_node_operation.hh" +#include "COM_utilities.hh" + #include "node_composite_util.hh" namespace blender::nodes::node_composite_movieclip_cc { @@ -79,6 +90,177 @@ static void node_composit_buts_movieclip_ex(uiLayout *layout, bContext *C, Point uiTemplateColorspaceSettings(layout, &clipptr, "colorspace_settings"); } +using namespace blender::realtime_compositor; + +class MovieClipOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + GPUTexture *movie_clip_texture = get_movie_clip_texture(); + + compute_image(movie_clip_texture); + compute_alpha(movie_clip_texture); + compute_stabilization_data(movie_clip_texture); + + free_movie_clip_texture(); + } + + void compute_image(GPUTexture *movie_clip_texture) + { + if (!should_compute_output("Image")) { + return; + } + + Result &result = get_result("Image"); + + /* The movie clip texture is invalid or missing, set an appropriate fallback value. */ + if (!movie_clip_texture) { + result.allocate_invalid(); + return; + } + + const int2 size = int2(GPU_texture_width(movie_clip_texture), + GPU_texture_height(movie_clip_texture)); + result.allocate_texture(Domain(size)); + + GPUShader *shader = shader_manager().get("compositor_convert_color_to_half_color"); + GPU_shader_bind(shader); + + const int input_unit = GPU_shader_get_texture_binding(shader, "input_tx"); + GPU_texture_bind(movie_clip_texture, input_unit); + + result.bind_as_image(shader, "output_img"); + + compute_dispatch_threads_at_least(shader, size); + + GPU_shader_unbind(); + GPU_texture_unbind(movie_clip_texture); + result.unbind_as_image(); + } + + void compute_alpha(GPUTexture *movie_clip_texture) + { + if (!should_compute_output("Alpha")) { + return; + } + + Result &result = get_result("Alpha"); + + /* The movie clip texture is invalid or missing, set an appropriate fallback value. */ + if (!movie_clip_texture) { + result.allocate_single_value(); + result.set_float_value(1.0f); + return; + } + + const int2 size = int2(GPU_texture_width(movie_clip_texture), + GPU_texture_height(movie_clip_texture)); + result.allocate_texture(Domain(size)); + + GPUShader *shader = shader_manager().get("compositor_extract_alpha_from_color"); + GPU_shader_bind(shader); + + const int input_unit = GPU_shader_get_texture_binding(shader, "input_tx"); + GPU_texture_bind(movie_clip_texture, input_unit); + + result.bind_as_image(shader, "output_img"); + + compute_dispatch_threads_at_least(shader, size); + + GPU_shader_unbind(); + GPU_texture_unbind(movie_clip_texture); + result.unbind_as_image(); + } + + void compute_stabilization_data(GPUTexture *movie_clip_texture) + { + /* The movie clip texture is invalid or missing, set appropriate fallback values. */ + if (!movie_clip_texture) { + if (should_compute_output("Offset X")) { + Result &result = get_result("Offset X"); + result.allocate_single_value(); + result.set_float_value(0.0f); + } + if (should_compute_output("Offset Y")) { + Result &result = get_result("Offset Y"); + result.allocate_single_value(); + result.set_float_value(0.0f); + } + if (should_compute_output("Scale")) { + Result &result = get_result("Scale"); + result.allocate_single_value(); + result.set_float_value(1.0f); + } + if (should_compute_output("Angle")) { + Result &result = get_result("Angle"); + result.allocate_single_value(); + result.set_float_value(0.0f); + } + return; + } + + MovieClip *movie_clip = get_movie_clip(); + const int frame_number = BKE_movieclip_remap_scene_to_clip_frame(movie_clip, + context().get_frame_number()); + const int width = GPU_texture_width(movie_clip_texture); + const int height = GPU_texture_height(movie_clip_texture); + + /* If the movie clip has no stabilization data, it will initialize the given values with + * fallback values regardless, so no need to handle that case. */ + float2 offset; + float scale, angle; + BKE_tracking_stabilization_data_get( + movie_clip, frame_number, width, height, offset, &scale, &angle); + + if (should_compute_output("Offset X")) { + Result &result = get_result("Offset X"); + result.allocate_single_value(); + result.set_float_value(offset.x); + } + if (should_compute_output("Offset Y")) { + Result &result = get_result("Offset Y"); + result.allocate_single_value(); + result.set_float_value(offset.y); + } + if (should_compute_output("Scale")) { + Result &result = get_result("Scale"); + result.allocate_single_value(); + result.set_float_value(scale); + } + if (should_compute_output("Angle")) { + Result &result = get_result("Angle"); + result.allocate_single_value(); + result.set_float_value(angle); + } + } + + GPUTexture *get_movie_clip_texture() + { + MovieClip *movie_clip = get_movie_clip(); + MovieClipUser *movie_clip_user = static_cast(bnode().storage); + BKE_movieclip_user_set_frame(movie_clip_user, context().get_frame_number()); + return BKE_movieclip_get_gpu_texture(movie_clip, movie_clip_user); + } + + void free_movie_clip_texture() + { + MovieClip *movie_clip = get_movie_clip(); + return BKE_movieclip_free_gputexture(movie_clip); + } + + MovieClip *get_movie_clip() + { + return (MovieClip *)bnode().id; + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new MovieClipOperation(context, node); +} + } // namespace blender::nodes::node_composite_movieclip_cc void register_node_type_cmp_movieclip() @@ -91,6 +273,7 @@ void register_node_type_cmp_movieclip() ntype.declare = file_ns::cmp_node_movieclip_declare; ntype.draw_buttons = file_ns::node_composit_buts_movieclip; ntype.draw_buttons_ex = file_ns::node_composit_buts_movieclip_ex; + ntype.get_compositor_operation = file_ns::get_compositor_operation; ntype.initfunc_api = file_ns::init; ntype.flag |= NODE_PREVIEW; node_type_storage( diff --git a/source/blender/nodes/composite/nodes/node_composite_rgb.cc b/source/blender/nodes/composite/nodes/node_composite_rgb.cc index 5bc4c67dd8e..6f3a00af7e3 100644 --- a/source/blender/nodes/composite/nodes/node_composite_rgb.cc +++ b/source/blender/nodes/composite/nodes/node_composite_rgb.cc @@ -5,6 +5,12 @@ * \ingroup cmpnodes */ +#include "BLI_math_vec_types.hh" + +#include "DNA_node_types.h" + +#include "COM_node_operation.hh" + #include "node_composite_util.hh" /* **************** RGB ******************** */ @@ -16,6 +22,29 @@ static void cmp_node_rgb_declare(NodeDeclarationBuilder &b) b.add_output(N_("RGBA")).default_value({0.5f, 0.5f, 0.5f, 1.0f}); } +using namespace blender::realtime_compositor; + +class RGBOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + Result &result = get_result("RGBA"); + result.allocate_single_value(); + + const bNodeSocket *socket = static_cast(bnode().outputs.first); + float4 color = float4(static_cast(socket->default_value)->value); + + result.set_color_value(color); + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new RGBOperation(context, node); +} + } // namespace blender::nodes::node_composite_rgb_cc void register_node_type_cmp_rgb() @@ -27,6 +56,7 @@ void register_node_type_cmp_rgb() cmp_node_type_base(&ntype, CMP_NODE_RGB, "RGB", NODE_CLASS_INPUT); ntype.declare = file_ns::cmp_node_rgb_declare; node_type_size_preset(&ntype, NODE_SIZE_SMALL); + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_scene_time.cc b/source/blender/nodes/composite/nodes/node_composite_scene_time.cc index 20bafb0d3d4..1f5317378bb 100644 --- a/source/blender/nodes/composite/nodes/node_composite_scene_time.cc +++ b/source/blender/nodes/composite/nodes/node_composite_scene_time.cc @@ -3,6 +3,8 @@ * \ingroup cmpnodes */ +#include "COM_node_operation.hh" + #include "node_composite_util.hh" namespace blender::nodes { @@ -13,6 +15,38 @@ static void cmp_node_scene_time_declare(NodeDeclarationBuilder &b) b.add_output(N_("Frame")); } +using namespace blender::realtime_compositor; + +class SceneTimeOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + execute_seconds(); + execute_frame(); + } + + void execute_seconds() + { + Result &result = get_result("Seconds"); + result.allocate_single_value(); + result.set_float_value(context().get_time()); + } + + void execute_frame() + { + Result &result = get_result("Frame"); + result.allocate_single_value(); + result.set_float_value(static_cast(context().get_frame_number())); + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new SceneTimeOperation(context, node); +} + } // namespace blender::nodes void register_node_type_cmp_scene_time() @@ -21,5 +55,7 @@ void register_node_type_cmp_scene_time() cmp_node_type_base(&ntype, CMP_NODE_SCENE_TIME, "Scene Time", NODE_CLASS_INPUT); ntype.declare = blender::nodes::cmp_node_scene_time_declare; + ntype.get_compositor_operation = blender::nodes::get_compositor_operation; + nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_value.cc b/source/blender/nodes/composite/nodes/node_composite_value.cc index a3269d3d1c2..a96e1db14ad 100644 --- a/source/blender/nodes/composite/nodes/node_composite_value.cc +++ b/source/blender/nodes/composite/nodes/node_composite_value.cc @@ -5,6 +5,8 @@ * \ingroup cmpnodes */ +#include "COM_node_operation.hh" + #include "node_composite_util.hh" /* **************** VALUE ******************** */ @@ -16,6 +18,29 @@ static void cmp_node_value_declare(NodeDeclarationBuilder &b) b.add_output(N_("Value")).default_value(0.5f); } +using namespace blender::realtime_compositor; + +class ValueOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + Result &result = get_result("Value"); + result.allocate_single_value(); + + const bNodeSocket *socket = static_cast(bnode().outputs.first); + float value = static_cast(socket->default_value)->value; + + result.set_float_value(value); + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new ValueOperation(context, node); +} + } // namespace blender::nodes::node_composite_value_cc void register_node_type_cmp_value() @@ -27,6 +52,7 @@ void register_node_type_cmp_value() cmp_node_type_base(&ntype, CMP_NODE_VALUE, "Value", NODE_CLASS_INPUT); ntype.declare = file_ns::cmp_node_value_declare; node_type_size_preset(&ntype, NODE_SIZE_SMALL); + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } -- cgit v1.2.3