diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index a479dcb574d..ff11090ca12 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -11,6 +11,7 @@ #include +#include "BLI_bounds_types.hh" #include "BLI_cache_mutex.hh" #include "BLI_float3x3.hh" #include "BLI_float4x4.hh" @@ -21,6 +22,7 @@ #include "BLI_task.hh" #include "BLI_vector.hh" #include "BLI_virtual_array.hh" +#include "BLI_shared_cache.hh" #include "BKE_attribute.hh" #include "BKE_attribute_math.hh" @@ -95,6 +97,13 @@ class CurvesGeometryRuntime { */ mutable Span evaluated_positions_span; + /** + * A cache of bounds shared between data-blocks with unchanged positions and radii. + * When data changes affect the bounds, the cache is "un-shared" with other geometries. + * See #SharedCache comments. + */ + mutable SharedCache> bounds_cache; + /** * Cache of lengths along each evaluated curve for each evaluated point. If a curve is * cyclic, it needs one more length value to correspond to the last segment, so in order to @@ -391,6 +400,11 @@ class CurvesGeometry : public ::CurvesGeometry { void tag_topology_changed(); /** Call after changing the "tilt" or "up" attributes. */ void tag_normals_changed(); + /** + * Call when making manual changes to the "radius" attribute. The attribute API will also call + * this in #finish() calls. + */ + void tag_radii_changed(); void translate(const float3 &translation); void transform(const float4x4 &matrix); diff --git a/source/blender/blenkernel/BKE_mesh_types.h b/source/blender/blenkernel/BKE_mesh_types.h index 80f61086052..29b4dafcd35 100644 --- a/source/blender/blenkernel/BKE_mesh_types.h +++ b/source/blender/blenkernel/BKE_mesh_types.h @@ -15,6 +15,10 @@ # include "DNA_customdata_types.h" +# include "BLI_bounds_types.hh" +# include "BLI_math_vec_types.hh" +# include "BLI_shared_cache.hh" + # include "MEM_guardedalloc.h" struct BVHCache; @@ -84,6 +88,12 @@ struct MeshRuntime { /** Needed to ensure some thread-safety during render data pre-processing. */ std::mutex render_mutex; + /** + * A cache of bounds shared between data-blocks with unchanged positions. When changing positions + * affect the bounds, the cache is "un-shared" with other geometries. See #SharedCache comments. + */ + SharedCache> bounds_cache; + /** Lazily initialized SoA data from the #edit_mesh field in #Mesh. */ EditMeshData *edit_data = nullptr; diff --git a/source/blender/blenkernel/BKE_pointcloud.h b/source/blender/blenkernel/BKE_pointcloud.h index d6367ac5a61..d493e051cb5 100644 --- a/source/blender/blenkernel/BKE_pointcloud.h +++ b/source/blender/blenkernel/BKE_pointcloud.h @@ -6,6 +6,15 @@ * \ingroup bke * \brief General operations for point clouds. */ + +#ifdef __cplusplus +# include + +# include "BLI_bounds_types.hh" +# include "BLI_math_vec_types.hh" +# include "BLI_shared_cache.hh" +#endif + #ifdef __cplusplus extern "C" { #endif @@ -22,6 +31,23 @@ struct Scene; extern const char *POINTCLOUD_ATTR_POSITION; extern const char *POINTCLOUD_ATTR_RADIUS; +#ifdef __cplusplus +namespace blender::bke { + +struct PointCloudRuntime { + /** + * A cache of bounds shared between data-blocks with unchanged positions and radii. + * When data changes affect the bounds, the cache is "un-shared" with other geometries. + * See #SharedCache comments. + */ + mutable SharedCache> bounds_cache; + + MEM_CXX_CLASS_ALLOC_FUNCS("PointCloudRuntime"); +}; + +} // namespace blender::bke +#endif + void *BKE_pointcloud_add(struct Main *bmain, const char *name); void *BKE_pointcloud_add_default(struct Main *bmain, const char *name); struct PointCloud *BKE_pointcloud_new_nomain(int totpoint); @@ -30,7 +56,6 @@ void BKE_pointcloud_nomain_to_pointcloud(struct PointCloud *pointcloud_src, bool take_ownership); struct BoundBox *BKE_pointcloud_boundbox_get(struct Object *ob); -bool BKE_pointcloud_minmax(const struct PointCloud *pointcloud, float r_min[3], float r_max[3]); bool BKE_pointcloud_attribute_required(const struct PointCloud *pointcloud, const char *name); diff --git a/source/blender/blenkernel/intern/curves.cc b/source/blender/blenkernel/intern/curves.cc index 61755a5be9b..8d3f90230f6 100644 --- a/source/blender/blenkernel/intern/curves.cc +++ b/source/blender/blenkernel/intern/curves.cc @@ -14,7 +14,6 @@ #include "DNA_material_types.h" #include "DNA_object_types.h" -#include "BLI_bounds.hh" #include "BLI_index_range.hh" #include "BLI_listbase.h" #include "BLI_math_base.h" @@ -94,6 +93,7 @@ static void curves_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, con dst.runtime = MEM_new(__func__); dst.runtime->type_counts = src.runtime->type_counts; + dst.runtime->bounds_cache = src.runtime->bounds_cache; curves_dst->batch_cache = nullptr; } diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index 43bdb8e7b8c..18b6cdc2691 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -95,6 +95,7 @@ static void copy_curves_geometry(CurvesGeometry &dst, const CurvesGeometry &src) /* Though type counts are a cache, they must be copied because they are calculated eagerly. */ dst.runtime->type_counts = src.runtime->type_counts; + dst.runtime->bounds_cache = src.runtime->bounds_cache; } CurvesGeometry::CurvesGeometry(const CurvesGeometry &other) @@ -918,20 +919,22 @@ void CurvesGeometry::tag_positions_changed() this->runtime->tangent_cache_mutex.tag_dirty(); this->runtime->normal_cache_mutex.tag_dirty(); this->runtime->length_cache_mutex.tag_dirty(); + this->runtime->bounds_cache.tag_dirty(); } void CurvesGeometry::tag_topology_changed() { - this->runtime->position_cache_mutex.tag_dirty(); - this->runtime->tangent_cache_mutex.tag_dirty(); - this->runtime->normal_cache_mutex.tag_dirty(); + this->tag_positions_changed(); this->runtime->offsets_cache_mutex.tag_dirty(); this->runtime->nurbs_basis_cache_mutex.tag_dirty(); - this->runtime->length_cache_mutex.tag_dirty(); } void CurvesGeometry::tag_normals_changed() { this->runtime->normal_cache_mutex.tag_dirty(); } +void CurvesGeometry::tag_radii_changed() +{ + this->runtime->bounds_cache.tag_dirty(); +} static void translate_positions(MutableSpan positions, const float3 &translation) { @@ -1006,25 +1009,28 @@ void CurvesGeometry::transform(const float4x4 &matrix) this->tag_positions_changed(); } -static std::optional> curves_bounds(const CurvesGeometry &curves) -{ - const Span positions = curves.positions(); - const VArray radii = curves.attributes().lookup_or_default( - ATTR_RADIUS, ATTR_DOMAIN_POINT, 0.0f); - if (!(radii.is_single() && radii.get_internal_single() == 0.0f)) { - return bounds::min_max_with_radii(positions, radii.get_internal_span()); - } - return bounds::min_max(positions); -} - bool CurvesGeometry::bounds_min_max(float3 &min, float3 &max) const { - const std::optional> bounds = curves_bounds(*this); - if (!bounds) { + if (this->points_num() == 0) { return false; } - min = math::min(bounds->min, min); - max = math::max(bounds->max, max); + + this->runtime->bounds_cache.ensure([&](Bounds &r_bounds) { + const Span positions = this->evaluated_positions(); + if (this->attributes().contains("radius")) { + const VArraySpan radii = this->attributes().lookup("radius"); + Array evaluated_radii(this->evaluated_points_num()); + this->interpolate_to_evaluated(radii, evaluated_radii.as_mutable_span()); + r_bounds = *bounds::min_max_with_radii(positions, evaluated_radii.as_span()); + } + else { + r_bounds = *bounds::min_max(positions); + } + }); + + const Bounds &bounds = this->runtime->bounds_cache.data(); + min = math::min(bounds.min, min); + max = math::max(bounds.max, max); return true; } diff --git a/source/blender/blenkernel/intern/editmesh_cache.cc b/source/blender/blenkernel/intern/editmesh_cache.cc index 438d287fb28..6017b81c3b3 100644 --- a/source/blender/blenkernel/intern/editmesh_cache.cc +++ b/source/blender/blenkernel/intern/editmesh_cache.cc @@ -122,7 +122,7 @@ bool BKE_editmesh_cache_calc_minmax(struct BMEditMesh *em, if (bm->totvert) { if (emd->vertexCos) { Span vert_coords(reinterpret_cast(emd->vertexCos), bm->totvert); - std::optional> bounds = bounds::min_max(vert_coords); + std::optional> bounds = bounds::min_max(vert_coords); BLI_assert(bounds.has_value()); copy_v3_v3(min, math::min(bounds->min, float3(min))); copy_v3_v3(max, math::max(bounds->max, float3(max))); diff --git a/source/blender/blenkernel/intern/geometry_component_curves.cc b/source/blender/blenkernel/intern/geometry_component_curves.cc index fff9004bc16..debcb35699e 100644 --- a/source/blender/blenkernel/intern/geometry_component_curves.cc +++ b/source/blender/blenkernel/intern/geometry_component_curves.cc @@ -315,6 +315,12 @@ static void tag_component_positions_changed(void *owner) curves.tag_positions_changed(); } +static void tag_component_radii_changed(void *owner) +{ + blender::bke::CurvesGeometry &curves = *static_cast(owner); + curves.tag_radii_changed(); +} + static void tag_component_normals_changed(void *owner) { blender::bke::CurvesGeometry &curves = *static_cast(owner); @@ -384,7 +390,7 @@ static ComponentAttributeProviders create_attribute_providers_for_curve() point_access, make_array_read_attribute, make_array_write_attribute, - nullptr); + tag_component_radii_changed); static BuiltinCustomDataLayerProvider id("id", ATTR_DOMAIN_POINT, diff --git a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc index 34cc99d2f92..71484bbd5f0 100644 --- a/source/blender/blenkernel/intern/geometry_component_pointcloud.cc +++ b/source/blender/blenkernel/intern/geometry_component_pointcloud.cc @@ -105,6 +105,18 @@ void PointCloudComponent::ensure_owns_direct_data() namespace blender::bke { +static void tag_component_positions_changed(void *owner) +{ + PointCloud &points = *static_cast(owner); + points.tag_positions_changed(); +} + +static void tag_component_radius_changed(void *owner) +{ + PointCloud &points = *static_cast(owner); + points.tag_radii_changed(); +} + /** * In this function all the attribute providers for a point cloud component are created. Most data * in this function is statically allocated, because it does not change over time. @@ -135,7 +147,7 @@ static ComponentAttributeProviders create_attribute_providers_for_point_cloud() point_access, make_array_read_attribute, make_array_write_attribute, - nullptr); + tag_component_positions_changed); static BuiltinCustomDataLayerProvider radius("radius", ATTR_DOMAIN_POINT, CD_PROP_FLOAT, @@ -146,7 +158,7 @@ static ComponentAttributeProviders create_attribute_providers_for_point_cloud() point_access, make_array_read_attribute, make_array_write_attribute, - nullptr); + tag_component_radius_changed); static BuiltinCustomDataLayerProvider id("id", ATTR_DOMAIN_POINT, CD_PROP_INT32, diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index ee4c398c3d6..f9e12ef0274 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -217,7 +217,7 @@ bool GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_ma using namespace blender; bool have_minmax = false; if (const PointCloud *pointcloud = this->get_pointcloud_for_read()) { - have_minmax |= BKE_pointcloud_minmax(pointcloud, *r_min, *r_max); + have_minmax |= pointcloud->bounds_min_max(*r_min, *r_max); } if (const Mesh *mesh = this->get_mesh_for_read()) { have_minmax |= BKE_mesh_wrapper_minmax(mesh, *r_min, *r_max); @@ -227,14 +227,7 @@ bool GeometrySet::compute_boundbox_without_instances(float3 *r_min, float3 *r_ma } if (const Curves *curves_id = this->get_curves_for_read()) { const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id->geometry); - /* Using the evaluated positions is somewhat arbitrary, but it is probably expected. */ - std::optional> min_max = bounds::min_max( - curves.evaluated_positions()); - if (min_max) { - have_minmax = true; - *r_min = math::min(*r_min, min_max->min); - *r_max = math::max(*r_max, min_max->max); - } + have_minmax |= curves.bounds_min_max(*r_min, *r_max); } return have_minmax; } diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index b8658139161..40b2029a04a 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -128,6 +128,11 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int * highly unlikely we want to create a duplicate and not use it for drawing. */ mesh_dst->runtime->is_original_bmesh = false; + /* Share the bounding box cache between the source and destination mesh for improved performance + * when the source is persistent and edits to the destination don't change the bounds. It will be + * "un-shared" as necessary when the positions are changed. */ + mesh_dst->runtime->bounds_cache = mesh_src->runtime->bounds_cache; + /* Only do tessface if we have no polys. */ const bool do_tessface = ((mesh_src->totface != 0) && (mesh_src->totpoly == 0)); @@ -1523,29 +1528,27 @@ bool BKE_mesh_minmax(const Mesh *me, float r_min[3], float r_max[3]) return false; } - struct Result { - float3 min; - float3 max; - }; - const Span verts = me->verts(); + me->runtime->bounds_cache.ensure([me](Bounds &r_bounds) { + const Span verts = me->verts(); + r_bounds = threading::parallel_reduce( + verts.index_range(), + 1024, + Bounds{float3(FLT_MAX), float3(-FLT_MAX)}, + [verts](IndexRange range, const Bounds &init) { + Bounds result = init; + for (const int i : range) { + math::min_max(float3(verts[i].co), result.min, result.max); + } + return result; + }, + [](const Bounds &a, const Bounds &b) { + return Bounds{math::min(a.min, b.min), math::max(a.max, b.max)}; + }); + }); - const Result minmax = threading::parallel_reduce( - verts.index_range(), - 1024, - Result{float3(FLT_MAX), float3(-FLT_MAX)}, - [verts](IndexRange range, const Result &init) { - Result result = init; - for (const int i : range) { - math::min_max(float3(verts[i].co), result.min, result.max); - } - return result; - }, - [](const Result &a, const Result &b) { - return Result{math::min(a.min, b.min), math::max(a.max, b.max)}; - }); - - copy_v3_v3(r_min, math::min(minmax.min, float3(r_min))); - copy_v3_v3(r_max, math::max(minmax.max, float3(r_max))); + const Bounds &bounds = me->runtime->bounds_cache.data(); + copy_v3_v3(r_min, math::min(bounds.min, float3(r_min))); + copy_v3_v3(r_max, math::max(bounds.max, float3(r_max))); return true; } diff --git a/source/blender/blenkernel/intern/mesh_runtime.cc b/source/blender/blenkernel/intern/mesh_runtime.cc index e90a298ad8d..62c11535a0b 100644 --- a/source/blender/blenkernel/intern/mesh_runtime.cc +++ b/source/blender/blenkernel/intern/mesh_runtime.cc @@ -222,6 +222,7 @@ void BKE_mesh_tag_coords_changed(Mesh *mesh) bvhcache_free(mesh->runtime->bvh_cache); mesh->runtime->bvh_cache = nullptr; } + mesh->runtime->bounds_cache.tag_dirty(); } void BKE_mesh_tag_coords_changed_uniformly(Mesh *mesh) diff --git a/source/blender/blenkernel/intern/pointcloud.cc b/source/blender/blenkernel/intern/pointcloud.cc index 119daccce20..8207bb2cd05 100644 --- a/source/blender/blenkernel/intern/pointcloud.cc +++ b/source/blender/blenkernel/intern/pointcloud.cc @@ -14,7 +14,7 @@ #include "BLI_bounds.hh" #include "BLI_index_range.hh" #include "BLI_listbase.h" -#include "BLI_math_vec_types.hh" +#include "BLI_math_vector.hh" #include "BLI_rand.h" #include "BLI_span.hh" #include "BLI_string.h" @@ -68,6 +68,8 @@ static void pointcloud_init_data(ID *id) nullptr, pointcloud->totpoint, POINTCLOUD_ATTR_POSITION); + + pointcloud->runtime = new blender::bke::PointCloudRuntime(); } static void pointcloud_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, const int flag) @@ -83,6 +85,9 @@ static void pointcloud_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, alloc_type, pointcloud_dst->totpoint); + pointcloud_dst->runtime = new blender::bke::PointCloudRuntime(); + pointcloud_dst->runtime->bounds_cache = pointcloud_src->runtime->bounds_cache; + pointcloud_dst->batch_cache = nullptr; } @@ -93,6 +98,7 @@ static void pointcloud_free_data(ID *id) BKE_pointcloud_batch_cache_free(pointcloud); CustomData_free(&pointcloud->pdata, pointcloud->totpoint); MEM_SAFE_FREE(pointcloud->mat); + delete pointcloud->runtime; } static void pointcloud_foreach_id(ID *id, LibraryForeachIDData *data) @@ -139,6 +145,8 @@ static void pointcloud_blend_read_data(BlendDataReader *reader, ID *id) /* Materials */ BLO_read_pointer_array(reader, (void **)&pointcloud->mat); + + pointcloud->runtime = new blender::bke::PointCloudRuntime(); } static void pointcloud_blend_read_lib(BlendLibReader *reader, ID *id) @@ -277,33 +285,27 @@ void BKE_pointcloud_nomain_to_pointcloud(PointCloud *pointcloud_src, } } -static std::optional> point_cloud_bounds( - const PointCloud &pointcloud) -{ - blender::bke::AttributeAccessor attributes = pointcloud.attributes(); - blender::VArraySpan positions = attributes.lookup_or_default( - POINTCLOUD_ATTR_POSITION, ATTR_DOMAIN_POINT, float3(0)); - blender::VArray radii = attributes.lookup_or_default( - POINTCLOUD_ATTR_RADIUS, ATTR_DOMAIN_POINT, 0.0f); - - if (!(radii.is_single() && radii.get_internal_single() == 0.0f)) { - return blender::bounds::min_max_with_radii(positions, radii.get_internal_span()); - } - return blender::bounds::min_max(positions); -} - -bool BKE_pointcloud_minmax(const PointCloud *pointcloud, float r_min[3], float r_max[3]) +bool PointCloud::bounds_min_max(blender::float3 &min, blender::float3 &max) const { using namespace blender; - - const std::optional> min_max = point_cloud_bounds(*pointcloud); - if (!min_max) { + using namespace blender::bke; + if (this->totpoint == 0) { return false; } - - copy_v3_v3(r_min, math::min(min_max->min, float3(r_min))); - copy_v3_v3(r_max, math::max(min_max->max, float3(r_max))); - + this->runtime->bounds_cache.ensure([&](Bounds &r_bounds) { + const AttributeAccessor attributes = this->attributes(); + const VArraySpan positions = attributes.lookup(POINTCLOUD_ATTR_POSITION); + if (attributes.contains(POINTCLOUD_ATTR_RADIUS)) { + const VArraySpan radii = attributes.lookup(POINTCLOUD_ATTR_RADIUS); + r_bounds = *bounds::min_max_with_radii(positions, radii); + } + else { + r_bounds = *bounds::min_max(positions); + } + }); + const Bounds &bounds = this->runtime->bounds_cache.data(); + min = math::min(bounds.min, min); + max = math::max(bounds.max, max); return true; } @@ -326,7 +328,7 @@ BoundBox *BKE_pointcloud_boundbox_get(Object *ob) } else { const PointCloud *pointcloud = static_cast(ob->data); - BKE_pointcloud_minmax(pointcloud, min, max); + pointcloud->bounds_min_max(min, max); } BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max); @@ -427,6 +429,16 @@ void BKE_pointcloud_data_update(struct Depsgraph *depsgraph, struct Scene *scene object->runtime.geometry_set_eval = new GeometrySet(std::move(geometry_set)); } +void PointCloud::tag_positions_changed() +{ + this->runtime->bounds_cache.tag_dirty(); +} + +void PointCloud::tag_radii_changed() +{ + this->runtime->bounds_cache.tag_dirty(); +} + /* Draw Cache */ void (*BKE_pointcloud_batch_cache_dirty_tag_cb)(PointCloud *pointcloud, int mode) = nullptr; diff --git a/source/blender/blenlib/BLI_bounds.hh b/source/blender/blenlib/BLI_bounds.hh index f5a18a0ea48..55866dd67bd 100644 --- a/source/blender/blenlib/BLI_bounds.hh +++ b/source/blender/blenlib/BLI_bounds.hh @@ -10,38 +10,34 @@ #include +#include "BLI_bounds_types.hh" #include "BLI_math_vector.hh" #include "BLI_task.hh" namespace blender::bounds { -template struct MinMaxResult { - T min; - T max; -}; - /** * Find the smallest and largest values element-wise in the span. */ -template static std::optional> min_max(Span values) +template static std::optional> min_max(Span values) { if (values.is_empty()) { return std::nullopt; } - const MinMaxResult init{values.first(), values.first()}; + const Bounds init{values.first(), values.first()}; return threading::parallel_reduce( values.index_range(), 1024, init, - [&](IndexRange range, const MinMaxResult &init) { - MinMaxResult result = init; + [&](IndexRange range, const Bounds &init) { + Bounds result = init; for (const int i : range) { math::min_max(values[i], result.min, result.max); } return result; }, - [](const MinMaxResult &a, const MinMaxResult &b) { - return MinMaxResult{math::min(a.min, b.min), math::max(a.max, b.max)}; + [](const Bounds &a, const Bounds &b) { + return Bounds{math::min(a.min, b.min), math::max(a.max, b.max)}; }); } @@ -50,27 +46,27 @@ template static std::optional> min_max(Span value * first. The template type T is expected to have an addition operator implemented with RadiusT. */ template -static std::optional> min_max_with_radii(Span values, Span radii) +static std::optional> min_max_with_radii(Span values, Span radii) { BLI_assert(values.size() == radii.size()); if (values.is_empty()) { return std::nullopt; } - const MinMaxResult init{values.first(), values.first()}; + const Bounds init{values.first(), values.first()}; return threading::parallel_reduce( values.index_range(), 1024, init, - [&](IndexRange range, const MinMaxResult &init) { - MinMaxResult result = init; + [&](IndexRange range, const Bounds &init) { + Bounds result = init; for (const int i : range) { result.min = math::min(values[i] - radii[i], result.min); result.max = math::max(values[i] + radii[i], result.max); } return result; }, - [](const MinMaxResult &a, const MinMaxResult &b) { - return MinMaxResult{math::min(a.min, b.min), math::max(a.max, b.max)}; + [](const Bounds &a, const Bounds &b) { + return Bounds{math::min(a.min, b.min), math::max(a.max, b.max)}; }); } diff --git a/source/blender/blenlib/BLI_bounds_types.hh b/source/blender/blenlib/BLI_bounds_types.hh new file mode 100644 index 00000000000..1a08a92b716 --- /dev/null +++ b/source/blender/blenlib/BLI_bounds_types.hh @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + */ + +namespace blender { + +template struct Bounds { + T min; + T max; +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_shared_cache.hh b/source/blender/blenlib/BLI_shared_cache.hh new file mode 100644 index 00000000000..2f95c97d50c --- /dev/null +++ b/source/blender/blenlib/BLI_shared_cache.hh @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_cache_mutex.hh" + +namespace blender { + +/** + * A `SharedCache` is meant to share lazily computed data between equivalent objects. It allows + * saving unnecessary computation by making a calculated value accessible from any object that + * shares the cache. Unlike `CacheMutex`, the cached data is embedded inside of this object. + * + * When data is copied (copy-on-write before changing a mesh, for example), the cache is shared, + * allowing its calculation on either the source or original to make the result available on both + * objects. As soon as either object is changed in a way that invalidates the cache, the data is + * "un-shared", and they will no-longer influence each other. + * + * One important use case is a typical CoW update loop of a persistent geometry data-block in + * `Main`. Even if bounds are only calculated on the evaluated *copied* geometry, if nothing + * changes them, they only need to be calculated on the first evaluation, because the same + * evaluated bounds are also accessible from the original geometry. + * + * The cache is implemented with a shared pointer, so it is relatively cheap, but to avoid + * unnecessary overhead it should only be used for relatively expensive computations. + */ +template class SharedCache { + struct CacheData { + CacheMutex mutex; + T data; + }; + std::shared_ptr cache_; + + public: + SharedCache() + { + /* The cache should be allocated to trigger sharing of the cached data as early as possible. */ + cache_ = std::make_shared(); + } + + /** Tag the data for recomputation and stop sharing the cache with other objects. */ + void tag_dirty() + { + if (cache_.unique()) { + cache_->mutex.tag_dirty(); + } + else { + cache_ = std::make_shared(); + } + } + + /** + * If the cache is dirty, trigger its computation with the provided function which should set + * the proper data. + */ + void ensure(FunctionRef compute_cache) + { + cache_->mutex.ensure([&]() { compute_cache(this->cache_->data); }); + } + + /** Retrieve the cached data. */ + const T &data() + { + BLI_assert(cache_->mutex.is_cached()); + return cache_->data; + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 782056615be..2e1334066af 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -177,6 +177,7 @@ set(SRC BLI_bitmap_draw_2d.h BLI_blenlib.h BLI_bounds.hh + BLI_bounds_types.hh BLI_boxpack_2d.h BLI_buffer.h BLI_cache_mutex.hh @@ -306,6 +307,7 @@ set(SRC BLI_session_uuid.h BLI_set.hh BLI_set_slots.hh + BLI_shared_cache.hh BLI_simd.h BLI_smallhash.h BLI_sort.h diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc index 684fcdbff9e..b8ab3ba4917 100644 --- a/source/blender/editors/sculpt_paint/sculpt.cc +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -5240,12 +5240,14 @@ void SCULPT_update_object_bounding_box(Object *ob) void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags) { + using namespace blender; Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; ARegion *region = CTX_wm_region(C); MultiresModifierData *mmd = ss->multires.modifier; RegionView3D *rv3d = CTX_wm_region_view3d(C); + Mesh *mesh = static_cast(ob->data); if (rv3d) { /* Mark for faster 3D viewport redraws. */ @@ -5304,6 +5306,17 @@ void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags) ED_region_tag_redraw_partial(region, &r, true); } } + + if (update_flags & SCULPT_UPDATE_COORDS && !ss->shapekey_active) { + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { + /* When sculpting and changing the positions of a mesh, tag them as changed and update. */ + BKE_mesh_tag_coords_changed(mesh); + /* Update the mesh's bounds eagerly since the PBVH already has that information. */ + mesh->runtime->bounds_cache.ensure([&](Bounds &r_bounds) { + BKE_pbvh_bounding_box(ob->sculpt->pbvh, r_bounds.min, r_bounds.max); + }); + } + } } void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType update_flags) diff --git a/source/blender/makesdna/DNA_pointcloud_types.h b/source/blender/makesdna/DNA_pointcloud_types.h index 34c5d153165..ca61f6bbc41 100644 --- a/source/blender/makesdna/DNA_pointcloud_types.h +++ b/source/blender/makesdna/DNA_pointcloud_types.h @@ -10,10 +10,21 @@ #include "DNA_customdata_types.h" #ifdef __cplusplus -namespace blender::bke { +# include "BLI_math_vec_types.hh" +#endif + +#ifdef __cplusplus +namespace blender { +template class Span; +namespace bke { class AttributeAccessor; class MutableAttributeAccessor; -} // namespace blender::bke +class PointCloudRuntime; +} // namespace bke +} // namespace blender +using PointCloudRuntimeHandle = blender::bke::PointCloudRuntime; +#else +typedef struct PointCloudRuntimeHandle PointCloudRuntimeHandle; #endif #ifdef __cplusplus @@ -42,8 +53,15 @@ typedef struct PointCloud { #ifdef __cplusplus blender::bke::AttributeAccessor attributes() const; blender::bke::MutableAttributeAccessor attributes_for_write(); + + void tag_positions_changed(); + void tag_radii_changed(); + + bool bounds_min_max(blender::float3 &min, blender::float3 &max) const; #endif + PointCloudRuntimeHandle *runtime; + /* Draw Cache */ void *batch_cache; } PointCloud;