Geometry: Cache bounds min and max, share between data-blocks

Bounding box calculation can be a large in some situations, especially
instancing. This patch caches the min and max of the bounding box in
runtime data of meshes, point clouds, and curves, implementing part of
T96968.

Bounds are now calculated lazily-- only after they are tagged dirty.
Also, cached bounds are also shared when copying geometry data-blocks
that have equivalent data. When bounds are calculated on an evaluated
data-block, they are also accessible on the original, and the next
evaluated ID will also share them. A geometry will stop sharing bounds
as soon as its positions (or radii) are changed.

Just caching the bounds gave a 2-3x speedup with thousands of mesh
geometry instances in the viewport. Sharing the bounds can eliminate
recalculations entirely in cases like copying meshes in geometry nodes
or the selection paint brush in curves sculpt mode, which causes a
reevaluation but doesn't change the positions.

**Implementation**
The sharing is achieved with a `shared_ptr` that points to a cache mutex
(from D16419) and the cached bounds data. When geometries are copied,
the bounds are shared by default, and only "un-shared" when the bounds
are tagged dirty.

Point clouds have a new runtime struct to store this data. Functions
for tagging the data dirty are improved for added for point clouds
and improved for curves. A missing tag has also been fixed for mesh
sculpt mode.

**Future**
There are further improvements which can be worked on next
- Apply changes to volume objects and other types where it makes sense
- Continue cleanup changes described in T96968
- Apply shared cache design to more expensive data like triangulation
  or normals

Differential Revision: https://developer.blender.org/D16204
This commit is contained in:
Hans Goudey 2022-11-15 13:46:55 -06:00
parent b2d9716b4a
commit e8f4010611
Notes: blender-bot 2023-02-27 00:12:28 +01:00
Referenced by commit 511ac66dab, Mesh: Use shared cache for derived triangulation
Referenced by issue #103566, Regression: Wrong bounding box for curve object
Referenced by issue #103387, Set Curve Radius is Effecting Bounding Box of Curves
Referenced by issue #96968, Object data bounds improvements proposal
Referenced by issue #96894, Regression: Exported sculpt details are missing till you press clear custom split normals (e.g. after FBX export/import roundtrip)
Referenced by issue #92963, Instancing Performance Improvements
Referenced by pull request #105154, Fix #103387: Radius affects curves bounding box
Referenced by commit 97a8bb450c, Fix #103387: Radius affects curves bounding box
18 changed files with 296 additions and 100 deletions

View File

@ -11,6 +11,7 @@
#include <mutex>
#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<float3> 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<float3>> 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);

View File

@ -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<float3>> bounds_cache;
/** Lazily initialized SoA data from the #edit_mesh field in #Mesh. */
EditMeshData *edit_data = nullptr;

View File

@ -6,6 +6,15 @@
* \ingroup bke
* \brief General operations for point clouds.
*/
#ifdef __cplusplus
# include <mutex>
# 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<float3>> 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);

View File

@ -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<bke::CurvesGeometryRuntime>(__func__);
dst.runtime->type_counts = src.runtime->type_counts;
dst.runtime->bounds_cache = src.runtime->bounds_cache;
curves_dst->batch_cache = nullptr;
}

View File

@ -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<float3> positions, const float3 &translation)
{
@ -1006,25 +1009,28 @@ void CurvesGeometry::transform(const float4x4 &matrix)
this->tag_positions_changed();
}
static std::optional<bounds::MinMaxResult<float3>> curves_bounds(const CurvesGeometry &curves)
{
const Span<float3> positions = curves.positions();
const VArray<float> radii = curves.attributes().lookup_or_default<float>(
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::MinMaxResult<float3>> 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<float3> &r_bounds) {
const Span<float3> positions = this->evaluated_positions();
if (this->attributes().contains("radius")) {
const VArraySpan<float> radii = this->attributes().lookup<float>("radius");
Array<float> 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<float3> &bounds = this->runtime->bounds_cache.data();
min = math::min(bounds.min, min);
max = math::max(bounds.max, max);
return true;
}

View File

@ -122,7 +122,7 @@ bool BKE_editmesh_cache_calc_minmax(struct BMEditMesh *em,
if (bm->totvert) {
if (emd->vertexCos) {
Span<float3> vert_coords(reinterpret_cast<const float3 *>(emd->vertexCos), bm->totvert);
std::optional<bounds::MinMaxResult<float3>> bounds = bounds::min_max(vert_coords);
std::optional<Bounds<float3>> 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)));

View File

@ -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<blender::bke::CurvesGeometry *>(owner);
curves.tag_radii_changed();
}
static void tag_component_normals_changed(void *owner)
{
blender::bke::CurvesGeometry &curves = *static_cast<blender::bke::CurvesGeometry *>(owner);
@ -384,7 +390,7 @@ static ComponentAttributeProviders create_attribute_providers_for_curve()
point_access,
make_array_read_attribute<float>,
make_array_write_attribute<float>,
nullptr);
tag_component_radii_changed);
static BuiltinCustomDataLayerProvider id("id",
ATTR_DOMAIN_POINT,

View File

@ -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<PointCloud *>(owner);
points.tag_positions_changed();
}
static void tag_component_radius_changed(void *owner)
{
PointCloud &points = *static_cast<PointCloud *>(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<float3>,
make_array_write_attribute<float3>,
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<float>,
make_array_write_attribute<float>,
nullptr);
tag_component_radius_changed);
static BuiltinCustomDataLayerProvider id("id",
ATTR_DOMAIN_POINT,
CD_PROP_INT32,

View File

@ -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<bounds::MinMaxResult<float3>> 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;
}

View File

@ -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<MVert> verts = me->verts();
me->runtime->bounds_cache.ensure([me](Bounds<float3> &r_bounds) {
const Span<MVert> verts = me->verts();
r_bounds = threading::parallel_reduce(
verts.index_range(),
1024,
Bounds<float3>{float3(FLT_MAX), float3(-FLT_MAX)},
[verts](IndexRange range, const Bounds<float3> &init) {
Bounds<float3> result = init;
for (const int i : range) {
math::min_max(float3(verts[i].co), result.min, result.max);
}
return result;
},
[](const Bounds<float3> &a, const Bounds<float3> &b) {
return Bounds<float3>{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<float3> &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;
}

View File

@ -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)

View File

@ -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<blender::bounds::MinMaxResult<float3>> point_cloud_bounds(
const PointCloud &pointcloud)
{
blender::bke::AttributeAccessor attributes = pointcloud.attributes();
blender::VArraySpan<float3> positions = attributes.lookup_or_default<float3>(
POINTCLOUD_ATTR_POSITION, ATTR_DOMAIN_POINT, float3(0));
blender::VArray<float> radii = attributes.lookup_or_default<float>(
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<bounds::MinMaxResult<float3>> 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<float3> &r_bounds) {
const AttributeAccessor attributes = this->attributes();
const VArraySpan<float3> positions = attributes.lookup<float3>(POINTCLOUD_ATTR_POSITION);
if (attributes.contains(POINTCLOUD_ATTR_RADIUS)) {
const VArraySpan<float> radii = attributes.lookup<float>(POINTCLOUD_ATTR_RADIUS);
r_bounds = *bounds::min_max_with_radii(positions, radii);
}
else {
r_bounds = *bounds::min_max(positions);
}
});
const Bounds<float3> &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<PointCloud *>(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;

View File

@ -10,38 +10,34 @@
#include <optional>
#include "BLI_bounds_types.hh"
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
namespace blender::bounds {
template<typename T> struct MinMaxResult {
T min;
T max;
};
/**
* Find the smallest and largest values element-wise in the span.
*/
template<typename T> static std::optional<MinMaxResult<T>> min_max(Span<T> values)
template<typename T> static std::optional<Bounds<T>> min_max(Span<T> values)
{
if (values.is_empty()) {
return std::nullopt;
}
const MinMaxResult<T> init{values.first(), values.first()};
const Bounds<T> init{values.first(), values.first()};
return threading::parallel_reduce(
values.index_range(),
1024,
init,
[&](IndexRange range, const MinMaxResult<T> &init) {
MinMaxResult<T> result = init;
[&](IndexRange range, const Bounds<T> &init) {
Bounds<T> result = init;
for (const int i : range) {
math::min_max(values[i], result.min, result.max);
}
return result;
},
[](const MinMaxResult<T> &a, const MinMaxResult<T> &b) {
return MinMaxResult<T>{math::min(a.min, b.min), math::max(a.max, b.max)};
[](const Bounds<T> &a, const Bounds<T> &b) {
return Bounds<T>{math::min(a.min, b.min), math::max(a.max, b.max)};
});
}
@ -50,27 +46,27 @@ template<typename T> static std::optional<MinMaxResult<T>> min_max(Span<T> value
* first. The template type T is expected to have an addition operator implemented with RadiusT.
*/
template<typename T, typename RadiusT>
static std::optional<MinMaxResult<T>> min_max_with_radii(Span<T> values, Span<RadiusT> radii)
static std::optional<Bounds<T>> min_max_with_radii(Span<T> values, Span<RadiusT> radii)
{
BLI_assert(values.size() == radii.size());
if (values.is_empty()) {
return std::nullopt;
}
const MinMaxResult<T> init{values.first(), values.first()};
const Bounds<T> init{values.first(), values.first()};
return threading::parallel_reduce(
values.index_range(),
1024,
init,
[&](IndexRange range, const MinMaxResult<T> &init) {
MinMaxResult<T> result = init;
[&](IndexRange range, const Bounds<T> &init) {
Bounds<T> 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<T> &a, const MinMaxResult<T> &b) {
return MinMaxResult<T>{math::min(a.min, b.min), math::max(a.max, b.max)};
[](const Bounds<T> &a, const Bounds<T> &b) {
return Bounds<T>{math::min(a.min, b.min), math::max(a.max, b.max)};
});
}

View File

@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*/
namespace blender {
template<typename T> struct Bounds {
T min;
T max;
};
} // namespace blender

View File

@ -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<typename T> class SharedCache {
struct CacheData {
CacheMutex mutex;
T data;
};
std::shared_ptr<CacheData> cache_;
public:
SharedCache()
{
/* The cache should be allocated to trigger sharing of the cached data as early as possible. */
cache_ = std::make_shared<CacheData>();
}
/** 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<CacheData>();
}
}
/**
* If the cache is dirty, trigger its computation with the provided function which should set
* the proper data.
*/
void ensure(FunctionRef<void(T &data)> 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

View File

@ -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

View File

@ -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<Mesh *>(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<float3> &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)

View File

@ -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<typename T> 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;