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); } };