From 3f6912ece2cc2d99cee51d8657a5a8def2575a2d Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Mon, 1 May 2023 11:29:06 +0200 Subject: [PATCH] Realtime Compositor: Implement Mask node This patch implements the Mask node for the realtime compositor. The evaluation of the mask is not GPU accelerated, but is cached as a form of temporary implementation until we implement a GPU evaluator for masks. One limitation currently is that we do not redraw the viewport while the mask is getting edited by the user, because always doing that will be redundant in most situations, and conditioning the redraw requires a lot of work that should be handled outside of this patch. This is similar to the Texture node implementation in 151a53110c. Pull Request: https://projects.blender.org/blender/blender/pulls/107464 --- source/blender/blenkernel/intern/mask.c | 6 + .../realtime_compositor/CMakeLists.txt | 2 + .../realtime_compositor/COM_context.hh | 3 + .../COM_static_cache_manager.hh | 4 +- .../cached_resources/COM_cached_mask.hh | 86 ++++++++ .../cached_resources/COM_cached_texture.hh | 5 +- .../cached_resources/intern/cached_mask.cc | 199 ++++++++++++++++++ .../cached_resources/intern/cached_texture.cc | 2 +- .../realtime_compositor/intern/context.cc | 5 + .../intern/static_cache_manager.cc | 1 + source/blender/draw/intern/draw_manager.c | 1 + source/blender/makesdna/DNA_mask_types.h | 2 + .../composite/nodes/node_composite_mask.cc | 78 ++++++- .../composite/nodes/node_composite_scale.cc | 2 +- .../composite/nodes/node_composite_texture.cc | 2 +- 15 files changed, 387 insertions(+), 11 deletions(-) create mode 100644 source/blender/compositor/realtime_compositor/cached_resources/COM_cached_mask.hh create mode 100644 source/blender/compositor/realtime_compositor/cached_resources/intern/cached_mask.cc diff --git a/source/blender/blenkernel/intern/mask.c b/source/blender/blenkernel/intern/mask.c index 45ab6289c14..5a7975faad8 100644 --- a/source/blender/blenkernel/intern/mask.c +++ b/source/blender/blenkernel/intern/mask.c @@ -40,6 +40,8 @@ #include "DEG_depsgraph_build.h" +#include "DRW_engine.h" + #include "BLO_read_write.h" static CLG_LogRef LOG = {"bke.mask"}; @@ -57,6 +59,8 @@ static void mask_copy_data(Main *UNUSED(bmain), /* TODO: add unused flag to those as well. */ BKE_mask_layer_copy_list(&mask_dst->masklayers, &mask_src->masklayers); + BLI_listbase_clear((ListBase *)&mask_dst->drawdata); + /* enable fake user by default */ id_fake_user_set(&mask_dst->id); } @@ -67,6 +71,8 @@ static void mask_free_data(ID *id) /* free mask data */ BKE_mask_layer_free_list(&mask->masklayers); + + DRW_drawdata_free(id); } static void mask_foreach_id(ID *id, LibraryForeachIDData *data) diff --git a/source/blender/compositor/realtime_compositor/CMakeLists.txt b/source/blender/compositor/realtime_compositor/CMakeLists.txt index f3a94610d1e..bc457ad92a4 100644 --- a/source/blender/compositor/realtime_compositor/CMakeLists.txt +++ b/source/blender/compositor/realtime_compositor/CMakeLists.txt @@ -70,12 +70,14 @@ set(SRC algorithms/COM_algorithm_smaa.hh algorithms/COM_algorithm_symmetric_separable_blur.hh + cached_resources/intern/cached_mask.cc cached_resources/intern/cached_texture.cc cached_resources/intern/morphological_distance_feather_weights.cc cached_resources/intern/smaa_precomputed_textures.cc cached_resources/intern/symmetric_blur_weights.cc cached_resources/intern/symmetric_separable_blur_weights.cc + cached_resources/COM_cached_mask.hh cached_resources/COM_cached_resource.hh cached_resources/COM_cached_texture.hh cached_resources/COM_morphological_distance_feather_weights.hh diff --git a/source/blender/compositor/realtime_compositor/COM_context.hh b/source/blender/compositor/realtime_compositor/COM_context.hh index 58a422416d6..014ae9d2252 100644 --- a/source/blender/compositor/realtime_compositor/COM_context.hh +++ b/source/blender/compositor/realtime_compositor/COM_context.hh @@ -82,6 +82,9 @@ class Context { /* Get the size of the compositing region. See get_compositing_region(). */ int2 get_compositing_region_size() const; + /* Get the normalized render percentage of the active scene. */ + float get_render_percentage() const; + /* Get the current frame number of the active scene. */ int get_frame_number() const; diff --git a/source/blender/compositor/realtime_compositor/COM_static_cache_manager.hh b/source/blender/compositor/realtime_compositor/COM_static_cache_manager.hh index 1f776d96c9e..f0781b5e425 100644 --- a/source/blender/compositor/realtime_compositor/COM_static_cache_manager.hh +++ b/source/blender/compositor/realtime_compositor/COM_static_cache_manager.hh @@ -2,6 +2,7 @@ #pragma once +#include "COM_cached_mask.hh" #include "COM_cached_texture.hh" #include "COM_morphological_distance_feather_weights.hh" #include "COM_smaa_precomputed_textures.hh" @@ -37,8 +38,9 @@ class StaticCacheManager { SymmetricBlurWeightsContainer symmetric_blur_weights; SymmetricSeparableBlurWeightsContainer symmetric_separable_blur_weights; MorphologicalDistanceFeatherWeightsContainer morphological_distance_feather_weights; - SMAAPrecomputedTexturesContainer smaa_precomputed_textures; CachedTextureContainer cached_textures; + CachedMaskContainer cached_masks; + SMAAPrecomputedTexturesContainer smaa_precomputed_textures; /* 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 diff --git a/source/blender/compositor/realtime_compositor/cached_resources/COM_cached_mask.hh b/source/blender/compositor/realtime_compositor/cached_resources/COM_cached_mask.hh new file mode 100644 index 00000000000..48070a4e0b1 --- /dev/null +++ b/source/blender/compositor/realtime_compositor/cached_resources/COM_cached_mask.hh @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include +#include +#include + +#include "BLI_map.hh" +#include "BLI_math_vector_types.hh" + +#include "GPU_texture.h" + +#include "DNA_mask_types.h" +#include "DNA_scene_types.h" + +#include "COM_cached_resource.hh" + +namespace blender::realtime_compositor { + +class Context; + +/* ------------------------------------------------------------------------------------------------ + * Cached Mask Key. + */ +class CachedMaskKey { + public: + int2 size; + bool use_feather; + int motion_blur_samples; + float motion_blur_shutter; + + CachedMaskKey(int2 size, bool use_feather, int motion_blur_samples, float motion_blur_shutter); + + uint64_t hash() const; +}; + +bool operator==(const CachedMaskKey &a, const CachedMaskKey &b); + +/* ------------------------------------------------------------------------------------------------- + * Cached Mask. + * + * A cached resource that computes and caches a GPU texture containing the result of evaluating the + * given mask ID on a space that spans the given size, parametrized by the given parameters. */ +class CachedMask : public CachedResource { + private: + GPUTexture *texture_ = nullptr; + + public: + CachedMask(Mask *mask, + int2 size, + int frame, + bool use_feather, + int motion_blur_samples, + float motion_blur_shutter); + + ~CachedMask(); + + GPUTexture *texture(); +}; + +/* ------------------------------------------------------------------------------------------------ + * Cached Mask Container. + */ +class CachedMaskContainer : CachedResourceContainer { + private: + Map>> map_; + + public: + void reset() override; + + /* Check if the given mask ID has changed since the last time it was retrieved through its + * recalculate flag, and if so, invalidate its corresponding cached mask and reset the + * recalculate flag to ready it to track the next change. Then, check if there is an available + * CachedMask 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. */ + CachedMask &get(Context &context, + Mask *mask, + int2 size, + bool use_feather, + int motion_blur_samples, + float motion_blur_shutter); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/cached_resources/COM_cached_texture.hh b/source/blender/compositor/realtime_compositor/cached_resources/COM_cached_texture.hh index f17a3ef88fc..bd9c27fff8a 100644 --- a/source/blender/compositor/realtime_compositor/cached_resources/COM_cached_texture.hh +++ b/source/blender/compositor/realtime_compositor/cached_resources/COM_cached_texture.hh @@ -39,9 +39,8 @@ bool operator==(const CachedTextureKey &a, const CachedTextureKey &b); /* ------------------------------------------------------------------------------------------------- * Cached Texture. * - * A cached resource that computes and caches a GPU texture containing the the result of evaluating - * the given texture ID on a space that spans the given size, modified by the given offset and - * scale. */ + * A cached resource that computes and caches a GPU texture containing the result of evaluating the + * given texture ID on a space that spans the given size, parametrized by the given parameters. */ class CachedTexture : public CachedResource { private: GPUTexture *color_texture_ = nullptr; diff --git a/source/blender/compositor/realtime_compositor/cached_resources/intern/cached_mask.cc b/source/blender/compositor/realtime_compositor/cached_resources/intern/cached_mask.cc new file mode 100644 index 00000000000..813153780ba --- /dev/null +++ b/source/blender/compositor/realtime_compositor/cached_resources/intern/cached_mask.cc @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include + +#include "BLI_array.hh" +#include "BLI_hash.hh" +#include "BLI_index_range.hh" +#include "BLI_listbase.h" +#include "BLI_math_vector_types.hh" +#include "BLI_task.hh" + +#include "GPU_texture.h" + +#include "BKE_lib_id.h" +#include "BKE_mask.h" + +#include "DNA_ID.h" +#include "DNA_mask_types.h" + +#include "COM_cached_mask.hh" +#include "COM_context.hh" + +namespace blender::realtime_compositor { + +/* -------------------------------------------------------------------- + * Cached Mask Key. + */ + +CachedMaskKey::CachedMaskKey(int2 size, + bool use_feather, + int motion_blur_samples, + float motion_blur_shutter) + : size(size), + use_feather(use_feather), + motion_blur_samples(motion_blur_samples), + motion_blur_shutter(motion_blur_shutter) +{ +} + +uint64_t CachedMaskKey::hash() const +{ + return get_default_hash_4(size, use_feather, motion_blur_samples, motion_blur_shutter); +} + +bool operator==(const CachedMaskKey &a, const CachedMaskKey &b) +{ + return a.size == b.size && a.use_feather == b.use_feather && + a.motion_blur_samples == b.motion_blur_samples && + a.motion_blur_shutter == b.motion_blur_shutter; +} + +/* -------------------------------------------------------------------- + * Cached Mask. + */ + +static Vector get_mask_raster_handles(Mask *mask, + int2 size, + int current_frame, + bool use_feather, + int motion_blur_samples, + float motion_blur_shutter) +{ + Vector handles; + + if (!mask) { + return handles; + } + + /* If motion blur samples are 1, that means motion blur is disabled, in that case, just return + * the currently evaluated raster handle. */ + if (motion_blur_samples == 1) { + MaskRasterHandle *handle = BKE_maskrasterize_handle_new(); + BKE_maskrasterize_handle_init(handle, mask, size.x, size.y, true, true, use_feather); + handles.append(handle); + return handles; + } + + /* Otherwise, we have a number of motion blur samples, so make a copy of the Mask ID and evaluate + * it at the different motion blur frames to get the needed raster handles. */ + Mask *evaluation_mask = reinterpret_cast( + BKE_id_copy_ex(nullptr, &mask->id, nullptr, LIB_ID_COPY_LOCALIZE | LIB_ID_COPY_NO_ANIMDATA)); + + /* We evaluate at the frames in the range [current_frame - shutter, current_frame + shutter]. */ + const float start_frame = current_frame - motion_blur_shutter; + const float frame_step = (motion_blur_shutter * 2.0f) / motion_blur_samples; + for (int i = 0; i < motion_blur_samples; i++) { + MaskRasterHandle *handle = BKE_maskrasterize_handle_new(); + BKE_mask_evaluate(evaluation_mask, start_frame + frame_step * i, true); + BKE_maskrasterize_handle_init( + handle, evaluation_mask, size.x, size.y, true, true, use_feather); + handles.append(handle); + } + + BKE_id_free(nullptr, &evaluation_mask->id); + + return handles; +} + +CachedMask::CachedMask(Mask *mask, + int2 size, + int frame, + bool use_feather, + int motion_blur_samples, + float motion_blur_shutter) +{ + Vector handles = get_mask_raster_handles( + mask, size, frame, use_feather, motion_blur_samples, motion_blur_shutter); + + Array evaluated_mask(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)) { + /* Compute the coordinates in the [0, 1] range and add 0.5 to evaluate the mask at the + * center of pixels. */ + const float2 coordinates = (float2(x, y) + 0.5f) / float2(size); + float mask_value = 0.0f; + for (MaskRasterHandle *handle : handles) { + mask_value += BKE_maskrasterize_handle_sample(handle, coordinates); + } + evaluated_mask[y * size.x + x] = mask_value / handles.size(); + } + } + }); + + for (MaskRasterHandle *handle : handles) { + BKE_maskrasterize_handle_free(handle); + } + + texture_ = GPU_texture_create_2d("Cached Mask", + size.x, + size.y, + 1, + GPU_R16F, + GPU_TEXTURE_USAGE_SHADER_READ, + evaluated_mask.data()); +} + +CachedMask::~CachedMask() +{ + GPU_texture_free(texture_); +} + +GPUTexture *CachedMask::texture() +{ + return texture_; +} + +/* -------------------------------------------------------------------- + * Cached Mask Container. + */ + +void CachedMaskContainer::reset() +{ + /* First, delete all cached masks that are no longer needed. */ + for (auto &cached_masks_for_id : map_.values()) { + cached_masks_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 masks to false to ready them to track + * their needed status for the next evaluation. */ + for (auto &cached_masks_for_id : map_.values()) { + for (auto &value : cached_masks_for_id.values()) { + value->needed = false; + } + } +} + +CachedMask &CachedMaskContainer::get(Context &context, + Mask *mask, + int2 size, + bool use_feather, + int motion_blur_samples, + float motion_blur_shutter) +{ + const CachedMaskKey key(size, use_feather, motion_blur_samples, motion_blur_shutter); + + auto &cached_masks_for_id = map_.lookup_or_add_default(mask->id.name); + + /* Invalidate the cache for that mask ID if it was changed and reset the recalculate flag. */ + if (context.query_id_recalc_flag(reinterpret_cast(mask)) & ID_RECALC_ALL) { + cached_masks_for_id.clear(); + } + + auto &cached_mask = *cached_masks_for_id.lookup_or_add_cb(key, [&]() { + return std::make_unique(mask, + size, + context.get_frame_number(), + use_feather, + motion_blur_samples, + motion_blur_shutter); + }); + + cached_mask.needed = true; + return cached_mask; +} + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/cached_resources/intern/cached_texture.cc b/source/blender/compositor/realtime_compositor/cached_resources/intern/cached_texture.cc index d1728779458..4a970beba9c 100644 --- a/source/blender/compositor/realtime_compositor/cached_resources/intern/cached_texture.cc +++ b/source/blender/compositor/realtime_compositor/cached_resources/intern/cached_texture.cc @@ -59,7 +59,7 @@ CachedTexture::CachedTexture( 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)) { - /* Compute the coordinates in the [0, 1] range and add 0.5 to evaluate the texture at the + /* Compute the coordinates in the [-1, 1] range and add 0.5 to evaluate the texture at the * center of pixels in case it was interpolated. */ float2 coordinates = ((float2(x, y) + 0.5f) / float2(size)) * 2.0f - 1.0f; /* Note that it is expected that the offset is scaled by the scale. */ diff --git a/source/blender/compositor/realtime_compositor/intern/context.cc b/source/blender/compositor/realtime_compositor/intern/context.cc index 3b2f2b221cb..d3150dcc3f7 100644 --- a/source/blender/compositor/realtime_compositor/intern/context.cc +++ b/source/blender/compositor/realtime_compositor/intern/context.cc @@ -19,6 +19,11 @@ int2 Context::get_compositing_region_size() const return int2(BLI_rcti_size_x(&compositing_region), BLI_rcti_size_y(&compositing_region)); } +float Context::get_render_percentage() const +{ + return get_scene()->r.size / 100.0f; +} + int Context::get_frame_number() const { return get_scene()->r.cfra; diff --git a/source/blender/compositor/realtime_compositor/intern/static_cache_manager.cc b/source/blender/compositor/realtime_compositor/intern/static_cache_manager.cc index 7fe3fa5dca9..fedae4430f9 100644 --- a/source/blender/compositor/realtime_compositor/intern/static_cache_manager.cc +++ b/source/blender/compositor/realtime_compositor/intern/static_cache_manager.cc @@ -10,6 +10,7 @@ void StaticCacheManager::reset() symmetric_separable_blur_weights.reset(); morphological_distance_feather_weights.reset(); cached_textures.reset(); + cached_masks.reset(); smaa_precomputed_textures.reset(); } diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c index 15c63bf6560..daf4675764f 100644 --- a/source/blender/draw/intern/draw_manager.c +++ b/source/blender/draw/intern/draw_manager.c @@ -833,6 +833,7 @@ static bool id_type_can_have_drawdata(const short id_type) case ID_WO: case ID_SCE: case ID_TE: + case ID_MSK: return true; /* no DrawData */ diff --git a/source/blender/makesdna/DNA_mask_types.h b/source/blender/makesdna/DNA_mask_types.h index a9010c78402..ceed7a43ed8 100644 --- a/source/blender/makesdna/DNA_mask_types.h +++ b/source/blender/makesdna/DNA_mask_types.h @@ -22,6 +22,8 @@ extern "C" { typedef struct Mask { ID id; struct AnimData *adt; + /* runtime (must be immediately after id for utilities to use it). */ + DrawDataList drawdata; /** Mask layers. */ ListBase masklayers; /** Index of active mask layer (-1 == None). */ diff --git a/source/blender/nodes/composite/nodes/node_composite_mask.cc b/source/blender/nodes/composite/nodes/node_composite_mask.cc index c9300d32cd5..840fcafb63f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_mask.cc +++ b/source/blender/nodes/composite/nodes/node_composite_mask.cc @@ -12,7 +12,9 @@ #include "UI_interface.h" #include "UI_resources.h" +#include "COM_cached_mask.hh" #include "COM_node_operation.hh" +#include "COM_utilities.hh" #include "node_composite_util.hh" @@ -20,6 +22,8 @@ namespace blender::nodes::node_composite_mask_cc { +NODE_STORAGE_FUNCS(NodeMask) + static void cmp_node_mask_declare(NodeDeclarationBuilder &b) { b.add_output(N_("Mask")); @@ -86,8 +90,76 @@ class MaskOperation : public NodeOperation { void execute() override { - get_result("Mask").allocate_invalid(); - context().set_info_message("Viewport compositor setup not fully supported"); + Result &output_mask = get_result("Mask"); + if (!get_mask()) { + output_mask.allocate_invalid(); + return; + } + + const Domain domain = compute_domain(); + CachedMask &cached_mask = context().cache_manager().cached_masks.get( + context(), + get_mask(), + domain.size, + get_use_feather(), + get_motion_blur_samples(), + get_motion_blur_shutter()); + + output_mask.allocate_texture(domain); + GPU_texture_copy(output_mask.texture(), cached_mask.texture()); + } + + Domain compute_domain() override + { + return Domain(compute_size()); + } + + int2 compute_size() + { + if (get_flags() & CMP_NODE_MASK_FLAG_SIZE_FIXED) { + return get_size(); + } + + if (get_flags() & CMP_NODE_MASK_FLAG_SIZE_FIXED_SCENE) { + return get_size() * context().get_render_percentage(); + } + + return context().get_compositing_region_size(); + } + + int2 get_size() + { + return int2(node_storage(bnode()).size_x, node_storage(bnode()).size_y); + } + + bool get_use_feather() + { + return !bool(get_flags() & CMP_NODE_MASK_FLAG_NO_FEATHER); + } + + int get_motion_blur_samples() + { + return use_motion_blur() ? bnode().custom2 : 1; + } + + float get_motion_blur_shutter() + { + return bnode().custom3; + } + + bool use_motion_blur() + { + return get_flags() & CMP_NODE_MASK_FLAG_MOTION_BLUR; + } + + CMPNodeMaskFlags get_flags() + { + return static_cast(bnode().custom1); + } + + Mask *get_mask() + { + return reinterpret_cast(bnode().id); } }; @@ -110,8 +182,6 @@ void register_node_type_cmp_mask() ntype.initfunc = file_ns::node_composit_init_mask; ntype.labelfunc = file_ns::node_mask_label; ntype.get_compositor_operation = file_ns::get_compositor_operation; - ntype.realtime_compositor_unsupported_message = N_( - "Node not supported in the Viewport compositor"); node_type_storage(&ntype, "NodeMask", node_free_standard_storage, node_copy_standard_storage); diff --git a/source/blender/nodes/composite/nodes/node_composite_scale.cc b/source/blender/nodes/composite/nodes/node_composite_scale.cc index c70cbf0098c..addf3a905b2 100644 --- a/source/blender/nodes/composite/nodes/node_composite_scale.cc +++ b/source/blender/nodes/composite/nodes/node_composite_scale.cc @@ -126,7 +126,7 @@ class ScaleOperation : public NodeOperation { /* Scale by the render resolution percentage. */ float2 get_scale_render_percent() { - return float2(context().get_scene()->r.size / 100.0f); + return float2(context().get_render_percentage()); } float2 get_scale_render_size() diff --git a/source/blender/nodes/composite/nodes/node_composite_texture.cc b/source/blender/nodes/composite/nodes/node_composite_texture.cc index e48fc7c7c0e..32057b235c7 100644 --- a/source/blender/nodes/composite/nodes/node_composite_texture.cc +++ b/source/blender/nodes/composite/nodes/node_composite_texture.cc @@ -81,7 +81,7 @@ class TextureOperation : public NodeOperation { Tex *get_texture() { - return (Tex *)bnode().id; + return reinterpret_cast(bnode().id); } };