Realtime Compositor: Implement Movie Distortion node #108230

Merged
Omar Emara merged 3 commits from OmarEmaraDev/blender:compositor-movie-distortion into main 2023-06-07 14:45:53 +02:00
9 changed files with 399 additions and 5 deletions

View File

@ -21,6 +21,7 @@ struct MovieClipUser;
struct MovieDistortion;
struct MovieReconstructContext;
struct MovieTracking;
struct MovieTrackingCamera;
struct MovieTrackingMarker;
struct MovieTrackingObject;
struct MovieTrackingPlaneMarker;
@ -464,6 +465,16 @@ void BKE_tracking_camera_principal_point_pixel_get(struct MovieClip *clip,
void BKE_tracking_camera_principal_point_pixel_set(struct MovieClip *clip,
const float principal_point_pixel[2]);
/* Compares distortion related parameters of camera. Ideally, this implementation will be
* abstracted away in the future, but for now, one needs to be careful about it and handle any
* extra parameters of distortions models. */
bool BKE_tracking_camera_distortion_equal(const struct MovieTrackingCamera *a,
const struct MovieTrackingCamera *b);
/* Hashes distortion related paramaters of camera. Ideally, this implementation will be
* abstracted away in the future, but for now, one needs to be careful about it and handle any
* extra parameters of distortions models. */
uint64_t BKE_tracking_camera_distortion_hash(const struct MovieTrackingCamera *camera);
/* --------------------------------------------------------------------
* (Un)distortion.
*/

View File

@ -6,6 +6,7 @@
* \ingroup bke
*/
#include <cstdint>
#include <limits.h>
#include <math.h>
#include <memory.h>
@ -23,9 +24,12 @@
#include "BLI_bitmap_draw_2d.h"
#include "BLI_ghash.h"
#include "BLI_hash.hh"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_math_base.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_string.h"
#include "BLI_string_utils.h"
#include "BLI_threads.h"
@ -2197,6 +2201,66 @@ void BKE_tracking_camera_principal_point_pixel_set(MovieClip *clip,
principal_point_pixel, frame_width, frame_height, camera->principal_point);
}
bool BKE_tracking_camera_distortion_equal(const MovieTrackingCamera *a,
const MovieTrackingCamera *b)
OmarEmaraDev marked this conversation as resolved Outdated

This is a bit confusing. Camera also defines sensor width, which is not compared here.

This is a bit confusing. Camera also defines sensor width, which is not compared here.
{
if (a->pixel_aspect != b->pixel_aspect || a->focal != b->focal ||
!equals_v2v2(a->principal_point, b->principal_point))
{
return false;
}
if (a->distortion_model != b->distortion_model) {
return false;
}
switch (a->distortion_model) {
case TRACKING_DISTORTION_MODEL_POLYNOMIAL:
return a->k1 == b->k1 && a->k2 == b->k2 && a->k3 == b->k3;
case TRACKING_DISTORTION_MODEL_DIVISION:
return a->division_k1 == b->division_k1 && a->division_k2 == b->division_k2;
case TRACKING_DISTORTION_MODEL_NUKE:
return a->nuke_k1 == b->nuke_k1 && a->nuke_k2 == b->nuke_k2;
case TRACKING_DISTORTION_MODEL_BROWN:
return a->brown_k1 == b->brown_k1 && a->brown_k2 == b->brown_k2 &&
a->brown_k3 == b->brown_k3 && a->brown_k4 == b->brown_k4 &&
a->brown_p1 == b->brown_p1 && a->brown_p2 == b->brown_p2;
}
OmarEmaraDev marked this conversation as resolved
Review

Please do not add default unless absolutely needed, as it makes it very hard to see at compile time where enumerator value is forgotten to be checked when adding a new value.

In this example it is as easy as moving the BLI_assert_unreachable past the switch statement.

Please do not add `default` unless absolutely needed, as it makes it very hard to see at compile time where enumerator value is forgotten to be checked when adding a new value. In this example it is as easy as moving the `BLI_assert_unreachable` past the `switch` statement.
BLI_assert_unreachable();
return false;
}
uint64_t BKE_tracking_camera_distortion_hash(const MovieTrackingCamera *camera)
{
using namespace blender;
switch (camera->distortion_model) {
OmarEmaraDev marked this conversation as resolved
Review

Is it intended to skip distortion_model, focal and so on?

Is it intended to skip `distortion_model`, `focal` and so on?
case TRACKING_DISTORTION_MODEL_POLYNOMIAL:
return get_default_hash_4(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float3(camera->k1, camera->k2, camera->k3));
case TRACKING_DISTORTION_MODEL_DIVISION:
return get_default_hash_4(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float2(camera->division_k1, camera->division_k2));
case TRACKING_DISTORTION_MODEL_NUKE:
return get_default_hash_4(camera->distortion_model,
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
float2(camera->nuke_k1, camera->nuke_k2));
case TRACKING_DISTORTION_MODEL_BROWN:
return get_default_hash_4(
float2(camera->pixel_aspect, camera->focal),
float2(camera->principal_point),
OmarEmaraDev marked this conversation as resolved
Review

Same as above.

Same as above.
float4(camera->brown_k1, camera->brown_k2, camera->brown_k3, camera->brown_k4),
float2(camera->brown_p1, camera->brown_p2));
}
BLI_assert_unreachable();
return 0;
}
/* --------------------------------------------------------------------
* (Un)distortion.
*/

