This replaces the deprecated DrawData mechanism by the usage of the update timestamp `last_update`. The compositor keeps the `last_update` value of the cached ID and compares it with the value on the ID at the time of evaluation. Rel #134690 Pull Request: https://projects.blender.org/blender/blender/pulls/134878
292 lines
11 KiB
C++
292 lines
11 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
#include "BLI_hash.hh"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math_base.hh"
|
|
#include "BLI_math_color.h"
|
|
#include "BLI_math_vector.hh"
|
|
#include "BLI_math_vector_types.hh"
|
|
#include "BLI_vector.hh"
|
|
|
|
#include "IMB_imbuf.hh"
|
|
|
|
#include "DNA_defaults.h"
|
|
#include "DNA_movieclip_types.h"
|
|
#include "DNA_tracking_types.h"
|
|
|
|
#include "GPU_shader.hh"
|
|
#include "GPU_storage_buffer.hh"
|
|
|
|
#include "BKE_movieclip.h"
|
|
#include "BKE_tracking.h"
|
|
|
|
#include "COM_context.hh"
|
|
#include "COM_keying_screen.hh"
|
|
#include "COM_result.hh"
|
|
#include "COM_utilities.hh"
|
|
|
|
namespace blender::compositor {
|
|
|
|
/* --------------------------------------------------------------------
|
|
* Keying Screen Key.
|
|
*/
|
|
|
|
KeyingScreenKey::KeyingScreenKey(int frame, float smoothness)
|
|
: frame(frame), smoothness(smoothness)
|
|
{
|
|
}
|
|
|
|
uint64_t KeyingScreenKey::hash() const
|
|
{
|
|
return get_default_hash(frame, smoothness);
|
|
}
|
|
|
|
bool operator==(const KeyingScreenKey &a, const KeyingScreenKey &b)
|
|
{
|
|
return a.frame == b.frame && a.smoothness == b.smoothness;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* Keying Screen.
|
|
*/
|
|
|
|
/* Computes the color and normalized positions of the keying screen markers in the given movie
|
|
* tracking object. The color is computed as the mean color of the search pattern of the marker. */
|
|
static void compute_marker_points(MovieClip *movie_clip,
|
|
MovieClipUser &movie_clip_user,
|
|
MovieTrackingObject *movie_tracking_object,
|
|
Vector<float2> &marker_positions,
|
|
Vector<float4> &marker_colors)
|
|
{
|
|
BLI_assert(marker_positions.is_empty());
|
|
BLI_assert(marker_colors.is_empty());
|
|
|
|
ImBuf *image_buffer = BKE_movieclip_get_ibuf(movie_clip, &movie_clip_user);
|
|
if (!image_buffer) {
|
|
return;
|
|
}
|
|
|
|
LISTBASE_FOREACH (MovieTrackingTrack *, track, &movie_tracking_object->tracks) {
|
|
const MovieTrackingMarker *marker = BKE_tracking_marker_get(track, movie_clip_user.framenr);
|
|
if (marker->flag & MARKER_DISABLED) {
|
|
continue;
|
|
}
|
|
|
|
/* Skip out of bound markers since they have no corresponding color. */
|
|
const float2 position = float2(marker->pos) + float2(track->offset);
|
|
if (math::clamp(position, float2(0.0f), float2(1.0f)) != position) {
|
|
continue;
|
|
}
|
|
|
|
ImBuf *pattern_image_buffer = BKE_tracking_get_pattern_imbuf(
|
|
image_buffer, track, marker, true, false);
|
|
if (!pattern_image_buffer) {
|
|
continue;
|
|
}
|
|
|
|
/* Find the mean color of the rectangular search pattern of the marker. */
|
|
float4 mean_color = float4(0.0f);
|
|
for (int i = 0; i < pattern_image_buffer->x * pattern_image_buffer->y; i++) {
|
|
if (pattern_image_buffer->float_buffer.data) {
|
|
mean_color += float4(&pattern_image_buffer->float_buffer.data[i * 4]);
|
|
}
|
|
else {
|
|
float4 linear_color;
|
|
uchar4 srgb_color = uchar4(&pattern_image_buffer->byte_buffer.data[i * 4]);
|
|
srgb_to_linearrgb_uchar4(linear_color, srgb_color);
|
|
mean_color += linear_color;
|
|
}
|
|
}
|
|
mean_color /= pattern_image_buffer->x * pattern_image_buffer->y;
|
|
|
|
marker_colors.append(mean_color);
|
|
marker_positions.append(position);
|
|
|
|
IMB_freeImBuf(pattern_image_buffer);
|
|
}
|
|
|
|
IMB_freeImBuf(image_buffer);
|
|
}
|
|
|
|
/* Get a MovieClipUser with an initialized clip frame number. */
|
|
static MovieClipUser get_movie_clip_user(Context &context, MovieClip *movie_clip)
|
|
{
|
|
MovieClipUser movie_clip_user = *DNA_struct_default_get(MovieClipUser);
|
|
const int scene_frame = context.get_frame_number();
|
|
const int clip_frame = BKE_movieclip_remap_scene_to_clip_frame(movie_clip, scene_frame);
|
|
BKE_movieclip_user_set_frame(&movie_clip_user, clip_frame);
|
|
return movie_clip_user;
|
|
}
|
|
|
|
KeyingScreen::KeyingScreen(Context &context,
|
|
MovieClip *movie_clip,
|
|
MovieTrackingObject *movie_tracking_object,
|
|
const float smoothness)
|
|
: result(context.create_result(ResultType::Color))
|
|
{
|
|
int2 size;
|
|
MovieClipUser movie_clip_user = get_movie_clip_user(context, movie_clip);
|
|
BKE_movieclip_get_size(movie_clip, &movie_clip_user, &size.x, &size.y);
|
|
|
|
Vector<float2> marker_positions;
|
|
Vector<float4> marker_colors;
|
|
compute_marker_points(
|
|
movie_clip, movie_clip_user, movie_tracking_object, marker_positions, marker_colors);
|
|
|
|
if (marker_positions.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
this->result.allocate_texture(Domain(size), false);
|
|
if (context.use_gpu()) {
|
|
this->compute_gpu(context, smoothness, marker_positions, marker_colors);
|
|
}
|
|
else {
|
|
this->compute_cpu(smoothness, marker_positions, marker_colors);
|
|
}
|
|
}
|
|
|
|
void KeyingScreen::compute_gpu(Context &context,
|
|
const float smoothness,
|
|
Vector<float2> &marker_positions,
|
|
const Vector<float4> &marker_colors)
|
|
{
|
|
GPUShader *shader = context.get_shader("compositor_keying_screen");
|
|
GPU_shader_bind(shader);
|
|
|
|
GPU_shader_uniform_1f(shader, "smoothness", smoothness);
|
|
GPU_shader_uniform_1i(shader, "number_of_markers", marker_positions.size());
|
|
|
|
/* SSBO needs to be aligned to 16 bytes, and since sizeof(float2) is only 8 bytes, we need to add
|
|
* a dummy element at the end for odd sizes to satisfy the alignment requirement. Notice that the
|
|
* number_of_markers uniform was already assigned above to the original size, so the dummy
|
|
* element has no effect in the shader. Also notice that the marker colors are always 16 byte
|
|
* aligned since sizeof(float4) is 16 bytes, so not need to add anything there. */
|
|
if (marker_positions.size() % 2 == 1) {
|
|
marker_positions.append(float2(0.0f));
|
|
}
|
|
|
|
GPUStorageBuf *positions_ssbo = GPU_storagebuf_create_ex(marker_positions.size() *
|
|
sizeof(float2),
|
|
marker_positions.data(),
|
|
GPU_USAGE_STATIC,
|
|
"Marker Positions");
|
|
const int positions_ssbo_location = GPU_shader_get_ssbo_binding(shader, "marker_positions");
|
|
GPU_storagebuf_bind(positions_ssbo, positions_ssbo_location);
|
|
|
|
GPUStorageBuf *colors_ssbo = GPU_storagebuf_create_ex(marker_colors.size() * sizeof(float4),
|
|
marker_colors.data(),
|
|
GPU_USAGE_STATIC,
|
|
"Marker Colors");
|
|
const int colors_ssbo_location = GPU_shader_get_ssbo_binding(shader, "marker_colors");
|
|
GPU_storagebuf_bind(colors_ssbo, colors_ssbo_location);
|
|
|
|
this->result.bind_as_image(shader, "output_img");
|
|
|
|
compute_dispatch_threads_at_least(shader, this->result.domain().size);
|
|
|
|
this->result.unbind_as_image();
|
|
GPU_storagebuf_unbind(positions_ssbo);
|
|
GPU_storagebuf_unbind(colors_ssbo);
|
|
GPU_shader_unbind();
|
|
|
|
GPU_storagebuf_free(positions_ssbo);
|
|
GPU_storagebuf_free(colors_ssbo);
|
|
}
|
|
|
|
void KeyingScreen::compute_cpu(const float smoothness,
|
|
const Vector<float2> &marker_positions,
|
|
const Vector<float4> &marker_colors)
|
|
{
|
|
float squared_shape_parameter = math::square(1.0f / smoothness);
|
|
const int2 size = this->result.domain().size;
|
|
parallel_for(size, [&](const int2 texel) {
|
|
float2 normalized_pixel_location = (float2(texel) + float2(0.5f)) / float2(size);
|
|
|
|
/* Interpolate the markers using a Gaussian Radial Basis Function Interpolation with the
|
|
* reciprocal of the smoothness as the shaping parameter. Equal weights are assigned to all
|
|
* markers, so no RBF fitting is required. */
|
|
float sum_of_weights = 0.0f;
|
|
float4 weighted_sum = float4(0.0f);
|
|
for (const int64_t i : marker_positions.index_range()) {
|
|
float2 marker_position = marker_positions[i];
|
|
float2 difference = normalized_pixel_location - marker_position;
|
|
float squared_distance = math::dot(difference, difference);
|
|
float gaussian = math::exp(-squared_distance * squared_shape_parameter);
|
|
|
|
float4 marker_color = marker_colors[i];
|
|
weighted_sum += marker_color * gaussian;
|
|
sum_of_weights += gaussian;
|
|
}
|
|
weighted_sum /= sum_of_weights;
|
|
|
|
this->result.store_pixel(texel, weighted_sum);
|
|
});
|
|
}
|
|
|
|
KeyingScreen::~KeyingScreen()
|
|
{
|
|
this->result.release();
|
|
}
|
|
|
|
/* --------------------------------------------------------------------
|
|
* Keying Screen Container.
|
|
*/
|
|
|
|
void KeyingScreenContainer::reset()
|
|
{
|
|
/* First, delete all cached keying screens that are no longer needed. */
|
|
for (auto &cached_keying_screens_for_id : map_.values()) {
|
|
cached_keying_screens_for_id.remove_if([](auto item) { return !item.value->needed; });
|
|
}
|
|
map_.remove_if([](auto item) { return item.value.is_empty(); });
|
|
|
|
/* Second, reset the needed status of the remaining cached keying screens to false to ready them
|
|
* to track their needed status for the next evaluation. */
|
|
for (auto &cached_keying_screens_for_id : map_.values()) {
|
|
for (auto &value : cached_keying_screens_for_id.values()) {
|
|
value->needed = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
Result &KeyingScreenContainer::get(Context &context,
|
|
MovieClip *movie_clip,
|
|
MovieTrackingObject *movie_tracking_object,
|
|
float smoothness)
|
|
{
|
|
const KeyingScreenKey key(context.get_frame_number(), smoothness);
|
|
|
|
/* We concatenate the movie clip ID name with the tracking object name to cache multiple tracking
|
|
* objects per movie clip. */
|
|
const std::string library_key = movie_clip->id.lib ? movie_clip->id.lib->id.name : "";
|
|
const std::string id_key = std::string(movie_clip->id.name) + library_key;
|
|
const std::string object_key = id_key + movie_tracking_object->name;
|
|
auto &cached_keying_screens_for_id = map_.lookup_or_add_default(object_key);
|
|
|
|
/* Invalidate the cache for that movie clip if it was changed since it was cached. */
|
|
if (!cached_keying_screens_for_id.is_empty() &&
|
|
movie_clip->runtime.last_update != update_counts_.lookup(id_key))
|
|
{
|
|
cached_keying_screens_for_id.clear();
|
|
}
|
|
|
|
auto &keying_screen = *cached_keying_screens_for_id.lookup_or_add_cb(key, [&]() {
|
|
return std::make_unique<KeyingScreen>(context, movie_clip, movie_tracking_object, smoothness);
|
|
});
|
|
|
|
/* Store the current update count to later compare to and check if the movie clip changed. */
|
|
update_counts_.add_overwrite(id_key, movie_clip->runtime.last_update);
|
|
|
|
keying_screen.needed = true;
|
|
return keying_screen.result;
|
|
}
|
|
|
|
} // namespace blender::compositor
|