Realtime Compositor: Immediately realize wrapped translations #113669

Merged
Omar Emara merged 3 commits from OmarEmaraDev/blender:wrapped-translations into main 2023-11-02 12:39:50 +01:00
11 changed files with 73 additions and 47 deletions

View File

@ -23,8 +23,8 @@ enum class Interpolation : uint8_t {
* Realization Options
*
* The options that describe how an input result prefer to be realized on some other domain. This
* is used by the Realize On Domain Operation to identify the appropriate method of realization.
* See the Domain class for more information. */
* is used by the Realize On Domain and Transform algorithms to identify the appropriate method of
* realization. See the Domain class for more information. */
struct RealizationOptions {
/* The interpolation method that should be used when performing realization. Since realizing a
* result involves projecting it on a different domain, which in turn, involves sampling the
@ -34,11 +34,11 @@ struct RealizationOptions {
/* If true, the result will be repeated infinitely along the horizontal axis when realizing the
* result. If false, regions outside of bounds of the result along the horizontal axis will be
* filled with zeros. */
bool repeat_x = false;
bool wrap_x = false;
/* If true, the result will be repeated infinitely along the vertical axis when realizing the
* result. If false, regions outside of bounds of the result along the vertical axis will be
* filled with zeros. */
bool repeat_y = false;
bool wrap_y = false;
};
/* ------------------------------------------------------------------------------------------------

View File

@ -198,6 +198,9 @@ class Result {
* a practical example of use. */
void steal_data(Result &source);
/* Sets the transformation of the domain of the result to the given transformation. */
void set_transformation(const float3x3 &transformation);
/* Transform the result by the given transformation. This effectively pre-multiply the given
* transformation by the current transformation of the domain of the result. */
void transform(const float3x3 &transformation);

View File

@ -12,8 +12,8 @@
namespace blender::realtime_compositor {
/* Transforms the given result based on the given transformation and interpolation, writing the
* transformed result to the given output.
/* Transforms the given result based on the given transformation and realization options, writing
* the transformed result to the given output.
*
* The rotation and scale components of the transformation are realized and the size of the result
* is increased/reduced to adapt to the new transformation. For instance, if the transformation is
@ -23,11 +23,16 @@ namespace blender::realtime_compositor {
* 16k.
*
* The translation component of the transformation is delayed and only stored in the domain of the
* result to be realized later when needed. */
* result to be realized later when needed, except if the realization options has wrapping enabled,
* in which case, the result will be translated such that it is clipped on the one side and wrapped
* on the opposite side.
*
* The empty areas around the image after rotation will either be transparent or repetitions of the
* image based on the realization options. */
void transform(Context &context,
Result &input,
Result &output,
float3x3 transformation,
Interpolation interpolation);
RealizationOptions realization_options);
} // namespace blender::realtime_compositor

View File

@ -62,6 +62,12 @@ void realize_on_domain(Context &context,
const float3x3 &input_transformation,
const RealizationOptions &realization_options)
{
const Domain input_domain = Domain(input.domain().size, input_transformation);
if (input_domain == domain) {
input.pass_through(output);
output.set_transformation(domain.transformation);
return;
}
GPUShader *shader = context.shader_manager().get(
get_realization_shader(input, realization_options));
@ -87,16 +93,14 @@ void realize_on_domain(Context &context,
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,
/* If the input wraps, 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);
realization_options.wrap_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);
realization_options.wrap_y ? GPU_SAMPLER_EXTEND_MODE_REPEAT :
GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
input.bind_as_texture(shader, "input_tx");

View File

@ -56,32 +56,30 @@ void transform(Context &context,
Result &input,
Result &output,
float3x3 transformation,
Interpolation interpolation)
RealizationOptions realization_options)
{
math::AngleRadian rotation;
float2 translation, scale;
math::to_loc_rot_scale(transformation, translation, rotation, scale);
/* 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);
/* Rotation and scale transformations are immediately realized. */
if (rotation != 0.0f || scale != float2(1.0f)) {
RealizationOptions realization_options = input.get_realization_options();
realization_options.interpolation = interpolation;
/* 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 input_domain = input.domain();
input_domain.transform(transformation);
/* Realize the input on the target domain using the full transformation. */
const Domain target_domain = compute_realized_transformation_domain(input_domain);
realize_on_domain(context,
input,
output,
target_domain,
transformation * input.domain().transformation,
realization_options);
const Domain target_domain = compute_realized_transformation_domain(input_domain);
realize_on_domain(
context, input, output, target_domain, input_domain.transformation, realization_options);
}
else {
input.pass_through(output);
const float3x3 translation_matrix = math::from_location<float3x3>(translation);
output.transform(translation_matrix);
}
output.get_realization_options().interpolation = interpolation;
output.get_realization_options().interpolation = realization_options.interpolation;
}
} // namespace blender::realtime_compositor

