From 56f8c1c0f6e6efd6ce60270245ec9370144fde4c Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Thu, 29 Feb 2024 11:08:49 +0100 Subject: [PATCH] Compositor: Unify variable size blur between CPU and GPU This patch unifies the variable size blur between the CPU and GPU compositor. The difference is due to how weights are computed and used. The CPU computed a nested array of weights for every possible size, that is, from size 1 to the base size. Then, it assumed the kernel was separable and reconstructed a 2D kernel by selecting two 1D weights array and multiplying them for every pixel of the blur window. The GPU on the other hand computes a single quadrant of the 2D weights kernel and sampled it directly in the blur window. We favor the GPU implementation since it makes no assumptions about the separability of the weights kernel and since the CPU has no performance advantage even with the assumption in place. Pull Request: https://projects.blender.org/blender/blender/pulls/118834 --- .../COM_GaussianBokehBlurOperation.cc | 145 +++++++++++------- .../COM_GaussianBokehBlurOperation.h | 6 +- tests/data | 2 +- 3 files changed, 90 insertions(+), 63 deletions(-) diff --git a/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc b/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc index d2b9786234c..4a1f337033a 100644 --- a/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc +++ b/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.cc @@ -2,6 +2,11 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include + +#include "BLI_index_range.hh" +#include "BLI_math_vector.hh" + #include "COM_GaussianBokehBlurOperation.h" #include "RE_pipeline.h" @@ -148,7 +153,7 @@ void GaussianBokehBlurOperation::update_memory_buffer_partial(MemoryBuffer *outp GaussianBlurReferenceOperation::GaussianBlurReferenceOperation() : BlurBaseOperation(DataType::Color) { - maintabs_ = nullptr; + weights_ = nullptr; use_variable_size_ = true; } @@ -206,23 +211,45 @@ void GaussianBlurReferenceOperation::init_execution() void GaussianBlurReferenceOperation::update_gauss() { - int i; - int x = std::max(filtersizex_, filtersizey_); - maintabs_ = (float **)MEM_mallocN(x * sizeof(float *), "gauss array"); - for (i = 0; i < x; i++) { - maintabs_[i] = make_gausstab(i + 1, i + 1); - } -} + const int2 radius = int2(filtersizex_, filtersizey_); + const float2 scale = math::safe_divide(float2(1.0f), float2(radius)); + const int2 size = radius + int2(1); -void GaussianBlurReferenceOperation::deinit_execution() -{ - int x, i; - x = std::max(filtersizex_, filtersizey_); - for (i = 0; i < x; i++) { - MEM_freeN(maintabs_[i]); + rcti weights_area; + BLI_rcti_init(&weights_area, 0, size.x, 0, size.y); + weights_ = std::make_unique(DataType::Value, weights_area, false); + + float sum = 0.0f; + + const float center_weight = RE_filter_value(data_.filtertype, 0.0f); + *weights_->get_elem(0, 0) = center_weight; + sum += center_weight; + + for (const int x : IndexRange(size.x).drop_front(1)) { + const float weight = RE_filter_value(data_.filtertype, x * scale.x); + *weights_->get_elem(x, 0) = weight; + sum += weight * 2.0f; + } + + for (const int y : IndexRange(size.y).drop_front(1)) { + const float weight = RE_filter_value(data_.filtertype, y * scale.y); + *weights_->get_elem(0, y) = weight; + sum += weight * 2.0f; + } + + for (const int y : IndexRange(size.y).drop_front(1)) { + for (const int x : IndexRange(size.x).drop_front(1)) { + const float weight = RE_filter_value(data_.filtertype, math::length(float2(x, y) * scale)); + *weights_->get_elem(x, y) = weight; + sum += weight * 4.0f; + } + } + + for (const int y : IndexRange(size.y)) { + for (const int x : IndexRange(size.x)) { + *weights_->get_elem(x, y) /= sum; + } } - MEM_freeN(maintabs_); - BlurBaseOperation::deinit_execution(); } void GaussianBlurReferenceOperation::get_area_of_interest(const int input_idx, @@ -246,56 +273,56 @@ void GaussianBlurReferenceOperation::update_memory_buffer_partial(MemoryBuffer * const rcti &area, Span inputs) { + const MemoryBuffer *size_input = inputs[SIZE_INPUT_INDEX]; const MemoryBuffer *image_input = inputs[IMAGE_INPUT_INDEX]; - MemoryBuffer *size_input = inputs[SIZE_INPUT_INDEX]; - for (BuffersIterator it = output->iterate_with({size_input}, area); !it.is_end(); ++it) { - const float ref_size = *it.in(0); - int ref_radx = int(ref_size * radx_); - int ref_rady = int(ref_size * rady_); - if (ref_radx > filtersizex_) { - ref_radx = filtersizex_; - } - else if (ref_radx < 1) { - ref_radx = 1; - } - if (ref_rady > filtersizey_) { - ref_rady = filtersizey_; - } - else if (ref_rady < 1) { - ref_rady = 1; + + int2 weights_size = int2(weights_->get_width(), weights_->get_height()); + int2 base_radius = weights_size - int2(1); + + for (BuffersIterator it = output->iterate_with({}, area); !it.is_end(); ++it) { + float4 accumulated_color = float4(0.0f); + float4 accumulated_weight = float4(0.0f); + + int2 radius = int2(math::ceil(float2(base_radius) * *size_input->get_elem(it.x, it.y))); + + float4 center_color = float4(image_input->get_elem_clamped(it.x, it.y)); + float center_weight = *weights_->get_elem(0, 0); + accumulated_color += center_color * center_weight; + accumulated_weight += center_weight; + + for (int x = 1; x <= radius.x; x++) { + float weight_coordinates = (x / float(radius.x)) * base_radius.x; + float weight; + weights_->read_elem_bilinear(weight_coordinates, 0.0f, &weight); + accumulated_color += float4(image_input->get_elem_clamped(it.x + x, it.y)) * weight; + accumulated_color += float4(image_input->get_elem_clamped(it.x - x, it.y)) * weight; + accumulated_weight += weight * 2.0f; } - const int x = it.x; - const int y = it.y; - if (ref_radx == 1 && ref_rady == 1) { - image_input->read_elem(x, y, it.out); - continue; + for (int y = 1; y <= radius.y; y++) { + float weight_coordinates = (y / float(radius.y)) * base_radius.y; + float weight; + weights_->read_elem_bilinear(0.0f, weight_coordinates, &weight); + accumulated_color += float4(image_input->get_elem_clamped(it.x, it.y + y)) * weight; + accumulated_color += float4(image_input->get_elem_clamped(it.x, it.y - y)) * weight; + accumulated_weight += weight * 2.0f; } - const int w = get_width(); - const int height = get_height(); - const int minxr = x - ref_radx < 0 ? -x : -ref_radx; - const int maxxr = x + ref_radx > w ? w - x : ref_radx; - const int minyr = y - ref_rady < 0 ? -y : -ref_rady; - const int maxyr = y + ref_rady > height ? height - y : ref_rady; - - const float *gausstabx = maintabs_[ref_radx - 1]; - const float *gausstabcentx = gausstabx + ref_radx; - const float *gausstaby = maintabs_[ref_rady - 1]; - const float *gausstabcenty = gausstaby + ref_rady; - - float gauss_sum = 0.0f; - float color_sum[4] = {0}; - const float *row_color = image_input->get_elem(x + minxr, y + minyr); - for (int i = minyr; i < maxyr; i++, row_color += image_input->row_stride) { - const float *color = row_color; - for (int j = minxr; j < maxxr; j++, color += image_input->elem_stride) { - const float val = gausstabcenty[i] * gausstabcentx[j]; - gauss_sum += val; - madd_v4_v4fl(color_sum, color, val); + for (int y = 1; y <= radius.y; y++) { + for (int x = 1; x <= radius.x; x++) { + float2 weight_coordinates = (float2(x, y) / float2(radius)) * float2(base_radius); + float weight; + weights_->read_elem_bilinear(weight_coordinates.x, weight_coordinates.y, &weight); + accumulated_color += float4(image_input->get_elem_clamped(it.x + x, it.y + y)) * weight; + accumulated_color += float4(image_input->get_elem_clamped(it.x - x, it.y + y)) * weight; + accumulated_color += float4(image_input->get_elem_clamped(it.x + x, it.y - y)) * weight; + accumulated_color += float4(image_input->get_elem_clamped(it.x - x, it.y - y)) * weight; + accumulated_weight += weight * 4.0f; } } - mul_v4_v4fl(it.out, color_sum, 1.0f / gauss_sum); + + accumulated_color = math::safe_divide(accumulated_color, accumulated_weight); + copy_v4_v4(it.out, accumulated_color); } } diff --git a/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.h b/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.h index a21d4063191..63728bccc0b 100644 --- a/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.h +++ b/source/blender/compositor/operations/COM_GaussianBokehBlurOperation.h @@ -4,6 +4,8 @@ #pragma once +#include + #include "COM_BlurBaseOperation.h" #include "COM_NodeOperation.h" #include "COM_QualityStepHelper.h" @@ -32,7 +34,7 @@ class GaussianBokehBlurOperation : public BlurBaseOperation { class GaussianBlurReferenceOperation : public BlurBaseOperation { private: - float **maintabs_; + std::unique_ptr weights_; void update_gauss(); int filtersizex_; @@ -45,8 +47,6 @@ class GaussianBlurReferenceOperation : public BlurBaseOperation { void init_data() override; void init_execution() override; - void deinit_execution() override; - void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, diff --git a/tests/data b/tests/data index c642b3b3455..3a0fc9f3bae 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit c642b3b34558919fb69d1277463c96e16bc5253c +Subproject commit 3a0fc9f3bae1f1a838ed9e2aac0648fdad291817