View File

@ -17,6 +17,9 @@ set(INC
../../render
../../gpu/intern
../../../../intern/guardedalloc
# dna_type_offsets.h
${CMAKE_CURRENT_BINARY_DIR}/../../makesdna/intern
)
set(INC_SYS
@ -74,6 +77,7 @@ set(SRC
cached_resources/intern/cached_mask.cc
cached_resources/intern/cached_texture.cc
cached_resources/intern/distortion_grid.cc
cached_resources/intern/morphological_distance_feather_weights.cc
cached_resources/intern/ocio_color_space_conversion_shader.cc
cached_resources/intern/smaa_precomputed_textures.cc
@ -83,6 +87,7 @@ set(SRC
cached_resources/COM_cached_mask.hh
cached_resources/COM_cached_resource.hh
cached_resources/COM_cached_texture.hh
cached_resources/COM_distortion_grid.hh
cached_resources/COM_morphological_distance_feather_weights.hh
cached_resources/COM_ocio_color_space_conversion_shader.hh
cached_resources/COM_smaa_precomputed_textures.hh
@ -133,6 +138,7 @@ set(GLSL_SRC
shaders/compositor_morphological_distance_feather.glsl
shaders/compositor_morphological_distance_threshold.glsl
shaders/compositor_morphological_step.glsl
shaders/compositor_movie_distortion.glsl
shaders/compositor_normalize.glsl
shaders/compositor_parallel_reduction.glsl
shaders/compositor_plane_deform.glsl
@ -234,6 +240,7 @@ set(SRC_SHADER_CREATE_INFOS
shaders/infos/compositor_morphological_distance_info.hh
shaders/infos/compositor_morphological_distance_threshold_info.hh
shaders/infos/compositor_morphological_step_info.hh
shaders/infos/compositor_movie_distortion_info.hh
shaders/infos/compositor_normalize_info.hh
shaders/infos/compositor_parallel_reduction_info.hh
shaders/infos/compositor_plane_deform_info.hh

View File

@ -6,6 +6,7 @@
#include "COM_cached_mask.hh"
#include "COM_cached_texture.hh"
#include "COM_distortion_grid.hh"
#include "COM_morphological_distance_feather_weights.hh"
#include "COM_ocio_color_space_conversion_shader.hh"
#include "COM_smaa_precomputed_textures.hh"
@ -45,6 +46,7 @@ class StaticCacheManager {
CachedMaskContainer cached_masks;
SMAAPrecomputedTexturesContainer smaa_precomputed_textures;
OCIOColorSpaceConversionShaderContainer ocio_color_space_conversion_shaders;
DistortionGridContainer distortion_grids;
/* Reset the cache manager by deleting the cached resources that are no longer needed because
* they weren't used in the last evaluation and prepare the remaining cached resources to track

View File

@ -0,0 +1,86 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
OmarEmaraDev marked this conversation as resolved Outdated

Please add copyright to the new files.

Please add copyright to the new files.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
#include <memory>
#include "BLI_map.hh"
#include "BLI_math_vector_types.hh"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "DNA_movieclip_types.h"
#include "COM_cached_resource.hh"
namespace blender::realtime_compositor {
enum class DistortionType : uint8_t {
Distort,
Undistort,
};
/* ------------------------------------------------------------------------------------------------
* Distortion Grid Key.
*/
class DistortionGridKey {
public:
MovieTrackingCamera camera;
int2 size;
DistortionType type;
int2 calibration_size;
DistortionGridKey(MovieTrackingCamera camera,
int2 size,
DistortionType type,
int2 calibration_size);
uint64_t hash() const;
};
bool operator==(const DistortionGridKey &a, const DistortionGridKey &b);
/* -------------------------------------------------------------------------------------------------
* Distortion Grid.
*
* A cached resource that computes and caches a GPU texture containing the normalized coordinates
* after applying the camera distortion of a given movie clip tracking camera. See the constructor
* for more information. */
class DistortionGrid : public CachedResource {
private:
GPUTexture *texture_ = nullptr;
public:
/* The calibration size is the size of the image where the tracking camera was calibrated, this
* is the size of the movie clip in most cases. */
DistortionGrid(MovieClip *movie_clip, int2 size, DistortionType type, int2 calibration_size);
~DistortionGrid();
void bind_as_texture(GPUShader *shader, const char *texture_name) const;
void unbind_as_texture() const;
};
/* ------------------------------------------------------------------------------------------------
* Distortion Grid Container.
*/
class DistortionGridContainer : CachedResourceContainer {
private:
Map<DistortionGridKey, std::unique_ptr<DistortionGrid>> map_;
public:
void reset() override;
/* Check if there is an available DistortionGrid cached resource with the given parameters in the
* container, if one exists, return it, otherwise, return a newly created one and add it to the
* container. In both cases, tag the cached resource as needed to keep it cached for the next
* evaluation. */
DistortionGrid &get(MovieClip *movie_clip, int2 size, DistortionType type, int frame_number);
};
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,160 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cstdint>
#include <memory>
#include "BLI_array.hh"
#include "BLI_hash.hh"
#include "BLI_index_range.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_task.hh"
#include "DNA_defaults.h"
#include "DNA_movieclip_types.h"
#include "DNA_tracking_types.h"
#include "GPU_texture.h"
#include "BKE_movieclip.h"
#include "BKE_tracking.h"
#include "COM_distortion_grid.hh"
namespace blender::realtime_compositor {
/* --------------------------------------------------------------------
* Distortion Grid Key.
*/
DistortionGridKey::DistortionGridKey(MovieTrackingCamera camera,
int2 size,
DistortionType type,
int2 calibration_size)
: camera(camera), size(size), type(type), calibration_size(calibration_size)
{
}
uint64_t DistortionGridKey::hash() const
{
return get_default_hash_4(
BKE_tracking_camera_distortion_hash(&camera), size, type, calibration_size);
}
bool operator==(const DistortionGridKey &a, const DistortionGridKey &b)
{
return BKE_tracking_camera_distortion_equal(&a.camera, &b.camera) && a.size == b.size &&
a.type == b.type && a.calibration_size == b.calibration_size;
}
/* --------------------------------------------------------------------
* Distortion Grid.
*/
DistortionGrid::DistortionGrid(MovieClip *movie_clip,
int2 size,
DistortionType type,
int2 calibration_size)
{
MovieDistortion *distortion = BKE_tracking_distortion_new(
&movie_clip->tracking, calibration_size.x, calibration_size.y);
Array<float2> distortion_grid(size.x * size.y);
threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
for (const int64_t y : sub_y_range) {
for (const int64_t x : IndexRange(size.x)) {
/* The tracking distortion functions expect the coordinates to be in the space of the image
* where the tracking camera was calibrated. So we first remap the coordinates into that
* space, apply the distortion, then remap back to the original coordinates space. This is
* done by dividing the by the size then multiplying by the calibration size, making sure
* to add 0.5 to evaluate at the center of pixels. */
float2 coordinates = ((float2(x, y) + 0.5f) / float2(size)) * float2(calibration_size);
if (type == DistortionType::Undistort) {
BKE_tracking_distortion_undistort_v2(distortion, coordinates, coordinates);
}
else {
BKE_tracking_distortion_distort_v2(distortion, coordinates, coordinates);
}
/* Note that we should remap the coordinates back into the original size by dividing by the
* calibration size and multiplying by the size, however, we skip the latter to store the
* coordinates in normalized form, since this is what the shader expects. */
distortion_grid[y * size.x + x] = coordinates / float2(calibration_size);
}
}
});
BKE_tracking_distortion_free(distortion);
texture_ = GPU_texture_create_2d("Distortion Grid",
size.x,
size.y,
1,
GPU_RG16F,
GPU_TEXTURE_USAGE_SHADER_READ,
*distortion_grid.data());
}
DistortionGrid::~DistortionGrid()
{
GPU_texture_free(texture_);
}
void DistortionGrid::bind_as_texture(GPUShader *shader, const char *texture_name) const
{
const int texture_image_unit = GPU_shader_get_sampler_binding(shader, texture_name);
GPU_texture_bind(texture_, texture_image_unit);
}
void DistortionGrid::unbind_as_texture() const
{
GPU_texture_unbind(texture_);
}
/* --------------------------------------------------------------------
* Distortion Grid Container.
*/
void DistortionGridContainer::reset()
{
/* First, delete all resources that are no longer needed. */
map_.remove_if([](auto item) { return !item.value->needed; });
/* Second, reset the needed status of the remaining resources to false to ready them to track
* their needed status for the next evaluation. */
for (auto &value : map_.values()) {
value->needed = false;
}
}
static int2 get_movie_clip_size(MovieClip *movie_clip, int frame_number)
{
MovieClipUser user = *DNA_struct_default_get(MovieClipUser);
BKE_movieclip_user_set_frame(&user, frame_number);
int2 size;
BKE_movieclip_get_size(movie_clip, &user, &size.x, &size.y);
return size;
}
DistortionGrid &DistortionGridContainer::get(MovieClip *movie_clip,
int2 size,
DistortionType type,
int frame_number)
{
const int2 calibration_size = get_movie_clip_size(movie_clip, frame_number);
const DistortionGridKey key(movie_clip->tracking.camera, size, type, calibration_size);
auto &distortion_grid = *map_.lookup_or_add_cb(key, [&]() {
return std::make_unique<DistortionGrid>(movie_clip, size, type, calibration_size);
});
distortion_grid.needed = true;
return distortion_grid;
}
} // namespace blender::realtime_compositor

View File

@ -0,0 +1,7 @@
#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)
void main()
{
ivec2 texel = ivec2(gl_GlobalInvocationID.xy);
imageStore(output_img, texel, texture(input_tx, texture_load(distortion_grid_tx, texel).xy));
}

View File

@ -0,0 +1,13 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_movie_distortion)
.local_group_size(16, 16)
.sampler(0, ImageType::FLOAT_2D, "input_tx")
.sampler(1, ImageType::FLOAT_2D, "distortion_grid_tx")
.image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "output_img")
.compute_source("compositor_movie_distortion.glsl")
.do_static_compilation(true);

View File

@ -8,6 +8,8 @@
#include "BLI_string_utf8.h"
#include "DNA_movieclip_types.h"
#include "BKE_context.h"
#include "BKE_lib_id.h"
#include "BKE_tracking.h"
@ -15,7 +17,12 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "GPU_shader.h"
#include "GPU_texture.h"
#include "COM_distortion_grid.hh"
#include "COM_node_operation.hh"
#include "COM_utilities.hh"
#include "node_composite_util.hh"
@ -25,7 +32,9 @@ namespace blender::nodes::node_composite_moviedistortion_cc {
static void cmp_node_moviedistortion_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Color>("Image").default_value({0.8f, 0.8f, 0.8f, 1.0f});
b.add_input<decl::Color>("Image")
.default_value({0.8f, 0.8f, 0.8f, 1.0f})
.compositor_domain_priority(0);
b.add_output<decl::Color>("Image");
}
@ -94,8 +103,45 @@ class MovieDistortionOperation : public NodeOperation {
void execute() override
{
get_input("Image").pass_through(get_result("Image"));
context().set_info_message("Viewport compositor setup not fully supported");
Result &input_image = get_input("Image");
Result &output_image = get_result("Image");
if (input_image.is_single_value() || !get_movie_clip()) {
input_image.pass_through(output_image);
return;
}
const Domain domain = compute_domain();
const DistortionGrid &distortion_grid = context().cache_manager().distortion_grids.get(
get_movie_clip(), domain.size, get_distortion_type(), context().get_frame_number());
GPUShader *shader = shader_manager().get("compositor_movie_distortion");
GPU_shader_bind(shader);
GPU_texture_extend_mode(input_image.texture(), GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
GPU_texture_filter_mode(input_image.texture(), true);
input_image.bind_as_texture(shader, "input_tx");
distortion_grid.bind_as_texture(shader, "distortion_grid_tx");
output_image.allocate_texture(domain);
output_image.bind_as_image(shader, "output_img");
compute_dispatch_threads_at_least(shader, domain.size);
input_image.unbind_as_texture();
distortion_grid.unbind_as_texture();
output_image.unbind_as_image();
GPU_shader_unbind();
}
DistortionType get_distortion_type()
{
return bnode().custom1 == 0 ? DistortionType::Distort : DistortionType::Undistort;
}
MovieClip *get_movie_clip()
{
return reinterpret_cast<MovieClip *>(bnode().id);
}
};
@ -119,8 +165,6 @@ void register_node_type_cmp_moviedistortion()
ntype.initfunc_api = file_ns::init;
node_type_storage(&ntype, nullptr, file_ns::storage_free, file_ns::storage_copy);
ntype.get_compositor_operation = file_ns::get_compositor_operation;
ntype.realtime_compositor_unsupported_message = N_(
"Node not supported in the Viewport compositor");
nodeRegisterType(&ntype);
}