Compositor: Remove XY scale from Displace node

This patch removes the XY scale inputs from the Displace node. The
inputs were redundant since they were just multiplied by the vector.
This simplifies the node and improves performance slightly.

Additionally, the Vector input was renamed to Displacement since it no
longer specifies a direction.

Pull Request: https://projects.blender.org/blender/blender/pulls/146356
This commit is contained in:
Omar Emara
2025-09-24 13:33:24 +02:00
committed by Omar Emara
parent 91d2d54703
commit 92be913a2a
8 changed files with 161 additions and 130 deletions

View File

@@ -27,7 +27,7 @@
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 87
#define BLENDER_FILE_SUBVERSION 88
/* 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

View File

@@ -2281,6 +2281,91 @@ static void version_dynamic_viewer_node_items(bNodeTree &ntree)
}
}
static void do_version_displace_node_remove_xy_scale(bNodeTree &node_tree, bNode &node)
{
blender::bke::node_tree_set_type(node_tree);
bNodeSocket *displacement_input = blender::bke::node_find_socket(node, SOCK_IN, "Displacement");
bNodeSocket *x_scale_input = blender::bke::node_find_socket(node, SOCK_IN, "X Scale");
bNodeSocket *y_scale_input = blender::bke::node_find_socket(node, SOCK_IN, "Y Scale");
/* Find the link going into the inputs of the node. */
bNodeLink *displacement_link = nullptr;
bNodeLink *x_scale_link = nullptr;
bNodeLink *y_scale_link = nullptr;
LISTBASE_FOREACH (bNodeLink *, link, &node_tree.links) {
if (link->tosock == displacement_input) {
displacement_link = link;
}
if (link->tosock == x_scale_input) {
x_scale_link = link;
}
if (link->tosock == y_scale_input) {
y_scale_link = 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 - 20.0f;
multiply_node->location[1] = node.location[1];
multiply_node->custom1 = NODE_VECTOR_MATH_MULTIPLY;
bNodeSocket *multiply_a_input = blender::bke::node_find_socket(
*multiply_node, SOCK_IN, "Vector");
bNodeSocket *multiply_b_input = blender::bke::node_find_socket(
*multiply_node, SOCK_IN, "Vector_001");
bNodeSocket *multiply_output = blender::bke::node_find_socket(
*multiply_node, SOCK_OUT, "Vector");
copy_v2_v2(multiply_a_input->default_value_typed<bNodeSocketValueVector>()->value,
displacement_input->default_value_typed<bNodeSocketValueVector>()->value);
if (displacement_link) {
version_node_add_link(node_tree,
*displacement_link->fromnode,
*displacement_link->fromsock,
*multiply_node,
*multiply_a_input);
blender::bke::node_remove_link(&node_tree, *displacement_link);
}
version_node_add_link(node_tree, *multiply_node, *multiply_output, node, *displacement_input);
bNode *combine_node = blender::bke::node_add_node(nullptr, node_tree, "ShaderNodeCombineXYZ");
combine_node->parent = node.parent;
combine_node->location[0] = multiply_node->location[0] - multiply_node->width - 20.0f;
combine_node->location[1] = multiply_node->location[1];
bNodeSocket *combine_x_input = blender::bke::node_find_socket(*combine_node, SOCK_IN, "X");
bNodeSocket *combine_y_input = blender::bke::node_find_socket(*combine_node, SOCK_IN, "Y");
bNodeSocket *combine_output = blender::bke::node_find_socket(*combine_node, SOCK_OUT, "Vector");
version_node_add_link(
node_tree, *combine_node, *combine_output, *multiply_node, *multiply_b_input);
combine_x_input->default_value_typed<bNodeSocketValueFloat>()->value =
x_scale_input->default_value_typed<bNodeSocketValueFloat>()->value;
if (x_scale_link) {
version_node_add_link(node_tree,
*x_scale_link->fromnode,
*x_scale_link->fromsock,
*combine_node,
*combine_x_input);
blender::bke::node_remove_link(&node_tree, *x_scale_link);
}
combine_y_input->default_value_typed<bNodeSocketValueFloat>()->value =
y_scale_input->default_value_typed<bNodeSocketValueFloat>()->value;
if (y_scale_link) {
version_node_add_link(node_tree,
*y_scale_link->fromnode,
*y_scale_link->fromsock,
*combine_node,
*combine_y_input);
blender::bke::node_remove_link(&node_tree, *y_scale_link);
}
}
void do_versions_after_linking_500(FileData *fd, Main *bmain)
{
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 9)) {
@@ -3414,6 +3499,21 @@ void blo_do_versions_500(FileData *fd, Library * /*lib*/, Main *bmain)
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 88)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type != NTREE_COMPOSIT) {
continue;
}
version_node_input_socket_name(node_tree, CMP_NODE_DISPLACE, "Vector", "Displacement");
LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) {
if (node->type_legacy == CMP_NODE_DISPLACE) {
do_version_displace_node_remove_xy_scale(*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.

View File

@@ -688,11 +688,8 @@ BLI_INLINE_METHOD float4 Result::sample(const float2 &coordinates,
extension_mode_x,
extension_mode_y);
break;
/* The anisotropic sampling requires separate handling with EWA. */
case Interpolation::Anisotropic:
BLI_assert_unreachable();
break;
case Interpolation::Bicubic:
case Interpolation::Anisotropic:
math::interpolate_cubic_bspline_wrapmode_fl(buffer,
pixel_value,
size.x,

View File

@@ -16,8 +16,7 @@ void main()
/* Note that the input displacement is in pixel space, so divide by the input size to transform
* it into the normalized sampler space. */
float2 scale = float2(texture_load(x_scale_tx, texel).x, texture_load(y_scale_tx, texel).x);
float2 displacement = texture_load(displacement_tx, texel).xy * scale / float2(input_size);
float2 displacement = texture_load(displacement_tx, texel).xy / float2(input_size);
float2 displaced_coordinates = coordinates - displacement;
imageStore(output_img, texel, SAMPLER_FUNCTION(input_tx, displaced_coordinates));

View File

@@ -20,8 +20,7 @@ void main()
/* Note that the input displacement is in pixel space, so divide by the input size to transform
* it into the normalized sampler space. */
float2 scale = float2(texture_load(x_scale_tx, texel).x, texture_load(y_scale_tx, texel).x);
float2 displacement = texture_load(displacement_tx, texel).xy * scale / float2(input_size);
float2 displacement = texture_load(displacement_tx, texel).xy / float2(input_size);
float2 displaced_coordinates = coordinates - displacement;
/* Store the displaced coordinates into the shared table and issue a barrier to later compute the

View File

@@ -8,8 +8,6 @@ GPU_SHADER_CREATE_INFO(compositor_displace_shared)
LOCAL_GROUP_SIZE(16, 16)
SAMPLER(0, sampler2D, input_tx)
SAMPLER(1, sampler2D, displacement_tx)
SAMPLER(2, sampler2D, x_scale_tx)
SAMPLER(3, sampler2D, y_scale_tx)
IMAGE(0, SFLOAT_16_16_16_16, write, image2D, output_img)
GPU_SHADER_CREATE_END()

View File

@@ -2,13 +2,9 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup cmpnodes
*/
#include "MEM_guardedalloc.h"
#include "BLI_assert.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_utildefines.h"
@@ -39,22 +35,9 @@ static void cmp_node_displace_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Color>("Image")
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
.structure_type(StructureType::Dynamic);
b.add_input<decl::Vector>("Vector")
b.add_input<decl::Vector>("Displacement")
.dimensions(2)
.default_value({1.0f, 1.0f})
.min(0.0f)
.max(1.0f)
.subtype(PROP_TRANSLATION)
.structure_type(StructureType::Dynamic);
b.add_input<decl::Float>("X Scale")
.default_value(0.0f)
.min(-1000.0f)
.max(1000.0f)
.structure_type(StructureType::Dynamic);
b.add_input<decl::Float>("Y Scale")
.default_value(0.0f)
.min(-1000.0f)
.max(1000.0f)
.default_value({0.0f, 0.0f})
.structure_type(StructureType::Dynamic);
PanelDeclarationBuilder &sampling_panel = b.add_panel("Sampling").default_closed(true);
@@ -87,13 +70,23 @@ class DisplaceOperation : public NodeOperation {
void execute() override
{
if (this->is_identity()) {
const Result &input = this->get_input("Image");
Result &output = this->get_result("Image");
const Result &input = this->get_input("Image");
Result &output = this->get_result("Image");
if (input.is_single_value()) {
output.share_data(input);
return;
}
const Result &displacement = this->get_input("Displacement");
if (displacement.is_single_value()) {
output.share_data(input);
output.transform(math::from_location<float3x3>(displacement.get_single_value<float2>()));
output.get_realization_options().interpolation = this->get_interpolation();
output.get_realization_options().extension_x = this->get_extension_mode_x();
output.get_realization_options().extension_y = this->get_extension_mode_y();
return;
}
if (this->context().use_gpu()) {
this->execute_gpu();
}
@@ -105,12 +98,10 @@ class DisplaceOperation : public NodeOperation {
void execute_gpu()
{
const Interpolation interpolation = this->get_interpolation();
const ExtensionMode extension_x = this->get_extension_mode_x();
const ExtensionMode extension_y = this->get_extension_mode_y();
gpu::Shader *shader = context().get_shader(this->get_shader_name(interpolation));
gpu::Shader *shader = this->context().get_shader(this->get_shader_name(interpolation));
GPU_shader_bind(shader);
const Result &input_image = get_input("Image");
const Result &input_image = this->get_input("Image");
if (interpolation == Interpolation::Anisotropic) {
GPU_texture_anisotropic_filter(input_image, true);
GPU_texture_mipmap_mode(input_image, true, true);
@@ -120,61 +111,49 @@ class DisplaceOperation : public NodeOperation {
interpolation, Interpolation::Bilinear, Interpolation::Bicubic);
GPU_texture_filter_mode(input_image, use_bilinear);
}
const ExtensionMode extension_x = this->get_extension_mode_x();
const ExtensionMode extension_y = this->get_extension_mode_y();
GPU_texture_extend_mode_x(input_image, map_extension_mode_to_extend_mode(extension_x));
GPU_texture_extend_mode_y(input_image, map_extension_mode_to_extend_mode(extension_y));
input_image.bind_as_texture(shader, "input_tx");
const Result &input_displacement = get_input("Vector");
input_displacement.bind_as_texture(shader, "displacement_tx");
const Result &input_x_scale = get_input("X Scale");
input_x_scale.bind_as_texture(shader, "x_scale_tx");
const Result &input_y_scale = get_input("Y Scale");
input_y_scale.bind_as_texture(shader, "y_scale_tx");
const Result &displacement = this->get_input("Displacement");
displacement.bind_as_texture(shader, "displacement_tx");
const Domain domain = compute_domain();
Result &output_image = get_result("Image");
const Domain domain = this->compute_domain();
Result &output_image = this->get_result("Image");
output_image.allocate_texture(domain);
output_image.bind_as_image(shader, "output_img");
compute_dispatch_threads_at_least(shader, domain.size);
input_image.unbind_as_texture();
input_displacement.unbind_as_texture();
input_x_scale.unbind_as_texture();
input_y_scale.unbind_as_texture();
displacement.unbind_as_texture();
output_image.unbind_as_image();
GPU_shader_unbind();
}
void execute_cpu()
{
const Result &image = get_input("Image");
const Result &input_displacement = get_input("Vector");
const Result &x_scale = get_input("X Scale");
const Result &y_scale = get_input("Y Scale");
const Result &image = this->get_input("Image");
const Result &displacement = this->get_input("Displacement");
const Interpolation interpolation = this->get_interpolation();
const ExtensionMode extension_x = this->get_extension_mode_x();
const ExtensionMode extension_y = this->get_extension_mode_y();
const Domain domain = compute_domain();
Result &output = get_result("Image");
const Domain domain = this->compute_domain();
Result &output = this->get_result("Image");
output.allocate_texture(domain);
const int2 size = domain.size;
if (interpolation == Interpolation::Anisotropic) {
this->compute_anisotropic(size, image, output, input_displacement, x_scale, y_scale);
this->compute_anisotropic(size, image, output, displacement);
}
else {
this->compute_interpolation(interpolation,
size,
image,
output,
input_displacement,
x_scale,
y_scale,
extension_x,
extension_y);
this->compute_interpolation(
interpolation, size, image, output, displacement, extension_x, extension_y);
}
}
@@ -182,15 +161,12 @@ class DisplaceOperation : public NodeOperation {
const int2 &size,
const Result &image,
Result &output,
const Result &input_displacement,
const Result &x_scale,
const Result &y_scale,
const Result &displacement,
const ExtensionMode &extension_mode_x,
const ExtensionMode &extension_mode_y) const
{
parallel_for(size, [&](const int2 base_texel) {
const float2 coordinates = compute_coordinates(
base_texel, size, input_displacement, x_scale, y_scale);
const float2 coordinates = this->compute_coordinates(base_texel, size, displacement);
output.store_pixel(
base_texel,
image.sample(coordinates, interpolation, extension_mode_x, extension_mode_y));
@@ -207,18 +183,8 @@ class DisplaceOperation : public NodeOperation {
void compute_anisotropic(const int2 &size,
const Result &image,
Result &output,
const Result &input_displacement,
const Result &x_scale,
const Result &y_scale) const
const Result &displacement) const
{
auto compute_anisotropic_pixel = [&](const int2 &texel,
const float2 &coordinates,
const float2 &x_gradient,
const float2 &y_gradient) {
/* Sample the input using the displaced coordinates passing in the computed gradients in
* order to utilize the anisotropic filtering capabilities of the sampler. */
output.store_pixel(texel, image.sample_ewa_zero(coordinates, x_gradient, y_gradient));
};
parallel_for(math::divide_ceil(size, int2(2)), [&](const int2 base_texel) {
/* Compute each of the pixels in the 2x2 block, making sure to exempt out of bounds right
* and upper pixels. */
@@ -230,14 +196,14 @@ class DisplaceOperation : public NodeOperation {
const int2 upper_left_texel = int2(x, y + 1);
const int2 upper_right_texel = int2(x + 1, y + 1);
const float2 lower_left_coordinates = compute_coordinates(
lower_left_texel, size, input_displacement, x_scale, y_scale);
const float2 lower_right_coordinates = compute_coordinates(
lower_right_texel, size, input_displacement, x_scale, y_scale);
const float2 upper_left_coordinates = compute_coordinates(
upper_left_texel, size, input_displacement, x_scale, y_scale);
const float2 upper_right_coordinates = compute_coordinates(
upper_right_texel, size, input_displacement, x_scale, y_scale);
const float2 lower_left_coordinates = this->compute_coordinates(
lower_left_texel, size, displacement);
const float2 lower_right_coordinates = this->compute_coordinates(
lower_right_texel, size, displacement);
const float2 upper_left_coordinates = this->compute_coordinates(
upper_left_texel, size, displacement);
const float2 upper_right_coordinates = this->compute_coordinates(
upper_right_texel, size, displacement);
/* Compute the partial derivatives using finite difference. Divide by the input size since
* sample_ewa_zero assumes derivatives with respect to texel coordinates. */
@@ -247,6 +213,14 @@ class DisplaceOperation : public NodeOperation {
const float2 upper_x_gradient = (upper_right_coordinates - upper_left_coordinates) / size.x;
/* Computes one of the 2x2 pixels given its texel location, coordinates, and gradients. */
auto compute_anisotropic_pixel = [&](const int2 &texel,
const float2 &coordinates,
const float2 &x_gradient,
const float2 &y_gradient) {
/* Sample the input using the displaced coordinates passing in the computed gradients in
* order to utilize the anisotropic filtering capabilities of the sampler. */
output.store_pixel(texel, image.sample_ewa_zero(coordinates, x_gradient, y_gradient));
};
compute_anisotropic_pixel(
lower_left_texel, lower_left_coordinates, lower_x_gradient, left_y_gradient);
@@ -265,23 +239,12 @@ class DisplaceOperation : public NodeOperation {
});
}
float2 compute_coordinates(const int2 &texel,
const int2 &size,
const Result &input_displacement,
const Result &x_scale,
const Result &y_scale) const
float2 compute_coordinates(const int2 &texel, const int2 &size, const Result &displacement) const
{
/* Add 0.5 to evaluate the sampler at the center of the pixel and divide by the image
* size to get the coordinates into the sampler's expected [0, 1] range. */
float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
/* Note that the input displacement is in pixel space, so divide by the input size to
* transform it into the normalized sampler space. */
float2 scale = float2(x_scale.load_pixel_extended<float, true>(texel),
y_scale.load_pixel_extended<float, true>(texel));
float2 displacement = input_displacement.load_pixel_extended<float2, true>(texel) * scale /
float2(size);
return coordinates - displacement;
const float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
return coordinates - displacement.load_pixel_extended<float2>(texel) / float2(size);
}
const char *get_shader_name(const Interpolation &interpolation) const
@@ -354,31 +317,6 @@ class DisplaceOperation : public NodeOperation {
return ExtensionMode::Clip;
}
bool is_identity()
{
const Result &input_image = get_input("Image");
if (input_image.is_single_value()) {
return true;
}
const Result &input_displacement = get_input("Vector");
if (input_displacement.is_single_value() &&
math::is_zero(input_displacement.get_single_value<float2>()))
{
return true;
}
const Result &input_x_scale = get_input("X Scale");
const Result &input_y_scale = get_input("Y Scale");
if (input_x_scale.is_single_value() && input_x_scale.get_single_value<float>() == 0.0f &&
input_y_scale.is_single_value() && input_y_scale.get_single_value<float>() == 0.0f)
{
return true;
}
return false;
}
};
static NodeOperation *get_compositor_operation(Context &context, DNode node)

Binary file not shown.