Compositor: Turn Blur node options to inputs
This patch turns the Blur node options to inputs. Size is now a 2D vector and replaces the Size X and Y option. Bokeh was renamed to Separable to reflect its actual function. Relative was removed in favor of the newly added Relative To Pixel node workflow. There is a slight difference in variable size blurring due to float vs integer computations, so two tests were updated. Reference #137223. Pull Request: https://projects.blender.org/blender/blender/pulls/139329
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<bNodeSocketValueBoolean>()->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<bNodeSocketValueBoolean>()->value;
|
||||
}
|
||||
else {
|
||||
property = input->default_value_typed<bNodeSocketValueBoolean>()->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<NodeBlurData *>(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<bNodeSocketValueVector>()->value[0]));
|
||||
storage->sizey = int(
|
||||
math::ceil(size_input->default_value_typed<bNodeSocketValueVector>()->value[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<NodeBlurData *>(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<bNodeSocketValueFloat>()->value;
|
||||
|
||||
blender::bke::node_modify_socket_type_static(
|
||||
node_tree, node, size_input, SOCK_VECTOR, PROP_NONE);
|
||||
size_input->default_value_typed<bNodeSocketValueVector>()->value[0] = old_size * storage->sizex;
|
||||
size_input->default_value_typed<bNodeSocketValueVector>()->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<bNodeSocketValueBoolean>()->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<bNodeSocketValueBoolean>()->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<bNodeSocketValueVector>()->value[0] = storage->percentx /
|
||||
100.0f;
|
||||
vector_input->default_value_typed<bNodeSocketValueVector>()->value[1] = storage->percenty /
|
||||
100.0f;
|
||||
}
|
||||
else {
|
||||
vector_input->default_value_typed<bNodeSocketValueVector>()->value[0] = storage->sizex;
|
||||
vector_input->default_value_typed<bNodeSocketValueVector>()->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<bNodeSocketValueVector>()->value[0] = (storage->percentx /
|
||||
100.0f) *
|
||||
old_size;
|
||||
vector_input->default_value_typed<bNodeSocketValueVector>()->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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<float>::lowest()),
|
||||
[&](const int2 texel, float3 &accumulated_value) {
|
||||
accumulated_value = math::max(accumulated_value, result.load_pixel<float3>(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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<bNode>();
|
||||
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<bNode>();
|
||||
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<bNode>();
|
||||
bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, "Size");
|
||||
return int(input->default_value_typed<bNodeSocketValueVector>()->value[0]);
|
||||
}
|
||||
|
||||
static void rna_NodeCrop_size_x_set(PointerRNA *ptr, const int value)
|
||||
{
|
||||
bNode *node = ptr->data_as<bNode>();
|
||||
bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, "Size");
|
||||
input->default_value_typed<bNodeSocketValueVector>()->value[0] = float(value);
|
||||
}
|
||||
|
||||
static int rna_NodeBlur_size_y_get(PointerRNA *ptr)
|
||||
{
|
||||
bNode *node = ptr->data_as<bNode>();
|
||||
bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, "Size");
|
||||
return int(input->default_value_typed<bNodeSocketValueVector>()->value[1]);
|
||||
}
|
||||
|
||||
static void rna_NodeCrop_size_y_set(PointerRNA *ptr, const int value)
|
||||
{
|
||||
bNode *node = ptr->data_as<bNode>();
|
||||
bNodeSocket *input = blender::bke::node_find_socket(*node, SOCK_IN, "Size");
|
||||
input->default_value_typed<bNodeSocketValueVector>()->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<typename T, const char *identifier>
|
||||
@@ -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<bool, node_input_extend_bounds>",
|
||||
"rna_node_property_to_input_setter<bool, node_input_extend_bounds>");
|
||||
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);
|
||||
|
||||
@@ -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<decl::Color>("Image")
|
||||
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
|
||||
.compositor_domain_priority(0);
|
||||
b.add_input<decl::Float>("Size")
|
||||
.default_value(1.0f)
|
||||
.min(0.0f)
|
||||
.max(1.0f)
|
||||
.compositor_domain_priority(1);
|
||||
b.add_input<decl::Color>("Image").default_value({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
b.add_input<decl::Vector>("Size").default_value({0.0f, 0.0f, 0.0f}).min(0.0f);
|
||||
b.add_input<decl::Bool>("Extend Bounds").default_value(false).compositor_expects_single_value();
|
||||
b.add_input<decl::Bool>("Separable")
|
||||
.default_value(true)
|
||||
.compositor_expects_single_value()
|
||||
.description(
|
||||
"Use faster approximation by blurring along the horizontal and vertical directions "
|
||||
"independently");
|
||||
|
||||
b.add_output<decl::Color>("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<float>(texel - offset), 0.0f, 1.0f);
|
||||
return math::max(float2(0.0f), size.load_pixel_extended<float3>(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<float3>().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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Binary file not shown.
BIN
tests/files/compositor/multiple_node_setups/compositor_renders/Fire2.png
(Stored with Git LFS)
BIN
tests/files/compositor/multiple_node_setups/compositor_renders/Fire2.png
(Stored with Git LFS)
Binary file not shown.
Reference in New Issue
Block a user