diff options
author | Omar Emara <mail@OmarEmara.dev> | 2022-10-11 14:22:52 +0300 |
---|---|---|
committer | Omar Emara <mail@OmarEmara.dev> | 2022-10-11 14:22:52 +0300 |
commit | 0037411f55ec3da4cfad79575d5531869ae5dc38 (patch) | |
tree | 92070a51c4ee039729fe2192649602b76b3af244 | |
parent | f6a69920313255277ad1c21fcb813ee6ac774db7 (diff) |
Realtime Compositor: Implement parallel reduction
This patch implements generic parallel reduction for the realtime
compositor and implements the Levels operation as an example. This patch
also introduces the notion of a "Compositor Algorithm", which is a
reusable operation that can be used to construct other operations.
Differential Revision: https://developer.blender.org/D16184
Reviewed By: Clement Foucault
10 files changed, 597 insertions, 8 deletions
diff --git a/source/blender/compositor/realtime_compositor/CMakeLists.txt b/source/blender/compositor/realtime_compositor/CMakeLists.txt index 1f1333332f5..5473a253241 100644 --- a/source/blender/compositor/realtime_compositor/CMakeLists.txt +++ b/source/blender/compositor/realtime_compositor/CMakeLists.txt @@ -53,6 +53,10 @@ set(SRC COM_static_shader_manager.hh COM_texture_pool.hh COM_utilities.hh + + algorithms/intern/algorithm_parallel_reduction.cc + + algorithms/COM_algorithm_parallel_reduction.hh ) set(LIB diff --git a/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_parallel_reduction.hh b/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_parallel_reduction.hh new file mode 100644 index 00000000000..9d0851eff84 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_parallel_reduction.hh @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_math_vec_types.hh" + +#include "GPU_texture.h" + +#include "COM_context.hh" + +namespace blender::realtime_compositor { + +/* -------------------------------------------------------------------- + * Sum Reductions. + */ + +/* Computes the sum of the red channel of all pixels in the given texture. */ +float sum_red(Context &context, GPUTexture *texture); + +/* Computes the sum of the green channel of all pixels in the given texture. */ +float sum_green(Context &context, GPUTexture *texture); + +/* Computes the sum of the blue channel of all pixels in the given texture. */ +float sum_blue(Context &context, GPUTexture *texture); + +/* Computes the sum of the luminance of all pixels in the given texture, using the given luminance + * coefficients to compute the luminance. */ +float sum_luminance(Context &context, GPUTexture *texture, float3 luminance_coefficients); + +/* -------------------------------------------------------------------- + * Sum Of Squared Difference Reductions. + */ + +/* Computes the sum of the squared difference between the red channel of all pixels in the given + * texture and the given subtrahend. This can be used to compute the standard deviation if the + * given subtrahend is the mean. */ +float sum_red_squared_difference(Context &context, GPUTexture *texture, float subtrahend); + +/* Computes the sum of the squared difference between the green channel of all pixels in the given + * texture and the given subtrahend. This can be used to compute the standard deviation if the + * given subtrahend is the mean. */ +float sum_green_squared_difference(Context &context, GPUTexture *texture, float subtrahend); + +/* Computes the sum of the squared difference between the blue channel of all pixels in the given + * texture and the given subtrahend. This can be used to compute the standard deviation if the + * given subtrahend is the mean. */ +float sum_blue_squared_difference(Context &context, GPUTexture *texture, float subtrahend); + +/* Computes the sum of the squared difference between the luminance of all pixels in the given + * texture and the given subtrahend, using the given luminance coefficients to compute the + * luminance. This can be used to compute the standard deviation if the given subtrahend is the + * mean. */ +float sum_luminance_squared_difference(Context &context, + GPUTexture *texture, + float3 luminance_coefficients, + float subtrahend); + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/algorithms/intern/algorithm_parallel_reduction.cc b/source/blender/compositor/realtime_compositor/algorithms/intern/algorithm_parallel_reduction.cc new file mode 100644 index 00000000000..7b571f381c6 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/algorithms/intern/algorithm_parallel_reduction.cc @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_math_vec_types.hh" +#include "BLI_math_vector.hh" + +#include "MEM_guardedalloc.h" + +#include "GPU_compute.h" +#include "GPU_shader.h" +#include "GPU_texture.h" + +#include "COM_context.hh" +#include "COM_utilities.hh" + +namespace blender::realtime_compositor { + +/* Reduces the given texture into a single value and returns it. The return value should be freed + * by a call to MEM_freeN. The return value is either a pointer to a float, or a pointer to an + * array of floats that represents a vector. This depends on the given format, which should be + * compatible with the reduction shader. + * + * The given reduction shader should be bound when calling the function and the shader is expected + * to be derived from the compositor_parallel_reduction.glsl shader, see that file for more + * information. Also see the compositor_parallel_reduction_info.hh file for example shader + * definitions. */ +static float *parallel_reduction_dispatch(Context &context, + GPUTexture *texture, + GPUShader *shader, + eGPUTextureFormat format) +{ + GPU_shader_uniform_1b(shader, "is_initial_reduction", true); + + GPUTexture *texture_to_reduce = texture; + int2 size_to_reduce = int2(GPU_texture_width(texture), GPU_texture_height(texture)); + + /* Dispatch the reduction shader until the texture reduces to a single pixel. */ + while (size_to_reduce != int2(1)) { + const int2 reduced_size = math::divide_ceil(size_to_reduce, int2(16)); + GPUTexture *reduced_texture = context.texture_pool().acquire(reduced_size, format); + + GPU_memory_barrier(GPU_BARRIER_TEXTURE_FETCH); + const int texture_image_unit = GPU_shader_get_texture_binding(shader, "input_tx"); + GPU_texture_bind(texture_to_reduce, texture_image_unit); + + const int image_unit = GPU_shader_get_texture_binding(shader, "output_img"); + GPU_texture_image_bind(reduced_texture, image_unit); + + GPU_compute_dispatch(shader, reduced_size.x, reduced_size.y, 1); + + GPU_texture_image_unbind(reduced_texture); + GPU_texture_unbind(texture_to_reduce); + + /* Release the input texture only if it is not the source texture, since the source texture is + * not acquired or owned by the function. */ + if (texture_to_reduce != texture) { + context.texture_pool().release(texture_to_reduce); + } + + texture_to_reduce = reduced_texture; + size_to_reduce = reduced_size; + + GPU_shader_uniform_1b(shader, "is_initial_reduction", false); + } + + GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE); + float *pixel = static_cast<float *>(GPU_texture_read(texture_to_reduce, GPU_DATA_FLOAT, 0)); + + /* Release the final texture only if it is not the source texture, since the source texture is + * not acquired or owned by the function. */ + if (texture_to_reduce != texture) { + context.texture_pool().release(texture_to_reduce); + } + + return pixel; +} + +/* -------------------------------------------------------------------- + * Sum Reductions. + */ + +float sum_red(Context &context, GPUTexture *texture) +{ + GPUShader *shader = context.shader_manager().get("compositor_sum_red"); + GPU_shader_bind(shader); + + float *reduced_value = parallel_reduction_dispatch(context, texture, shader, GPU_R32F); + const float sum = *reduced_value; + MEM_freeN(reduced_value); + GPU_shader_unbind(); + + return sum; +} + +float sum_green(Context &context, GPUTexture *texture) +{ + GPUShader *shader = context.shader_manager().get("compositor_sum_green"); + GPU_shader_bind(shader); + + float *reduced_value = parallel_reduction_dispatch(context, texture, shader, GPU_R32F); + const float sum = *reduced_value; + MEM_freeN(reduced_value); + GPU_shader_unbind(); + + return sum; +} + +float sum_blue(Context &context, GPUTexture *texture) +{ + GPUShader *shader = context.shader_manager().get("compositor_sum_blue"); + GPU_shader_bind(shader); + + float *reduced_value = parallel_reduction_dispatch(context, texture, shader, GPU_R32F); + const float sum = *reduced_value; + MEM_freeN(reduced_value); + GPU_shader_unbind(); + + return sum; +} + +float sum_luminance(Context &context, GPUTexture *texture, float3 luminance_coefficients) +{ + GPUShader *shader = context.shader_manager().get("compositor_sum_luminance"); + GPU_shader_bind(shader); + + GPU_shader_uniform_3fv(shader, "luminance_coefficients", luminance_coefficients); + + float *reduced_value = parallel_reduction_dispatch(context, texture, shader, GPU_R32F); + const float sum = *reduced_value; + MEM_freeN(reduced_value); + GPU_shader_unbind(); + + return sum; +} + +/* -------------------------------------------------------------------- + * Sum Of Squared Difference Reductions. + */ + +float sum_red_squared_difference(Context &context, GPUTexture *texture, float subtrahend) +{ + GPUShader *shader = context.shader_manager().get("compositor_sum_red_squared_difference"); + GPU_shader_bind(shader); + + GPU_shader_uniform_1f(shader, "subtrahend", subtrahend); + + float *reduced_value = parallel_reduction_dispatch(context, texture, shader, GPU_R32F); + const float sum = *reduced_value; + MEM_freeN(reduced_value); + GPU_shader_unbind(); + + return sum; +} + +float sum_green_squared_difference(Context &context, GPUTexture *texture, float subtrahend) +{ + GPUShader *shader = context.shader_manager().get("compositor_sum_green_squared_difference"); + GPU_shader_bind(shader); + + GPU_shader_uniform_1f(shader, "subtrahend", subtrahend); + + float *reduced_value = parallel_reduction_dispatch(context, texture, shader, GPU_R32F); + const float sum = *reduced_value; + MEM_freeN(reduced_value); + GPU_shader_unbind(); + + return sum; +} + +float sum_blue_squared_difference(Context &context, GPUTexture *texture, float subtrahend) +{ + GPUShader *shader = context.shader_manager().get("compositor_sum_blue_squared_difference"); + GPU_shader_bind(shader); + + GPU_shader_uniform_1f(shader, "subtrahend", subtrahend); + + float *reduced_value = parallel_reduction_dispatch(context, texture, shader, GPU_R32F); + const float sum = *reduced_value; + MEM_freeN(reduced_value); + GPU_shader_unbind(); + + return sum; +} + +float sum_luminance_squared_difference(Context &context, + GPUTexture *texture, + float3 luminance_coefficients, + float subtrahend) +{ + GPUShader *shader = context.shader_manager().get("compositor_sum_luminance_squared_difference"); + GPU_shader_bind(shader); + + GPU_shader_uniform_3fv(shader, "luminance_coefficients", luminance_coefficients); + GPU_shader_uniform_1f(shader, "subtrahend", subtrahend); + + float *reduced_value = parallel_reduction_dispatch(context, texture, shader, GPU_R32F); + const float sum = *reduced_value; + MEM_freeN(reduced_value); + GPU_shader_unbind(); + + return sum; +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index e2285a3fd3e..34d53eaa230 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -346,6 +346,7 @@ set(GLSL_SRC shaders/compositor/compositor_morphological_distance_feather.glsl shaders/compositor/compositor_morphological_distance_threshold.glsl shaders/compositor/compositor_morphological_step.glsl + shaders/compositor/compositor_parallel_reduction.glsl shaders/compositor/compositor_projector_lens_distortion.glsl shaders/compositor/compositor_realize_on_domain.glsl shaders/compositor/compositor_screen_lens_distortion.glsl @@ -626,6 +627,7 @@ set(SRC_SHADER_CREATE_INFOS shaders/compositor/infos/compositor_morphological_distance_info.hh shaders/compositor/infos/compositor_morphological_distance_threshold_info.hh shaders/compositor/infos/compositor_morphological_step_info.hh + shaders/compositor/infos/compositor_parallel_reduction_info.hh shaders/compositor/infos/compositor_projector_lens_distortion_info.hh shaders/compositor/infos/compositor_realize_on_domain_info.hh shaders/compositor/infos/compositor_screen_lens_distortion_info.hh diff --git a/source/blender/gpu/shaders/compositor/compositor_parallel_reduction.glsl b/source/blender/gpu/shaders/compositor/compositor_parallel_reduction.glsl new file mode 100644 index 00000000000..f6f84aa24c1 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_parallel_reduction.glsl @@ -0,0 +1,98 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +/* This shader reduces the given texture into a smaller texture of a size equal to the number of + * work groups. In particular, each work group reduces its contents into a single value and writes + * that value to a single pixel in the output image. The shader can be dispatched multiple times to + * eventually reduce the image into a single pixel. + * + * The shader works by loading the whole data of each work group into a linear array, then it + * reduces the second half of the array onto the first half of the array, then it reduces the + * second quarter of the array onto the first quarter or the array, and so on until only one + * element remains. The following figure illustrates the process for sum reduction on 8 elements. + * + * .---. .---. .---. .---. .---. .---. .---. .---. + * | 0 | | 1 | | 2 | | 3 | | 4 | | 5 | | 6 | | 7 | Original data. + * '---' '---' '---' '---' '---' '---' '---' '---' + * |.____|_____|_____|_____| | | | + * || |.____|_____|___________| | | + * || || |.____|_________________| | + * || || || |.______________________| <--First reduction. Stride = 4. + * || || || || + * .---. .---. .---. .----. + * | 4 | | 6 | | 8 | | 10 | <--Data after first reduction. + * '---' '---' '---' '----' + * |.____|_____| | + * || |.__________| <--Second reduction. Stride = 2. + * || || + * .----. .----. + * | 12 | | 16 | <--Data after second reduction. + * '----' '----' + * |.____| + * || <--Third reduction. Stride = 1. + * .----. + * | 28 | + * '----' <--Data after third reduction. + * + * + * The shader is generic enough to implement many types of reductions. This is done by using macros + * that the developer should define to implement a certain reduction operation. Those include, + * TYPE, IDENTITY, INITIALIZE, LOAD, and REDUCE. See the implementation below for more information + * as well as the compositor_parallel_reduction_info.hh for example reductions operations. */ + +/* Doing the reduction in shared memory is faster, so create a shared array where the whole data + * of the work group will be loaded and reduced. The 2D structure of the work group is irrelevant + * for reduction, so we just load the data in a 1D array to simplify reduction. The developer is + * expected to define the TYPE macro to be a float or a vec4, depending on the type of data being + * reduced. */ +const uint reduction_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y; +shared TYPE reduction_data[reduction_size]; + +void main() +{ + /* Load the data from the texture, while returning IDENTITY for out of bound coordinates. The + * developer is expected to define the IDENTITY macro to be a vec4 that does not affect the + * output of the reduction. For instance, sum reductions have an identity of vec4(0.0), while + * max value reductions have an identity of vec4(FLT_MIN). */ + vec4 value = texture_load(input_tx, ivec2(gl_GlobalInvocationID.xy), IDENTITY); + + /* Initialize the shared array given the previously loaded value. This step can be different + * depending on whether this is the initial reduction pass or a latter one. Indeed, the input + * texture for the initial reduction is the source texture itself, while the input texture to a + * latter reduction pass is an intermediate texture after one or more reductions have happened. + * This is significant because the data being reduced might be computed from the original data + * and different from it, for instance, when summing the luminance of an image, the original data + * is a vec4 color, while the reduced data is a float luminance value. So for the initial + * reduction pass, the luminance will be computed from the color, reduced, then stored into an + * intermediate float texture. On the other hand, for latter reduction passes, the luminance will + * be loaded directly and reduced without extra processing. So the developer is expected to + * define the INITIALIZE and LOAD macros to be expressions that derive the needed value from the + * loaded value for the initial reduction pass and latter ones respectively. */ + reduction_data[gl_LocalInvocationIndex] = is_initial_reduction ? INITIALIZE(value) : LOAD(value); + + /* Reduce the reduction data by half on every iteration until only one element remains. See the + * above figure for an intuitive understanding of the stride value. */ + for (uint stride = reduction_size / 2; stride > 0; stride /= 2) { + barrier(); + + /* Only the threads up to the current stride should be active as can be seen in the diagram + * above. */ + if (gl_LocalInvocationIndex >= stride) { + continue; + } + + /* Reduce each two elements that are stride apart, writing the result to the element with the + * lower index, as can be seen in the diagram above. The developer is expected to define the + * REDUCE macro to be a commutative and associative binary operator suitable for parallel + * reduction. */ + reduction_data[gl_LocalInvocationIndex] = REDUCE( + reduction_data[gl_LocalInvocationIndex], reduction_data[gl_LocalInvocationIndex + stride]); + } + + /* Finally, the result of the reduction is available as the first element in the reduction data, + * write it to the pixel corresponding to the work group, making sure only the one thread writes + * it. */ + barrier(); + if (gl_LocalInvocationIndex == 0) { + imageStore(output_img, ivec2(gl_WorkGroupID.xy), vec4(reduction_data[0])); + } +} diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_parallel_reduction_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_parallel_reduction_info.hh new file mode 100644 index 00000000000..2e661f280af --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_parallel_reduction_info.hh @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_parallel_reduction_shared) + .local_group_size(16, 16) + .push_constant(Type::BOOL, "is_initial_reduction") + .sampler(0, ImageType::FLOAT_2D, "input_tx") + .compute_source("compositor_parallel_reduction.glsl"); + +/* -------------------------------------------------------------------- + * Sum Reductions. + */ + +GPU_SHADER_CREATE_INFO(compositor_sum_float_shared) + .additional_info("compositor_parallel_reduction_shared") + .image(0, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .define("TYPE", "float") + .define("IDENTITY", "vec4(0.0)") + .define("LOAD(value)", "value.x") + .define("REDUCE(lhs, rhs)", "lhs + rhs"); + +GPU_SHADER_CREATE_INFO(compositor_sum_red) + .additional_info("compositor_sum_float_shared") + .define("INITIALIZE(value)", "value.r") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_sum_green) + .additional_info("compositor_sum_float_shared") + .define("INITIALIZE(value)", "value.g") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_sum_blue) + .additional_info("compositor_sum_float_shared") + .define("INITIALIZE(value)", "value.b") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_sum_luminance) + .additional_info("compositor_sum_float_shared") + .push_constant(Type::VEC3, "luminance_coefficients") + .define("INITIALIZE(value)", "dot(value.rgb, luminance_coefficients)") + .do_static_compilation(true); + +/* -------------------------------------------------------------------- + * Sum Of Squared Difference Reductions. + */ + +GPU_SHADER_CREATE_INFO(compositor_sum_squared_difference_float_shared) + .additional_info("compositor_parallel_reduction_shared") + .image(0, GPU_R32F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .push_constant(Type::FLOAT, "subtrahend") + .define("TYPE", "float") + .define("IDENTITY", "vec4(subtrahend)") + .define("LOAD(value)", "value.x") + .define("REDUCE(lhs, rhs)", "lhs + rhs"); + +GPU_SHADER_CREATE_INFO(compositor_sum_red_squared_difference) + .additional_info("compositor_sum_squared_difference_float_shared") + .define("INITIALIZE(value)", "pow(value.r - subtrahend, 2.0)") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_sum_green_squared_difference) + .additional_info("compositor_sum_squared_difference_float_shared") + .define("INITIALIZE(value)", "pow(value.g - subtrahend, 2.0)") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_sum_blue_squared_difference) + .additional_info("compositor_sum_squared_difference_float_shared") + .define("INITIALIZE(value)", "pow(value.b - subtrahend, 2.0)") + .do_static_compilation(true); + +GPU_SHADER_CREATE_INFO(compositor_sum_luminance_squared_difference) + .additional_info("compositor_sum_squared_difference_float_shared") + .push_constant(Type::VEC3, "luminance_coefficients") + .define("INITIALIZE(value)", "pow(dot(value.rgb, luminance_coefficients) - subtrahend, 2.0)") + .do_static_compilation(true); diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 4715543ebaf..89730006103 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -2077,6 +2077,15 @@ typedef enum CMPNodeFilterMethod { CMP_NODE_FILTER_SHARP_DIAMOND = 7, } CMPNodeFilterMethod; +/* Levels Node. Stored in custom1. */ +typedef enum CMPNodeLevelsChannel { + CMP_NODE_LEVLES_LUMINANCE = 1, + CMP_NODE_LEVLES_RED = 2, + CMP_NODE_LEVLES_GREEN = 3, + CMP_NODE_LEVLES_BLUE = 4, + CMP_NODE_LEVLES_LUMINANCE_BT709 = 5, +} CMPNodeLevelsChannel; + /* Plane track deform node. */ enum { diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 2b138b3b9b4..0b0970a835e 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -6813,11 +6813,11 @@ static void def_cmp_levels(StructRNA *srna) PropertyRNA *prop; static const EnumPropertyItem channel_items[] = { - {1, "COMBINED_RGB", 0, "Combined", "Combined RGB"}, - {2, "RED", 0, "Red", "Red Channel"}, - {3, "GREEN", 0, "Green", "Green Channel"}, - {4, "BLUE", 0, "Blue", "Blue Channel"}, - {5, "LUMINANCE", 0, "Luminance", "Luminance Channel"}, + {CMP_NODE_LEVLES_LUMINANCE, "COMBINED_RGB", 0, "Combined", "Combined RGB"}, + {CMP_NODE_LEVLES_RED, "RED", 0, "Red", "Red Channel"}, + {CMP_NODE_LEVLES_GREEN, "GREEN", 0, "Green", "Green Channel"}, + {CMP_NODE_LEVLES_BLUE, "BLUE", 0, "Blue", "Blue Channel"}, + {CMP_NODE_LEVLES_LUMINANCE_BT709, "LUMINANCE", 0, "Luminance", "Luminance Channel"}, {0, NULL, 0, NULL, NULL}, }; diff --git a/source/blender/nodes/composite/CMakeLists.txt b/source/blender/nodes/composite/CMakeLists.txt index 2537e8e93cc..4255a2dde21 100644 --- a/source/blender/nodes/composite/CMakeLists.txt +++ b/source/blender/nodes/composite/CMakeLists.txt @@ -18,6 +18,7 @@ set(INC ../../render ../../windowmanager ../../compositor/realtime_compositor + ../../compositor/realtime_compositor/algorithms ../../../../intern/guardedalloc # dna_type_offsets.h diff --git a/source/blender/nodes/composite/nodes/node_composite_levels.cc b/source/blender/nodes/composite/nodes/node_composite_levels.cc index a4fe1f33813..4c901372b9f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_levels.cc +++ b/source/blender/nodes/composite/nodes/node_composite_levels.cc @@ -5,9 +5,18 @@ * \ingroup cmpnodes */ +#include <cmath> + +#include "BLI_assert.h" +#include "BLI_math_vec_types.hh" +#include "BLI_math_vector.hh" + #include "UI_interface.h" #include "UI_resources.h" +#include "IMB_colormanagement.h" + +#include "COM_algorithm_parallel_reduction.hh" #include "COM_node_operation.hh" #include "node_composite_util.hh" @@ -18,7 +27,9 @@ namespace blender::nodes::node_composite_levels_cc { static void cmp_node_levels_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({0.0f, 0.0f, 0.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({0.0f, 0.0f, 0.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Float>(N_("Mean")); b.add_output<decl::Float>(N_("Std Dev")); } @@ -36,13 +47,140 @@ static void node_composit_buts_view_levels(uiLayout *layout, bContext * /*C*/, P using namespace blender::realtime_compositor; class LevelsOperation : public NodeOperation { + private: + constexpr static float luminance_coefficients_bt709_[3] = {0.2126f, 0.7152f, 0.0722f}; + public: using NodeOperation::NodeOperation; void execute() override { - get_result("Mean").allocate_invalid(); - get_result("Std Dev").allocate_invalid(); + if (get_input("Image").is_single_value()) { + execute_single_value(); + return; + } + + const float mean = compute_mean(); + + Result &mean_result = get_result("Mean"); + if (mean_result.should_compute()) { + mean_result.allocate_single_value(); + mean_result.set_float_value(mean); + } + + Result &standard_deviation_result = get_result("Std Dev"); + if (standard_deviation_result.should_compute()) { + const float standard_deviation = compute_standard_deviation(mean); + standard_deviation_result.allocate_single_value(); + standard_deviation_result.set_float_value(standard_deviation); + } + } + + void execute_single_value() + { + Result &standard_deviation_result = get_result("Std Dev"); + if (standard_deviation_result.should_compute()) { + standard_deviation_result.allocate_single_value(); + standard_deviation_result.set_float_value(0.0f); + } + + Result &mean_result = get_result("Mean"); + if (!mean_result.should_compute()) { + return; + } + + mean_result.allocate_single_value(); + const float3 input = float3(get_input("Image").get_color_value()); + + switch (get_channel()) { + case CMP_NODE_LEVLES_RED: + mean_result.set_float_value(input.x); + break; + case CMP_NODE_LEVLES_GREEN: + mean_result.set_float_value(input.y); + break; + case CMP_NODE_LEVLES_BLUE: + mean_result.set_float_value(input.z); + break; + case CMP_NODE_LEVLES_LUMINANCE_BT709: + mean_result.set_float_value(math::dot(input, float3(luminance_coefficients_bt709_))); + break; + case CMP_NODE_LEVLES_LUMINANCE: { + float luminance_coefficients[3]; + IMB_colormanagement_get_luminance_coefficients(luminance_coefficients); + mean_result.set_float_value(math::dot(input, float3(luminance_coefficients))); + break; + } + default: + BLI_assert_unreachable(); + break; + } + } + + float compute_mean() + { + const Result &input = get_input("Image"); + return compute_sum() / (input.domain().size.x * input.domain().size.y); + } + + float compute_sum() + { + const Result &input = get_input("Image"); + switch (get_channel()) { + case CMP_NODE_LEVLES_RED: + return sum_red(context(), input.texture()); + case CMP_NODE_LEVLES_GREEN: + return sum_green(context(), input.texture()); + case CMP_NODE_LEVLES_BLUE: + return sum_blue(context(), input.texture()); + case CMP_NODE_LEVLES_LUMINANCE_BT709: + return sum_luminance(context(), input.texture(), float3(luminance_coefficients_bt709_)); + case CMP_NODE_LEVLES_LUMINANCE: { + float luminance_coefficients[3]; + IMB_colormanagement_get_luminance_coefficients(luminance_coefficients); + return sum_luminance(context(), input.texture(), float3(luminance_coefficients)); + } + default: + BLI_assert_unreachable(); + return 0.0f; + } + } + + float compute_standard_deviation(float mean) + { + const Result &input = get_input("Image"); + const float sum = compute_sum_squared_difference(mean); + return std::sqrt(sum / (input.domain().size.x * input.domain().size.y)); + } + + float compute_sum_squared_difference(float subtrahend) + { + const Result &input = get_input("Image"); + switch (get_channel()) { + case CMP_NODE_LEVLES_RED: + return sum_red_squared_difference(context(), input.texture(), subtrahend); + case CMP_NODE_LEVLES_GREEN: + return sum_green_squared_difference(context(), input.texture(), subtrahend); + case CMP_NODE_LEVLES_BLUE: + return sum_blue_squared_difference(context(), input.texture(), subtrahend); + case CMP_NODE_LEVLES_LUMINANCE_BT709: + return sum_luminance_squared_difference( + context(), input.texture(), float3(luminance_coefficients_bt709_), subtrahend); + case CMP_NODE_LEVLES_LUMINANCE: { + float luminance_coefficients[3]; + IMB_colormanagement_get_luminance_coefficients(luminance_coefficients); + return sum_luminance_squared_difference( + context(), input.texture(), float3(luminance_coefficients), subtrahend); + } + default: + BLI_assert_unreachable(); + return 0.0f; + } + } + + CMPNodeLevelsChannel get_channel() + { + return static_cast<CMPNodeLevelsChannel>(bnode().custom1); } }; |