diff options
author | Omar Emara <mail@OmarEmara.dev> | 2022-04-01 14:14:08 +0300 |
---|---|---|
committer | Omar Emara <mail@OmarEmara.dev> | 2022-04-01 14:14:08 +0300 |
commit | 2cbfeca07e30ec6ecc135cf41c4a71c51a2659fb (patch) | |
tree | 47f1342747d5230fc16fe1eb7d9d2c5d81fdb2d6 | |
parent | 7865678225cbc56b727390c3ecdbfd353cad31d7 (diff) |
Viewport Compositor: Implement Crop node
6 files changed, 217 insertions, 1 deletions
diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 469ea3ea037..68a6b5d22c1 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -309,8 +309,10 @@ set(GLSL_SRC shaders/compositor/material/gpu_shader_compositor_texture_utils.glsl shaders/compositor/material/gpu_shader_compositor_type_conversion.glsl + shaders/compositor/compositor_alpha_crop.glsl shaders/compositor/compositor_convert.glsl shaders/compositor/compositor_filter.glsl + shaders/compositor/compositor_image_crop.glsl shaders/compositor/compositor_realize_on_domain.glsl shaders/compositor/compositor_split_viewer.glsl @@ -495,8 +497,10 @@ set(SHADER_CREATE_INFOS shaders/infos/gpu_shader_text_info.hh shaders/infos/gpu_srgb_to_framebuffer_space_info.hh + shaders/compositor/infos/compositor_alpha_crop_info.hh shaders/compositor/infos/compositor_convert_info.hh shaders/compositor/infos/compositor_filter_info.hh + shaders/compositor/infos/compositor_image_crop_info.hh shaders/compositor/infos/compositor_realize_on_domain_info.hh shaders/compositor/infos/compositor_split_viewer_info.hh ) diff --git a/source/blender/gpu/shaders/compositor/compositor_alpha_crop.glsl b/source/blender/gpu/shaders/compositor/compositor_alpha_crop.glsl new file mode 100644 index 00000000000..344d34b30d4 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_alpha_crop.glsl @@ -0,0 +1,10 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utils.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + /* Write the pixel color if it is inside the cropping region, otherwise, write zero. */ + bool is_inside = all(greaterThan(xy, lower_bound)) && all(lessThan(xy, higher_bound)); + vec4 color = is_inside ? texture_load(input_image, xy) : vec4(0.0); + imageStore(output_image, xy, color); +} diff --git a/source/blender/gpu/shaders/compositor/compositor_image_crop.glsl b/source/blender/gpu/shaders/compositor/compositor_image_crop.glsl new file mode 100644 index 00000000000..fe2c2818ba3 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/compositor_image_crop.glsl @@ -0,0 +1,7 @@ +#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utils.glsl) + +void main() +{ + ivec2 xy = ivec2(gl_GlobalInvocationID.xy); + imageStore(output_image, xy, texture_load(input_image, xy + lower_bound)); +} diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_alpha_crop_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_alpha_crop_info.hh new file mode 100644 index 00000000000..0f0be5f95bb --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_alpha_crop_info.hh @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_alpha_crop) + .local_group_size(16, 16) + .push_constant(Type::IVEC2, "lower_bound") + .push_constant(Type::IVEC2, "higher_bound") + .sampler(0, ImageType::FLOAT_2D, "input_image") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .compute_source("compositor_alpha_crop.glsl") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/compositor/infos/compositor_image_crop_info.hh b/source/blender/gpu/shaders/compositor/infos/compositor_image_crop_info.hh new file mode 100644 index 00000000000..381075a8944 --- /dev/null +++ b/source/blender/gpu/shaders/compositor/infos/compositor_image_crop_info.hh @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +#include "gpu_shader_create_info.hh" + +GPU_SHADER_CREATE_INFO(compositor_image_crop) + .local_group_size(16, 16) + .push_constant(Type::IVEC2, "lower_bound") + .sampler(0, ImageType::FLOAT_2D, "input_image") + .image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_image") + .compute_source("compositor_image_crop.glsl") + .do_static_compilation(true); diff --git a/source/blender/nodes/composite/nodes/node_composite_crop.cc b/source/blender/nodes/composite/nodes/node_composite_crop.cc index e14b7d04ea6..6d978389cfb 100644 --- a/source/blender/nodes/composite/nodes/node_composite_crop.cc +++ b/source/blender/nodes/composite/nodes/node_composite_crop.cc @@ -21,11 +21,22 @@ * \ingroup cmpnodes */ +#include "BLI_math_base.h" +#include "BLI_math_vec_types.hh" + +#include "DNA_node_types.h" + #include "RNA_access.h" #include "UI_interface.h" #include "UI_resources.h" +#include "GPU_compute.h" +#include "GPU_shader.h" +#include "GPU_texture.h" + +#include "NOD_compositor_execute.hh" + #include "node_composite_util.hh" /* **************** Crop ******************** */ @@ -34,7 +45,9 @@ namespace blender::nodes::node_composite_crop_cc { static void cmp_node_crop_declare(NodeDeclarationBuilder &b) { - b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f}); + b.add_input<decl::Color>(N_("Image")) + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .compositor_domain_priority(0); b.add_output<decl::Color>(N_("Image")); } @@ -70,6 +83,162 @@ static void node_composit_buts_crop(uiLayout *layout, bContext *UNUSED(C), Point } } +using namespace blender::viewport_compositor; + +class CropOperation : public NodeOperation { + public: + using NodeOperation::NodeOperation; + + void execute() override + { + /* The operation does nothing, so just pass the input through. */ + if (is_identity()) { + get_input("Image").pass_through(get_result("Image")); + } + + if (get_is_image_crop()) { + execute_image_crop(); + } + else { + execute_alpha_crop(); + } + } + + /* Crop by replacing areas outside of the cropping bounds with zero alpha. The output have the + * same domain as the input image. */ + void execute_alpha_crop() + { + GPUShader *shader = GPU_shader_create_from_info_name("compositor_alpha_crop"); + GPU_shader_bind(shader); + + int2 lower_bound, higher_bound; + compute_cropping_bounds(lower_bound, higher_bound); + GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + GPU_shader_uniform_2iv(shader, "higher_bound", higher_bound); + + const Result &input_image = get_input("Image"); + input_image.bind_as_texture(shader, "input_image"); + + Result &output_image = get_result("Image"); + output_image.allocate_texture(compute_domain()); + output_image.bind_as_image(shader, "output_image"); + + const int2 size = compute_domain().size; + GPU_compute_dispatch(shader, size.x / 16 + 1, size.y / 16 + 1, 1); + + input_image.unbind_as_texture(); + output_image.unbind_as_image(); + GPU_shader_unbind(); + GPU_shader_free(shader); + } + + /* Crop the image into a new size that matches the cropping bounds. */ + void execute_image_crop() + { + int2 lower_bound, higher_bound; + compute_cropping_bounds(lower_bound, higher_bound); + + /* The image is cropped into nothing, so just return a single zero value. */ + if (lower_bound.x == higher_bound.x || lower_bound.y == higher_bound.y) { + Result &result = get_result("Image"); + result.allocate_single_value(); + result.set_color_value(float4(0.0f)); + return; + } + + GPUShader *shader = GPU_shader_create_from_info_name("compositor_image_crop"); + GPU_shader_bind(shader); + + GPU_shader_uniform_2iv(shader, "lower_bound", lower_bound); + + const Result &input_image = get_input("Image"); + input_image.bind_as_texture(shader, "input_image"); + + const int2 size = higher_bound - lower_bound; + + Result &output_image = get_result("Image"); + output_image.allocate_texture(Domain(size, compute_domain().transformation)); + output_image.bind_as_image(shader, "output_image"); + + GPU_compute_dispatch(shader, size.x / 16 + 1, size.y / 16 + 1, 1); + + input_image.unbind_as_texture(); + output_image.unbind_as_image(); + GPU_shader_unbind(); + GPU_shader_free(shader); + } + + /* If true, the image should actually be cropped into a new size. Otherwise, if false, the region + * outside of the cropping bounds will be set to a zero alpha value. */ + bool get_is_image_crop() + { + return node().custom1; + } + + bool get_is_relative() + { + return node().custom2; + } + + NodeTwoXYs &get_node_two_xys() + { + return *static_cast<NodeTwoXYs *>(node().storage); + } + + /* Returns true if the operation does nothing and the input can be passed through. */ + bool is_identity() + { + const Result &input = get_input("Image"); + /* Single value inputs can't be cropped and are returned as is. */ + if (input.is_single_value()) { + return true; + } + + int2 lower_bound, higher_bound; + compute_cropping_bounds(lower_bound, higher_bound); + const int2 input_size = input.domain().size; + /* The cropping bounds cover the whole image, so no cropping happens. */ + if (lower_bound == int2(0) && higher_bound == input_size) { + return true; + } + + return false; + } + + void compute_cropping_bounds(int2 &lower_bound, int2 &higher_bound) + { + const NodeTwoXYs &node_two_xys = get_node_two_xys(); + const int2 input_size = get_input("Image").domain().size; + + if (get_is_relative()) { + /* The cropping bounds are relative to the image size. The factors are in the [0, 1] range, + * so it is guaranteed that they won't go over the input image size. */ + lower_bound.x = input_size.x * node_two_xys.fac_x1; + lower_bound.y = input_size.y * node_two_xys.fac_y2; + higher_bound.x = input_size.x * node_two_xys.fac_x2; + higher_bound.y = input_size.y * node_two_xys.fac_y1; + } + else { + /* Make sure the bounds don't go over the input image size. */ + lower_bound.x = min_ii(node_two_xys.x1, input_size.x); + lower_bound.y = min_ii(node_two_xys.y2, input_size.y); + higher_bound.x = min_ii(node_two_xys.x2, input_size.x); + higher_bound.y = min_ii(node_two_xys.y1, input_size.y); + } + + /* Make sure higher bound is actually higher than the lower bound. */ + lower_bound.x = min_ii(lower_bound.x, higher_bound.x); + lower_bound.y = min_ii(lower_bound.y, higher_bound.y); + higher_bound.x = max_ii(lower_bound.x, higher_bound.x); + higher_bound.y = max_ii(lower_bound.y, higher_bound.y); + } +}; + +static NodeOperation *get_compositor_operation(Context &context, DNode node) +{ + return new CropOperation(context, node); +} + } // namespace blender::nodes::node_composite_crop_cc void register_node_type_cmp_crop() @@ -83,6 +252,7 @@ void register_node_type_cmp_crop() ntype.draw_buttons = file_ns::node_composit_buts_crop; node_type_init(&ntype, file_ns::node_composit_init_crop); node_type_storage(&ntype, "NodeTwoXYs", node_free_standard_storage, node_copy_standard_storage); + ntype.get_compositor_operation = file_ns::get_compositor_operation; nodeRegisterType(&ntype); } |