diff --git a/source/blender/compositor/realtime_compositor/CMakeLists.txt b/source/blender/compositor/realtime_compositor/CMakeLists.txt index 230dbbf4fdb..abf1e4e639c 100644 --- a/source/blender/compositor/realtime_compositor/CMakeLists.txt +++ b/source/blender/compositor/realtime_compositor/CMakeLists.txt @@ -188,6 +188,7 @@ set(GLSL_SRC shaders/compositor_movie_distortion.glsl shaders/compositor_normalize.glsl shaders/compositor_parallel_reduction.glsl + shaders/compositor_pixelate.glsl shaders/compositor_plane_deform.glsl shaders/compositor_plane_deform_motion_blur.glsl shaders/compositor_premultiply_alpha.glsl @@ -311,6 +312,7 @@ set(SRC_SHADER_CREATE_INFOS shaders/infos/compositor_movie_distortion_info.hh shaders/infos/compositor_normalize_info.hh shaders/infos/compositor_parallel_reduction_info.hh + shaders/infos/compositor_pixelate_info.hh shaders/infos/compositor_plane_deform_info.hh shaders/infos/compositor_plane_deform_motion_blur_info.hh shaders/infos/compositor_premultiply_alpha_info.hh diff --git a/source/blender/compositor/realtime_compositor/shaders/compositor_pixelate.glsl b/source/blender/compositor/realtime_compositor/shaders/compositor_pixelate.glsl new file mode 100644 index 00000000000..83c327f461e --- /dev/null +++ b/source/blender/compositor/realtime_compositor/shaders/compositor_pixelate.glsl @@ -0,0 +1,24 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl) + +void main() +{ + ivec2 texel = ivec2(gl_GlobalInvocationID.xy); + + ivec2 start = (texel / ivec2(pixel_size)) * ivec2(pixel_size); + ivec2 end = min(start + ivec2(pixel_size), texture_size(input_tx)); + + vec4 accumulated_color = vec4(0.0); + for (int y = start.y; y < end.y; y++) { + for (int x = start.x; x < end.x; x++) { + accumulated_color += texture_load_unbound(input_tx, ivec2(x, y)); + } + } + + ivec2 size = end - start; + int count = size.x * size.y; + imageStore(output_img, texel, accumulated_color / count); +} diff --git a/source/blender/compositor/realtime_compositor/shaders/infos/compositor_pixelate_info.hh b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_pixelate_info.hh new file mode 100644 index 00000000000..47cc6cf4661 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/shaders/infos/compositor_pixelate_info.hh @@ -0,0 +1,13 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_pixelate) + .local_group_size(16, 16) + .push_constant(Type::INT, "pixel_size") + .sampler(0, ImageType::FLOAT_2D, "input_tx") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img") + .compute_source("compositor_pixelate.glsl") + .do_static_compilation(true); diff --git a/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_texture_utilities.glsl b/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_texture_utilities.glsl index 4da3e376cb6..1fc2dbfa59f 100644 --- a/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_texture_utilities.glsl +++ b/source/blender/compositor/realtime_compositor/shaders/library/gpu_shader_compositor_texture_utilities.glsl @@ -28,6 +28,12 @@ vec4 texture_load(sampler2D sampler_2d, ivec2 texel) return texelFetch(sampler_2d, clamp(texel, ivec2(0), texture_bounds), 0); } +/* A shorthand for 2D texelFetch with zero LOD. */ +vec4 texture_load_unbound(sampler2D sampler_2d, ivec2 texel) +{ + return texelFetch(sampler_2d, texel, 0); +} + /* A shorthand for 2D texelFetch with zero LOD and a fallback value for out-of-bound access. */ vec4 texture_load(sampler2D sampler_2d, ivec2 texel, vec4 fallback) { diff --git a/source/blender/nodes/composite/nodes/node_composite_pixelate.cc b/source/blender/nodes/composite/nodes/node_composite_pixelate.cc index baba7c8bf87..440d6fda654 100644 --- a/source/blender/nodes/composite/nodes/node_composite_pixelate.cc +++ b/source/blender/nodes/composite/nodes/node_composite_pixelate.cc @@ -6,9 +6,10 @@ * \ingroup cmpnodes */ -#include "BLI_math_matrix.hh" +#include "GPU_shader.h" #include "COM_node_operation.hh" +#include "COM_utilities.hh" #include "UI_interface.hh" #include "UI_resources.hh" @@ -21,7 +22,7 @@ namespace blender::nodes::node_composite_pixelate_cc { static void cmp_node_pixelate_declare(NodeDeclarationBuilder &b) { - b.add_input("Color"); + b.add_input("Color").compositor_domain_priority(0); b.add_output("Color"); } @@ -43,36 +44,34 @@ class PixelateOperation : public NodeOperation { void execute() override { - /* It might seems strange that the input is passed through without any processing, but note - * that the actual processing happens inside the domain realization input processor of the - * input. Indeed, the pixelate node merely realizes its input on a smaller-sized domain that - * matches its apparent size, that is, its size after the domain transformation. The pixelate - * node has no effect if the input is scaled-up. See the compute_domain method for more - * information. */ - Result &result = get_result("Color"); - get_input("Color").pass_through(result); + Result &input_image = get_input("Color"); + Result &output_image = get_result("Color"); + if (input_image.is_single_value()) { + input_image.pass_through(output_image); + return; + } - result.get_realization_options().interpolation = Interpolation::Nearest; + GPUShader *shader = context().get_shader("compositor_pixelate"); + GPU_shader_bind(shader); + + GPU_shader_uniform_1i(shader, "pixel_size", get_pixel_size()); + + input_image.bind_as_texture(shader, "input_tx"); + + const Domain domain = compute_domain(); + output_image.allocate_texture(domain); + output_image.bind_as_image(shader, "output_img"); + + compute_dispatch_threads_at_least(shader, domain.size); + + GPU_shader_unbind(); + output_image.unbind_as_image(); + input_image.unbind_as_texture(); } - /* Compute a smaller-sized domain that matches the apparent size of the input while having a unit - * scale transformation, see the execute method for more information. */ - Domain compute_domain() override + float get_pixel_size() { - Domain domain = get_input("Color").domain(); - - /* Get the scaling component of the domain transformation, but make sure it doesn't exceed 1, - * because pixelation should only happen if the input is scaled down. */ - const float2 scale = math::min(float2(1.0f), math::to_scale(float2x2(domain.transformation))); - - /* Multiply the size of the domain by its scale to match its apparent size, but make sure it is - * at least 1 pixel in both axis. */ - domain.size = math::max(int2(float2(domain.size) * scale), int2(1)); - - /* Reset the scale of the transformation by transforming it with the inverse of the scale. */ - domain.transformation *= math::from_scale(math::safe_divide(float2(1.0f), scale)); - - return domain; + return bnode().custom1; } };