Blender V5.0
node_composite_displace.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2006 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "MEM_guardedalloc.h"
6
7#include "BLI_math_matrix.hh"
8#include "BLI_math_vector.hh"
10#include "BLI_utildefines.h"
11
12#include "DNA_node_types.h"
13
14#include "RNA_enum_types.hh"
15
16#include "GPU_shader.hh"
17#include "GPU_texture.hh"
18
19#include "BKE_node.hh"
20
21#include "COM_domain.hh"
22#include "COM_node_operation.hh"
23#include "COM_utilities.hh"
24
26
28
30{
31 b.use_custom_socket_order();
32 b.allow_any_socket_order();
33
34 b.add_input<decl::Color>("Image")
35 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
36 .hide_value()
37 .structure_type(StructureType::Dynamic);
38 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
39
40 b.add_input<decl::Vector>("Displacement")
41 .dimensions(2)
42 .default_value({0.0f, 0.0f})
43 .structure_type(StructureType::Dynamic);
44
45 PanelDeclarationBuilder &sampling_panel = b.add_panel("Sampling").default_closed(true);
46 sampling_panel.add_input<decl::Menu>("Interpolation")
49 .description("Interpolation method")
50 .optional_label();
51 sampling_panel.add_input<decl::Menu>("Extension X")
52 .default_value(CMP_NODE_EXTENSION_MODE_CLIP)
54 .description("The extension mode applied to the X axis")
55 .optional_label();
56 sampling_panel.add_input<decl::Menu>("Extension Y")
57 .default_value(CMP_NODE_EXTENSION_MODE_CLIP)
59 .description("The extension mode applied to the Y axis")
60 .optional_label();
61}
62
63static void cmp_node_init_displace(bNodeTree * /*ntree*/, bNode *node)
64{
65 /* Unused, kept for forward compatibility. */
67 node->storage = data;
68}
69
70using namespace blender::compositor;
71
73 public:
75
76 void execute() override
77 {
78 const Result &input = this->get_input("Image");
79 Result &output = this->get_result("Image");
80 if (input.is_single_value()) {
81 output.share_data(input);
82 return;
83 }
84
85 const Result &displacement = this->get_input("Displacement");
86 if (displacement.is_single_value()) {
87 output.share_data(input);
89 output.get_realization_options().interpolation = this->get_interpolation();
92 return;
93 }
94
95 if (this->context().use_gpu()) {
96 this->execute_gpu();
97 }
98 else {
99 this->execute_cpu();
100 }
101 }
102
104 {
105 const Interpolation interpolation = this->get_interpolation();
106 gpu::Shader *shader = this->context().get_shader(this->get_shader_name(interpolation));
107 GPU_shader_bind(shader);
108
109 const Result &input_image = this->get_input("Image");
110 if (interpolation == Interpolation::Anisotropic) {
111 GPU_texture_anisotropic_filter(input_image, true);
112 GPU_texture_mipmap_mode(input_image, true, true);
113 }
114 else {
115 const bool use_bilinear = ELEM(
117 GPU_texture_filter_mode(input_image, use_bilinear);
118 }
119
120 const ExtensionMode extension_x = this->get_extension_mode_x();
121 const ExtensionMode extension_y = this->get_extension_mode_y();
124 input_image.bind_as_texture(shader, "input_tx");
125
126 const Result &displacement = this->get_input("Displacement");
127 displacement.bind_as_texture(shader, "displacement_tx");
128
129 const Domain domain = this->compute_domain();
130 Result &output_image = this->get_result("Image");
131 output_image.allocate_texture(domain);
132 output_image.bind_as_image(shader, "output_img");
133
135
136 input_image.unbind_as_texture();
137 displacement.unbind_as_texture();
138 output_image.unbind_as_image();
140 }
141
143 {
144 const Result &image = this->get_input("Image");
145 const Result &displacement = this->get_input("Displacement");
146
147 const Interpolation interpolation = this->get_interpolation();
148 const ExtensionMode extension_x = this->get_extension_mode_x();
149 const ExtensionMode extension_y = this->get_extension_mode_y();
150 const Domain domain = this->compute_domain();
151 Result &output = this->get_result("Image");
152 output.allocate_texture(domain);
153
154 const int2 size = domain.size;
155
156 if (interpolation == Interpolation::Anisotropic) {
157 this->compute_anisotropic(size, image, output, displacement);
158 }
159 else {
161 interpolation, size, image, output, displacement, extension_x, extension_y);
162 }
163 }
164
165 void compute_interpolation(const Interpolation &interpolation,
166 const int2 &size,
167 const Result &image,
168 Result &output,
169 const Result &displacement,
170 const ExtensionMode &extension_mode_x,
171 const ExtensionMode &extension_mode_y) const
172 {
173 parallel_for(size, [&](const int2 base_texel) {
174 const float2 coordinates = this->compute_coordinates(base_texel, size, displacement);
175 output.store_pixel(
176 base_texel,
177 image.sample(coordinates, interpolation, extension_mode_x, extension_mode_y));
178 });
179 }
180
181 /* In order to perform EWA sampling, we need to compute the partial derivative of the
182 * displaced coordinates along the x and y directions using a finite difference
183 * approximation. But in order to avoid loading multiple neighboring displacement values for
184 * each pixel, we operate on the image in 2x2 blocks of pixels, where the derivatives are
185 * computed horizontally and vertically across the 2x2 block such that odd texels use a
186 * forward finite difference equation while even invocations use a backward finite difference
187 * equation. */
189 const Result &image,
190 Result &output,
191 const Result &displacement) const
192 {
193 parallel_for(math::divide_ceil(size, int2(2)), [&](const int2 base_texel) {
194 /* Compute each of the pixels in the 2x2 block, making sure to exempt out of bounds right
195 * and upper pixels. */
196 const int x = base_texel.x * 2;
197 const int y = base_texel.y * 2;
198
199 const int2 lower_left_texel = int2(x, y);
200 const int2 lower_right_texel = int2(x + 1, y);
201 const int2 upper_left_texel = int2(x, y + 1);
202 const int2 upper_right_texel = int2(x + 1, y + 1);
203
204 const float2 lower_left_coordinates = this->compute_coordinates(
205 lower_left_texel, size, displacement);
206 const float2 lower_right_coordinates = this->compute_coordinates(
207 lower_right_texel, size, displacement);
208 const float2 upper_left_coordinates = this->compute_coordinates(
209 upper_left_texel, size, displacement);
210 const float2 upper_right_coordinates = this->compute_coordinates(
211 upper_right_texel, size, displacement);
212
213 /* Compute the partial derivatives using finite difference. Divide by the input size since
214 * sample_ewa_zero assumes derivatives with respect to texel coordinates. */
215 const float2 lower_x_gradient = (lower_right_coordinates - lower_left_coordinates) / size.x;
216 const float2 left_y_gradient = (upper_left_coordinates - lower_left_coordinates) / size.y;
217 const float2 right_y_gradient = (upper_right_coordinates - lower_right_coordinates) / size.y;
218 const float2 upper_x_gradient = (upper_right_coordinates - upper_left_coordinates) / size.x;
219
220 /* Computes one of the 2x2 pixels given its texel location, coordinates, and gradients. */
221 auto compute_anisotropic_pixel = [&](const int2 &texel,
222 const float2 &coordinates,
223 const float2 &x_gradient,
224 const float2 &y_gradient) {
225 /* Sample the input using the displaced coordinates passing in the computed gradients in
226 * order to utilize the anisotropic filtering capabilities of the sampler. */
227 output.store_pixel(texel, image.sample_ewa_zero(coordinates, x_gradient, y_gradient));
228 };
229
230 compute_anisotropic_pixel(
231 lower_left_texel, lower_left_coordinates, lower_x_gradient, left_y_gradient);
232 if (lower_right_texel.x != size.x) {
233 compute_anisotropic_pixel(
234 lower_right_texel, lower_right_coordinates, lower_x_gradient, right_y_gradient);
235 }
236 if (upper_left_texel.y != size.y) {
237 compute_anisotropic_pixel(
238 upper_left_texel, upper_left_coordinates, upper_x_gradient, left_y_gradient);
239 }
240 if (upper_right_texel.x != size.x && upper_right_texel.y != size.y) {
241 compute_anisotropic_pixel(
242 upper_right_texel, upper_right_coordinates, upper_x_gradient, right_y_gradient);
243 }
244 });
245 }
246
247 float2 compute_coordinates(const int2 &texel, const int2 &size, const Result &displacement) const
248 {
249 /* Note that the input displacement is in pixel space, so divide by the input size to
250 * transform it into the normalized sampler space. */
251 const float2 coordinates = (float2(texel) + float2(0.5f)) / float2(size);
252 return coordinates - displacement.load_pixel_extended<float2>(texel) / float2(size);
253 }
254
255 const char *get_shader_name(const Interpolation &interpolation) const
256 {
257 switch (interpolation) {
259 return "compositor_displace_anisotropic";
261 return "compositor_displace_bicubic";
264 return "compositor_displace";
265 }
266
267 return "compositor_displace";
268 }
269
271 {
272 const Result &input = this->get_input("Interpolation");
273 const MenuValue default_menu_value = MenuValue(CMP_NODE_INTERPOLATION_BILINEAR);
274 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
275 const CMPNodeInterpolation interpolation = static_cast<CMPNodeInterpolation>(menu_value.value);
276 switch (interpolation) {
285 }
286
288 }
289
291 {
292 const Result &input = this->get_input("Extension X");
293 const MenuValue default_menu_value = MenuValue(CMP_NODE_EXTENSION_MODE_CLIP);
294 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
295 const CMPExtensionMode extension_x = static_cast<CMPExtensionMode>(menu_value.value);
296 switch (extension_x) {
298 return ExtensionMode::Clip;
303 }
304
305 return ExtensionMode::Clip;
306 }
307
309 {
310 const Result &input = this->get_input("Extension Y");
311 const MenuValue default_menu_value = MenuValue(CMP_NODE_EXTENSION_MODE_CLIP);
312 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
313 const CMPExtensionMode extension_y = static_cast<CMPExtensionMode>(menu_value.value);
314 switch (extension_y) {
316 return ExtensionMode::Clip;
321 }
322
323 return ExtensionMode::Clip;
324 }
325};
326
328{
329 return new DisplaceOperation(context, node);
330}
331
332} // namespace blender::nodes::node_composite_displace_cc
333
335{
337
338 static blender::bke::bNodeType ntype;
339
340 cmp_node_type_base(&ntype, "CompositorNodeDisplace", CMP_NODE_DISPLACE);
341 ntype.ui_name = "Displace";
342 ntype.ui_description = "Displace pixel position using an offset vector";
343 ntype.enum_name_legacy = "DISPLACE";
345 ntype.declare = file_ns::cmp_node_displace_declare;
346 ntype.initfunc = file_ns::cmp_node_init_displace;
348 ntype, "NodeDisplaceData", node_free_standard_storage, node_copy_standard_storage);
349 ntype.get_compositor_operation = file_ns::get_compositor_operation;
350
352}
#define NODE_CLASS_DISTORT
Definition BKE_node.hh:455
#define CMP_NODE_DISPLACE
#define ELEM(...)
CMPNodeInterpolation
@ CMP_NODE_INTERPOLATION_NEAREST
@ CMP_NODE_INTERPOLATION_BILINEAR
@ CMP_NODE_INTERPOLATION_BICUBIC
@ CMP_NODE_INTERPOLATION_ANISOTROPIC
CMPExtensionMode
@ CMP_NODE_EXTENSION_MODE_EXTEND
@ CMP_NODE_EXTENSION_MODE_CLIP
@ CMP_NODE_EXTENSION_MODE_REPEAT
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_unbind()
void GPU_texture_extend_mode_y(blender::gpu::Texture *texture, GPUSamplerExtendMode extend_mode)
void GPU_texture_extend_mode_x(blender::gpu::Texture *texture, GPUSamplerExtendMode extend_mode)
void GPU_texture_anisotropic_filter(blender::gpu::Texture *texture, bool use_aniso)
void GPU_texture_mipmap_mode(blender::gpu::Texture *texture, bool use_mipmap, bool use_filter)
void GPU_texture_filter_mode(blender::gpu::Texture *texture, bool use_filter)
Read Guarded memory(de)allocation.
#define NOD_REGISTER_NODE(REGISTER_FUNC)
BMesh const char void * data
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
gpu::Shader * get_shader(const char *info_name, ResultPrecision precision)
NodeOperation(Context &context, DNode node)
Result & get_result(StringRef identifier)
Definition operation.cc:39
Result & get_input(StringRef identifier) const
Definition operation.cc:138
virtual Domain compute_domain()
Definition operation.cc:56
RealizationOptions & get_realization_options()
Definition result.cc:630
void allocate_texture(const Domain domain, const bool from_pool=true, const std::optional< ResultStorageType > storage_type=std::nullopt)
Definition result.cc:389
void unbind_as_texture() const
Definition result.cc:511
float4 sample(const float2 &coordinates, const Interpolation &interpolation, const ExtensionMode &extend_mode_x, const ExtensionMode &extend_mode_y) const
void bind_as_texture(gpu::Shader *shader, const char *texture_name) const
Definition result.cc:487
T load_pixel_extended(const int2 &texel) const
void unbind_as_image() const
Definition result.cc:517
float4 sample_ewa_zero(const float2 &coordinates, const float2 &x_gradient, const float2 &y_gradient) const
void bind_as_image(gpu::Shader *shader, const char *image_name, bool read=false) const
Definition result.cc:498
bool is_single_value() const
Definition result.cc:758
const T & get_single_value() const
DeclType::Builder & add_input(StringRef name, StringRef identifier="")
float2 compute_coordinates(const int2 &texel, const int2 &size, const Result &displacement) const
const char * get_shader_name(const Interpolation &interpolation) const
void compute_interpolation(const Interpolation &interpolation, const int2 &size, const Result &image, Result &output, const Result &displacement, const ExtensionMode &extension_mode_x, const ExtensionMode &extension_mode_y) const
void compute_anisotropic(const int2 &size, const Result &image, Result &output, const Result &displacement) const
#define input
#define output
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void node_type_storage(bNodeType &ntype, std::optional< StringRefNull > storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:5414
void compute_dispatch_threads_at_least(gpu::Shader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:196
GPUSamplerExtendMode map_extension_mode_to_extend_mode(const ExtensionMode &mode)
Definition domain.cc:70
void parallel_for(const int2 range, const Function &function)
VecBase< T, Size > divide_ceil(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
MatT from_location(const typename MatT::loc_type &location)
static void cmp_node_displace_declare(NodeDeclarationBuilder &b)
static void cmp_node_init_displace(bNodeTree *, bNode *node)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
static void register_node_type_cmp_displace()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:42
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:54
const EnumPropertyItem rna_enum_node_compositor_extension_items[]
const EnumPropertyItem rna_enum_node_compositor_interpolation_items[]
void * storage
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:348
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
const char * enum_name_legacy
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:362