diff options
175 files changed, 10972 insertions, 195 deletions
diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 191066df89f..4364a11ec22 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -2260,6 +2260,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel): ({"property": "use_sculpt_tools_tilt"}, "T82877"), ({"property": "use_extended_asset_browser"}, ("project/view/130/", "Project Page")), ({"property": "use_override_templates"}, ("T73318", "Milestone 4")), + ({"property": "use_realtime_compositor"}, "T91293"), ), ) diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 80ea33f8a75..0fb592cc26b 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -6104,6 +6104,24 @@ class VIEW3D_PT_shading_render_pass(Panel): layout.prop(shading, "render_pass", text="") +class VIEW3D_PT_shading_compositor(Panel): + bl_space_type = 'VIEW_3D' + bl_region_type = 'HEADER' + bl_label = "Realtime Compositor" + bl_parent_id = 'VIEW3D_PT_shading' + + @classmethod + def poll(cls, context): + return (context.space_data.shading.type in ('MATERIAL', 'RENDERED') and + context.preferences.experimental.use_realtime_compositor) + + def draw(self, context): + shading = context.space_data.shading + + layout = self.layout + layout.prop(shading, "use_compositor") + + class VIEW3D_PT_gizmo_display(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'HEADER' @@ -7890,6 +7908,7 @@ classes = ( VIEW3D_PT_shading_options_shadow, VIEW3D_PT_shading_options_ssao, VIEW3D_PT_shading_render_pass, + VIEW3D_PT_shading_compositor, VIEW3D_PT_gizmo_display, VIEW3D_PT_overlay, VIEW3D_PT_overlay_guides, diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 7ec8000b6f9..4f95a02d6f0 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -101,6 +101,7 @@ typedef struct bNodeSocketTemplate { namespace blender { class CPPType; namespace nodes { +class DNode; class NodeMultiFunctionBuilder; class GeoNodeExecParams; class NodeDeclarationBuilder; @@ -109,6 +110,11 @@ class GatherLinkSearchOpParams; namespace fn { class MFDataType; } // namespace fn +namespace realtime_compositor { +class Context; +class NodeOperation; +class ShaderNode; +} // namespace realtime_compositor } // namespace blender using CPPTypeHandle = blender::CPPType; @@ -123,7 +129,14 @@ using SocketGetGeometryNodesCPPValueFunction = void (*)(const struct bNodeSocket using NodeGatherSocketLinkOperationsFunction = void (*)(blender::nodes::GatherLinkSearchOpParams ¶ms); +using NodeGetCompositorOperationFunction = blender::realtime_compositor::NodeOperation + *(*)(blender::realtime_compositor::Context &context, blender::nodes::DNode node); +using NodeGetCompositorShaderNodeFunction = + blender::realtime_compositor::ShaderNode *(*)(blender::nodes::DNode node); + #else +typedef void *NodeGetCompositorOperationFunction; +typedef void *NodeGetCompositorShaderNodeFunction; typedef void *NodeMultiFunctionBuildFunction; typedef void *NodeGeometryExecFunction; typedef void *NodeDeclareFunction; @@ -309,6 +322,14 @@ typedef struct bNodeType { /* gpu */ NodeGPUExecFunction gpu_fn; + /* Get an instance of this node's compositor operation. Freeing the instance is the + * responsibility of the caller. */ + NodeGetCompositorOperationFunction get_compositor_operation; + + /* Get an instance of this node's compositor shader node. Freeing the instance is the + * responsibility of the caller. */ + NodeGetCompositorShaderNodeFunction get_compositor_shader_node; + /* Build a multi-function for this node. */ NodeMultiFunctionBuildFunction build_multi_function; diff --git a/source/blender/blenkernel/intern/image_gpu.cc b/source/blender/blenkernel/intern/image_gpu.cc index 6edb9e1b24c..9d1243b3e9c 100644 --- a/source/blender/blenkernel/intern/image_gpu.cc +++ b/source/blender/blenkernel/intern/image_gpu.cc @@ -354,6 +354,7 @@ static GPUTexture *image_get_gpu_texture(Image *ima, ima->gpu_pass = requested_pass; ima->gpu_layer = requested_layer; ima->gpu_view = requested_view; + BKE_image_partial_update_mark_full_update(ima); } #undef GPU_FLAGS_TO_CHECK diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index 55e349423bb..4731f828a3f 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -1,6 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-or-later # Copyright 2011 Blender Foundation. All rights reserved. +add_subdirectory(realtime_compositor) + set(INC . intern diff --git a/source/blender/compositor/realtime_compositor/CMakeLists.txt b/source/blender/compositor/realtime_compositor/CMakeLists.txt new file mode 100644 index 00000000000..e4b48a6621e --- /dev/null +++ b/source/blender/compositor/realtime_compositor/CMakeLists.txt @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +set(INC + . + ../../gpu + ../../nodes + ../../imbuf + ../../blenlib + ../../makesdna + ../../makesrna + ../../blenkernel + ../../gpu/intern + ../../../../intern/guardedalloc +) + + +set(SRC + intern/compile_state.cc + intern/context.cc + intern/conversion_operation.cc + intern/domain.cc + intern/evaluator.cc + intern/input_single_value_operation.cc + intern/node_operation.cc + intern/operation.cc + intern/realize_on_domain_operation.cc + intern/reduce_to_single_value_operation.cc + intern/result.cc + intern/scheduler.cc + intern/shader_node.cc + intern/shader_operation.cc + intern/shader_pool.cc + intern/simple_operation.cc + intern/texture_pool.cc + intern/utilities.cc + + COM_compile_state.hh + COM_context.hh + COM_conversion_operation.hh + COM_domain.hh + COM_evaluator.hh + COM_input_descriptor.hh + COM_input_single_value_operation.hh + COM_node_operation.hh + COM_operation.hh + COM_realize_on_domain_operation.hh + COM_reduce_to_single_value_operation.hh + COM_result.hh + COM_scheduler.hh + COM_shader_node.hh + COM_shader_operation.hh + COM_shader_pool.hh + COM_simple_operation.hh + COM_texture_pool.hh + COM_utilities.hh +) + +set(LIB + bf_gpu + bf_nodes + bf_imbuf + bf_blenlib + bf_blenkernel +) + +blender_add_lib(bf_realtime_compositor "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/compositor/realtime_compositor/COM_compile_state.hh b/source/blender/compositor/realtime_compositor/COM_compile_state.hh new file mode 100644 index 00000000000..3e2d27515f8 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_compile_state.hh @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_map.hh" + +#include "NOD_derived_node_tree.hh" + +#include "COM_domain.hh" +#include "COM_node_operation.hh" +#include "COM_scheduler.hh" +#include "COM_shader_operation.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +/* ------------------------------------------------------------------------------------------------ + * Compile State + * + * The compile state a utility class used to track the state of compilation when compiling the node + * tree. In particular, it tracks two important pieces of information, each of which is described + * in one of the following sections. + * + * First, it stores a mapping between all nodes and the operations they were compiled into. The + * mapping are stored independently depending on the type of the operation in the node_operations_ + * and shader_operations_ maps. So those two maps are mutually exclusive. The compiler should call + * the map_node_to_node_operation and map_node_to_shader_operation methods to populate those maps + * as soon as it compiles a node into an operation. Those maps are used to retrieve the results of + * outputs linked to the inputs of operations. See the get_result_from_output_socket method for + * more details. For the node tree shown below, nodes 1, 2, and 6 are mapped to their compiled + * operations in the node_operation_ map. While nodes 3 and 4 are both mapped to the first shader + * operation, and node 5 is mapped to the second shader operation in the shader_operations_ map. + * + * Shader Operation 1 Shader Operation 2 + * +-----------------------------------+ +------------------+ + * .------------. | .------------. .------------. | | .------------. | .------------. + * | Node 1 | | | Node 3 | | Node 4 | | | | Node 5 | | | Node 6 | + * | |----|--| |--| |---|-----|--| |--|--| | + * | | .-|--| | | | | .--|--| | | | | + * '------------' | | '------------' '------------' | | | '------------' | '------------' + * | +-----------------------------------+ | +------------------+ + * .------------. | | + * | Node 2 | | | + * | |--'----------------------------------------' + * | | + * '------------' + * + * Second, it stores the shader compile unit as well as its domain. One should first go over the + * discussion in COM_evaluator.hh for a high level description of the mechanism of the compile + * unit. The one important detail in this class is the should_compile_shader_compile_unit method, + * which implements the criteria of whether the compile unit should be compiled given the node + * currently being processed as an argument. Those criteria are described as follows. If the + * compile unit is empty as is the case when processing nodes 1, 2, and 3, then it plainly + * shouldn't be compiled. If the given node is not a shader node, then it can't be added to the + * compile unit and the unit is considered complete and should be compiled, as is the case when + * processing node 6. If the computed domain of the given node is not compatible with the domain of + * the compiled unit, then it can't be added to the unit and the unit is considered complete and + * should be compiled, as is the case when processing node 5, more on this in the next section. + * Otherwise, the given node is compatible with the compile unit and can be added to it, so the + * unit shouldn't be compiled just yet, as is the case when processing node 4. + * + * Special attention should be given to the aforementioned domain compatibility criterion. One + * should first go over the discussion in COM_domain.hh for more information on domains. When a + * compile unit gets eventually compiled to a shader operation, that operation will have a certain + * operation domain, and any node that gets added to the compile unit should itself have a computed + * node domain that is compatible with that operation domain, otherwise, had the node been compiled + * into its own operation separately, the result would have been be different. For instance, + * consider the above node tree where node 1 outputs a 100x100 result, node 2 outputs a 50x50 + * result, the first input in node 3 has the highest domain priority, and the second input in node + * 5 has the highest domain priority. In this case, shader operation 1 will output a 100x100 + * result, and shader operation 2 will output a 50x50 result, because that's the computed operation + * domain for each of them. So node 6 will get a 50x50 result. Now consider the same node tree, but + * where all three nodes 3, 4, and 5 were compiled into a single shader operation as shown the node + * tree below. In that case, shader operation 1 will output a 100x100 result, because that's its + * computed operation domain. So node 6 will get a 100x100 result. As can be seen, the final result + * is different even though the node tree is the same. That's why the compiler can decide to + * compile the compile unit early even though further nodes can still be technically added to it. + * + * Shader Operation 1 + * +------------------------------------------------------+ + * .------------. | .------------. .------------. .------------. | .------------. + * | Node 1 | | | Node 3 | | Node 4 | | Node 5 | | | Node 6 | + * | |----|--| |--| |------| |--|--| | + * | | .-|--| | | | .---| | | | | + * '------------' | | '------------' '------------' | '------------' | '------------' + * | +----------------------------------|-------------------+ + * .------------. | | + * | Node 2 | | | + * | |--'------------------------------------' + * | | + * '------------' + * + * To check for the domain compatibility between the compile unit and the node being processed, + * the domain of the compile unit is assumed to be the domain of the first node whose computed + * domain is not an identity domain. Identity domains corresponds to single value results, so those + * are always compatible with any domain. The domain of the compile unit is computed and set in + * the add_node_to_shader_compile_unit method. When processing a node, the computed domain of node + * is compared to the compile unit domain in the should_compile_shader_compile_unit method, noting + * that identity domains are always compatible. Node domains are computed in the + * compute_shader_node_domain method, which is analogous to Operation::compute_domain for nodes + * that are not yet compiled. */ +class CompileState { + private: + /* A reference to the node execution schedule that is being compiled. */ + const Schedule &schedule_; + /* Those two maps associate each node with the operation it was compiled into. Each node is + * either compiled into a node operation and added to node_operations, or compiled into a shader + * operation and added to shader_operations. Those maps are used to retrieve the results of + * outputs linked to the inputs of operations. See the get_result_from_output_socket method for + * more information. */ + Map<DNode, NodeOperation *> node_operations_; + Map<DNode, ShaderOperation *> shader_operations_; + /* A contiguous subset of the node execution schedule that contains the group of nodes that will + * be compiled together into a Shader Operation. See the discussion in COM_evaluator.hh for + * more information. */ + ShaderCompileUnit shader_compile_unit_; + /* The domain of the shader compile unit. */ + Domain shader_compile_unit_domain_ = Domain::identity(); + + public: + /* Construct a compile state from the node execution schedule being compiled. */ + CompileState(const Schedule &schedule); + + /* Get a reference to the node execution schedule being compiled. */ + const Schedule &get_schedule(); + + /* Add an association between the given node and the give node operation that the node was + * compiled into in the node_operations_ map. */ + void map_node_to_node_operation(DNode node, NodeOperation *operation); + + /* Add an association between the given node and the give shader operation that the node was + * compiled into in the shader_operations_ map. */ + void map_node_to_shader_operation(DNode node, ShaderOperation *operation); + + /* Returns a reference to the result of the operation corresponding to the given output that the + * given output's node was compiled to. */ + Result &get_result_from_output_socket(DOutputSocket output); + + /* Add the given node to the compile unit. And if the domain of the compile unit is not yet + * determined or was determined to be an identity domain, update it to the computed domain for + * the give node. */ + void add_node_to_shader_compile_unit(DNode node); + + /* Get a reference to the shader compile unit. */ + ShaderCompileUnit &get_shader_compile_unit(); + + /* Clear the compile unit. This should be called once the compile unit is compiled to ready it to + * track the next potential compile unit. */ + void reset_shader_compile_unit(); + + /* Determines if the compile unit should be compiled based on a number of criteria give the node + * currently being processed. Those criteria are as follows: + * - If compile unit is empty, then it can't and shouldn't be compiled. + * - If the given node is not a shader node, then it can't be added to the compile unit + * and the unit is considered complete and should be compiled. + * - If the computed domain of the given node is not compatible with the domain of the compile + * unit, then it can't be added to it and the unit is considered complete and should be + * compiled. */ + bool should_compile_shader_compile_unit(DNode node); + + private: + /* Compute the node domain of the given shader node. This is analogous to the + * Operation::compute_domain method, except it is computed from the node itself as opposed to a + * compiled operation. See the discussion in COM_domain.hh for more information. */ + Domain compute_shader_node_domain(DNode node); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_context.hh b/source/blender/compositor/realtime_compositor/COM_context.hh new file mode 100644 index 00000000000..3772254412a --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_context.hh @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_math_vec_types.hh" +#include "BLI_string_ref.hh" + +#include "DNA_scene_types.h" + +#include "GPU_texture.h" + +#include "COM_shader_pool.hh" +#include "COM_texture_pool.hh" + +namespace blender::realtime_compositor { + +/* ------------------------------------------------------------------------------------------------ + * Context + * + * A Context is an abstract class that is implemented by the caller of the evaluator to provide the + * necessary data and functionalities for the correct operation of the evaluator. This includes + * providing input data like render passes and the active scene, as well as references to the data + * where the output of the evaluator will be written. The class also provides a reference to the + * texture pool which should be implemented by the caller and provided during construction. + * Finally, the class have an instance of a shader pool for convenient shader acquisition. */ +class Context { + private: + /* A texture pool that can be used to allocate textures for the compositor efficiently. */ + TexturePool &texture_pool_; + /* A shader pool that can be used to create shaders for the compositor efficiently. */ + ShaderPool shader_pool_; + + public: + Context(TexturePool &texture_pool); + + /* Get the active compositing scene. */ + virtual const Scene *get_scene() const = 0; + + /* Get the dimensions of the viewport. */ + virtual int2 get_viewport_size() = 0; + + /* Get the texture representing the viewport where the result of the compositor should be + * written. This should be called by output nodes to get their target texture. */ + virtual GPUTexture *get_viewport_texture() = 0; + + /* Get the texture where the given render pass is stored. This should be called by the Render + * Layer node to populate its outputs. */ + virtual GPUTexture *get_pass_texture(int view_layer, eScenePassType pass_type) = 0; + + /* Get the name of the view currently being rendered. */ + virtual StringRef get_view_name() = 0; + + /* Set an info message. This is called by the compositor evaluator to inform or warn the user + * about something, typically an error. The implementation should display the message in an + * appropriate place, which can be directly in the UI or just logged to the output stream. */ + virtual void set_info_message(StringRef message) const = 0; + + /* Get the current frame number of the active scene. */ + int get_frame_number() const; + + /* Get the current time in seconds of the active scene. */ + float get_time() const; + + /* Get a reference to the texture pool of this context. */ + TexturePool &texture_pool(); + + /* Get a reference to the shader pool of this context. */ + ShaderPool &shader_pool(); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_conversion_operation.hh b/source/blender/compositor/realtime_compositor/COM_conversion_operation.hh new file mode 100644 index 00000000000..15e1d0722ea --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_conversion_operation.hh @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "GPU_shader.h" + +#include "COM_context.hh" +#include "COM_input_descriptor.hh" +#include "COM_result.hh" +#include "COM_simple_operation.hh" + +namespace blender::realtime_compositor { + +/* ------------------------------------------------------------------------------------------------- + * Conversion Operation + * + * A simple operation that converts a result from a certain type to another. See the derived + * classes for more details. */ +class ConversionOperation : public SimpleOperation { + public: + using SimpleOperation::SimpleOperation; + + /* If the input result is a single value, execute_single is called. Otherwise, the shader + * provided by get_conversion_shader is dispatched. */ + void execute() override; + + /* Determine if a conversion operation is needed for the input with the given result and + * descriptor. If it is not needed, return a null pointer. If it is needed, return an instance of + * the appropriate conversion operation. */ + static SimpleOperation *construct_if_needed(Context &context, + const Result &input_result, + const InputDescriptor &input_descriptor); + + protected: + /* Convert the input single value result to the output single value result. */ + virtual void execute_single(const Result &input, Result &output) = 0; + + /* Get the shader the will be used for conversion. */ + virtual GPUShader *get_conversion_shader() const = 0; +}; + +/* ------------------------------------------------------------------------------------------------- + * Convert Float To Vector Operation + * + * Takes a float result and outputs a vector result. All three components of the output are filled + * with the input float. */ +class ConvertFloatToVectorOperation : public ConversionOperation { + public: + ConvertFloatToVectorOperation(Context &context); + + void execute_single(const Result &input, Result &output) override; + + GPUShader *get_conversion_shader() const override; +}; + +/* ------------------------------------------------------------------------------------------------- + * Convert Float To Color Operation + * + * Takes a float result and outputs a color result. All three color channels of the output are + * filled with the input float and the alpha channel is set to 1. */ +class ConvertFloatToColorOperation : public ConversionOperation { + public: + ConvertFloatToColorOperation(Context &context); + + void execute_single(const Result &input, Result &output) override; + + GPUShader *get_conversion_shader() const override; +}; + +/* ------------------------------------------------------------------------------------------------- + * Convert Color To Float Operation + * + * Takes a color result and outputs a float result. The output is the average of the three color + * channels, the alpha channel is ignored. */ +class ConvertColorToFloatOperation : public ConversionOperation { + public: + ConvertColorToFloatOperation(Context &context); + + void execute_single(const Result &input, Result &output) override; + + GPUShader *get_conversion_shader() const override; +}; + +/* ------------------------------------------------------------------------------------------------- + * Convert Color To Vector Operation + * + * Takes a color result and outputs a vector result. The output is a copy of the three color + * channels to the three vector components. */ +class ConvertColorToVectorOperation : public ConversionOperation { + public: + ConvertColorToVectorOperation(Context &context); + + void execute_single(const Result &input, Result &output) override; + + GPUShader *get_conversion_shader() const override; +}; + +/* ------------------------------------------------------------------------------------------------- + * Convert Vector To Float Operation + * + * Takes a vector result and outputs a float result. The output is the average of the three + * components. */ +class ConvertVectorToFloatOperation : public ConversionOperation { + public: + ConvertVectorToFloatOperation(Context &context); + + void execute_single(const Result &input, Result &output) override; + + GPUShader *get_conversion_shader() const override; +}; + +/* ------------------------------------------------------------------------------------------------- + * Convert Vector To Color Operation + * + * Takes a vector result and outputs a color result. The output is a copy of the three vector + * components to the three color channels with the alpha channel set to 1. */ +class ConvertVectorToColorOperation : public ConversionOperation { + public: + ConvertVectorToColorOperation(Context &context); + + void execute_single(const Result &input, Result &output) override; + + GPUShader *get_conversion_shader() const override; +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_domain.hh b/source/blender/compositor/realtime_compositor/COM_domain.hh new file mode 100644 index 00000000000..dd669d5df6e --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_domain.hh @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include <cstdint> + +#include "BLI_float3x3.hh" +#include "BLI_math_vec_types.hh" + +namespace blender::realtime_compositor { + +/* Possible interpolations to use when realizing an input result of some domain on another domain. + * See the RealizationOptions class for more information. */ +enum class Interpolation : uint8_t { + Nearest, + Bilinear, + Bicubic, +}; + +/* ------------------------------------------------------------------------------------------------ + * Realization Options + * + * The options that describe how an input result prefer to be realized on some other domain. This + * is used by the Realize On Domain Operation to identify the appropriate method of realization. + * See the Domain class for more information. */ +class RealizationOptions { + public: + /* The interpolation method that should be used when performing realization. Since realizing a + * result involves projecting it on a different domain, which in turn, involves sampling the + * result at arbitrary locations, the interpolation identifies the method used for computing the + * value at those arbitrary locations. */ + Interpolation interpolation = Interpolation::Nearest; + /* If true, the result will be repeated infinitely along the horizontal axis when realizing the + * result. If false, regions outside of bounds of the result along the horizontal axis will be + * filled with zeros. */ + bool repeat_x = false; + /* If true, the result will be repeated infinitely along the vertical axis when realizing the + * result. If false, regions outside of bounds of the result along the vertical axis will be + * filled with zeros. */ + bool repeat_y = false; +}; + +/* ------------------------------------------------------------------------------------------------ + * Domain + * + * The compositor is designed in such a way as to allow compositing in an infinite virtual + * compositing space. Consequently, any result of an operation is not only represented by its image + * output, but also by its transformation in that virtual space. The transformation of the result + * together with the dimension of its image is stored and represented by a Domain. In the figure + * below, two results of different domains are illustrated on the virtual compositing space. One of + * the results is centered in space with an image dimension of 800px × 600px, and the other result + * is scaled down and translated such that it lies in the upper right quadrant of the space with an + * image dimension of 800px × 400px. The position of the domain is in pixel space, and the domain + * is considered centered if it has an identity transformation. Note that both results have the + * same resolution, but occupy different areas of the virtual compositing space. + * + * y + * ^ + * 800px x 600px | + * .---------------------|---------------------. + * | | 800px x 600px | + * | | .-------------. | + * | | | | | + * | | '-------------' | + * ------|---------------------|---------------------|------> x + * | | | + * | | | + * | | | + * | | | + * '---------------------|---------------------' + * | + * + * By default, results have domains of identity transformations, that is, they are centered in + * space, but a transformation operation like the rotate, translate, or transform operations will + * adjust the transformation to make the result reside somewhere different in space. The domain of + * a single value result is irrelevant and always set to an identity domain. + * + * An operation is typically only concerned about a subset of the virtual compositing space, this + * subset is represented by a domain which is called the Operation Domain. It follows that before + * the operation itself is executed, inputs will typically be realized on the operation domain to + * be in the same domain and have the same dimension as that of the operation domain. This process + * is called Domain Realization and is implemented using an operation called the Realize On Domain + * Operation. Realization involves projecting the result onto the target domain, copying the area + * of the result that intersects the target domain, and filling the rest with zeros or repetitions + * of the result depending on the realization options that can be set by the user. Consequently, + * operations can generally expect their inputs to have the same dimension and can operate on them + * directly and transparently. For instance, if an operation takes both results illustrated in + * the figure above, and the operation has an operation domain that matches the bigger domain, the + * result with the bigger domain will not need to be realized because it already has a domain that + * matches that of the operation domain, but the result with the smaller domain will have to be + * realized into a new result that has the same domain as the domain of the bigger result. Assuming + * no repetition, the output of the realization will be an all zeros image with dimension 800px × + * 600px with a small scaled version of the smaller result copied into the upper right quadrant of + * the image. The following figure illustrates the realization process on a different set of + * results + * + * Realized Result + * +-------------+ +-------------+ + * | Operation | | | + * | Domain | | Zeros | + * | | ----> | | + * +-----|-----+ | |-----+ | + * | | C | | | C | | + * | +-----|-------+ +-----'-------+ + * | Domain Of | + * | Input | + * +-----------+ + * + * An operation can operate in an arbitrary operation domain, but in most cases, the operation + * domain is inferred from the inputs of the operation. In particular, one of the inputs is said to + * be the Domain Input of the operation and the operation domain is inferred from its domain. It + * follows that this particular input will not need realization, because it already has the correct + * domain. The domain input selection mechanism is as follows. Each of the inputs are assigned a + * value by the developer called the Domain Priority, the domain input is then chosen as the + * non-single value input with the highest domain priority, zero being the highest priority. See + * Operation::compute_domain for more information. + * + * The aforementioned logic for operation domain computation is only a default that works for most + * cases, but an operation can override the compute_domain method to implement a different logic. + * For instance, output nodes have an operation domain the same size as the viewport and with an + * identity transformation, their operation domain doesn't depend on the inputs at all. + * + * For instance, a filter operation has two inputs, a factor and a color, the latter of which is + * assigned a domain priority of 0 and the former is assigned a domain priority of 1. If the color + * input is not a single value input, then the color input is considered to be the domain input of + * the operation and the operation domain is computed to be the same domain as the color input, + * because it has the highest priority. It follows that if the factor input has a different domain + * than the computed domain of the operation, it will be projected and realized on it to have the + * same domain as described above. On the other hand, if the color input is a single value input, + * then the factor input is considered to be the domain input and the operation domain will be the + * same as the domain of the factor input, because it has the second highest domain priority. + * Finally, if both inputs are single value inputs, the operation domain will be an identity domain + * and is irrelevant, because the output will be a domain-less single value. */ +class Domain { + public: + /* The size of the domain in pixels. */ + int2 size; + /* The 2D transformation of the domain defining its translation in pixels, rotation, and scale in + * the virtual compositing space. */ + float3x3 transformation; + /* The options that describe how this domain prefer to be realized on some other domain. See the + * RealizationOptions class for more information. */ + RealizationOptions realization_options; + + public: + /* A size only constructor that sets the transformation to identity. */ + Domain(int2 size); + + Domain(int2 size, float3x3 transformation); + + /* Transform the domain by the given transformation. This effectively pre-multiply the given + * transformation by the current transformation of the domain. */ + void transform(const float3x3 &transformation); + + /* Returns a domain of size 1x1 and an identity transformation. */ + static Domain identity(); +}; + +/* Compare the size and transformation of the domain. The realization_options are not compared + * because they only describe the method of realization on another domain, which is not technically + * a property of the domain itself. */ +bool operator==(const Domain &a, const Domain &b); + +/* Inverse of the above equality operator. */ +bool operator!=(const Domain &a, const Domain &b); + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_evaluator.hh b/source/blender/compositor/realtime_compositor/COM_evaluator.hh new file mode 100644 index 00000000000..fd6feb0948b --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_evaluator.hh @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include <memory> + +#include "BLI_vector.hh" + +#include "DNA_node_types.h" + +#include "NOD_derived_node_tree.hh" + +#include "COM_compile_state.hh" +#include "COM_context.hh" +#include "COM_node_operation.hh" +#include "COM_operation.hh" +#include "COM_shader_operation.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +/* ------------------------------------------------------------------------------------------------ + * Evaluator + * + * The evaluator is the main class of the compositor and the entry point of its execution. The + * evaluator compiles the compositor node tree and evaluates it to compute its output. It is + * constructed from a compositor node tree and a compositor context. Upon calling the evaluate + * method, the evaluator will check if the node tree is already compiled into an operations stream, + * and if it is, it will go over it and evaluate the operations in order. It is then the + * responsibility of the caller to call the reset method when the node tree changes to invalidate + * the operations stream. A reset is also required if the resources used by the node tree change, + * for instances, when the dimensions of an image used by the node tree changes. This is necessary + * because the evaluator compiles the node tree into an operations stream that is specifically + * optimized for the structure of the resources used by the node tree. + * + * Otherwise, if the node tree is not yet compiled, the evaluator will compile it into an + * operations stream, evaluating the operations in the process. It should be noted that operations + * are evaluated as soon as they are compiled, as opposed to compiling the whole operations stream + * and then evaluating it in a separate step. This is important because, as mentioned before, the + * operations stream is optimized specifically for the structure of the resources used by the node + * tree, which is only known after the operations are evaluated. In other words, the evaluator uses + * the evaluated results of previously compiled operations to compile the operations that follow + * them in an optimized manner. + * + * Compilation starts by computing an optimized node execution schedule by calling the + * compute_schedule function, see the discussion in COM_scheduler.hh for more details. For the node + * tree shown below, the execution schedule is denoted by the node numbers. The compiler then goes + * over the execution schedule in order and compiles each node into either a Node Operation or a + * Shader Operation, depending on the node type, see the is_shader_node function. A Shader + * operation is constructed from a group of nodes forming a contiguous subset of the node execution + * schedule. For instance, in the node tree shown below, nodes 3 and 4 are compiled together into a + * shader operation and node 5 is compiled into its own shader operation, both of which are + * contiguous subsets of the node execution schedule. This process is described in details in the + * following section. + * + * Shader Operation 1 Shader Operation 2 + * +-----------------------------------+ +------------------+ + * .------------. | .------------. .------------. | | .------------. | .------------. + * | Node 1 | | | Node 3 | | Node 4 | | | | Node 5 | | | Node 6 | + * | |----|--| |--| |---|-----|--| |--|--| | + * | | .-|--| | | | | .--|--| | | | | + * '------------' | | '------------' '------------' | | | '------------' | '------------' + * | +-----------------------------------+ | +------------------+ + * .------------. | | + * | Node 2 | | | + * | |--'----------------------------------------' + * | | + * '------------' + * + * For non shader nodes, the compilation process is straight forward, the compiler instantiates a + * node operation from the node, map its inputs to the results of the outputs they are linked to, + * and evaluates the operations. However, for shader nodes, since a group of nodes can be compiled + * together into a shader operation, the compilation process is a bit involved. The compiler uses + * an instance of the Compile State class to keep track of the compilation process. The compiler + * state stores the so called "shader compile unit", which is the current group of nodes that will + * eventually be compiled together into a shader operation. While going over the schedule, the + * compiler adds the shader nodes to the compile unit until it decides that the compile unit is + * complete and should be compiled. This is typically decided when the current node is not + * compatible with the compile unit and can't be added to it, only then it compiles the compile + * unit into a shader operation and resets it to ready it to track the next potential group of + * nodes that will form a shader operation. This decision is made based on various criteria in the + * should_compile_shader_compile_unit function. See the discussion in COM_compile_state.hh for more + * details of those criteria, but perhaps the most evident of which is whether the node is actually + * a shader node, if it isn't, then it evidently can't be added to the compile unit and the compile + * unit is should be compiled. + * + * For the node tree above, the compilation process is as follows. The compiler goes over the node + * execution schedule in order considering each node. Nodes 1 and 2 are not shader node so they are + * compiled into node operations and added to the operations stream. The current compile unit is + * empty, so it is not compiled. Node 3 is a shader node, and since the compile unit is currently + * empty, it is unconditionally added to it. Node 4 is a shader node, it was decided---for the sake + * of the demonstration---that it is compatible with the compile unit and can be added to it. Node + * 5 is a shader node, but it was decided---for the sake of the demonstration---that it is not + * compatible with the compile unit, so the compile unit is considered complete and is compiled + * first, adding the first shader operation to the operations stream and resetting the compile + * unit. Node 5 is then added to the now empty compile unit similar to node 3. Node 6 is not a + * shader node, so the compile unit is considered complete and is compiled first, adding the first + * shader operation to the operations stream and resetting the compile unit. Finally, node 6 is + * compiled into a node operation similar to nodes 1 and 2 and added to the operations stream. */ +class Evaluator { + private: + /* A reference to the compositor context. */ + Context &context_; + /* A reference to the compositor node tree. */ + bNodeTree &node_tree_; + /* The derived and reference node trees representing the compositor node tree. Those are + * initialized when the node tree is compiled and freed when the evaluator resets. */ + NodeTreeRefMap node_tree_reference_map_; + std::unique_ptr<DerivedNodeTree> derived_node_tree_; + /* The compiled operations stream. This contains ordered pointers to the operations that were + * compiled. This is initialized when the node tree is compiled and freed when the evaluator + * resets. The is_compiled_ member indicates whether the operation stream can be used or needs to + * be compiled first. Note that the operations stream can be empty even when compiled, this can + * happen when the node tree is empty or invalid for instance. */ + Vector<std::unique_ptr<Operation>> operations_stream_; + /* True if the node tree is already compiled into an operations stream that can be evaluated + * directly. False if the node tree is not compiled yet and needs to be compiled. */ + bool is_compiled_ = false; + + public: + /* Construct an evaluator from a compositor node tree and a context. */ + Evaluator(Context &context, bNodeTree &node_tree); + + /* Evaluate the compositor node tree. If the node tree is already compiled into an operations + * stream, that stream will be evaluated directly. Otherwise, the node tree will be compiled and + * evaluated. */ + void evaluate(); + + /* Invalidate the operations stream that was compiled for the node tree. This should be called + * when the node tree changes or the structure of any of the resources used by it changes. By + * structure, we mean things like the dimensions of the used images, while changes to their + * contents do not necessitate a reset. */ + void reset(); + + private: + /* Check if the compositor node tree is valid by checking if it has: + * - Cyclic links. + * - Undefined nodes or sockets. + * - Unsupported nodes. + * If the node tree is valid, true is returned. Otherwise, false is returned, and an appropriate + * error message is set by calling the context's set_info_message method. */ + bool validate_node_tree(); + + /* Compile the node tree into an operations stream and evaluate it. */ + void compile_and_evaluate(); + + /* Compile the given node into a node operation, map each input to the result of the output + * linked to it, update the compile state, add the newly created operation to the operations + * stream, and evaluate the operation. */ + void compile_and_evaluate_node(DNode node, CompileState &compile_state); + + /* Map each input of the node operation to the result of the output linked to it. Unlinked inputs + * are mapped to the result of a newly created Input Single Value Operation, which is added to + * the operations stream and evaluated. Since this method might add operations to the operations + * stream, the actual node operation should only be added to the stream once this method is + * called. */ + void map_node_operation_inputs_to_their_results(DNode node, + NodeOperation *operation, + CompileState &compile_state); + + /* Compile the shader compile unit into a shader operation, map each input of the operation to + * the result of the output linked to it, update the compile state, add the newly created + * operation to the operations stream, evaluate the operation, and finally reset the shader + * compile unit. */ + void compile_and_evaluate_shader_compile_unit(CompileState &compile_state); + + /* Map each input of the shader operation to the result of the output linked to it. */ + void map_shader_operation_inputs_to_their_results(ShaderOperation *operation, + CompileState &compile_state); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_input_descriptor.hh b/source/blender/compositor/realtime_compositor/COM_input_descriptor.hh new file mode 100644 index 00000000000..215a92ab3b4 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_input_descriptor.hh @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "COM_result.hh" + +namespace blender::realtime_compositor { + +/* ------------------------------------------------------------------------------------------------ + * Input Descriptor + * + * A class that describes an input of an operation. */ +class InputDescriptor { + public: + /* The type of input. This may be different that the type of result that the operation will + * receive for the input, in which case, an implicit conversion operation will be added as an + * input processor to convert it to the required type. */ + ResultType type; + /* If true, then the input does not need to be realized on the domain of the operation before its + * execution. See the discussion in COM_domain.hh for more information. */ + bool skip_realization = false; + /* The priority of the input for determining the operation domain. The non-single value input + * with the highest priority will be used to infer the operation domain, the highest priority + * being zero. See the discussion in COM_domain.hh for more information. */ + int domain_priority = 0; + /* If true, the input expects a single value, and if a non-single value is provided, a default + * single value will be used instead, see the get_<type>_value_default methods in the Result + * class. It follows that this also implies skip_realization, because we don't need to realize a + * result that will be discarded anyways. If false, the input can work with both single and + * non-single values. */ + bool expects_single_value = false; +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_input_single_value_operation.hh b/source/blender/compositor/realtime_compositor/COM_input_single_value_operation.hh new file mode 100644 index 00000000000..bbcd336ee11 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_input_single_value_operation.hh @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_string_ref.hh" + +#include "NOD_derived_node_tree.hh" + +#include "COM_context.hh" +#include "COM_operation.hh" +#include "COM_result.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +/* ------------------------------------------------------------------------------------------------ + * Input Single Value Operation + * + * An input single value operation is an operation that outputs a single value result whose value + * is the value of an unlinked input socket. This is typically used to initialize the values of + * unlinked node input sockets. */ +class InputSingleValueOperation : public Operation { + private: + /* The identifier of the output. */ + static const StringRef output_identifier_; + /* The input socket whose value will be computed as the operation's result. */ + DInputSocket input_socket_; + + public: + InputSingleValueOperation(Context &context, DInputSocket input_socket); + + /* Allocate a single value result and set its value to the default value of the input socket. */ + void execute() override; + + /* Get a reference to the output result of the operation, this essentially calls the super + * get_result with the output identifier of the operation. */ + Result &get_result(); + + private: + /* Populate the result of the operation, this essentially calls the super populate_result method + * with the output identifier of the operation. */ + void populate_result(Result result); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_node_operation.hh b/source/blender/compositor/realtime_compositor/COM_node_operation.hh new file mode 100644 index 00000000000..94bc4dfd39d --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_node_operation.hh @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_string_ref.hh" + +#include "DNA_node_types.h" + +#include "NOD_derived_node_tree.hh" + +#include "COM_context.hh" +#include "COM_operation.hh" +#include "COM_scheduler.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +/* ------------------------------------------------------------------------------------------------ + * Node Operation + * + * A node operation is a subclass of operation that nodes should implement and instantiate in the + * get_compositor_operation function of bNodeType, passing the inputs given to that function to the + * constructor. This class essentially just implements a default constructor that populates output + * results for all outputs of the node as well as input descriptors for all inputs of the nodes + * based on their socket declaration. The class also provides some utility methods for easier + * implementation of nodes. */ +class NodeOperation : public Operation { + private: + /* The node that this operation represents. */ + DNode node_; + + public: + /* Populate the output results based on the node outputs and populate the input descriptors based + * on the node inputs. */ + NodeOperation(Context &context, DNode node); + + /* Compute and set the initial reference counts of all the results of the operation. The + * reference counts of the results are the number of operations that use those results, which is + * computed as the number of inputs whose node is part of the schedule and is linked to the + * output corresponding to each result. The node execution schedule is given as an input. */ + void compute_results_reference_counts(const Schedule &schedule); + + protected: + /* Returns a reference to the derived node that this operation represents. */ + const DNode &node() const; + + /* Returns a reference to the node that this operation represents. */ + const bNode &bnode() const; + + /* Returns true if the output identified by the given identifier is needed and should be + * computed, otherwise returns false. */ + bool should_compute_output(StringRef identifier); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_operation.hh b/source/blender/compositor/realtime_compositor/COM_operation.hh new file mode 100644 index 00000000000..b512a368c59 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_operation.hh @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include <memory> + +#include "BLI_map.hh" +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" + +#include "COM_context.hh" +#include "COM_domain.hh" +#include "COM_input_descriptor.hh" +#include "COM_result.hh" +#include "COM_shader_pool.hh" +#include "COM_texture_pool.hh" + +namespace blender::realtime_compositor { + +/* Forward declare simple operation because it is used in the operation definition. */ +class SimpleOperation; + +/* A type representing a vector of simple operations that store the input processors for a + * particular input. */ +using ProcessorsVector = Vector<std::unique_ptr<SimpleOperation>>; + +/* ------------------------------------------------------------------------------------------------ + * Operation + * + * The operation is the basic unit of the compositor. The evaluator compiles the compositor node + * tree into an ordered stream of operations which are then executed in order during evaluation. + * The operation class can be sub-classed to implement a new operation. Operations have a number of + * inputs and outputs that are declared during construction and are identified by string + * identifiers. Inputs are declared by calling declare_input_descriptor providing an appropriate + * descriptor. Those inputs are mapped to the results computed by other operations whose outputs + * are linked to the inputs. Such mappings are established by the compiler during compilation by + * calling the map_input_to_result method. Outputs are populated by calling the populate_result + * method, providing a result of an appropriate type. Upon execution, the operation allocates a + * result for each of its outputs and computes their value based on its inputs and options. + * + * Each input may have one or more input processors, which are simple operations that process the + * inputs before the operation is executed, see the discussion in COM_simple_operation.hh for more + * information. And thus the effective input of the operation is the result of the last input + * processor if one exists. Input processors are added and evaluated by calling the + * add_and_evaluate_input_processors method, which provides a default implementation that does + * things like implicit conversion, domain realization, and more. This default implementation can, + * however, be overridden, extended, or removed. Once the input processors are added and evaluated + * for the first time, they are stored in the operation and future evaluations can evaluate them + * directly without having to add them again. + * + * The operation is evaluated by calling the evaluate method, which first adds the input processors + * if they weren't added already and evaluates them, then it resets the results of the operation, + * then it calls the execute method of the operation, and finally it releases the results mapped to + * the inputs to declare that they are no longer needed. */ +class Operation { + private: + /* A reference to the compositor context. This member references the same object in all + * operations but is included in the class for convenience. */ + Context &context_; + /* A mapping between each output of the operation identified by its identifier and the result for + * that output. A result for each output of the operation should be constructed and added to the + * map during operation construction by calling the populate_result method. The results should be + * allocated and their contents should be computed in the execute method. */ + Map<StringRef, Result> results_; + /* A mapping between each input of the operation identified by its identifier and its input + * descriptor. Those descriptors should be declared during operation construction by calling the + * declare_input_descriptor method. */ + Map<StringRef, InputDescriptor> input_descriptors_; + /* A mapping between each input of the operation identified by its identifier and a pointer to + * the computed result providing its data. The mapped result is either one that was computed by + * another operation or one that was internally computed in the operation by the last input + * processor for that input. It is the responsibility of the evaluator to map the inputs to their + * linked results before evaluating the operation by calling the map_input_to_result method. */ + Map<StringRef, Result *> results_mapped_to_inputs_; + /* A mapping between each input of the operation identified by its identifier and an ordered list + * of simple operations to process that input. This is initialized the first time the input + * processors are evaluated by calling the add_and_evaluate_input_processors method. Further + * evaluations will evaluate the processors directly without the need to add them again. The + * input_processors_added_ member indicates whether the processors were already added and can be + * evaluated directly or need to be added and evaluated. */ + Map<StringRef, ProcessorsVector> input_processors_; + /* True if the input processors were already added and can be evaluated directly. False if the + * input processors are not yet added and needs to be added. */ + bool input_processors_added_ = false; + + public: + Operation(Context &context); + + virtual ~Operation(); + + /* Evaluate the operation by: + * 1. Evaluating the input processors. + * 2. Resetting the results of the operation. + * 3. Calling the execute method of the operation. + * 4. Releasing the results mapped to the inputs. */ + void evaluate(); + + /* Get a reference to the output result identified by the given identifier. */ + Result &get_result(StringRef identifier); + + /* Map the input identified by the given identifier to the result providing its data. See + * results_mapped_to_inputs_ for more details. This should be called by the evaluator to + * establish links between different operations. */ + void map_input_to_result(StringRef identifier, Result *result); + + protected: + /* Compute the operation domain of this operation. By default, this implements a default logic + * that infers the operation domain from the inputs, which may be overridden for a different + * logic. See the discussion in COM_domain.hh for the inference logic and more information. */ + virtual Domain compute_domain(); + + /* Add and evaluate any needed input processors, which essentially just involves calling the + * add_and_evaluate_input_processor method with the needed processors. This is called before + * executing the operation to prepare its inputs. The class defines a default implementation + * which adds typically needed processors, but derived classes can override the method to have + * a different implementation, extend the implementation, or remove it entirely. */ + virtual void add_and_evaluate_input_processors(); + + /* Given the identifier of an input of the operation and a processor operation: + * - Add the given processor to the list of input processors for the input. + * - Map the input of the processor to be the result of the last input processor or the result + * mapped to the input if no previous processors exists. + * - Switch the result mapped to the input to be the output result of the processor. + * - Evaluate the processor. */ + void add_and_evaluate_input_processor(StringRef identifier, SimpleOperation *processor); + + /* This method should allocate the operation results, execute the operation, and compute the + * output results. */ + virtual void execute() = 0; + + /* Get a reference to the result connected to the input identified by the given identifier. */ + Result &get_input(StringRef identifier) const; + + /* Switch the result mapped to the input identified by the given identifier with the given + * result. */ + void switch_result_mapped_to_input(StringRef identifier, Result *result); + + /* Add the given result to the results_ map identified by the given output identifier. This + * should be called during operation construction for all outputs. The provided result shouldn't + * be allocated or initialized, this will happen later during execution. */ + void populate_result(StringRef identifier, Result result); + + /* Declare the descriptor of the input identified by the given identifier to be the given + * descriptor. Adds the given descriptor to the input_descriptors_ map identified by the given + * input identifier. This should be called during operation constructor for all inputs. */ + void declare_input_descriptor(StringRef identifier, InputDescriptor descriptor); + + /* Get a reference to the descriptor of the input identified by the given identified. */ + InputDescriptor &get_input_descriptor(StringRef identified); + + /* Returns a reference to the compositor context. */ + Context &context(); + + /* Returns a reference to the texture pool of the compositor context. */ + TexturePool &texture_pool() const; + + /* Returns a reference to the shader pool of the compositor context. */ + ShaderPool &shader_pool() const; + + private: + /* Evaluate the input processors. If the input processors were already added they will be + * evaluated directly. Otherwise, the input processors will be added and evaluated. */ + void evaluate_input_processors(); + + /* Resets the results of the operation. See the reset method in the Result class for more + * information. */ + void reset_results(); + + /* Release the results that are mapped to the inputs of the operation. This is called after the + * evaluation of the operation to declare that the results are no longer needed by this + * operation. */ + void release_inputs(); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_realize_on_domain_operation.hh b/source/blender/compositor/realtime_compositor/COM_realize_on_domain_operation.hh new file mode 100644 index 00000000000..357a3f88bd9 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_realize_on_domain_operation.hh @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "GPU_shader.h" + +#include "COM_context.hh" +#include "COM_domain.hh" +#include "COM_input_descriptor.hh" +#include "COM_result.hh" +#include "COM_simple_operation.hh" + +namespace blender::realtime_compositor { + +/* ------------------------------------------------------------------------------------------------ + * Realize On Domain Operation + * + * A simple operation that projects the input on a certain target domain, copies the area of the + * input that intersects the target domain, and fill the rest with zeros or repetitions of the + * input depending on the realization options of the target domain. See the discussion in + * COM_domain.hh for more information. */ +class RealizeOnDomainOperation : public SimpleOperation { + private: + /* The target domain to realize the input on. */ + Domain domain_; + + public: + RealizeOnDomainOperation(Context &context, Domain domain, ResultType type); + + void execute() override; + + /* Determine if a realize on domain operation is needed for the input with the given result and + * descriptor in an operation with the given operation domain. If it is not needed, return a null + * pointer. If it is needed, return an instance of the operation. */ + static SimpleOperation *construct_if_needed(Context &context, + const Result &input_result, + const InputDescriptor &input_descriptor, + const Domain &operaiton_domain); + + protected: + /* The operation domain is just the target domain. */ + Domain compute_domain() override; + + private: + /* Get the realization shader of the appropriate type. */ + GPUShader *get_realization_shader(); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_reduce_to_single_value_operation.hh b/source/blender/compositor/realtime_compositor/COM_reduce_to_single_value_operation.hh new file mode 100644 index 00000000000..2f5a82c79b7 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_reduce_to_single_value_operation.hh @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "COM_context.hh" +#include "COM_result.hh" +#include "COM_simple_operation.hh" + +namespace blender::realtime_compositor { + +/* ------------------------------------------------------------------------------------------------ + * Reduce To Single Value Operation + * + * A simple operation that reduces its input result into a single value output result. The input is + * assumed to be a texture result of size 1x1, that is, a texture composed of a single pixel, the + * value of which shall serve as the single value of the output result. */ +class ReduceToSingleValueOperation : public SimpleOperation { + public: + ReduceToSingleValueOperation(Context &context, ResultType type); + + /* Download the input pixel from the GPU texture and set its value to the value of the allocated + * single value output result. */ + void execute() override; + + /* Determine if a reduce to single value operation is needed for the input with the + * given result. If it is not needed, return a null pointer. If it is needed, return an instance + * of the operation. */ + static SimpleOperation *construct_if_needed(Context &context, const Result &input_result); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_result.hh b/source/blender/compositor/realtime_compositor/COM_result.hh new file mode 100644 index 00000000000..2ac28b99346 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_result.hh @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_float3x3.hh" +#include "BLI_math_vec_types.hh" + +#include "GPU_shader.h" +#include "GPU_texture.h" + +#include "COM_domain.hh" +#include "COM_texture_pool.hh" + +namespace blender::realtime_compositor { + +/* Possible data types that operations can operate on. They either represent the base type of the + * result texture or a single value result. */ +enum class ResultType : uint8_t { + Float, + Vector, + Color, +}; + +/* ------------------------------------------------------------------------------------------------ + * Result + * + * A result represents the computed value of an output of an operation. A result can either + * represent an image or a single value. A result is typed, and can be of type color, vector, or + * float. Single value results are stored in 1x1 textures to make them easily accessible in + * shaders. But the same value is also stored in the value union member of the result for any + * host-side processing. The texture of the result is allocated from the texture pool referenced by + * the result. + * + * Results are reference counted and their textures are released once their reference count reaches + * zero. After constructing a result, the set_initial_reference_count method is called to declare + * the number of operations that needs this result. Once each operation that needs the result no + * longer needs it, the release method is called and the reference count is decremented, until it + * reaches zero, where the result's texture is then released. Since results are eventually + * decremented to zero by the end of every evaluation, the reference count is restored before every + * evaluation to its initial reference count by calling the reset method, which is why a separate + * member initial_reference_count_ is stored to keep track of the initial value. + * + * A result not only represents an image, but also the area it occupies in the virtual compositing + * space. This area is called the Domain of the result, see the discussion in COM_domain.hh for + * more information. + * + * A result can be a proxy result that merely wraps another master result, in which case, it shares + * its values and delegates all reference counting to it. While a proxy result shares the value of + * the master result, it can have a different domain. Consequently, transformation operations are + * implemented using proxy results, where their results are proxy results of their inputs but with + * their domains transformed based on their options. Moreover, proxy results can also be used as + * the results of identity operations, that is, operations that do nothing to their inputs in + * certain configurations. In which case, the proxy result is left as is with no extra + * transformation on its domain whatsoever. Proxy results can be created by calling the + * pass_through method, see that method for more details. */ +class Result { + private: + /* The base type of the texture or the type of the single value. */ + ResultType type_; + /* If true, the result is a single value, otherwise, the result is a texture. */ + bool is_single_value_; + /* A GPU texture storing the result data. This will be a 1x1 texture if the result is a single + * value, the value of which will be identical to that of the value member. See class description + * for more information. */ + GPUTexture *texture_ = nullptr; + /* The texture pool used to allocate the texture of the result, this should be initialized during + * construction. */ + TexturePool *texture_pool_ = nullptr; + /* The number of operations that currently needs this result. At the time when the result is + * computed, this member will have a value that matches initial_reference_count_. Once each + * operation that needs the result no longer needs it, the release method is called and the + * reference count is decremented, until it reaches zero, where the result's texture is then + * released. If this result have a master result, then this reference count is irrelevant and + * shadowed by the reference count of the master result. */ + int reference_count_; + /* The number of operations that reference and use this result at the time when it was initially + * computed. Since reference_count_ is decremented and always becomes zero at the end of the + * evaluation, this member is used to reset the reference count of the results for later + * evaluations by calling the reset method. This member is also used to determine if this result + * should be computed by calling the should_compute method. */ + int initial_reference_count_; + /* If the result is a single value, this member stores the value of the result, the value of + * which will be identical to that stored in the texture member. The active union member depends + * on the type of the result. This member is uninitialized and should not be used if the result + * is a texture. */ + union { + float float_value_; + float3 vector_value_; + float4 color_value_; + }; + /* The domain of the result. This only matters if the result was a texture. See the discussion in + * COM_domain.hh for more information. */ + Domain domain_ = Domain::identity(); + /* If not nullptr, then this result wraps and shares the value of another master result. In this + * case, calls to texture-related methods like increment_reference_count and release should + * operate on the master result as opposed to this result. This member is typically set upon + * calling the pass_through method, which sets this result to be the master of a target result. + * See that method for more information. */ + Result *master_ = nullptr; + + public: + /* Construct a result of the given type with the given texture pool that will be used to allocate + * and release the result's texture. */ + Result(ResultType type, TexturePool &texture_pool); + + /* Declare the result to be a texture result, allocate a texture of an appropriate type with + * the size of the given domain from the result's texture pool, and set the domain of the result + * to the given domain. */ + void allocate_texture(Domain domain); + + /* Declare the result to be a single value result, allocate a texture of an appropriate + * type with size 1x1 from the result's texture pool, and set the domain to be an identity + * domain. See class description for more information. */ + void allocate_single_value(); + + /* Allocate a single value result and set its value to zero. This is called for results whose + * value can't be computed and are considered invalid. */ + void allocate_invalid(); + + /* Bind the texture of the result to the texture image unit with the given name in the currently + * bound given shader. This also inserts a memory barrier for texture fetches to ensure any prior + * writes to the texture are reflected before reading from it. */ + void bind_as_texture(GPUShader *shader, const char *texture_name) const; + + /* Bind the texture of the result to the image unit with the given name in the currently bound + * given shader. */ + void bind_as_image(GPUShader *shader, const char *image_name) const; + + /* Unbind the texture which was previously bound using bind_as_texture. */ + void unbind_as_texture() const; + + /* Unbind the texture which was previously bound using bind_as_image. */ + void unbind_as_image() const; + + /* Pass this result through to a target result, in which case, the target result becomes a proxy + * result with this result as its master result. This is done by making the target result a copy + * of this result, essentially having identical values between the two and consequently sharing + * the underlying texture. An exception is the initial reference count, whose value is retained + * and not copied, because it is a property of the original result and is needed for correctly + * resetting the result before the next evaluation. Additionally, this result is set to be the + * master of the target result, by setting the master member of the target. Finally, the + * reference count of the result is incremented by the reference count of the target result. See + * the discussion above for more information. */ + void pass_through(Result &target); + + /* Transform the result by the given transformation. This effectively pre-multiply the given + * transformation by the current transformation of the domain of the result. */ + void transform(const float3x3 &transformation); + + /* Get a reference to the realization options of this result. See the RealizationOptions class + * for more information. */ + RealizationOptions &get_realization_options(); + + /* If the result is a single value result of type float, return its float value. Otherwise, an + * uninitialized value is returned. */ + float get_float_value() const; + + /* If the result is a single value result of type vector, return its vector value. Otherwise, an + * uninitialized value is returned. */ + float3 get_vector_value() const; + + /* If the result is a single value result of type color, return its color value. Otherwise, an + * uninitialized value is returned. */ + float4 get_color_value() const; + + /* Same as get_float_value but returns a default value if the result is not a single value. */ + float get_float_value_default(float default_value) const; + + /* Same as get_vector_value but returns a default value if the result is not a single value. */ + float3 get_vector_value_default(const float3 &default_value) const; + + /* Same as get_color_value but returns a default value if the result is not a single value. */ + float4 get_color_value_default(const float4 &default_value) const; + + /* If the result is a single value result of type float, set its float value and upload it to the + * texture. Otherwise, an undefined behavior is invoked. */ + void set_float_value(float value); + + /* If the result is a single value result of type vector, set its vector value and upload it to + * the texture. Otherwise, an undefined behavior is invoked. */ + void set_vector_value(const float3 &value); + + /* If the result is a single value result of type color, set its color value and upload it to the + * texture. Otherwise, an undefined behavior is invoked. */ + void set_color_value(const float4 &value); + + /* Set the value of initial_reference_count_, see that member for more details. This should be + * called after constructing the result to declare the number of operations that needs it. */ + void set_initial_reference_count(int count); + + /* Reset the result to prepare it for a new evaluation. This should be called before evaluating + * the operation that computes this result. First, set the value of reference_count_ to the value + * of initial_reference_count_ since reference_count_ may have already been decremented to zero + * in a previous evaluation. Second, set master_ to nullptr because the result may have been + * turned into a proxy result in a previous evaluation. Other fields don't need to be reset + * because they are runtime and overwritten during evaluation. */ + void reset(); + + /* Increment the reference count of the result by the given count. If this result have a master + * result, the reference count of the master result is incremented instead. */ + void increment_reference_count(int count = 1); + + /* Decrement the reference count of the result and release the its texture back into the texture + * pool if the reference count reaches zero. This should be called when an operation that used + * this result no longer needs it. If this result have a master result, the master result is + * released instead. */ + void release(); + + /* Returns true if this result should be computed and false otherwise. The result should be + * computed if its reference count is not zero, that is, its result is used by at least one + * operation. */ + bool should_compute(); + + /* Returns the type of the result. */ + ResultType type() const; + + /* Returns true if the result is a texture and false of it is a single value. */ + bool is_texture() const; + + /* Returns true if the result is a single value and false of it is a texture. */ + bool is_single_value() const; + + /* Returns the allocated GPU texture of the result. */ + GPUTexture *texture() const; + + /* Returns the reference count of the result. If this result have a master result, then the + * reference count of the master result is returned instead. */ + int reference_count() const; + + /* Returns a reference to the domain of the result. See the Domain class. */ + const Domain &domain() const; +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_scheduler.hh b/source/blender/compositor/realtime_compositor/COM_scheduler.hh new file mode 100644 index 00000000000..4f778b32145 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_scheduler.hh @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_vector_set.hh" + +#include "NOD_derived_node_tree.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +/* A type representing the ordered set of nodes defining the schedule of node execution. */ +using Schedule = VectorSet<DNode>; + +/* Computes the execution schedule of the node tree. This is essentially a post-order depth first + * traversal of the node tree from the output node to the leaf input nodes, with informed order of + * traversal of dependencies based on a heuristic estimation of the number of needed buffers. */ +Schedule compute_schedule(DerivedNodeTree &tree); + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_shader_node.hh b/source/blender/compositor/realtime_compositor/COM_shader_node.hh new file mode 100644 index 00000000000..ab5565ba88d --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_shader_node.hh @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_vector.hh" + +#include "DNA_node_types.h" + +#include "GPU_material.h" + +#include "NOD_derived_node_tree.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +/* ------------------------------------------------------------------------------------------------ + * Shader Node + * + * A shader node encapsulates a compositor node tree that is capable of being used together with + * other shader nodes to construct a Shader Operation using the GPU material compiler. A GPU node + * stack for each of the node inputs and outputs is stored and populated during construction in + * order to represent the node as a GPU node inside the GPU material graph, see GPU_material.h for + * more information. Derived classes should implement the compile method to add the node and link + * it to the GPU material given to the method. The compiler is expected to initialize the input + * links of the node before invoking the compile method. See the discussion in + * COM_shader_operation.hh for more information. */ +class ShaderNode { + private: + /* The node that this operation represents. */ + DNode node_; + /* The GPU node stacks of the inputs of the node. Those are populated during construction in the + * populate_inputs method. The links of the inputs are initialized by the GPU material compiler + * prior to calling the compile method. There is an extra stack at the end to mark the end of the + * array, as this is what the GPU module functions expect. */ + Vector<GPUNodeStack> inputs_; + /* The GPU node stacks of the outputs of the node. Those are populated during construction in the + * populate_outputs method. There is an extra stack at the end to mark the end of the array, as + * this is what the GPU module functions expect. */ + Vector<GPUNodeStack> outputs_; + + public: + /* Construct the node by populating both its inputs and outputs. */ + ShaderNode(DNode node); + + virtual ~ShaderNode() = default; + + /* Compile the node by adding the appropriate GPU material graph nodes and linking the + * appropriate resources. */ + virtual void compile(GPUMaterial *material) = 0; + + /* Returns a contiguous array containing the GPU node stacks of each input. */ + GPUNodeStack *get_inputs_array(); + + /* Returns a contiguous array containing the GPU node stacks of each output. */ + GPUNodeStack *get_outputs_array(); + + protected: + /* Returns a reference to the derived node that this operation represents. */ + const DNode &node() const; + + /* Returns a reference to the node this operations represents. */ + bNode &bnode() const; + + private: + /* Populate the inputs of the node. The input link is set to nullptr and is expected to be + * initialized by the GPU material compiler before calling the compile method. */ + void populate_inputs(); + /* Populate the outputs of the node. The output link is set to nullptr and is expected to be + * initialized by the compile method. */ + void populate_outputs(); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_shader_operation.hh b/source/blender/compositor/realtime_compositor/COM_shader_operation.hh new file mode 100644 index 00000000000..3303e579336 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_shader_operation.hh @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include <memory> + +#include "BLI_map.hh" +#include "BLI_string_ref.hh" +#include "BLI_vector_set.hh" + +#include "GPU_material.h" +#include "GPU_shader.h" + +#include "NOD_derived_node_tree.hh" + +#include "COM_context.hh" +#include "COM_operation.hh" +#include "COM_scheduler.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +/* A type representing a contiguous subset of the node execution schedule that will be compiled + * into a Shader Operation. */ +using ShaderCompileUnit = VectorSet<DNode>; +/* A type representing a map that associates the identifier of each input of the operation with the + * output socket it is linked to. */ +using InputsToLinkedOutputsMap = Map<StringRef, DOutputSocket>; +/* A type representing a map that associates the output socket that provides the result of an + * output of the operation with the identifier of that output. */ +using OutputSocketsToOutputIdentifiersMap = Map<DOutputSocket, StringRef>; + +/* ------------------------------------------------------------------------------------------------ + * Shader Operation + * + * An operation that evaluates a shader compiled from a contiguous subset of the node execution + * schedule using the GPU material compiler, see GPU_material.h for more information. The subset + * of the node execution schedule is called a shader compile unit, see the discussion in + * COM_compile_state.hh for more information. + * + * Consider the following node graph with a node execution schedule denoted by the number on each + * node. The compiler may decide to compile a subset of the execution schedule into a shader + * operation, in this case, the nodes from 3 to 5 were compiled together into a shader operation. + * This subset is called the shader compile unit. See the discussion in COM_evaluator.hh for more + * information on the compilation process. Each of the nodes inside the compile unit implements a + * Shader Node which is instantiated, stored in shader_nodes_, and used during compilation. See the + * discussion in COM_shader_node.hh for more information. Links that are internal to the shader + * operation are established between the input and outputs of the shader nodes, for instance, the + * links between nodes 3 and 4 as well as those between nodes 4 and 5. However, links that cross + * the boundary of the shader operation needs special handling. + * + * Shader Operation + * +------------------------------------------------------+ + * .------------. | .------------. .------------. .------------. | .------------. + * | Node 1 | | | Node 3 | | Node 4 | | Node 5 | | | Node 6 | + * | |----|--| |--| |------| |--|--| | + * | | .-|--| | | | .---| | | | | + * '------------' | | '------------' '------------' | '------------' | '------------' + * | +----------------------------------|-------------------+ + * .------------. | | + * | Node 2 | | | + * | |--'------------------------------------' + * | | + * '------------' + * + * Links from nodes that are not part of the shader operation to nodes that are part of the shader + * operation are considered inputs of the operation itself and are declared as such. For instance, + * the link from node 1 to node 3 is declared as an input to the operation, and the same applies + * for the links from node 2 to nodes 3 and 5. Note, however, that only one input is declared for + * each distinct output socket, so both links from node 2 share the same input of the operation. + * Once an input is declared for a distinct output socket: + * + * 1. A material texture is added to the GPU material. This texture will be bound to the result of + * the output socket during evaluation. + * 2. The newly added texture is mapped to the output socket in output_to_material_texture_map_ + * to share that same texture for all inputs linked to the same output socket. + * 3. A texture loader GPU material node that samples the newly added material texture is added and + * linked to the GPU input stacks of shader nodes that the output socket is linked to. + * 4. The input of the operation is mapped to the output socket to help the compiler in + * establishing links between the operations. See the get_inputs_to_linked_outputs_map method. + * + * Links from nodes that are part of the shader operation to nodes that are not part of the shader + * operation are considered outputs of the operation itself and are declared as such. For instance, + * the link from node 5 to node 6 is declared as an output to the operation. Once an output is + * declared for an output socket: + * + * 1. A material image is added to the GPU material. This image will be bound to the result of + * the operation output during evaluation. This is the image where the result of that output + * will be written. + * 2. An image storer GPU material node that stores the output value in the newly added material + * image is added and linked to the GPU output stack of the output. + * 4. The output of the operation is mapped to the output socket to help the compiler in + * establishing links between the operations. See the get_inputs_to_linked_outputs_map method. + * + * The GPU material is declared as a compute material and its compute source is used to construct a + * compute shader that is then dispatched during operation evaluation after binding the inputs, + * outputs, and any necessary resources. */ +class ShaderOperation : public Operation { + private: + /* The compile unit that will be compiled into this shader operation. */ + ShaderCompileUnit compile_unit_; + /* The GPU material backing the operation. This is created and compiled during construction and + * freed during destruction. */ + GPUMaterial *material_; + /* A map that associates each node in the compile unit with an instance of its shader node. */ + Map<DNode, std::unique_ptr<ShaderNode>> shader_nodes_; + /* A map that associates the identifier of each input of the operation with the output socket it + * is linked to. See the above discussion for more information. */ + InputsToLinkedOutputsMap inputs_to_linked_outputs_map_; + /* A map that associates the output socket that provides the result of an output of the operation + * with the identifier of that output. See the above discussion for more information. */ + OutputSocketsToOutputIdentifiersMap output_sockets_to_output_identifiers_map_; + /* A map that associates the output socket of a node that is not part of the shader operation to + * the material texture that was created for it. This is used to share the same material texture + * with all inputs that are linked to the same output socket. */ + Map<DOutputSocket, GPUMaterialTexture *> output_to_material_texture_map_; + + public: + /* Construct and compile a GPU material from the given shader compile unit by calling + * GPU_material_from_callbacks with the appropriate callbacks. */ + ShaderOperation(Context &context, ShaderCompileUnit &compile_unit); + + /* Free the GPU material. */ + ~ShaderOperation(); + + /* Allocate the output results, bind the shader and all its needed resources, then dispatch the + * shader. */ + void execute() override; + + /* Get the identifier of the operation output corresponding to the given output socket. This is + * called by the compiler to identify the operation output that provides the result for an input + * by providing the output socket that the input is linked to. See + * output_sockets_to_output_identifiers_map_ for more information. */ + StringRef get_output_identifier_from_output_socket(DOutputSocket output); + + /* Get a reference to the inputs to linked outputs map of the operation. This is called by the + * compiler to identify the output that each input of the operation is linked to for correct + * input mapping. See inputs_to_linked_outputs_map_ for more information. */ + InputsToLinkedOutputsMap &get_inputs_to_linked_outputs_map(); + + /* Compute and set the initial reference counts of all the results of the operation. The + * reference counts of the results are the number of operations that use those results, which is + * computed as the number of inputs whose node is part of the schedule and is linked to the + * output corresponding to each of the results of the operation. The node execution schedule is + * given as an input. */ + void compute_results_reference_counts(const Schedule &schedule); + + private: + /* Bind the uniform buffer of the GPU material as well as any color band textures needed by the + * GPU material. Other resources like attributes and textures that reference images are not bound + * because the GPU material is guaranteed not to have any of them. Textures that reference the + * inputs of the operation and images that reference the outputs of the operation are bound in + * the bind_inputs and bind_outputs methods respectively. The compiled shader of the material is + * given as an argument and assumed to be bound. */ + void bind_material_resources(GPUShader *shader); + + /* Bind the input results of the operation to the appropriate textures in the GPU material. The + * material textures stored in output_to_material_texture_map_ have sampler names that match + * the identifiers of the operation inputs that they correspond to. The compiled shader of the + * material is given as an argument and assumed to be bound. */ + void bind_inputs(GPUShader *shader); + + /* Bind the output results of the operation to the appropriate images in the GPU material. Every + * image in the GPU material corresponds to one of the outputs of the operation, an output whose + * identifier is the name of the image in the GPU material shader. The compiled shader of the + * material is given as an argument and assumed to be bound. */ + void bind_outputs(GPUShader *shader); + + /* A static callback method of interface GPUMaterialSetupFn that is passed to + * GPU_material_from_callbacks to setup the GPU material. The thunk parameter will be a pointer + * to the instance of ShaderOperation that is being compiled. This methods setup the GPU + * material as a compute one. */ + static void setup_material(void *thunk, GPUMaterial *material); + + /* A static callback method of interface GPUMaterialCompileFn that is passed to + * GPU_material_from_callbacks to compile the GPU material. The thunk parameter will be a pointer + * to the instance of ShaderOperation that is being compiled. The method goes over the compile + * unit and does the following for each node: + * + * - Instantiate a ShaderNode from the node and add it to shader_nodes_. + * - Link the inputs of the node if needed. The inputs are either linked to other nodes in the + * GPU material graph or they are exposed as inputs to the shader operation itself if they are + * linked to nodes that are not part of the shader operation. + * - Call the compile method of the shader node to actually add and link the GPU material graph + * nodes. + * - If any of the outputs of the node are linked to nodes that are not part of the shader + * operation, they are exposed as outputs to the shader operation itself. */ + static void compile_material(void *thunk, GPUMaterial *material); + + /* Link the inputs of the node if needed. Unlinked inputs are ignored as they will be linked by + * the node compile method. If the input is linked to a node that is not part of the shader + * operation, the input will be exposed as an input to the shader operation and linked to it. + * While if the input is linked to a node that is part of the shader operation, then it is linked + * to that node in the GPU material node graph. */ + void link_node_inputs(DNode node, GPUMaterial *material); + + /* Given the input of a node that is part of the shader operation which is linked to the given + * output of a node that is also part of the shader operation, map the output link of the GPU + * node stack of the output to the input link of the GPU node stack of the input. This + * essentially establishes the needed links in the GPU material node graph. */ + void map_node_input(DInputSocket input, DOutputSocket output); + + /* Given the input of a node that is part of the shader operation which is linked to the given + * output of a node that is not part of the shader operation, declare a new input to the + * operation and link it appropriately as detailed in the discussion above. */ + void declare_operation_input_if_needed(DInputSocket input, + DOutputSocket output, + GPUMaterial *material); + + /* Link the input node stack corresponding to the given input to an input loader GPU material + * node sampling the material texture corresponding to the given output. */ + void link_material_input_loader(DInputSocket input, DOutputSocket output, GPUMaterial *material); + + /* Populate the output results of the shader operation for outputs of the given node that + * are linked to nodes outside of the shader operation. */ + void populate_results_for_node(DNode node, GPUMaterial *material); + + /* Given the output of a node that is part of the shader operation which is linked to an input of + * a node that is not part of the shader operation, declare a new output to the operation and + * link it appropriately as detailed in the discussion above. */ + void populate_operation_result(DOutputSocket output, GPUMaterial *material); + + /* A static callback method of interface GPUCodegenCallbackFn that is passed to + * GPU_material_from_callbacks to create the shader create info of the GPU material. The thunk + * parameter will be a pointer to the instance of ShaderOperation that is being compiled. + * This method setup the shader create info as a compute shader and sets its generated source + * based on the GPU material code generator output. */ + static void generate_material(void *thunk, + GPUMaterial *material, + GPUCodegenOutput *code_generator); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_shader_pool.hh b/source/blender/compositor/realtime_compositor/COM_shader_pool.hh new file mode 100644 index 00000000000..e7db398b972 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_shader_pool.hh @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_map.hh" +#include "BLI_string_ref.hh" + +#include "GPU_shader.h" + +namespace blender::realtime_compositor { + +/* ------------------------------------------------------------------------------------------------- + * Shader Pool + * + * A shader pool is a pool of shaders identified by their info name that can be reused throughout + * the evaluation of the compositor and are only freed when the shader pool is destroyed. */ +class ShaderPool { + private: + /* The set of shaders identified by their info name that are currently available in the pool to + * be acquired. */ + Map<StringRef, GPUShader *> shaders_; + + public: + ~ShaderPool(); + + /* Check if there is an available shader with the given info name in the pool, if such shader + * exists, return it, otherwise, return a newly created shader and add it to the pool. */ + GPUShader *acquire(const char *info_name); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_simple_operation.hh b/source/blender/compositor/realtime_compositor/COM_simple_operation.hh new file mode 100644 index 00000000000..2f743da50d6 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_simple_operation.hh @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_string_ref.hh" + +#include "COM_operation.hh" +#include "COM_result.hh" + +namespace blender::realtime_compositor { + +/* ------------------------------------------------------------------------------------------------ + * Simple Operation + * + * A simple operation is an operation that takes exactly one input and computes exactly one output. + * Moreover, the output is guaranteed to only have a single user, that is, its reference count will + * be one. Such operations can be attached to the inputs of operations to pre-process the inputs to + * prepare them before the operation is executed.*/ +class SimpleOperation : public Operation { + private: + /* The identifier of the output. This is constant for all operations. */ + static const StringRef output_identifier_; + /* The identifier of the input. This is constant for all operations. */ + static const StringRef input_identifier_; + + public: + using Operation::Operation; + + /* Get a reference to the output result of the operation, this essentially calls the super + * get_result method with the output identifier of the operation. */ + Result &get_result(); + + /* Map the input of the operation to the given result, this essentially calls the super + * map_input_to_result method with the input identifier of the operation. */ + void map_input_to_result(Result *result); + + protected: + /* Simple operations don't need input processors, so override with an empty implementation. */ + void add_and_evaluate_input_processors() override; + + /* Get a reference to the input result of the operation, this essentially calls the super + * get_result method with the input identifier of the operation. */ + Result &get_input(); + + /* Switch the result mapped to the input with the given result, this essentially calls the super + * switch_result_mapped_to_input method with the input identifier of the operation. */ + void switch_result_mapped_to_input(Result *result); + + /* Populate the result of the operation, this essentially calls the super populate_result method + * with the output identifier of the operation and sets the initial reference count of the result + * to 1, since the result of an operation operation is guaranteed to have a single user. */ + void populate_result(Result result); + + /* Declare the descriptor of the input of the operation to be the given descriptor, this + * essentially calls the super declare_input_descriptor method with the input identifier of the + * operation. */ + void declare_input_descriptor(InputDescriptor descriptor); + + /* Get a reference to the descriptor of the input, this essentially calls the super + * get_input_descriptor method with the input identifier of the operation. */ + InputDescriptor &get_input_descriptor(); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_texture_pool.hh b/source/blender/compositor/realtime_compositor/COM_texture_pool.hh new file mode 100644 index 00000000000..cc6641d288f --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_texture_pool.hh @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include <cstdint> + +#include "BLI_map.hh" +#include "BLI_math_vec_types.hh" +#include "BLI_vector.hh" + +#include "GPU_texture.h" + +namespace blender::realtime_compositor { + +/* ------------------------------------------------------------------------------------------------ + * Texture Pool Key + * + * A key used to identify a texture specification in a texture pool. Defines a hash and an equality + * operator for use in a hash map. */ +class TexturePoolKey { + public: + int2 size; + eGPUTextureFormat format; + + /* Construct a key from the given texture size and format. */ + TexturePoolKey(int2 size, eGPUTextureFormat format); + + /* Construct a key from the size and format of the given texture. */ + TexturePoolKey(const GPUTexture *texture); + + uint64_t hash() const; +}; + +bool operator==(const TexturePoolKey &a, const TexturePoolKey &b); + +/* ------------------------------------------------------------------------------------------------ + * Texture Pool + * + * A texture pool allows the allocation and reuse of textures throughout the execution of the + * compositor to avoid memory fragmentation and texture allocation overheads. The texture pool + * delegates the actual texture allocation to an allocate_texture method that should be implemented + * by the caller of the compositor evaluator, allowing a more agnostic and flexible execution that + * can be controlled by the caller. If the compositor is expected to execute frequently, like on + * every redraw, then the allocation method should use a persistent texture pool to allow + * cross-evaluation texture pooling, for instance, by using the DRWTexturePool. But if the + * evaluator is expected to execute infrequently, the allocated textures can just be freed when the + * evaluator is done, that is, when the pool is destructed. */ +class TexturePool { + private: + /* The set of textures in the pool that are available to acquire for each distinct texture + * specification. */ + Map<TexturePoolKey, Vector<GPUTexture *>> textures_; + + public: + /* Check if there is an available texture with the given specification in the pool, if such + * texture exists, return it, otherwise, return a newly allocated texture. Expect the texture to + * be uncleared and possibly contains garbage data. */ + GPUTexture *acquire(int2 size, eGPUTextureFormat format); + + /* Shorthand for acquire with GPU_RGBA16F format. */ + GPUTexture *acquire_color(int2 size); + + /* Shorthand for acquire with GPU_RGBA16F format. Identical to acquire_color because vectors + * are stored in RGBA textures, due to the limited support for RGB textures. */ + GPUTexture *acquire_vector(int2 size); + + /* Shorthand for acquire with GPU_R16F format. */ + GPUTexture *acquire_float(int2 size); + + /* Put the texture back into the pool, potentially to be acquired later by another user. Expects + * the texture to be one that was acquired using the same texture pool. */ + void release(GPUTexture *texture); + + /* Reset the texture pool by clearing all available textures without freeing the textures. If the + * textures will no longer be needed, they should be freed in the destructor. This should be + * called after the compositor is done evaluating. */ + void reset(); + + private: + /* Returns a newly allocated texture with the given specification. This method should be + * implemented by the caller of the compositor evaluator. See the class description for more + * information. */ + virtual GPUTexture *allocate_texture(int2 size, eGPUTextureFormat format) = 0; +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_utilities.hh b/source/blender/compositor/realtime_compositor/COM_utilities.hh new file mode 100644 index 00000000000..0e359c2090a --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_utilities.hh @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_function_ref.hh" +#include "BLI_math_vec_types.hh" + +#include "NOD_derived_node_tree.hh" + +#include "GPU_shader.h" + +#include "COM_input_descriptor.hh" +#include "COM_result.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +/* Get the origin socket of the given node input. If the input is not linked, the socket itself is + * returned. If the input is linked, the socket that is linked to it is returned, which could + * either be an input or an output. An input socket is returned when the given input is connected + * to an unlinked input of a group input node. */ +DSocket get_input_origin_socket(DInputSocket input); + +/* Get the output socket linked to the given node input. If the input is not linked to an output, a + * null output is returned. */ +DOutputSocket get_output_linked_to_input(DInputSocket input); + +/* Get the result type that corresponds to the type of the given socket. */ +ResultType get_node_socket_result_type(const SocketRef *socket); + +/* Returns true if any of the nodes linked to the given output satisfies the given condition, and + * false otherwise. */ +bool is_output_linked_to_node_conditioned(DOutputSocket output, + FunctionRef<bool(DNode)> condition); + +/* Returns the number of inputs linked to the given output that satisfy the given condition. */ +int number_of_inputs_linked_to_output_conditioned(DOutputSocket output, + FunctionRef<bool(DInputSocket)> condition); + +/* A node is a shader node if it defines a method to get a shader node operation. */ +bool is_shader_node(DNode node); + +/* Returns true if the given node is supported, that is, have an implementation. Returns false + * otherwise. */ +bool is_node_supported(DNode node); + +/* Get the input descriptor of the given input socket. */ +InputDescriptor input_descriptor_from_input_socket(const InputSocketRef *socket); + +/* Dispatch the given compute shader in a 2D compute space such that the number of invocations in + * both dimensions is as small as possible but at least covers the entirety of global_size assuming + * the shader has a local group size given by local_size. That means that the number of invocations + * might be a bit larger than global_size, so shaders has to put that into consideration. A default + * local size of 16x16 is assumed, which is the optimal local size for many image processing + * shaders. */ +void compute_dispatch_global(GPUShader *shader, int2 global_size, int2 local_size = int2(16)); + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/compile_state.cc b/source/blender/compositor/realtime_compositor/intern/compile_state.cc new file mode 100644 index 00000000000..9fde6568103 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/compile_state.cc @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <limits> + +#include "BLI_math_vec_types.hh" + +#include "DNA_node_types.h" + +#include "NOD_derived_node_tree.hh" + +#include "COM_compile_state.hh" +#include "COM_domain.hh" +#include "COM_input_descriptor.hh" +#include "COM_node_operation.hh" +#include "COM_result.hh" +#include "COM_scheduler.hh" +#include "COM_shader_operation.hh" +#include "COM_utilities.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +CompileState::CompileState(const Schedule &schedule) : schedule_(schedule) +{ +} + +const Schedule &CompileState::get_schedule() +{ + return schedule_; +} + +void CompileState::map_node_to_node_operation(DNode node, NodeOperation *operations) +{ + return node_operations_.add_new(node, operations); +} + +void CompileState::map_node_to_shader_operation(DNode node, ShaderOperation *operations) +{ + return shader_operations_.add_new(node, operations); +} + +Result &CompileState::get_result_from_output_socket(DOutputSocket output) +{ + /* The output belongs to a node that was compiled into a standard node operation, so return a + * reference to the result from that operation using the output identifier. */ + if (node_operations_.contains(output.node())) { + NodeOperation *operation = node_operations_.lookup(output.node()); + return operation->get_result(output->identifier()); + } + + /* Otherwise, the output belongs to a node that was compiled into a shader operation, so + * retrieve the internal identifier of that output and return a reference to the result from + * that operation using the retrieved identifier. */ + ShaderOperation *operation = shader_operations_.lookup(output.node()); + return operation->get_result(operation->get_output_identifier_from_output_socket(output)); +} + +void CompileState::add_node_to_shader_compile_unit(DNode node) +{ + /* Add the node to the shader compile unit. */ + shader_compile_unit_.add_new(node); + + /* If the domain of the shader compile unit is not yet determined or was determined to be + * an identity domain, update it to be the computed domain of the node. */ + if (shader_compile_unit_domain_ == Domain::identity()) { + shader_compile_unit_domain_ = compute_shader_node_domain(node); + } +} + +ShaderCompileUnit &CompileState::get_shader_compile_unit() +{ + return shader_compile_unit_; +} + +void CompileState::reset_shader_compile_unit() +{ + return shader_compile_unit_.clear(); +} + +bool CompileState::should_compile_shader_compile_unit(DNode node) +{ + /* If the shader compile unit is empty, then it can't be compiled yet. */ + if (shader_compile_unit_.is_empty()) { + return false; + } + + /* If the node is not a shader node, then it can't be added to the shader compile unit and the + * shader compile unit is considered complete and should be compiled. */ + if (!is_shader_node(node)) { + return true; + } + + /* If the computed domain of the node doesn't matches the domain of the shader compile unit, then + * it can't be added to the shader compile unit and the shader compile unit is considered + * complete and should be compiled. Identity domains are an exception as they are always + * compatible because they represents single values. */ + if (shader_compile_unit_domain_ != Domain::identity() && + shader_compile_unit_domain_ != compute_shader_node_domain(node)) { + return true; + } + + /* Otherwise, the node is compatible and can be added to the compile unit and it shouldn't be + * compiled just yet. */ + return false; +} + +Domain CompileState::compute_shader_node_domain(DNode node) +{ + /* Default to an identity domain in case no domain input was found, most likely because all + * inputs are single values. */ + Domain node_domain = Domain::identity(); + int current_domain_priority = std::numeric_limits<int>::max(); + + /* Go over the inputs and find the domain of the non single value input with the highest domain + * priority. */ + for (const InputSocketRef *input_ref : node->inputs()) { + const DInputSocket input{node.context(), input_ref}; + + /* Get the output linked to the input. If it is null, that means the input is unlinked, so skip + * it. */ + const DOutputSocket output = get_output_linked_to_input(input); + if (!output) { + continue; + } + + /* Get the input descriptor of the input. */ + const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input_ref); + + /* If the output belongs to a node that is part of the shader compile unit, then the domain of + * the input is the domain of the compile unit itself. */ + if (shader_compile_unit_.contains(output.node())) { + /* Single value inputs can't be domain inputs. */ + if (shader_compile_unit_domain_.size == int2(1)) { + continue; + } + + /* Notice that the lower the domain priority value is, the higher the priority is, hence the + * less than comparison. */ + if (input_descriptor.domain_priority < current_domain_priority) { + node_domain = shader_compile_unit_domain_; + current_domain_priority = input_descriptor.domain_priority; + } + continue; + } + + /* Get the result linked to the input. */ + const Result &result = get_result_from_output_socket(output); + + /* A single value input can't be a domain input. */ + if (result.is_single_value() || input_descriptor.expects_single_value) { + continue; + } + + /* Notice that the lower the domain priority value is, the higher the priority is, hence the + * less than comparison. */ + if (input_descriptor.domain_priority < current_domain_priority) { + node_domain = result.domain(); + current_domain_priority = input_descriptor.domain_priority; + } + } + + return node_domain; +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/context.cc b/source/blender/compositor/realtime_compositor/intern/context.cc new file mode 100644 index 00000000000..199547ef8fc --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/context.cc @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "COM_context.hh" +#include "COM_shader_pool.hh" +#include "COM_texture_pool.hh" + +namespace blender::realtime_compositor { + +Context::Context(TexturePool &texture_pool) : texture_pool_(texture_pool) +{ +} + +int Context::get_frame_number() const +{ + return get_scene()->r.cfra; +} + +float Context::get_time() const +{ + const float frame_number = static_cast<float>(get_frame_number()); + const float frame_rate = static_cast<float>(get_scene()->r.frs_sec) / + static_cast<float>(get_scene()->r.frs_sec_base); + return frame_number / frame_rate; +} + +TexturePool &Context::texture_pool() +{ + return texture_pool_; +} + +ShaderPool &Context::shader_pool() +{ + return shader_pool_; +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/conversion_operation.cc b/source/blender/compositor/realtime_compositor/intern/conversion_operation.cc new file mode 100644 index 00000000000..08a3c2c3899 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/conversion_operation.cc @@ -0,0 +1,220 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_math_vec_types.hh" + +#include "GPU_shader.h" + +#include "COM_context.hh" +#include "COM_conversion_operation.hh" +#include "COM_input_descriptor.hh" +#include "COM_result.hh" +#include "COM_utilities.hh" + +namespace blender::realtime_compositor { + +/* ------------------------------------------------------------------------------------------------- + * Conversion Operation. + */ + +void ConversionOperation::execute() +{ + Result &result = get_result(); + const Result &input = get_input(); + + if (input.is_single_value()) { + result.allocate_single_value(); + execute_single(input, result); + return; + } + + result.allocate_texture(input.domain()); + + GPUShader *shader = get_conversion_shader(); + GPU_shader_bind(shader); + + input.bind_as_texture(shader, "input_sampler"); + result.bind_as_image(shader, "output_image"); + + compute_dispatch_global(shader, input.domain().size); + + input.unbind_as_texture(); + result.unbind_as_image(); + GPU_shader_unbind(); +} + +SimpleOperation *ConversionOperation::construct_if_needed(Context &context, + const Result &input_result, + const InputDescriptor &input_descriptor) +{ + ResultType result_type = input_result.type(); + ResultType expected_type = input_descriptor.type; + + /* If the result type differs from the expected type, return an instance of an appropriate + * conversion operation. Otherwise, return a null pointer. */ + if (result_type == ResultType::Float && expected_type == ResultType::Vector) { + return new ConvertFloatToVectorOperation(context); + } + else if (result_type == ResultType::Float && expected_type == ResultType::Color) { + return new ConvertFloatToColorOperation(context); + } + else if (result_type == ResultType::Color && expected_type == ResultType::Float) { + return new ConvertColorToFloatOperation(context); + } + else if (result_type == ResultType::Color && expected_type == ResultType::Vector) { + return new ConvertColorToVectorOperation(context); + } + else if (result_type == ResultType::Vector && expected_type == ResultType::Float) { + return new ConvertVectorToFloatOperation(context); + } + else if (result_type == ResultType::Vector && expected_type == ResultType::Color) { + return new ConvertVectorToColorOperation(context); + } + else { + return nullptr; + } +} + +/* ------------------------------------------------------------------------------------------------- + * Convert Float To Vector Operation. + */ + +ConvertFloatToVectorOperation::ConvertFloatToVectorOperation(Context &context) + : ConversionOperation(context) +{ + InputDescriptor input_descriptor; + input_descriptor.type = ResultType::Float; + declare_input_descriptor(input_descriptor); + populate_result(Result(ResultType::Vector, texture_pool())); +} + +void ConvertFloatToVectorOperation::execute_single(const Result &input, Result &output) +{ + output.set_vector_value(float3(input.get_float_value())); +} + +GPUShader *ConvertFloatToVectorOperation::get_conversion_shader() const +{ + return shader_pool().acquire("compositor_convert_float_to_vector"); +} + +/* ------------------------------------------------------------------------------------------------- + * Convert Float To Color Operation. + */ + +ConvertFloatToColorOperation::ConvertFloatToColorOperation(Context &context) + : ConversionOperation(context) +{ + InputDescriptor input_descriptor; + input_descriptor.type = ResultType::Float; + declare_input_descriptor(input_descriptor); + populate_result(Result(ResultType::Color, texture_pool())); +} + +void ConvertFloatToColorOperation::execute_single(const Result &input, Result &output) +{ + float4 color = float4(input.get_float_value()); + color[3] = 1.0f; + output.set_color_value(color); +} + +GPUShader *ConvertFloatToColorOperation::get_conversion_shader() const +{ + return shader_pool().acquire("compositor_convert_float_to_color"); +} + +/* ------------------------------------------------------------------------------------------------- + * Convert Color To Float Operation. + */ + +ConvertColorToFloatOperation::ConvertColorToFloatOperation(Context &context) + : ConversionOperation(context) +{ + InputDescriptor input_descriptor; + input_descriptor.type = ResultType::Color; + declare_input_descriptor(input_descriptor); + populate_result(Result(ResultType::Float, texture_pool())); +} + +void ConvertColorToFloatOperation::execute_single(const Result &input, Result &output) +{ + float4 color = input.get_color_value(); + output.set_float_value((color[0] + color[1] + color[2]) / 3.0f); +} + +GPUShader *ConvertColorToFloatOperation::get_conversion_shader() const +{ + return shader_pool().acquire("compositor_convert_color_to_float"); +} + +/* ------------------------------------------------------------------------------------------------- + * Convert Color To Vector Operation. + */ + +ConvertColorToVectorOperation::ConvertColorToVectorOperation(Context &context) + : ConversionOperation(context) +{ + InputDescriptor input_descriptor; + input_descriptor.type = ResultType::Color; + declare_input_descriptor(input_descriptor); + populate_result(Result(ResultType::Vector, texture_pool())); +} + +void ConvertColorToVectorOperation::execute_single(const Result &input, Result &output) +{ + float4 color = input.get_color_value(); + output.set_vector_value(float3(color)); +} + +GPUShader *ConvertColorToVectorOperation::get_conversion_shader() const +{ + return shader_pool().acquire("compositor_convert_color_to_vector"); +} + +/* ------------------------------------------------------------------------------------------------- + * Convert Vector To Float Operation. + */ + +ConvertVectorToFloatOperation::ConvertVectorToFloatOperation(Context &context) + : ConversionOperation(context) +{ + InputDescriptor input_descriptor; + input_descriptor.type = ResultType::Vector; + declare_input_descriptor(input_descriptor); + populate_result(Result(ResultType::Float, texture_pool())); +} + +void ConvertVectorToFloatOperation::execute_single(const Result &input, Result &output) +{ + float3 vector = input.get_vector_value(); + output.set_float_value((vector[0] + vector[1] + vector[2]) / 3.0f); +} + +GPUShader *ConvertVectorToFloatOperation::get_conversion_shader() const +{ + return shader_pool().acquire("compositor_convert_vector_to_float"); +} + +/* ------------------------------------------------------------------------------------------------- + * Convert Vector To Color Operation. + */ + +ConvertVectorToColorOperation::ConvertVectorToColorOperation(Context &context) + : ConversionOperation(context) +{ + InputDescriptor input_descriptor; + input_descriptor.type = ResultType::Vector; + declare_input_descriptor(input_descriptor); + populate_result(Result(ResultType::Color, texture_pool())); +} + +void ConvertVectorToColorOperation::execute_single(const Result &input, Result &output) +{ + output.set_color_value(float4(input.get_vector_value(), 1.0f)); +} + +GPUShader *ConvertVectorToColorOperation::get_conversion_shader() const +{ + return shader_pool().acquire("compositor_convert_vector_to_color"); +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/domain.cc b/source/blender/compositor/realtime_compositor/intern/domain.cc new file mode 100644 index 00000000000..31b297c212e --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/domain.cc @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_float3x3.hh" +#include "BLI_math_vec_types.hh" + +#include "COM_domain.hh" + +namespace blender::realtime_compositor { + +Domain::Domain(int2 size) : size(size), transformation(float3x3::identity()) +{ +} + +Domain::Domain(int2 size, float3x3 transformation) : size(size), transformation(transformation) +{ +} + +void Domain::transform(const float3x3 &input_transformation) +{ + transformation = input_transformation * transformation; +} + +Domain Domain::identity() +{ + return Domain(int2(1), float3x3::identity()); +} + +bool operator==(const Domain &a, const Domain &b) +{ + return a.size == b.size && a.transformation == b.transformation; +} + +bool operator!=(const Domain &a, const Domain &b) +{ + return !(a == b); +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/evaluator.cc b/source/blender/compositor/realtime_compositor/intern/evaluator.cc new file mode 100644 index 00000000000..af27b89ca26 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/evaluator.cc @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <string> + +#include "DNA_node_types.h" + +#include "NOD_derived_node_tree.hh" + +#include "COM_compile_state.hh" +#include "COM_context.hh" +#include "COM_evaluator.hh" +#include "COM_input_single_value_operation.hh" +#include "COM_node_operation.hh" +#include "COM_operation.hh" +#include "COM_result.hh" +#include "COM_scheduler.hh" +#include "COM_shader_operation.hh" +#include "COM_utilities.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +Evaluator::Evaluator(Context &context, bNodeTree &node_tree) + : context_(context), node_tree_(node_tree) +{ +} + +void Evaluator::evaluate() +{ + /* Reset the texture pool that was potentially populated from a previous evaluation. */ + context_.texture_pool().reset(); + + /* The node tree is not compiled yet, so compile and evaluate the node tree. */ + if (!is_compiled_) { + compile_and_evaluate(); + is_compiled_ = true; + return; + } + + /* The node tree is already compiled, so just go over the operations stream and evaluate the + * operations in order. */ + for (const std::unique_ptr<Operation> &operation : operations_stream_) { + operation->evaluate(); + } +} + +void Evaluator::reset() +{ + /* Reset evaluator state. */ + operations_stream_.clear(); + derived_node_tree_.reset(); + node_tree_reference_map_.clear(); + + /* Mark the node tree as in need to be compiled. */ + is_compiled_ = false; +} + +bool Evaluator::validate_node_tree() +{ + if (derived_node_tree_->has_link_cycles()) { + context_.set_info_message("Compositor node tree has cyclic links!"); + return false; + } + + if (derived_node_tree_->has_undefined_nodes_or_sockets()) { + context_.set_info_message("Compositor node tree has undefined nodes or sockets!"); + return false; + } + + /* Find any of the unsupported nodes in the node tree. We only track one of them because we + * display a message for only one at a time to avoid long messages. */ + DNode unsupported_node; + derived_node_tree_->foreach_node([&](DNode node) { + if (!is_node_supported(node)) { + unsupported_node = node; + } + }); + + /* unsupported_node is null if no unsupported node was found. */ + if (unsupported_node) { + std::string message = "Compositor node tree has an unsupported node: "; + context_.set_info_message(message + unsupported_node->idname()); + return false; + } + + return true; +} + +void Evaluator::compile_and_evaluate() +{ + /* Construct and initialize a derived node tree from the compositor node tree. */ + derived_node_tree_.reset(new DerivedNodeTree(node_tree_, node_tree_reference_map_)); + + /* Validate the node tree and do nothing if it is invalid. */ + if (!validate_node_tree()) { + return; + } + + /* Compute the node execution schedule. */ + const Schedule schedule = compute_schedule(*derived_node_tree_); + + /* Declare a compile state to use for tracking the state of the compilation. */ + CompileState compile_state(schedule); + + /* Go over the nodes in the schedule, compiling them into either node operations or shader + * operations. */ + for (const DNode &node : schedule) { + /* Ask the compile state if now would be a good time to compile the shader compile unit given + * the current node, and if it is, compile and evaluate it. */ + if (compile_state.should_compile_shader_compile_unit(node)) { + compile_and_evaluate_shader_compile_unit(compile_state); + } + + /* If the node is a shader node, then add it to the shader compile unit. */ + if (is_shader_node(node)) { + compile_state.add_node_to_shader_compile_unit(node); + } + else { + /* Otherwise, compile and evaluate the node into a node operation. */ + compile_and_evaluate_node(node, compile_state); + } + } +} + +void Evaluator::compile_and_evaluate_node(DNode node, CompileState &compile_state) +{ + /* Get an instance of the node's compositor operation. */ + NodeOperation *operation = node->typeinfo()->get_compositor_operation(context_, node); + + /* Map the node to the compiled operation. */ + compile_state.map_node_to_node_operation(node, operation); + + /* Map the inputs of the operation to the results of the outputs they are linked to. */ + map_node_operation_inputs_to_their_results(node, operation, compile_state); + + /* Add the operation to the operations stream. This has to be done after input mapping because + * the method may add Input Single Value Operations to the operations stream. */ + operations_stream_.append(std::unique_ptr<Operation>(operation)); + + /* Compute the initial reference counts of the results of the operation. */ + operation->compute_results_reference_counts(compile_state.get_schedule()); + + /* Evaluate the operation. */ + operation->evaluate(); +} + +void Evaluator::map_node_operation_inputs_to_their_results(DNode node, + NodeOperation *operation, + CompileState &compile_state) +{ + for (const InputSocketRef *input_ref : node->inputs()) { + const DInputSocket input{node.context(), input_ref}; + + /* Get the output linked to the input. */ + const DOutputSocket output = get_output_linked_to_input(input); + + /* The output is not null, which means the input is linked. So map the input to the result we + * get from the output. */ + if (output) { + Result &result = compile_state.get_result_from_output_socket(output); + operation->map_input_to_result(input->identifier(), &result); + continue; + } + + /* Otherwise, the output is null, which means the input is unlinked. So map the input to the + * result of a newly created Input Single Value Operation. */ + InputSingleValueOperation *input_operation = new InputSingleValueOperation(context_, input); + operation->map_input_to_result(input->identifier(), &input_operation->get_result()); + + /* Add the input operation to the operations stream. */ + operations_stream_.append(std::unique_ptr<InputSingleValueOperation>(input_operation)); + + /* Evaluate the input operation. */ + input_operation->evaluate(); + } +} + +void Evaluator::compile_and_evaluate_shader_compile_unit(CompileState &compile_state) +{ + /* Compile the shader compile unit into a shader operation. */ + ShaderCompileUnit &compile_unit = compile_state.get_shader_compile_unit(); + ShaderOperation *operation = new ShaderOperation(context_, compile_unit); + + /* Map each of the nodes in the compile unit to the compiled operation. */ + for (DNode node : compile_unit) { + compile_state.map_node_to_shader_operation(node, operation); + } + + /* Map the inputs of the operation to the results of the outputs they are linked to. */ + map_shader_operation_inputs_to_their_results(operation, compile_state); + + /* Add the operation to the operations stream. */ + operations_stream_.append(std::unique_ptr<Operation>(operation)); + + /* Compute the initial reference counts of the results of the operation. */ + operation->compute_results_reference_counts(compile_state.get_schedule()); + + /* Evaluate the operation. */ + operation->evaluate(); + + /* Clear the shader compile unit to ready it for tracking the next shader operation. */ + compile_state.reset_shader_compile_unit(); +} + +void Evaluator::map_shader_operation_inputs_to_their_results(ShaderOperation *operation, + CompileState &compile_state) +{ + /* For each input of the operation, retrieve the result of the output linked to it, and map the + * result to the input. */ + InputsToLinkedOutputsMap &map = operation->get_inputs_to_linked_outputs_map(); + for (const InputsToLinkedOutputsMap::Item &item : map.items()) { + Result &result = compile_state.get_result_from_output_socket(item.value); + operation->map_input_to_result(item.key, &result); + } +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/input_single_value_operation.cc b/source/blender/compositor/realtime_compositor/intern/input_single_value_operation.cc new file mode 100644 index 00000000000..195252e1c4c --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/input_single_value_operation.cc @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_math_vec_types.hh" + +#include "COM_input_single_value_operation.hh" +#include "COM_operation.hh" +#include "COM_result.hh" +#include "COM_utilities.hh" + +namespace blender::realtime_compositor { + +const StringRef InputSingleValueOperation::output_identifier_ = StringRef("Output"); + +InputSingleValueOperation::InputSingleValueOperation(Context &context, DInputSocket input_socket) + : Operation(context), input_socket_(input_socket) +{ + /* Populate the output result. */ + const ResultType result_type = get_node_socket_result_type(input_socket_.socket_ref()); + Result result = Result(result_type, texture_pool()); + + /* The result of an input single value operation is guaranteed to have a single user. */ + result.set_initial_reference_count(1); + + populate_result(result); +} + +void InputSingleValueOperation::execute() +{ + /* Allocate a single value for the result. */ + Result &result = get_result(); + result.allocate_single_value(); + + /* Set the value of the result to the default value of the input socket. */ + switch (result.type()) { + case ResultType::Float: + result.set_float_value(input_socket_->default_value<bNodeSocketValueFloat>()->value); + break; + case ResultType::Vector: + result.set_vector_value( + float3(input_socket_->default_value<bNodeSocketValueVector>()->value)); + break; + case ResultType::Color: + result.set_color_value(float4(input_socket_->default_value<bNodeSocketValueRGBA>()->value)); + break; + } +} + +Result &InputSingleValueOperation::get_result() +{ + return Operation::get_result(output_identifier_); +} + +void InputSingleValueOperation::populate_result(Result result) +{ + Operation::populate_result(output_identifier_, result); +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/node_operation.cc b/source/blender/compositor/realtime_compositor/intern/node_operation.cc new file mode 100644 index 00000000000..9d6332a8b2f --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/node_operation.cc @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <memory> + +#include "BLI_map.hh" +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" + +#include "DNA_node_types.h" + +#include "NOD_derived_node_tree.hh" +#include "NOD_node_declaration.hh" + +#include "COM_context.hh" +#include "COM_input_descriptor.hh" +#include "COM_node_operation.hh" +#include "COM_operation.hh" +#include "COM_result.hh" +#include "COM_scheduler.hh" +#include "COM_utilities.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +NodeOperation::NodeOperation(Context &context, DNode node) : Operation(context), node_(node) +{ + /* Populate the output results. */ + for (const OutputSocketRef *output : node->outputs()) { + const ResultType result_type = get_node_socket_result_type(output); + const Result result = Result(result_type, texture_pool()); + populate_result(output->identifier(), result); + } + + /* Populate the input descriptors. */ + for (const InputSocketRef *input : node->inputs()) { + const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input); + declare_input_descriptor(input->identifier(), input_descriptor); + } +} + +void NodeOperation::compute_results_reference_counts(const Schedule &schedule) +{ + for (const OutputSocketRef *output_ref : node()->outputs()) { + const DOutputSocket output{node().context(), output_ref}; + + const int reference_count = number_of_inputs_linked_to_output_conditioned( + output, [&](DInputSocket input) { return schedule.contains(input.node()); }); + + get_result(output->identifier()).set_initial_reference_count(reference_count); + } +} + +const DNode &NodeOperation::node() const +{ + return node_; +} + +const bNode &NodeOperation::bnode() const +{ + return *node_->bnode(); +} + +bool NodeOperation::should_compute_output(StringRef identifier) +{ + return get_result(identifier).should_compute(); +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/operation.cc b/source/blender/compositor/realtime_compositor/intern/operation.cc new file mode 100644 index 00000000000..85893918dda --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/operation.cc @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <limits> +#include <memory> + +#include "BLI_map.hh" +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" + +#include "COM_context.hh" +#include "COM_conversion_operation.hh" +#include "COM_domain.hh" +#include "COM_input_descriptor.hh" +#include "COM_operation.hh" +#include "COM_realize_on_domain_operation.hh" +#include "COM_reduce_to_single_value_operation.hh" +#include "COM_result.hh" +#include "COM_shader_pool.hh" +#include "COM_simple_operation.hh" +#include "COM_texture_pool.hh" + +namespace blender::realtime_compositor { + +Operation::Operation(Context &context) : context_(context) +{ +} + +Operation::~Operation() = default; + +void Operation::evaluate() +{ + evaluate_input_processors(); + + reset_results(); + + execute(); + + release_inputs(); +} + +Result &Operation::get_result(StringRef identifier) +{ + return results_.lookup(identifier); +} + +void Operation::map_input_to_result(StringRef identifier, Result *result) +{ + results_mapped_to_inputs_.add_new(identifier, result); +} + +Domain Operation::compute_domain() +{ + /* Default to an identity domain in case no domain input was found, most likely because all + * inputs are single values. */ + Domain operation_domain = Domain::identity(); + int current_domain_priority = std::numeric_limits<int>::max(); + + /* Go over the inputs and find the domain of the non single value input with the highest domain + * priority. */ + for (StringRef identifier : input_descriptors_.keys()) { + const Result &result = get_input(identifier); + const InputDescriptor &descriptor = get_input_descriptor(identifier); + + /* A single value input can't be a domain input. */ + if (result.is_single_value() || descriptor.expects_single_value) { + continue; + } + + /* Notice that the lower the domain priority value is, the higher the priority is, hence the + * less than comparison. */ + if (descriptor.domain_priority < current_domain_priority) { + operation_domain = result.domain(); + current_domain_priority = descriptor.domain_priority; + } + } + + return operation_domain; +} + +void Operation::add_and_evaluate_input_processors() +{ + /* Add and evaluate reduce to single value input processors if needed. */ + for (const StringRef &identifier : results_mapped_to_inputs_.keys()) { + SimpleOperation *single_value = ReduceToSingleValueOperation::construct_if_needed( + context(), get_input(identifier)); + add_and_evaluate_input_processor(identifier, single_value); + } + + /* Add and evaluate conversion input processors if needed. */ + for (const StringRef &identifier : results_mapped_to_inputs_.keys()) { + SimpleOperation *conversion = ConversionOperation::construct_if_needed( + context(), get_input(identifier), get_input_descriptor(identifier)); + add_and_evaluate_input_processor(identifier, conversion); + } + + /* Add and evaluate realize on domain input processors if needed. */ + for (const StringRef &identifier : results_mapped_to_inputs_.keys()) { + SimpleOperation *realize_on_domain = RealizeOnDomainOperation::construct_if_needed( + context(), get_input(identifier), get_input_descriptor(identifier), compute_domain()); + add_and_evaluate_input_processor(identifier, realize_on_domain); + } +} + +void Operation::add_and_evaluate_input_processor(StringRef identifier, SimpleOperation *processor) +{ + /* Allow null inputs to facilitate construct_if_needed pattern of addition. For instance, see the + * implementation of the add_and_evaluate_input_processors method. */ + if (!processor) { + return; + } + + /* Get a reference to the input processors vector for the given input. */ + ProcessorsVector &processors = input_processors_.lookup_or_add_default(identifier); + + /* Get the result that should serve as the input for the processor. This is either the result + * mapped to the input or the result of the last processor depending on whether this is the first + * processor or not. */ + Result &result = processors.is_empty() ? get_input(identifier) : processors.last()->get_result(); + + /* Map the input result of the processor and add it to the processors vector. */ + processor->map_input_to_result(&result); + processors.append(std::unique_ptr<SimpleOperation>(processor)); + + /* Switch the result mapped to the input to be the output result of the processor. */ + switch_result_mapped_to_input(identifier, &processor->get_result()); + + /* Evaluate the input processor. */ + processor->evaluate(); +} + +Result &Operation::get_input(StringRef identifier) const +{ + return *results_mapped_to_inputs_.lookup(identifier); +} + +void Operation::switch_result_mapped_to_input(StringRef identifier, Result *result) +{ + results_mapped_to_inputs_.lookup(identifier) = result; +} + +void Operation::populate_result(StringRef identifier, Result result) +{ + results_.add_new(identifier, result); +} + +void Operation::declare_input_descriptor(StringRef identifier, InputDescriptor descriptor) +{ + input_descriptors_.add_new(identifier, descriptor); +} + +InputDescriptor &Operation::get_input_descriptor(StringRef identifier) +{ + return input_descriptors_.lookup(identifier); +} + +Context &Operation::context() +{ + return context_; +} + +TexturePool &Operation::texture_pool() const +{ + return context_.texture_pool(); +} + +ShaderPool &Operation::shader_pool() const +{ + return context_.shader_pool(); +} + +void Operation::evaluate_input_processors() +{ + /* The input processors are not added yet, so add and evaluate the input processors. */ + if (!input_processors_added_) { + add_and_evaluate_input_processors(); + input_processors_added_ = true; + return; + } + + /* The input processors are already added, so just go over the input processors and evaluate + * them. */ + for (const ProcessorsVector &processors : input_processors_.values()) { + for (const std::unique_ptr<SimpleOperation> &processor : processors) { + processor->evaluate(); + } + } +} + +void Operation::reset_results() +{ + for (Result &result : results_.values()) { + result.reset(); + } +} + +void Operation::release_inputs() +{ + for (Result *result : results_mapped_to_inputs_.values()) { + result->release(); + } +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/realize_on_domain_operation.cc b/source/blender/compositor/realtime_compositor/intern/realize_on_domain_operation.cc new file mode 100644 index 00000000000..4c16f291637 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/realize_on_domain_operation.cc @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_float3x3.hh" +#include "BLI_math_vec_types.hh" +#include "BLI_utildefines.h" + +#include "GPU_shader.h" +#include "GPU_texture.h" + +#include "COM_context.hh" +#include "COM_domain.hh" +#include "COM_input_descriptor.hh" +#include "COM_realize_on_domain_operation.hh" +#include "COM_result.hh" +#include "COM_utilities.hh" + +namespace blender::realtime_compositor { + +RealizeOnDomainOperation::RealizeOnDomainOperation(Context &context, + Domain domain, + ResultType type) + : SimpleOperation(context), domain_(domain) +{ + InputDescriptor input_descriptor; + input_descriptor.type = type; + declare_input_descriptor(input_descriptor); + populate_result(Result(type, texture_pool())); +} + +void RealizeOnDomainOperation::execute() +{ + Result &input = get_input(); + Result &result = get_result(); + + result.allocate_texture(domain_); + + GPUShader *shader = get_realization_shader(); + GPU_shader_bind(shader); + + /* Transform the input space into the domain space. */ + const float3x3 local_transformation = input.domain().transformation * + domain_.transformation.inverted(); + + /* Set the origin of the transformation to be the center of the domain. */ + const float3x3 transformation = float3x3::from_origin_transformation( + local_transformation, float2(domain_.size) / 2.0f); + + /* Invert the transformation because the shader transforms the domain coordinates instead of the + * input image itself and thus expect the inverse. */ + const float3x3 inverse_transformation = transformation.inverted(); + + /* Set the inverse of the transform to the shader. */ + GPU_shader_uniform_mat3_as_mat4(shader, "inverse_transformation", inverse_transformation.ptr()); + + /* The texture sampler should use bilinear interpolation for both the bilinear and bicubic + * cases, as the logic used by the bicubic realization shader expects textures to use bilinear + * interpolation. */ + const bool use_bilinear = ELEM(input.get_realization_options().interpolation, + Interpolation::Bilinear, + Interpolation::Bicubic); + GPU_texture_filter_mode(input.texture(), use_bilinear); + + /* Make out-of-bound texture access return zero by clamping to border color. And make texture + * wrap appropriately if the input repeats. */ + const bool repeats = input.get_realization_options().repeat_x || + input.get_realization_options().repeat_y; + GPU_texture_wrap_mode(input.texture(), repeats, false); + + input.bind_as_texture(shader, "input_sampler"); + result.bind_as_image(shader, "domain"); + + compute_dispatch_global(shader, domain_.size); + + input.unbind_as_texture(); + result.unbind_as_image(); + GPU_shader_unbind(); +} + +GPUShader *RealizeOnDomainOperation::get_realization_shader() +{ + switch (get_result().type()) { + case ResultType::Color: + return shader_pool().acquire("compositor_realize_on_domain_color"); + case ResultType::Vector: + return shader_pool().acquire("compositor_realize_on_domain_vector"); + case ResultType::Float: + return shader_pool().acquire("compositor_realize_on_domain_float"); + } + + BLI_assert_unreachable(); + return nullptr; +} + +Domain RealizeOnDomainOperation::compute_domain() +{ + return domain_; +} + +SimpleOperation *RealizeOnDomainOperation::construct_if_needed( + Context &context, + const Result &input_result, + const InputDescriptor &input_descriptor, + const Domain &operation_domain) +{ + /* This input wants to skip realization, the operation is not needed. */ + if (input_descriptor.skip_realization) { + return nullptr; + } + + /* The input expects a single value and if no single value is provided, it will be ignored and a + * default value will be used, so no need to realize it and the operation is not needed. */ + if (input_descriptor.expects_single_value) { + return nullptr; + } + + /* Input result is a single value and does not need realization, the operation is not needed. */ + if (input_result.is_single_value()) { + return nullptr; + } + + /* The input have an identical domain to the operation domain, so no need to realize it and the + * operation is not needed. */ + if (input_result.domain() == operation_domain) { + return nullptr; + } + + /* Otherwise, realization is needed. */ + return new RealizeOnDomainOperation(context, operation_domain, input_descriptor.type); +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/reduce_to_single_value_operation.cc b/source/blender/compositor/realtime_compositor/intern/reduce_to_single_value_operation.cc new file mode 100644 index 00000000000..cfa08ffc639 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/reduce_to_single_value_operation.cc @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "GPU_state.h" +#include "GPU_texture.h" + +#include "MEM_guardedalloc.h" + +#include "COM_context.hh" +#include "COM_input_descriptor.hh" +#include "COM_reduce_to_single_value_operation.hh" +#include "COM_result.hh" + +namespace blender::realtime_compositor { + +ReduceToSingleValueOperation::ReduceToSingleValueOperation(Context &context, ResultType type) + : SimpleOperation(context) +{ + InputDescriptor input_descriptor; + input_descriptor.type = type; + declare_input_descriptor(input_descriptor); + populate_result(Result(type, texture_pool())); +} + +void ReduceToSingleValueOperation::execute() +{ + /* Download the input pixel from the GPU texture. */ + const Result &input = get_input(); + GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); + float *pixel = static_cast<float *>(GPU_texture_read(input.texture(), GPU_DATA_FLOAT, 0)); + + /* Allocate a single value result and set its value to the value of the downloaded pixel. */ + Result &result = get_result(); + result.allocate_single_value(); + switch (result.type()) { + case ResultType::Color: + result.set_color_value(pixel); + break; + case ResultType::Vector: + result.set_vector_value(pixel); + break; + case ResultType::Float: + result.set_float_value(*pixel); + break; + } + + /* Free the downloaded pixel. */ + MEM_freeN(pixel); +} + +SimpleOperation *ReduceToSingleValueOperation::construct_if_needed(Context &context, + const Result &input_result) +{ + /* Input result is already a single value, the operation is not needed. */ + if (input_result.is_single_value()) { + return nullptr; + } + + /* The input is a full sized texture and can't be reduced to a single value, the operation is not + * needed. */ + if (input_result.domain().size != int2(1)) { + return nullptr; + } + + /* The input is a texture of a single pixel and can be reduced to a single value. */ + return new ReduceToSingleValueOperation(context, input_result.type()); +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/result.cc b/source/blender/compositor/realtime_compositor/intern/result.cc new file mode 100644 index 00000000000..c6704001f09 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/result.cc @@ -0,0 +1,258 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_float3x3.hh" +#include "BLI_math_vec_types.hh" + +#include "GPU_shader.h" +#include "GPU_state.h" +#include "GPU_texture.h" + +#include "COM_domain.hh" +#include "COM_result.hh" +#include "COM_texture_pool.hh" + +namespace blender::realtime_compositor { + +Result::Result(ResultType type, TexturePool &texture_pool) + : type_(type), texture_pool_(&texture_pool) +{ +} + +void Result::allocate_texture(Domain domain) +{ + is_single_value_ = false; + switch (type_) { + case ResultType::Float: + texture_ = texture_pool_->acquire_float(domain.size); + break; + case ResultType::Vector: + texture_ = texture_pool_->acquire_vector(domain.size); + break; + case ResultType::Color: + texture_ = texture_pool_->acquire_color(domain.size); + break; + } + domain_ = domain; +} + +void Result::allocate_single_value() +{ + is_single_value_ = true; + /* Single values are stored in 1x1 textures as well as the single value members. */ + const int2 texture_size{1, 1}; + switch (type_) { + case ResultType::Float: + texture_ = texture_pool_->acquire_float(texture_size); + break; + case ResultType::Vector: + texture_ = texture_pool_->acquire_vector(texture_size); + break; + case ResultType::Color: + texture_ = texture_pool_->acquire_color(texture_size); + break; + } + domain_ = Domain::identity(); +} + +void Result::allocate_invalid() +{ + allocate_single_value(); + switch (type_) { + case ResultType::Float: + set_float_value(0.0f); + break; + case ResultType::Vector: + set_vector_value(float3(0.0f)); + break; + case ResultType::Color: + set_color_value(float4(0.0f)); + break; + } +} + +void Result::bind_as_texture(GPUShader *shader, const char *texture_name) const +{ + /* Make sure any prior writes to the texture are reflected before reading from it. */ + GPU_memory_barrier(GPU_BARRIER_TEXTURE_FETCH); + + const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture_name); + GPU_texture_bind(texture_, texture_image_unit); +} + +void Result::bind_as_image(GPUShader *shader, const char *image_name) const +{ + const int image_unit = GPU_shader_get_texture_binding(shader, image_name); + GPU_texture_image_bind(texture_, image_unit); +} + +void Result::unbind_as_texture() const +{ + GPU_texture_unbind(texture_); +} + +void Result::unbind_as_image() const +{ + GPU_texture_image_unbind(texture_); +} + +void Result::pass_through(Result &target) +{ + /* Increment the reference count of the master by the original reference count of the target. */ + increment_reference_count(target.reference_count()); + + /* Make the target an exact copy of this result, but keep the initial reference count, as this is + * a property of the original result and is needed for correctly resetting the result before the + * next evaluation. */ + const int initial_reference_count = target.initial_reference_count_; + target = *this; + target.initial_reference_count_ = initial_reference_count; + + /* Set the master of the target to be this result. */ + target.master_ = this; +} + +void Result::transform(const float3x3 &transformation) +{ + domain_.transform(transformation); +} + +RealizationOptions &Result::get_realization_options() +{ + return domain_.realization_options; +} + +float Result::get_float_value() const +{ + return float_value_; +} + +float3 Result::get_vector_value() const +{ + return vector_value_; +} + +float4 Result::get_color_value() const +{ + return color_value_; +} + +float Result::get_float_value_default(float default_value) const +{ + if (is_single_value()) { + return get_float_value(); + } + return default_value; +} + +float3 Result::get_vector_value_default(const float3 &default_value) const +{ + if (is_single_value()) { + return get_vector_value(); + } + return default_value; +} + +float4 Result::get_color_value_default(const float4 &default_value) const +{ + if (is_single_value()) { + return get_color_value(); + } + return default_value; +} + +void Result::set_float_value(float value) +{ + float_value_ = value; + GPU_texture_update(texture_, GPU_DATA_FLOAT, &float_value_); +} + +void Result::set_vector_value(const float3 &value) +{ + vector_value_ = value; + GPU_texture_update(texture_, GPU_DATA_FLOAT, vector_value_); +} + +void Result::set_color_value(const float4 &value) +{ + color_value_ = value; + GPU_texture_update(texture_, GPU_DATA_FLOAT, color_value_); +} + +void Result::set_initial_reference_count(int count) +{ + initial_reference_count_ = count; +} + +void Result::reset() +{ + master_ = nullptr; + reference_count_ = initial_reference_count_; +} + +void Result::increment_reference_count(int count) +{ + /* If there is a master result, increment its reference count instead. */ + if (master_) { + master_->increment_reference_count(count); + return; + } + + reference_count_ += count; +} + +void Result::release() +{ + /* If there is a master result, release it instead. */ + if (master_) { + master_->release(); + return; + } + + /* Decrement the reference count, and if it reaches zero, release the texture back into the + * texture pool. */ + reference_count_--; + if (reference_count_ == 0) { + texture_pool_->release(texture_); + } +} + +bool Result::should_compute() +{ + return initial_reference_count_ != 0; +} + +ResultType Result::type() const +{ + return type_; +} + +bool Result::is_texture() const +{ + return !is_single_value_; +} + +bool Result::is_single_value() const +{ + return is_single_value_; +} + +GPUTexture *Result::texture() const +{ + return texture_; +} + +int Result::reference_count() const +{ + /* If there is a master result, return its reference count instead. */ + if (master_) { + return master_->reference_count(); + } + return reference_count_; +} + +const Domain &Result::domain() const +{ + return domain_; +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/scheduler.cc b/source/blender/compositor/realtime_compositor/intern/scheduler.cc new file mode 100644 index 00000000000..ea7838079ec --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/scheduler.cc @@ -0,0 +1,315 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_map.hh" +#include "BLI_set.hh" +#include "BLI_stack.hh" +#include "BLI_vector.hh" +#include "BLI_vector_set.hh" + +#include "NOD_derived_node_tree.hh" + +#include "COM_scheduler.hh" +#include "COM_utilities.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +/* Compute the output node whose result should be computed. The output node is the node marked as + * NODE_DO_OUTPUT. If multiple types of output nodes are marked, then the preference will be + * CMP_NODE_COMPOSITE > CMP_NODE_VIEWER > CMP_NODE_SPLITVIEWER. If no output node exists, a null + * node will be returned. */ +static DNode compute_output_node(DerivedNodeTree &tree) +{ + /* Get the top most node tree reference from the derived node tree. */ + const NodeTreeRef &root_tree = tree.root_context().tree(); + + /* First search over composite nodes. */ + for (const NodeRef *node : root_tree.nodes_by_type("CompositorNodeComposite")) { + if (node->bnode()->flag & NODE_DO_OUTPUT) { + return DNode(&tree.root_context(), node); + } + } + + /* Then search over viewer nodes. */ + for (const NodeRef *node : root_tree.nodes_by_type("CompositorNodeViewer")) { + if (node->bnode()->flag & NODE_DO_OUTPUT) { + return DNode(&tree.root_context(), node); + } + } + + /* Finally search over split viewer nodes. */ + for (const NodeRef *node : root_tree.nodes_by_type("CompositorNodeSplitViewer")) { + if (node->bnode()->flag & NODE_DO_OUTPUT) { + return DNode(&tree.root_context(), node); + } + } + + /* No output node found, return a null node. */ + return DNode(); +} + +/* A type representing a mapping that associates each node with a heuristic estimation of the + * number of intermediate buffers needed to compute it and all of its dependencies. See the + * compute_number_of_needed_buffers function for more information. */ +using NeededBuffers = Map<DNode, int>; + +/* Compute a heuristic estimation of the number of intermediate buffers needed to compute each node + * and all of its dependencies for all nodes that the given node depends on. The output is a map + * that maps each node with the number of intermediate buffers needed to compute it and all of its + * dependencies. + * + * Consider a node that takes n number of buffers as an input from a number of node dependencies, + * which we shall call the input nodes. The node also computes and outputs m number of buffers. + * In order for the node to compute its output, a number of intermediate buffers will be needed. + * Since the node takes n buffers and outputs m buffers, then the number of buffers directly + * needed by the node is (n + m). But each of the input buffers are computed by a node that, in + * turn, needs a number of buffers to compute its output. So the total number of buffers needed + * to compute the output of the node is max(n + m, d) where d is the number of buffers needed by + * the input node that needs the largest number of buffers. We only consider the input node that + * needs the largest number of buffers, because those buffers can be reused by any input node + * that needs a lesser number of buffers. + * + * Shader nodes, however, are a special case because links between two shader nodes inside the same + * shader operation don't pass a buffer, but a single value in the compiled shader. So for shader + * nodes, only inputs and outputs linked to nodes that are not shader nodes should be considered. + * Note that this might not actually be true, because the compiler may decide to split a shader + * operation into multiples ones that will pass buffers, but this is not something that can be + * known at scheduling-time. See the discussion in COM_compile_state.hh, COM_evaluator.hh, and + * COM_shader_operation.hh for more information. In the node tree shown below, node 4 will have + * exactly the same number of needed buffers by node 3, because its inputs and outputs are all + * internally linked in the shader operation. + * + * Shader Operation + * +------------------------------------------------------+ + * .------------. | .------------. .------------. .------------. | .------------. + * | Node 1 | | | Node 3 | | Node 4 | | Node 5 | | | Node 6 | + * | |----|--| |--| |------| |--|--| | + * | | .-|--| | | | .---| | | | | + * '------------' | | '------------' '------------' | '------------' | '------------' + * | +----------------------------------|-------------------+ + * .------------. | | + * | Node 2 | | | + * | |--'------------------------------------' + * | | + * '------------' + * + * Note that the computed output is not guaranteed to be accurate, and will not be in most cases. + * The computation is merely a heuristic estimation that works well in most cases. This is due to a + * number of reasons: + * - The node tree is actually a graph that allows output sharing, which is not something that was + * taken into consideration in this implementation because it is difficult to correctly consider. + * - Each node may allocate any number of internal buffers, which is not taken into account in this + * implementation because it rarely affects the output and is done by very few nodes. + * - The compiler may decide to compiler the schedule differently depending on runtime information + * which we can merely speculate at scheduling-time as described above. */ +static NeededBuffers compute_number_of_needed_buffers(DNode output_node) +{ + NeededBuffers needed_buffers; + + /* A stack of nodes used to traverse the node tree starting from the output node. */ + Stack<DNode> node_stack = {output_node}; + + /* Traverse the node tree in a post order depth first manner and compute the number of needed + * buffers for each node. Post order traversal guarantee that all the node dependencies of each + * node are computed before it. This is done by pushing all the uncomputed node dependencies to + * the node stack first and only popping and computing the node when all its node dependencies + * were computed. */ + while (!node_stack.is_empty()) { + /* Do not pop the node immediately, as it may turn out that we can't compute its number of + * needed buffers just yet because its dependencies weren't computed, it will be popped later + * when needed. */ + DNode &node = node_stack.peek(); + + /* Go over the node dependencies connected to the inputs of the node and push them to the node + * stack if they were not computed already. */ + Set<DNode> pushed_nodes; + for (const InputSocketRef *input_ref : node->inputs()) { + const DInputSocket input{node.context(), input_ref}; + + /* Get the output linked to the input. If it is null, that means the input is unlinked and + * has no dependency node. */ + const DOutputSocket output = get_output_linked_to_input(input); + if (!output) { + continue; + } + + /* The node dependency was already computed or pushed before, so skip it. */ + if (needed_buffers.contains(output.node()) || pushed_nodes.contains(output.node())) { + continue; + } + + /* The output node needs to be computed, push the node dependency to the node stack and + * indicate that it was pushed. */ + node_stack.push(output.node()); + pushed_nodes.add_new(output.node()); + } + + /* If any of the node dependencies were pushed, that means that not all of them were computed + * and consequently we can't compute the number of needed buffers for this node just yet. */ + if (!pushed_nodes.is_empty()) { + continue; + } + + /* We don't need to store the result of the pop because we already peeked at it before. */ + node_stack.pop(); + + /* Compute the number of buffers that the node takes as an input as well as the number of + * buffers needed to compute the most demanding of the node dependencies. */ + int number_of_input_buffers = 0; + int buffers_needed_by_dependencies = 0; + for (const InputSocketRef *input_ref : node->inputs()) { + const DInputSocket input{node.context(), input_ref}; + + /* Get the output linked to the input. If it is null, that means the input is unlinked. + * Unlinked inputs do not take a buffer, so skip those inputs. */ + const DOutputSocket output = get_output_linked_to_input(input); + if (!output) { + continue; + } + + /* Since this input is linked, if the link is not between two shader nodes, it means that the + * node takes a buffer through this input and so we increment the number of input buffers. */ + if (!is_shader_node(node) || !is_shader_node(output.node())) { + number_of_input_buffers++; + } + + /* If the number of buffers needed by the node dependency is more than the total number of + * buffers needed by the dependencies, then update the latter to be the former. This is + * computing the "d" in the aforementioned equation "max(n + m, d)". */ + const int buffers_needed_by_dependency = needed_buffers.lookup(output.node()); + if (buffers_needed_by_dependency > buffers_needed_by_dependencies) { + buffers_needed_by_dependencies = buffers_needed_by_dependency; + } + } + + /* Compute the number of buffers that will be computed/output by this node. */ + int number_of_output_buffers = 0; + for (const OutputSocketRef *output_ref : node->outputs()) { + const DOutputSocket output{node.context(), output_ref}; + + /* The output is not linked, it outputs no buffer. */ + if (output->logically_linked_sockets().is_empty()) { + continue; + } + + /* If any of the links is not between two shader nodes, it means that the node outputs + * a buffer through this output and so we increment the number of output buffers. */ + if (!is_output_linked_to_node_conditioned(output, is_shader_node) || !is_shader_node(node)) { + number_of_output_buffers++; + } + } + + /* Compute the heuristic estimation of the number of needed intermediate buffers to compute + * this node and all of its dependencies. This is computing the aforementioned equation + * "max(n + m, d)". */ + const int total_buffers = MAX2(number_of_input_buffers + number_of_output_buffers, + buffers_needed_by_dependencies); + needed_buffers.add(node, total_buffers); + } + + return needed_buffers; +} + +/* There are multiple different possible orders of evaluating a node graph, each of which needs + * to allocate a number of intermediate buffers to store its intermediate results. It follows + * that we need to find the evaluation order which uses the least amount of intermediate buffers. + * For instance, consider a node that takes two input buffers A and B. Each of those buffers is + * computed through a number of nodes constituting a sub-graph whose root is the node that + * outputs that buffer. Suppose the number of intermediate buffers needed to compute A and B are + * N(A) and N(B) respectively and N(A) > N(B). Then evaluating the sub-graph computing A would be + * a better option than that of B, because had B was computed first, its outputs will need to be + * stored in extra buffers in addition to the buffers needed by A. The number of buffers needed by + * each node is estimated as described in the compute_number_of_needed_buffers function. + * + * This is a heuristic generalization of the Sethi–Ullman algorithm, a generalization that + * doesn't always guarantee an optimal evaluation order, as the optimal evaluation order is very + * difficult to compute, however, this method works well in most cases. Moreover it assumes that + * all buffers will have roughly the same size, which may not always be the case. */ +Schedule compute_schedule(DerivedNodeTree &tree) +{ + Schedule schedule; + + /* Compute the output node whose result should be computed. */ + const DNode output_node = compute_output_node(tree); + + /* No output node, the node tree has no effect, return an empty schedule. */ + if (!output_node) { + return schedule; + } + + /* Compute the number of buffers needed by each node connected to the output. */ + const NeededBuffers needed_buffers = compute_number_of_needed_buffers(output_node); + + /* A stack of nodes used to traverse the node tree starting from the output node. */ + Stack<DNode> node_stack = {output_node}; + + /* Traverse the node tree in a post order depth first manner, scheduling the nodes in an order + * informed by the number of buffers needed by each node. Post order traversal guarantee that all + * the node dependencies of each node are scheduled before it. This is done by pushing all the + * unscheduled node dependencies to the node stack first and only popping and scheduling the node + * when all its node dependencies were scheduled. */ + while (!node_stack.is_empty()) { + /* Do not pop the node immediately, as it may turn out that we can't schedule it just yet + * because its dependencies weren't scheduled, it will be popped later when needed. */ + DNode &node = node_stack.peek(); + + /* Compute the nodes directly connected to the node inputs sorted by their needed buffers such + * that the node with the lowest number of needed buffers comes first. Note that we actually + * want the node with the highest number of needed buffers to be schedule first, but since + * those are pushed to the traversal stack, we need to push them in reverse order. */ + Vector<DNode> sorted_dependency_nodes; + for (const InputSocketRef *input_ref : node->inputs()) { + const DInputSocket input{node.context(), input_ref}; + + /* Get the output linked to the input. If it is null, that means the input is unlinked and + * has no dependency node, so skip it. */ + const DOutputSocket output = get_output_linked_to_input(input); + if (!output) { + continue; + } + + /* The dependency node was added before, so skip it. The number of dependency nodes is very + * small, typically less than 3, so a linear search is okay. */ + if (sorted_dependency_nodes.contains(output.node())) { + continue; + } + + /* The dependency node was already schedule, so skip it. */ + if (schedule.contains(output.node())) { + continue; + } + + /* Sort in ascending order on insertion, the number of dependency nodes is very small, + * typically less than 3, so insertion sort is okay. */ + int insertion_position = 0; + for (int i = 0; i < sorted_dependency_nodes.size(); i++) { + if (needed_buffers.lookup(output.node()) > + needed_buffers.lookup(sorted_dependency_nodes[i])) { + insertion_position++; + } + else { + break; + } + } + sorted_dependency_nodes.insert(insertion_position, output.node()); + } + + /* Push the sorted dependency nodes to the node stack in order. */ + for (const DNode &dependency_node : sorted_dependency_nodes) { + node_stack.push(dependency_node); + } + + /* If there are no sorted dependency nodes, that means they were all already scheduled or that + * none exists in the first place, so we can pop and schedule the node now. */ + if (sorted_dependency_nodes.is_empty()) { + /* The node might have already been scheduled, so we don't use add_new here and simply don't + * add it if it was already scheduled. */ + schedule.add(node_stack.pop()); + } + } + + return schedule; +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/shader_node.cc b/source/blender/compositor/realtime_compositor/intern/shader_node.cc new file mode 100644 index 00000000000..76adb2b1ab2 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/shader_node.cc @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_assert.h" +#include "BLI_math_vector.h" + +#include "DNA_node_types.h" + +#include "NOD_derived_node_tree.hh" + +#include "GPU_material.h" + +#include "COM_shader_node.hh" +#include "COM_utilities.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +ShaderNode::ShaderNode(DNode node) : node_(node) +{ + populate_inputs(); + populate_outputs(); +} + +GPUNodeStack *ShaderNode::get_inputs_array() +{ + return inputs_.data(); +} + +GPUNodeStack *ShaderNode::get_outputs_array() +{ + return outputs_.data(); +} + +const DNode &ShaderNode::node() const +{ + return node_; +} + +bNode &ShaderNode::bnode() const +{ + return *node_->bnode(); +} + +static eGPUType gpu_type_from_socket_type(eNodeSocketDatatype type) +{ + switch (type) { + case SOCK_FLOAT: + return GPU_FLOAT; + case SOCK_VECTOR: + return GPU_VEC3; + case SOCK_RGBA: + return GPU_VEC4; + default: + BLI_assert_unreachable(); + return GPU_NONE; + } +} + +static void gpu_stack_vector_from_socket(float *vector, const SocketRef *socket) +{ + switch (socket->bsocket()->type) { + case SOCK_FLOAT: + vector[0] = socket->default_value<bNodeSocketValueFloat>()->value; + return; + case SOCK_VECTOR: + copy_v3_v3(vector, socket->default_value<bNodeSocketValueVector>()->value); + return; + case SOCK_RGBA: + copy_v4_v4(vector, socket->default_value<bNodeSocketValueRGBA>()->value); + return; + default: + BLI_assert_unreachable(); + } +} + +static void populate_gpu_node_stack(DSocket socket, GPUNodeStack &stack) +{ + /* Make sure this stack is not marked as the end of the stack array. */ + stack.end = false; + /* This will be initialized later by the GPU material compiler or the compile method. */ + stack.link = nullptr; + /* Socket type and its corresponding GPU type. */ + stack.sockettype = socket->bsocket()->type; + stack.type = gpu_type_from_socket_type((eNodeSocketDatatype)socket->bsocket()->type); + + if (socket->is_input()) { + /* Get the origin socket connected to the input if any. */ + const DInputSocket input{socket.context(), &socket->as_input()}; + DSocket origin = get_input_origin_socket(input); + /* The input is linked if the origin socket is not null and is an output socket. Had it been an + * input socket, then it is an unlinked input of a group input node. */ + stack.hasinput = origin->is_output(); + /* Get the socket value from the origin if it is an input, because then it would be an unlinked + * input of a group input node, otherwise, get the value from the socket itself. */ + if (origin->is_input()) { + gpu_stack_vector_from_socket(stack.vec, origin.socket_ref()); + } + else { + gpu_stack_vector_from_socket(stack.vec, socket.socket_ref()); + } + } + else { + stack.hasoutput = socket->is_logically_linked(); + } +} + +void ShaderNode::populate_inputs() +{ + /* Reserve a stack for each input in addition to an extra stack at the end to mark the end of the + * array, as this is what the GPU module functions expect. */ + inputs_.resize(node_->inputs().size() + 1); + inputs_.last().end = true; + + for (int i = 0; i < node_->inputs().size(); i++) { + populate_gpu_node_stack(node_.input(i), inputs_[i]); + } +} + +void ShaderNode::populate_outputs() +{ + /* Reserve a stack for each output in addition to an extra stack at the end to mark the end of + * the array, as this is what the GPU module functions expect. */ + outputs_.resize(node_->outputs().size() + 1); + outputs_.last().end = true; + + for (int i = 0; i < node_->outputs().size(); i++) { + populate_gpu_node_stack(node_.output(i), outputs_[i]); + } +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/shader_operation.cc b/source/blender/compositor/realtime_compositor/intern/shader_operation.cc new file mode 100644 index 00000000000..e06107a66c2 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/shader_operation.cc @@ -0,0 +1,345 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <memory> + +#include "BLI_listbase.h" +#include "BLI_string_ref.hh" +#include "BLI_utildefines.h" + +#include "GPU_material.h" +#include "GPU_shader.h" +#include "GPU_texture.h" +#include "GPU_uniform_buffer.h" + +#include "gpu_shader_create_info.hh" + +#include "NOD_derived_node_tree.hh" +#include "NOD_node_declaration.hh" + +#include "COM_context.hh" +#include "COM_operation.hh" +#include "COM_result.hh" +#include "COM_scheduler.hh" +#include "COM_shader_node.hh" +#include "COM_shader_operation.hh" +#include "COM_utilities.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +ShaderOperation::ShaderOperation(Context &context, ShaderCompileUnit &compile_unit) + : Operation(context), compile_unit_(compile_unit) +{ + material_ = GPU_material_from_callbacks( + &setup_material, &compile_material, &generate_material, this); + GPU_material_status_set(material_, GPU_MAT_QUEUED); + GPU_material_compile(material_); +} + +ShaderOperation::~ShaderOperation() +{ + GPU_material_free_single(material_); +} + +void ShaderOperation::execute() +{ + /* Allocate all the outputs of the operation on its computed domain. */ + const Domain domain = compute_domain(); + for (StringRef identifier : output_sockets_to_output_identifiers_map_.values()) { + Result &result = get_result(identifier); + result.allocate_texture(domain); + } + + GPUShader *shader = GPU_material_get_shader(material_); + GPU_shader_bind(shader); + + bind_material_resources(shader); + bind_inputs(shader); + bind_outputs(shader); + + compute_dispatch_global(shader, domain.size); + + GPU_texture_unbind_all(); + GPU_texture_image_unbind_all(); + GPU_uniformbuf_unbind_all(); + GPU_shader_unbind(); +} + +StringRef ShaderOperation::get_output_identifier_from_output_socket(DOutputSocket output) +{ + return output_sockets_to_output_identifiers_map_.lookup(output); +} + +InputsToLinkedOutputsMap &ShaderOperation::get_inputs_to_linked_outputs_map() +{ + return inputs_to_linked_outputs_map_; +} + +void ShaderOperation::compute_results_reference_counts(const Schedule &schedule) +{ + for (const OutputSocketsToOutputIdentifiersMap::Item &item : + output_sockets_to_output_identifiers_map_.items()) { + + const int reference_count = number_of_inputs_linked_to_output_conditioned( + item.key, [&](DInputSocket input) { return schedule.contains(input.node()); }); + + get_result(item.value).set_initial_reference_count(reference_count); + } +} + +void ShaderOperation::bind_material_resources(GPUShader *shader) +{ + /* Bind the uniform buffer of the material if it exists. It may not exist if the GPU material has + * no uniforms. */ + GPUUniformBuf *ubo = GPU_material_uniform_buffer_get(material_); + if (ubo) { + GPU_uniformbuf_bind(ubo, GPU_shader_get_uniform_block_binding(shader, GPU_UBO_BLOCK_NAME)); + } + + /* Bind color band textures needed by the material. */ + ListBase textures = GPU_material_textures(material_); + LISTBASE_FOREACH (GPUMaterialTexture *, texture, &textures) { + if (texture->colorband) { + const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture->sampler_name); + GPU_texture_bind(*texture->colorband, texture_image_unit); + } + } +} + +void ShaderOperation::bind_inputs(GPUShader *shader) +{ + for (const GPUMaterialTexture *material_texture : output_to_material_texture_map_.values()) { + const char *sampler_name = material_texture->sampler_name; + get_input(sampler_name).bind_as_texture(shader, sampler_name); + } +} + +void ShaderOperation::bind_outputs(GPUShader *shader) +{ + ListBase images = GPU_material_images(material_); + LISTBASE_FOREACH (GPUMaterialImage *, image, &images) { + get_result(image->name_in_shader).bind_as_image(shader, image->name_in_shader); + } +} + +void ShaderOperation::setup_material(void *UNUSED(thunk), GPUMaterial *material) +{ + GPU_material_is_compute_set(material, true); +} + +void ShaderOperation::compile_material(void *thunk, GPUMaterial *material) +{ + ShaderOperation *operation = static_cast<ShaderOperation *>(thunk); + for (DNode node : operation->compile_unit_) { + /* Instantiate a shader node for the node and add it to the shader_nodes_ map. */ + ShaderNode *shader_node = node->typeinfo()->get_compositor_shader_node(node); + operation->shader_nodes_.add_new(node, std::unique_ptr<ShaderNode>(shader_node)); + + /* Link the inputs of the shader node if needed. */ + operation->link_node_inputs(node, material); + + /* Compile the node itself. */ + shader_node->compile(material); + + /* Populate the output results for the shader node if needed. */ + operation->populate_results_for_node(node, material); + } +} + +void ShaderOperation::link_node_inputs(DNode node, GPUMaterial *material) +{ + for (const InputSocketRef *input_ref : node->inputs()) { + const DInputSocket input{node.context(), input_ref}; + + /* Get the output linked to the input. If it is null, that means the input is unlinked. + * Unlinked inputs are linked by the node compile method, so skip this here. */ + const DOutputSocket output = get_output_linked_to_input(input); + if (!output) { + continue; + } + + /* If the origin node is part of the shader operation, then just map the output stack link to + * the input stack link. */ + if (compile_unit_.contains(output.node())) { + map_node_input(input, output); + continue; + } + + /* Otherwise, the origin node is not part of the shader operation, so an input to the shader + * operation must be declared if it wasn't declared for the same output already. */ + declare_operation_input_if_needed(input, output, material); + + /* Link the input to an input loader GPU material node sampling the result of the output. */ + link_material_input_loader(input, output, material); + } +} + +void ShaderOperation::map_node_input(DInputSocket input, DOutputSocket output) +{ + /* Get the GPU node stack of the output. */ + ShaderNode &output_node = *shader_nodes_.lookup(output.node()); + GPUNodeStack &output_stack = output_node.get_outputs_array()[output->index()]; + + /* Get the GPU node stack of the input. */ + ShaderNode &input_node = *shader_nodes_.lookup(input.node()); + GPUNodeStack &input_stack = input_node.get_inputs_array()[input->index()]; + + /* Map the output link to the input link. */ + input_stack.link = output_stack.link; +} + +static const char *get_load_function_name(DInputSocket input) +{ + switch (input->bsocket()->type) { + case SOCK_FLOAT: + return "load_input_float"; + case SOCK_VECTOR: + return "load_input_vector"; + case SOCK_RGBA: + return "load_input_color"; + default: + BLI_assert_unreachable(); + return ""; + } +} + +void ShaderOperation::declare_operation_input_if_needed(DInputSocket input, + DOutputSocket output, + GPUMaterial *material) +{ + /* An input was already declared for that same output, so no need to declare it again. */ + if (output_to_material_texture_map_.contains(output)) { + return; + } + + /* Add a new material texture to the GPU material. */ + GPUMaterialTexture *material_texture = GPU_material_add_texture(material, GPU_SAMPLER_DEFAULT); + + /* Map the output socket to the material texture that was created for it. */ + output_to_material_texture_map_.add(output, material_texture); + + /* Declare the input descriptor using the name of the sampler in the shader as the identifier. */ + StringRef identifier = material_texture->sampler_name; + const InputDescriptor input_descriptor = input_descriptor_from_input_socket(input.socket_ref()); + declare_input_descriptor(identifier, input_descriptor); + + /* Map the operation input to the output socket it is linked to. */ + inputs_to_linked_outputs_map_.add_new(identifier, output); +} + +void ShaderOperation::link_material_input_loader(DInputSocket input, + DOutputSocket output, + GPUMaterial *material) +{ + /* Create a link from the material texture that corresponds to the given output. */ + GPUMaterialTexture *material_texture = output_to_material_texture_map_.lookup(output); + GPUNodeLink *input_texture_link = GPU_image_from_material_texture(material_texture); + + /* Get the node stack of the input. */ + ShaderNode &node = *shader_nodes_.lookup(input.node()); + GPUNodeStack &stack = node.get_inputs_array()[input->index()]; + + /* Link the input node stack to an input loader sampling the input texture. */ + const char *load_function_name = get_load_function_name(input); + GPU_link(material, load_function_name, input_texture_link, &stack.link); +} + +void ShaderOperation::populate_results_for_node(DNode node, GPUMaterial *material) +{ + for (const OutputSocketRef *output_ref : node->outputs()) { + const DOutputSocket output{node.context(), output_ref}; + + /* If any of the nodes linked to the output are not part of the shader operation, then an + * output result needs to be populated. */ + const bool need_to_populate_result = is_output_linked_to_node_conditioned( + output, [&](DNode node) { return !compile_unit_.contains(node); }); + + if (need_to_populate_result) { + populate_operation_result(output, material); + } + } +} + +static const char *get_store_function_name(ResultType type) +{ + switch (type) { + case ResultType::Float: + return "store_output_float"; + case ResultType::Vector: + return "store_output_vector"; + case ResultType::Color: + return "store_output_color"; + } + + BLI_assert_unreachable(); + return nullptr; +} + +static eGPUTextureFormat texture_format_from_result_type(ResultType type) +{ + switch (type) { + case ResultType::Float: + return GPU_R16F; + case ResultType::Vector: + return GPU_RGBA16F; + case ResultType::Color: + return GPU_RGBA16F; + } + + BLI_assert_unreachable(); + return GPU_RGBA16F; +} + +void ShaderOperation::populate_operation_result(DOutputSocket output, GPUMaterial *material) +{ + /* Construct a result of an appropriate type. */ + const ResultType result_type = get_node_socket_result_type(output.socket_ref()); + const Result result = Result(result_type, texture_pool()); + + /* Add a new material image to the GPU material. */ + const eGPUTextureFormat format = texture_format_from_result_type(result_type); + GPUMaterialImage *material_image = GPU_material_add_image_texture(material, format); + + /* Add the result using the name of the image in the shader as the identifier. */ + StringRef identifier = material_image->name_in_shader; + populate_result(identifier, result); + + /* Map the output socket to the identifier of the result. */ + output_sockets_to_output_identifiers_map_.add_new(output, identifier); + + /* Create a link from the material image that corresponds to the given output. */ + GPUNodeLink *output_image_link = GPU_image_texture_from_material_image(material_image); + + /* Get the node stack of the output. */ + ShaderNode &node = *shader_nodes_.lookup(output.node()); + GPUNodeLink *output_link = node.get_outputs_array()[output->index()].link; + + /* Link the output node stack to an output storer storing in the newly added image. */ + const char *store_function_name = get_store_function_name(result_type); + GPU_link(material, store_function_name, output_image_link, output_link); +} + +void ShaderOperation::generate_material(void *UNUSED(thunk), + GPUMaterial *UNUSED(material), + GPUCodegenOutput *code_generator_output) +{ + gpu::shader::ShaderCreateInfo &info = *reinterpret_cast<gpu::shader::ShaderCreateInfo *>( + code_generator_output->create_info); + + /* The GPU material adds resources without explicit locations, so make sure it is done by the + * shader creator. */ + info.auto_resource_location(true); + + info.local_group_size(16, 16); + + /* Add implementation for implicit conversion operations inserted by the code generator. */ + info.typedef_source("gpu_shader_compositor_type_conversion.glsl"); + + /* Add the compute source code of the generator in a main function and set it as the generated + * compute source of the shader create info. */ + std::string source = "void main()\n{\n" + std::string(code_generator_output->compute) + "}\n"; + info.compute_source_generated = source; +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/shader_pool.cc b/source/blender/compositor/realtime_compositor/intern/shader_pool.cc new file mode 100644 index 00000000000..3b9de579917 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/shader_pool.cc @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "GPU_shader.h" + +#include "COM_shader_pool.hh" + +namespace blender::realtime_compositor { + +ShaderPool::~ShaderPool() +{ + for (GPUShader *shader : shaders_.values()) { + GPU_shader_free(shader); + } +} + +GPUShader *ShaderPool::acquire(const char *info_name) +{ + /* If a shader with the same info name already exists in the pool, return it, otherwise, create a + * new shader from the info name and return it. */ + return shaders_.lookup_or_add_cb( + info_name, [info_name]() { return GPU_shader_create_from_info_name(info_name); }); +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/simple_operation.cc b/source/blender/compositor/realtime_compositor/intern/simple_operation.cc new file mode 100644 index 00000000000..ffb29554df9 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/simple_operation.cc @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "COM_input_descriptor.hh" +#include "COM_operation.hh" +#include "COM_result.hh" +#include "COM_simple_operation.hh" + +namespace blender::realtime_compositor { + +const StringRef SimpleOperation::input_identifier_ = StringRef("Input"); +const StringRef SimpleOperation::output_identifier_ = StringRef("Output"); + +Result &SimpleOperation::get_result() +{ + return Operation::get_result(output_identifier_); +} + +void SimpleOperation::map_input_to_result(Result *result) +{ + Operation::map_input_to_result(input_identifier_, result); +} + +void SimpleOperation::add_and_evaluate_input_processors() +{ +} + +Result &SimpleOperation::get_input() +{ + return Operation::get_input(input_identifier_); +} + +void SimpleOperation::switch_result_mapped_to_input(Result *result) +{ + Operation::switch_result_mapped_to_input(input_identifier_, result); +} + +void SimpleOperation::populate_result(Result result) +{ + Operation::populate_result(output_identifier_, result); + /* The result of a simple operation is guaranteed to have a single user. */ + get_result().set_initial_reference_count(1); +} + +void SimpleOperation::declare_input_descriptor(InputDescriptor descriptor) +{ + Operation::declare_input_descriptor(input_identifier_, descriptor); +} + +InputDescriptor &SimpleOperation::get_input_descriptor() +{ + return Operation::get_input_descriptor(input_identifier_); +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/texture_pool.cc b/source/blender/compositor/realtime_compositor/intern/texture_pool.cc new file mode 100644 index 00000000000..1568970a030 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/texture_pool.cc @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <cstdint> + +#include "BLI_hash.hh" +#include "BLI_map.hh" +#include "BLI_math_vec_types.hh" +#include "BLI_vector.hh" + +#include "GPU_texture.h" + +#include "COM_texture_pool.hh" + +namespace blender::realtime_compositor { + +/* -------------------------------------------------------------------- + * Texture Pool Key. + */ + +TexturePoolKey::TexturePoolKey(int2 size, eGPUTextureFormat format) : size(size), format(format) +{ +} + +TexturePoolKey::TexturePoolKey(const GPUTexture *texture) +{ + size = int2(GPU_texture_width(texture), GPU_texture_height(texture)); + format = GPU_texture_format(texture); +} + +uint64_t TexturePoolKey::hash() const +{ + return get_default_hash_3(size.x, size.y, format); +} + +bool operator==(const TexturePoolKey &a, const TexturePoolKey &b) +{ + return a.size == b.size && a.format == b.format; +} + +/* -------------------------------------------------------------------- + * Texture Pool. + */ + +GPUTexture *TexturePool::acquire(int2 size, eGPUTextureFormat format) +{ + /* Check if there is an available texture with the required specification, and if one exists, + * return it. */ + const TexturePoolKey key = TexturePoolKey(size, format); + Vector<GPUTexture *> &available_textures = textures_.lookup_or_add_default(key); + if (!available_textures.is_empty()) { + return available_textures.pop_last(); + } + + /* Otherwise, allocate a new texture. */ + return allocate_texture(size, format); +} + +GPUTexture *TexturePool::acquire_color(int2 size) +{ + return acquire(size, GPU_RGBA16F); +} + +GPUTexture *TexturePool::acquire_vector(int2 size) +{ + /* Vectors are stored in RGBA textures because RGB textures have limited support. */ + return acquire(size, GPU_RGBA16F); +} + +GPUTexture *TexturePool::acquire_float(int2 size) +{ + return acquire(size, GPU_R16F); +} + +void TexturePool::release(GPUTexture *texture) +{ + textures_.lookup(TexturePoolKey(texture)).append(texture); +} + +void TexturePool::reset() +{ + textures_.clear(); +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/utilities.cc b/source/blender/compositor/realtime_compositor/intern/utilities.cc new file mode 100644 index 00000000000..06e82bba52b --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/utilities.cc @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_assert.h" +#include "BLI_function_ref.hh" +#include "BLI_math_vec_types.hh" +#include "BLI_utildefines.h" + +#include "DNA_node_types.h" + +#include "NOD_derived_node_tree.hh" +#include "NOD_node_declaration.hh" + +#include "GPU_compute.h" +#include "GPU_shader.h" + +#include "COM_operation.hh" +#include "COM_result.hh" +#include "COM_utilities.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; +using TargetSocketPathInfo = DOutputSocket::TargetSocketPathInfo; + +DSocket get_input_origin_socket(DInputSocket input) +{ + /* The input is unlinked. Return the socket itself. */ + if (input->logically_linked_sockets().is_empty()) { + return input; + } + + /* Only a single origin socket is guaranteed to exist. */ + DSocket socket; + input.foreach_origin_socket([&](const DSocket origin) { socket = origin; }); + return socket; +} + +DOutputSocket get_output_linked_to_input(DInputSocket input) +{ + /* Get the origin socket of this input, which will be an output socket if the input is linked + * to an output. */ + const DSocket origin = get_input_origin_socket(input); + + /* If the origin socket is an input, that means the input is unlinked, return a null output + * socket. */ + if (origin->is_input()) { + return DOutputSocket(); + } + + /* Now that we know the origin is an output, return a derived output from it. */ + return DOutputSocket(origin.context(), &origin->as_output()); +} + +ResultType get_node_socket_result_type(const SocketRef *socket) +{ + switch (socket->bsocket()->type) { + case SOCK_FLOAT: + return ResultType::Float; + case SOCK_VECTOR: + return ResultType::Vector; + case SOCK_RGBA: + return ResultType::Color; + default: + BLI_assert_unreachable(); + return ResultType::Float; + } +} + +bool is_output_linked_to_node_conditioned(DOutputSocket output, FunctionRef<bool(DNode)> condition) +{ + bool condition_satisfied = false; + output.foreach_target_socket( + [&](DInputSocket target, const TargetSocketPathInfo &UNUSED(path_info)) { + if (condition(target.node())) { + condition_satisfied = true; + return; + } + }); + return condition_satisfied; +} + +int number_of_inputs_linked_to_output_conditioned(DOutputSocket output, + FunctionRef<bool(DInputSocket)> condition) +{ + int count = 0; + output.foreach_target_socket( + [&](DInputSocket target, const TargetSocketPathInfo &UNUSED(path_info)) { + if (condition(target)) { + count++; + } + }); + return count; +} + +bool is_shader_node(DNode node) +{ + return node->typeinfo()->get_compositor_shader_node; +} + +bool is_node_supported(DNode node) +{ + return node->typeinfo()->get_compositor_operation || + node->typeinfo()->get_compositor_shader_node; +} + +InputDescriptor input_descriptor_from_input_socket(const InputSocketRef *socket) +{ + using namespace nodes; + InputDescriptor input_descriptor; + input_descriptor.type = get_node_socket_result_type(socket); + const NodeDeclaration *node_declaration = socket->node().declaration(); + /* Not every node have a declaration, in which case, we assume the default values for the rest of + * the properties. */ + if (!node_declaration) { + return input_descriptor; + } + const SocketDeclarationPtr &socket_declaration = node_declaration->inputs()[socket->index()]; + input_descriptor.domain_priority = socket_declaration->compositor_domain_priority(); + input_descriptor.expects_single_value = socket_declaration->compositor_expects_single_value(); + return input_descriptor; +} + +void compute_dispatch_global(GPUShader *shader, int2 global_size, int2 local_size) +{ + /* If the global size is divisible by the local size, dispatch the number of needed groups, which + * is their division. If it is not divisible, then dispatch an extra group to cover the remaining + * invocations, which means the actual global size of the invocation will be a bit larger than + * the give one. */ + const int x = (global_size.x / local_size.x) + (global_size.x % local_size.x != 0); + const int y = (global_size.y / local_size.y) + (global_size.y % local_size.y != 0); + GPU_compute_dispatch(shader, x, y, 1); +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt index 9cb3743dd02..31b262ba320 100644 --- a/source/blender/draw/CMakeLists.txt +++ b/source/blender/draw/CMakeLists.txt @@ -23,6 +23,7 @@ set(INC ../nodes ../render ../render/intern + ../compositor/realtime_compositor ../windowmanager ../../../intern/atomic @@ -104,6 +105,7 @@ set(SRC intern/smaa_textures.c engines/basic/basic_engine.c engines/basic/basic_shader.c + engines/compositor/compositor_engine.cc engines/image/image_engine.cc engines/image/image_shader.cc engines/eevee/eevee_bloom.c @@ -225,6 +227,7 @@ set(SRC intern/smaa_textures.h engines/basic/basic_engine.h engines/basic/basic_private.h + engines/compositor/compositor_engine.h engines/eevee/eevee_engine.h engines/eevee/eevee_lightcache.h engines/eevee/eevee_lut.h @@ -256,6 +259,7 @@ set(SRC set(LIB bf_blenkernel bf_blenlib + bf_realtime_compositor bf_windowmanager ) diff --git a/source/blender/draw/engines/compositor/compositor_engine.cc b/source/blender/draw/engines/compositor/compositor_engine.cc new file mode 100644 index 00000000000..d7feb62cb18 --- /dev/null +++ b/source/blender/draw/engines/compositor/compositor_engine.cc @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_listbase.h" +#include "BLI_math_vec_types.hh" +#include "BLI_string_ref.hh" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "DNA_ID_enums.h" +#include "DNA_scene_types.h" + +#include "DEG_depsgraph_query.h" + +#include "DRW_render.h" + +#include "IMB_colormanagement.h" + +#include "COM_context.hh" +#include "COM_evaluator.hh" +#include "COM_texture_pool.hh" + +#include "GPU_texture.h" + +namespace blender::realtime_compositor { + +class DRWTexturePool : public TexturePool { + public: + GPUTexture *allocate_texture(int2 size, eGPUTextureFormat format) override + { + DrawEngineType *owner = (DrawEngineType *)this; + return DRW_texture_pool_query_2d(size.x, size.y, format, owner); + } +}; + +class DRWContext : public Context { + private: + /* A pointer to the info message of the compositor engine. This is a char array of size + * GPU_INFO_SIZE. The message is cleared prior to updating or evaluating the compositor. */ + char *info_message_; + + public: + DRWContext(TexturePool &texture_pool, char *info_message) + : Context(texture_pool), info_message_(info_message) + { + } + + const Scene *get_scene() const override + { + return DRW_context_state_get()->scene; + } + + int2 get_viewport_size() override + { + return int2(float2(DRW_viewport_size_get())); + } + + GPUTexture *get_viewport_texture() override + { + return DRW_viewport_texture_list_get()->color; + } + + GPUTexture *get_pass_texture(int UNUSED(view_layer), eScenePassType UNUSED(pass_type)) override + { + return get_viewport_texture(); + } + + StringRef get_view_name() override + { + const SceneRenderView *view = static_cast<SceneRenderView *>( + BLI_findlink(&get_scene()->r.views, DRW_context_state_get()->v3d->multiview_eye)); + return view->name; + } + + void set_info_message(StringRef message) const override + { + message.copy(info_message_, GPU_INFO_SIZE); + } +}; + +class Engine { + private: + DRWTexturePool texture_pool_; + DRWContext context_; + Evaluator evaluator_; + /* Stores the viewport size at the time the last compositor evaluation happened. See the + * update_viewport_size method for more information. */ + int2 last_viewport_size_; + + public: + Engine(char *info_message) + : context_(texture_pool_, info_message), + evaluator_(context_, node_tree()), + last_viewport_size_(context_.get_viewport_size()) + { + } + + /* Update the viewport size and evaluate the compositor. */ + void draw() + { + update_viewport_size(); + evaluator_.evaluate(); + } + + /* If the size of the viewport changed from the last time the compositor was evaluated, update + * the viewport size and reset the evaluator. That's because the evaluator compiles the node tree + * in a manner that is specifically optimized for the size of the viewport. This should be called + * before evaluating the compositor. */ + void update_viewport_size() + { + if (last_viewport_size_ == context_.get_viewport_size()) { + return; + } + + last_viewport_size_ = context_.get_viewport_size(); + + evaluator_.reset(); + } + + /* If the compositor node tree changed, reset the evaluator. */ + void update(const Depsgraph *depsgraph) + { + if (DEG_id_type_updated(depsgraph, ID_NT)) { + evaluator_.reset(); + } + } + + /* Get a reference to the compositor node tree. */ + static bNodeTree &node_tree() + { + return *DRW_context_state_get()->scene->nodetree; + } +}; + +} // namespace blender::realtime_compositor + +using namespace blender::realtime_compositor; + +typedef struct CompositorData { + DrawEngineType *engine_type; + DRWViewportEmptyList *fbl; + DRWViewportEmptyList *txl; + DRWViewportEmptyList *psl; + DRWViewportEmptyList *stl; + Engine *instance_data; + char info[GPU_INFO_SIZE]; +} CompositorData; + +static void compositor_engine_init(void *data) +{ + CompositorData *compositor_data = static_cast<CompositorData *>(data); + + if (!compositor_data->instance_data) { + compositor_data->instance_data = new Engine(compositor_data->info); + } +} + +static void compositor_engine_free(void *instance_data) +{ + Engine *engine = static_cast<Engine *>(instance_data); + delete engine; +} + +static void compositor_engine_draw(void *data) +{ + const CompositorData *compositor_data = static_cast<CompositorData *>(data); + compositor_data->instance_data->draw(); +} + +static void compositor_engine_update(void *data) +{ + CompositorData *compositor_data = static_cast<CompositorData *>(data); + + /* Clear any info message that was set in a previous update. */ + compositor_data->info[0] = '\0'; + + if (compositor_data->instance_data) { + compositor_data->instance_data->update(DRW_context_state_get()->depsgraph); + } +} + +extern "C" { + +static const DrawEngineDataSize compositor_data_size = DRW_VIEWPORT_DATA_SIZE(CompositorData); + +DrawEngineType draw_engine_compositor_type = { + nullptr, /* next */ + nullptr, /* prev */ + N_("Compositor"), /* idname */ + &compositor_data_size, /* vedata_size */ + &compositor_engine_init, /* engine_init */ + nullptr, /* engine_free */ + &compositor_engine_free, /* instance_free */ + nullptr, /* cache_init */ + nullptr, /* cache_populate */ + nullptr, /* cache_finish */ + &compositor_engine_draw, /* draw_scene */ + &compositor_engine_update, /* view_update */ + nullptr, /* id_update */ + nullptr, /* render_to_image */ + nullptr, /* store_metadata */ +}; +} diff --git a/source/blender/draw/engines/compositor/compositor_engine.h b/source/blender/draw/engines/compositor/compositor_engine.h new file mode 100644 index 00000000000..5de0de8a0b3 --- /dev/null +++ b/source/blender/draw/engines/compositor/compositor_engine.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +extern DrawEngineType draw_engine_compositor_type; + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index bc9d0a3d02a..dd36a9c6719 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -43,6 +43,7 @@ #include "DNA_camera_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" +#include "DNA_userdef_types.h" #include "DNA_world_types.h" #include "ED_gpencil.h" @@ -84,6 +85,7 @@ #include "draw_cache_impl.h" #include "engines/basic/basic_engine.h" +#include "engines/compositor/compositor_engine.h" #include "engines/eevee/eevee_engine.h" #include "engines/eevee_next/eevee_engine.h" #include "engines/external/external_engine.h" @@ -1225,6 +1227,31 @@ static void drw_engines_enable_editors(void) } } +static bool is_compositor_enabled(void) +{ + if (!U.experimental.use_realtime_compositor) { + return false; + } + + if (!(DST.draw_ctx.v3d->shading.flag & V3D_SHADING_COMPOSITOR)) { + return false; + } + + if (!(DST.draw_ctx.v3d->shading.type > OB_MATERIAL)) { + return false; + } + + if (!DST.draw_ctx.scene->use_nodes) { + return false; + } + + if (!DST.draw_ctx.scene->nodetree) { + return false; + } + + return true; +} + static void drw_engines_enable(ViewLayer *UNUSED(view_layer), RenderEngineType *engine_type, bool gpencil_engine_needed) @@ -1237,6 +1264,11 @@ static void drw_engines_enable(ViewLayer *UNUSED(view_layer), if (gpencil_engine_needed && ((drawtype >= OB_SOLID) || !use_xray)) { use_drw_engine(&draw_engine_gpencil_type); } + + if (is_compositor_enabled()) { + use_drw_engine(&draw_engine_compositor_type); + } + drw_engines_enable_overlays(); #ifdef WITH_DRAW_DEBUG @@ -1608,7 +1640,6 @@ void DRW_draw_render_loop_ex(struct Depsgraph *depsgraph, GPUViewport *viewport, const bContext *evil_C) { - Scene *scene = DEG_get_evaluated_scene(depsgraph); ViewLayer *view_layer = DEG_get_evaluated_view_layer(depsgraph); RegionView3D *rv3d = region->regiondata; @@ -2959,6 +2990,7 @@ void DRW_engines_register(void) DRW_engine_register(&draw_engine_overlay_type); DRW_engine_register(&draw_engine_select_type); DRW_engine_register(&draw_engine_basic_type); + DRW_engine_register(&draw_engine_compositor_type); #ifdef WITH_DRAW_DEBUG DRW_engine_register(&draw_engine_debug_select_type); #endif diff --git a/source/blender/editors/render/render_update.cc b/source/blender/editors/render/render_update.cc index 3d26e764211..7cefcf9815e 100644 --- a/source/blender/editors/render/render_update.cc +++ b/source/blender/editors/render/render_update.cc @@ -95,20 +95,20 @@ void ED_render_view3d_update(Depsgraph *depsgraph, CTX_free(C); } - else { - RenderEngineType *engine_type = ED_view3d_engine_type(scene, v3d->shading.type); - if (updated) { - DRWUpdateContext drw_context = {nullptr}; - drw_context.bmain = bmain; - drw_context.depsgraph = depsgraph; - drw_context.scene = scene; - drw_context.view_layer = view_layer; - drw_context.region = region; - drw_context.v3d = v3d; - drw_context.engine_type = engine_type; - DRW_notify_view_update(&drw_context); - } + + if (!updated) { + continue; } + + DRWUpdateContext drw_context = {nullptr}; + drw_context.bmain = bmain; + drw_context.depsgraph = depsgraph; + drw_context.scene = scene; + drw_context.view_layer = view_layer; + drw_context.region = region; + drw_context.v3d = v3d; + drw_context.engine_type = ED_view3d_engine_type(scene, v3d->shading.type); + DRW_notify_view_update(&drw_context); } } diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index 9ed2fec96db..84bcbfd53fe 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -1210,6 +1210,9 @@ static void view3d_main_region_listener(const wmRegionListenerParams *params) break; } break; + case NC_NODE: + ED_region_tag_redraw(region); + break; case NC_WORLD: switch (wmn->data) { case ND_WORLD_DRAW: diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index faf68cf2197..2b4945069c4 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -312,6 +312,46 @@ set(GLSL_SRC shaders/common/gpu_shader_common_math_utils.glsl shaders/common/gpu_shader_common_mix_rgb.glsl + shaders/compositor/compositor_alpha_crop.glsl + shaders/compositor/compositor_blur.glsl + shaders/compositor/compositor_box_mask.glsl + shaders/compositor/compositor_convert.glsl + shaders/compositor/compositor_ellipse_mask.glsl + shaders/compositor/compositor_filter.glsl + shaders/compositor/compositor_flip.glsl + shaders/compositor/compositor_image_crop.glsl + shaders/compositor/compositor_projector_lens_distortion.glsl + shaders/compositor/compositor_realize_on_domain.glsl + shaders/compositor/compositor_screen_lens_distortion.glsl + shaders/compositor/compositor_split_viewer.glsl + + shaders/compositor/library/gpu_shader_compositor_alpha_over.glsl + shaders/compositor/library/gpu_shader_compositor_bright_contrast.glsl + shaders/compositor/library/gpu_shader_compositor_channel_matte.glsl + shaders/compositor/library/gpu_shader_compositor_chroma_matte.glsl + shaders/compositor/library/gpu_shader_compositor_color_balance.glsl + shaders/compositor/library/gpu_shader_compositor_color_correction.glsl + shaders/compositor/library/gpu_shader_compositor_color_matte.glsl + shaders/compositor/library/gpu_shader_compositor_color_spill.glsl + shaders/compositor/library/gpu_shader_compositor_color_to_luminance.glsl + shaders/compositor/library/gpu_shader_compositor_difference_matte.glsl + shaders/compositor/library/gpu_shader_compositor_distance_matte.glsl + shaders/compositor/library/gpu_shader_compositor_exposure.glsl + shaders/compositor/library/gpu_shader_compositor_gamma.glsl + shaders/compositor/library/gpu_shader_compositor_hue_correct.glsl + shaders/compositor/library/gpu_shader_compositor_hue_saturation_value.glsl + shaders/compositor/library/gpu_shader_compositor_invert.glsl + shaders/compositor/library/gpu_shader_compositor_load_input.glsl + shaders/compositor/library/gpu_shader_compositor_luminance_matte.glsl + shaders/compositor/library/gpu_shader_compositor_map_value.glsl + shaders/compositor/library/gpu_shader_compositor_normal.glsl + shaders/compositor/library/gpu_shader_compositor_posterize.glsl + shaders/compositor/library/gpu_shader_compositor_separate_combine.glsl + shaders/compositor/library/gpu_shader_compositor_set_alpha.glsl + shaders/compositor/library/gpu_shader_compositor_store_output.glsl + shaders/compositor/library/gpu_shader_compositor_texture_utilities.glsl + shaders/compositor/library/gpu_shader_compositor_type_conversion.glsl + shaders/material/gpu_shader_material_add_shader.glsl shaders/material/gpu_shader_material_ambient_occlusion.glsl shaders/material/gpu_shader_material_anisotropic.glsl @@ -511,6 +551,19 @@ set(SRC_SHADER_CREATE_INFOS shaders/infos/gpu_shader_simple_lighting_info.hh shaders/infos/gpu_shader_text_info.hh shaders/infos/gpu_srgb_to_framebuffer_space_info.hh + + shaders/compositor/infos/compositor_alpha_crop_info.hh + shaders/compositor/infos/compositor_blur_info.hh + shaders/compositor/infos/compositor_box_mask_info.hh + shaders/compositor/infos/compositor_convert_info.hh + shaders/compositor/infos/compositor_ellipse_mask_info.hh + shaders/compositor/infos/compositor_filter_info.hh + shaders/compositor/infos/compositor_flip_info.hh + shaders/compositor/infos/compositor_image_crop_info.hh + shaders/compositor/infos/compositor_projector_lens_distortion_info.hh + shaders/compositor/infos/compositor_realize_on_domain_info.hh + shaders/compositor/infos/compositor_screen_lens_distortion_info.hh + shaders/compositor/infos/compositor_split_viewer_info.hh ) set(SHADER_CREATE_INFOS_CONTENT "") diff --git a/source/blender/gpu/GPU_material.h b/source/blender/gpu/GPU_material.h index c0633f0323d..704248b863a 100644 --- a/source/blender/gpu/GPU_material.h +++ b/source/blender/gpu/GPU_material.h @@ -59,6 +59,7 @@ typedef enum eGPUType { GPU_TEX2D = 1002, GPU_TEX2D_ARRAY = 1003, GPU_TEX3D = 1004, + GPU_IMAGE_2D = 1005, /* GLSL Struct types */ GPU_CLOSURE = 1007, @@ -121,6 +122,7 @@ typedef struct GPUCodegenOutput { char *surface; char *volume; char *thickness; + char *compute; char *material_functions; GPUShaderCreateInfo *create_info; @@ -213,6 +215,7 @@ GPUMaterial *GPU_material_from_nodetree(struct Scene *scene, void *thunk); void GPU_material_compile(GPUMaterial *mat); +void GPU_material_free_single(GPUMaterial *material); void GPU_material_free(struct ListBase *gpumaterial); void GPU_material_acquire(GPUMaterial *mat); @@ -233,6 +236,9 @@ struct Material *GPU_material_get_material(GPUMaterial *material); eGPUMaterialStatus GPU_material_status(GPUMaterial *mat); void GPU_material_status_set(GPUMaterial *mat, eGPUMaterialStatus status); +bool GPU_material_is_compute(GPUMaterial *material); +void GPU_material_is_compute_set(GPUMaterial *material, bool is_compute); + struct GPUUniformBuf *GPU_material_uniform_buffer_get(GPUMaterial *material); /** * Create dynamic UBO from parameters @@ -280,8 +286,16 @@ typedef struct GPUMaterialTexture { int sampler_state; /* eGPUSamplerState */ } GPUMaterialTexture; +/* A reference to a write only 2D image of a specific format. */ +typedef struct GPUMaterialImage { + struct GPUMaterialImage *next, *prev; + eGPUTextureFormat format; + char name_in_shader[32]; +} GPUMaterialImage; + ListBase GPU_material_attributes(GPUMaterial *material); ListBase GPU_material_textures(GPUMaterial *material); +ListBase GPU_material_images(GPUMaterial *material); typedef struct GPUUniformAttr { struct GPUUniformAttr *next, *prev; @@ -308,6 +322,26 @@ struct GHash *GPU_uniform_attr_list_hash_new(const char *info); void GPU_uniform_attr_list_copy(GPUUniformAttrList *dest, GPUUniformAttrList *src); void GPU_uniform_attr_list_free(GPUUniformAttrList *set); +GPUMaterialTexture *GPU_material_add_texture(GPUMaterial *material, + eGPUSamplerState sampler_state); +GPUMaterialImage *GPU_material_add_image_texture(GPUMaterial *material, eGPUTextureFormat format); + +GPUNodeLink *GPU_image_from_material_texture(GPUMaterialTexture *texture); +GPUNodeLink *GPU_image_texture_from_material_image(GPUMaterialImage *image); + +typedef void (*GPUMaterialSetupFn)(void *thunk, GPUMaterial *material); +typedef void (*GPUMaterialCompileFn)(void *thunk, GPUMaterial *material); + +/* Construct a GPU material from a set of callbacks. The setup callback should set the appropriate + * flags or members to the material. The compile callback should construct the material graph by + * adding and linking the necessary GPU material graph nodes. The generate function should + * construct the needed shader by initializing the passed shader create info structure. The given + * thunk will be passed as the first parameter of each callback. */ +GPUMaterial *GPU_material_from_callbacks(GPUMaterialSetupFn setup_function, + GPUMaterialCompileFn compile_function, + GPUCodegenCallbackFn generate_function, + void *thunk); + #ifdef __cplusplus } #endif diff --git a/source/blender/gpu/GPU_shader.h b/source/blender/gpu/GPU_shader.h index 3460d33fe68..c154f1adc8b 100644 --- a/source/blender/gpu/GPU_shader.h +++ b/source/blender/gpu/GPU_shader.h @@ -177,7 +177,9 @@ void GPU_shader_uniform_4f(GPUShader *sh, const char *name, float x, float y, fl void GPU_shader_uniform_2fv(GPUShader *sh, const char *name, const float data[2]); void GPU_shader_uniform_3fv(GPUShader *sh, const char *name, const float data[3]); void GPU_shader_uniform_4fv(GPUShader *sh, const char *name, const float data[4]); +void GPU_shader_uniform_2iv(GPUShader *sh, const char *name, const int data[2]); void GPU_shader_uniform_mat4(GPUShader *sh, const char *name, const float data[4][4]); +void GPU_shader_uniform_mat3_as_mat4(GPUShader *sh, const char *name, const float data[3][3]); void GPU_shader_uniform_2fv_array(GPUShader *sh, const char *name, int len, const float (*val)[2]); void GPU_shader_uniform_4fv_array(GPUShader *sh, const char *name, int len, const float (*val)[4]); diff --git a/source/blender/gpu/intern/gpu_codegen.cc b/source/blender/gpu/intern/gpu_codegen.cc index a5ba0949a83..a846c57b2a4 100644 --- a/source/blender/gpu/intern/gpu_codegen.cc +++ b/source/blender/gpu/intern/gpu_codegen.cc @@ -188,6 +188,8 @@ static std::ostream &operator<<(std::ostream &stream, const GPUInput *input) return stream << input->texture->sampler_name; case GPU_SOURCE_TEX_TILED_MAPPING: return stream << input->texture->tiled_mapping_name; + case GPU_SOURCE_IMAGE: + return stream << input->image->name_in_shader; default: BLI_assert(0); return stream; @@ -260,6 +262,7 @@ class GPUCodegen { MEM_SAFE_FREE(output.volume); MEM_SAFE_FREE(output.thickness); MEM_SAFE_FREE(output.displacement); + MEM_SAFE_FREE(output.compute); MEM_SAFE_FREE(output.material_functions); delete create_info; BLI_freelistN(&ubo_inputs_); @@ -281,6 +284,7 @@ class GPUCodegen { void node_serialize(std::stringstream &eval_ss, const GPUNode *node); char *graph_serialize(eGPUNodeTag tree_tag, GPUNodeLink *output_link); + char *graph_serialize_compute(); static char *extract_c_str(std::stringstream &stream) { @@ -373,6 +377,16 @@ void GPUCodegen::generate_resources() } } + /* Images. */ + LISTBASE_FOREACH (GPUMaterialImage *, image, &graph.images) { + info.image(0, + image->format, + Qualifier::WRITE, + ImageType::FLOAT_2D, + image->name_in_shader, + Frequency::BATCH); + } + if (!BLI_listbase_is_empty(&ubo_inputs_)) { /* NOTE: generate_uniform_buffer() should have sorted the inputs before this. */ ss << "struct NodeTree {\n"; @@ -467,7 +481,9 @@ void GPUCodegen::node_serialize(std::stringstream &eval_ss, const GPUNode *node) eval_ss << input; break; } - eval_ss << ", "; + if (input->next || !BLI_listbase_is_empty(&node->outputs)) { + eval_ss << ", "; + } } /* Output arguments. */ LISTBASE_FOREACH (GPUOutput *, output, &node->outputs) { @@ -501,6 +517,18 @@ char *GPUCodegen::graph_serialize(eGPUNodeTag tree_tag, GPUNodeLink *output_link return eval_c_str; } +char *GPUCodegen::graph_serialize_compute() +{ + /* Serialize all nodes. */ + std::stringstream eval_ss; + LISTBASE_FOREACH (GPUNode *, node, &graph.nodes) { + node_serialize(eval_ss, node); + } + char *eval_c_str = extract_c_str(eval_ss); + BLI_hash_mm2a_add(&hm2a_, (uchar *)eval_c_str, eval_ss.str().size()); + return eval_c_str; +} + void GPUCodegen::generate_uniform_buffer() { /* Extract uniform inputs. */ @@ -540,6 +568,9 @@ void GPUCodegen::generate_graphs() output.volume = graph_serialize(GPU_NODE_TAG_VOLUME, graph.outlink_volume); output.displacement = graph_serialize(GPU_NODE_TAG_DISPLACEMENT, graph.outlink_displacement); output.thickness = graph_serialize(GPU_NODE_TAG_THICKNESS, graph.outlink_thickness); + if (GPU_material_is_compute(&mat)) { + output.compute = graph_serialize_compute(); + } if (!BLI_listbase_is_empty(&graph.material_functions)) { std::stringstream eval_ss; @@ -570,9 +601,15 @@ GPUPass *GPU_generate_pass(GPUMaterial *material, GPUCodegenCallbackFn finalize_source_cb, void *thunk) { - /* Prune the unused nodes and extract attributes before compiling so the - * generated VBOs are ready to accept the future shader. */ - gpu_node_graph_prune_unused(graph); + /* Only prune unused nodes if the GPU material is not a compute one, as nodes in compute + * materials can make arbitrary reads and writes in any node. It is then the responsibility of + * the caller to make sure no unused nodes exists. */ + if (!GPU_material_is_compute(material)) { + gpu_node_graph_prune_unused(graph); + } + + /* Extract attributes before compiling so the generated VBOs are ready to accept the future + * shader. */ gpu_node_graph_finalize_uniform_attrs(graph); GPUCodegen codegen(material, graph); diff --git a/source/blender/gpu/intern/gpu_material.c b/source/blender/gpu/intern/gpu_material.c index 808a8ccd955..56965d4c389 100644 --- a/source/blender/gpu/intern/gpu_material.c +++ b/source/blender/gpu/intern/gpu_material.c @@ -60,6 +60,8 @@ struct GPUMaterial { eGPUMaterialStatus status; /** Some flags about the nodetree & the needed resources. */ eGPUMaterialFlag flag; + /** If true, all material nodes will be serialized into a compute source. */ + bool is_compute; /* Identify shader variations (shadow, probe, world background...). * Should be unique even across render engines. */ uint64_t uuid; @@ -144,7 +146,7 @@ static void gpu_material_ramp_texture_build(GPUMaterial *mat) mat->coba_builder = NULL; } -static void gpu_material_free_single(GPUMaterial *material) +void GPU_material_free_single(GPUMaterial *material) { bool do_free = atomic_sub_and_fetch_uint32(&material->refcount, 1) == 0; if (!do_free) { @@ -176,7 +178,7 @@ void GPU_material_free(ListBase *gpumaterial) LISTBASE_FOREACH (LinkData *, link, gpumaterial) { GPUMaterial *material = link->data; DRW_deferred_shader_remove(material); - gpu_material_free_single(material); + GPU_material_free_single(material); } BLI_freelistN(gpumaterial); } @@ -226,6 +228,11 @@ ListBase GPU_material_textures(GPUMaterial *material) return material->graph.textures; } +ListBase GPU_material_images(GPUMaterial *material) +{ + return material->graph.images; +} + GPUUniformAttrList *GPU_material_uniform_attributes(GPUMaterial *material) { GPUUniformAttrList *attrs = &material->graph.uniform_attrs; @@ -599,6 +606,16 @@ void GPU_material_status_set(GPUMaterial *mat, eGPUMaterialStatus status) mat->status = status; } +bool GPU_material_is_compute(GPUMaterial *material) +{ + return material->is_compute; +} + +void GPU_material_is_compute_set(GPUMaterial *material, bool is_compute) +{ + material->is_compute = is_compute; +} + /* Code generation */ bool GPU_material_has_surface_output(GPUMaterial *mat) @@ -724,7 +741,7 @@ void GPU_material_acquire(GPUMaterial *mat) void GPU_material_release(GPUMaterial *mat) { - gpu_material_free_single(mat); + GPU_material_free_single(mat); } void GPU_material_compile(GPUMaterial *mat) @@ -775,3 +792,46 @@ void GPU_materials_free(Main *bmain) // BKE_world_defaults_free_gpu(); BKE_material_defaults_free_gpu(); } + +GPUMaterial *GPU_material_from_callbacks(GPUMaterialSetupFn setup_function, + GPUMaterialCompileFn compile_function, + GPUCodegenCallbackFn generate_function, + void *thunk) +{ + /* Allocate a new material and its material graph. */ + GPUMaterial *material = MEM_callocN(sizeof(GPUMaterial), "GPUMaterial"); + material->graph.used_libraries = BLI_gset_new( + BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "GPUNodeGraph.used_libraries"); + material->refcount = 1; + + /* Setup the newly allocated material. */ + setup_function(thunk, material); + + /* Compile the material. */ + compile_function(thunk, material); + + /* Create and initialize the texture storing color bands. */ + gpu_material_ramp_texture_build(material); + + /* Lookup an existing pass in the cache or generate a new one. */ + material->pass = GPU_generate_pass(material, &material->graph, generate_function, thunk); + + /* The pass already exists in the pass cache but its shader already failed to compile. */ + if (material->pass == NULL) { + material->status = GPU_MAT_FAILED; + gpu_node_graph_free(&material->graph); + return material; + } + + /* The pass already exists in the pass cache and its shader is already compiled. */ + GPUShader *shader = GPU_pass_shader_get(material->pass); + if (shader != NULL) { + material->status = GPU_MAT_SUCCESS; + gpu_node_graph_free_nodes(&material->graph); + return material; + } + + /* The material was created successfully but still needs to be compiled. */ + material->status = GPU_MAT_CREATED; + return material; +} diff --git a/source/blender/gpu/intern/gpu_material_library.h b/source/blender/gpu/intern/gpu_material_library.h index b071ab85f3a..5aa311ed101 100644 --- a/source/blender/gpu/intern/gpu_material_library.h +++ b/source/blender/gpu/intern/gpu_material_library.h @@ -20,9 +20,13 @@ extern "C" { struct GSet; typedef enum { - FUNCTION_QUAL_IN, - FUNCTION_QUAL_OUT, - FUNCTION_QUAL_INOUT, + FUNCTION_QUAL_NONE = 0, + FUNCTION_QUAL_IN = 1 << 0, + FUNCTION_QUAL_OUT = 1 << 1, + FUNCTION_QUAL_INOUT = 1 << 2, + FUNCTION_QUAL_CONST = 1 << 3, + FUNCTION_QUAL_RESTRICT = 1 << 4, + FUNCTION_QUAL_WRITEONLY = 1 << 5, } GPUFunctionQual; typedef struct GPUFunction { diff --git a/source/blender/gpu/intern/gpu_node_graph.c b/source/blender/gpu/intern/gpu_node_graph.c index 3c6a03c56d3..94cfe90e0a5 100644 --- a/source/blender/gpu/intern/gpu_node_graph.c +++ b/source/blender/gpu/intern/gpu_node_graph.c @@ -99,6 +99,10 @@ static void gpu_node_input_link(GPUNode *node, GPUNodeLink *link, const eGPUType input->source = GPU_SOURCE_TEX; input->texture = link->texture; break; + case GPU_NODE_LINK_IMAGE_TEXTURE: + input->source = GPU_SOURCE_IMAGE; + input->image = link->image; + break; case GPU_NODE_LINK_IMAGE_TILED_MAPPING: input->source = GPU_SOURCE_TEX_TILED_MAPPING; input->texture = link->texture; @@ -437,7 +441,8 @@ static GPUMaterialTexture *gpu_node_graph_add_texture(GPUNodeGraph *graph, int num_textures = 0; GPUMaterialTexture *tex = graph->textures.first; for (; tex; tex = tex->next) { - if (tex->ima == ima && tex->colorband == colorband && tex->sampler_state == sampler_state) { + if (tex->ima && tex->ima == ima && tex->colorband == colorband && + tex->sampler_state == sampler_state) { break; } num_textures++; @@ -466,6 +471,15 @@ static GPUMaterialTexture *gpu_node_graph_add_texture(GPUNodeGraph *graph, return tex; } +static GPUMaterialImage *gpu_node_graph_add_image(GPUNodeGraph *graph, eGPUTextureFormat format) +{ + GPUMaterialImage *image = MEM_callocN(sizeof(GPUMaterialImage), __func__); + image->format = format; + const int images_count = BLI_listbase_count(&graph->images); + BLI_snprintf(image->name_in_shader, sizeof(image->name_in_shader), "image%d", images_count); + BLI_addtail(&graph->images, image); + return image; +} /* Creating Inputs */ GPUNodeLink *GPU_attribute(GPUMaterial *mat, const eCustomDataType type, const char *name) @@ -592,6 +606,34 @@ GPUNodeLink *GPU_color_band(GPUMaterial *mat, int size, float *pixels, float *ro return link; } +GPUMaterialTexture *GPU_material_add_texture(GPUMaterial *material, eGPUSamplerState sampler_state) +{ + GPUNodeGraph *graph = gpu_material_node_graph(material); + return gpu_node_graph_add_texture(graph, NULL, NULL, NULL, GPU_NODE_LINK_IMAGE, sampler_state); +} + +GPUMaterialImage *GPU_material_add_image_texture(GPUMaterial *material, eGPUTextureFormat format) +{ + GPUNodeGraph *graph = gpu_material_node_graph(material); + return gpu_node_graph_add_image(graph, format); +} + +GPUNodeLink *GPU_image_from_material_texture(GPUMaterialTexture *texture) +{ + GPUNodeLink *link = gpu_node_link_create(); + link->link_type = GPU_NODE_LINK_IMAGE; + link->texture = texture; + return link; +} + +GPUNodeLink *GPU_image_texture_from_material_image(GPUMaterialImage *image) +{ + GPUNodeLink *link = gpu_node_link_create(); + link->link_type = GPU_NODE_LINK_IMAGE_TEXTURE; + link->image = image; + return link; +} + /* Creating Nodes */ bool GPU_link(GPUMaterial *mat, const char *name, ...) @@ -613,7 +655,7 @@ bool GPU_link(GPUMaterial *mat, const char *name, ...) va_start(params, name); for (i = 0; i < function->totparam; i++) { - if (function->paramqual[i] == FUNCTION_QUAL_OUT) { + if (function->paramqual[i] & (FUNCTION_QUAL_OUT | FUNCTION_QUAL_INOUT)) { linkptr = va_arg(params, GPUNodeLink **); gpu_node_output(node, function->paramtype[i], linkptr); } @@ -671,7 +713,7 @@ static bool gpu_stack_link_v(GPUMaterial *material, } for (i = 0; i < function->totparam; i++) { - if (function->paramqual[i] == FUNCTION_QUAL_OUT) { + if (function->paramqual[i] & (FUNCTION_QUAL_OUT | FUNCTION_QUAL_INOUT)) { if (totout == 0) { linkptr = va_arg(params, GPUNodeLink **); gpu_node_output(node, function->paramtype[i], linkptr); @@ -787,6 +829,7 @@ void gpu_node_graph_free(GPUNodeGraph *graph) gpu_node_graph_free_nodes(graph); BLI_freelistN(&graph->textures); + BLI_freelistN(&graph->images); BLI_freelistN(&graph->attributes); GPU_uniform_attr_list_free(&graph->uniform_attrs); diff --git a/source/blender/gpu/intern/gpu_node_graph.h b/source/blender/gpu/intern/gpu_node_graph.h index ae472d5b7aa..d8d6ca6f20d 100644 --- a/source/blender/gpu/intern/gpu_node_graph.h +++ b/source/blender/gpu/intern/gpu_node_graph.h @@ -34,6 +34,7 @@ typedef enum eGPUDataSource { GPU_SOURCE_STRUCT, GPU_SOURCE_TEX, GPU_SOURCE_TEX_TILED_MAPPING, + GPU_SOURCE_IMAGE, GPU_SOURCE_FUNCTION_CALL, } eGPUDataSource; @@ -49,6 +50,7 @@ typedef enum { GPU_NODE_LINK_OUTPUT, GPU_NODE_LINK_UNIFORM, GPU_NODE_LINK_DIFFERENTIATE_FLOAT_FN, + GPU_NODE_LINK_IMAGE_TEXTURE, } GPUNodeLinkType; typedef enum { @@ -96,6 +98,8 @@ struct GPUNodeLink { struct GPUMaterialTexture *texture; /* GPU_NODE_LINK_DIFFERENTIATE_FLOAT_FN */ const char *function_name; + /* GPU_NODE_LINK_IMAGE_TEXTURE */ + struct GPUMaterialImage *image; }; }; @@ -130,6 +134,8 @@ typedef struct GPUInput { struct GPUUniformAttr *uniform_attr; /* GPU_SOURCE_FUNCTION_CALL */ char function_call[64]; + /* GPU_SOURCE_IMAGE */ + struct GPUMaterialImage *image; }; } GPUInput; @@ -162,6 +168,7 @@ typedef struct GPUNodeGraph { /* Requested attributes and textures. */ ListBase attributes; ListBase textures; + ListBase images; /* The list of uniform attributes. */ GPUUniformAttrList uniform_attrs; diff --git a/source/blender/gpu/intern/gpu_shader.cc b/source/blender/gpu/intern/gpu_shader.cc index fe9aacb95f9..184e4f3e60c 100644 --- a/source/blender/gpu/intern/gpu_shader.cc +++ b/source/blender/gpu/intern/gpu_shader.cc @@ -7,6 +7,7 @@ #include "MEM_guardedalloc.h" +#include "BLI_math_matrix.h" #include "BLI_string_utils.h" #include "GPU_capabilities.h" @@ -370,8 +371,7 @@ GPUShader *GPU_shader_create_from_info(const GPUShaderCreateInfo *_info) shader->geometry_shader_from_glsl(sources); } - if (!info.compute_source_.is_empty()) { - auto code = gpu_shader_dependency_get_resolved_source(info.compute_source_); + if (!info.compute_source_.is_empty() || !info.compute_source_generated.empty()) { std::string layout = shader->compute_layout_declare(info); Vector<const char *> sources; @@ -381,7 +381,11 @@ GPUShader *GPU_shader_create_from_info(const GPUShaderCreateInfo *_info) sources.extend(typedefs); sources.append(resources.c_str()); sources.append(layout.c_str()); - sources.extend(code); + if (!info.compute_source_.is_empty()) { + sources.extend(gpu_shader_dependency_get_resolved_source(info.compute_source_)); + } + sources.extend(info.dependencies_generated); + sources.append(info.compute_source_generated.c_str()); shader->compute_shader_from_glsl(sources); } @@ -702,12 +706,25 @@ void GPU_shader_uniform_4fv(GPUShader *sh, const char *name, const float data[4] GPU_shader_uniform_vector(sh, loc, 4, 1, data); } +void GPU_shader_uniform_2iv(GPUShader *sh, const char *name, const int data[2]) +{ + const int loc = GPU_shader_get_uniform(sh, name); + GPU_shader_uniform_vector_int(sh, loc, 2, 1, data); +} + void GPU_shader_uniform_mat4(GPUShader *sh, const char *name, const float data[4][4]) { const int loc = GPU_shader_get_uniform(sh, name); GPU_shader_uniform_vector(sh, loc, 16, 1, (const float *)data); } +void GPU_shader_uniform_mat3_as_mat4(GPUShader *sh, const char *name, const float data[3][3]) +{ + float matrix[4][4]; + copy_m4_m3(matrix, data); + GPU_shader_uniform_mat4(sh, name, matrix); +} + void GPU_shader_uniform_2fv_array(GPUShader *sh, const char *name, int len, const float (*val)[2]) { const int loc = GPU_shader_get_uniform(sh, name); diff --git a/source/blender/gpu/intern/gpu_shader_create_info.cc b/source/blender/gpu/intern/gpu_shader_create_info.cc index f5b90989481..44f51b86bf0 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info.cc +++ b/source/blender/gpu/intern/gpu_shader_create_info.cc @@ -145,7 +145,7 @@ std::string ShaderCreateInfo::check_error() const std::string error; /* At least a vertex shader and a fragment shader are required, or only a compute shader. */ - if (this->compute_source_.is_empty()) { + if (this->compute_source_.is_empty() && this->compute_source_generated.empty()) { if (this->vertex_source_.is_empty()) { error += "Missing vertex shader in " + this->name_ + ".\n"; } diff --git a/source/blender/gpu/intern/gpu_shader_create_info.hh b/source/blender/gpu/intern/gpu_shader_create_info.hh index 4927ef75a75..6423fe07261 100644 --- a/source/blender/gpu/intern/gpu_shader_create_info.hh +++ b/source/blender/gpu/intern/gpu_shader_create_info.hh @@ -298,6 +298,7 @@ struct ShaderCreateInfo { /** Manually set generated code. */ std::string vertex_source_generated = ""; std::string fragment_source_generated = ""; + std::string compute_source_generated = ""; std::string geometry_source_generated = ""; std::string typedef_source_generated = ""; /** Manually set generated dependencies. */ @@ -818,6 +819,7 @@ struct ShaderCreateInfo { TEST_EQUAL(*this, b, builtins_); TEST_EQUAL(*this, b, vertex_source_generated); TEST_EQUAL(*this, b, fragment_source_generated); + TEST_EQUAL(*this, b, compute_source_generated); TEST_EQUAL(*this, b, typedef_source_generated); TEST_VECTOR_EQUAL(*this, b, vertex_inputs_); TEST_EQUAL(*this, b, geometry_layout_); diff --git a/source/blender/gpu/intern/gpu_shader_dependency.cc b/source/blender/gpu/intern/gpu_shader_dependency.cc index 842914f5bed..8d53a170d22 100644 --- a/source/blender/gpu/intern/gpu_shader_dependency.cc +++ b/source/blender/gpu/intern/gpu_shader_dependency.cc @@ -17,6 +17,7 @@ #include "BLI_map.hh" #include "BLI_set.hh" #include "BLI_string_ref.hh" +#include "BLI_vector_set.hh" #include "gpu_material_library.h" #include "gpu_shader_create_info.hh" @@ -391,7 +392,7 @@ struct GPUSource { auto arg_parse = [&](const StringRef str, int64_t &cursor, - StringRef &out_qualifier, + VectorSet<StringRef> &out_qualifiers, StringRef &out_type, StringRef &out_name) -> bool { int64_t arg_start = cursor + 1; @@ -405,16 +406,19 @@ struct GPUSource { } const StringRef arg = str.substr(arg_start, cursor - arg_start); + /* Add all key words to the qualifiers vector set. Then pop the last element and store it in + * the name then pop the second to last element and store it in the type. The elements left + * are all the specified qualifiers. */ int64_t keyword_cursor = 0; - out_qualifier = keyword_parse(arg, keyword_cursor); - out_type = keyword_parse(arg, keyword_cursor); - out_name = keyword_parse(arg, keyword_cursor); - if (out_name.is_empty()) { - /* No qualifier case. */ - out_name = out_type; - out_type = out_qualifier; - out_qualifier = arg.substr(0, 0); + while (true) { + StringRef keyword = keyword_parse(arg, keyword_cursor); + if (keyword.is_empty()) { + break; + } + out_qualifiers.add(keyword); } + out_name = out_qualifiers.pop(); + out_type = out_qualifiers.pop(); return true; }; @@ -452,8 +456,9 @@ struct GPUSource { func->totparam = 0; int64_t args_cursor = -1; - StringRef arg_qualifier, arg_type, arg_name; - while (arg_parse(func_args, args_cursor, arg_qualifier, arg_type, arg_name)) { + StringRef arg_type, arg_name; + VectorSet<StringRef> arg_qualifiers; + while (arg_parse(func_args, args_cursor, arg_qualifiers, arg_type, arg_name)) { if (func->totparam >= ARRAY_SIZE(func->paramtype)) { print_error(input, source.find(func_name), "Too much parameter in function"); @@ -461,13 +466,25 @@ struct GPUSource { } auto parse_qualifier = [](StringRef qualifier) -> GPUFunctionQual { + if (qualifier == "in") { + return FUNCTION_QUAL_IN; + } if (qualifier == "out") { return FUNCTION_QUAL_OUT; } if (qualifier == "inout") { return FUNCTION_QUAL_INOUT; } - return FUNCTION_QUAL_IN; + if (qualifier == "const") { + return FUNCTION_QUAL_CONST; + } + if (qualifier == "restrict") { + return FUNCTION_QUAL_RESTRICT; + } + if (qualifier == "writeonly") { + return FUNCTION_QUAL_WRITEONLY; + } + return FUNCTION_QUAL_NONE; }; auto parse_type = [](StringRef type) -> eGPUType { @@ -504,10 +521,17 @@ struct GPUSource { if (type == "Closure") { return GPU_CLOSURE; } + if (type == "image2D") { + return GPU_IMAGE_2D; + } return GPU_NONE; }; - func->paramqual[func->totparam] = parse_qualifier(arg_qualifier); + GPUFunctionQual qualifiers = FUNCTION_QUAL_NONE; + for (StringRef qualifier : arg_qualifiers) { + qualifiers = static_cast<GPUFunctionQual>(qualifiers | parse_qualifier(qualifier)); + } + func->paramqual[func->totparam] = qualifiers; func->paramtype[func->totparam] = parse_type(arg_type); if (func->paramtype[func->totparam] == GPU_NONE) { @@ -594,7 +618,8 @@ struct GPUSource { bool is_from_material_library() const { return (filename.startswith("gpu_shader_material_") || - filename.startswith("gpu_shader_common_")) && + filename.startswith("gpu_shader_common_") || + filename.startswith("gpu_shader_compositor_")) && filename.endswith(".glsl"); } }; diff --git a/source/blender/gpu/shaders/common/gpu_shader_common_color_utils.glsl b/source/blender/gpu/shaders/common/gpu_shader_common_color_utils.glsl index fe89985ae7f..33108d3a989 100644 --- a/source/blender/gpu/shaders/common/gpu_shader_common_color_utils.glsl +++ b/source/blender/gpu/shaders/common/gpu_shader_common_color_utils.glsl @@ -140,6 +140,84 @@ void hsl_to_rgb(vec4 hsl, out vec4 outcol) outcol = vec4((nr - 0.5) * chroma + l, (ng - 0.5) * chroma + l, (nb - 0.5) * chroma + l, hsl.w); } +/* ** YCCA to RGBA ** */ + +void ycca_to_rgba_itu_601(vec4 ycca, out vec4 color) +{ + ycca.xyz *= 255.0; + ycca.xyz -= vec3(16.0, 128.0, 128.0); + color.rgb = mat3(vec3(1.164), 0.0, -0.392, 2.017, 1.596, -0.813, 0.0) * ycca.xyz; + color.rgb /= 255.0; + color.a = ycca.a; +} + +void ycca_to_rgba_itu_709(vec4 ycca, out vec4 color) +{ + ycca.xyz *= 255.0; + ycca.xyz -= vec3(16.0, 128.0, 128.0); + color.rgb = mat3(vec3(1.164), 0.0, -0.213, 2.115, 1.793, -0.534, 0.0) * ycca.xyz; + color.rgb /= 255.0; + color.a = ycca.a; +} + +void ycca_to_rgba_jpeg(vec4 ycca, out vec4 color) +{ + ycca.xyz *= 255.0; + color.rgb = mat3(vec3(1.0), 0.0, -0.34414, 1.772, 1.402, -0.71414, 0.0) * ycca.xyz; + color.rgb += vec3(-179.456, 135.45984, -226.816); + color.rgb /= 255.0; + color.a = ycca.a; +} + +/* ** RGBA to YCCA ** */ + +void rgba_to_ycca_itu_601(vec4 rgba, out vec4 ycca) +{ + rgba.rgb *= 255.0; + ycca.xyz = mat3(0.257, -0.148, 0.439, 0.504, -0.291, -0.368, 0.098, 0.439, -0.071) * rgba.rgb; + ycca.xyz += vec3(16.0, 128.0, 128.0); + ycca.xyz /= 255.0; + ycca.a = rgba.a; +} + +void rgba_to_ycca_itu_709(vec4 rgba, out vec4 ycca) +{ + rgba.rgb *= 255.0; + ycca.xyz = mat3(0.183, -0.101, 0.439, 0.614, -0.338, -0.399, 0.062, 0.439, -0.040) * rgba.rgb; + ycca.xyz += vec3(16.0, 128.0, 128.0); + ycca.xyz /= 255.0; + ycca.a = rgba.a; +} + +void rgba_to_ycca_jpeg(vec4 rgba, out vec4 ycca) +{ + rgba.rgb *= 255.0; + ycca.xyz = mat3(0.299, -0.16874, 0.5, 0.587, -0.33126, -0.41869, 0.114, 0.5, -0.08131) * + rgba.rgb; + ycca.xyz += vec3(0.0, 128.0, 128.0); + ycca.xyz /= 255.0; + ycca.a = rgba.a; +} + +/* ** YUVA to RGBA ** */ + +void yuva_to_rgba_itu_709(vec4 yuva, out vec4 color) +{ + color.rgb = mat3(vec3(1.0), 0.0, -0.21482, 2.12798, 1.28033, -0.38059, 0.0) * yuva.xyz; + color.a = yuva.a; +} + +/* ** RGBA to YUVA ** */ + +void rgba_to_yuva_itu_709(vec4 rgba, out vec4 yuva) +{ + yuva.xyz = mat3(0.2126, -0.09991, 0.615, 0.7152, -0.33609, -0.55861, 0.0722, 0.436, -0.05639) * + rgba.rgb; + yuva.a = rgba.a; +} + +/* ** Alpha Handling ** */ + void color_alpha_clear(vec4 color, out vec4 result) { result = vec4(color.rgb, 1.0); @@ -147,15 +225,50 @@ void color_alpha_clear(vec4 color, out vec4 result) void color_alpha_premultiply(vec4 color, out vec4 result) { - result = vec4(color.rgb * color.a, 1.0); + result = vec4(color.rgb * color.a, color.a); } void color_alpha_unpremultiply(vec4 color, out vec4 result) { if (color.a == 0.0 || color.a == 1.0) { - result = vec4(color.rgb, 1.0); + result = color; } else { - result = vec4(color.rgb / color.a, 1.0); + result = vec4(color.rgb / color.a, color.a); + } +} + +float linear_rgb_to_srgb(float color) +{ + if (color < 0.0031308) { + return (color < 0.0) ? 0.0 : color * 12.92; + } + + return 1.055 * pow(color, 1.0 / 2.4) - 0.055; +} + +vec3 linear_rgb_to_srgb(vec3 color) +{ + return vec3( + linear_rgb_to_srgb(color.r), linear_rgb_to_srgb(color.g), linear_rgb_to_srgb(color.b)); +} + +float srgb_to_linear_rgb(float color) +{ + if (color < 0.04045) { + return (color < 0.0) ? 0.0 : color * (1.0 / 12.92); } + + return pow((color + 0.055) * (1.0 / 1.055), 2.4); +} + +vec3 srgb_to_linear_rgb(vec3 color) +{ + return vec3( + srgb_to_linear_rgb(color.r), srgb_to_linear_rgb(color.g), srgb_to_linear_rgb(color.b)); +} + +float get_luminance(vec3 color, vec3 luminance_coefficients) +{ + return dot(color, luminance_coefficients); } diff --git a/source/blender/gpu/shaders/common/gpu_shader_common_curves.glsl b/source/blender/gpu/shaders/common/gpu_shader_common_curves.glsl index 8948ed77557..50bdeb9bbbe 100644 --- a/source/blender/gpu/shaders/common/gpu_shader_common_curves.glsl +++ b/source/blender/gpu/shaders/common/gpu_shader_common_curves.glsl @@ -95,6 +95,81 @@ void curves_combined_only(float factor, result = mix(color, result, factor); } +/* Contrary to standard tone curve implementations, the film-like implementation tries to preserve + * the hue of the colors as much as possible. To understand why this might be a problem, consider + * the violet color (0.5, 0.0, 1.0). If this color was to be evaluated at a power curve x^4, the + * color will be blue (0.0625, 0.0, 1.0). So the color changes and not just its luminosity, which + * is what film-like tone curves tries to avoid. + * + * First, the channels with the lowest and highest values are identified and evaluated at the + * curve. Then, the third channel---the median---is computed while maintaining the original hue of + * the color. To do that, we look at the equation for deriving the hue from RGB values. Assuming + * the maximum, minimum, and median channels are known, and ignoring the 1/3 period offset of the + * hue, the equation is: + * + * hue = (median - min) / (max - min) [1] + * + * Since we have the new values for the minimum and maximum after evaluating at the curve, we also + * have: + * + * hue = (new_median - new_min) / (new_max - new_min) [2] + * + * Since we want the hue to be equivalent, by equating [1] and [2] and rearranging: + * + * (new_median - new_min) / (new_max - new_min) = (median - min) / (max - min) + * new_median - new_min = (new_max - new_min) * (median - min) / (max - min) + * new_median = new_min + (new_max - new_min) * (median - min) / (max - min) + * new_median = new_min + (median - min) * ((new_max - new_min) / (max - min)) [QED] + * + * Which gives us the median color that preserves the hue. More intuitively, the median is computed + * such that the change in the distance from the median to the minimum is proportional to the + * change in the distance from the minimum to the maximum. Finally, each of the new minimum, + * maximum, and median values are written to the color channel that they were originally extracted + * from. */ +void curves_film_like(float factor, + vec4 color, + vec4 black_level, + vec4 white_level, + sampler1DArray curve_map, + const float layer, + float range_minimum, + float range_divider, + float start_slope, + float end_slope, + out vec4 result) +{ + vec4 balanced = white_balance(color, black_level, white_level); + + /* Find the maximum, minimum, and median of the color channels. */ + float minimum = min(balanced.r, min(balanced.g, balanced.b)); + float maximum = max(balanced.r, max(balanced.g, balanced.b)); + float median = max(min(balanced.r, balanced.g), min(balanced.b, max(balanced.r, balanced.g))); + + /* Evaluate alpha curve map at the maximum and minimum channels. The alpha curve is the Combined + * curve in the UI. */ + float min_parameter = NORMALIZE_PARAMETER(minimum, range_minimum, range_divider); + float max_parameter = NORMALIZE_PARAMETER(maximum, range_minimum, range_divider); + float new_min = texture(curve_map, vec2(min_parameter, layer)).a; + float new_max = texture(curve_map, vec2(max_parameter, layer)).a; + + /* Then, extrapolate if needed. */ + new_min = extrapolate_if_needed(min_parameter, new_min, start_slope, end_slope); + new_max = extrapolate_if_needed(max_parameter, new_max, start_slope, end_slope); + + /* Compute the new median using the ratio between the new and the original range. */ + float scaling_ratio = (new_max - new_min) / (maximum - minimum); + float new_median = new_min + (median - minimum) * scaling_ratio; + + /* Write each value to its original channel. */ + bvec3 channel_is_min = equal(balanced.rgb, vec3(minimum)); + vec3 median_or_min = mix(vec3(new_median), vec3(new_min), channel_is_min); + bvec3 channel_is_max = equal(balanced.rgb, vec3(maximum)); + result.rgb = mix(median_or_min, vec3(new_max), channel_is_max); + result.a = color.a; + + result = mix(color, result, factor); +} + void curves_vector(vec3 vector, sampler1DArray curve_map, const float layer, diff --git a/source/blender/gpu/shaders/common/gpu_shader_common_math_utils.glsl b/source/blender/gpu/shaders/common/gpu_shader_common_math_utils.glsl index 124654963fd..1ba22b4c5da 100644 --- a/source/blender/gpu/shaders/common/gpu_shader_common_math_utils.glsl +++ b/source/blender/gpu/shaders/common/gpu_shader_common_math_utils.glsl @@ -34,6 +34,17 @@ float compatible_pow(float x, float y) return pow(x, y); } +/* A version of pow that returns a fallback value if the computation is undefined. From the spec: + * The result is undefined if x < 0 or if x = 0 and y is less than or equal 0. */ +float fallback_pow(float x, float y, float fallback) +{ + if (x < 0.0 || (x == 0.0 && y <= 0.0)) { + return fallback; + } + + return pow(x, y); +} + float wrap(float a, float b, float c) { float range = b - c; @@ -114,8 +125,24 @@ void vector_copy(vec3 normal, out vec3 outnormal) outnormal = normal; } +vec3 fallback_pow(vec3 a, float b, vec3 fallback) +{ + return vec3(fallback_pow(a.x, b, fallback.x), + fallback_pow(a.y, b, fallback.y), + fallback_pow(a.z, b, fallback.z)); +} + /* Matirx Math */ +/* Return a 2D rotation matrix with the angle that the input 2D vector makes with the x axis. */ +mat2 vector_to_rotation_matrix(vec2 vector) +{ + vec2 normalized_vector = normalize(vector); + float cos_angle = normalized_vector.x; + float sin_angle = normalized_vector.y; + return mat2(cos_angle, sin_angle, -sin_angle, cos_angle); +} + mat3 euler_to_mat3(vec3 euler) { float cx = cos(euler.x); diff --git a/source/blender/gpu/shaders/common/gpu_shader_common_mix_rgb.glsl b/source/blender/gpu/shaders/common/gpu_shader_common_mix_rgb.glsl index f9652f1150b..ce709b6882f 100644 --- a/source/blender/gpu/shaders/common/gpu_shader_common_mix_rgb.glsl +++ b/source/blender/gpu/shaders/common/gpu_shader_common_mix_rgb.glsl @@ -278,6 +278,7 @@ void mix_soft(float fac, vec4 col1, vec4 col2, out vec4 outcol) vec4 one = vec4(1.0); vec4 scr = one - (one - col2) * (one - col1); outcol = facm * col1 + fac * ((one - col1) * col2 * col1 + col1 * scr); + outcol.a = col1.a; } void mix_linear(float fac, vec4 col1, vec4 col2, out vec4 outcol) @@ -285,9 +286,15 @@ void mix_linear(float fac, vec4 col1, vec4 col2, out vec4 outcol) fac = clamp(fac, 0.0, 1.0); outcol = col1 + fac * (2.0 * (col2 - vec4(0.5))); + outcol.a = col1.a; } -void clamp_color(vec3 vec, vec3 min, vec3 max, out vec3 out_vec) +void clamp_color(vec4 vec, const vec4 min, const vec4 max, out vec4 out_vec) { out_vec = clamp(vec, min, max); } + +void multiply_by_alpha(float factor, vec4 color, out float result) +{ + result = factor * color.a; +} diff --git a/source/blender/gpu/shaders/compositor/compositor_alpha_crop.glsl b/source/blender/gpu/shaders/compositor/compositor_alpha_crop.glsl new file mode 100644 index 00000000000..23e33b3f354 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_alpha_crop.glsl @@ -0,0 +1,11 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + /* The lower bound is inclusive and upper bound is exclusive. */ + bool is_inside = all(greaterThanEqual(xy, lower_bound)) && all(lessThan(xy, upper_bound)); + /* Write the pixel color if it is inside the cropping region, otherwise, write zero. */ + vec4 color = is_inside ? texture_load(input_image, xy) : vec4(0.0); + imageStore(output_image, xy, color); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_blur.glsl b/source/blender/gpu/shaders/compositor/compositor_blur.glsl new file mode 100644 index 00000000000..074d5071004 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_blur.glsl @@ -0,0 +1,20 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + + const int weights_size = texture_size(weights); + const int blur_size = weights_size / 2; + + vec4 color = vec4(0.0); + for (int i = 0; i < weights_size; i++) { +#if defined(BLUR_HORIZONTAL) + const ivec2 offset = ivec2(i - blur_size, 0); +#elif defined(BLUR_VERTICAL) + const ivec2 offset = ivec2(0, i - blur_size); +#endif + color += texture_load(input_image, xy + offset) * texture_load(weights, i).x; + } + imageStore(output_image, xy, color); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_box_mask.glsl b/source/blender/gpu/shaders/compositor/compositor_box_mask.glsl new file mode 100644 index 00000000000..be874f78911 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_box_mask.glsl @@ -0,0 +1,27 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + + vec2 uv = vec2(xy) / vec2(domain_size - ivec2(1)); + uv -= location; + uv.y *= float(domain_size.y) / float(domain_size.x); + uv = mat2(cos_angle, -sin_angle, sin_angle, cos_angle) * uv; + bool is_inside = all(lessThan(abs(uv), size)); + + float base_mask_value = texture_load(base_mask, xy).x; + float value = texture_load(mask_value, xy).x; + +#if defined(CMP_NODE_MASKTYPE_ADD) + float output_mask_value = is_inside ? max(base_mask_value, value) : base_mask_value; +#elif defined(CMP_NODE_MASKTYPE_SUBTRACT) + float output_mask_value = is_inside ? clamp(base_mask_value - value, 0.0, 1.0) : base_mask_value; +#elif defined(CMP_NODE_MASKTYPE_MULTIPLY) + float output_mask_value = is_inside ? base_mask_value * value : 0.0; +#elif defined(CMP_NODE_MASKTYPE_NOT) + float output_mask_value = is_inside ? (base_mask_value > 0.0 ? 0.0 : value) : base_mask_value; +#endif + + imageStore(output_mask, xy, vec4(output_mask_value)); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_convert.glsl b/source/blender/gpu/shaders/compositor/compositor_convert.glsl new file mode 100644 index 00000000000..ecbdb38da21 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_convert.glsl @@ -0,0 +1,8 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + vec4 texel = texture_load(input_sampler, xy); + imageStore(output_image, xy, CONVERT_EXPRESSION); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_ellipse_mask.glsl b/source/blender/gpu/shaders/compositor/compositor_ellipse_mask.glsl new file mode 100644 index 00000000000..86fb951445f --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_ellipse_mask.glsl @@ -0,0 +1,27 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + + vec2 uv = vec2(xy) / vec2(domain_size - ivec2(1)); + uv -= location; + uv.y *= float(domain_size.y) / float(domain_size.x); + uv = mat2(cos_angle, -sin_angle, sin_angle, cos_angle) * uv; + bool is_inside = length(uv / radius) < 1.0; + + float base_mask_value = texture_load(base_mask, xy).x; + float value = texture_load(mask_value, xy).x; + +#if defined(CMP_NODE_MASKTYPE_ADD) + float output_mask_value = is_inside ? max(base_mask_value, value) : base_mask_value; +#elif defined(CMP_NODE_MASKTYPE_SUBTRACT) + float output_mask_value = is_inside ? clamp(base_mask_value - value, 0.0, 1.0) : base_mask_value; +#elif defined(CMP_NODE_MASKTYPE_MULTIPLY) + float output_mask_value = is_inside ? base_mask_value * value : 0.0; +#elif defined(CMP_NODE_MASKTYPE_NOT) + float output_mask_value = is_inside ? (base_mask_value > 0.0 ? 0.0 : value) : base_mask_value; +#endif + + imageStore(output_mask, xy, vec4(output_mask_value)); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_filter.glsl b/source/blender/gpu/shaders/compositor/compositor_filter.glsl new file mode 100644 index 00000000000..f1bb0fb8bd2 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_filter.glsl @@ -0,0 +1,22 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + + /* Compute the dot product between the kernel and the 3x3 window around the current pixel. */ + vec4 color = vec4(0); + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) { + color += texture_load(input_image, xy + ivec2(i - 1, j - 1)) * kernel[j][i]; + } + } + + /* Mix with the original color at the center of the kernel using the input factor. */ + color = mix(texture_load(input_image, xy), color, texture_load(factor, xy).x); + + /* Make sure the output is never negative. */ + color = max(color, 0.0); + + imageStore(output_image, xy, color); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_flip.glsl b/source/blender/gpu/shaders/compositor/compositor_flip.glsl new file mode 100644 index 00000000000..bf929931e61 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_flip.glsl @@ -0,0 +1,15 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = texture_size(input_image); + ivec2 flipped_xy = xy; +#if defined(FLIP_X) + flipped_xy.x = size.x - xy.x - 1; +#endif +#if defined(FLIP_Y) + flipped_xy.y = size.y - xy.y - 1; +#endif + imageStore(output_image, xy, texture_load(input_image, flipped_xy)); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_image_crop.glsl b/source/blender/gpu/shaders/compositor/compositor_image_crop.glsl new file mode 100644 index 00000000000..ca9b2867db7 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_image_crop.glsl @@ -0,0 +1,7 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + imageStore(output_image, xy, texture_load(input_image, xy + lower_bound)); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_projector_lens_distortion.glsl b/source/blender/gpu/shaders/compositor/compositor_projector_lens_distortion.glsl new file mode 100644 index 00000000000..0c65d798e0a --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_projector_lens_distortion.glsl @@ -0,0 +1,16 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + + /* Get the normalized coordinates of the pixel centers. */ + vec2 normalized_xy = (vec2(xy) + vec2(0.5)) / vec2(texture_size(input_image)); + + /* Sample the red and blue channels shifted by the dispersion amount. */ + const float red = texture(input_image, normalized_xy + vec2(dispersion, 0.0)).r; + const float green = texture_load(input_image, xy).g; + const float blue = texture(input_image, normalized_xy - vec2(dispersion, 0.0)).b; + + imageStore(output_image, xy, vec4(red, green, blue, 1.0)); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_realize_on_domain.glsl b/source/blender/gpu/shaders/compositor/compositor_realize_on_domain.glsl new file mode 100644 index 00000000000..860571db025 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_realize_on_domain.glsl @@ -0,0 +1,25 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + + /* First, transform the input image by transforming the domain coordinates with the inverse of + * input image's transformation. The inverse transformation is an affine matrix and thus the + * coordinates should be in homogeneous coordinates. */ + vec2 coordinates = (mat3(inverse_transformation) * vec3(xy, 1.0)).xy; + + /* Since an input image with an identity transformation is supposed to be centered in the domain, + * we subtract the offset between the lower left corners of the input image and the domain, which + * is half the difference between their sizes, because the difference in size is on both sides of + * the centered image. */ + ivec2 domain_size = imageSize(domain); + ivec2 input_size = texture_size(input_sampler); + vec2 offset = (domain_size - input_size) / 2.0; + + /* Subtract the offset and divide by the input image size to get the relevant coordinates into + * the sampler's expected [0, 1] range. */ + vec2 normalized_coordinates = (coordinates - offset) / input_size; + + imageStore(domain, xy, texture(input_sampler, normalized_coordinates)); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_screen_lens_distortion.glsl b/source/blender/gpu/shaders/compositor/compositor_screen_lens_distortion.glsl new file mode 100644 index 00000000000..a6c1a874546 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_screen_lens_distortion.glsl @@ -0,0 +1,151 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_hash.glsl) +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +/* A model that approximates lens distortion parameterized by a distortion parameter and dependent + * on the squared distance to the center of the image. The distorted pixel is then computed as the + * scalar multiplication of the pixel coordinates with the value returned by this model. See the + * compute_distorted_uv function for more details. */ +float compute_distortion_scale(float distortion, float distance_squared) +{ + return 1.0 / (1.0 + sqrt(1.0 - distortion * distance_squared)); +} + +/* A vectorized version of compute_distortion_scale that is applied on the chromatic distortion + * parameters passed to the shader. */ +vec3 compute_chromatic_distortion_scale(float distance_squared) +{ + return 1.0 / (1.0 + sqrt(1.0 - chromatic_distortion * distance_squared)); +} + +/* Compute the image coordinates after distortion by the given distortion scale computed by the + * compute_distortion_scale function. Note that the function expects centered normalized UV + * coordinates but outputs non-centered image coordinates. */ +vec2 compute_distorted_uv(vec2 uv, float scale) +{ + return (uv * scale + 0.5) * texture_size(input_image) - 0.5; +} + +/* Compute the number of integration steps that should be used to approximate the distorted pixel + * using a heuristic, see the compute_number_of_steps function for more details. The numbers of + * steps is proportional to the number of pixels spanned by the distortion amount. For jitter + * distortion, the square root of the distortion amount plus 1 is used with a minimum of 2 steps. + * For non-jitter distortion, the distortion amount plus 1 is used as the number of steps */ +int compute_number_of_integration_steps_heuristic(float distortion) +{ +#if defined(JITTER) + return distortion < 4.0 ? 2 : int(sqrt(distortion + 1.0)); +#else + return int(distortion + 1.0); +#endif +} + +/* Compute the number of integration steps that should be used to compute each channel of the + * distorted pixel. Each of the channels are distorted by their respective chromatic distortion + * amount, then the amount of distortion between each two consecutive channels is computed, this + * amount is then used to heuristically infer the number of needed integration steps, see the + * integrate_distortion function for more information. */ +ivec3 compute_number_of_integration_steps(vec2 uv, float distance_squared) +{ + /* Distort each channel by its respective chromatic distortion amount. */ + const vec3 distortion_scale = compute_chromatic_distortion_scale(distance_squared); + const vec2 distorted_uv_red = compute_distorted_uv(uv, distortion_scale.r); + const vec2 distorted_uv_green = compute_distorted_uv(uv, distortion_scale.g); + const vec2 distorted_uv_blue = compute_distorted_uv(uv, distortion_scale.b); + + /* Infer the number of needed integration steps to compute the distorted red channel starting + * from the green channel. */ + const float distortion_red = distance(distorted_uv_red, distorted_uv_green); + const int steps_red = compute_number_of_integration_steps_heuristic(distortion_red); + + /* Infer the number of needed integration steps to compute the distorted blue channel starting + * from the green channel. */ + const float distortion_blue = distance(distorted_uv_green, distorted_uv_blue); + const int steps_blue = compute_number_of_integration_steps_heuristic(distortion_blue); + + /* The number of integration steps used to compute the green channel is the sum of both the red + * and the blue channel steps because it is computed once with each of them. */ + return ivec3(steps_red, steps_red + steps_blue, steps_blue); +} + +/* Returns a random jitter amount, which is essentially a random value in the [0, 1] range. If + * jitter is not enabled, return a constant 0.5 value instead. */ +float get_jitter(int seed) +{ +#if defined(JITTER) + return hash_uint3_to_float(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y, seed); +#else + return 0.5; +#endif +} + +/* Each color channel may have a different distortion with the guarantee that the red will have the + * lowest distortion while the blue will have the highest one. If each channel is distorted + * independently, the image will look disintegrated, with each channel seemingly merely shifted. + * Consequently, the distorted pixels needs to be computed by integrating along the path of change + * of distortion starting from one channel to another. For instance, to compute the distorted red + * from the distorted green, we accumulate the color of the distorted pixel starting from the + * distortion of the red, taking small steps until we reach the distortion of the green. The pixel + * color is weighted such that it is maximum at the start distortion and zero at the end distortion + * in an arithmetic progression. The integration steps can be augmented with random values to + * simulate lens jitter. Finally, it should be noted that this function integrates both the start + * and end channels in reverse directions for more efficient computation. */ +vec3 integrate_distortion(int start, int end, float distance_squared, vec2 uv, int steps) +{ + vec3 accumulated_color = vec3(0.0); + const float distortion_amount = chromatic_distortion[end] - chromatic_distortion[start]; + for (int i = 0; i < steps; i++) { + /* The increment will be in the [0, 1) range across iterations. */ + const float increment = (i + get_jitter(i)) / steps; + const float distortion = chromatic_distortion[start] + increment * distortion_amount; + const float distortion_scale = compute_distortion_scale(distortion, distance_squared); + + /* Sample the color at the distorted coordinates and accumulate it weighted by the increment + * value for both the start and end channels. */ + const vec2 distorted_uv = compute_distorted_uv(uv, distortion_scale); + const vec4 color = texture(input_image, distorted_uv / texture_size(input_image)); + accumulated_color[start] += (1.0 - increment) * color[start]; + accumulated_color[end] += increment * color[end]; + } + return accumulated_color; +} + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + + /* Compute the UV image coordinates in the range [-1, 1] as well as the squared distance to the + * center of the image, which is at (0, 0) in the UV coordinates. */ + const vec2 center = texture_size(input_image) / 2.0; + const vec2 uv = scale * (xy + 0.5 - center) / center; + const float distance_squared = dot(uv, uv); + + /* If any of the color channels will get distorted outside of the screen beyond what is possible, + * write a zero transparent color and return. */ + if (any(greaterThan(chromatic_distortion * distance_squared, vec3(1.0)))) { + imageStore(output_image, xy, vec4(0.0)); + return; + } + + /* Compute the number of integration steps that should be used to compute each channel of the + * distorted pixel. */ + const ivec3 number_of_steps = compute_number_of_integration_steps(uv, distance_squared); + + /* Integrate the distortion of the red and green, then the green and blue channels. That means + * the green will be integrated twice, but this is accounted for in the number of steps which the + * color will later be divided by. See the compute_number_of_integration_steps function for more + * details. */ + vec3 color = vec3(0.0); + color += integrate_distortion(0, 1, distance_squared, uv, number_of_steps.r); + color += integrate_distortion(1, 2, distance_squared, uv, number_of_steps.b); + + /* The integration above performed weighted accumulation, and thus the color needs to be divided + * by the sum of the weights. Assuming no jitter, the weights are generated as an arithmetic + * progression starting from (0.5 / n) to ((n - 0.5) / n) for n terms. The sum of an arithmetic + * progression can be computed as (n * (start + end) / 2), which when subsisting the start and + * end reduces to (n / 2). So the color should be multiplied by 2 / n. The jitter sequence + * approximately sums to the same value because it is a uniform random value whose mean value is + * 0.5, so the expression doesn't change regardless of jitter. */ + color *= 2.0 / vec3(number_of_steps); + + imageStore(output_image, xy, vec4(color, 1.0)); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_split_viewer.glsl b/source/blender/gpu/shaders/compositor/compositor_split_viewer.glsl new file mode 100644 index 00000000000..c86b8e462ba --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_split_viewer.glsl @@ -0,0 +1,13 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); +#if defined(SPLIT_HORIZONTAL) + bool condition = (view_size.x * split_ratio) < xy.x; +#elif defined(SPLIT_VERTICAL) + bool condition = (view_size.y * split_ratio) < xy.y; +#endif + vec4 color = condition ? texture_load(first_image, xy) : texture_load(second_image, xy); + imageStore(output_image, xy, color); +} diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_alpha_crop_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_alpha_crop_info.hh new file mode 100644 index 00000000000..5b1c0451c33 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_alpha_crop_info.hh @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_alpha_crop) + .local_group_size(16, 16) + .push_constant(Type::IVEC2, "lower_bound") + .push_constant(Type::IVEC2, "upper_bound") + .sampler(0, ImageType::FLOAT_2D, "input_image") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .compute_source("compositor_alpha_crop.glsl") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_blur_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_blur_info.hh new file mode 100644 index 00000000000..868ce56a54b --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_blur_info.hh @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_blur_shared) + .local_group_size(16, 16) + .sampler(0, ImageType::FLOAT_2D, "input_image") + .sampler(1, ImageType::FLOAT_1D, "weights") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .compute_source("compositor_blur.glsl"); + +GPU_SHADER_CREATE_INFO(compositor_blur_horizontal) + .additional_info("compositor_blur_shared") + .define("BLUR_HORIZONTAL") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_blur_vertical) + .additional_info("compositor_blur_shared") + .define("BLUR_VERTICAL") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_box_mask_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_box_mask_info.hh new file mode 100644 index 00000000000..7b3b3b2d3b5 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_box_mask_info.hh @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_box_mask_shared) + .local_group_size(16, 16) + .push_constant(Type::IVEC2, "domain_size") + .push_constant(Type::VEC2, "location") + .push_constant(Type::VEC2, "size") + .push_constant(Type::FLOAT, "cos_angle") + .push_constant(Type::FLOAT, "sin_angle") + .sampler(0, ImageType::FLOAT_2D, "base_mask") + .sampler(1, ImageType::FLOAT_2D, "mask_value") + .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_mask") + .compute_source("compositor_box_mask.glsl"); + +GPU_SHADER_CREATE_INFO(compositor_box_mask_add) + .additional_info("compositor_box_mask_shared") + .define("CMP_NODE_MASKTYPE_ADD") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_box_mask_subtract) + .additional_info("compositor_box_mask_shared") + .define("CMP_NODE_MASKTYPE_SUBTRACT") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_box_mask_multiply) + .additional_info("compositor_box_mask_shared") + .define("CMP_NODE_MASKTYPE_MULTIPLY") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_box_mask_not) + .additional_info("compositor_box_mask_shared") + .define("CMP_NODE_MASKTYPE_NOT") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_convert_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_convert_info.hh new file mode 100644 index 00000000000..17599f142cb --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_convert_info.hh @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_convert_shared) + .local_group_size(16, 16) + .sampler(0, ImageType::FLOAT_2D, "input_sampler") + .typedef_source("gpu_shader_compositor_type_conversion.glsl") + .compute_source("compositor_convert.glsl"); + +GPU_SHADER_CREATE_INFO(compositor_convert_float_to_vector) + .additional_info("compositor_convert_shared") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .define("CONVERT_EXPRESSION", "vec4(vec3_from_float(texel.x), 0.0)") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_convert_float_to_color) + .additional_info("compositor_convert_shared") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .define("CONVERT_EXPRESSION", "vec4_from_float(texel.x)") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_convert_color_to_float) + .additional_info("compositor_convert_shared") + .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .define("CONVERT_EXPRESSION", "vec4(float_from_vec4(texel), vec3(0.0))") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_convert_color_to_vector) + .additional_info("compositor_convert_shared") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .define("CONVERT_EXPRESSION", "vec4(vec3_from_vec4(texel), 0.0)") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_convert_vector_to_float) + .additional_info("compositor_convert_shared") + .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .define("CONVERT_EXPRESSION", "vec4(float_from_vec3(texel.xyz), vec3(0.0))") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_convert_vector_to_color) + .additional_info("compositor_convert_shared") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .define("CONVERT_EXPRESSION", "vec4_from_vec3(texel.xyz)") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_convert_color_to_alpha) + .additional_info("compositor_convert_shared") + .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .define("CONVERT_EXPRESSION", "vec4(texel.a, vec3(0.0))") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_convert_color_to_half_color) + .additional_info("compositor_convert_shared") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .define("CONVERT_EXPRESSION", "texel") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_convert_float_to_half_float) + .additional_info("compositor_convert_shared") + .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .define("CONVERT_EXPRESSION", "vec4(texel.r, vec3(0.0))") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_ellipse_mask_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_ellipse_mask_info.hh new file mode 100644 index 00000000000..dbb1e2a240a --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_ellipse_mask_info.hh @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_ellipse_mask_shared) + .local_group_size(16, 16) + .push_constant(Type::IVEC2, "domain_size") + .push_constant(Type::VEC2, "location") + .push_constant(Type::VEC2, "radius") + .push_constant(Type::FLOAT, "cos_angle") + .push_constant(Type::FLOAT, "sin_angle") + .sampler(0, ImageType::FLOAT_2D, "base_mask") + .sampler(1, ImageType::FLOAT_2D, "mask_value") + .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_mask") + .compute_source("compositor_ellipse_mask.glsl"); + +GPU_SHADER_CREATE_INFO(compositor_ellipse_mask_add) + .additional_info("compositor_ellipse_mask_shared") + .define("CMP_NODE_MASKTYPE_ADD") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_ellipse_mask_subtract) + .additional_info("compositor_ellipse_mask_shared") + .define("CMP_NODE_MASKTYPE_SUBTRACT") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_ellipse_mask_multiply) + .additional_info("compositor_ellipse_mask_shared") + .define("CMP_NODE_MASKTYPE_MULTIPLY") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_ellipse_mask_not) + .additional_info("compositor_ellipse_mask_shared") + .define("CMP_NODE_MASKTYPE_NOT") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_filter_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_filter_info.hh new file mode 100644 index 00000000000..9c3e075c965 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_filter_info.hh @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_filter) + .local_group_size(16, 16) + .push_constant(Type::MAT4, "kernel") + .sampler(0, ImageType::FLOAT_2D, "input_image") + .sampler(1, ImageType::FLOAT_2D, "factor") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .compute_source("compositor_filter.glsl") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_flip_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_flip_info.hh new file mode 100644 index 00000000000..0f27c5b1676 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_flip_info.hh @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_flip_shared) + .local_group_size(16, 16) + .sampler(0, ImageType::FLOAT_2D, "input_image") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .compute_source("compositor_flip.glsl"); + +GPU_SHADER_CREATE_INFO(compositor_flip_x) + .additional_info("compositor_flip_shared") + .define("FLIP_X") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_flip_y) + .additional_info("compositor_flip_shared") + .define("FLIP_Y") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_flip_x_and_y) + .additional_info("compositor_flip_shared") + .define("FLIP_X") + .define("FLIP_Y") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_image_crop_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_image_crop_info.hh new file mode 100644 index 00000000000..381075a8944 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_image_crop_info.hh @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_image_crop) + .local_group_size(16, 16) + .push_constant(Type::IVEC2, "lower_bound") + .sampler(0, ImageType::FLOAT_2D, "input_image") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .compute_source("compositor_image_crop.glsl") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_projector_lens_distortion_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_projector_lens_distortion_info.hh new file mode 100644 index 00000000000..938b3cf1959 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_projector_lens_distortion_info.hh @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_projector_lens_distortion) + .local_group_size(16, 16) + .push_constant(Type::FLOAT, "dispersion") + .sampler(0, ImageType::FLOAT_2D, "input_image") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .compute_source("compositor_projector_lens_distortion.glsl") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_realize_on_domain_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_realize_on_domain_info.hh new file mode 100644 index 00000000000..249a0cd974c --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_realize_on_domain_info.hh @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_realize_on_domain_shared) + .local_group_size(16, 16) + .push_constant(Type::MAT4, "inverse_transformation") + .sampler(0, ImageType::FLOAT_2D, "input_sampler") + .compute_source("compositor_realize_on_domain.glsl"); + +GPU_SHADER_CREATE_INFO(compositor_realize_on_domain_color) + .additional_info("compositor_realize_on_domain_shared") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "domain") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_realize_on_domain_vector) + .additional_info("compositor_realize_on_domain_shared") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "domain") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_realize_on_domain_float) + .additional_info("compositor_realize_on_domain_shared") + .image(0, GPU_R16F, Qualifier::WRITE, ImageType::FLOAT_2D, "domain") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_screen_lens_distortion_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_screen_lens_distortion_info.hh new file mode 100644 index 00000000000..240ed5c32c7 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_screen_lens_distortion_info.hh @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_screen_lens_distortion_shared) + .local_group_size(16, 16) + .push_constant(Type::VEC3, "chromatic_distortion") + .push_constant(Type::FLOAT, "scale") + .sampler(0, ImageType::FLOAT_2D, "input_image") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .compute_source("compositor_screen_lens_distortion.glsl"); + +GPU_SHADER_CREATE_INFO(compositor_screen_lens_distortion) + .additional_info("compositor_screen_lens_distortion_shared") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_screen_lens_distortion_jitter) + .additional_info("compositor_screen_lens_distortion_shared") + .define("JITTER") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_split_viewer_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_split_viewer_info.hh new file mode 100644 index 00000000000..0d8f178ebd7 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_split_viewer_info.hh @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_split_viewer_shared) + .local_group_size(16, 16) + .push_constant(Type::FLOAT, "split_ratio") + .push_constant(Type::IVEC2, "view_size") + .sampler(0, ImageType::FLOAT_2D, "first_image") + .sampler(1, ImageType::FLOAT_2D, "second_image") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .compute_source("compositor_split_viewer.glsl"); + +GPU_SHADER_CREATE_INFO(compositor_split_viewer_horizontal) + .additional_info("compositor_split_viewer_shared") + .define("SPLIT_HORIZONTAL") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_split_viewer_vertical) + .additional_info("compositor_split_viewer_shared") + .define("SPLIT_VERTICAL") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_alpha_over.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_alpha_over.glsl new file mode 100644 index 00000000000..99483c89039 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_alpha_over.glsl @@ -0,0 +1,51 @@ +void node_composite_alpha_over_mixed( + float factor, vec4 color, vec4 over_color, float premultiply_factor, out vec4 result) +{ + if (over_color.a <= 0.0) { + result = color; + } + else if (factor == 1.0 && over_color.a >= 1.0) { + result = over_color; + } + else { + float add_factor = 1.0 - premultiply_factor + over_color.a * premultiply_factor; + float premultiplier = factor * add_factor; + float multiplier = 1.0 - factor * over_color.a; + + result = multiplier * color + vec2(premultiplier, factor).xxxy * over_color; + } +} + +void node_composite_alpha_over_key(float factor, vec4 color, vec4 over_color, out vec4 result) +{ + if (over_color.a <= 0.0) { + result = color; + } + else if (factor == 1.0 && over_color.a >= 1.0) { + result = over_color; + } + else { + float premultiplier = factor * over_color.a; + + result.rgb = mix(color.rgb, over_color.rgb, premultiplier); + result.a = (1.0 - premultiplier) * color.a + factor * over_color.a; + } +} + +void node_composite_alpha_over_premultiply(float factor, + vec4 color, + vec4 over_color, + out vec4 result) +{ + if (over_color.a < 0.0) { + result = color; + } + else if (factor == 1.0 && over_color.a >= 1.0) { + result = over_color; + } + else { + float multiplier = 1.0 - factor * over_color.a; + + result = multiplier * color + factor * over_color; + } +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_bright_contrast.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_bright_contrast.glsl new file mode 100644 index 00000000000..ce71b4fd8a4 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_bright_contrast.glsl @@ -0,0 +1,38 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +/* The algorithm is by Werner D. Streidt + * (http://visca.com/ffactory/archives/5-99/msg00021.html) + * Extracted of OpenCV demhist.c + */ + +#define FLT_EPSILON 1.192092896e-07F + +void node_composite_bright_contrast( + vec4 color, float brightness, float contrast, const float use_premultiply, out vec4 result) +{ + brightness /= 100.0; + float delta = contrast / 200.0; + + float multiplier, offset; + if (contrast > 0.0) { + multiplier = 1.0 - delta * 2.0; + multiplier = 1.0 / max(multiplier, FLT_EPSILON); + offset = multiplier * (brightness - delta); + } + else { + delta *= -1.0; + multiplier = max(1.0 - delta * 2.0, 0.0); + offset = multiplier * brightness + delta; + } + + if (use_premultiply != 0.0) { + color_alpha_unpremultiply(color, color); + } + + result.rgb = color.rgb * multiplier + offset; + result.a = color.a; + + if (use_premultiply != 0.0) { + color_alpha_premultiply(result, result); + } +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_channel_matte.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_channel_matte.glsl new file mode 100644 index 00000000000..f2dcc9543f2 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_channel_matte.glsl @@ -0,0 +1,52 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +#define CMP_NODE_CHANNEL_MATTE_CS_RGB 1.0 +#define CMP_NODE_CHANNEL_MATTE_CS_HSV 2.0 +#define CMP_NODE_CHANNEL_MATTE_CS_YUV 3.0 +#define CMP_NODE_CHANNEL_MATTE_CS_YCC 4.0 + +void node_composite_channel_matte(vec4 color, + const float color_space, + const float matte_channel, + const vec2 limit_channels, + float max_limit, + float min_limit, + out vec4 result, + out float matte) +{ + vec4 channels; + if (color_space == CMP_NODE_CHANNEL_MATTE_CS_HSV) { + rgb_to_hsv(color, channels); + } + else if (color_space == CMP_NODE_CHANNEL_MATTE_CS_YUV) { + rgba_to_yuva_itu_709(color, channels); + } + else if (color_space == CMP_NODE_CHANNEL_MATTE_CS_YCC) { + rgba_to_ycca_itu_709(color, channels); + } + else { + channels = color; + } + + float matte_value = channels[int(matte_channel)]; + float limit_value = max(channels[int(limit_channels.x)], channels[int(limit_channels.y)]); + + float alpha = 1.0 - (matte_value - limit_value); + if (alpha > max_limit) { + alpha = color.a; + } + else if (alpha < min_limit) { + alpha = 0.0; + } + else { + alpha = (alpha - min_limit) / (max_limit - min_limit); + } + + matte = min(alpha, color.a); + result = color * matte; +} + +#undef CMP_NODE_CHANNEL_MATTE_CS_RGB +#undef CMP_NODE_CHANNEL_MATTE_CS_HSV +#undef CMP_NODE_CHANNEL_MATTE_CS_YUV +#undef CMP_NODE_CHANNEL_MATTE_CS_YCC diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_chroma_matte.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_chroma_matte.glsl new file mode 100644 index 00000000000..3b5f1fb6623 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_chroma_matte.glsl @@ -0,0 +1,42 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl) + +/* Algorithm from the book Video Demystified. Chapter 7. Chroma Keying. */ +void node_composite_chroma_matte(vec4 color, + vec4 key, + float acceptance, + float cutoff, + float falloff, + out vec4 result, + out float matte) +{ + vec4 color_ycca; + rgba_to_ycca_itu_709(color, color_ycca); + vec4 key_ycca; + rgba_to_ycca_itu_709(key, key_ycca); + + /* Normalize the CrCb components into the [-1, 1] range. */ + vec2 color_cc = color_ycca.yz * 2.0 - 1.0; + vec2 key_cc = key_ycca.yz * 2.0 - 1.0; + + /* Rotate the color onto the space of the key such that x axis of the color space passes through + * the key color. */ + color_cc = vector_to_rotation_matrix(key_cc * vec2(1.0, -1.0)) * color_cc; + + /* Compute foreground key. If positive, the value is in the [0, 1] range. */ + float foreground_key = color_cc.x - (abs(color_cc.y) / acceptance); + + /* Negative foreground key values retain the original alpha. Positive values are scaled by the + * falloff, while colors that make an angle less than the cutoff angle get a zero alpha. */ + float alpha = color.a; + if (foreground_key > 0.0) { + alpha = 1.0 - (foreground_key / falloff); + + if (abs(atan(color_cc.y, color_cc.x)) < (cutoff / 2.0)) { + alpha = 0.0; + } + } + + /* Compute output. */ + matte = min(alpha, color.a); + result = color * matte; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_balance.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_balance.glsl new file mode 100644 index 00000000000..bffb94cdedb --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_balance.glsl @@ -0,0 +1,34 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +void node_composite_color_balance_lgg( + float factor, vec4 color, vec3 lift, vec3 gamma, vec3 gain, out vec4 result) +{ + lift = 2.0 - lift; + vec3 srgb_color = linear_rgb_to_srgb(color.rgb); + vec3 lift_balanced = ((srgb_color - 1.0) * lift) + 1.0; + + vec3 gain_balanced = lift_balanced * gain; + gain_balanced = max(gain_balanced, vec3(0.0)); + + vec3 linear_color = srgb_to_linear_rgb(gain_balanced); + gamma = mix(gamma, vec3(1e-6), equal(gamma, vec3(0.0))); + vec3 gamma_balanced = pow(linear_color, 1.0 / gamma); + + result.rgb = mix(color.rgb, gamma_balanced, min(factor, 1.0)); + result.a = color.a; +} + +void node_composite_color_balance_asc_cdl(float factor, + vec4 color, + vec3 offset, + vec3 power, + vec3 slope, + float offset_basis, + out vec4 result) +{ + offset += offset_basis; + vec3 balanced = color.rgb * slope + offset; + balanced = pow(max(balanced, vec3(0.0)), power); + result.rgb = mix(color.rgb, balanced, min(factor, 1.0)); + result.a = color.a; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_correction.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_correction.glsl new file mode 100644 index 00000000000..9b4858f03be --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_correction.glsl @@ -0,0 +1,87 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl) +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +void node_composite_color_correction(vec4 color, + float mask, + const vec3 enabled_channels, + float start_midtones, + float end_midtones, + float master_saturation, + float master_contrast, + float master_gamma, + float master_gain, + float master_lift, + float shadows_saturation, + float shadows_contrast, + float shadows_gamma, + float shadows_gain, + float shadows_lift, + float midtones_saturation, + float midtones_contrast, + float midtones_gamma, + float midtones_gain, + float midtones_lift, + float highlights_saturation, + float highlights_contrast, + float highlights_gamma, + float highlights_gain, + float highlights_lift, + const vec3 luminance_coefficients, + out vec4 result) +{ + const float margin = 0.10; + const float margin_divider = 0.5 / margin; + float level = (color.r + color.g + color.b) / 3.0; + float level_shadows = 0.0; + float level_midtones = 0.0; + float level_highlights = 0.0; + if (level < (start_midtones - margin)) { + level_shadows = 1.0; + } + else if (level < (start_midtones + margin)) { + level_midtones = ((level - start_midtones) * margin_divider) + 0.5; + level_shadows = 1.0 - level_midtones; + } + else if (level < (end_midtones - margin)) { + level_midtones = 1.0; + } + else if (level < (end_midtones + margin)) { + level_highlights = ((level - end_midtones) * margin_divider) + 0.5; + level_midtones = 1.0 - level_highlights; + } + else { + level_highlights = 1.0; + } + + float contrast = level_shadows * shadows_contrast; + contrast += level_midtones * midtones_contrast; + contrast += level_highlights * highlights_contrast; + contrast *= master_contrast; + float saturation = level_shadows * shadows_saturation; + saturation += level_midtones * midtones_saturation; + saturation += level_highlights * highlights_saturation; + saturation *= master_saturation; + float gamma = level_shadows * shadows_gamma; + gamma += level_midtones * midtones_gamma; + gamma += level_highlights * highlights_gamma; + gamma *= master_gamma; + float gain = level_shadows * shadows_gain; + gain += level_midtones * midtones_gain; + gain += level_highlights * highlights_gain; + gain *= master_gain; + float lift = level_shadows * shadows_lift; + lift += level_midtones * midtones_lift; + lift += level_highlights * highlights_lift; + lift += master_lift; + + float inverse_gamma = 1.0 / gamma; + float luma = get_luminance(color.rgb, luminance_coefficients); + + vec3 corrected = luma + saturation * (color.rgb - luma); + corrected = 0.5 + (corrected - 0.5) * contrast; + corrected = fallback_pow(corrected * gain + lift, inverse_gamma, corrected); + corrected = mix(color.rgb, corrected, min(mask, 1.0)); + + result.rgb = mix(corrected, color.rgb, equal(enabled_channels, vec3(0.0))); + result.a = color.a; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_matte.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_matte.glsl new file mode 100644 index 00000000000..038471bc1bc --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_matte.glsl @@ -0,0 +1,27 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +void node_composite_color_matte(vec4 color, + vec4 key, + float hue_epsilon, + float saturation_epsilon, + float value_epsilon, + out vec4 result, + out float matte) + +{ + vec4 color_hsva; + rgb_to_hsv(color, color_hsva); + vec4 key_hsva; + rgb_to_hsv(key, key_hsva); + + bool is_within_saturation = distance(color_hsva.y, key_hsva.y) < saturation_epsilon; + bool is_within_value = distance(color_hsva.z, key_hsva.z) < value_epsilon; + bool is_within_hue = distance(color_hsva.x, key_hsva.x) < hue_epsilon; + /* Hue wraps around, so check the distance around the boundary. */ + float min_hue = min(color_hsva.x, key_hsva.x); + float max_hue = max(color_hsva.x, key_hsva.x); + is_within_hue = is_within_hue || ((min_hue + (1.0 - max_hue)) < hue_epsilon); + + matte = (is_within_hue && is_within_saturation && is_within_value) ? 0.0 : color.a; + result = color * matte; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_spill.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_spill.glsl new file mode 100644 index 00000000000..3a747b6972d --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_spill.glsl @@ -0,0 +1,13 @@ +void node_composite_color_spill(vec4 color, + float factor, + float spill_channel, + vec3 spill_scale, + vec2 limit_channels, + float limit_scale, + out vec4 result) +{ + float average_limit = (color[int(limit_channels.x)] + color[int(limit_channels.y)]) / 2.0; + float map = factor * color[int(spill_channel)] - limit_scale * average_limit; + result.rgb = map > 0.0 ? color.rgb + spill_scale * map : color.rgb; + result.a = color.a; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_to_luminance.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_to_luminance.glsl new file mode 100644 index 00000000000..bcdd625bd4f --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_color_to_luminance.glsl @@ -0,0 +1,6 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +void color_to_luminance(vec4 color, const vec3 luminance_coefficients, out float result) +{ + result = get_luminance(color.rgb, luminance_coefficients); +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_difference_matte.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_difference_matte.glsl new file mode 100644 index 00000000000..d769cadce3c --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_difference_matte.glsl @@ -0,0 +1,10 @@ +void node_composite_difference_matte( + vec4 color, vec4 key, float tolerance, float falloff, out vec4 result, out float matte) +{ + vec4 difference = abs(color - key); + float average_difference = (difference.r + difference.g + difference.b) / 3.0; + bool is_opaque = average_difference > tolerance + falloff; + float alpha = is_opaque ? color.a : (max(0.0, average_difference - tolerance) / falloff); + matte = min(alpha, color.a); + result = color * matte; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_distance_matte.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_distance_matte.glsl new file mode 100644 index 00000000000..9beed66826c --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_distance_matte.glsl @@ -0,0 +1,26 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +void node_composite_distance_matte_rgba( + vec4 color, vec4 key, float tolerance, float falloff, out vec4 result, out float matte) +{ + float difference = distance(color.rgb, key.rgb); + bool is_opaque = difference > tolerance + falloff; + float alpha = is_opaque ? color.a : max(0.0, difference - tolerance) / falloff; + matte = min(alpha, color.a); + result = color * matte; +} + +void node_composite_distance_matte_ycca( + vec4 color, vec4 key, float tolerance, float falloff, out vec4 result, out float matte) +{ + vec4 color_ycca; + rgba_to_ycca_itu_709(color, color_ycca); + vec4 key_ycca; + rgba_to_ycca_itu_709(key, key_ycca); + + float difference = distance(color_ycca.yz, key_ycca.yz); + bool is_opaque = difference > tolerance + falloff; + float alpha = is_opaque ? color.a : max(0.0, difference - tolerance) / falloff; + matte = min(alpha, color.a); + result = color * matte; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_exposure.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_exposure.glsl new file mode 100644 index 00000000000..206fd25cd4b --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_exposure.glsl @@ -0,0 +1,6 @@ +void node_composite_exposure(vec4 color, float exposure, out vec4 result) +{ + float multiplier = pow(2.0, exposure); + result.rgb = color.rgb * multiplier; + result.a = color.a; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_gamma.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_gamma.glsl new file mode 100644 index 00000000000..53070d4b0e2 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_gamma.glsl @@ -0,0 +1,7 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl) + +void node_composite_gamma(vec4 color, float gamma, out vec4 result) +{ + result.rgb = fallback_pow(color.rgb, gamma, color.rgb); + result.a = color.a; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_hue_correct.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_hue_correct.glsl new file mode 100644 index 00000000000..ae06a1935af --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_hue_correct.glsl @@ -0,0 +1,26 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +void node_composite_hue_correct(float factor, + vec4 color, + sampler1DArray curve_map, + const float layer, + vec3 minimums, + vec3 range_dividers, + out vec4 result) +{ + vec4 hsv; + rgb_to_hsv(color, hsv); + vec3 parameters = (hsv.x - minimums) * range_dividers; + + /* A value of 0.5 means no change, so adjust to get an identity at 0.5. */ + hsv.x += texture(curve_map, vec2(parameters.x, layer)).x - 0.5; + hsv.y *= texture(curve_map, vec2(parameters.y, layer)).y * 2.0; + hsv.z *= texture(curve_map, vec2(parameters.z, layer)).z * 2.0; + + hsv.x = fract(hsv.x); + hsv.y = clamp(hsv.y, 0.0, 1.0); + + hsv_to_rgb(hsv, result); + + result = mix(color, result, factor); +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_hue_saturation_value.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_hue_saturation_value.glsl new file mode 100644 index 00000000000..dd5eb33d318 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_hue_saturation_value.glsl @@ -0,0 +1,16 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +void node_composite_hue_saturation_value( + vec4 color, float hue, float saturation, float value, float factor, out vec4 result) +{ + vec4 hsv; + rgb_to_hsv(color, hsv); + + hsv.x = fract(hsv.x + hue + 0.5); + hsv.y = clamp(hsv.y * saturation, 0.0, 1.0); + hsv.z = hsv.z * value; + + hsv_to_rgb(hsv, result); + + result = mix(color, result, factor); +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_invert.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_invert.glsl new file mode 100644 index 00000000000..59be746da7f --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_invert.glsl @@ -0,0 +1,13 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +void node_composite_invert(float fac, vec4 color, float do_rgb, float do_alpha, out vec4 result) +{ + result = color; + if (do_rgb != 0.0) { + result.rgb = 1.0 - result.rgb; + } + if (do_alpha != 0.0) { + result.a = 1.0 - result.a; + } + result = mix(color, result, fac); +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_load_input.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_load_input.glsl new file mode 100644 index 00000000000..29402ef523a --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_load_input.glsl @@ -0,0 +1,19 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void load_input_float(sampler2D input_sampler, out float value) +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + value = texture_load(input_sampler, xy).x; +} + +void load_input_vector(sampler2D input_sampler, out vec3 vector) +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + vector = texture_load(input_sampler, xy).xyz; +} + +void load_input_color(sampler2D input_sampler, out vec4 color) +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + color = texture_load(input_sampler, xy); +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_luminance_matte.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_luminance_matte.glsl new file mode 100644 index 00000000000..3647ac583fe --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_luminance_matte.glsl @@ -0,0 +1,14 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +void node_composite_luminance_matte(vec4 color, + float high, + float low, + const vec3 luminance_coefficients, + out vec4 result, + out float matte) +{ + float luminance = get_luminance(color.rgb, luminance_coefficients); + float alpha = clamp(0.0, 1.0, (luminance - low) / (high - low)); + matte = min(alpha, color.a); + result = color * matte; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_map_value.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_map_value.glsl new file mode 100644 index 00000000000..20874b4ef44 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_map_value.glsl @@ -0,0 +1,56 @@ +/* An arbitrary value determined by Blender. */ +#define BLENDER_ZMAX 10000.0 + +void node_composite_map_range(float value, + float from_min, + float from_max, + float to_min, + float to_max, + const float should_clamp, + out float result) +{ + if (abs(from_max - from_min) < 1e-6) { + result = 0.0; + } + else { + if (value >= -BLENDER_ZMAX && value <= BLENDER_ZMAX) { + result = (value - from_min) / (from_max - from_min); + result = to_min + result * (to_max - to_min); + } + else if (value > BLENDER_ZMAX) { + result = to_max; + } + else { + result = to_min; + } + + if (should_clamp != 0.0) { + if (to_max > to_min) { + result = clamp(result, to_min, to_max); + } + else { + result = clamp(result, to_max, to_min); + } + } + } +} + +void node_composite_map_value(float value, + float offset, + float size, + const float use_min, + float min, + const float use_max, + float max, + out float result) +{ + result = (value + offset) * size; + + if (use_min != 0.0 && result < min) { + result = min; + } + + if (use_max != 0.0 && result > max) { + result = max; + } +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_normal.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_normal.glsl new file mode 100644 index 00000000000..a2e3b6c4aaa --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_normal.glsl @@ -0,0 +1,9 @@ +void node_composite_normal(vec3 input_vector, + vec3 input_normal, + out vec3 result_normal, + out float result_dot) +{ + vec3 normal = normalize(input_normal); + result_normal = normal; + result_dot = -dot(input_vector, normal); +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_posterize.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_posterize.glsl new file mode 100644 index 00000000000..ee8ae234abe --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_posterize.glsl @@ -0,0 +1,6 @@ +void node_composite_posterize(vec4 color, float steps, out vec4 result) +{ + steps = clamp(steps, 2.0, 1024.0); + result = floor(color * steps) / steps; + result.a = color.a; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_separate_combine.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_separate_combine.glsl new file mode 100644 index 00000000000..e8585f9eed3 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_separate_combine.glsl @@ -0,0 +1,115 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_color_utils.glsl) + +/* ** Combine/Separate XYZ ** */ + +void node_composite_combine_xyz(float x, float y, float z, out vec3 vector) +{ + vector = vec3(x, y, z); +} + +void node_composite_separate_xyz(vec3 vector, out float x, out float y, out float z) +{ + x = vector.x; + y = vector.y; + z = vector.z; +} + +/* ** Combine/Separate RGBA ** */ + +void node_composite_combine_rgba(float r, float g, float b, float a, out vec4 color) +{ + color = vec4(r, g, b, a); +} + +void node_composite_separate_rgba(vec4 color, out float r, out float g, out float b, out float a) +{ + r = color.r; + g = color.g; + b = color.b; + a = color.a; +} + +/* ** Combine/Separate HSVA ** */ + +void node_composite_combine_hsva(float h, float s, float v, float a, out vec4 color) +{ + hsv_to_rgb(vec4(h, s, v, a), color); +} + +void node_composite_separate_hsva(vec4 color, out float h, out float s, out float v, out float a) +{ + vec4 hsva; + rgb_to_hsv(color, hsva); + h = hsva.x; + s = hsva.y; + v = hsva.z; + a = hsva.a; +} + +/* ** Combine/Separate YCCA ** */ + +void node_composite_combine_ycca_itu_601(float y, float cb, float cr, float a, out vec4 color) +{ + ycca_to_rgba_itu_601(vec4(y, cb, cr, a), color); +} + +void node_composite_combine_ycca_itu_709(float y, float cb, float cr, float a, out vec4 color) +{ + ycca_to_rgba_itu_709(vec4(y, cb, cr, a), color); +} + +void node_composite_combine_ycca_jpeg(float y, float cb, float cr, float a, out vec4 color) +{ + ycca_to_rgba_jpeg(vec4(y, cb, cr, a), color); +} + +void node_composite_separate_ycca_itu_601( + vec4 color, out float y, out float cb, out float cr, out float a) +{ + vec4 ycca; + rgba_to_ycca_itu_601(color, ycca); + y = ycca.x; + cb = ycca.y; + cr = ycca.z; + a = ycca.a; +} + +void node_composite_separate_ycca_itu_709( + vec4 color, out float y, out float cb, out float cr, out float a) +{ + vec4 ycca; + rgba_to_ycca_itu_709(color, ycca); + y = ycca.x; + cb = ycca.y; + cr = ycca.z; + a = ycca.a; +} + +void node_composite_separate_ycca_jpeg( + vec4 color, out float y, out float cb, out float cr, out float a) +{ + vec4 ycca; + rgba_to_ycca_jpeg(color, ycca); + y = ycca.x; + cb = ycca.y; + cr = ycca.z; + a = ycca.a; +} + +/* ** Combine/Separate YUVA ** */ + +void node_composite_combine_yuva_itu_709(float y, float u, float v, float a, out vec4 color) +{ + yuva_to_rgba_itu_709(vec4(y, u, v, a), color); +} + +void node_composite_separate_yuva_itu_709( + vec4 color, out float y, out float u, out float v, out float a) +{ + vec4 yuva; + rgba_to_yuva_itu_709(color, yuva); + y = yuva.x; + u = yuva.y; + v = yuva.z; + a = yuva.a; +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_set_alpha.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_set_alpha.glsl new file mode 100644 index 00000000000..95380d1ed0f --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_set_alpha.glsl @@ -0,0 +1,9 @@ +void node_composite_set_alpha_apply(vec4 color, float alpha, out vec4 result) +{ + result = color * alpha; +} + +void node_composite_set_alpha_replace(vec4 color, float alpha, out vec4 result) +{ + result = vec4(color.rgb, alpha); +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_store_output.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_store_output.glsl new file mode 100644 index 00000000000..b8e797aa82c --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_store_output.glsl @@ -0,0 +1,32 @@ +/* The GLSL specification is not clear about passing images to functions and consequently functions + * with image parameters are not portable across driver implementations or even non-functioning in + * some drivers. See https://github.com/KhronosGroup/GLSL/issues/57. + * To work around this, we use macros instead of functions. However, to make those macros usable in + * the GPU material library, we also define function counterparts that are guarded with #if 0 such + * that they are not used in the shader but are parsed by the GPU shader dependency parser. */ + +#if 0 +void store_output_float(restrict writeonly image2D output_image, float value) +{ + imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), vec4(value)); +} + +void store_output_vector(restrict writeonly image2D output_image, vec3 vector) +{ + imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), vec4(vector, 0.0)); +} + +void store_output_color(restrict writeonly image2D output_image, vec4 color) +{ + imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), color); +} +#else +# define store_output_float(output_image, value) \ + imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), vec4(value)); + +# define store_output_vector(output_image, vector) \ + imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), vec4(vector, 0.0)); + +# define store_output_color(output_image, color) \ + imageStore(output_image, ivec2(gl_GlobalInvocationID.xy), color); +#endif diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_texture_utilities.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_texture_utilities.glsl new file mode 100644 index 00000000000..1a304af85c6 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_texture_utilities.glsl @@ -0,0 +1,25 @@ +/* A shorthand for 1D textureSize with a zero LOD. */ +int texture_size(sampler1D sampler) +{ + return textureSize(sampler, 0); +} + +/* A shorthand for 1D texelFetch with zero LOD and bounded access clamped to border. */ +vec4 texture_load(sampler1D sampler, int x) +{ + const int texture_bound = texture_size(sampler) - 1; + return texelFetch(sampler, clamp(x, 0, texture_bound), 0); +} + +/* A shorthand for 2D textureSize with a zero LOD. */ +ivec2 texture_size(sampler2D sampler) +{ + return textureSize(sampler, 0); +} + +/* A shorthand for 2D texelFetch with zero LOD and bounded access clamped to border. */ +vec4 texture_load(sampler2D sampler, ivec2 xy) +{ + const ivec2 texture_bounds = texture_size(sampler) - ivec2(1); + return texelFetch(sampler, clamp(xy, ivec2(0), texture_bounds), 0); +} diff --git a/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_type_conversion.glsl b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_type_conversion.glsl new file mode 100644 index 00000000000..75c76fd7341 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/library/gpu_shader_compositor_type_conversion.glsl @@ -0,0 +1,29 @@ +float float_from_vec4(vec4 vector) +{ + return dot(vector.rgb, vec3(1.0)) / 3.0; +} + +float float_from_vec3(vec3 vector) +{ + return dot(vector, vec3(1.0)) / 3.0; +} + +vec3 vec3_from_vec4(vec4 vector) +{ + return vector.rgb; +} + +vec3 vec3_from_float(float value) +{ + return vec3(value); +} + +vec4 vec4_from_vec3(vec3 vector) +{ + return vec4(vector, 1.0); +} + +vec4 vec4_from_float(float value) +{ + return vec4(vec3(value), 1.0); +} diff --git a/source/blender/imbuf/IMB_colormanagement.h b/source/blender/imbuf/IMB_colormanagement.h index 2f0d2f9b449..75542774398 100644 --- a/source/blender/imbuf/IMB_colormanagement.h +++ b/source/blender/imbuf/IMB_colormanagement.h @@ -56,6 +56,8 @@ bool IMB_colormanagement_space_name_is_data(const char *name); bool IMB_colormanagement_space_name_is_scene_linear(const char *name); bool IMB_colormanagement_space_name_is_srgb(const char *name); +BLI_INLINE void IMB_colormanagement_get_luminance_coefficients(float rgb[3]); + /** * Convert a float RGB triplet to the correct luminance weighted average. * diff --git a/source/blender/imbuf/intern/colormanagement_inline.c b/source/blender/imbuf/intern/colormanagement_inline.c index 668307ec802..ff8ea8f684e 100644 --- a/source/blender/imbuf/intern/colormanagement_inline.c +++ b/source/blender/imbuf/intern/colormanagement_inline.c @@ -11,6 +11,11 @@ #include "BLI_math_vector.h" #include "IMB_colormanagement_intern.h" +void IMB_colormanagement_get_luminance_coefficients(float rgb[3]) +{ + copy_v3_v3(rgb, imbuf_luma_coefficients); +} + float IMB_colormanagement_get_luminance(const float rgb[3]) { return dot_v3v3(imbuf_luma_coefficients, rgb); diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index e449605ed81..63594d55183 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -653,6 +653,8 @@ typedef struct UserDef_Experimental { char enable_eevee_next; char use_sculpt_texture_paint; char use_draw_manager_acquire_lock; + char use_realtime_compositor; + char _pad[7]; /** `makesdna` does not allow empty structs. */ } UserDef_Experimental; diff --git a/source/blender/makesdna/DNA_view3d_types.h b/source/blender/makesdna/DNA_view3d_types.h index 8554d070dc3..0d281032b7e 100644 --- a/source/blender/makesdna/DNA_view3d_types.h +++ b/source/blender/makesdna/DNA_view3d_types.h @@ -486,6 +486,7 @@ enum { V3D_SHADING_SCENE_LIGHTS_RENDER = (1 << 12), V3D_SHADING_SCENE_WORLD_RENDER = (1 << 13), V3D_SHADING_STUDIOLIGHT_VIEW_ROTATION = (1 << 14), + V3D_SHADING_COMPOSITOR = (1 << 15), }; #define V3D_USES_SCENE_LIGHTS(v3d) \ diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index cae86801402..b87c39f2c3c 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -4192,6 +4192,14 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Shader AOV Name", "Name of the active Shader AOV"); RNA_def_property_flag(prop, PROP_HIDDEN); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL); + + prop = RNA_def_property(srna, "use_compositor", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_COMPOSITOR); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_boolean_default(prop, false); + RNA_def_property_ui_text( + prop, "Use Compositor", "Preview the compositor output inside the viewport in realtime"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL); } static void rna_def_space_view3d_overlay(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 43e8879fc17..95dfcedbc03 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -6420,6 +6420,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Sculpt Mode Tilt Support", "Support for pen tablet tilt events in Sculpt Mode"); + prop = RNA_def_property(srna, "use_realtime_compositor", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "use_realtime_compositor", 1); + RNA_def_property_ui_text(prop, "Realtime Compositor", "Enable the new realtime compositor"); + prop = RNA_def_property(srna, "use_sculpt_texture_paint", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "use_sculpt_texture_paint", 1); RNA_def_property_ui_text(prop, "Sculpt Texture Paint", "Use texture painting in Sculpt Mode"); diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh index 4e78f6c1142..d8b8c354230 100644 --- a/source/blender/nodes/NOD_node_declaration.hh +++ b/source/blender/nodes/NOD_node_declaration.hh @@ -88,6 +88,14 @@ class SocketDeclaration { InputSocketFieldType input_field_type_ = InputSocketFieldType::None; OutputFieldDependency output_field_dependency_; + /** The priority of the input for determining the domain of the node. See + * realtime_compositor::InputDescriptor for more information. */ + int compositor_domain_priority_ = 0; + + /** This input expects a single value and can't operate on non-single values. See + * realtime_compositor::InputDescriptor for more information. */ + bool compositor_expects_single_value_ = false; + /** Utility method to make the socket available if there is a straightforward way to do so. */ std::function<void(bNode &)> make_available_fn_; @@ -124,6 +132,9 @@ class SocketDeclaration { InputSocketFieldType input_field_type() const; const OutputFieldDependency &output_field_dependency() const; + int compositor_domain_priority() const; + bool compositor_expects_single_value() const; + protected: void set_common_flags(bNodeSocket &socket) const; bool matches_common_data(const bNodeSocket &socket) const; @@ -238,6 +249,22 @@ class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder { return *(Self *)this; } + /** The priority of the input for determining the domain of the node. See + * realtime_compositor::InputDescriptor for more information. */ + Self &compositor_domain_priority(int priority) + { + decl_->compositor_domain_priority_ = priority; + return *(Self *)this; + } + + /** This input expects a single value and can't operate on non-single values. See + * realtime_compositor::InputDescriptor for more information. */ + Self &compositor_expects_single_value(bool value = true) + { + decl_->compositor_expects_single_value_ = value; + return *(Self *)this; + } + /** * Pass a function that sets properties on the node required to make the corresponding socket * available, if it is not available on the default state of the node. The function is allowed to @@ -428,6 +455,16 @@ inline const OutputFieldDependency &SocketDeclaration::output_field_dependency() return output_field_dependency_; } +inline int SocketDeclaration::compositor_domain_priority() const +{ + return compositor_domain_priority_; +} + +inline bool SocketDeclaration::compositor_expects_single_value() const +{ + return compositor_expects_single_value_; +} + inline void SocketDeclaration::make_available(bNode &node) const { if (make_available_fn_) { diff --git a/source/blender/nodes/composite/CMakeLists.txt b/source/blender/nodes/composite/CMakeLists.txt index c0100d77889..c918abaa9ce 100644 --- a/source/blender/nodes/composite/CMakeLists.txt +++ b/source/blender/nodes/composite/CMakeLists.txt @@ -10,11 +10,14 @@ set(INC ../../blenlib ../../blentranslation ../../depsgraph + ../../functions + ../../gpu ../../imbuf ../../makesdna ../../makesrna ../../render ../../windowmanager + ../../compositor/realtime_compositor ../../../../intern/guardedalloc # dna_type_offsets.h @@ -120,6 +123,10 @@ set(SRC node_composite_util.hh ) +set(LIB + bf_realtime_compositor +) + if(WITH_IMAGE_OPENEXR) add_definitions(-DWITH_OPENEXR) endif() diff --git a/source/blender/nodes/composite/nodes/node_composite_alpha_over.cc b/source/blender/nodes/composite/nodes/node_composite_alpha_over.cc index d392b810bc1..64c59eb24e3 100644 --- a/source/blender/nodes/composite/nodes/node_composite_alpha_over.cc +++ b/source/blender/nodes/composite/nodes/node_composite_alpha_over.cc @@ -8,6 +8,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** ALPHAOVER ******************** */ @@ -16,9 +20,18 @@ namespace blender::nodes::node_composite_alpha_over_cc { static void cmp_node_alphaover_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Fac")).default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Color>(N_("Image"), "Image_001").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>(N_("Fac")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .compositor_domain_priority(2); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Color>(N_("Image"), "Image_001") + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); } @@ -36,6 +49,52 @@ static void node_composit_buts_alphaover(uiLayout *layout, bContext *UNUSED(C), uiItemR(col, ptr, "premul", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class AlphaOverShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const float premultiply_factor = get_premultiply_factor(); + if (premultiply_factor != 0.0f) { + GPU_stack_link(material, + &bnode(), + "node_composite_alpha_over_mixed", + inputs, + outputs, + GPU_uniform(&premultiply_factor)); + return; + } + + if (get_use_premultiply()) { + GPU_stack_link(material, &bnode(), "node_composite_alpha_over_key", inputs, outputs); + return; + } + + GPU_stack_link(material, &bnode(), "node_composite_alpha_over_premultiply", inputs, outputs); + } + + bool get_use_premultiply() + { + return bnode().custom1; + } + + float get_premultiply_factor() + { + return ((NodeTwoFloats *)bnode().storage)->x; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new AlphaOverShaderNode(node); +} + } // namespace blender::nodes::node_composite_alpha_over_cc void register_node_type_cmp_alphaover() @@ -50,6 +109,7 @@ void register_node_type_cmp_alphaover() node_type_init(&ntype, file_ns::node_alphaover_init); node_type_storage( &ntype, "NodeTwoFloats", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_blur.cc b/source/blender/nodes/composite/nodes/node_composite_blur.cc index 7beffe15c8e..ce267f3acc1 100644 --- a/source/blender/nodes/composite/nodes/node_composite_blur.cc +++ b/source/blender/nodes/composite/nodes/node_composite_blur.cc @@ -5,11 +5,26 @@ * \ingroup cmpnodes */ +#include "BLI_assert.h" +#include "BLI_math_base.hh" +#include "BLI_math_vec_types.hh" +#include "BLI_vector.hh" + +#include "DNA_scene_types.h" + +#include "RE_pipeline.h" + #include "RNA_access.h" #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_state.h" +#include "GPU_texture.h" + +#include "COM_node_operation.hh" +#include "COM_utilities.hh" + #include "node_composite_util.hh" /* **************** BLUR ******************** */ @@ -71,6 +86,197 @@ static void node_composit_buts_blur(uiLayout *layout, bContext *UNUSED(C), Point uiItemR(col, ptr, "use_extended_bounds", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class BlurWeights { + public: + int type_; + float radius_; + GPUTexture *texture_ = nullptr; + + ~BlurWeights() + { + if (texture_) { + GPU_texture_free(texture_); + } + } + + void bind_as_texture(GPUShader *shader, const char *texture_name) + { + const int texture_image_unit = GPU_shader_get_texture_binding(shader, texture_name); + GPU_texture_bind(texture_, texture_image_unit); + } + + void unbind_as_texture() + { + GPU_texture_unbind(texture_); + } + + void update(float radius, int type) + { + if (texture_ && type == type_ && radius == radius_) { + return; + } + + if (texture_) { + GPU_texture_free(texture_); + } + + const int size = ceil(radius); + Vector<float> weights(2 * size + 1); + + float sum = 0.0f; + const float scale = radius > 0.0f ? 1.0f / radius : 0.0f; + for (int i = -size; i <= size; i++) { + const float weight = RE_filter_value(type, i * scale); + sum += weight; + weights[i + size] = weight; + } + + for (int i = 0; i < weights.size(); i++) { + weights[i] /= sum; + } + + texture_ = GPU_texture_create_1d("Weights", weights.size(), 1, GPU_R32F, weights.data()); + + type_ = type; + radius_ = radius; + } +}; + +class BlurOperation : public NodeOperation { + private: + /* Cached blur weights for the horizontal pass. */ + BlurWeights horizontal_weights_; + /* Cached blur weights for the vertical pass. */ + BlurWeights vertical_weights_; + + public: + using NodeOperation::NodeOperation; + + void execute() override + { + if (is_identity()) { + get_input("Image").pass_through(get_result("Image")); + return; + } + + GPUTexture *horizontal_pass_result = execute_horizontal_pass(); + execute_vertical_pass(horizontal_pass_result); + } + + /* Blur the input image horizontally using the horizontal weights, write the output into an + * intermediate texture, and return it. */ + GPUTexture *execute_horizontal_pass() + { + GPUShader *shader = shader_pool().acquire("compositor_blur_horizontal"); + GPU_shader_bind(shader); + + const Result &input_image = get_input("Image"); + input_image.bind_as_texture(shader, "input_image"); + + horizontal_weights_.update(compute_blur_radius().x, get_blur_data().filtertype); + horizontal_weights_.bind_as_texture(shader, "weights"); + + const Domain domain = compute_domain(); + + GPUTexture *horizontal_pass_result = texture_pool().acquire_color(domain.size); + const int image_unit = GPU_shader_get_texture_binding(shader, "output_image"); + GPU_texture_image_bind(horizontal_pass_result, image_unit); + + compute_dispatch_global(shader, domain.size); + + GPU_shader_unbind(); + input_image.unbind_as_texture(); + horizontal_weights_.unbind_as_texture(); + GPU_texture_image_unbind(horizontal_pass_result); + + return horizontal_pass_result; + } + + /* Blur the intermediate texture returned by the horizontal pass using the vertical weights and + * write the output into the result. */ + void execute_vertical_pass(GPUTexture *horizontal_pass_result) + { + GPUShader *shader = shader_pool().acquire("compositor_blur_vertical"); + GPU_shader_bind(shader); + + GPU_memory_barrier(GPU_BARRIER_TEXTURE_FETCH); + const int texture_image_unit = GPU_shader_get_texture_binding(shader, "input_image"); + GPU_texture_bind(horizontal_pass_result, texture_image_unit); + + vertical_weights_.update(compute_blur_radius().y, get_blur_data().filtertype); + vertical_weights_.bind_as_texture(shader, "weights"); + + const Domain domain = compute_domain(); + + Result &output_image = get_result("Image"); + output_image.allocate_texture(domain); + output_image.bind_as_image(shader, "output_image"); + + compute_dispatch_global(shader, domain.size); + + GPU_shader_unbind(); + output_image.unbind_as_image(); + vertical_weights_.unbind_as_texture(); + GPU_texture_unbind(horizontal_pass_result); + } + + float2 compute_blur_radius() + { + const float size = get_input("Size").get_float_value_default(1.0f); + if (!get_blur_data().relative) { + return float2(get_blur_data().sizex, get_blur_data().sizey) * size; + } + + int2 input_size = get_input("Image").domain().size; + switch (get_blur_data().aspect) { + case CMP_NODE_BLUR_ASPECT_Y: + input_size.y = input_size.x; + break; + case CMP_NODE_BLUR_ASPECT_X: + input_size.x = input_size.y; + break; + default: + BLI_assert(get_blur_data().aspect == CMP_NODE_BLUR_ASPECT_NONE); + break; + } + return float2(input_size) * get_size_factor() * size; + } + + float2 get_size_factor() + { + return float2(get_blur_data().percentx, get_blur_data().percenty) / 100.0f; + } + + NodeBlurData &get_blur_data() + { + return *static_cast<NodeBlurData *>(bnode().storage); + } + + /* Returns true if the operation does nothing and the input can be passed through. */ + bool is_identity() + { + const Result &input = get_input("Image"); + /* Single value inputs can't be blurred and are returned as is. */ + if (input.is_single_value()) { + return true; + } + + /* Zero blur radius. The operation does nothing and the input can be passed through. */ + if (compute_blur_radius() == float2(0.0)) { + return true; + } + + return false; + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new BlurOperation(context, node); +} + } // namespace blender::nodes::node_composite_blur_cc void register_node_type_cmp_blur() @@ -86,6 +292,7 @@ void register_node_type_cmp_blur() node_type_init(&ntype, file_ns::node_composit_init_blur); node_type_storage( &ntype, "NodeBlurData", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_boxmask.cc b/source/blender/nodes/composite/nodes/node_composite_boxmask.cc index f39b69c63f2..e099684abeb 100644 --- a/source/blender/nodes/composite/nodes/node_composite_boxmask.cc +++ b/source/blender/nodes/composite/nodes/node_composite_boxmask.cc @@ -5,9 +5,18 @@ * \ingroup cmpnodes */ +#include <cmath> + +#include "BLI_math_vec_types.hh" + #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_shader.h" + +#include "COM_node_operation.hh" +#include "COM_utilities.hh" + #include "node_composite_util.hh" /* **************** SCALAR MATH ******************** */ @@ -48,6 +57,98 @@ static void node_composit_buts_boxmask(uiLayout *layout, bContext *UNUSED(C), Po uiItemR(layout, ptr, "mask_type", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class BoxMaskOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + GPUShader *shader = shader_pool().acquire(get_shader_name()); + GPU_shader_bind(shader); + + const Domain domain = compute_domain(); + + GPU_shader_uniform_2iv(shader, "domain_size", domain.size); + + GPU_shader_uniform_2fv(shader, "location", get_location()); + GPU_shader_uniform_2fv(shader, "size", get_size() / 2.0f); + GPU_shader_uniform_1f(shader, "cos_angle", std::cos(get_angle())); + GPU_shader_uniform_1f(shader, "sin_angle", std::sin(get_angle())); + + const Result &input_mask = get_input("Mask"); + input_mask.bind_as_texture(shader, "base_mask"); + + const Result &value = get_input("Value"); + value.bind_as_texture(shader, "mask_value"); + + Result &output_mask = get_result("Mask"); + output_mask.allocate_texture(domain); + output_mask.bind_as_image(shader, "output_mask"); + + compute_dispatch_global(shader, domain.size); + + input_mask.unbind_as_texture(); + value.unbind_as_texture(); + output_mask.unbind_as_image(); + GPU_shader_unbind(); + } + + Domain compute_domain() override + { + if (get_input("Mask").is_single_value()) { + return Domain(context().get_viewport_size()); + } + return get_input("Mask").domain(); + } + + int get_mask_type() + { + return bnode().custom1; + } + + const char *get_shader_name() + { + switch (get_mask_type()) { + default: + case CMP_NODE_MASKTYPE_ADD: + return "compositor_box_mask_add"; + case CMP_NODE_MASKTYPE_SUBTRACT: + return "compositor_box_mask_subtract"; + case CMP_NODE_MASKTYPE_MULTIPLY: + return "compositor_box_mask_multiply"; + case CMP_NODE_MASKTYPE_NOT: + return "compositor_box_mask_not"; + } + } + + NodeBoxMask &get_node_box_mask() + { + return *static_cast<NodeBoxMask *>(bnode().storage); + } + + float2 get_location() + { + return float2(get_node_box_mask().x, get_node_box_mask().y); + } + + float2 get_size() + { + return float2(get_node_box_mask().width, get_node_box_mask().height); + } + + float get_angle() + { + return get_node_box_mask().rotation; + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new BoxMaskOperation(context, node); +} + } // namespace blender::nodes::node_composite_boxmask_cc void register_node_type_cmp_boxmask() @@ -61,6 +162,7 @@ void register_node_type_cmp_boxmask() ntype.draw_buttons = file_ns::node_composit_buts_boxmask; node_type_init(&ntype, file_ns::node_composit_init_boxmask); node_type_storage(&ntype, "NodeBoxMask", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_brightness.cc b/source/blender/nodes/composite/nodes/node_composite_brightness.cc index 65ed2885d9b..fa22f551de6 100644 --- a/source/blender/nodes/composite/nodes/node_composite_brightness.cc +++ b/source/blender/nodes/composite/nodes/node_composite_brightness.cc @@ -8,6 +8,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** Bright and Contrast ******************** */ @@ -16,9 +20,11 @@ namespace blender::nodes::node_composite_brightness_cc { static void cmp_node_brightcontrast_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>(N_("Bright")).min(-100.0f).max(100.0f); - b.add_input<decl::Float>(N_("Contrast")).min(-100.0f).max(100.0f); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Float>(N_("Bright")).min(-100.0f).max(100.0f).compositor_domain_priority(1); + b.add_input<decl::Float>(N_("Contrast")).min(-100.0f).max(100.0f).compositor_domain_priority(2); b.add_output<decl::Color>(N_("Image")); } @@ -34,6 +40,38 @@ static void node_composit_buts_brightcontrast(uiLayout *layout, uiItemR(layout, ptr, "use_premultiply", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class BrightContrastShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const float use_premultiply = get_use_premultiply(); + + GPU_stack_link(material, + &bnode(), + "node_composite_bright_contrast", + inputs, + outputs, + GPU_constant(&use_premultiply)); + } + + bool get_use_premultiply() + { + return bnode().custom1; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new BrightContrastShaderNode(node); +} + } // namespace blender::nodes::node_composite_brightness_cc void register_node_type_cmp_brightcontrast() @@ -46,6 +84,7 @@ void register_node_type_cmp_brightcontrast() ntype.declare = file_ns::cmp_node_brightcontrast_declare; ntype.draw_buttons = file_ns::node_composit_buts_brightcontrast; node_type_init(&ntype, file_ns::node_composit_init_brightcontrast); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_channel_matte.cc b/source/blender/nodes/composite/nodes/node_composite_channel_matte.cc index 627f07fdfce..e17e85f8b87 100644 --- a/source/blender/nodes/composite/nodes/node_composite_channel_matte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_channel_matte.cc @@ -10,6 +10,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* ******************* Channel Matte Node ********************************* */ @@ -18,7 +22,9 @@ namespace blender::nodes::node_composite_channel_matte_cc { static void cmp_node_channel_matte_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Color>(N_("Image")); b.add_output<decl::Float>(N_("Matte")); } @@ -79,6 +85,96 @@ static void node_composit_buts_channel_matte(uiLayout *layout, col, ptr, "limit_min", UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class ChannelMatteShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const float color_space = get_color_space(); + const float matte_channel = get_matte_channel(); + float limit_channels[2]; + get_limit_channels(limit_channels); + const float max_limit = get_max_limit(); + const float min_limit = get_min_limit(); + + GPU_stack_link(material, + &bnode(), + "node_composite_channel_matte", + inputs, + outputs, + GPU_constant(&color_space), + GPU_constant(&matte_channel), + GPU_constant(limit_channels), + GPU_uniform(&max_limit), + GPU_uniform(&min_limit)); + } + + /* 1 -> CMP_NODE_CHANNEL_MATTE_CS_RGB + * 2 -> CMP_NODE_CHANNEL_MATTE_CS_HSV + * 3 -> CMP_NODE_CHANNEL_MATTE_CS_YUV + * 4 -> CMP_NODE_CHANNEL_MATTE_CS_YCC */ + int get_color_space() + { + return bnode().custom1; + } + + /* Get the index of the channel used to generate the matte. */ + int get_matte_channel() + { + return bnode().custom2 - 1; + } + + NodeChroma *get_node_chroma() + { + return static_cast<NodeChroma *>(bnode().storage); + } + + /* Get the index of the channel used to compute the limit value. */ + int get_limit_channel() + { + return get_node_chroma()->channel - 1; + } + + /* Get the indices of the channels used to compute the limit value. We always assume the limit + * algorithm is Max, if it is a single limit channel, store it in both limit channels, because + * the maximum of two identical values is is the same value. */ + void get_limit_channels(float limit_channels[2]) + { + if (get_node_chroma()->algorithm) { + /* If the algorithm is Max, store the indices of the other two channels other than the matte + * channel. */ + limit_channels[0] = (get_matte_channel() + 1) % 3; + limit_channels[1] = (get_matte_channel() + 2) % 3; + } + else { + /* If the algorithm is Single, store the index of the limit channel in both channels. */ + limit_channels[0] = get_limit_channel(); + limit_channels[1] = get_limit_channel(); + } + } + + float get_max_limit() + { + return get_node_chroma()->t1; + } + + float get_min_limit() + { + return get_node_chroma()->t2; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new ChannelMatteShaderNode(node); +} + } // namespace blender::nodes::node_composite_channel_matte_cc void register_node_type_cmp_channel_matte() @@ -93,6 +189,7 @@ void register_node_type_cmp_channel_matte() ntype.flag |= NODE_PREVIEW; node_type_init(&ntype, file_ns::node_composit_init_channel_matte); node_type_storage(&ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_chroma_matte.cc b/source/blender/nodes/composite/nodes/node_composite_chroma_matte.cc index 69319c6825d..9dcd4213949 100644 --- a/source/blender/nodes/composite/nodes/node_composite_chroma_matte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_chroma_matte.cc @@ -5,11 +5,15 @@ * \ingroup cmpnodes */ -#include "BLI_math_rotation.h" +#include <cmath> #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* ******************* Chroma Key ********************************************************** */ @@ -18,8 +22,12 @@ namespace blender::nodes::node_composite_chroma_matte_cc { static void cmp_node_chroma_matte_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Color>(N_("Key Color")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Color>(N_("Key Color")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); b.add_output<decl::Float>(N_("Matte")); } @@ -51,6 +59,57 @@ static void node_composit_buts_chroma_matte(uiLayout *layout, bContext *UNUSED(C // uiItemR(col, ptr, "shadow_adjust", UI_ITEM_R_SLIDER, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class ChromaMatteShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const float acceptance = get_acceptance(); + const float cutoff = get_cutoff(); + const float falloff = get_falloff(); + + GPU_stack_link(material, + &bnode(), + "node_composite_chroma_matte", + inputs, + outputs, + GPU_uniform(&acceptance), + GPU_uniform(&cutoff), + GPU_uniform(&falloff)); + } + + NodeChroma *get_node_chroma() + { + return static_cast<NodeChroma *>(bnode().storage); + } + + float get_acceptance() + { + return std::tan(get_node_chroma()->t1) / 2.0f; + } + + float get_cutoff() + { + return get_node_chroma()->t2; + } + + float get_falloff() + { + return get_node_chroma()->fstrength; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new ChromaMatteShaderNode(node); +} + } // namespace blender::nodes::node_composite_chroma_matte_cc void register_node_type_cmp_chroma_matte() @@ -65,6 +124,7 @@ void register_node_type_cmp_chroma_matte() ntype.flag |= NODE_PREVIEW; node_type_init(&ntype, file_ns::node_composit_init_chroma_matte); node_type_storage(&ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_color_matte.cc b/source/blender/nodes/composite/nodes/node_composite_color_matte.cc index 474fb1b72f2..5e3aaf512e6 100644 --- a/source/blender/nodes/composite/nodes/node_composite_color_matte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_color_matte.cc @@ -8,6 +8,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* ******************* Color Matte ********************************************************** */ @@ -16,8 +20,12 @@ namespace blender::nodes::node_composite_color_matte_cc { static void cmp_node_color_matte_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Color>(N_("Key Color")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Color>(N_("Key Color")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); b.add_output<decl::Float>(N_("Matte")); } @@ -50,6 +58,58 @@ static void node_composit_buts_color_matte(uiLayout *layout, bContext *UNUSED(C) col, ptr, "color_value", UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class ColorMatteShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const float hue_epsilon = get_hue_epsilon(); + const float saturation_epsilon = get_saturation_epsilon(); + const float value_epsilon = get_value_epsilon(); + + GPU_stack_link(material, + &bnode(), + "node_composite_color_matte", + inputs, + outputs, + GPU_uniform(&hue_epsilon), + GPU_uniform(&saturation_epsilon), + GPU_uniform(&value_epsilon)); + } + + NodeChroma *get_node_chroma() + { + return static_cast<NodeChroma *>(bnode().storage); + } + + float get_hue_epsilon() + { + /* Divide by 2 because the hue wraps around. */ + return get_node_chroma()->t1 / 2.0f; + } + + float get_saturation_epsilon() + { + return get_node_chroma()->t2; + } + + float get_value_epsilon() + { + return get_node_chroma()->t3; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new ColorMatteShaderNode(node); +} + } // namespace blender::nodes::node_composite_color_matte_cc void register_node_type_cmp_color_matte() @@ -64,6 +124,7 @@ void register_node_type_cmp_color_matte() ntype.flag |= NODE_PREVIEW; node_type_init(&ntype, file_ns::node_composit_init_color_matte); node_type_storage(&ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_color_spill.cc b/source/blender/nodes/composite/nodes/node_composite_color_spill.cc index 9ad5dfbaeb2..f2c066431b7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_color_spill.cc +++ b/source/blender/nodes/composite/nodes/node_composite_color_spill.cc @@ -10,6 +10,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* ******************* Color Spill Suppression ********************************* */ @@ -18,8 +22,15 @@ namespace blender::nodes::node_composite_color_spill_cc { static void cmp_node_color_spill_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>(N_("Fac")).default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Float>(N_("Fac")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); } @@ -80,6 +91,106 @@ static void node_composit_buts_color_spill(uiLayout *layout, bContext *UNUSED(C) } } +using namespace blender::realtime_compositor; + +class ColorSpillShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const float spill_channel = get_spill_channel(); + float spill_scale[3]; + get_spill_scale(spill_scale); + float limit_channels[2]; + get_limit_channels(limit_channels); + const float limit_scale = get_limit_scale(); + + GPU_stack_link(material, + &bnode(), + "node_composite_color_spill", + inputs, + outputs, + GPU_uniform(&spill_channel), + GPU_uniform(spill_scale), + GPU_uniform(limit_channels), + GPU_uniform(&limit_scale)); + } + + /* Get the index of the channel used for spilling. */ + int get_spill_channel() + { + return bnode().custom1 - 1; + } + + /* Get limiting algorithm. + * 0 -> Single. + * 1 -> Average. */ + int get_limiting_algorithm() + { + return bnode().custom2; + } + + NodeColorspill *get_node_color_spill() + { + return static_cast<NodeColorspill *>(bnode().storage); + } + + void get_spill_scale(float spill_scale[3]) + { + const NodeColorspill *node_color_spill = get_node_color_spill(); + if (node_color_spill->unspill == 0) { + spill_scale[0] = 0.0f; + spill_scale[1] = 0.0f; + spill_scale[2] = 0.0f; + spill_scale[get_spill_channel()] = -1.0f; + } + else { + spill_scale[0] = node_color_spill->uspillr; + spill_scale[1] = node_color_spill->uspillg; + spill_scale[2] = node_color_spill->uspillb; + spill_scale[get_spill_channel()] *= -1.0f; + } + } + + /* Get the index of the channel used for limiting. */ + int get_limit_channel() + { + return get_node_color_spill()->limchan; + } + + /* Get the indices of the channels used to compute the limit value. We always assume the limit + * algorithm is Average, if it is a single limit channel, store it in both limit channels, + * because the average of two identical values is the same value. */ + void get_limit_channels(float limit_channels[2]) + { + if (get_limiting_algorithm() == 1) { + /* If the algorithm is Average, store the indices of the other two channels other than the + * spill channel. */ + limit_channels[0] = (get_spill_channel() + 1) % 3; + limit_channels[1] = (get_spill_channel() + 2) % 3; + } + else { + /* If the algorithm is Single, store the index of the limit channel in both channels. */ + limit_channels[0] = get_limit_channel(); + limit_channels[1] = get_limit_channel(); + } + } + + float get_limit_scale() + { + return get_node_color_spill()->limscale; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new ColorSpillShaderNode(node); +} + } // namespace blender::nodes::node_composite_color_spill_cc void register_node_type_cmp_color_spill() @@ -94,6 +205,7 @@ void register_node_type_cmp_color_spill() node_type_init(&ntype, file_ns::node_composit_init_color_spill); node_type_storage( &ntype, "NodeColorspill", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc b/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc index dd081c8fc12..90c5f6b4748 100644 --- a/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc +++ b/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc @@ -10,6 +10,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* ******************* Color Balance ********************************* */ @@ -46,8 +50,15 @@ namespace blender::nodes::node_composite_colorbalance_cc { static void cmp_node_colorbalance_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Fac")).default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>(N_("Fac")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .compositor_domain_priority(1); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Color>(N_("Image")); } @@ -139,6 +150,61 @@ static void node_composit_buts_colorbalance_ex(uiLayout *layout, } } +using namespace blender::realtime_compositor; + +class ColorBalanceShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const NodeColorBalance *node_color_balance = get_node_color_balance(); + + if (get_color_balance_method() == 0) { + GPU_stack_link(material, + &bnode(), + "node_composite_color_balance_lgg", + inputs, + outputs, + GPU_uniform(node_color_balance->lift), + GPU_uniform(node_color_balance->gamma), + GPU_uniform(node_color_balance->gain)); + return; + } + + GPU_stack_link(material, + &bnode(), + "node_composite_color_balance_asc_cdl", + inputs, + outputs, + GPU_uniform(node_color_balance->offset), + GPU_uniform(node_color_balance->power), + GPU_uniform(node_color_balance->slope), + GPU_uniform(&node_color_balance->offset_basis)); + } + + /* Get color balance method. + * 0 -> LGG. + * 1 -> ASC-CDL. */ + int get_color_balance_method() + { + return bnode().custom1; + } + + NodeColorBalance *get_node_color_balance() + { + return static_cast<NodeColorBalance *>(bnode().storage); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new ColorBalanceShaderNode(node); +} + } // namespace blender::nodes::node_composite_colorbalance_cc void register_node_type_cmp_colorbalance() @@ -155,6 +221,7 @@ void register_node_type_cmp_colorbalance() node_type_init(&ntype, file_ns::node_composit_init_colorbalance); node_type_storage( &ntype, "NodeColorBalance", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_colorcorrection.cc b/source/blender/nodes/composite/nodes/node_composite_colorcorrection.cc index 39ecd277cec..36e6672ce1c 100644 --- a/source/blender/nodes/composite/nodes/node_composite_colorcorrection.cc +++ b/source/blender/nodes/composite/nodes/node_composite_colorcorrection.cc @@ -5,9 +5,15 @@ * \ingroup cmpnodes */ +#include "IMB_colormanagement.h" + #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* ******************* Color Correction ********************************* */ @@ -16,8 +22,14 @@ namespace blender::nodes::node_composite_colorcorrection_cc { static void cmp_node_colorcorrection_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>(N_("Mask")).default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Float>(N_("Mask")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); } @@ -266,6 +278,73 @@ static void node_composit_buts_colorcorrection_ex(uiLayout *layout, uiItemR(row, ptr, "midtones_end", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class ColorCorrectionShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + float enabled_channels[3]; + get_enabled_channels(enabled_channels); + float luminance_coefficients[3]; + IMB_colormanagement_get_luminance_coefficients(luminance_coefficients); + + const NodeColorCorrection *node_color_correction = get_node_color_correction(); + + GPU_stack_link(material, + &bnode(), + "node_composite_color_correction", + inputs, + outputs, + GPU_constant(enabled_channels), + GPU_uniform(&node_color_correction->startmidtones), + GPU_uniform(&node_color_correction->endmidtones), + GPU_uniform(&node_color_correction->master.saturation), + GPU_uniform(&node_color_correction->master.contrast), + GPU_uniform(&node_color_correction->master.gamma), + GPU_uniform(&node_color_correction->master.gain), + GPU_uniform(&node_color_correction->master.lift), + GPU_uniform(&node_color_correction->shadows.saturation), + GPU_uniform(&node_color_correction->shadows.contrast), + GPU_uniform(&node_color_correction->shadows.gamma), + GPU_uniform(&node_color_correction->shadows.gain), + GPU_uniform(&node_color_correction->shadows.lift), + GPU_uniform(&node_color_correction->midtones.saturation), + GPU_uniform(&node_color_correction->midtones.contrast), + GPU_uniform(&node_color_correction->midtones.gamma), + GPU_uniform(&node_color_correction->midtones.gain), + GPU_uniform(&node_color_correction->midtones.lift), + GPU_uniform(&node_color_correction->highlights.saturation), + GPU_uniform(&node_color_correction->highlights.contrast), + GPU_uniform(&node_color_correction->highlights.gamma), + GPU_uniform(&node_color_correction->highlights.gain), + GPU_uniform(&node_color_correction->highlights.lift), + GPU_constant(luminance_coefficients)); + } + + void get_enabled_channels(float enabled_channels[3]) + { + for (int i = 0; i < 3; i++) { + enabled_channels[i] = (bnode().custom1 & (1 << i)) ? 1.0f : 0.0f; + } + } + + NodeColorCorrection *get_node_color_correction() + { + return static_cast<NodeColorCorrection *>(bnode().storage); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new ColorCorrectionShaderNode(node); +} + } // namespace blender::nodes::node_composite_colorcorrection_cc void register_node_type_cmp_colorcorrection() @@ -282,6 +361,7 @@ void register_node_type_cmp_colorcorrection() node_type_init(&ntype, file_ns::node_composit_init_colorcorrection); node_type_storage( &ntype, "NodeColorCorrection", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_composite.cc b/source/blender/nodes/composite/nodes/node_composite_composite.cc index d35ce7dc11a..b270804662f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_composite.cc +++ b/source/blender/nodes/composite/nodes/node_composite_composite.cc @@ -5,9 +5,16 @@ * \ingroup cmpnodes */ +#include "BLI_math_vec_types.hh" + #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_state.h" +#include "GPU_texture.h" + +#include "COM_node_operation.hh" + #include "node_composite_util.hh" /* **************** COMPOSITE ******************** */ @@ -26,6 +33,43 @@ static void node_composit_buts_composite(uiLayout *layout, bContext *UNUSED(C), uiItemR(layout, ptr, "use_alpha", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class CompositeOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + const Result &input_image = get_input("Image"); + GPUTexture *viewport_texture = context().get_viewport_texture(); + + /* If the input image is a texture, copy the input texture to the viewport texture. */ + if (input_image.is_texture()) { + /* Make sure any prior writes to the texture are reflected before copying it. */ + GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); + + GPU_texture_copy(viewport_texture, input_image.texture()); + } + else { + /* Otherwise, if the input image is a single color value, clear the viewport texture to that + * color. */ + GPU_texture_clear(viewport_texture, GPU_DATA_FLOAT, input_image.get_color_value()); + } + } + + /* The operation domain have the same dimensions of the viewport without any transformations. */ + Domain compute_domain() override + { + return Domain(context().get_viewport_size()); + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new CompositeOperation(context, node); +} + } // namespace blender::nodes::node_composite_composite_cc void register_node_type_cmp_composite() @@ -37,6 +81,7 @@ void register_node_type_cmp_composite() cmp_node_type_base(&ntype, CMP_NODE_COMPOSITE, "Composite", NODE_CLASS_OUTPUT); ntype.declare = file_ns::cmp_node_composite_declare; ntype.draw_buttons = file_ns::node_composit_buts_composite; + ntype.get_compositor_operation = file_ns::get_compositor_operation; ntype.flag |= NODE_PREVIEW; ntype.no_muting = true; diff --git a/source/blender/nodes/composite/nodes/node_composite_crop.cc b/source/blender/nodes/composite/nodes/node_composite_crop.cc index 823e1052dd0..763e1f6b682 100644 --- a/source/blender/nodes/composite/nodes/node_composite_crop.cc +++ b/source/blender/nodes/composite/nodes/node_composite_crop.cc @@ -5,11 +5,22 @@ * \ingroup cmpnodes */ +#include "BLI_math_base.h" +#include "BLI_math_vec_types.hh" + +#include "DNA_node_types.h" + #include "RNA_access.h" #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" /* **************** Crop ******************** */ @@ -18,7 +29,9 @@ namespace blender::nodes::node_composite_crop_cc { static void cmp_node_crop_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Color>(N_("Image")); } @@ -54,6 +67,161 @@ static void node_composit_buts_crop(uiLayout *layout, bContext *UNUSED(C), Point } } +using namespace blender::realtime_compositor; + +class CropOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + /* The operation does nothing, so just pass the input through. */ + if (is_identity()) { + get_input("Image").pass_through(get_result("Image")); + return; + } + + if (get_is_image_crop()) { + execute_image_crop(); + } + else { + execute_alpha_crop(); + } + } + + /* Crop by replacing areas outside of the cropping bounds with zero alpha. The output have the + * same domain as the input image. */ + void execute_alpha_crop() + { + GPUShader *shader = shader_pool().acquire("compositor_alpha_crop"); + GPU_shader_bind(shader); + + int2 lower_bound, upper_bound; + compute_cropping_bounds(lower_bound, upper_bound); + GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + GPU_shader_uniform_2iv(shader, "upper_bound", upper_bound); + + const Result &input_image = get_input("Image"); + input_image.bind_as_texture(shader, "input_image"); + + const Domain domain = compute_domain(); + + Result &output_image = get_result("Image"); + output_image.allocate_texture(domain); + output_image.bind_as_image(shader, "output_image"); + + compute_dispatch_global(shader, domain.size); + + input_image.unbind_as_texture(); + output_image.unbind_as_image(); + GPU_shader_unbind(); + } + + /* Crop the image into a new size that matches the cropping bounds. */ + void execute_image_crop() + { + int2 lower_bound, upper_bound; + compute_cropping_bounds(lower_bound, upper_bound); + + /* The image is cropped into nothing, so just return a single zero value. */ + if (lower_bound.x == upper_bound.x || lower_bound.y == upper_bound.y) { + Result &result = get_result("Image"); + result.allocate_invalid(); + return; + } + + GPUShader *shader = shader_pool().acquire("compositor_image_crop"); + GPU_shader_bind(shader); + + GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + + const Result &input_image = get_input("Image"); + input_image.bind_as_texture(shader, "input_image"); + + const int2 size = upper_bound - lower_bound; + + Result &output_image = get_result("Image"); + output_image.allocate_texture(Domain(size, compute_domain().transformation)); + output_image.bind_as_image(shader, "output_image"); + + compute_dispatch_global(shader, size); + + input_image.unbind_as_texture(); + output_image.unbind_as_image(); + GPU_shader_unbind(); + } + + /* If true, the image should actually be cropped into a new size. Otherwise, if false, the region + * outside of the cropping bounds will be set to a zero alpha value. */ + bool get_is_image_crop() + { + return bnode().custom1; + } + + bool get_is_relative() + { + return bnode().custom2; + } + + NodeTwoXYs &get_node_two_xys() + { + return *static_cast<NodeTwoXYs *>(bnode().storage); + } + + /* Returns true if the operation does nothing and the input can be passed through. */ + bool is_identity() + { + const Result &input = get_input("Image"); + /* Single value inputs can't be cropped and are returned as is. */ + if (input.is_single_value()) { + return true; + } + + int2 lower_bound, upper_bound; + compute_cropping_bounds(lower_bound, upper_bound); + const int2 input_size = input.domain().size; + /* The cropping bounds cover the whole image, so no cropping happens. */ + if (lower_bound == int2(0) && upper_bound == input_size) { + return true; + } + + return false; + } + + void compute_cropping_bounds(int2 &lower_bound, int2 &upper_bound) + { + const NodeTwoXYs &node_two_xys = get_node_two_xys(); + const int2 input_size = get_input("Image").domain().size; + + if (get_is_relative()) { + /* The cropping bounds are relative to the image size. The factors are in the [0, 1] range, + * so it is guaranteed that they won't go over the input image size. */ + lower_bound.x = input_size.x * node_two_xys.fac_x1; + lower_bound.y = input_size.y * node_two_xys.fac_y2; + upper_bound.x = input_size.x * node_two_xys.fac_x2; + upper_bound.y = input_size.y * node_two_xys.fac_y1; + } + else { + /* Make sure the bounds don't go over the input image size. */ + lower_bound.x = min_ii(node_two_xys.x1, input_size.x); + lower_bound.y = min_ii(node_two_xys.y2, input_size.y); + upper_bound.x = min_ii(node_two_xys.x2, input_size.x); + upper_bound.y = min_ii(node_two_xys.y1, input_size.y); + } + + /* Make sure upper bound is actually higher than the lower bound. */ + lower_bound.x = min_ii(lower_bound.x, upper_bound.x); + lower_bound.y = min_ii(lower_bound.y, upper_bound.y); + upper_bound.x = max_ii(lower_bound.x, upper_bound.x); + upper_bound.y = max_ii(lower_bound.y, upper_bound.y); + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new CropOperation(context, node); +} + } // namespace blender::nodes::node_composite_crop_cc void register_node_type_cmp_crop() @@ -67,6 +235,7 @@ void register_node_type_cmp_crop() ntype.draw_buttons = file_ns::node_composit_buts_crop; node_type_init(&ntype, file_ns::node_composit_init_crop); node_type_storage(&ntype, "NodeTwoXYs", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc index 9193f91087a..8fb45a5a90b 100644 --- a/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_cryptomatte.cc @@ -313,6 +313,7 @@ void register_node_type_cmp_cryptomatte() ntype.poll = file_ns::node_poll_cryptomatte; node_type_storage( &ntype, "NodeCryptomatte", file_ns::node_free_cryptomatte, file_ns::node_copy_cryptomatte); + nodeRegisterType(&ntype); } @@ -376,6 +377,7 @@ void register_node_type_cmp_cryptomatte_legacy() node_type_init(&ntype, file_ns::node_init_cryptomatte_legacy); node_type_storage( &ntype, "NodeCryptomatte", file_ns::node_free_cryptomatte, file_ns::node_copy_cryptomatte); + nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_curves.cc b/source/blender/nodes/composite/nodes/node_composite_curves.cc index fff0d467f75..ea534935d10 100644 --- a/source/blender/nodes/composite/nodes/node_composite_curves.cc +++ b/source/blender/nodes/composite/nodes/node_composite_curves.cc @@ -5,14 +5,23 @@ * \ingroup cmpnodes */ +#include "BLI_math_base.h" + +#include "BKE_colortools.h" + #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_node_operation.hh" +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** CURVE Time ******************** */ -namespace blender::nodes::node_composite_curves_cc { +namespace blender::nodes::node_composite_time_curves_cc { static void cmp_node_time_declare(NodeDeclarationBuilder &b) { @@ -27,11 +36,65 @@ static void node_composit_init_curves_time(bNodeTree *UNUSED(ntree), bNode *node node->storage = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); } -} // namespace blender::nodes::node_composite_curves_cc +using namespace blender::realtime_compositor; + +class TimeCurveOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + Result &result = get_result("Fac"); + result.allocate_single_value(); + + CurveMapping *curve_mapping = get_curve_mapping(); + BKE_curvemapping_init(curve_mapping); + const float time = BKE_curvemapping_evaluateF(curve_mapping, 0, compute_normalized_time()); + result.set_float_value(clamp_f(time, 0.0f, 1.0f)); + } + + CurveMapping *get_curve_mapping() + { + return static_cast<CurveMapping *>(bnode().storage); + } + + int get_start_time() + { + return bnode().custom1; + } + + int get_end_time() + { + return bnode().custom2; + } + + float compute_normalized_time() + { + const int frame_number = context().get_frame_number(); + if (frame_number < get_start_time()) { + return 0.0f; + } + if (frame_number > get_end_time()) { + return 1.0f; + } + if (get_start_time() == get_end_time()) { + return 0.0f; + } + return static_cast<float>(frame_number - get_start_time()) / + static_cast<float>(get_end_time() - get_start_time()); + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new TimeCurveOperation(context, node); +} + +} // namespace blender::nodes::node_composite_time_curves_cc void register_node_type_cmp_curve_time() { - namespace file_ns = blender::nodes::node_composite_curves_cc; + namespace file_ns = blender::nodes::node_composite_time_curves_cc; static bNodeType ntype; @@ -40,17 +103,22 @@ void register_node_type_cmp_curve_time() node_type_size(&ntype, 200, 140, 320); node_type_init(&ntype, file_ns::node_composit_init_curves_time); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } /* **************** CURVE VEC ******************** */ -namespace blender::nodes::node_composite_curves_cc { +namespace blender::nodes::node_composite_vector_curves_cc { static void cmp_node_curve_vec_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Vector>(N_("Vector")).default_value({0.0f, 0.0f, 0.0f}).min(-1.0f).max(1.0f); + b.add_input<decl::Vector>(N_("Vector")) + .default_value({0.0f, 0.0f, 0.0f}) + .min(-1.0f) + .max(1.0f) + .compositor_domain_priority(0); b.add_output<decl::Vector>(N_("Vector")); } @@ -64,11 +132,63 @@ static void node_buts_curvevec(uiLayout *layout, bContext *UNUSED(C), PointerRNA uiTemplateCurveMapping(layout, ptr, "mapping", 'v', false, false, false, false); } -} // namespace blender::nodes::node_composite_curves_cc +using namespace blender::realtime_compositor; + +class VectorCurvesShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + CurveMapping *curve_mapping = get_curve_mapping(); + + BKE_curvemapping_init(curve_mapping); + float *band_values; + int band_size; + BKE_curvemapping_table_RGBA(curve_mapping, &band_values, &band_size); + float band_layer; + GPUNodeLink *band_texture = GPU_color_band(material, band_size, band_values, &band_layer); + + float start_slopes[CM_TOT]; + float end_slopes[CM_TOT]; + BKE_curvemapping_compute_slopes(curve_mapping, start_slopes, end_slopes); + float range_minimums[CM_TOT]; + BKE_curvemapping_get_range_minimums(curve_mapping, range_minimums); + float range_dividers[CM_TOT]; + BKE_curvemapping_compute_range_dividers(curve_mapping, range_dividers); + + GPU_stack_link(material, + &bnode(), + "curves_vector", + inputs, + outputs, + band_texture, + GPU_constant(&band_layer), + GPU_uniform(range_minimums), + GPU_uniform(range_dividers), + GPU_uniform(start_slopes), + GPU_uniform(end_slopes)); + } + + CurveMapping *get_curve_mapping() + { + return static_cast<CurveMapping *>(bnode().storage); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new VectorCurvesShaderNode(node); +} + +} // namespace blender::nodes::node_composite_vector_curves_cc void register_node_type_cmp_curve_vec() { - namespace file_ns = blender::nodes::node_composite_curves_cc; + namespace file_ns = blender::nodes::node_composite_vector_curves_cc; static bNodeType ntype; @@ -78,19 +198,26 @@ void register_node_type_cmp_curve_vec() node_type_size(&ntype, 200, 140, 320); node_type_init(&ntype, file_ns::node_composit_init_curve_vec); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } /* **************** CURVE RGB ******************** */ -namespace blender::nodes::node_composite_curves_cc { +namespace blender::nodes::node_composite_rgb_curves_cc { static void cmp_node_rgbcurves_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Fac")).default_value(1.0f).min(-1.0f).max(1.0f).subtype( - PROP_FACTOR); - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>(N_("Fac")) + .default_value(1.0f) + .min(-1.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .compositor_domain_priority(1); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_input<decl::Color>(N_("Black Level")).default_value({0.0f, 0.0f, 0.0f, 1.0f}); b.add_input<decl::Color>(N_("White Level")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); b.add_output<decl::Color>(N_("Image")); @@ -101,11 +228,96 @@ static void node_composit_init_curve_rgb(bNodeTree *UNUSED(ntree), bNode *node) node->storage = BKE_curvemapping_add(4, 0.0f, 0.0f, 1.0f, 1.0f); } -} // namespace blender::nodes::node_composite_curves_cc +using namespace blender::realtime_compositor; + +class RGBCurvesShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + CurveMapping *curve_mapping = get_curve_mapping(); + + BKE_curvemapping_init(curve_mapping); + float *band_values; + int band_size; + BKE_curvemapping_table_RGBA(curve_mapping, &band_values, &band_size); + float band_layer; + GPUNodeLink *band_texture = GPU_color_band(material, band_size, band_values, &band_layer); + + float start_slopes[CM_TOT]; + float end_slopes[CM_TOT]; + BKE_curvemapping_compute_slopes(curve_mapping, start_slopes, end_slopes); + float range_minimums[CM_TOT]; + BKE_curvemapping_get_range_minimums(curve_mapping, range_minimums); + float range_dividers[CM_TOT]; + BKE_curvemapping_compute_range_dividers(curve_mapping, range_dividers); + + if (curve_mapping->tone == CURVE_TONE_FILMLIKE) { + GPU_stack_link(material, + &bnode(), + "curves_film_like", + inputs, + outputs, + band_texture, + GPU_constant(&band_layer), + GPU_uniform(&range_minimums[3]), + GPU_uniform(&range_dividers[3]), + GPU_uniform(&start_slopes[3]), + GPU_uniform(&end_slopes[3])); + return; + } + + /* If the RGB curves do nothing, use a function that skips RGB computations. */ + if (BKE_curvemapping_is_map_identity(curve_mapping, 0) && + BKE_curvemapping_is_map_identity(curve_mapping, 1) && + BKE_curvemapping_is_map_identity(curve_mapping, 2)) { + GPU_stack_link(material, + &bnode(), + "curves_combined_only", + inputs, + outputs, + band_texture, + GPU_constant(&band_layer), + GPU_uniform(&range_minimums[3]), + GPU_uniform(&range_dividers[3]), + GPU_uniform(&start_slopes[3]), + GPU_uniform(&end_slopes[3])); + return; + } + + GPU_stack_link(material, + &bnode(), + "curves_combined_rgb", + inputs, + outputs, + band_texture, + GPU_constant(&band_layer), + GPU_uniform(range_minimums), + GPU_uniform(range_dividers), + GPU_uniform(start_slopes), + GPU_uniform(end_slopes)); + } + + CurveMapping *get_curve_mapping() + { + return static_cast<CurveMapping *>(bnode().storage); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new RGBCurvesShaderNode(node); +} + +} // namespace blender::nodes::node_composite_rgb_curves_cc void register_node_type_cmp_curve_rgb() { - namespace file_ns = blender::nodes::node_composite_curves_cc; + namespace file_ns = blender::nodes::node_composite_rgb_curves_cc; static bNodeType ntype; @@ -114,6 +326,7 @@ void register_node_type_cmp_curve_rgb() node_type_size(&ntype, 200, 140, 320); node_type_init(&ntype, file_ns::node_composit_init_curve_rgb); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_diff_matte.cc b/source/blender/nodes/composite/nodes/node_composite_diff_matte.cc index 20dd61a9725..9241e0e37af 100644 --- a/source/blender/nodes/composite/nodes/node_composite_diff_matte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_diff_matte.cc @@ -8,6 +8,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* ******************* channel Difference Matte ********************************* */ @@ -16,8 +20,12 @@ namespace blender::nodes::node_composite_diff_matte_cc { static void cmp_node_diff_matte_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image 1")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Color>(N_("Image 2")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image 1")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Color>(N_("Image 2")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); b.add_output<decl::Color>(N_("Matte")); } @@ -40,6 +48,50 @@ static void node_composit_buts_diff_matte(uiLayout *layout, bContext *UNUSED(C), uiItemR(col, ptr, "falloff", UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class DifferenceMatteShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const float tolerance = get_tolerance(); + const float falloff = get_falloff(); + + GPU_stack_link(material, + &bnode(), + "node_composite_difference_matte", + inputs, + outputs, + GPU_uniform(&tolerance), + GPU_uniform(&falloff)); + } + + NodeChroma *get_node_chroma() + { + return static_cast<NodeChroma *>(bnode().storage); + } + + float get_tolerance() + { + return get_node_chroma()->t1; + } + + float get_falloff() + { + return get_node_chroma()->t2; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new DifferenceMatteShaderNode(node); +} + } // namespace blender::nodes::node_composite_diff_matte_cc void register_node_type_cmp_diff_matte() @@ -54,6 +106,7 @@ void register_node_type_cmp_diff_matte() ntype.flag |= NODE_PREVIEW; node_type_init(&ntype, file_ns::node_composit_init_diff_matte); node_type_storage(&ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_distance_matte.cc b/source/blender/nodes/composite/nodes/node_composite_distance_matte.cc index a8646d8498e..3153cfa66c7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_distance_matte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_distance_matte.cc @@ -8,6 +8,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* ******************* channel Distance Matte ********************************* */ @@ -16,8 +20,12 @@ namespace blender::nodes::node_composite_distance_matte_cc { static void cmp_node_distance_matte_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Color>(N_("Key Color")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Color>(N_("Key Color")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); b.add_output<decl::Float>(N_("Matte")); } @@ -48,6 +56,68 @@ static void node_composit_buts_distance_matte(uiLayout *layout, uiItemR(col, ptr, "falloff", UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class DistanceMatteShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const float tolerance = get_tolerance(); + const float falloff = get_falloff(); + + if (get_color_space() == 1) { + GPU_stack_link(material, + &bnode(), + "node_composite_distance_matte_rgba", + inputs, + outputs, + GPU_uniform(&tolerance), + GPU_uniform(&falloff)); + return; + } + + GPU_stack_link(material, + &bnode(), + "node_composite_distance_matte_ycca", + inputs, + outputs, + GPU_uniform(&tolerance), + GPU_uniform(&falloff)); + } + + NodeChroma *get_node_chroma() + { + return static_cast<NodeChroma *>(bnode().storage); + } + + /* 0 -> YCCA + * 1 -> RGBA */ + int get_color_space() + { + return get_node_chroma()->channel; + } + + float get_tolerance() + { + return get_node_chroma()->t1; + } + + float get_falloff() + { + return get_node_chroma()->t2; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new DistanceMatteShaderNode(node); +} + } // namespace blender::nodes::node_composite_distance_matte_cc void register_node_type_cmp_distance_matte() @@ -62,6 +132,7 @@ void register_node_type_cmp_distance_matte() ntype.flag |= NODE_PREVIEW; node_type_init(&ntype, file_ns::node_composit_init_distance_matte); node_type_storage(&ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_ellipsemask.cc b/source/blender/nodes/composite/nodes/node_composite_ellipsemask.cc index 4da6a0a442e..4104f60e0c3 100644 --- a/source/blender/nodes/composite/nodes/node_composite_ellipsemask.cc +++ b/source/blender/nodes/composite/nodes/node_composite_ellipsemask.cc @@ -5,9 +5,18 @@ * \ingroup cmpnodes */ +#include <cmath> + +#include "BLI_math_vec_types.hh" + #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_shader.h" + +#include "COM_node_operation.hh" +#include "COM_utilities.hh" + #include "node_composite_util.hh" /* **************** SCALAR MATH ******************** */ @@ -46,6 +55,98 @@ static void node_composit_buts_ellipsemask(uiLayout *layout, bContext *UNUSED(C) uiItemR(layout, ptr, "mask_type", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class EllipseMaskOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + GPUShader *shader = shader_pool().acquire(get_shader_name()); + GPU_shader_bind(shader); + + const Domain domain = compute_domain(); + + GPU_shader_uniform_2iv(shader, "domain_size", domain.size); + + GPU_shader_uniform_2fv(shader, "location", get_location()); + GPU_shader_uniform_2fv(shader, "radius", get_size() / 2.0f); + GPU_shader_uniform_1f(shader, "cos_angle", std::cos(get_angle())); + GPU_shader_uniform_1f(shader, "sin_angle", std::sin(get_angle())); + + const Result &input_mask = get_input("Mask"); + input_mask.bind_as_texture(shader, "base_mask"); + + const Result &value = get_input("Value"); + value.bind_as_texture(shader, "mask_value"); + + Result &output_mask = get_result("Mask"); + output_mask.allocate_texture(domain); + output_mask.bind_as_image(shader, "output_mask"); + + compute_dispatch_global(shader, domain.size); + + input_mask.unbind_as_texture(); + value.unbind_as_texture(); + output_mask.unbind_as_image(); + GPU_shader_unbind(); + } + + Domain compute_domain() override + { + if (get_input("Mask").is_single_value()) { + return Domain(context().get_viewport_size()); + } + return get_input("Mask").domain(); + } + + int get_mask_type() + { + return bnode().custom1; + } + + const char *get_shader_name() + { + switch (get_mask_type()) { + default: + case CMP_NODE_MASKTYPE_ADD: + return "compositor_ellipse_mask_add"; + case CMP_NODE_MASKTYPE_SUBTRACT: + return "compositor_ellipse_mask_subtract"; + case CMP_NODE_MASKTYPE_MULTIPLY: + return "compositor_ellipse_mask_multiply"; + case CMP_NODE_MASKTYPE_NOT: + return "compositor_ellipse_mask_not"; + } + } + + NodeEllipseMask &get_node_ellipse_mask() + { + return *static_cast<NodeEllipseMask *>(bnode().storage); + } + + float2 get_location() + { + return float2(get_node_ellipse_mask().x, get_node_ellipse_mask().y); + } + + float2 get_size() + { + return float2(get_node_ellipse_mask().width, get_node_ellipse_mask().height); + } + + float get_angle() + { + return get_node_ellipse_mask().rotation; + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new EllipseMaskOperation(context, node); +} + } // namespace blender::nodes::node_composite_ellipsemask_cc void register_node_type_cmp_ellipsemask() @@ -61,6 +162,7 @@ void register_node_type_cmp_ellipsemask() node_type_init(&ntype, file_ns::node_composit_init_ellipsemask); node_type_storage( &ntype, "NodeEllipseMask", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_exposure.cc b/source/blender/nodes/composite/nodes/node_composite_exposure.cc index 881cfc11058..19b93680ff6 100644 --- a/source/blender/nodes/composite/nodes/node_composite_exposure.cc +++ b/source/blender/nodes/composite/nodes/node_composite_exposure.cc @@ -5,6 +5,10 @@ * \ingroup cmpnodes */ +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** Exposure ******************** */ @@ -13,11 +17,33 @@ namespace blender::nodes::node_composite_exposure_cc { static void cmp_node_exposure_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>(N_("Exposure")).min(-10.0f).max(10.0f); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Float>(N_("Exposure")).min(-10.0f).max(10.0f).compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); } +using namespace blender::realtime_compositor; + +class ExposureShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_exposure", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new ExposureShaderNode(node); +} + } // namespace blender::nodes::node_composite_exposure_cc void register_node_type_cmp_exposure() @@ -28,6 +54,7 @@ void register_node_type_cmp_exposure() cmp_node_type_base(&ntype, CMP_NODE_EXPOSURE, "Exposure", NODE_CLASS_OP_COLOR); ntype.declare = file_ns::cmp_node_exposure_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_filter.cc b/source/blender/nodes/composite/nodes/node_composite_filter.cc index c343c21feb2..c4646afc282 100644 --- a/source/blender/nodes/composite/nodes/node_composite_filter.cc +++ b/source/blender/nodes/composite/nodes/node_composite_filter.cc @@ -8,6 +8,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" /* **************** FILTER ******************** */ @@ -16,8 +22,15 @@ namespace blender::nodes::node_composite_filter_cc { static void cmp_node_filter_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Fac")).default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>(N_("Fac")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .compositor_domain_priority(1); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Color>(N_("Image")); } @@ -26,6 +39,157 @@ static void node_composit_buts_filter(uiLayout *layout, bContext *UNUSED(C), Poi uiItemR(layout, ptr, "filter_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); } +using namespace blender::realtime_compositor; + +class FilterOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + GPUShader *shader = shader_pool().acquire("compositor_filter"); + GPU_shader_bind(shader); + + float kernel[3][3]; + get_filter_kernel(kernel); + GPU_shader_uniform_mat3_as_mat4(shader, "kernel", kernel); + + const Result &input_image = get_input("Image"); + input_image.bind_as_texture(shader, "input_image"); + + const Result &factor = get_input("Fac"); + factor.bind_as_texture(shader, "factor"); + + const Domain domain = compute_domain(); + + Result &output_image = get_result("Image"); + output_image.allocate_texture(domain); + output_image.bind_as_image(shader, "output_image"); + + compute_dispatch_global(shader, domain.size); + + input_image.unbind_as_texture(); + factor.unbind_as_texture(); + output_image.unbind_as_image(); + GPU_shader_unbind(); + } + + int get_filter_method() + { + return bnode().custom1; + } + + void get_filter_kernel(float kernel[3][3]) + { + switch (get_filter_method()) { + case CMP_FILT_SOFT: + kernel[0][0] = 1.0f / 16.0f; + kernel[0][1] = 2.0f / 16.0f; + kernel[0][2] = 1.0f / 16.0f; + kernel[1][0] = 2.0f / 16.0f; + kernel[1][1] = 4.0f / 16.0f; + kernel[1][2] = 2.0f / 16.0f; + kernel[2][0] = 1.0f / 16.0f; + kernel[2][1] = 2.0f / 16.0f; + kernel[2][2] = 1.0f / 16.0f; + break; + case CMP_FILT_SHARP_BOX: + kernel[0][0] = -1.0f; + kernel[0][1] = -1.0f; + kernel[0][2] = -1.0f; + kernel[1][0] = -1.0f; + kernel[1][1] = 9.0f; + kernel[1][2] = -1.0f; + kernel[2][0] = -1.0f; + kernel[2][1] = -1.0f; + kernel[2][2] = -1.0f; + break; + case CMP_FILT_LAPLACE: + kernel[0][0] = -1.0f / 8.0f; + kernel[0][1] = -1.0f / 8.0f; + kernel[0][2] = -1.0f / 8.0f; + kernel[1][0] = -1.0f / 8.0f; + kernel[1][1] = 1.0f; + kernel[1][2] = -1.0f / 8.0f; + kernel[2][0] = -1.0f / 8.0f; + kernel[2][1] = -1.0f / 8.0f; + kernel[2][2] = -1.0f / 8.0f; + break; + case CMP_FILT_SOBEL: + kernel[0][0] = 1.0f; + kernel[0][1] = 0.0f; + kernel[0][2] = -1.0f; + kernel[1][0] = 2.0f; + kernel[1][1] = 0.0f; + kernel[1][2] = -2.0f; + kernel[2][0] = 1.0f; + kernel[2][1] = 0.0f; + kernel[2][2] = -1.0f; + break; + case CMP_FILT_PREWITT: + kernel[0][0] = 1.0f; + kernel[0][1] = 0.0f; + kernel[0][2] = -1.0f; + kernel[1][0] = 1.0f; + kernel[1][1] = 0.0f; + kernel[1][2] = -1.0f; + kernel[2][0] = 1.0f; + kernel[2][1] = 0.0f; + kernel[2][2] = -1.0f; + break; + case CMP_FILT_KIRSCH: + kernel[0][0] = 5.0f; + kernel[0][1] = -3.0f; + kernel[0][2] = -2.0f; + kernel[1][0] = 5.0f; + kernel[1][1] = -3.0f; + kernel[1][2] = -2.0f; + kernel[2][0] = 5.0f; + kernel[2][1] = -3.0f; + kernel[2][2] = -2.0f; + break; + case CMP_FILT_SHADOW: + kernel[0][0] = 1.0f; + kernel[0][1] = 0.0f; + kernel[0][2] = -1.0f; + kernel[1][0] = 2.0f; + kernel[1][1] = 1.0f; + kernel[1][2] = -2.0f; + kernel[2][0] = 1.0f; + kernel[2][1] = 0.0f; + kernel[2][2] = -1.0f; + break; + case CMP_FILT_SHARP_DIAMOND: + kernel[0][0] = 0.0f; + kernel[0][1] = -1.0f; + kernel[0][2] = 0.0f; + kernel[1][0] = -1.0f; + kernel[1][1] = 5.0f; + kernel[1][2] = -1.0f; + kernel[2][0] = 0.0f; + kernel[2][1] = -1.0f; + kernel[2][2] = 0.0f; + break; + default: + kernel[0][0] = 0.0f; + kernel[0][1] = 0.0f; + kernel[0][2] = 0.0f; + kernel[1][0] = 0.0f; + kernel[1][1] = 1.0f; + kernel[1][2] = 0.0f; + kernel[2][0] = 0.0f; + kernel[2][1] = 0.0f; + kernel[2][2] = 0.0f; + break; + } + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new FilterOperation(context, node); +} + } // namespace blender::nodes::node_composite_filter_cc void register_node_type_cmp_filter() @@ -38,6 +202,7 @@ void register_node_type_cmp_filter() ntype.declare = file_ns::cmp_node_filter_declare; ntype.draw_buttons = file_ns::node_composit_buts_filter; ntype.labelfunc = node_filter_label; + ntype.get_compositor_operation = file_ns::get_compositor_operation; ntype.flag |= NODE_PREVIEW; nodeRegisterType(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_flip.cc b/source/blender/nodes/composite/nodes/node_composite_flip.cc index 37b9a2d020d..9a14f6dce90 100644 --- a/source/blender/nodes/composite/nodes/node_composite_flip.cc +++ b/source/blender/nodes/composite/nodes/node_composite_flip.cc @@ -5,9 +5,17 @@ * \ingroup cmpnodes */ +#include "BLI_assert.h" + #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" /* **************** Flip ******************** */ @@ -16,7 +24,9 @@ namespace blender::nodes::node_composite_flip_cc { static void cmp_node_flip_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Color>(N_("Image")); } @@ -25,6 +35,69 @@ static void node_composit_buts_flip(uiLayout *layout, bContext *UNUSED(C), Point uiItemR(layout, ptr, "axis", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); } +using namespace blender::realtime_compositor; + +class FlipOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + Result &input = get_input("Image"); + Result &result = get_result("Image"); + + /* Can't flip a single value, pass it through to the output. */ + if (input.is_single_value()) { + input.pass_through(result); + return; + } + + GPUShader *shader = get_flip_shader(); + GPU_shader_bind(shader); + + input.bind_as_texture(shader, "input_image"); + + const Domain domain = compute_domain(); + + result.allocate_texture(domain); + result.bind_as_image(shader, "output_image"); + + compute_dispatch_global(shader, domain.size); + + input.unbind_as_texture(); + result.unbind_as_image(); + GPU_shader_unbind(); + } + + GPUShader *get_flip_shader() + { + switch (get_flip_mode()) { + case 0: + return shader_pool().acquire("compositor_flip_x"); + case 1: + return shader_pool().acquire("compositor_flip_y"); + case 2: + return shader_pool().acquire("compositor_flip_x_and_y"); + } + + BLI_assert_unreachable(); + return nullptr; + } + + /* 0 -> Flip along x. + * 1 -> Flip along y. + * 2 -> Flip along both x and y. */ + int get_flip_mode() + { + return bnode().custom1; + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new FlipOperation(context, node); +} + } // namespace blender::nodes::node_composite_flip_cc void register_node_type_cmp_flip() @@ -36,6 +109,7 @@ void register_node_type_cmp_flip() cmp_node_type_base(&ntype, CMP_NODE_FLIP, "Flip", NODE_CLASS_DISTORT); ntype.declare = file_ns::cmp_node_flip_declare; ntype.draw_buttons = file_ns::node_composit_buts_flip; + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_gamma.cc b/source/blender/nodes/composite/nodes/node_composite_gamma.cc index b4b8502e915..660d8068231 100644 --- a/source/blender/nodes/composite/nodes/node_composite_gamma.cc +++ b/source/blender/nodes/composite/nodes/node_composite_gamma.cc @@ -5,6 +5,10 @@ * \ingroup cmpnodes */ +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** Gamma Tools ******************** */ @@ -13,15 +17,38 @@ namespace blender::nodes::node_composite_gamma_cc { static void cmp_node_gamma_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_input<decl::Float>(N_("Gamma")) .default_value(1.0f) .min(0.001f) .max(10.0f) - .subtype(PROP_UNSIGNED); + .subtype(PROP_UNSIGNED) + .compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); } +using namespace blender::realtime_compositor; + +class GammaShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_gamma", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new GammaShaderNode(node); +} + } // namespace blender::nodes::node_composite_gamma_cc void register_node_type_cmp_gamma() @@ -32,6 +59,7 @@ void register_node_type_cmp_gamma() cmp_node_type_base(&ntype, CMP_NODE_GAMMA, "Gamma", NODE_CLASS_OP_COLOR); ntype.declare = file_ns::cmp_node_gamma_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_hue_sat_val.cc b/source/blender/nodes/composite/nodes/node_composite_hue_sat_val.cc index 08a048829df..091864a06f7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_hue_sat_val.cc +++ b/source/blender/nodes/composite/nodes/node_composite_hue_sat_val.cc @@ -5,6 +5,10 @@ * \ingroup cmpnodes */ +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** Hue Saturation ******************** */ @@ -13,22 +17,56 @@ namespace blender::nodes::node_composite_hue_sat_val_cc { static void cmp_node_huesatval_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>(N_("Hue")).default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Float>(N_("Hue")) + .default_value(0.5f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .compositor_domain_priority(1); b.add_input<decl::Float>(N_("Saturation")) .default_value(1.0f) .min(0.0f) .max(2.0f) - .subtype(PROP_FACTOR); + .subtype(PROP_FACTOR) + .compositor_domain_priority(2); b.add_input<decl::Float>(N_("Value")) .default_value(1.0f) .min(0.0f) .max(2.0f) - .subtype(PROP_FACTOR); - b.add_input<decl::Float>(N_("Fac")).default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); + .subtype(PROP_FACTOR) + .compositor_domain_priority(3); + b.add_input<decl::Float>(N_("Fac")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .compositor_domain_priority(4); b.add_output<decl::Color>(N_("Image")); } +using namespace blender::realtime_compositor; + +class HueSaturationValueShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_hue_saturation_value", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new HueSaturationValueShaderNode(node); +} + } // namespace blender::nodes::node_composite_hue_sat_val_cc void register_node_type_cmp_hue_sat() @@ -39,6 +77,7 @@ void register_node_type_cmp_hue_sat() cmp_node_type_base(&ntype, CMP_NODE_HUE_SAT, "Hue Saturation Value", NODE_CLASS_OP_COLOR); ntype.declare = file_ns::cmp_node_huesatval_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc b/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc index bb5e6bf06a8..325eaee7243 100644 --- a/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc +++ b/source/blender/nodes/composite/nodes/node_composite_huecorrect.cc @@ -5,14 +5,27 @@ * \ingroup cmpnodes */ +#include "BKE_colortools.h" + +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" namespace blender::nodes::node_composite_huecorrect_cc { static void cmp_node_huecorrect_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Fac")).default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>(N_("Fac")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .compositor_domain_priority(1); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Color>(N_("Image")); } @@ -33,6 +46,53 @@ static void node_composit_init_huecorrect(bNodeTree *UNUSED(ntree), bNode *node) cumapping->cur = 1; } +using namespace blender::realtime_compositor; + +class HueCorrectShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + CurveMapping *curve_mapping = get_curve_mapping(); + + BKE_curvemapping_init(curve_mapping); + float *band_values; + int band_size; + BKE_curvemapping_table_RGBA(curve_mapping, &band_values, &band_size); + float band_layer; + GPUNodeLink *band_texture = GPU_color_band(material, band_size, band_values, &band_layer); + + float range_minimums[CM_TOT]; + BKE_curvemapping_get_range_minimums(curve_mapping, range_minimums); + float range_dividers[CM_TOT]; + BKE_curvemapping_compute_range_dividers(curve_mapping, range_dividers); + + GPU_stack_link(material, + &bnode(), + "node_composite_hue_correct", + inputs, + outputs, + band_texture, + GPU_constant(&band_layer), + GPU_uniform(range_minimums), + GPU_uniform(range_dividers)); + } + + CurveMapping *get_curve_mapping() + { + return static_cast<CurveMapping *>(bnode().storage); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new HueCorrectShaderNode(node); +} + } // namespace blender::nodes::node_composite_huecorrect_cc void register_node_type_cmp_huecorrect() @@ -46,6 +106,7 @@ void register_node_type_cmp_huecorrect() node_type_size(&ntype, 320, 140, 500); node_type_init(&ntype, file_ns::node_composit_init_huecorrect); node_type_storage(&ntype, "CurveMapping", node_free_curves, node_copy_curves); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_image.cc b/source/blender/nodes/composite/nodes/node_composite_image.cc index d071e9f13db..7cf05bb076e 100644 --- a/source/blender/nodes/composite/nodes/node_composite_image.cc +++ b/source/blender/nodes/composite/nodes/node_composite_image.cc @@ -8,23 +8,34 @@ #include "node_composite_util.hh" #include "BLI_linklist.h" +#include "BLI_math_vec_types.hh" #include "BLI_utildefines.h" #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_image.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_scene.h" +#include "DEG_depsgraph_query.h" + #include "DNA_scene_types.h" #include "RE_engine.h" +#include "RE_pipeline.h" #include "RNA_access.h" #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[] = { @@ -431,6 +442,170 @@ 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_pool().acquire(get_shader_name(identifier)); + GPU_shader_bind(shader); + + const int input_unit = GPU_shader_get_texture_binding(shader, "input_sampler"); + GPU_texture_bind(image_texture, input_unit); + + result.bind_as_image(shader, "output_image"); + + compute_dispatch_global(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(); + 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_convert_color_to_alpha"; + } + 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<ImageUser *>(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<RenderLayer *>(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<NodeImageLayer *>(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)); + } +}; + +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() @@ -444,6 +619,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; @@ -467,7 +643,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) { @@ -593,11 +769,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_pass_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_pool().acquire("compositor_convert_color_to_alpha"); + GPU_shader_bind(shader); + + const int input_unit = GPU_shader_get_texture_binding(shader, "input_sampler"); + GPU_texture_bind(pass_texture, input_unit); + + alpha_result.bind_as_image(shader, "output_image"); + + compute_dispatch_global(shader, size); + + GPU_shader_unbind(); + GPU_texture_unbind(pass_texture); + alpha_result.unbind_as_image(); + + for (const OutputSocketRef *output : node()->outputs()) { + if (output->identifier() == "Image" || output->identifier() == "Alpha") { + continue; + } + 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; @@ -606,6 +831,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_invert.cc b/source/blender/nodes/composite/nodes/node_composite_invert.cc index 6dff043537a..4bfcc7b6b9c 100644 --- a/source/blender/nodes/composite/nodes/node_composite_invert.cc +++ b/source/blender/nodes/composite/nodes/node_composite_invert.cc @@ -8,6 +8,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** INVERT ******************** */ @@ -16,8 +20,15 @@ namespace blender::nodes::node_composite_invert_cc { static void cmp_node_invert_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Fac")).default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_input<decl::Color>(N_("Color")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>(N_("Fac")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .compositor_domain_priority(1); + b.add_input<decl::Color>(N_("Color")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Color>(N_("Color")); } @@ -35,6 +46,45 @@ static void node_composit_buts_invert(uiLayout *layout, bContext *UNUSED(C), Poi uiItemR(col, ptr, "invert_alpha", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class InvertShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const float do_rgb = get_do_rgb(); + const float do_alpha = get_do_alpha(); + + GPU_stack_link(material, + &bnode(), + "node_composite_invert", + inputs, + outputs, + GPU_constant(&do_rgb), + GPU_constant(&do_alpha)); + } + + bool get_do_rgb() + { + return bnode().custom1 & CMP_CHAN_RGB; + } + + bool get_do_alpha() + { + return bnode().custom1 & CMP_CHAN_A; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new InvertShaderNode(node); +} + } // namespace blender::nodes::node_composite_invert_cc void register_node_type_cmp_invert() @@ -47,6 +97,7 @@ void register_node_type_cmp_invert() ntype.declare = file_ns::cmp_node_invert_declare; ntype.draw_buttons = file_ns::node_composit_buts_invert; node_type_init(&ntype, file_ns::node_composit_init_invert); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_lensdist.cc b/source/blender/nodes/composite/nodes/node_composite_lensdist.cc index 593b7cc9b71..e70fe4ddde0 100644 --- a/source/blender/nodes/composite/nodes/node_composite_lensdist.cc +++ b/source/blender/nodes/composite/nodes/node_composite_lensdist.cc @@ -5,20 +5,48 @@ * \ingroup cmpnodes */ +#include "BLI_math_base.h" +#include "BLI_math_vec_types.hh" + #include "RNA_access.h" #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" +/* Distortion can't be exactly -1.0 as it will cause infinite pincushion distortion. */ +#define MINIMUM_DISTORTION -0.999f +/* Arbitrary scaling factor for the dispersion input in projector distortion mode. */ +#define PROJECTOR_DISPERSION_SCALE 5.0f +/* Arbitrary scaling factor for the dispersion input in screen distortion mode. */ +#define SCREEN_DISPERSION_SCALE 4.0f +/* Arbitrary scaling factor for the distortion input. */ +#define DISTORTION_SCALE 4.0f + namespace blender::nodes::node_composite_lensdist_cc { static void cmp_node_lensdist_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>(N_("Distort")).default_value(0.0f).min(-0.999f).max(1.0f); - b.add_input<decl::Float>(N_("Dispersion")).default_value(0.0f).min(0.0f).max(1.0f); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Float>(N_("Distort")) + .default_value(0.0f) + .min(MINIMUM_DISTORTION) + .max(1.0f) + .compositor_expects_single_value(); + b.add_input<decl::Float>(N_("Dispersion")) + .default_value(0.0f) + .min(0.0f) + .max(1.0f) + .compositor_expects_single_value(); b.add_output<decl::Color>(N_("Image")); } @@ -42,6 +70,178 @@ static void node_composit_buts_lensdist(uiLayout *layout, bContext *UNUSED(C), P uiItemR(col, ptr, "use_fit", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class LensDistortionOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + if (is_identity()) { + get_input("Image").pass_through(get_result("Image")); + return; + } + + if (get_is_projector()) { + execute_projector_distortion(); + } + else { + execute_screen_distortion(); + } + } + + void execute_projector_distortion() + { + GPUShader *shader = shader_pool().acquire("compositor_projector_lens_distortion"); + GPU_shader_bind(shader); + + const Result &input_image = get_input("Image"); + input_image.bind_as_texture(shader, "input_image"); + + GPU_texture_filter_mode(input_image.texture(), true); + GPU_texture_wrap_mode(input_image.texture(), false, false); + + const Domain domain = compute_domain(); + + const float dispersion = (get_dispersion() * PROJECTOR_DISPERSION_SCALE) / domain.size.x; + GPU_shader_uniform_1f(shader, "dispersion", dispersion); + + Result &output_image = get_result("Image"); + output_image.allocate_texture(domain); + output_image.bind_as_image(shader, "output_image"); + + compute_dispatch_global(shader, domain.size); + + input_image.unbind_as_texture(); + output_image.unbind_as_image(); + GPU_shader_unbind(); + } + + void execute_screen_distortion() + { + GPUShader *shader = shader_pool().acquire(get_screen_distortion_shader()); + GPU_shader_bind(shader); + + const Result &input_image = get_input("Image"); + input_image.bind_as_texture(shader, "input_image"); + + GPU_texture_filter_mode(input_image.texture(), true); + GPU_texture_wrap_mode(input_image.texture(), false, false); + + const Domain domain = compute_domain(); + + const float3 chromatic_distortion = compute_chromatic_distortion(); + GPU_shader_uniform_3fv(shader, "chromatic_distortion", chromatic_distortion); + + GPU_shader_uniform_1f(shader, "scale", compute_scale()); + + Result &output_image = get_result("Image"); + output_image.allocate_texture(domain); + output_image.bind_as_image(shader, "output_image"); + + compute_dispatch_global(shader, domain.size); + + input_image.unbind_as_texture(); + output_image.unbind_as_image(); + GPU_shader_unbind(); + } + + const char *get_screen_distortion_shader() + { + if (get_is_jitter()) { + return "compositor_screen_lens_distortion_jitter"; + } + return "compositor_screen_lens_distortion"; + } + + float get_distortion() + { + const Result &input = get_input("Distort"); + return clamp_f(input.get_float_value_default(0.0f), MINIMUM_DISTORTION, 1.0f); + } + + float get_dispersion() + { + const Result &input = get_input("Dispersion"); + return clamp_f(input.get_float_value_default(0.0f), 0.0f, 1.0f); + } + + /* Get the distortion amount for each channel. The green channel has a distortion amount that + * matches that specified in the node inputs, while the red and blue channels have higher and + * lower distortion amounts respectively based on the dispersion value. */ + float3 compute_chromatic_distortion() + { + const float green_distortion = get_distortion(); + const float dispersion = get_dispersion() / SCREEN_DISPERSION_SCALE; + const float red_distortion = clamp_f(green_distortion + dispersion, MINIMUM_DISTORTION, 1.0f); + const float blue_distortion = clamp_f(green_distortion - dispersion, MINIMUM_DISTORTION, 1.0f); + return float3(red_distortion, green_distortion, blue_distortion) * DISTORTION_SCALE; + } + + /* The distortion model model will distort the image in such a way that the result will no longer + * fit the domain of the original image, so we scale the image to account for that. If get_is_fit + * is false, then the scaling factor will be such that the furthest pixels horizontally and + * vertically are at the boundary of the image. Otherwise, if get_is_fit is true, the scaling + * factor will be such that the furthest pixels diagonally are at the corner of the image. */ + float compute_scale() + { + const float3 distortion = compute_chromatic_distortion() / DISTORTION_SCALE; + const float maximum_distortion = max_fff(distortion[0], distortion[1], distortion[2]); + + if (get_is_fit() && (maximum_distortion > 0.0f)) { + return 1.0f / (1.0f + 2.0f * maximum_distortion); + } + return 1.0f / (1.0f + maximum_distortion); + } + + bool get_is_projector() + { + return get_node_lens_distortion().proj; + } + + bool get_is_jitter() + { + return get_node_lens_distortion().jit; + } + + bool get_is_fit() + { + return get_node_lens_distortion().fit; + } + + NodeLensDist &get_node_lens_distortion() + { + return *static_cast<NodeLensDist *>(bnode().storage); + } + + /* Returns true if the operation does nothing and the input can be passed through. */ + bool is_identity() + { + /* The input is a single value and the operation does nothing. */ + if (get_input("Image").is_single_value()) { + return true; + } + + /* Projector have zero dispersion and does nothing. */ + if (get_is_projector() && get_dispersion() == 0.0f) { + return true; + } + + /* Both distortion and dispersion are zero and the operation does nothing. */ + if (get_distortion() == 0.0f && get_dispersion() == 0.0f) { + return true; + } + + return false; + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new LensDistortionOperation(context, node); +} + } // namespace blender::nodes::node_composite_lensdist_cc void register_node_type_cmp_lensdist() @@ -56,6 +256,7 @@ void register_node_type_cmp_lensdist() node_type_init(&ntype, file_ns::node_composit_init_lensdist); node_type_storage( &ntype, "NodeLensDist", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_luma_matte.cc b/source/blender/nodes/composite/nodes/node_composite_luma_matte.cc index 94697a2aafd..092a12a7ea4 100644 --- a/source/blender/nodes/composite/nodes/node_composite_luma_matte.cc +++ b/source/blender/nodes/composite/nodes/node_composite_luma_matte.cc @@ -5,9 +5,15 @@ * \ingroup cmpnodes */ +#include "IMB_colormanagement.h" + #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* ******************* Luma Matte Node ********************************* */ @@ -16,7 +22,9 @@ namespace blender::nodes::node_composite_luma_matte_cc { static void cmp_node_luma_matte_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Color>(N_("Image")); b.add_output<decl::Float>(N_("Matte")); } @@ -40,6 +48,53 @@ static void node_composit_buts_luma_matte(uiLayout *layout, bContext *UNUSED(C), col, ptr, "limit_min", UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_SLIDER, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class LuminanceMatteShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const float high = get_high(); + const float low = get_low(); + float luminance_coefficients[3]; + IMB_colormanagement_get_luminance_coefficients(luminance_coefficients); + + GPU_stack_link(material, + &bnode(), + "node_composite_luminance_matte", + inputs, + outputs, + GPU_uniform(&high), + GPU_uniform(&low), + GPU_constant(luminance_coefficients)); + } + + NodeChroma *get_node_chroma() + { + return static_cast<NodeChroma *>(bnode().storage); + } + + float get_high() + { + return get_node_chroma()->t1; + } + + float get_low() + { + return get_node_chroma()->t2; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new LuminanceMatteShaderNode(node); +} + } // namespace blender::nodes::node_composite_luma_matte_cc void register_node_type_cmp_luma_matte() @@ -54,6 +109,7 @@ void register_node_type_cmp_luma_matte() ntype.flag |= NODE_PREVIEW; node_type_init(&ntype, file_ns::node_composit_init_luma_matte); node_type_storage(&ntype, "NodeChroma", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_map_range.cc b/source/blender/nodes/composite/nodes/node_composite_map_range.cc index e52c6d096b9..e72869efa93 100644 --- a/source/blender/nodes/composite/nodes/node_composite_map_range.cc +++ b/source/blender/nodes/composite/nodes/node_composite_map_range.cc @@ -8,6 +8,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** Map Range ******************** */ @@ -16,11 +20,31 @@ namespace blender::nodes::node_composite_map_range_cc { static void cmp_node_map_range_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Value")).default_value(1.0f).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("From Min")).default_value(0.0f).min(-10000.0f).max(10000.0f); - b.add_input<decl::Float>(N_("From Max")).default_value(1.0f).min(-10000.0f).max(10000.0f); - b.add_input<decl::Float>(N_("To Min")).default_value(0.0f).min(-10000.0f).max(10000.0f); - b.add_input<decl::Float>(N_("To Max")).default_value(1.0f).min(-10000.0f).max(10000.0f); + b.add_input<decl::Float>(N_("Value")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .compositor_domain_priority(0); + b.add_input<decl::Float>(N_("From Min")) + .default_value(0.0f) + .min(-10000.0f) + .max(10000.0f) + .compositor_domain_priority(1); + b.add_input<decl::Float>(N_("From Max")) + .default_value(1.0f) + .min(-10000.0f) + .max(10000.0f) + .compositor_domain_priority(2); + b.add_input<decl::Float>(N_("To Min")) + .default_value(0.0f) + .min(-10000.0f) + .max(10000.0f) + .compositor_domain_priority(3); + b.add_input<decl::Float>(N_("To Max")) + .default_value(1.0f) + .min(-10000.0f) + .max(10000.0f) + .compositor_domain_priority(4); b.add_output<decl::Float>(N_("Value")); } @@ -32,6 +56,38 @@ static void node_composit_buts_map_range(uiLayout *layout, bContext *UNUSED(C), uiItemR(col, ptr, "use_clamp", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class MapRangeShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const float should_clamp = get_should_clamp(); + + GPU_stack_link(material, + &bnode(), + "node_composite_map_range", + inputs, + outputs, + GPU_constant(&should_clamp)); + } + + bool get_should_clamp() + { + return bnode().custom1; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new MapRangeShaderNode(node); +} + } // namespace blender::nodes::node_composite_map_range_cc void register_node_type_cmp_map_range() @@ -43,6 +99,7 @@ void register_node_type_cmp_map_range() cmp_node_type_base(&ntype, CMP_NODE_MAP_RANGE, "Map Range", NODE_CLASS_OP_VECTOR); ntype.declare = file_ns::cmp_node_map_range_declare; ntype.draw_buttons = file_ns::node_composit_buts_map_range; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_map_value.cc b/source/blender/nodes/composite/nodes/node_composite_map_value.cc index b069cce93fc..3dd94cbc514 100644 --- a/source/blender/nodes/composite/nodes/node_composite_map_value.cc +++ b/source/blender/nodes/composite/nodes/node_composite_map_value.cc @@ -10,6 +10,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** MAP VALUE ******************** */ @@ -18,7 +22,11 @@ namespace blender::nodes::node_composite_map_value_cc { static void cmp_node_map_value_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Value")).default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Float>(N_("Value")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .compositor_domain_priority(0); b.add_output<decl::Float>(N_("Value")); } @@ -48,6 +56,56 @@ static void node_composit_buts_map_value(uiLayout *layout, bContext *UNUSED(C), uiItemR(sub, ptr, "max", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); } +using namespace blender::realtime_compositor; + +class MapValueShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const TexMapping *texture_mapping = get_texture_mapping(); + + const float use_min = get_use_min(); + const float use_max = get_use_max(); + + GPU_stack_link(material, + &bnode(), + "node_composite_map_value", + inputs, + outputs, + GPU_uniform(texture_mapping->loc), + GPU_uniform(texture_mapping->size), + GPU_constant(&use_min), + GPU_uniform(texture_mapping->min), + GPU_constant(&use_max), + GPU_uniform(texture_mapping->max)); + } + + TexMapping *get_texture_mapping() + { + return static_cast<TexMapping *>(bnode().storage); + } + + bool get_use_min() + { + return get_texture_mapping()->flag & TEXMAP_CLIP_MIN; + } + + bool get_use_max() + { + return get_texture_mapping()->flag & TEXMAP_CLIP_MAX; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new MapValueShaderNode(node); +} + } // namespace blender::nodes::node_composite_map_value_cc void register_node_type_cmp_map_value() @@ -61,6 +119,7 @@ void register_node_type_cmp_map_value() ntype.draw_buttons = file_ns::node_composit_buts_map_value; node_type_init(&ntype, file_ns::node_composit_init_map_value); node_type_storage(&ntype, "TexMapping", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_math.cc b/source/blender/nodes/composite/nodes/node_composite_math.cc index 7b2eadef2cb..536e8c35f38 100644 --- a/source/blender/nodes/composite/nodes/node_composite_math.cc +++ b/source/blender/nodes/composite/nodes/node_composite_math.cc @@ -5,6 +5,12 @@ * \ingroup cmpnodes */ +#include "GPU_material.h" + +#include "COM_shader_node.hh" + +#include "NOD_math_functions.hh" + #include "node_composite_util.hh" /* **************** SCALAR MATH ******************** */ @@ -13,18 +19,72 @@ namespace blender::nodes::node_composite_math_cc { static void cmp_node_math_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Value")).default_value(0.5f).min(-10000.0f).max(10000.0f); + b.add_input<decl::Float>(N_("Value")) + .default_value(0.5f) + .min(-10000.0f) + .max(10000.0f) + .compositor_domain_priority(0); b.add_input<decl::Float>(N_("Value"), "Value_001") .default_value(0.5f) .min(-10000.0f) - .max(10000.0f); + .max(10000.0f) + .compositor_domain_priority(1); b.add_input<decl::Float>(N_("Value"), "Value_002") .default_value(0.5f) .min(-10000.0f) - .max(10000.0f); + .max(10000.0f) + .compositor_domain_priority(2); b.add_output<decl::Float>(N_("Value")); } +using namespace blender::realtime_compositor; + +class MathShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), get_shader_function_name(), inputs, outputs); + + if (!get_should_clamp()) { + return; + } + + const float min = 0.0f; + const float max = 1.0f; + GPU_link(material, + "clamp_value", + outputs[0].link, + GPU_constant(&min), + GPU_constant(&max), + &outputs[0].link); + } + + int get_mode() + { + return bnode().custom1; + } + + const char *get_shader_function_name() + { + return get_float_math_operation_info(get_mode())->shader_name.c_str(); + } + + bool get_should_clamp() + { + return bnode().custom2 & SHD_MATH_CLAMP; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new MathShaderNode(node); +} + } // namespace blender::nodes::node_composite_math_cc void register_node_type_cmp_math() @@ -37,6 +97,7 @@ void register_node_type_cmp_math() ntype.declare = file_ns::cmp_node_math_declare; ntype.labelfunc = node_math_label; node_type_update(&ntype, node_math_update); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_mixrgb.cc b/source/blender/nodes/composite/nodes/node_composite_mixrgb.cc index fc11aa188b0..f2985db9a67 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mixrgb.cc +++ b/source/blender/nodes/composite/nodes/node_composite_mixrgb.cc @@ -5,6 +5,14 @@ * \ingroup cmpnodes */ +#include "BLI_assert.h" + +#include "DNA_material_types.h" + +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** MIX RGB ******************** */ @@ -13,12 +21,118 @@ namespace blender::nodes::node_composite_mixrgb_cc { static void cmp_node_mixrgb_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Fac")).default_value(1.0f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Color>(N_("Image"), "Image_001").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Float>(N_("Fac")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .compositor_domain_priority(2); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Color>(N_("Image"), "Image_001") + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); } +using namespace blender::realtime_compositor; + +class MixRGBShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), get_shader_function_name(), inputs, outputs); + + if (get_use_alpha()) { + GPU_link(material, "multiply_by_alpha", inputs[0].link, inputs[1].link, &inputs[0].link); + } + + if (!get_should_clamp()) { + return; + } + + const float min[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + const float max[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + GPU_link(material, + "clamp_color", + outputs[0].link, + GPU_constant(min), + GPU_constant(max), + &outputs[0].link); + } + + int get_mode() + { + return bnode().custom1; + } + + const char *get_shader_function_name() + { + switch (get_mode()) { + case MA_RAMP_BLEND: + return "mix_blend"; + case MA_RAMP_ADD: + return "mix_add"; + case MA_RAMP_MULT: + return "mix_mult"; + case MA_RAMP_SUB: + return "mix_sub"; + case MA_RAMP_SCREEN: + return "mix_screen"; + case MA_RAMP_DIV: + return "mix_div"; + case MA_RAMP_DIFF: + return "mix_diff"; + case MA_RAMP_DARK: + return "mix_dark"; + case MA_RAMP_LIGHT: + return "mix_light"; + case MA_RAMP_OVERLAY: + return "mix_overlay"; + case MA_RAMP_DODGE: + return "mix_dodge"; + case MA_RAMP_BURN: + return "mix_burn"; + case MA_RAMP_HUE: + return "mix_hue"; + case MA_RAMP_SAT: + return "mix_sat"; + case MA_RAMP_VAL: + return "mix_val"; + case MA_RAMP_COLOR: + return "mix_color"; + case MA_RAMP_SOFT: + return "mix_soft"; + case MA_RAMP_LINEAR: + return "mix_linear"; + } + + BLI_assert_unreachable(); + return nullptr; + } + + bool get_use_alpha() + { + return bnode().custom2 & SHD_MIXRGB_USE_ALPHA; + } + + bool get_should_clamp() + { + return bnode().custom2 & SHD_MIXRGB_CLAMP; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new MixRGBShaderNode(node); +} + } // namespace blender::nodes::node_composite_mixrgb_cc void register_node_type_cmp_mix_rgb() @@ -31,6 +145,7 @@ void register_node_type_cmp_mix_rgb() ntype.flag |= NODE_PREVIEW; ntype.declare = file_ns::cmp_node_mixrgb_declare; ntype.labelfunc = node_blend_label; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_movieclip.cc b/source/blender/nodes/composite/nodes/node_composite_movieclip.cc index a4d5f294fe0..76aed311049 100644 --- a/source/blender/nodes/composite/nodes/node_composite_movieclip.cc +++ b/source/blender/nodes/composite/nodes/node_composite_movieclip.cc @@ -5,8 +5,12 @@ * \ingroup cmpnodes */ +#include "BLI_math_vec_types.hh" + #include "BKE_context.h" #include "BKE_lib_id.h" +#include "BKE_movieclip.h" + #include "DNA_defaults.h" #include "RNA_access.h" @@ -14,6 +18,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 +89,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_pool().acquire("compositor_convert_color_to_half_color"); + GPU_shader_bind(shader); + + const int input_unit = GPU_shader_get_texture_binding(shader, "input_sampler"); + GPU_texture_bind(movie_clip_texture, input_unit); + + result.bind_as_image(shader, "output_image"); + + compute_dispatch_global(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_pool().acquire("compositor_convert_color_to_alpha"); + GPU_shader_bind(shader); + + const int input_unit = GPU_shader_get_texture_binding(shader, "input_sampler"); + GPU_texture_bind(movie_clip_texture, input_unit); + + result.bind_as_image(shader, "output_image"); + + compute_dispatch_global(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<MovieClipUser *>(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 +272,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_normal.cc b/source/blender/nodes/composite/nodes/node_composite_normal.cc index b4dd0bbacd0..f61ace01cfd 100644 --- a/source/blender/nodes/composite/nodes/node_composite_normal.cc +++ b/source/blender/nodes/composite/nodes/node_composite_normal.cc @@ -5,6 +5,10 @@ * \ingroup cmpnodes */ +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** NORMAL ******************** */ @@ -17,7 +21,8 @@ static void cmp_node_normal_declare(NodeDeclarationBuilder &b) .default_value({0.0f, 0.0f, 1.0f}) .min(-1.0f) .max(1.0f) - .subtype(PROP_DIRECTION); + .subtype(PROP_DIRECTION) + .compositor_domain_priority(0); b.add_output<decl::Vector>(N_("Normal")) .default_value({0.0f, 0.0f, 1.0f}) .min(-1.0f) @@ -26,6 +31,37 @@ static void cmp_node_normal_declare(NodeDeclarationBuilder &b) b.add_output<decl::Float>(N_("Dot")); } +using namespace blender::realtime_compositor; + +class NormalShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, + &bnode(), + "node_composite_normal", + inputs, + outputs, + GPU_uniform(get_vector_value())); + } + + /* The vector value is stored in the default value of the output socket. */ + float *get_vector_value() + { + return node().output_by_identifier("Normal")->default_value<bNodeSocketValueVector>()->value; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new NormalShaderNode(node); +} + } // namespace blender::nodes::node_composite_normal_cc void register_node_type_cmp_normal() @@ -36,6 +72,7 @@ void register_node_type_cmp_normal() cmp_node_type_base(&ntype, CMP_NODE_NORMAL, "Normal", NODE_CLASS_OP_VECTOR); ntype.declare = file_ns::cmp_node_normal_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_posterize.cc b/source/blender/nodes/composite/nodes/node_composite_posterize.cc index c97035d55ea..1268219e7e2 100644 --- a/source/blender/nodes/composite/nodes/node_composite_posterize.cc +++ b/source/blender/nodes/composite/nodes/node_composite_posterize.cc @@ -5,6 +5,10 @@ * \ingroup cmpnodes */ +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** Posterize ******************** */ @@ -13,11 +17,37 @@ namespace blender::nodes::node_composite_posterize_cc { static void cmp_node_posterize_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>(N_("Steps")).default_value(8.0f).min(2.0f).max(1024.0f); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Float>(N_("Steps")) + .default_value(8.0f) + .min(2.0f) + .max(1024.0f) + .compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); } +using namespace blender::realtime_compositor; + +class PosterizeShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_posterize", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new PosterizeShaderNode(node); +} + } // namespace blender::nodes::node_composite_posterize_cc void register_node_type_cmp_posterize() @@ -28,6 +58,7 @@ void register_node_type_cmp_posterize() cmp_node_type_base(&ntype, CMP_NODE_POSTERIZE, "Posterize", NODE_CLASS_OP_COLOR); ntype.declare = file_ns::cmp_node_posterize_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_premulkey.cc b/source/blender/nodes/composite/nodes/node_composite_premulkey.cc index 000cc9df90a..2b4f2863742 100644 --- a/source/blender/nodes/composite/nodes/node_composite_premulkey.cc +++ b/source/blender/nodes/composite/nodes/node_composite_premulkey.cc @@ -8,6 +8,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** Premul and Key Alpha Convert ******************** */ @@ -16,7 +20,9 @@ namespace blender::nodes::node_composite_premulkey_cc { static void cmp_node_premulkey_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Color>(N_("Image")); } @@ -25,6 +31,38 @@ static void node_composit_buts_premulkey(uiLayout *layout, bContext *UNUSED(C), uiItemR(layout, ptr, "mapping", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); } +using namespace blender::realtime_compositor; + +class AlphaConvertShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + if (get_mode() == 0) { + GPU_stack_link(material, &bnode(), "color_alpha_premultiply", inputs, outputs); + return; + } + + GPU_stack_link(material, &bnode(), "color_alpha_unpremultiply", inputs, outputs); + } + + /* 0 -> Premultiply Alpha. + * 1 -> Unpremultiply Alpha. */ + int get_mode() + { + return bnode().custom1; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new AlphaConvertShaderNode(node); +} + } // namespace blender::nodes::node_composite_premulkey_cc void register_node_type_cmp_premulkey() @@ -36,6 +74,7 @@ void register_node_type_cmp_premulkey() cmp_node_type_base(&ntype, CMP_NODE_PREMULKEY, "Alpha Convert", NODE_CLASS_CONVERTER); ntype.declare = file_ns::cmp_node_premulkey_declare; ntype.draw_buttons = file_ns::node_composit_buts_premulkey; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } 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<decl::Color>(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<bNodeSocket *>(bnode().outputs.first); + float4 color = float4(static_cast<bNodeSocketValueRGBA *>(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_rotate.cc b/source/blender/nodes/composite/nodes/node_composite_rotate.cc index a083bc1837b..35caa3cd242 100644 --- a/source/blender/nodes/composite/nodes/node_composite_rotate.cc +++ b/source/blender/nodes/composite/nodes/node_composite_rotate.cc @@ -5,9 +5,14 @@ * \ingroup cmpnodes */ +#include "BLI_assert.h" +#include "BLI_float3x3.hh" + #include "UI_interface.h" #include "UI_resources.h" +#include "COM_node_operation.hh" + #include "node_composite_util.hh" /* **************** Rotate ******************** */ @@ -16,12 +21,15 @@ namespace blender::nodes::node_composite_rotate_cc { static void cmp_node_rotate_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_input<decl::Float>(N_("Degr")) .default_value(0.0f) .min(-10000.0f) .max(10000.0f) - .subtype(PROP_ANGLE); + .subtype(PROP_ANGLE) + .compositor_expects_single_value(); b.add_output<decl::Color>(N_("Image")); } @@ -35,6 +43,47 @@ static void node_composit_buts_rotate(uiLayout *layout, bContext *UNUSED(C), Poi uiItemR(layout, ptr, "filter_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); } +using namespace blender::realtime_compositor; + +class RotateOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + Result &input = get_input("Image"); + Result &result = get_result("Image"); + input.pass_through(result); + + const float rotation = get_input("Degr").get_float_value_default(0.0f); + + const float3x3 transformation = float3x3::from_rotation(rotation); + + result.transform(transformation); + result.get_realization_options().interpolation = get_interpolation(); + } + + Interpolation get_interpolation() + { + switch (bnode().custom1) { + case 0: + return Interpolation::Nearest; + case 1: + return Interpolation::Bilinear; + case 2: + return Interpolation::Bicubic; + } + + BLI_assert_unreachable(); + return Interpolation::Nearest; + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new RotateOperation(context, node); +} + } // namespace blender::nodes::node_composite_rotate_cc void register_node_type_cmp_rotate() @@ -47,6 +96,7 @@ void register_node_type_cmp_rotate() ntype.declare = file_ns::cmp_node_rotate_declare; ntype.draw_buttons = file_ns::node_composit_buts_rotate; node_type_init(&ntype, file_ns::node_composit_init_rotate); + 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<decl::Float>(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<float>(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_sepcomb_hsva.cc b/source/blender/nodes/composite/nodes/node_composite_sepcomb_hsva.cc index a0d2485ea5a..ef160a9a50c 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcomb_hsva.cc +++ b/source/blender/nodes/composite/nodes/node_composite_sepcomb_hsva.cc @@ -5,57 +5,110 @@ * \ingroup cmpnodes */ +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** SEPARATE HSVA ******************** */ -namespace blender::nodes::node_composite_sepcomb_hsva_cc { +namespace blender::nodes::node_composite_separate_hsva_cc { static void cmp_node_sephsva_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Float>(N_("H")); b.add_output<decl::Float>(N_("S")); b.add_output<decl::Float>(N_("V")); b.add_output<decl::Float>(N_("A")); } -} // namespace blender::nodes::node_composite_sepcomb_hsva_cc +using namespace blender::realtime_compositor; + +class SeparateHSVAShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_separate_hsva", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new SeparateHSVAShaderNode(node); +} + +} // namespace blender::nodes::node_composite_separate_hsva_cc void register_node_type_cmp_sephsva() { - namespace file_ns = blender::nodes::node_composite_sepcomb_hsva_cc; + namespace file_ns = blender::nodes::node_composite_separate_hsva_cc; static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPHSVA_LEGACY, "Separate HSVA", NODE_CLASS_CONVERTER); ntype.declare = file_ns::cmp_node_sephsva_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; + nodeRegisterType(&ntype); } /* **************** COMBINE HSVA ******************** */ -namespace blender::nodes::node_composite_sepcomb_hsva_cc { +namespace blender::nodes::node_composite_combine_hsva_cc { static void cmp_node_combhsva_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("H")).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("S")).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("V")).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("A")).default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Float>(N_("H")).min(0.0f).max(1.0f).compositor_domain_priority(0); + b.add_input<decl::Float>(N_("S")).min(0.0f).max(1.0f).compositor_domain_priority(1); + b.add_input<decl::Float>(N_("V")).min(0.0f).max(1.0f).compositor_domain_priority(2); + b.add_input<decl::Float>(N_("A")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .compositor_domain_priority(3); b.add_output<decl::Color>(N_("Image")); } -} // namespace blender::nodes::node_composite_sepcomb_hsva_cc +using namespace blender::realtime_compositor; + +class CombineHSVAShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_combine_hsva", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new CombineHSVAShaderNode(node); +} + +} // namespace blender::nodes::node_composite_combine_hsva_cc void register_node_type_cmp_combhsva() { - namespace file_ns = blender::nodes::node_composite_sepcomb_hsva_cc; + namespace file_ns = blender::nodes::node_composite_combine_hsva_cc; static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBHSVA_LEGACY, "Combine HSVA", NODE_CLASS_CONVERTER); ntype.declare = file_ns::cmp_node_combhsva_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcomb_rgba.cc b/source/blender/nodes/composite/nodes/node_composite_sepcomb_rgba.cc index ae46681b0f4..c059f1eb6c1 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcomb_rgba.cc +++ b/source/blender/nodes/composite/nodes/node_composite_sepcomb_rgba.cc @@ -5,57 +5,110 @@ * \ingroup cmpnodes */ +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** SEPARATE RGBA ******************** */ -namespace blender::nodes::node_composite_sepcomb_rgba_cc { + +namespace blender::nodes::node_composite_separate_rgba_cc { static void cmp_node_seprgba_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Float>(N_("R")); b.add_output<decl::Float>(N_("G")); b.add_output<decl::Float>(N_("B")); b.add_output<decl::Float>(N_("A")); } -} // namespace blender::nodes::node_composite_sepcomb_rgba_cc +using namespace blender::realtime_compositor; + +class SeparateRGBAShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_separate_rgba", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new SeparateRGBAShaderNode(node); +} + +} // namespace blender::nodes::node_composite_separate_rgba_cc void register_node_type_cmp_seprgba() { - namespace file_ns = blender::nodes::node_composite_sepcomb_rgba_cc; + namespace file_ns = blender::nodes::node_composite_separate_rgba_cc; static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPRGBA_LEGACY, "Separate RGBA", NODE_CLASS_CONVERTER); ntype.declare = file_ns::cmp_node_seprgba_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } /* **************** COMBINE RGBA ******************** */ -namespace blender::nodes::node_composite_sepcomb_rgba_cc { +namespace blender::nodes::node_composite_combine_rgba_cc { static void cmp_node_combrgba_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("R")).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("G")).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("B")).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("A")).default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Float>(N_("R")).min(0.0f).max(1.0f).compositor_domain_priority(0); + b.add_input<decl::Float>(N_("G")).min(0.0f).max(1.0f).compositor_domain_priority(1); + b.add_input<decl::Float>(N_("B")).min(0.0f).max(1.0f).compositor_domain_priority(2); + b.add_input<decl::Float>(N_("A")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .compositor_domain_priority(3); b.add_output<decl::Color>(N_("Image")); } -} // namespace blender::nodes::node_composite_sepcomb_rgba_cc +using namespace blender::realtime_compositor; + +class CombineRGBAShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_combine_rgba", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new CombineRGBAShaderNode(node); +} + +} // namespace blender::nodes::node_composite_combine_rgba_cc void register_node_type_cmp_combrgba() { - namespace file_ns = blender::nodes::node_composite_sepcomb_rgba_cc; + namespace file_ns = blender::nodes::node_composite_combine_rgba_cc; static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBRGBA_LEGACY, "Combine RGBA", NODE_CLASS_CONVERTER); ntype.declare = file_ns::cmp_node_combrgba_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcomb_xyz.cc b/source/blender/nodes/composite/nodes/node_composite_sepcomb_xyz.cc index 4979c376cab..e288e698808 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcomb_xyz.cc +++ b/source/blender/nodes/composite/nodes/node_composite_sepcomb_xyz.cc @@ -5,10 +5,15 @@ * \ingroup cmpnodes */ +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** SEPARATE XYZ ******************** */ -namespace blender::nodes { + +namespace blender::nodes::node_composite_separate_xyz_cc { static void cmp_node_separate_xyz_declare(NodeDeclarationBuilder &b) { @@ -18,21 +23,44 @@ static void cmp_node_separate_xyz_declare(NodeDeclarationBuilder &b) b.add_output<decl::Float>("Z"); } -} // namespace blender::nodes +using namespace blender::realtime_compositor; + +class SeparateXYZShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_separate_xyz", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new SeparateXYZShaderNode(node); +} + +} // namespace blender::nodes::node_composite_separate_xyz_cc void register_node_type_cmp_separate_xyz() { + namespace file_ns = blender::nodes::node_composite_separate_xyz_cc; + static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPARATE_XYZ, "Separate XYZ", NODE_CLASS_CONVERTER); - ntype.declare = blender::nodes::cmp_node_separate_xyz_declare; + ntype.declare = file_ns::cmp_node_separate_xyz_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } /* **************** COMBINE XYZ ******************** */ -namespace blender::nodes { +namespace blender::nodes::node_composite_combine_xyz_cc { static void cmp_node_combine_xyz_declare(NodeDeclarationBuilder &b) { @@ -42,14 +70,37 @@ static void cmp_node_combine_xyz_declare(NodeDeclarationBuilder &b) b.add_output<decl::Vector>("Vector"); } -} // namespace blender::nodes +using namespace blender::realtime_compositor; + +class CombineXYZShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_combine_xyz", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new CombineXYZShaderNode(node); +} + +} // namespace blender::nodes::node_composite_combine_xyz_cc void register_node_type_cmp_combine_xyz() { + namespace file_ns = blender::nodes::node_composite_combine_xyz_cc; + static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBINE_XYZ, "Combine XYZ", NODE_CLASS_CONVERTER); - ntype.declare = blender::nodes::cmp_node_combine_xyz_declare; + ntype.declare = file_ns::cmp_node_combine_xyz_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcomb_ycca.cc b/source/blender/nodes/composite/nodes/node_composite_sepcomb_ycca.cc index a3c40b61e64..551e298a043 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcomb_ycca.cc +++ b/source/blender/nodes/composite/nodes/node_composite_sepcomb_ycca.cc @@ -5,15 +5,23 @@ * \ingroup cmpnodes */ +#include "BLI_assert.h" + +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** SEPARATE YCCA ******************** */ -namespace blender::nodes::node_composite_sepcomb_ycca_cc { +namespace blender::nodes::node_composite_separate_ycca_cc { static void cmp_node_sepycca_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Float>(N_("Y")); b.add_output<decl::Float>(N_("Cb")); b.add_output<decl::Float>(N_("Cr")); @@ -25,31 +33,84 @@ static void node_composit_init_mode_sepycca(bNodeTree *UNUSED(ntree), bNode *nod node->custom1 = 1; /* BLI_YCC_ITU_BT709 */ } -} // namespace blender::nodes::node_composite_sepcomb_ycca_cc +using namespace blender::realtime_compositor; + +class SeparateYCCAShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), get_shader_function_name(), inputs, outputs); + } + + int get_mode() + { + return bnode().custom1; + } + + const char *get_shader_function_name() + { + switch (get_mode()) { + case BLI_YCC_ITU_BT601: + return "node_composite_separate_ycca_itu_601"; + case BLI_YCC_ITU_BT709: + return "node_composite_separate_ycca_itu_709"; + case BLI_YCC_JFIF_0_255: + return "node_composite_separate_ycca_jpeg"; + } + + BLI_assert_unreachable(); + return nullptr; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new SeparateYCCAShaderNode(node); +} + +} // namespace blender::nodes::node_composite_separate_ycca_cc void register_node_type_cmp_sepycca() { - namespace file_ns = blender::nodes::node_composite_sepcomb_ycca_cc; + namespace file_ns = blender::nodes::node_composite_separate_ycca_cc; static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPYCCA_LEGACY, "Separate YCbCrA", NODE_CLASS_CONVERTER); ntype.declare = file_ns::cmp_node_sepycca_declare; node_type_init(&ntype, file_ns::node_composit_init_mode_sepycca); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } /* **************** COMBINE YCCA ******************** */ -namespace blender::nodes::node_composite_sepcomb_ycca_cc { +namespace blender::nodes::node_composite_combine_ycca_cc { static void cmp_node_combycca_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Y")).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("Cb")).default_value(0.5f).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("Cr")).default_value(0.5f).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("A")).default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Float>(N_("Y")).min(0.0f).max(1.0f).compositor_domain_priority(0); + b.add_input<decl::Float>(N_("Cb")) + .default_value(0.5f) + .min(0.0f) + .max(1.0f) + .compositor_domain_priority(1); + b.add_input<decl::Float>(N_("Cr")) + .default_value(0.5f) + .min(0.0f) + .max(1.0f) + .compositor_domain_priority(2); + b.add_input<decl::Float>(N_("A")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .compositor_domain_priority(3); b.add_output<decl::Color>(N_("Image")); } @@ -58,17 +119,58 @@ static void node_composit_init_mode_combycca(bNodeTree *UNUSED(ntree), bNode *no node->custom1 = 1; /* BLI_YCC_ITU_BT709 */ } -} // namespace blender::nodes::node_composite_sepcomb_ycca_cc +using namespace blender::realtime_compositor; + +class CombineYCCAShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), get_shader_function_name(), inputs, outputs); + } + + int get_mode() + { + return bnode().custom1; + } + + const char *get_shader_function_name() + { + switch (get_mode()) { + case BLI_YCC_ITU_BT601: + return "node_composite_combine_ycca_itu_601"; + case BLI_YCC_ITU_BT709: + return "node_composite_combine_ycca_itu_709"; + case BLI_YCC_JFIF_0_255: + return "node_composite_combine_ycca_jpeg"; + } + + BLI_assert_unreachable(); + return nullptr; + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new CombineYCCAShaderNode(node); +} + +} // namespace blender::nodes::node_composite_combine_ycca_cc void register_node_type_cmp_combycca() { - namespace file_ns = blender::nodes::node_composite_sepcomb_ycca_cc; + namespace file_ns = blender::nodes::node_composite_combine_ycca_cc; static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBYCCA_LEGACY, "Combine YCbCrA", NODE_CLASS_CONVERTER); ntype.declare = file_ns::cmp_node_combycca_declare; node_type_init(&ntype, file_ns::node_composit_init_mode_combycca); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_sepcomb_yuva.cc b/source/blender/nodes/composite/nodes/node_composite_sepcomb_yuva.cc index 7fdece5904d..87fffb52708 100644 --- a/source/blender/nodes/composite/nodes/node_composite_sepcomb_yuva.cc +++ b/source/blender/nodes/composite/nodes/node_composite_sepcomb_yuva.cc @@ -5,58 +5,110 @@ * \ingroup cmpnodes */ +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** SEPARATE YUVA ******************** */ -namespace blender::nodes::node_composite_sepcomb_yuva_cc { +namespace blender::nodes::node_composite_separate_yuva_cc { static void cmp_node_sepyuva_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Float>(N_("Y")); b.add_output<decl::Float>(N_("U")); b.add_output<decl::Float>(N_("V")); b.add_output<decl::Float>(N_("A")); } -} // namespace blender::nodes::node_composite_sepcomb_yuva_cc +using namespace blender::realtime_compositor; + +class SeparateYUVAShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_separate_yuva_itu_709", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new SeparateYUVAShaderNode(node); +} + +} // namespace blender::nodes::node_composite_separate_yuva_cc void register_node_type_cmp_sepyuva() { - namespace file_ns = blender::nodes::node_composite_sepcomb_yuva_cc; + namespace file_ns = blender::nodes::node_composite_separate_yuva_cc; static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_SEPYUVA_LEGACY, "Separate YUVA", NODE_CLASS_CONVERTER); ntype.declare = file_ns::cmp_node_sepyuva_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } /* **************** COMBINE YUVA ******************** */ -namespace blender::nodes::node_composite_sepcomb_yuva_cc { +namespace blender::nodes::node_composite_combine_yuva_cc { static void cmp_node_combyuva_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Y")).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("U")).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("V")).min(0.0f).max(1.0f); - b.add_input<decl::Float>(N_("A")).default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Float>(N_("Y")).min(0.0f).max(1.0f).compositor_domain_priority(0); + b.add_input<decl::Float>(N_("U")).min(0.0f).max(1.0f).compositor_domain_priority(1); + b.add_input<decl::Float>(N_("V")).min(0.0f).max(1.0f).compositor_domain_priority(2); + b.add_input<decl::Float>(N_("A")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .compositor_domain_priority(3); b.add_output<decl::Color>(N_("Image")); } -} // namespace blender::nodes::node_composite_sepcomb_yuva_cc +using namespace blender::realtime_compositor; + +class CombineYUVAShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + GPU_stack_link(material, &bnode(), "node_composite_combine_yuva_itu_709", inputs, outputs); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new CombineYUVAShaderNode(node); +} + +} // namespace blender::nodes::node_composite_combine_yuva_cc void register_node_type_cmp_combyuva() { - namespace file_ns = blender::nodes::node_composite_sepcomb_yuva_cc; + namespace file_ns = blender::nodes::node_composite_combine_yuva_cc; static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_COMBYUVA_LEGACY, "Combine YUVA", NODE_CLASS_CONVERTER); ntype.declare = file_ns::cmp_node_combyuva_declare; + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_setalpha.cc b/source/blender/nodes/composite/nodes/node_composite_setalpha.cc index 8aeaafbbf67..b4e98186974 100644 --- a/source/blender/nodes/composite/nodes/node_composite_setalpha.cc +++ b/source/blender/nodes/composite/nodes/node_composite_setalpha.cc @@ -8,6 +8,10 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** SET ALPHA ******************** */ @@ -16,8 +20,14 @@ namespace blender::nodes::node_composite_setalpha_cc { static void cmp_node_setalpha_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>(N_("Alpha")).default_value(1.0f).min(0.0f).max(1.0f); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Float>(N_("Alpha")) + .default_value(1.0f) + .min(0.0f) + .max(1.0f) + .compositor_domain_priority(1); b.add_output<decl::Color>(N_("Image")); } @@ -33,6 +43,38 @@ static void node_composit_buts_set_alpha(uiLayout *layout, bContext *UNUSED(C), uiItemR(layout, ptr, "mode", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class SetAlphaShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + const NodeSetAlpha *node_set_alpha = get_node_set_alpha(); + + if (node_set_alpha->mode == CMP_NODE_SETALPHA_MODE_APPLY) { + GPU_stack_link(material, &bnode(), "node_composite_set_alpha_apply", inputs, outputs); + return; + } + + GPU_stack_link(material, &bnode(), "node_composite_set_alpha_replace", inputs, outputs); + } + + NodeSetAlpha *get_node_set_alpha() + { + return static_cast<NodeSetAlpha *>(bnode().storage); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new SetAlphaShaderNode(node); +} + } // namespace blender::nodes::node_composite_setalpha_cc void register_node_type_cmp_setalpha() @@ -47,6 +89,7 @@ void register_node_type_cmp_setalpha() node_type_init(&ntype, file_ns::node_composit_init_setalpha); node_type_storage( &ntype, "NodeSetAlpha", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_split_viewer.cc b/source/blender/nodes/composite/nodes/node_composite_split_viewer.cc index ab325c4559f..a16421dfae3 100644 --- a/source/blender/nodes/composite/nodes/node_composite_split_viewer.cc +++ b/source/blender/nodes/composite/nodes/node_composite_split_viewer.cc @@ -11,6 +11,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" /* **************** SPLIT VIEWER ******************** */ @@ -43,6 +49,72 @@ static void node_composit_buts_splitviewer(uiLayout *layout, bContext *UNUSED(C) uiItemR(col, ptr, "factor", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class ViewerOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + GPUShader *shader = get_split_viewer_shader(); + GPU_shader_bind(shader); + + const int2 size = compute_domain().size; + + GPU_shader_uniform_1f(shader, "split_ratio", get_split_ratio()); + GPU_shader_uniform_2iv(shader, "view_size", size); + + const Result &first_image = get_input("Image"); + first_image.bind_as_texture(shader, "first_image"); + const Result &second_image = get_input("Image_001"); + second_image.bind_as_texture(shader, "second_image"); + + GPUTexture *viewport_texture = context().get_viewport_texture(); + const int image_unit = GPU_shader_get_texture_binding(shader, "output_image"); + GPU_texture_image_bind(viewport_texture, image_unit); + + compute_dispatch_global(shader, size); + + first_image.unbind_as_texture(); + second_image.unbind_as_texture(); + GPU_texture_image_unbind(viewport_texture); + GPU_shader_unbind(); + } + + /* The operation domain have the same dimensions of the viewport without any transformations. */ + Domain compute_domain() override + { + return Domain(context().get_viewport_size()); + } + + GPUShader *get_split_viewer_shader() + { + if (get_split_axis() == 0) { + return shader_pool().acquire("compositor_split_viewer_horizontal"); + } + + return shader_pool().acquire("compositor_split_viewer_vertical"); + } + + /* 0 -> Split Horizontal. + * 1 -> Split Vertical. */ + int get_split_axis() + { + return bnode().custom2; + } + + float get_split_ratio() + { + return bnode().custom1 / 100.0f; + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new ViewerOperation(context, node); +} + } // namespace blender::nodes::node_composite_split_viewer_cc void register_node_type_cmp_splitviewer() @@ -57,6 +129,7 @@ void register_node_type_cmp_splitviewer() ntype.flag |= NODE_PREVIEW; node_type_init(&ntype, file_ns::node_composit_init_splitviewer); node_type_storage(&ntype, "ImageUser", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_operation = file_ns::get_compositor_operation; ntype.no_muting = true; diff --git a/source/blender/nodes/composite/nodes/node_composite_switch.cc b/source/blender/nodes/composite/nodes/node_composite_switch.cc index bda490572e9..767802cc442 100644 --- a/source/blender/nodes/composite/nodes/node_composite_switch.cc +++ b/source/blender/nodes/composite/nodes/node_composite_switch.cc @@ -8,6 +8,8 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "COM_node_operation.hh" + #include "node_composite_util.hh" /* **************** Switch ******************** */ @@ -26,6 +28,30 @@ static void node_composit_buts_switch(uiLayout *layout, bContext *UNUSED(C), Poi uiItemR(layout, ptr, "check", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class SwitchOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + Result &input = get_input(get_condition() ? "On" : "Off"); + Result &result = get_result("Image"); + input.pass_through(result); + } + + bool get_condition() + { + return bnode().custom1; + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new SwitchOperation(context, node); +} + } // namespace blender::nodes::node_composite_switch_cc void register_node_type_cmp_switch() @@ -38,5 +64,7 @@ void register_node_type_cmp_switch() ntype.declare = file_ns::cmp_node_switch_declare; ntype.draw_buttons = file_ns::node_composit_buts_switch; 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_switchview.cc b/source/blender/nodes/composite/nodes/node_composite_switchview.cc index 2cf3da03a05..e74c3b6007a 100644 --- a/source/blender/nodes/composite/nodes/node_composite_switchview.cc +++ b/source/blender/nodes/composite/nodes/node_composite_switchview.cc @@ -11,6 +11,8 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "COM_node_operation.hh" + #include "node_composite_util.hh" /* **************** SWITCH VIEW ******************** */ @@ -140,6 +142,25 @@ static void node_composit_buts_switch_view_ex(uiLayout *layout, nullptr); } +using namespace blender::realtime_compositor; + +class SwitchViewOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + Result &input = get_input(context().get_view_name()); + Result &result = get_result("Image"); + input.pass_through(result); + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new SwitchViewOperation(context, node); +} + } // namespace blender::nodes::node_composite_switchview_cc void register_node_type_cmp_switch_view() @@ -153,6 +174,7 @@ void register_node_type_cmp_switch_view() ntype.draw_buttons_ex = file_ns::node_composit_buts_switch_view_ex; ntype.initfunc_api = file_ns::init_switch_view; node_type_update(&ntype, file_ns::cmp_node_switch_view_update); + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_transform.cc b/source/blender/nodes/composite/nodes/node_composite_transform.cc index fe72f5e33ca..7c5866d2d06 100644 --- a/source/blender/nodes/composite/nodes/node_composite_transform.cc +++ b/source/blender/nodes/composite/nodes/node_composite_transform.cc @@ -5,9 +5,15 @@ * \ingroup cmpnodes */ +#include "BLI_assert.h" +#include "BLI_float3x3.hh" +#include "BLI_math_vector.h" + #include "UI_interface.h" #include "UI_resources.h" +#include "COM_node_operation.hh" + #include "node_composite_util.hh" /* **************** Transform ******************** */ @@ -16,15 +22,30 @@ namespace blender::nodes::node_composite_transform_cc { static void cmp_node_transform_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({0.8f, 0.8f, 0.8f, 1.0f}); - b.add_input<decl::Float>(N_("X")).default_value(0.0f).min(-10000.0f).max(10000.0f); - b.add_input<decl::Float>(N_("Y")).default_value(0.0f).min(-10000.0f).max(10000.0f); + b.add_input<decl::Color>(N_("Image")) + .default_value({0.8f, 0.8f, 0.8f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Float>(N_("X")) + .default_value(0.0f) + .min(-10000.0f) + .max(10000.0f) + .compositor_expects_single_value(); + b.add_input<decl::Float>(N_("Y")) + .default_value(0.0f) + .min(-10000.0f) + .max(10000.0f) + .compositor_expects_single_value(); b.add_input<decl::Float>(N_("Angle")) .default_value(0.0f) .min(-10000.0f) .max(10000.0f) - .subtype(PROP_ANGLE); - b.add_input<decl::Float>(N_("Scale")).default_value(1.0f).min(0.0001f).max(CMP_SCALE_MAX); + .subtype(PROP_ANGLE) + .compositor_expects_single_value(); + b.add_input<decl::Float>(N_("Scale")) + .default_value(1.0f) + .min(0.0001f) + .max(CMP_SCALE_MAX) + .compositor_expects_single_value(); b.add_output<decl::Color>(N_("Image")); } @@ -33,6 +54,51 @@ static void node_composit_buts_transform(uiLayout *layout, bContext *UNUSED(C), uiItemR(layout, ptr, "filter_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); } +using namespace blender::realtime_compositor; + +class TransformOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + Result &input = get_input("Image"); + Result &result = get_result("Image"); + input.pass_through(result); + + const float2 translation = float2(get_input("X").get_float_value_default(0.0f), + get_input("Y").get_float_value_default(0.0f)); + const float rotation = get_input("Angle").get_float_value_default(0.0f); + const float2 scale = float2(get_input("Scale").get_float_value_default(1.0f)); + + const float3x3 transformation = float3x3::from_translation_rotation_scale( + translation, rotation, scale); + + result.transform(transformation); + result.get_realization_options().interpolation = get_interpolation(); + } + + Interpolation get_interpolation() + { + switch (bnode().custom1) { + case 0: + return Interpolation::Nearest; + case 1: + return Interpolation::Bilinear; + case 2: + return Interpolation::Bicubic; + } + + BLI_assert_unreachable(); + return Interpolation::Nearest; + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new TransformOperation(context, node); +} + } // namespace blender::nodes::node_composite_transform_cc void register_node_type_cmp_transform() @@ -44,6 +110,7 @@ void register_node_type_cmp_transform() cmp_node_type_base(&ntype, CMP_NODE_TRANSFORM, "Transform", NODE_CLASS_DISTORT); ntype.declare = file_ns::cmp_node_transform_declare; ntype.draw_buttons = file_ns::node_composit_buts_transform; + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_translate.cc b/source/blender/nodes/composite/nodes/node_composite_translate.cc index bbdc8ca4d31..fbd53b8310f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_translate.cc +++ b/source/blender/nodes/composite/nodes/node_composite_translate.cc @@ -5,9 +5,14 @@ * \ingroup cmpnodes */ +#include "BLI_float3x3.hh" +#include "BLI_math_vec_types.hh" + #include "UI_interface.h" #include "UI_resources.h" +#include "COM_node_operation.hh" + #include "node_composite_util.hh" /* **************** Translate ******************** */ @@ -16,9 +21,19 @@ namespace blender::nodes::node_composite_translate_cc { static void cmp_node_translate_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); - b.add_input<decl::Float>(N_("X")).default_value(0.0f).min(-10000.0f).max(10000.0f); - b.add_input<decl::Float>(N_("Y")).default_value(0.0f).min(-10000.0f).max(10000.0f); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); + b.add_input<decl::Float>(N_("X")) + .default_value(0.0f) + .min(-10000.0f) + .max(10000.0f) + .compositor_expects_single_value(); + b.add_input<decl::Float>(N_("Y")) + .default_value(0.0f) + .min(-10000.0f) + .max(10000.0f) + .compositor_expects_single_value(); b.add_output<decl::Color>(N_("Image")); } @@ -34,6 +49,59 @@ static void node_composit_buts_translate(uiLayout *layout, bContext *UNUSED(C), uiItemR(layout, ptr, "wrap_axis", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); } +using namespace blender::realtime_compositor; + +class TranslateOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + Result &input = get_input("Image"); + Result &result = get_result("Image"); + input.pass_through(result); + + float x = get_input("X").get_float_value_default(0.0f); + float y = get_input("Y").get_float_value_default(0.0f); + if (get_use_relative()) { + x *= input.domain().size.x; + y *= input.domain().size.y; + } + + const float2 translation = float2(x, y); + const float3x3 transformation = float3x3::from_translation(translation); + + result.transform(transformation); + result.get_realization_options().repeat_x = get_repeat_x(); + result.get_realization_options().repeat_y = get_repeat_y(); + } + + NodeTranslateData &get_node_translate() + { + return *static_cast<NodeTranslateData *>(bnode().storage); + } + + bool get_use_relative() + { + return get_node_translate().relative; + } + + bool get_repeat_x() + { + return ELEM(get_node_translate().wrap_axis, CMP_NODE_WRAP_X, CMP_NODE_WRAP_XY); + } + + bool get_repeat_y() + { + return ELEM(get_node_translate().wrap_axis, CMP_NODE_WRAP_Y, CMP_NODE_WRAP_XY); + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new TranslateOperation(context, node); +} + } // namespace blender::nodes::node_composite_translate_cc void register_node_type_cmp_translate() @@ -48,6 +116,7 @@ void register_node_type_cmp_translate() node_type_init(&ntype, file_ns::node_composit_init_translate); node_type_storage( &ntype, "NodeTranslateData", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc b/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc index f71028bf8c1..faf0fbb9311 100644 --- a/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc +++ b/source/blender/nodes/composite/nodes/node_composite_val_to_rgb.cc @@ -5,16 +5,31 @@ * \ingroup cmpnodes */ +#include "BLI_assert.h" + +#include "IMB_colormanagement.h" + +#include "BKE_colorband.h" + +#include "GPU_material.h" + +#include "COM_shader_node.hh" + #include "node_composite_util.hh" /* **************** VALTORGB ******************** */ -namespace blender::nodes::node_composite_val_to_rgb_cc { +namespace blender::nodes::node_composite_color_ramp_cc { static void cmp_node_valtorgb_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Float>(N_("Fac")).default_value(0.5f).min(0.0f).max(1.0f).subtype(PROP_FACTOR); - b.add_output<decl::Color>(N_("Image")); + b.add_input<decl::Float>(N_("Fac")) + .default_value(0.5f) + .min(0.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .compositor_domain_priority(1); + b.add_output<decl::Color>(N_("Image")).compositor_domain_priority(0); b.add_output<decl::Float>(N_("Alpha")); } @@ -23,11 +38,94 @@ static void node_composit_init_valtorgb(bNodeTree *UNUSED(ntree), bNode *node) node->storage = BKE_colorband_add(true); } -} // namespace blender::nodes::node_composite_val_to_rgb_cc +using namespace blender::realtime_compositor; + +class ColorRampShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + struct ColorBand *color_band = get_color_band(); + + /* Common / easy case optimization. */ + if ((color_band->tot <= 2) && (color_band->color_mode == COLBAND_BLEND_RGB)) { + float mul_bias[2]; + switch (color_band->ipotype) { + case COLBAND_INTERP_LINEAR: + mul_bias[0] = 1.0f / (color_band->data[1].pos - color_band->data[0].pos); + mul_bias[1] = -mul_bias[0] * color_band->data[0].pos; + GPU_stack_link(material, + &bnode(), + "valtorgb_opti_linear", + inputs, + outputs, + GPU_uniform(mul_bias), + GPU_uniform(&color_band->data[0].r), + GPU_uniform(&color_band->data[1].r)); + return; + case COLBAND_INTERP_CONSTANT: + mul_bias[1] = max_ff(color_band->data[0].pos, color_band->data[1].pos); + GPU_stack_link(material, + &bnode(), + "valtorgb_opti_constant", + inputs, + outputs, + GPU_uniform(&mul_bias[1]), + GPU_uniform(&color_band->data[0].r), + GPU_uniform(&color_band->data[1].r)); + return; + case COLBAND_INTERP_EASE: + mul_bias[0] = 1.0f / (color_band->data[1].pos - color_band->data[0].pos); + mul_bias[1] = -mul_bias[0] * color_band->data[0].pos; + GPU_stack_link(material, + &bnode(), + "valtorgb_opti_ease", + inputs, + outputs, + GPU_uniform(mul_bias), + GPU_uniform(&color_band->data[0].r), + GPU_uniform(&color_band->data[1].r)); + return; + default: + BLI_assert_unreachable(); + return; + } + } + + float *array, layer; + int size; + BKE_colorband_evaluate_table_rgba(color_band, &array, &size); + GPUNodeLink *tex = GPU_color_band(material, size, array, &layer); + + if (color_band->ipotype == COLBAND_INTERP_CONSTANT) { + GPU_stack_link( + material, &bnode(), "valtorgb_nearest", inputs, outputs, tex, GPU_constant(&layer)); + return; + } + + GPU_stack_link(material, &bnode(), "valtorgb", inputs, outputs, tex, GPU_constant(&layer)); + } + + struct ColorBand *get_color_band() + { + return static_cast<struct ColorBand *>(bnode().storage); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new ColorRampShaderNode(node); +} + +} // namespace blender::nodes::node_composite_color_ramp_cc void register_node_type_cmp_valtorgb() { - namespace file_ns = blender::nodes::node_composite_val_to_rgb_cc; + namespace file_ns = blender::nodes::node_composite_color_ramp_cc; static bNodeType ntype; @@ -36,31 +134,63 @@ void register_node_type_cmp_valtorgb() node_type_size(&ntype, 240, 200, 320); node_type_init(&ntype, file_ns::node_composit_init_valtorgb); node_type_storage(&ntype, "ColorBand", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; nodeRegisterType(&ntype); } /* **************** RGBTOBW ******************** */ -namespace blender::nodes::node_composite_val_to_rgb_cc { +namespace blender::nodes::node_composite_rgb_to_bw_cc { static void cmp_node_rgbtobw_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({0.8f, 0.8f, 0.8f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({0.8f, 0.8f, 0.8f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Color>(N_("Val")); } -} // namespace blender::nodes::node_composite_val_to_rgb_cc +using namespace blender::realtime_compositor; + +class RGBToBWShaderNode : public ShaderNode { + public: + using ShaderNode::ShaderNode; + + void compile(GPUMaterial *material) override + { + GPUNodeStack *inputs = get_inputs_array(); + GPUNodeStack *outputs = get_outputs_array(); + + float luminance_coefficients[3]; + IMB_colormanagement_get_luminance_coefficients(luminance_coefficients); + + GPU_stack_link(material, + &bnode(), + "color_to_luminance", + inputs, + outputs, + GPU_constant(luminance_coefficients)); + } +}; + +static ShaderNode *get_compositor_shader_node(DNode node) +{ + return new RGBToBWShaderNode(node); +} + +} // namespace blender::nodes::node_composite_rgb_to_bw_cc void register_node_type_cmp_rgbtobw() { - namespace file_ns = blender::nodes::node_composite_val_to_rgb_cc; + namespace file_ns = blender::nodes::node_composite_rgb_to_bw_cc; static bNodeType ntype; cmp_node_type_base(&ntype, CMP_NODE_RGBTOBW, "RGB to BW", NODE_CLASS_CONVERTER); ntype.declare = file_ns::cmp_node_rgbtobw_declare; node_type_size_preset(&ntype, NODE_SIZE_SMALL); + ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; 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<decl::Float>(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<bNodeSocket *>(bnode().outputs.first); + float value = static_cast<bNodeSocketValueFloat *>(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); } diff --git a/source/blender/nodes/composite/nodes/node_composite_viewer.cc b/source/blender/nodes/composite/nodes/node_composite_viewer.cc index 05f395183b5..de5fdf7e364 100644 --- a/source/blender/nodes/composite/nodes/node_composite_viewer.cc +++ b/source/blender/nodes/composite/nodes/node_composite_viewer.cc @@ -13,6 +13,11 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_state.h" +#include "GPU_texture.h" + +#include "COM_node_operation.hh" + #include "node_composite_util.hh" /* **************** VIEWER ******************** */ @@ -55,6 +60,43 @@ static void node_composit_buts_viewer_ex(uiLayout *layout, bContext *UNUSED(C), } } +using namespace blender::realtime_compositor; + +class ViewerOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + const Result &input_image = get_input("Image"); + GPUTexture *viewport_texture = context().get_viewport_texture(); + + /* If the input image is a texture, copy the input texture to the viewport texture. */ + if (input_image.is_texture()) { + /* Make sure any prior writes to the texture are reflected before copying it. */ + GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); + + GPU_texture_copy(viewport_texture, input_image.texture()); + } + else { + /* Otherwise, if the input image is a single color value, clear the viewport texture to that + * color. */ + GPU_texture_clear(viewport_texture, GPU_DATA_FLOAT, input_image.get_color_value()); + } + } + + /* The operation domain have the same dimensions of the viewport without any transformations. */ + Domain compute_domain() override + { + return Domain(context().get_viewport_size()); + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new ViewerOperation(context, node); +} + } // namespace blender::nodes::node_composite_viewer_cc void register_node_type_cmp_viewer() @@ -70,6 +112,7 @@ void register_node_type_cmp_viewer() ntype.flag |= NODE_PREVIEW; node_type_init(&ntype, file_ns::node_composit_init_viewer); node_type_storage(&ntype, "ImageUser", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_operation = file_ns::get_compositor_operation; ntype.no_muting = true; |