diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 2e12d046cc4..6c984118706 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 7 +#define BLENDER_FILE_SUBVERSION 8 /* 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/blenloader/intern/versioning_500.cc b/source/blender/blenloader/intern/versioning_500.cc index 7c093b8fb4d..5e0b5e7c160 100644 --- a/source/blender/blenloader/intern/versioning_500.cc +++ b/source/blender/blenloader/intern/versioning_500.cc @@ -146,6 +146,26 @@ void blo_do_versions_500(FileData * /*fd*/, Library * /*lib*/, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 8)) { + FOREACH_NODETREE_BEGIN (bmain, ntree, id) { + if (ntree->type != NTREE_COMPOSIT) { + continue; + } + LISTBASE_FOREACH (bNode *, node, &ntree->nodes) { + if (node->type_legacy != CMP_NODE_DISPLACE) { + continue; + } + if (node->storage != nullptr) { + continue; + } + NodeDisplaceData *data = MEM_callocN(__func__); + data->interpolation = CMP_NODE_INTERPOLATION_ANISOTROPIC; + node->storage = data; + } + } + 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. diff --git a/source/blender/compositor/CMakeLists.txt b/source/blender/compositor/CMakeLists.txt index 276f15f7f35..719cb713f18 100644 --- a/source/blender/compositor/CMakeLists.txt +++ b/source/blender/compositor/CMakeLists.txt @@ -182,6 +182,7 @@ set(GLSL_SRC shaders/compositor_deriche_gaussian_blur_sum.glsl shaders/compositor_directional_blur.glsl shaders/compositor_displace.glsl + shaders/compositor_displace_anisotropic.glsl shaders/compositor_double_edge_mask_compute_boundary.glsl shaders/compositor_double_edge_mask_compute_gradient.glsl shaders/compositor_edge_filter.glsl diff --git a/source/blender/compositor/shaders/compositor_displace.glsl b/source/blender/compositor/shaders/compositor_displace.glsl index b3bd9f8c9e7..1a252cb46cc 100644 --- a/source/blender/compositor/shaders/compositor_displace.glsl +++ b/source/blender/compositor/shaders/compositor_displace.glsl @@ -2,13 +2,9 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include "gpu_shader_bicubic_sampler_lib.glsl" #include "gpu_shader_compositor_texture_utilities.glsl" -/* A shared table that stores the displaced coordinates of all pixels in the work group. This is - * necessary to avoid recomputing displaced coordinates when computing the gradients necessary for - * anisotropic filtering, see the implementation for more information. */ -shared float2 displaced_coordinates_table[gl_WorkGroupSize.x][gl_WorkGroupSize.y]; - void main() { int2 texel = int2(gl_GlobalInvocationID.xy); @@ -24,39 +20,5 @@ void main() float2 displacement = texture_load(displacement_tx, texel).xy * scale / float2(input_size); float2 displaced_coordinates = coordinates - displacement; - /* Store the displaced coordinates into the shared table and issue a barrier to later compute the - * gradients from the table. */ - int2 table_index = int2(gl_LocalInvocationID.xy); - displaced_coordinates_table[table_index.x][table_index.y] = displaced_coordinates; - barrier(); - - /* Compute the partial derivative of the displaced coordinates along the x direction using a - * finite difference approximation. Odd invocations use a forward finite difference equation - * while even invocations use a backward finite difference equation. This is done such that - * invocations at the edges of the work group wouldn't need access to pixels that are outside of - * the work group. - * - * The x_step value is 1 for even invocations and when added to the x table index and multiplied - * by the result yields a standard forward finite difference equation. The x_step value is -1 for - * odd invocations and when added to the x table index and multiplied by the result yields a - * standard backward finite difference equation, because multiplication by -1 flips the order of - * subtraction. - * - * Divide by the input size since textureGrad assumes derivatives with respect to texel - * coordinates. */ - int x_step = (table_index.x % 2) * -2 + 1; - float2 x_neighbor = displaced_coordinates_table[table_index.x + x_step][table_index.y]; - float2 x_gradient = (x_neighbor - displaced_coordinates) * x_step / input_size.x; - - /* Compute the partial derivative of the displaced coordinates along the y direction using a - * finite difference approximation. See the previous code section for more information. */ - int y_step = (table_index.y % 2) * -2 + 1; - float2 y_neighbor = displaced_coordinates_table[table_index.x][table_index.y + y_step]; - float2 y_gradient = (y_neighbor - displaced_coordinates) * y_step / input_size.y; - - /* Sample the input using the displaced coordinates passing in the computed gradients in order to - * utilize the anisotropic filtering capabilities of the sampler. */ - float4 displaced_color = textureGrad(input_tx, displaced_coordinates, x_gradient, y_gradient); - - imageStore(output_img, texel, displaced_color); + imageStore(output_img, texel, SAMPLER_FUNCTION(input_tx, displaced_coordinates)); } diff --git a/source/blender/compositor/shaders/compositor_displace_anisotropic.glsl b/source/blender/compositor/shaders/compositor_displace_anisotropic.glsl new file mode 100644 index 00000000000..b3bd9f8c9e7 --- /dev/null +++ b/source/blender/compositor/shaders/compositor_displace_anisotropic.glsl @@ -0,0 +1,62 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gpu_shader_compositor_texture_utilities.glsl" + +/* A shared table that stores the displaced coordinates of all pixels in the work group. This is + * necessary to avoid recomputing displaced coordinates when computing the gradients necessary for + * anisotropic filtering, see the implementation for more information. */ +shared float2 displaced_coordinates_table[gl_WorkGroupSize.x][gl_WorkGroupSize.y]; + +void main() +{ + int2 texel = int2(gl_GlobalInvocationID.xy); + int2 input_size = texture_size(input_tx); + + /* Add 0.5 to evaluate the input 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(input_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(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 displaced_coordinates = coordinates - displacement; + + /* Store the displaced coordinates into the shared table and issue a barrier to later compute the + * gradients from the table. */ + int2 table_index = int2(gl_LocalInvocationID.xy); + displaced_coordinates_table[table_index.x][table_index.y] = displaced_coordinates; + barrier(); + + /* Compute the partial derivative of the displaced coordinates along the x direction using a + * finite difference approximation. Odd invocations use a forward finite difference equation + * while even invocations use a backward finite difference equation. This is done such that + * invocations at the edges of the work group wouldn't need access to pixels that are outside of + * the work group. + * + * The x_step value is 1 for even invocations and when added to the x table index and multiplied + * by the result yields a standard forward finite difference equation. The x_step value is -1 for + * odd invocations and when added to the x table index and multiplied by the result yields a + * standard backward finite difference equation, because multiplication by -1 flips the order of + * subtraction. + * + * Divide by the input size since textureGrad assumes derivatives with respect to texel + * coordinates. */ + int x_step = (table_index.x % 2) * -2 + 1; + float2 x_neighbor = displaced_coordinates_table[table_index.x + x_step][table_index.y]; + float2 x_gradient = (x_neighbor - displaced_coordinates) * x_step / input_size.x; + + /* Compute the partial derivative of the displaced coordinates along the y direction using a + * finite difference approximation. See the previous code section for more information. */ + int y_step = (table_index.y % 2) * -2 + 1; + float2 y_neighbor = displaced_coordinates_table[table_index.x][table_index.y + y_step]; + float2 y_gradient = (y_neighbor - displaced_coordinates) * y_step / input_size.y; + + /* Sample the input using the displaced coordinates passing in the computed gradients in order to + * utilize the anisotropic filtering capabilities of the sampler. */ + float4 displaced_color = textureGrad(input_tx, displaced_coordinates, x_gradient, y_gradient); + + imageStore(output_img, texel, displaced_color); +} diff --git a/source/blender/compositor/shaders/infos/compositor_displace_info.hh b/source/blender/compositor/shaders/infos/compositor_displace_info.hh index 973bc61c399..cba862fb059 100644 --- a/source/blender/compositor/shaders/infos/compositor_displace_info.hh +++ b/source/blender/compositor/shaders/infos/compositor_displace_info.hh @@ -4,13 +4,31 @@ #include "gpu_shader_create_info.hh" -GPU_SHADER_CREATE_INFO(compositor_displace) +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, GPU_RGBA16F, write, image2D, output_img) +GPU_SHADER_CREATE_END() + +GPU_SHADER_CREATE_INFO(compositor_displace) +ADDITIONAL_INFO(compositor_displace_shared) COMPUTE_SOURCE("compositor_displace.glsl") +DEFINE_VALUE("SAMPLER_FUNCTION", "texture") +DO_STATIC_COMPILATION() +GPU_SHADER_CREATE_END() + +GPU_SHADER_CREATE_INFO(compositor_displace_bicubic) +ADDITIONAL_INFO(compositor_displace_shared) +COMPUTE_SOURCE("compositor_displace.glsl") +DEFINE_VALUE("SAMPLER_FUNCTION", "texture_bicubic") +DO_STATIC_COMPILATION() +GPU_SHADER_CREATE_END() + +GPU_SHADER_CREATE_INFO(compositor_displace_anisotropic) +ADDITIONAL_INFO(compositor_displace_shared) +COMPUTE_SOURCE("compositor_displace_anisotropic.glsl") DO_STATIC_COMPILATION() GPU_SHADER_CREATE_END() diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index b970084d32b..379aca49f99 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1577,6 +1577,10 @@ typedef struct NodeScaleData { short interpolation; } NodeScaleData; +typedef struct NodeDisplaceData { + short interpolation; +} NodeDisplaceData; + typedef struct NodePlaneTrackDeformData { char tracking_object[64]; char plane_track_name[64]; diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 70ccd0d3265..06569786651 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -8066,6 +8066,19 @@ static void def_cmp_dilate_erode(BlenderRNA * /*brna*/, StructRNA *srna) RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } +static void def_cmp_displace(BlenderRNA * /*brna*/, StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeDisplaceData", "storage"); + + prop = RNA_def_property(srna, "interpolation", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "interpolation"); + RNA_def_property_enum_items(prop, cmp_interpolation_items); + RNA_def_property_ui_text(prop, "Interpolation", "Interpolation method"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); +} + static void def_cmp_inpaint(BlenderRNA * /*brna*/, StructRNA *srna) { PropertyRNA *prop; @@ -13995,7 +14008,7 @@ static void rna_def_nodes(BlenderRNA *brna) define("CompositorNode", "CompositorNodeDespeckle", def_cmp_despeckle); define("CompositorNode", "CompositorNodeDiffMatte", def_cmp_diff_matte); define("CompositorNode", "CompositorNodeDilateErode", def_cmp_dilate_erode); - define("CompositorNode", "CompositorNodeDisplace"); + define("CompositorNode", "CompositorNodeDisplace", def_cmp_displace); define("CompositorNode", "CompositorNodeDistanceMatte", def_cmp_distance_matte); define("CompositorNode", "CompositorNodeDoubleEdgeMask", def_cmp_double_edge_mask); define("CompositorNode", "CompositorNodeEllipseMask", def_cmp_ellipsemask); diff --git a/source/blender/nodes/composite/nodes/node_composite_displace.cc b/source/blender/nodes/composite/nodes/node_composite_displace.cc index 726d78bdd04..4343f971a53 100644 --- a/source/blender/nodes/composite/nodes/node_composite_displace.cc +++ b/source/blender/nodes/composite/nodes/node_composite_displace.cc @@ -6,21 +6,34 @@ * \ingroup cmpnodes */ +#include "MEM_guardedalloc.h" + +#include "BKE_node.hh" + +#include "BLI_assert.h" #include "BLI_math_vector.hh" #include "BLI_math_vector_types.hh" +#include "DNA_node_types.h" +#include "RNA_types.hh" + #include "GPU_shader.hh" #include "GPU_texture.hh" +#include "COM_domain.hh" #include "COM_node_operation.hh" #include "COM_utilities.hh" +#include "UI_interface_layout.hh" +#include "UI_resources.hh" #include "node_composite_util.hh" /* **************** Displace ******************** */ namespace blender::nodes::node_composite_displace_cc { +NODE_STORAGE_FUNCS(NodeDisplaceData) + static void cmp_node_displace_declare(NodeDeclarationBuilder &b) { b.add_input("Image") @@ -46,6 +59,18 @@ static void cmp_node_displace_declare(NodeDeclarationBuilder &b) b.add_output("Image"); } +static void cmp_node_init_displace(bNodeTree * /*ntree*/, bNode *node) +{ + NodeDisplaceData *data = MEM_callocN(__func__); + data->interpolation = CMP_NODE_INTERPOLATION_ANISOTROPIC; + node->storage = data; +} + +static void cmp_buts_displace(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + layout->prop(ptr, "interpolation", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); +} + using namespace blender::compositor; class DisplaceOperation : public NodeOperation { @@ -71,12 +96,20 @@ class DisplaceOperation : public NodeOperation { void execute_gpu() { - GPUShader *shader = context().get_shader("compositor_displace"); + GPUShader *shader = context().get_shader(this->get_shader_name()); GPU_shader_bind(shader); const Result &input_image = get_input("Image"); - GPU_texture_mipmap_mode(input_image, true, true); - GPU_texture_anisotropic_filter(input_image, true); + const Interpolation interpolation = this->get_interpolation(); + if (interpolation == Interpolation::Anisotropic) { + GPU_texture_anisotropic_filter(input_image, true); + GPU_texture_mipmap_mode(input_image, true, true); + } + else { + const bool use_bilinear = ELEM( + interpolation, Interpolation::Bilinear, Interpolation::Bicubic); + GPU_texture_filter_mode(input_image, use_bilinear); + } GPU_texture_extend_mode(input_image, GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER); input_image.bind_as_texture(shader, "input_tx"); @@ -109,18 +142,76 @@ class DisplaceOperation : public NodeOperation { const Result &x_scale = get_input("X Scale"); const Result &y_scale = get_input("Y Scale"); + const Interpolation interpolation = this->get_interpolation(); const Domain domain = compute_domain(); Result &output = get_result("Image"); output.allocate_texture(domain); - /* In order to perform EWA sampling, we need to compute the partial derivative of the displaced - * coordinates along the x and y directions using a finite difference approximation. But in - * order to avoid loading multiple neighboring displacement values for each pixel, we operate - * on the image in 2x2 blocks of pixels, where the derivatives are computed horizontally and - * vertically across the 2x2 block such that odd texels use a forward finite difference - * equation while even invocations use a backward finite difference equation. */ const int2 size = domain.size; + + if (interpolation == Interpolation::Anisotropic) { + this->compute_anisotropic(size, image, output, input_displacement, x_scale, y_scale); + } + else { + this->compute_interpolation( + interpolation, size, image, output, input_displacement, x_scale, y_scale); + } + } + + void compute_interpolation(const Interpolation &interpolation, + const int2 &size, + const Result &image, + Result &output, + const Result &input_displacement, + const Result &x_scale, + const Result &y_scale) const + { + parallel_for(size, [&](const int2 base_texel) { + const float2 coordinates = compute_coordinates( + base_texel, size, input_displacement, x_scale, y_scale); + switch (interpolation) { + /* The anisotropic case requires gradient computation and is handled separately. */ + case Interpolation::Anisotropic: + BLI_assert_unreachable(); + break; + case Interpolation::Nearest: + output.store_pixel(base_texel, image.sample_nearest_zero(coordinates)); + break; + case Interpolation::Bilinear: + output.store_pixel(base_texel, image.sample_bilinear_zero(coordinates)); + break; + case Interpolation::Bicubic: + output.store_pixel(base_texel, image.sample_cubic_wrap(coordinates, false, false)); + break; + } + }); + } + + /* In order to perform EWA sampling, we need to compute the partial derivative of the + * displaced coordinates along the x and y directions using a finite difference + * approximation. But in order to avoid loading multiple neighboring displacement values for + * each pixel, we operate on the image in 2x2 blocks of pixels, where the derivatives are + * computed horizontally and vertically across the 2x2 block such that odd texels use a + * forward finite difference equation while even invocations use a backward finite difference + * equation. */ + void compute_anisotropic(const int2 &size, + const Result &image, + Result &output, + const Result &input_displacement, + const Result &x_scale, + const Result &y_scale) 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. */ const int x = base_texel.x * 2; const int y = base_texel.y * 2; @@ -129,24 +220,14 @@ class DisplaceOperation : public NodeOperation { const int2 upper_left_texel = int2(x, y + 1); const int2 upper_right_texel = int2(x + 1, y + 1); - auto compute_coordinates = [&](const int2 &texel) -> float2 { - /* 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(texel), - y_scale.load_pixel_extended(texel)); - float2 displacement = input_displacement.load_pixel_extended(texel).xy() * - scale / float2(size); - return coordinates - displacement; - }; - - const float2 lower_left_coordinates = compute_coordinates(lower_left_texel); - const float2 lower_right_coordinates = compute_coordinates(lower_right_texel); - const float2 upper_left_coordinates = compute_coordinates(upper_left_texel); - const float2 upper_right_coordinates = compute_coordinates(upper_right_texel); + 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); /* Compute the partial derivatives using finite difference. Divide by the input size since * sample_ewa_zero assumes derivatives with respect to texel coordinates. */ @@ -156,33 +237,68 @@ 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_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. */ - float4 displaced_color = image.sample_ewa_zero(coordinates, x_gradient, y_gradient); - output.store_pixel(texel, displaced_color); - }; - /* Compute each of the pixels in the 2x2 block, making sure to exempt out of bounds right - * and upper pixels. */ - compute_pixel(lower_left_texel, lower_left_coordinates, lower_x_gradient, left_y_gradient); + compute_anisotropic_pixel( + lower_left_texel, lower_left_coordinates, lower_x_gradient, left_y_gradient); if (lower_right_texel.x != size.x) { - compute_pixel( + compute_anisotropic_pixel( lower_right_texel, lower_right_coordinates, lower_x_gradient, right_y_gradient); } if (upper_left_texel.y != size.y) { - compute_pixel(upper_left_texel, upper_left_coordinates, upper_x_gradient, left_y_gradient); + compute_anisotropic_pixel( + upper_left_texel, upper_left_coordinates, upper_x_gradient, left_y_gradient); } if (upper_right_texel.x != size.x && upper_right_texel.y != size.y) { - compute_pixel( + compute_anisotropic_pixel( upper_right_texel, upper_right_coordinates, upper_x_gradient, right_y_gradient); } }); } + float2 compute_coordinates(const int2 &texel, + const int2 &size, + const Result &input_displacement, + const Result &x_scale, + const Result &y_scale) 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(texel), + y_scale.load_pixel_extended(texel)); + float2 displacement = input_displacement.load_pixel_extended(texel).xy() * + scale / float2(size); + return coordinates - displacement; + } + + const char *get_shader_name() const + { + if (this->get_interpolation() == Interpolation::Anisotropic) { + return "compositor_displace_anisotropic"; + } + return "compositor_displace"; + } + + Interpolation get_interpolation() const + { + switch (node_storage(bnode()).interpolation) { + case CMP_NODE_INTERPOLATION_ANISOTROPIC: + return Interpolation::Anisotropic; + case CMP_NODE_INTERPOLATION_NEAREST: + return Interpolation::Nearest; + case CMP_NODE_INTERPOLATION_BILINEAR: + return Interpolation::Bilinear; + case CMP_NODE_INTERPOLATION_BICUBIC: + return Interpolation::Bicubic; + } + + BLI_assert_unreachable(); + return Interpolation::Nearest; + } + bool is_identity() { const Result &input_image = get_input("Image"); @@ -228,6 +344,10 @@ static void register_node_type_cmp_displace() ntype.enum_name_legacy = "DISPLACE"; ntype.nclass = NODE_CLASS_DISTORT; ntype.declare = file_ns::cmp_node_displace_declare; + ntype.draw_buttons = file_ns::cmp_buts_displace; + ntype.initfunc = file_ns::cmp_node_init_displace; + blender::bke::node_type_storage( + ntype, "NodeDisplaceData", node_free_standard_storage, node_copy_standard_storage); ntype.get_compositor_operation = file_ns::get_compositor_operation; blender::bke::node_register_type(ntype);