Realtime Compositor: Immediately realize transformations #112332
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue