diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 046c9ee962e..8108c479703 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 79 +#define BLENDER_FILE_SUBVERSION 80 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index caa5e8c3080..a44aee470d8 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -680,10 +680,16 @@ static void write_compositor_legacy_properties(bNodeTree &node_tree) } for (bNode *node : node_tree.all_nodes()) { - auto write_input_to_property_bool_char = [&](const char *identifier, char &property) { - const bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, identifier); - property = input->default_value_typed()->value; - }; + auto write_input_to_property_bool_char = + [&](const char *identifier, char &property, const bool invert = false) { + const bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, identifier); + if (invert) { + property = !input->default_value_typed()->value; + } + else { + property = input->default_value_typed()->value; + } + }; auto write_input_to_property_bool_short = [&](const char *identifier, short &property) { const bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, identifier); @@ -1123,6 +1129,19 @@ static void write_compositor_legacy_properties(bNodeTree &node_tree) write_input_to_property_float("Output Temperature", storage->output_temperature); write_input_to_property_float("Output Tint", storage->output_tint); } + + if (node->type_legacy == CMP_NODE_BLUR) { + write_input_to_property_bool_int16_flag("Extend Bounds", node->custom1, (1 << 1)); + + NodeBlurData *storage = static_cast(node->storage); + write_input_to_property_bool_char("Separable", storage->bokeh, true); + + const bNodeSocket *size_input = blender::bke::node_find_socket(*node, SOCK_IN, "Size"); + storage->sizex = int( + math::ceil(size_input->default_value_typed()->value[0])); + storage->sizey = int( + math::ceil(size_input->default_value_typed()->value[1])); + } } } diff --git a/source/blender/blenloader/intern/versioning_450.cc b/source/blender/blenloader/intern/versioning_450.cc index 5c471c00115..f3d6e01bbc0 100644 --- a/source/blender/blenloader/intern/versioning_450.cc +++ b/source/blender/blenloader/intern/versioning_450.cc @@ -4085,6 +4085,186 @@ static void do_version_vector_sockets_dimensions(bNodeTree *node_tree) } } +/* The options were converted into inputs, but the Relative option was removed. If relative is + * enabled, we add Relative To Pixel nodes to convert the relative values to pixels. */ +static void do_version_blur_node_options_to_inputs(bNodeTree *node_tree, bNode *node) +{ + NodeBlurData *storage = static_cast(node->storage); + if (!storage) { + return; + } + + bNodeSocket *size_input = blender::bke::node_find_socket(*node, SOCK_IN, "Size"); + const float old_size = size_input->default_value_typed()->value; + + blender::bke::node_modify_socket_type_static( + node_tree, node, size_input, SOCK_VECTOR, PROP_NONE); + size_input->default_value_typed()->value[0] = old_size * storage->sizex; + size_input->default_value_typed()->value[1] = old_size * storage->sizey; + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Extend Bounds")) { + bNodeSocket *input = blender::bke::node_add_static_socket( + *node_tree, *node, SOCK_IN, SOCK_BOOLEAN, PROP_NONE, "Extend Bounds", "Extend Bounds"); + input->default_value_typed()->value = bool(node->custom1 & (1 << 1)); + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Separable")) { + bNodeSocket *input = blender::bke::node_add_static_socket( + *node_tree, *node, SOCK_IN, SOCK_BOOLEAN, PROP_NONE, "Separable", "Separable"); + input->default_value_typed()->value = !bool(storage->bokeh); + } + + /* Find links going into the node. */ + bNodeLink *image_link = nullptr; + bNodeLink *size_link = nullptr; + LISTBASE_FOREACH (bNodeLink *, link, &node_tree->links) { + if (link->tonode != node) { + continue; + } + + if (blender::StringRef(link->tosock->identifier) == "Image") { + image_link = link; + } + + if (blender::StringRef(link->tosock->identifier) == "Size") { + size_link = link; + } + } + + if (size_link) { + bNode *multiply_node = blender::bke::node_add_node( + nullptr, *node_tree, "ShaderNodeVectorMath"); + multiply_node->parent = node->parent; + multiply_node->location[0] = node->location[0] - node->width - 40.0f; + multiply_node->location[1] = node->location[1]; + + multiply_node->custom1 = NODE_VECTOR_MATH_SCALE; + + bNodeSocket *vector_input = blender::bke::node_find_socket(*multiply_node, SOCK_IN, "Vector"); + bNodeSocket *scale_input = blender::bke::node_find_socket(*multiply_node, SOCK_IN, "Scale"); + bNodeSocket *vector_output = blender::bke::node_find_socket( + *multiply_node, SOCK_OUT, "Vector"); + + if (storage->relative) { + vector_input->default_value_typed()->value[0] = storage->percentx / + 100.0f; + vector_input->default_value_typed()->value[1] = storage->percenty / + 100.0f; + } + else { + vector_input->default_value_typed()->value[0] = storage->sizex; + vector_input->default_value_typed()->value[1] = storage->sizey; + } + + version_node_add_link( + *node_tree, *size_link->fromnode, *size_link->fromsock, *multiply_node, *scale_input); + bNodeLink &new_link = version_node_add_link( + *node_tree, *multiply_node, *vector_output, *node, *size_input); + blender::bke::node_remove_link(node_tree, *size_link); + size_link = &new_link; + } + + /* If Relative is not enabled or no image is connected, nothing else to do. */ + if (!bool(storage->relative) || !image_link) { + return; + } + + bNode *relative_to_pixel_node = blender::bke::node_add_node( + nullptr, *node_tree, "CompositorNodeRelativeToPixel"); + relative_to_pixel_node->parent = node->parent; + relative_to_pixel_node->location[0] = node->location[0] - node->width - 20.0f; + relative_to_pixel_node->location[1] = node->location[1]; + + relative_to_pixel_node->custom1 = CMP_NODE_RELATIVE_TO_PIXEL_DATA_TYPE_VECTOR; + switch (storage->aspect) { + case CMP_NODE_BLUR_ASPECT_Y: + relative_to_pixel_node->custom2 = CMP_NODE_RELATIVE_TO_PIXEL_REFERENCE_DIMENSION_Y; + break; + case CMP_NODE_BLUR_ASPECT_X: + relative_to_pixel_node->custom2 = CMP_NODE_RELATIVE_TO_PIXEL_REFERENCE_DIMENSION_X; + break; + case CMP_NODE_BLUR_ASPECT_NONE: + relative_to_pixel_node->custom2 = + CMP_NODE_RELATIVE_TO_PIXEL_REFERENCE_DIMENSION_PER_DIMENSION; + break; + default: + BLI_assert_unreachable(); + break; + } + + bNodeSocket *image_input = blender::bke::node_find_socket( + *relative_to_pixel_node, SOCK_IN, "Image"); + bNodeSocket *vector_input = blender::bke::node_find_socket( + *relative_to_pixel_node, SOCK_IN, "Vector Value"); + bNodeSocket *vector_output = blender::bke::node_find_socket( + *relative_to_pixel_node, SOCK_OUT, "Vector Value"); + + version_node_add_link(*node_tree, + *image_link->fromnode, + *image_link->fromsock, + *relative_to_pixel_node, + *image_input); + if (size_link) { + version_node_add_link(*node_tree, + *size_link->fromnode, + *size_link->fromsock, + *relative_to_pixel_node, + *vector_input); + blender::bke::node_remove_link(node_tree, *size_link); + } + else { + vector_input->default_value_typed()->value[0] = (storage->percentx / + 100.0f) * + old_size; + vector_input->default_value_typed()->value[1] = (storage->percenty / + 100.0f) * + old_size; + } + version_node_add_link(*node_tree, *relative_to_pixel_node, *vector_output, *node, *size_input); +} + +/* The options were converted into inputs. */ +static void do_version_blur_node_options_to_inputs_animation(bNodeTree *node_tree, bNode *node) +{ + /* Compute the RNA path of the node. */ + char escaped_node_name[sizeof(node->name) * 2 + 1]; + BLI_str_escape(escaped_node_name, node->name, sizeof(escaped_node_name)); + const std::string node_rna_path = fmt::format("nodes[\"{}\"]", escaped_node_name); + + BKE_fcurves_id_cb(&node_tree->id, [&](ID * /*id*/, FCurve *fcurve) { + /* The FCurve does not belong to the node since its RNA path doesn't start with the node's RNA + * path. */ + if (!blender::StringRef(fcurve->rna_path).startswith(node_rna_path)) { + return; + } + + /* Change the RNA path of the FCurve from the old properties to the new inputs, adjusting the + * values of the FCurves frames when needed. */ + char *old_rna_path = fcurve->rna_path; + if (BLI_str_endswith(fcurve->rna_path, "size_x")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[1].default_value"); + fcurve->array_index = 0; + } + else if (BLI_str_endswith(fcurve->rna_path, "size_y")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[1].default_value"); + fcurve->array_index = 1; + } + else if (BLI_str_endswith(fcurve->rna_path, "use_extended_bounds")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[2].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "use_bokeh")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[3].default_value"); + adjust_fcurve_key_frame_values( + fcurve, PROP_BOOLEAN, [&](const float value) { return 1.0f - value; }); + } + + /* The RNA path was changed, free the old path. */ + if (fcurve->rna_path != old_rna_path) { + MEM_freeN(old_rna_path); + } + }); +} + void do_versions_after_linking_450(FileData * /*fd*/, Main *bmain) { if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 8)) { @@ -4652,7 +4832,7 @@ void do_versions_after_linking_450(FileData * /*fd*/, Main *bmain) FOREACH_NODETREE_END; } - if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 74)) { + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 75)) { FOREACH_NODETREE_BEGIN (bmain, node_tree, id) { if (node_tree->type == NTREE_COMPOSIT) { LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) { @@ -4688,6 +4868,19 @@ void do_versions_after_linking_450(FileData * /*fd*/, Main *bmain) FOREACH_NODETREE_END; } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 80)) { + FOREACH_NODETREE_BEGIN (bmain, node_tree, id) { + if (node_tree->type == NTREE_COMPOSIT) { + LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) { + if (node->type_legacy == CMP_NODE_BLUR) { + do_version_blur_node_options_to_inputs_animation(node_tree, node); + } + } + } + } + FOREACH_NODETREE_END; + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. @@ -5862,6 +6055,19 @@ void blo_do_versions_450(FileData * /*fd*/, Library * /*lib*/, Main *bmain) FOREACH_NODETREE_END; } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 80)) { + FOREACH_NODETREE_BEGIN (bmain, node_tree, id) { + if (node_tree->type == NTREE_COMPOSIT) { + LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) { + if (node->type_legacy == CMP_NODE_BLUR) { + do_version_blur_node_options_to_inputs(node_tree, node); + } + } + } + } + FOREACH_NODETREE_END; + } + /* Always run this versioning (keep at the bottom of the function). Meshes are written with the * legacy format which always needs to be converted to the new format on file load. To be moved * to a subversion check in 5.0. */ diff --git a/source/blender/compositor/algorithms/COM_algorithm_parallel_reduction.hh b/source/blender/compositor/algorithms/COM_algorithm_parallel_reduction.hh index 63824e721b4..109fea0fa04 100644 --- a/source/blender/compositor/algorithms/COM_algorithm_parallel_reduction.hh +++ b/source/blender/compositor/algorithms/COM_algorithm_parallel_reduction.hh @@ -78,6 +78,9 @@ float maximum_luminance(Context &context, /* Computes the maximum float value of all pixels in the given result. */ float maximum_float(Context &context, const Result &result); +/* Computes the maximum float3 value of all pixels in the given result. */ +float3 maximum_float3(Context &context, const Result &result); + /* Computes the maximum float of all pixels in the given float result, limited to the given range. * Values outside of the given range are ignored. If non of the pixel values are in the range, the * lower bound of the range is returned. For instance, if the given range is [-10, 10] and the diff --git a/source/blender/compositor/algorithms/intern/parallel_reduction.cc b/source/blender/compositor/algorithms/intern/parallel_reduction.cc index cbc11e5e18d..99310e6c473 100644 --- a/source/blender/compositor/algorithms/intern/parallel_reduction.cc +++ b/source/blender/compositor/algorithms/intern/parallel_reduction.cc @@ -583,6 +583,40 @@ float maximum_float(Context &context, const Result &result) return maximum_float_cpu(result); } +static float3 maximum_float3_gpu(Context &context, const Result &result) +{ + GPUShader *shader = context.get_shader("compositor_maximum_float3", ResultPrecision::Full); + GPU_shader_bind(shader); + + float *reduced_value = parallel_reduction_dispatch( + result, shader, Result::gpu_texture_format(ResultType::Float3, ResultPrecision::Full)); + const float3 maximum = reduced_value; + MEM_freeN(reduced_value); + GPU_shader_unbind(); + + return maximum; +} + +static float3 maximum_float3_cpu(const Result &result) +{ + return parallel_reduce( + result.domain().size, + float3(std::numeric_limits::lowest()), + [&](const int2 texel, float3 &accumulated_value) { + accumulated_value = math::max(accumulated_value, result.load_pixel(texel)); + }, + [&](const float3 &a, const float3 &b) { return math::max(a, b); }); +} + +float3 maximum_float3(Context &context, const Result &result) +{ + if (context.use_gpu()) { + return maximum_float3_gpu(context, result); + } + + return maximum_float3_cpu(result); +} + static float maximum_float_in_range_gpu(Context &context, const Result &result, const float lower_bound, diff --git a/source/blender/compositor/shaders/compositor_symmetric_blur_variable_size.glsl b/source/blender/compositor/shaders/compositor_symmetric_blur_variable_size.glsl index 3f7c088605a..bda294cf4ea 100644 --- a/source/blender/compositor/shaders/compositor_symmetric_blur_variable_size.glsl +++ b/source/blender/compositor/shaders/compositor_symmetric_blur_variable_size.glsl @@ -29,11 +29,11 @@ float4 load_input(int2 texel) /* Similar to load_input but loads the size instead and clamps to borders instead of returning zero * for out of bound access. See load_input for more information. */ -float load_size(int2 texel) +float2 load_size(int2 texel) { int2 blur_radius = texture_size(weights_tx) - 1; int2 offset = extend_bounds ? blur_radius : int2(0); - return clamp(texture_load(size_tx, texel - offset).x, 0.0f, 1.0f); + return max(float2(0.0), texture_load(size_tx, texel - offset).xy); } void main() @@ -43,13 +43,9 @@ void main() float4 accumulated_color = float4(0.0f); float4 accumulated_weight = float4(0.0f); - /* The weights texture only stores the weights for the first quadrant, but since the weights are - * symmetric, other quadrants can be found using mirroring. It follows that the base blur radius - * is the weights texture size minus one, where the one corresponds to the zero weight. */ - int2 weights_size = texture_size(weights_tx); - int2 base_radius = weights_size - int2(1); - int2 radius = int2(ceil(float2(base_radius) * load_size(texel))); - float2 coordinates_scale = float2(1.0f) / float2(radius + int2(1)); + const float2 size = load_size(texel); + int2 radius = int2(ceil(size)); + float2 coordinates_scale = float2(1.0f) / (size + float2(1)); /* First, compute the contribution of the center pixel. */ float4 center_color = load_input(texel); diff --git a/source/blender/compositor/shaders/infos/compositor_parallel_reduction_info.hh b/source/blender/compositor/shaders/infos/compositor_parallel_reduction_info.hh index 57c38b127b8..d1c7bc549f5 100644 --- a/source/blender/compositor/shaders/infos/compositor_parallel_reduction_info.hh +++ b/source/blender/compositor/shaders/infos/compositor_parallel_reduction_info.hh @@ -147,6 +147,17 @@ DEFINE_VALUE("REDUCE(lhs, rhs)", "max(rhs, lhs)") DO_STATIC_COMPILATION() GPU_SHADER_CREATE_END() +GPU_SHADER_CREATE_INFO(compositor_maximum_float3) +ADDITIONAL_INFO(compositor_parallel_reduction_shared) +IMAGE(0, GPU_RGBA32F, write, image2D, output_img) +DEFINE_VALUE("TYPE", "vec4") +DEFINE_VALUE("IDENTITY", "vec4(FLT_MIN)") +DEFINE_VALUE("INITIALIZE(value)", "value") +DEFINE_VALUE("LOAD(value)", "value") +DEFINE_VALUE("REDUCE(lhs, rhs)", "max(rhs, lhs)") +DO_STATIC_COMPILATION() +GPU_SHADER_CREATE_END() + GPU_SHADER_CREATE_INFO(compositor_maximum_float_in_range) ADDITIONAL_INFO(compositor_parallel_reduction_shared) IMAGE(0, GPU_R32F, write, image2D, output_img) diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 50d6f800bb1..1fa46177e98 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1092,10 +1092,6 @@ typedef enum CMPNodeMaskFlags { CMP_NODE_MASK_FLAG_SIZE_FIXED_SCENE = (1 << 9), } CMPNodeMaskFlags; -enum { - CMP_NODEFLAG_BLUR_EXTEND_BOUNDS = (1 << 1), -}; - typedef struct NodeFrame { short flag; short label_size; diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 9b7eee940b1..5e32f722e66 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -3935,6 +3935,52 @@ static void rna_NodeCrop_min_y_set(PointerRNA *ptr, const int value) value - RNA_int_get(&y_input_rna_pointer, "default_value")); } +static bool rna_NodeBlur_use_bokeh_get(PointerRNA *ptr) +{ + bNode *node = ptr->data_as(); + bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, "Separable"); + PointerRNA input_rna_pointer = RNA_pointer_create_discrete( + ptr->owner_id, &RNA_NodeSocket, input); + return !RNA_boolean_get(&input_rna_pointer, "default_value"); +} + +static void rna_NodeCrop_use_bokeh_set(PointerRNA *ptr, const bool value) +{ + bNode *node = ptr->data_as(); + bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, "Separable"); + PointerRNA input_rna_pointer = RNA_pointer_create_discrete( + ptr->owner_id, &RNA_NodeSocket, input); + RNA_boolean_set(&input_rna_pointer, "default_value", !value); +} + +static int rna_NodeBlur_size_x_get(PointerRNA *ptr) +{ + bNode *node = ptr->data_as(); + bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, "Size"); + return int(input->default_value_typed()->value[0]); +} + +static void rna_NodeCrop_size_x_set(PointerRNA *ptr, const int value) +{ + bNode *node = ptr->data_as(); + bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, "Size"); + input->default_value_typed()->value[0] = float(value); +} + +static int rna_NodeBlur_size_y_get(PointerRNA *ptr) +{ + bNode *node = ptr->data_as(); + bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, "Size"); + return int(input->default_value_typed()->value[1]); +} + +static void rna_NodeCrop_size_y_set(PointerRNA *ptr, const int value) +{ + bNode *node = ptr->data_as(); + bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, "Size"); + input->default_value_typed()->value[1] = float(value); +} + /* A getter that returns the value of the input socket with the given template identifier and type. * The RNA pointer is assumed to represent a node. */ template @@ -7381,53 +7427,61 @@ static void def_cmp_blur(BlenderRNA * /*brna*/, StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "use_extended_bounds", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, nullptr, "custom1", CMP_NODEFLAG_BLUR_EXTEND_BOUNDS); - RNA_def_property_ui_text( - prop, "Extend Bounds", "Extend bounds of the input image to fully fit blurred image"); + RNA_def_property_boolean_funcs( + prop, + "rna_node_property_to_input_getter", + "rna_node_property_to_input_setter"); + RNA_def_property_ui_text(prop, + "Extend Bounds", + "Extend bounds of the input image to fully fit blurred image. " + "(Deprecated: Use Extend Bounds input instead.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); RNA_def_struct_sdna_from(srna, "NodeBlurData", "storage"); prop = RNA_def_property(srna, "size_x", PROP_INT, PROP_NONE); - RNA_def_property_int_sdna(prop, nullptr, "sizex"); + RNA_def_property_int_funcs(prop, "rna_NodeBlur_size_x_get", "rna_NodeCrop_size_x_set", nullptr); RNA_def_property_range(prop, 0, 2048); - RNA_def_property_ui_text(prop, "Size X", ""); + RNA_def_property_ui_text(prop, "Size X", "(Deprecated: Use Size input instead.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "size_y", PROP_INT, PROP_NONE); - RNA_def_property_int_sdna(prop, nullptr, "sizey"); + RNA_def_property_int_funcs(prop, "rna_NodeBlur_size_y_get", "rna_NodeCrop_size_y_set", nullptr); RNA_def_property_range(prop, 0, 2048); - RNA_def_property_ui_text(prop, "Size Y", ""); + RNA_def_property_ui_text(prop, "Size Y", "(Deprecated: Use Size input instead.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "use_relative", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "relative", 1); RNA_def_property_ui_text( - prop, "Relative", "Use relative (percent) values to define blur radius"); + prop, + "Relative", + "Use relative (percent) values to define blur radius. (Deprecated: Unused.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "aspect_correction", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, nullptr, "aspect"); RNA_def_property_enum_items(prop, aspect_correction_type_items); - RNA_def_property_ui_text(prop, "Aspect Correction", "Type of aspect correction to use"); + RNA_def_property_ui_text( + prop, "Aspect Correction", "Type of aspect correction to use. (Deprecated: Unused.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "factor", PROP_FLOAT, PROP_NONE); RNA_def_property_float_sdna(prop, nullptr, "fac"); RNA_def_property_range(prop, 0.0f, 2.0f); - RNA_def_property_ui_text(prop, "Factor", ""); + RNA_def_property_ui_text(prop, "Factor", "(Deprecated: Unused.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "factor_x", PROP_FLOAT, PROP_PERCENTAGE); RNA_def_property_float_sdna(prop, nullptr, "percentx"); RNA_def_property_range(prop, 0.0f, 100.0f); - RNA_def_property_ui_text(prop, "Relative Size X", ""); + RNA_def_property_ui_text(prop, "Relative Size X", "(Deprecated: Unused.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "factor_y", PROP_FLOAT, PROP_PERCENTAGE); RNA_def_property_float_sdna(prop, nullptr, "percenty"); RNA_def_property_range(prop, 0.0f, 100.0f); - RNA_def_property_ui_text(prop, "Relative Size Y", ""); + RNA_def_property_ui_text(prop, "Relative Size Y", "(Deprecated: Unused.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "filter_type", PROP_ENUM, PROP_NONE); @@ -7438,8 +7492,9 @@ static void def_cmp_blur(BlenderRNA * /*brna*/, StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "use_bokeh", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, nullptr, "bokeh", 1); - RNA_def_property_ui_text(prop, "Bokeh", "Use circular filter (slower)"); + RNA_def_property_boolean_funcs(prop, "rna_NodeBlur_use_bokeh_get", "rna_NodeCrop_use_bokeh_set"); + RNA_def_property_ui_text( + prop, "Bokeh", "Use circular filter (slower). (Deprecated: Use Separable input instead.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "use_gamma_correction", PROP_BOOLEAN, PROP_NONE); diff --git a/source/blender/nodes/composite/nodes/node_composite_blur.cc b/source/blender/nodes/composite/nodes/node_composite_blur.cc index b24c178cf8e..4d7f5f0183f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_blur.cc +++ b/source/blender/nodes/composite/nodes/node_composite_blur.cc @@ -18,6 +18,7 @@ #include "GPU_shader.hh" +#include "COM_algorithm_parallel_reduction.hh" #include "COM_algorithm_recursive_gaussian_blur.hh" #include "COM_algorithm_symmetric_separable_blur.hh" #include "COM_node_operation.hh" @@ -34,14 +35,16 @@ NODE_STORAGE_FUNCS(NodeBlurData) static void cmp_node_blur_declare(NodeDeclarationBuilder &b) { - b.add_input("Image") - .default_value({1.0f, 1.0f, 1.0f, 1.0f}) - .compositor_domain_priority(0); - b.add_input("Size") - .default_value(1.0f) - .min(0.0f) - .max(1.0f) - .compositor_domain_priority(1); + b.add_input("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input("Size").default_value({0.0f, 0.0f, 0.0f}).min(0.0f); + b.add_input("Extend Bounds").default_value(false).compositor_expects_single_value(); + b.add_input("Separable") + .default_value(true) + .compositor_expects_single_value() + .description( + "Use faster approximation by blurring along the horizontal and vertical directions " + "independently"); + b.add_output("Image"); } @@ -54,37 +57,7 @@ static void node_composit_init_blur(bNodeTree * /*ntree*/, bNode *node) static void node_composit_buts_blur(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { - uiLayout *col, *row; - - col = &layout->column(false); - const int filter = RNA_enum_get(ptr, "filter_type"); - - col->prop(ptr, "filter_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); - if (filter != R_FILTER_FAST_GAUSS) { - col->prop(ptr, "use_bokeh", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - } - - col->prop(ptr, "use_relative", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - if (RNA_boolean_get(ptr, "use_relative")) { - col->label(IFACE_("Aspect Correction"), ICON_NONE); - row = &layout->row(true); - row->prop(ptr, - "aspect_correction", - UI_ITEM_R_SPLIT_EMPTY_NAME | UI_ITEM_R_EXPAND, - std::nullopt, - ICON_NONE); - - col = &layout->column(true); - col->prop(ptr, "factor_x", UI_ITEM_R_SPLIT_EMPTY_NAME, IFACE_("X"), ICON_NONE); - col->prop(ptr, "factor_y", UI_ITEM_R_SPLIT_EMPTY_NAME, IFACE_("Y"), ICON_NONE); - } - else { - col = &layout->column(true); - col->prop(ptr, "size_x", UI_ITEM_R_SPLIT_EMPTY_NAME, IFACE_("X"), ICON_NONE); - col->prop(ptr, "size_y", UI_ITEM_R_SPLIT_EMPTY_NAME, IFACE_("Y"), ICON_NONE); - } - col->prop(ptr, "use_extended_bounds", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); + layout->prop(ptr, "filter_type", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); } using namespace blender::compositor; @@ -102,22 +75,22 @@ class BlurOperation : public NodeOperation { return; } - if (node_storage(bnode()).filtertype == R_FILTER_FAST_GAUSS) { - recursive_gaussian_blur(context(), input, output, compute_blur_radius()); + if (!this->get_input("Size").is_single_value()) { + this->execute_variable_size(input, output); } - else if (!this->get_input("Size").is_single_value()) { - execute_variable_size(input, output); + else if (node_storage(bnode()).filtertype == R_FILTER_FAST_GAUSS) { + recursive_gaussian_blur(context(), input, output, this->get_blur_size()); } else if (use_separable_filter()) { symmetric_separable_blur(context(), input, output, - compute_blur_radius(), + this->get_blur_size(), node_storage(bnode()).filtertype, get_extend_bounds()); } else { - execute_constant_size(input, output); + this->execute_constant_size(input, output); } } @@ -140,7 +113,7 @@ class BlurOperation : public NodeOperation { input.bind_as_texture(shader, "input_tx"); - const float2 blur_radius = compute_blur_radius(); + const float2 blur_radius = this->get_blur_size(); const Result &weights = context().cache_manager().symmetric_blur_weights.get( context(), node_storage(bnode()).filtertype, blur_radius); @@ -165,7 +138,7 @@ class BlurOperation : public NodeOperation { void execute_constant_size_cpu(const Result &input, Result &output) { - const float2 blur_radius = this->compute_blur_radius(); + const float2 blur_radius = this->get_blur_size(); const Result &weights = this->context().cache_manager().symmetric_blur_weights.get( this->context(), node_storage(this->bnode()).filtertype, blur_radius); @@ -241,6 +214,10 @@ class BlurOperation : public NodeOperation { void execute_variable_size_gpu(const Result &input, Result &output) { + const float2 blur_radius = this->compute_maximum_blur_size(); + const Result &weights = context().cache_manager().symmetric_blur_weights.get( + context(), node_storage(bnode()).filtertype, blur_radius); + GPUShader *shader = context().get_shader("compositor_symmetric_blur_variable_size"); GPU_shader_bind(shader); @@ -248,10 +225,6 @@ class BlurOperation : public NodeOperation { input.bind_as_texture(shader, "input_tx"); - const float2 blur_radius = compute_blur_radius(); - - const Result &weights = context().cache_manager().symmetric_blur_weights.get( - context(), node_storage(bnode()).filtertype, blur_radius); weights.bind_as_texture(shader, "weights_tx"); const Result &input_size = get_input("Size"); @@ -277,7 +250,7 @@ class BlurOperation : public NodeOperation { void execute_variable_size_cpu(const Result &input, Result &output) { - const float2 blur_radius = this->compute_blur_radius(); + const float2 blur_radius = this->compute_maximum_blur_size(); const Result &weights = this->context().cache_manager().symmetric_blur_weights.get( this->context(), node_storage(this->bnode()).filtertype, blur_radius); @@ -300,21 +273,16 @@ class BlurOperation : public NodeOperation { auto load_size = [&](const int2 texel) { int2 blur_radius = weights.domain().size - 1; int2 offset = extend_bounds ? blur_radius : int2(0); - return math::clamp(size.load_pixel_extended(texel - offset), 0.0f, 1.0f); + return math::max(float2(0.0f), size.load_pixel_extended(texel - offset).xy()); }; parallel_for(domain.size, [&](const int2 texel) { float4 accumulated_color = float4(0.0f); float4 accumulated_weight = float4(0.0f); - /* The weights texture only stores the weights for the first quadrant, but since the weights - * are symmetric, other quadrants can be found using mirroring. It follows that the base blur - * radius is the weights texture size minus one, where the one corresponds to the zero - * weight. */ - int2 weights_size = weights.domain().size; - int2 base_radius = weights_size - int2(1); - int2 radius = int2(math::ceil(float2(base_radius) * load_size(texel))); - float2 coordinates_scale = float2(1.0f) / float2(radius + int2(1)); + const float2 size = load_size(texel); + int2 radius = int2(math::ceil(size)); + float2 coordinates_scale = float2(1.0f) / (size + float2(1.0f)); /* First, compute the contribution of the center pixel. */ float4 center_color = load_input(texel); @@ -368,6 +336,11 @@ class BlurOperation : public NodeOperation { }); } + float2 compute_maximum_blur_size() + { + return maximum_float3(this->context(), this->get_input("Size")).xy(); + } + /* Loads the input color of the pixel at the given texel. If bounds are extended, then the input * is treated as padded by a blur size amount of pixels of zero color, and the given texel is * assumed to be in the space of the image after padding. So we offset the texel by the blur @@ -393,41 +366,19 @@ class BlurOperation : public NodeOperation { return color; } - float2 compute_blur_radius() - { - const float size = math::clamp(get_input("Size").get_single_value_default(1.0f), 0.0f, 1.0f); - - if (!node_storage(bnode()).relative) { - return float2(node_storage(bnode()).sizex, node_storage(bnode()).sizey) * size; - } - - int2 image_size = get_input("Image").domain().size; - switch (node_storage(bnode()).aspect) { - case CMP_NODE_BLUR_ASPECT_Y: - image_size.y = image_size.x; - break; - case CMP_NODE_BLUR_ASPECT_X: - image_size.x = image_size.y; - break; - default: - BLI_assert(node_storage(bnode()).aspect == CMP_NODE_BLUR_ASPECT_NONE); - break; - } - - return float2(image_size) * get_size_factor() * size; - } - - /* 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. */ + const Result &input = this->get_input("Image"); 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)) { + const Result &size = this->get_input("Size"); + if (!size.is_single_value()) { + return false; + } + + if (this->get_blur_size() == float2(0.0)) { return true; } @@ -436,13 +387,13 @@ class BlurOperation : public NodeOperation { /* The blur node can operate with different filter types, evaluated on the normalized distance to * the center of the filter. Some of those filters are separable and can be computed as such. If - * the bokeh member is disabled in the node, then the filter is always computed as separable even - * if it is not in fact separable, in which case, the used filter is a cheaper approximation to - * the actual filter. If the bokeh member is enabled, then the filter is computed as separable if - * it is in fact separable and as a normal 2D filter otherwise. */ + * the Separable input is true, then the filter is always computed as separable even if it is not + * in fact separable, in which case, the used filter is a cheaper approximation to the actual + * filter. Otherwise, the filter is computed as separable if it is in fact separable and as a + * normal 2D filter otherwise. */ bool use_separable_filter() { - if (!node_storage(bnode()).bokeh) { + if (this->get_separable()) { return true; } @@ -456,14 +407,20 @@ class BlurOperation : public NodeOperation { } } - float2 get_size_factor() + float2 get_blur_size() { - return float2(node_storage(bnode()).percentx, node_storage(bnode()).percenty) / 100.0f; + BLI_assert(this->get_input("Size").is_single_value()); + return math::max(float2(0.0f), this->get_input("Size").get_single_value().xy()); + } + + bool get_separable() + { + return this->get_input("Separable").get_single_value_default(true); } bool get_extend_bounds() { - return bnode().custom1 & CMP_NODEFLAG_BLUR_EXTEND_BOUNDS; + return this->get_input("Extend Bounds").get_single_value_default(false); } }; diff --git a/tests/files/compositor/anisotropic_filtering/compositor_renders/compositor-nodes-desintegrate-wipe-01.png b/tests/files/compositor/anisotropic_filtering/compositor_renders/compositor-nodes-desintegrate-wipe-01.png index 2aba7416b5a..9e7950451d3 100644 --- a/tests/files/compositor/anisotropic_filtering/compositor_renders/compositor-nodes-desintegrate-wipe-01.png +++ b/tests/files/compositor/anisotropic_filtering/compositor_renders/compositor-nodes-desintegrate-wipe-01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45894201202ad3d9a9484e8a68f70178edcd08e456dd79a29006e24a8736a573 -size 41985 +oid sha256:e5aa5cee3c2facd26e2d2796dc500573ad4034fdccd5851f6011795a401bb2d9 +size 42130 diff --git a/tests/files/compositor/multiple_node_setups/compositor_renders/Fire2.png b/tests/files/compositor/multiple_node_setups/compositor_renders/Fire2.png index 54a821683ce..42336c5c455 100644 --- a/tests/files/compositor/multiple_node_setups/compositor_renders/Fire2.png +++ b/tests/files/compositor/multiple_node_setups/compositor_renders/Fire2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fd3b8c0dbbb1929f276110f1b8ebcddc9a14a0b728f48873bd7558146550995 -size 101578 +oid sha256:35c9d93c7179de35274e60a60953e5eef88cd168983c4899d5d9a2118d1151cb +size 102008