Refactor: Compositor: Moved wrapped translation to its own code

This patch moves wrapped translation from a special case of the general
transform algorithm to the Translate node. Since the Translate node is
the only user of this special case, it doesn't make sense to complicate
a generate algorithm with it. This will make future refactors of this
code easier.

Pull Request: https://projects.blender.org/blender/blender/pulls/132793
This commit is contained in:
Omar Emara
2025-01-08 12:42:13 +01:00
committed by Omar Emara
parent ba50d5a658
commit 8f8ae302ba
6 changed files with 148 additions and 24 deletions

View File

@@ -248,6 +248,7 @@ set(GLSL_SRC
shaders/compositor_symmetric_separable_blur_variable_size.glsl
shaders/compositor_tone_map_photoreceptor.glsl
shaders/compositor_tone_map_simple.glsl
shaders/compositor_translate_wrapped.glsl
shaders/compositor_van_vliet_gaussian_blur.glsl
shaders/compositor_van_vliet_gaussian_blur_sum.glsl
shaders/compositor_write_output.glsl

View File

@@ -83,20 +83,11 @@ void transform(Context &context,
const float3x3 &transformation,
RealizationOptions realization_options)
{
/* If we are wrapping, the input is translated but the target domain remains fixed, which results
* in the input clipping on one side and wrapping on the opposite side. This mask vector can be
* multiplied to the translation component of the transformation to remove it. */
const float2 wrap_mask = float2(realization_options.wrap_x ? 0.0f : 1.0f,
realization_options.wrap_y ? 0.0f : 1.0f);
/* Compute a transformed input domain, excluding translations of wrapped axes. */
Domain input_domain = input.domain();
float3x3 domain_transformation = transformation;
domain_transformation.location() *= wrap_mask;
input_domain.transform(domain_transformation);
Domain transformed_domain = input.domain();
transformed_domain.transform(transformation);
/* Realize the input on the target domain using the full transformation. */
const Domain target_domain = compute_realized_transformation_domain(context, input_domain);
const Domain target_domain = compute_realized_transformation_domain(context, transformed_domain);
realize_on_domain(context,
input,
output,

View File

@@ -0,0 +1,16 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_bicubic_sampler_lib.glsl"
#include "gpu_shader_compositor_texture_utilities.glsl"
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = texture_size(input_tx);
vec2 translated_coordinates = (vec2(texel) + vec2(0.5f) - translation) / vec2(size);
imageStore(output_img, texel, SAMPLER_FUNCTION(input_tx, translated_coordinates));
}

View File

@@ -0,0 +1,23 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_translate_wrapped_shared)
LOCAL_GROUP_SIZE(16, 16)
PUSH_CONSTANT(VEC2, translation)
SAMPLER(0, FLOAT_2D, input_tx)
IMAGE(0, GPU_RGBA16F, WRITE, FLOAT_2D, output_img)
COMPUTE_SOURCE("compositor_translate_wrapped.glsl")
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_translate_wrapped)
ADDITIONAL_INFO(compositor_translate_wrapped_shared)
DEFINE_VALUE("SAMPLER_FUNCTION", "texture")
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_translate_wrapped_bicubic)
ADDITIONAL_INFO(compositor_translate_wrapped_shared)
DEFINE_VALUE("SAMPLER_FUNCTION", "texture_bicubic")
GPU_SHADER_CREATE_END()

View File

@@ -107,6 +107,7 @@
#include "compositor_symmetric_separable_blur_variable_size_info.hh"
#include "compositor_tone_map_photoreceptor_info.hh"
#include "compositor_tone_map_simple_info.hh"
#include "compositor_translate_wrapped_info.hh"
#include "compositor_van_vliet_gaussian_blur_info.hh"
#include "compositor_write_output_info.hh"
#include "compositor_z_combine_info.hh"

View File

@@ -6,12 +6,12 @@
* \ingroup cmpnodes
*/
#include "BLI_assert.h"
#include "BLI_math_matrix.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "COM_algorithm_transform.hh"
#include "COM_node_operation.hh"
#include "node_composite_util.hh"
@@ -61,25 +61,117 @@ class TranslateOperation : public NodeOperation {
void execute() override
{
Result &input = get_input("Image");
Result &result = get_result("Image");
Result &input = this->get_input("Image");
float x = get_input("X").get_single_value_default(0.0f);
float y = get_input("Y").get_single_value_default(0.0f);
if (get_use_relative()) {
float x = this->get_input("X").get_single_value_default(0.0f);
float y = this->get_input("Y").get_single_value_default(0.0f);
if (this->get_use_relative()) {
x *= input.domain().size.x;
y *= input.domain().size.y;
}
const float2 translation = float2(x, y);
const float3x3 transformation = math::from_location<float3x3>(translation);
RealizationOptions realization_options = input.get_realization_options();
realization_options.wrap_x = get_wrap_x();
realization_options.wrap_y = get_wrap_y();
realization_options.interpolation = get_interpolation();
if (this->get_wrap_x() || this->get_wrap_y()) {
this->execute_wrapped(translation);
}
else {
Result &output = this->get_result("Image");
input.pass_through(output);
output.transform(math::from_location<float3x3>(translation));
output.get_realization_options().interpolation = this->get_interpolation();
}
}
transform(context(), input, result, transformation, realization_options);
void execute_wrapped(const float2 &translation)
{
BLI_assert(this->get_wrap_x() || this->get_wrap_y());
/* Get the translation components that wrap. */
const float2 wrapped_translation = float2(this->get_wrap_x() ? translation.x : 0.0f,
this->get_wrap_y() ? translation.y : 0.0f);
if (this->context().use_gpu()) {
this->execute_wrapped_gpu(wrapped_translation);
}
else {
this->execute_wrapped_cpu(wrapped_translation);
}
/* If we are wrapping on both sides, then there is nothing left to do. Otherwise, we will have
* to transform the output by the translation components that do not wrap. While also setting
* up the appropriate interpolation. */
if (this->get_wrap_x() && this->get_wrap_y()) {
return;
}
/* Get the translation components that do not wrap. */
const float2 non_wrapped_translation = float2(this->get_wrap_x() ? 0.0f : translation.x,
this->get_wrap_y() ? 0.0f : translation.y);
Result &output = this->get_result("Image");
output.transform(math::from_location<float3x3>(non_wrapped_translation));
output.get_realization_options().interpolation = this->get_interpolation();
}
void execute_wrapped_gpu(const float2 &translation)
{
const Result &input = this->get_input("Image");
Result &output = this->get_result("Image");
const Interpolation interpolation = this->get_interpolation();
GPUShader *shader = this->context().get_shader(interpolation == Interpolation::Bicubic ?
"compositor_translate_wrapped_bicubic" :
"compositor_translate_wrapped");
GPU_shader_bind(shader);
GPU_shader_uniform_2fv(shader, "translation", translation);
/* The texture sampler should use bilinear interpolation for both the bilinear and bicubic
* cases, as the logic used by the bicubic realization shader expects textures to use bilinear
* interpolation. */
const bool use_bilinear = ELEM(interpolation, Interpolation::Bilinear, Interpolation::Bicubic);
GPU_texture_filter_mode(input, use_bilinear);
GPU_texture_extend_mode(input, GPU_SAMPLER_EXTEND_MODE_REPEAT);
input.bind_as_texture(shader, "input_tx");
output.allocate_texture(input.domain());
output.bind_as_image(shader, "output_img");
compute_dispatch_threads_at_least(shader, input.domain().size);
input.unbind_as_texture();
output.unbind_as_image();
GPU_shader_unbind();
}
void execute_wrapped_cpu(const float2 &translation)
{
const Result &input = this->get_input("Image");
Result &output = this->get_result("Image");
output.allocate_texture(input.domain());
const bool wrap_x = this->get_wrap_x();
const bool wrap_y = this->get_wrap_y();
const Interpolation interpolation = this->get_interpolation();
const int2 size = input.domain().size;
parallel_for(size, [&](const int2 texel) {
float2 translated_coordinates = (float2(texel) + float2(0.5f) - translation) / float2(size);
float4 sample;
switch (interpolation) {
case Interpolation::Nearest:
sample = input.sample_nearest_wrap(translated_coordinates, wrap_x, wrap_y);
break;
case Interpolation::Bilinear:
sample = input.sample_bilinear_wrap(translated_coordinates, wrap_x, wrap_y);
break;
case Interpolation::Bicubic:
sample = input.sample_cubic_wrap(translated_coordinates, wrap_x, wrap_y);
break;
}
output.store_pixel(texel, sample);
});
}
Interpolation get_interpolation()