WIP: use generic copy-on-write system to avoid unnecessary data copies #104470

Closed
Jacques Lucke wants to merge 50 commits from JacquesLucke/blender:temp-copy-on-write-customdata into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
8 changed files with 74 additions and 26 deletions
Showing only changes of commit 6065a378cf - Show all commits

View File

@ -10,6 +10,24 @@
namespace blender::bke {
class AnonymousAttributeID;
class AnonymousAttributeIDCOW : public bCopyOnWrite {
private:
AnonymousAttributeID *id_;
public:
AnonymousAttributeIDCOW(AnonymousAttributeID *id) : bCopyOnWrite(1), id_(id)
{
}
private:
void delete_self_with_data() override
{
MEM_delete(id_);
}
};
/**
* An #AnonymousAttributeID contains information about a specific anonymous attribute.
* Like normal attributes, anonymous attributes are also identified by their name, so one should
@ -34,7 +52,7 @@ namespace blender::bke {
*/
class AnonymousAttributeID {
private:
bCopyOnWrite cow_;
AnonymousAttributeIDCOW cow_;
protected:
std::string name_;

View File

@ -41,6 +41,19 @@ class Instances;
class GeometryComponent;
class GeometryComponentCOW : public bCopyOnWrite {
private:
GeometryComponent *component_;
public:
GeometryComponentCOW(GeometryComponent *component) : bCopyOnWrite(1), component_(component)
{
}
private:
void delete_self_with_data() override;
};
/**
* This is the base class for specialized geometry component types. A geometry component handles
* a user count to allow avoiding duplication when it is wrapped with #UserCounter. It also handles
@ -49,7 +62,7 @@ class GeometryComponent;
class GeometryComponent {
private:
GeometryComponentType type_;
bCopyOnWrite cow_;
GeometryComponentCOW cow_;
public:
GeometryComponent(GeometryComponentType type);

View File

@ -4,8 +4,7 @@
namespace blender::bke {
AnonymousAttributeID::AnonymousAttributeID()
: cow_(1, this, [this](const bCopyOnWrite * /*cow*/) { MEM_delete(this); })
AnonymousAttributeID::AnonymousAttributeID() : cow_(this)
{
}

View File

@ -2361,14 +2361,31 @@ CustomData CustomData_shallow_copy_remove_non_bmesh_attributes(const CustomData
return dst;
}
class CustomDataLayerCOW : public bCopyOnWrite {
private:
const void *data_;
int totelem_;
eCustomDataType type_;
public:
CustomDataLayerCOW(const void *data, const int totelem, const eCustomDataType type)
: bCopyOnWrite(1), data_(data), totelem_(totelem), type_(type)
{
}
private:
void delete_self_with_data() override
{
free_layer_data(type_, data_, totelem_);
MEM_delete(this);
}
};
static bCopyOnWrite *make_cow_for_array(const eCustomDataType type,
const void *data,
const int totelem)
{
return MEM_new<bCopyOnWrite>(__func__, 1, data, [type, totelem](const bCopyOnWrite *cow) {
free_layer_data(type, cow->data(), totelem);
MEM_delete(cow);
});
return MEM_new<CustomDataLayerCOW>(__func__, data, totelem, type);
}
static void ensure_layer_data_is_mutable(CustomDataLayer &layer, const int totelem)
@ -5119,7 +5136,7 @@ void CustomData_blend_write(BlendWriter *writer,
for (const CustomDataLayer &layer : layers_to_write) {
if (BLO_write_is_undo(writer) && layer.cow != nullptr) {
BLO_write_cow(writer, layer.cow);
BLO_write_cow(writer, layer.data, layer.cow);
continue;
}
switch (layer.type) {

View File

@ -35,12 +35,16 @@ using blender::Vector;
using blender::bke::InstanceReference;
using blender::bke::Instances;
void GeometryComponentCOW::delete_self_with_data()
{
delete component_;
}
/* -------------------------------------------------------------------- */
/** \name Geometry Component
* \{ */
GeometryComponent::GeometryComponent(GeometryComponentType type)
: type_(type), cow_(1, this, [this](const bCopyOnWrite * /*cow*/) { delete this; })
GeometryComponent::GeometryComponent(GeometryComponentType type) : type_(type), cow_(this)
{
}

View File

@ -30,12 +30,9 @@ struct bCopyOnWrite : blender::NonCopyable, blender::NonMovable {
using DeleteFn = std::function<void(const bCopyOnWrite *cow)>;
mutable std::atomic<int> users_;
const void *data_;
DeleteFn delete_fn_;
public:
bCopyOnWrite(const int initial_users, const void *data, DeleteFn delete_fn)
: users_(initial_users), data_(data), delete_fn_(std::move(delete_fn))
bCopyOnWrite(const int initial_users) : users_(initial_users)
{
}
@ -44,11 +41,6 @@ struct bCopyOnWrite : blender::NonCopyable, blender::NonMovable {
BLI_assert(this->is_mutable());
}
const void *data() const
{
return data_;
}
bool is_shared() const
{
return users_.load(std::memory_order_relaxed) >= 2;
@ -64,16 +56,18 @@ struct bCopyOnWrite : blender::NonCopyable, blender::NonMovable {
users_.fetch_add(1, std::memory_order_relaxed);
}
bool user_remove_and_delete_if_last() const
void user_remove_and_delete_if_last() const
{
const int old_user_count = users_.fetch_sub(1, std::memory_order_relaxed);
BLI_assert(old_user_count >= 1);
const bool was_last_user = old_user_count == 1;
if (was_last_user) {
delete_fn_(this);
const_cast<bCopyOnWrite *>(this)->delete_self_with_data();
}
return was_last_user;
}
private:
virtual void delete_self_with_data() = 0;
};
namespace blender {

View File

@ -186,7 +186,7 @@ void BLO_write_string(BlendWriter *writer, const char *data_ptr);
* copied. A free-function has to be provided, which is called when the undo step is freed and no
* one else references the data anymore.
*/
void BLO_write_cow(BlendWriter *writer, const bCopyOnWrite *cow);
void BLO_write_cow(BlendWriter *writer, const void *data_ptr, const bCopyOnWrite *cow);
#endif
/**

View File

@ -1687,14 +1687,17 @@ bool BLO_write_is_undo(BlendWriter *writer)
return writer->wd->use_memfile;
}
void BLO_write_cow(BlendWriter *writer, const bCopyOnWrite *cow)
void BLO_write_cow(BlendWriter *writer, const void *data_ptr, const bCopyOnWrite *cow)
{
BLI_assert(writer->wd->use_memfile);
if (data_ptr == nullptr) {
return;
}
MemFile &memfile = *writer->wd->mem.written_memfile;
if (memfile.cow_storage == nullptr) {
memfile.cow_storage = MEM_new<MemFileCowStorage>(__func__);
}
if (memfile.cow_storage->map.add(cow->data(), cow)) {
if (memfile.cow_storage->map.add(data_ptr, cow)) {
cow->user_add();
}
}