diff --git a/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_jump_flooding.hh b/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_jump_flooding.hh index f98806d3324..21d57a1120f 100644 --- a/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_jump_flooding.hh +++ b/source/blender/compositor/realtime_compositor/algorithms/COM_algorithm_jump_flooding.hh @@ -16,10 +16,10 @@ namespace blender::realtime_compositor { * accurately approximate an euclidean distance transform, finally, it can be used to flood fill * regions of an image. * - * The input is expected to be initialized by the initialize_jump_flooding_value function from the - * gpu_shader_compositor_jump_flooding_lib.glsl library. Seed pixels should specify true for the - * is_seed argument, and false otherwise. The texel input should be the texel location of the - * pixel. Both the input and output results should be of type ResultType::Int2. + * The input is expected to be initialized by the initialize_jump_flooding_value function. Seed + * pixels should specify true for the is_seed argument, and false otherwise. The texel input should + * be the texel location of the pixel. Both the input and output results should be of type + * ResultType::Int2. * * To compute a Voronoi diagram, the pixels lying at the centroid of the Voronoi cell should be * marked as seed pixels. To compute an euclidean distance transform of a region or flood fill a @@ -44,4 +44,23 @@ namespace blender::realtime_compositor { * paper. */ void jump_flooding(Context &context, Result &input, Result &output); +/* A special value that indicates that the pixel has not be flooded yet, and consequently is not a + * seed pixel. */ +#define JUMP_FLOODING_NON_FLOODED_VALUE int2(-1) + +/* Given the texel location of the closest seed pixel and whether the pixel is flooded, encode that + * information in an int2. */ +inline int2 encode_jump_flooding_value(const int2 &closest_seed_texel, const bool is_flooded) +{ + return is_flooded ? closest_seed_texel : JUMP_FLOODING_NON_FLOODED_VALUE; +} + +/* Initialize the pixel at the given texel location for the algorithm as being seed or background. + * This essentially calls encode_jump_flooding_value with the texel location, because the pixel is + * the closest seed to itself. */ +inline int2 initialize_jump_flooding_value(const int2 &texel, const bool is_seed) +{ + return encode_jump_flooding_value(texel, is_seed); +} + } // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/algorithms/intern/jump_flooding.cc b/source/blender/compositor/realtime_compositor/algorithms/intern/jump_flooding.cc index b1f0aae7528..07fd7e640a5 100644 --- a/source/blender/compositor/realtime_compositor/algorithms/intern/jump_flooding.cc +++ b/source/blender/compositor/realtime_compositor/algorithms/intern/jump_flooding.cc @@ -2,11 +2,14 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include #include #include "BLI_assert.h" #include "BLI_math_base.h" #include "BLI_math_base.hh" +#include "BLI_math_vector.hh" +#include "BLI_math_vector_types.hh" #include "GPU_shader.hh" @@ -18,7 +21,7 @@ namespace blender::realtime_compositor { -static void jump_flooding_pass(Context &context, Result &input, Result &output, int step_size) +static void jump_flooding_pass_gpu(Context &context, Result &input, Result &output, int step_size) { GPUShader *shader = context.get_shader("compositor_jump_flooding", ResultPrecision::Half); GPU_shader_bind(shader); @@ -35,6 +38,75 @@ static void jump_flooding_pass(Context &context, Result &input, Result &output, output.unbind_as_image(); } +/* This function implements a single pass of the Jump Flooding algorithm described in sections 3.1 + * and 3.2 of the paper: + * + * Rong, Guodong, and Tiow-Seng Tan. "Jump flooding in GPU with applications to Voronoi diagram + * and distance transform." Proceedings of the 2006 symposium on Interactive 3D graphics and + * games. 2006. + * + * The function is a straightforward implementation of the aforementioned sections of the paper, + * noting that the nil special value in the paper is equivalent to JUMP_FLOODING_NON_FLOODED_VALUE. + * + * The `COM_algorithm_jump_flooding.hh` header contains the necessary utility functions to + * initialize and encode the jump flooding values. */ +static void jump_flooding_pass_cpu(Result &input, Result &output, int step_size) +{ + parallel_for(input.domain().size, [&](const int2 texel) { + /* For each of the previously flooded pixels in the 3x3 window of the given step size around + * the center pixel, find the position of the closest seed pixel that is closest to the current + * center pixel. */ + int2 closest_seed_texel = int2(0); + float minimum_squared_distance = std::numeric_limits::max(); + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + int2 offset = int2(i, j) * step_size; + + /* Use #JUMP_FLOODING_NON_FLOODED_VALUE as a fallback value to exempt out of bound pixels + * from the loop as can be seen in the following continue condition. */ + int4 fallback = int4(JUMP_FLOODING_NON_FLOODED_VALUE, int2(0)); + int2 jump_flooding_value = input.load_pixel_fallback(texel + offset, fallback).xy(); + + /* The pixel is either not flooded yet or is out of bound, so skip it. */ + if (jump_flooding_value == JUMP_FLOODING_NON_FLOODED_VALUE) { + continue; + } + + /* The neighboring pixel is flooded, so its flooding value is the texel of the closest seed + * pixel to this neighboring pixel. */ + int2 closest_seed_texel_to_neighbor = jump_flooding_value; + + /* Compute the squared distance to the neighbor's closest seed pixel. */ + float squared_distance = math::distance_squared(float2(closest_seed_texel_to_neighbor), + float2(texel)); + + if (squared_distance < minimum_squared_distance) { + minimum_squared_distance = squared_distance; + closest_seed_texel = closest_seed_texel_to_neighbor; + } + } + } + + /* If the minimum squared distance is still #std::numeric_limits::max(), that means the + * loop never got past the continue condition and thus no flooding happened. If flooding + * happened, we encode the closest seed texel in the format expected by the algorithm. */ + bool flooding_happened = minimum_squared_distance != std::numeric_limits::max(); + int2 jump_flooding_value = encode_jump_flooding_value(closest_seed_texel, flooding_happened); + + output.store_pixel(texel, int4(jump_flooding_value, int2(0))); + }); +} + +static void jump_flooding_pass(Context &context, Result &input, Result &output, int step_size) +{ + if (context.use_gpu()) { + jump_flooding_pass_gpu(context, input, output, step_size); + } + else { + jump_flooding_pass_cpu(input, output, step_size); + } +} + void jump_flooding(Context &context, Result &input, Result &output) { BLI_assert(input.type() == ResultType::Int2);