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
This commit is contained in:
parent
f6a6992031
commit
0037411f55
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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]));
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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 {
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ set(INC
|
|||
../../render
|
||||
../../windowmanager
|
||||
../../compositor/realtime_compositor
|
||||
../../compositor/realtime_compositor/algorithms
|
||||
../../../../intern/guardedalloc
|
||||
|
||||
# dna_type_offsets.h
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue