Blender V5.0
jump_flooding.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <limits>
6#include <utility>
7
8#include "BLI_assert.h"
9#include "BLI_math_base.h"
10#include "BLI_math_base.hh"
11#include "BLI_math_vector.hh"
13
14#include "GPU_shader.hh"
15
16#include "COM_context.hh"
17#include "COM_result.hh"
18#include "COM_utilities.hh"
19
21
22namespace blender::compositor {
23
24static void jump_flooding_pass_gpu(Context &context, Result &input, Result &output, int step_size)
25{
26 gpu::Shader *shader = context.get_shader("compositor_jump_flooding", ResultPrecision::Half);
27 GPU_shader_bind(shader);
28
29 GPU_shader_uniform_1i(shader, "step_size", step_size);
30
31 input.bind_as_texture(shader, "input_tx");
32 output.bind_as_image(shader, "output_img");
33
34 compute_dispatch_threads_at_least(shader, input.domain().size);
35
37 input.unbind_as_texture();
38 output.unbind_as_image();
39}
40
41/* This function implements a single pass of the Jump Flooding algorithm described in sections 3.1
42 * and 3.2 of the paper:
43 *
44 * Rong, Guodong, and Tiow-Seng Tan. "Jump flooding in GPU with applications to Voronoi diagram
45 * and distance transform." Proceedings of the 2006 symposium on Interactive 3D graphics and
46 * games. 2006.
47 *
48 * The function is a straightforward implementation of the aforementioned sections of the paper,
49 * noting that the nil special value in the paper is equivalent to JUMP_FLOODING_NON_FLOODED_VALUE.
50 *
51 * The `COM_algorithm_jump_flooding.hh` header contains the necessary utility functions to
52 * initialize and encode the jump flooding values. */
53static void jump_flooding_pass_cpu(Result &input, Result &output, int step_size)
54{
55 parallel_for(input.domain().size, [&](const int2 texel) {
56 /* For each of the previously flooded pixels in the 3x3 window of the given step size around
57 * the center pixel, find the position of the closest seed pixel that is closest to the current
58 * center pixel. */
59 int2 closest_seed_texel = int2(0);
60 float minimum_squared_distance = std::numeric_limits<float>::max();
61 for (int j = -1; j <= 1; j++) {
62 for (int i = -1; i <= 1; i++) {
63 int2 offset = int2(i, j) * step_size;
64
65 /* Use #JUMP_FLOODING_NON_FLOODED_VALUE as a fallback value to exempt out of bound pixels
66 * from the loop as can be seen in the following continue condition. */
67 int2 fallback = JUMP_FLOODING_NON_FLOODED_VALUE;
68 int2 jump_flooding_value = input.load_pixel_fallback(texel + offset, fallback);
69
70 /* The pixel is either not flooded yet or is out of bound, so skip it. */
71 if (jump_flooding_value == JUMP_FLOODING_NON_FLOODED_VALUE) {
72 continue;
73 }
74
75 /* The neighboring pixel is flooded, so its flooding value is the texel of the closest seed
76 * pixel to this neighboring pixel. */
77 int2 closest_seed_texel_to_neighbor = jump_flooding_value;
78
79 /* Compute the squared distance to the neighbor's closest seed pixel. */
80 float squared_distance = math::distance_squared(float2(closest_seed_texel_to_neighbor),
81 float2(texel));
82
83 if (squared_distance < minimum_squared_distance) {
84 minimum_squared_distance = squared_distance;
85 closest_seed_texel = closest_seed_texel_to_neighbor;
86 }
87 }
88 }
89
90 /* If the minimum squared distance is still #std::numeric_limits<float>::max(), that means the
91 * loop never got past the continue condition and thus no flooding happened. If flooding
92 * happened, we encode the closest seed texel in the format expected by the algorithm. */
93 bool flooding_happened = minimum_squared_distance != std::numeric_limits<float>::max();
94 int2 jump_flooding_value = encode_jump_flooding_value(closest_seed_texel, flooding_happened);
95
96 output.store_pixel(texel, jump_flooding_value);
97 });
98}
99
100static void jump_flooding_pass(Context &context, Result &input, Result &output, int step_size)
101{
102 if (context.use_gpu()) {
103 jump_flooding_pass_gpu(context, input, output, step_size);
104 }
105 else {
107 }
108}
109
111{
114
115 /* First, run a jump flooding pass with a step size of 1. This initial pass is proposed by the
116 * 1+FJA variant to improve accuracy. */
117 Result initial_flooded_result = context.create_result(ResultType::Int2, ResultPrecision::Half);
118 initial_flooded_result.allocate_texture(input.domain());
119 jump_flooding_pass(context, input, initial_flooded_result, 1);
120
121 /* We compute the result using a ping-pong buffer, so create an intermediate result. */
122 Result *result_to_flood = &initial_flooded_result;
123 Result intermediate_result = context.create_result(ResultType::Int2, ResultPrecision::Half);
124 intermediate_result.allocate_texture(input.domain());
125 Result *result_after_flooding = &intermediate_result;
126
127 /* The algorithm starts with a step size that is half the size of the image. However, the
128 * algorithm assumes a square image that is a power of two in width without loss of generality.
129 * To generalize that, we use half the next power of two of the maximum dimension. */
130 const int max_size = math::max(input.domain().size.x, input.domain().size.y);
131 int step_size = power_of_2_max_i(max_size) / 2;
132
133 /* Successively apply a jump flooding pass, halving the step size every time and swapping the
134 * ping-pong buffers. */
135 while (step_size != 0) {
136 jump_flooding_pass(context, *result_to_flood, *result_after_flooding, step_size);
137 std::swap(result_to_flood, result_after_flooding);
138 step_size /= 2;
139 }
140
141 /* Notice that the output of the last pass is stored in result_to_flood due to the last swap, so
142 * steal the data from it and release the other buffer. */
143 result_after_flooding->release();
144 output.steal_data(*result_to_flood);
145}
146
147} // namespace blender::compositor
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE int power_of_2_max_i(int n)
void GPU_shader_bind(blender::gpu::Shader *shader, const blender::gpu::shader::SpecializationConstants *constants_state=nullptr)
void GPU_shader_uniform_1i(blender::gpu::Shader *sh, const char *name, int value)
void GPU_shader_unbind()
void allocate_texture(const Domain domain, const bool from_pool=true, const std::optional< ResultStorageType > storage_type=std::nullopt)
Definition result.cc:389
#define input
#define output
void compute_dispatch_threads_at_least(gpu::Shader *shader, int2 threads_range, int2 local_size=int2(16))
Definition utilities.cc:196
static void jump_flooding_pass_cpu(Result &input, Result &output, int step_size)
static void jump_flooding_pass(Context &context, Result &input, Result &output, int step_size)
int2 encode_jump_flooding_value(const int2 &closest_seed_texel, const bool is_flooded)
void jump_flooding(Context &context, Result &input, Result &output)
void parallel_for(const int2 range, const Function &function)
static void jump_flooding_pass_gpu(Context &context, Result &input, Result &output, int step_size)
T max(const T &a, const T &b)
VecBase< int32_t, 2 > int2