View File

@ -179,6 +179,11 @@ void Result::steal_data(Result &source)
source.texture_pool_ = nullptr;
}
void Result::set_transformation(const float3x3 &transformation)
{
domain_.transformation = transformation;
}
void Result::transform(const float3x3 &transformation)
{
domain_.transform(transformation);

View File

@ -60,7 +60,10 @@ class RotateOperation : public NodeOperation {
const math::AngleRadian rotation = get_input("Degr").get_float_value_default(0.0f);
const float3x3 transformation = math::from_rotation<float3x3>(rotation);
transform(context(), input, output, transformation, get_interpolation());
RealizationOptions realization_options = input.get_realization_options();
realization_options.interpolation = get_interpolation();
transform(context(), input, output, transformation, realization_options);
}
Interpolation get_interpolation()

View File

@ -93,8 +93,7 @@ class ScaleOperation : public NodeOperation {
const float3x3 transformation = math::from_loc_rot_scale<float3x3>(
translation, rotation, scale);
const Interpolation interpolation = input.get_realization_options().interpolation;
transform(context(), input, output, transformation, interpolation);
transform(context(), input, output, transformation, input.get_realization_options());
}
float2 get_scale()

View File

@ -108,7 +108,10 @@ class Stabilize2DOperation : public NodeOperation {
transformation = math::invert(transformation);
}
transform(context(), input, output, transformation, get_interpolation());
RealizationOptions realization_options = input.get_realization_options();
realization_options.interpolation = get_interpolation();
transform(context(), input, output, transformation, realization_options);
}
Interpolation get_interpolation()

View File

@ -75,7 +75,10 @@ class TransformOperation : public NodeOperation {
const float3x3 transformation = math::from_loc_rot_scale<float3x3>(
translation, rotation, scale);
transform(context(), input, output, transformation, get_interpolation());
RealizationOptions realization_options = input.get_realization_options();
realization_options.interpolation = get_interpolation();
transform(context(), input, output, transformation, realization_options);
}
Interpolation get_interpolation()

View File

@ -11,6 +11,7 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "COM_algorithm_transform.hh"
#include "COM_node_operation.hh"
#include "node_composite_util.hh"
@ -73,9 +74,11 @@ class TranslateOperation : public NodeOperation {
const float2 translation = float2(x, y);
const float3x3 transformation = math::from_location<float3x3>(translation);
result.transform(transformation);
result.get_realization_options().repeat_x = get_repeat_x();
result.get_realization_options().repeat_y = get_repeat_y();
RealizationOptions realization_options = input.get_realization_options();
realization_options.wrap_x = get_wrap_x();
realization_options.wrap_y = get_wrap_y();
transform(context(), input, result, transformation, realization_options);
}
bool get_use_relative()
@ -83,12 +86,12 @@ class TranslateOperation : public NodeOperation {
return node_storage(bnode()).relative;
}
bool get_repeat_x()
bool get_wrap_x()
{
return ELEM(node_storage(bnode()).wrap_axis, CMP_NODE_WRAP_X, CMP_NODE_WRAP_XY);
}
bool get_repeat_y()
bool get_wrap_y()
{
return ELEM(node_storage(bnode()).wrap_axis, CMP_NODE_WRAP_Y, CMP_NODE_WRAP_XY);
}