Realtime Compositor: Immediately realize transformations #112332

Merged
Omar Emara merged 6 commits from OmarEmaraDev/blender:realize-transformation into main 2023-10-12 11:04:59 +02:00
6 changed files with 215 additions and 135 deletions
Showing only changes of commit 9bc909b336 - Show all commits

View File

@ -66,6 +66,7 @@ set(SRC
algorithms/intern/morphological_distance.cc
algorithms/intern/morphological_distance_feather.cc
algorithms/intern/parallel_reduction.cc
algorithms/intern/realize_on_domain.cc
algorithms/intern/smaa.cc
algorithms/intern/summed_area_table.cc
algorithms/intern/symmetric_separable_blur.cc
@ -73,6 +74,7 @@ set(SRC
algorithms/COM_algorithm_morphological_distance.hh
algorithms/COM_algorithm_morphological_distance_feather.hh
algorithms/COM_algorithm_parallel_reduction.hh
algorithms/COM_algorithm_realize_on_domain.hh
algorithms/COM_algorithm_smaa.hh
algorithms/COM_algorithm_summed_area_table.hh
algorithms/COM_algorithm_symmetric_separable_blur.hh

View File

@ -42,10 +42,6 @@ class RealizeOnDomainOperation : public SimpleOperation {
protected:
/* The operation domain is just the target domain. */
Domain compute_domain() override;
private:
/* Get the realization shader of the appropriate type. */
GPUShader *get_realization_shader();
};
/* ------------------------------------------------------------------------------------------------
@ -69,13 +65,6 @@ class RealizeTransformationOperation {
static SimpleOperation *construct_if_needed(Context &context,
const Result &input_result,
const InputDescriptor &input_descriptor);
private:
/* Given the domain of an input and its realization options, compute a domain such that the
* appropriate transformations specified in the realization options become identity and the size
* of the domain is increased/reduced to adapt to the new domain. */
static Domain compute_target_domain(const Domain &input_domain,
const InputRealizationOptions &realization_options);
};
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,37 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_math_matrix_types.hh"
#include "COM_context.hh"
#include "COM_domain.hh"
#include "COM_result.hh"
namespace blender::realtime_compositor {
/* Given a potentially transformed domain, compute a domain such that the transformations specified
* in the given realization options become identity and the size of the domain is increased/reduced
* to adapt to the new transformation. For instance, if realize_rotation is true and the domain is
* rotated, the returned domain will have zero rotation but expanded size to account for the
* bounding box of the domain after rotation. */
Domain compute_realized_transformation_domain(const Domain &domain,
bool realize_rotation,
bool realize_scale);
/* Projects the input on a target domain, copies the area of the input that intersects the target
* domain, and fill the rest with zeros or repetitions of the input depending on the realization
* options. The transformation and realization options of the input are ignored and the given
* input_transformation and realization_options are used instead to allow the caller to change them
* without mutating the input result directly. See the discussion in COM_domain.hh for more
* information on what realization on domain means. */
void realize_on_domain(Context &context,
Result &input,
Result &output,
const Domain &domain,
const float3x3 &input_transformation,
const RealizationOptions &realization_options);
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,140 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_angle_types.hh"
#include "BLI_math_matrix.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_utildefines.h"
#include "GPU_capabilities.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_context.hh"
#include "COM_domain.hh"
#include "COM_result.hh"
#include "COM_utilities.hh"
#include "COM_algorithm_realize_on_domain.hh"
namespace blender::realtime_compositor {
Domain compute_realized_transformation_domain(const Domain &domain,
bool realize_rotation,
bool realize_scale)
{
if (!realize_rotation && !realize_scale) {
return domain;
}
math::AngleRadian rotation;
float2 translation, scale;
float2 size = float2(domain.size);
math::to_loc_rot_scale(domain.transformation, translation, rotation, scale);
/* Set the rotation to zero and expand the domain size to fit the bounding box of the rotated
* result. */
if (realize_rotation) {
const float sine = math::abs(math::sin(rotation));
const float cosine = math::abs(math::cos(rotation));
size = float2(size.x * sine + size.y * cosine, size.x * cosine + size.y * sine);
rotation = 0.0f;
}
/* Set the scale to 1 and scale the domain size to adapt to the new domain. */
if (realize_scale) {
size *= scale;
scale = float2(1.0f);
}
const float3x3 transformation = math::from_loc_rot_scale<float3x3>(translation, rotation, scale);
return Domain(math::min(int2(math::ceil(size)), int2(GPU_max_texture_size())), transformation);
}
static const char *get_realization_shader(Result &input,
const RealizationOptions &realization_options)
{
if (realization_options.interpolation == Interpolation::Bicubic) {
switch (input.type()) {
case ResultType::Color:
return "compositor_realize_on_domain_bicubic_color";
case ResultType::Vector:
return "compositor_realize_on_domain_bicubic_vector";
case ResultType::Float:
return "compositor_realize_on_domain_bicubic_float";
}
}
else {
switch (input.type()) {
case ResultType::Color:
return "compositor_realize_on_domain_color";
case ResultType::Vector:
return "compositor_realize_on_domain_vector";
case ResultType::Float:
return "compositor_realize_on_domain_float";
}
}
BLI_assert_unreachable();
return nullptr;
}
void realize_on_domain(Context &context,
Result &input,
Result &output,
const Domain &domain,
const float3x3 &input_transformation,
const RealizationOptions &realization_options)
{
GPUShader *shader = context.shader_manager().get(
get_realization_shader(input, realization_options));
GPU_shader_bind(shader);
/* Transform the input space into the domain space. */
const float3x3 local_transformation = math::invert(domain.transformation) * input_transformation;
/* Set the origin of the transformation to be the center of the domain. */
const float3x3 transformation = math::from_origin_transform<float3x3>(
local_transformation, float2(domain.size) / 2.0f);
/* Invert the transformation because the shader transforms the domain coordinates instead of the
* input image itself and thus expect the inverse. */
const float3x3 inverse_transformation = math::invert(transformation);
GPU_shader_uniform_mat3_as_mat4(shader, "inverse_transformation", inverse_transformation.ptr());
/* 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(
realization_options.interpolation, Interpolation::Bilinear, Interpolation::Bicubic);
GPU_texture_filter_mode(input.texture(), use_bilinear);
/* If the input repeats, set a repeating wrap mode for out-of-bound texture access. Otherwise,
* make out-of-bound texture access return zero by setting a clamp to border extend mode. */
GPU_texture_extend_mode_x(input.texture(),
realization_options.repeat_x ?
GPU_SAMPLER_EXTEND_MODE_REPEAT :
GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
GPU_texture_extend_mode_y(input.texture(),
realization_options.repeat_y ?
GPU_SAMPLER_EXTEND_MODE_REPEAT :
GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
input.bind_as_texture(shader, "input_tx");
output.allocate_texture(domain);
output.bind_as_image(shader, "domain_img");
compute_dispatch_threads_at_least(shader, domain.size);
input.unbind_as_texture();
output.unbind_as_image();
GPU_shader_unbind();
}
} // namespace blender::realtime_compositor

View File

@ -2,21 +2,13 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_math_angle_types.hh"
#include "BLI_math_matrix.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_utildefines.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_algorithm_realize_on_domain.hh"
#include "COM_context.hh"
#include "COM_domain.hh"
#include "COM_input_descriptor.hh"
#include "COM_realize_on_domain_operation.hh"
#include "COM_result.hh"
#include "COM_utilities.hh"
#include "COM_realize_on_domain_operation.hh"
namespace blender::realtime_compositor {
@ -37,82 +29,12 @@ RealizeOnDomainOperation::RealizeOnDomainOperation(Context &context,
void RealizeOnDomainOperation::execute()
{
Result &input = get_input();
Result &result = get_result();
result.allocate_texture(domain_);
GPUShader *shader = get_realization_shader();
GPU_shader_bind(shader);
/* Transform the input space into the domain space. */
const float3x3 local_transformation = math::invert(domain_.transformation) *
input.domain().transformation;
/* Set the origin of the transformation to be the center of the domain. */
const float3x3 transformation = math::from_origin_transform<float3x3>(
local_transformation, float2(domain_.size) / 2.0f);
/* Invert the transformation because the shader transforms the domain coordinates instead of the
* input image itself and thus expect the inverse. */
const float3x3 inverse_transformation = math::invert(transformation);
GPU_shader_uniform_mat3_as_mat4(shader, "inverse_transformation", inverse_transformation.ptr());
/* 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(input.get_realization_options().interpolation,
Interpolation::Bilinear,
Interpolation::Bicubic);
GPU_texture_filter_mode(input.texture(), use_bilinear);
/* If the input repeats, set a repeating wrap mode for out-of-bound texture access. Otherwise,
* make out-of-bound texture access return zero by setting a clamp to border extend mode. */
GPU_texture_extend_mode_x(input.texture(),
input.get_realization_options().repeat_x ?
GPU_SAMPLER_EXTEND_MODE_REPEAT :
GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
GPU_texture_extend_mode_y(input.texture(),
input.get_realization_options().repeat_y ?
GPU_SAMPLER_EXTEND_MODE_REPEAT :
GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
input.bind_as_texture(shader, "input_tx");
result.bind_as_image(shader, "domain_img");
compute_dispatch_threads_at_least(shader, domain_.size);
input.unbind_as_texture();
result.unbind_as_image();
GPU_shader_unbind();
}
GPUShader *RealizeOnDomainOperation::get_realization_shader()
{
if (get_input().get_realization_options().interpolation == Interpolation::Bicubic) {
switch (get_result().type()) {
case ResultType::Color:
return shader_manager().get("compositor_realize_on_domain_bicubic_color");
case ResultType::Vector:
return shader_manager().get("compositor_realize_on_domain_bicubic_vector");
case ResultType::Float:
return shader_manager().get("compositor_realize_on_domain_bicubic_float");
}
}
else {
switch (get_result().type()) {
case ResultType::Color:
return shader_manager().get("compositor_realize_on_domain_color");
case ResultType::Vector:
return shader_manager().get("compositor_realize_on_domain_vector");
case ResultType::Float:
return shader_manager().get("compositor_realize_on_domain_float");
}
}
BLI_assert_unreachable();
return nullptr;
realize_on_domain(context(),
get_input(),
get_result(),
domain_,
get_input().domain().transformation,
get_input().get_realization_options());
}
Domain RealizeOnDomainOperation::compute_domain()
@ -156,38 +78,6 @@ SimpleOperation *RealizeOnDomainOperation::construct_if_needed(
* Realize Transformation Operation
*/
Domain RealizeTransformationOperation::compute_target_domain(
const Domain &input_domain, const InputRealizationOptions &realization_options)
{
if (!realization_options.realize_rotation && !realization_options.realize_scale) {
return input_domain;
}
math::AngleRadian rotation;
float2 translation, scale;
float2 size = float2(input_domain.size);
math::to_loc_rot_scale(input_domain.transformation, translation, rotation, scale);
/* Set the rotation to zero and expand the domain size to fit the bounding box of the rotated
* result. */
if (realization_options.realize_rotation) {
const float sine = math::abs(math::sin(rotation));
const float cosine = math::abs(math::cos(rotation));
size = float2(size.x * sine + size.y * cosine, size.x * cosine + size.y * sine);
rotation = 0.0f;
}
/* Set the scale to 1 and scale the domain size to adapt to the new domain. */
if (realization_options.realize_scale) {
size *= scale;
scale = float2(1.0f);
}
const float3x3 transformation = math::from_loc_rot_scale<float3x3>(translation, rotation, scale);
return Domain(int2(math::ceil(size)), transformation);
}
SimpleOperation *RealizeTransformationOperation::construct_if_needed(
Context &context, const Result &input_result, const InputDescriptor &input_descriptor)
{
@ -202,8 +92,10 @@ SimpleOperation *RealizeTransformationOperation::construct_if_needed(
return nullptr;
}
const Domain target_domain = compute_target_domain(input_result.domain(),
input_descriptor.realization_options);
const Domain target_domain = compute_realized_transformation_domain(
input_result.domain(),
input_descriptor.realization_options.realize_rotation,
input_descriptor.realization_options.realize_scale);
/* The input have an identical domain to the target domain, either because the input doesn't need
* to realize its transformations or because it has identity transformations, so no need to

View File

@ -13,6 +13,7 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "COM_algorithm_realize_on_domain.hh"
#include "COM_node_operation.hh"
#include "node_composite_util.hh"
@ -66,16 +67,35 @@ class TransformOperation : public NodeOperation {
{
Result &input = get_input("Image");
Result &result = get_result("Image");
input.pass_through(result);
const float2 translation = float2(get_input("X").get_float_value_default(0.0f),
get_input("Y").get_float_value_default(0.0f));
const AngleRadian rotation = AngleRadian(get_input("Angle").get_float_value_default(0.0f));
const float2 scale = float2(get_input("Scale").get_float_value_default(1.0f));
const float3x3 transformation = from_loc_rot_scale<float3x3>(translation, rotation, scale);
if (rotation == 0.0f && scale == float2(1.0f)) {
input.pass_through(result);
}
else {
RealizationOptions realization_options = input.get_realization_options();
realization_options.interpolation = get_interpolation();
result.transform(transformation);
Domain input_domain = input.domain();
input_domain.transform(from_loc_rot_scale<float3x3>(translation, rotation, scale));
const Domain target_domain = compute_realized_transformation_domain(
input_domain, true, true);
realize_on_domain(context(),
input,
result,
target_domain,
input_domain.transformation,
realization_options);
}
const float3x3 translation_matrix = from_location<float3x3>(translation);
result.transform(translation_matrix);
result.get_realization_options().interpolation = get_interpolation();
}