Realtime Compositor: Rewrite Pixelate node #117243

Merged
Omar Emara merged 3 commits from OmarEmaraDev/blender:gpu-piexelate-node into main 2024-01-18 11:52:30 +01:00
5 changed files with 72 additions and 28 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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);

View File

@ -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)
{

View File

@ -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<decl::Color>("Color");
b.add_input<decl::Color>("Color").compositor_domain_priority(0);
b.add_output<decl::Color>("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<float3x3>(math::safe_divide(float2(1.0f), scale));
return domain;
return bnode().custom1;
}
};