Files
test/source/blender/blenkernel/intern/attribute_storage.cc
Hans Goudey 2673224ee5 Attributes: Implement memory counter for AttributeStorage
Similar to CustomData_count_memory.
2025-05-20 10:21:23 -04:00

557 lines
19 KiB
C++

/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "CLG_log.h"
#include "BLI_assert.h"
#include "BLI_implicit_sharing.hh"
#include "BLI_memory_counter.hh"
#include "BLI_resource_scope.hh"
#include "BLI_string_utils.hh"
#include "BLI_vector_set.hh"
#include "BLO_read_write.hh"
#include "DNA_attribute_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_attribute_storage.hh"
#include "BKE_attribute_storage_blend_write.hh"
static CLG_LogRef LOG = {"bke.attribute_storage"};
namespace blender::bke {
class ArrayDataImplicitSharing : public ImplicitSharingInfo {
private:
void *data_;
int64_t size_;
const CPPType &type_;
/* This struct could also store caches about the array data, like the min and max values. */
public:
ArrayDataImplicitSharing(void *data, const int64_t size, const CPPType &type)
: ImplicitSharingInfo(), data_(data), size_(size), type_(type)
{
}
private:
void delete_self_with_data() override
{
if (data_ != nullptr) {
type_.destruct_n(data_, size_);
MEM_freeN(data_);
}
MEM_delete(this);
}
void delete_data_only() override
{
type_.destruct_n(data_, size_);
MEM_freeN(data_);
data_ = nullptr;
size_ = 0;
}
};
void AttributeStorage::foreach(FunctionRef<void(Attribute &)> fn)
{
for (const std::unique_ptr<Attribute> &attribute : this->runtime->attributes) {
fn(*attribute);
}
}
void AttributeStorage::foreach(FunctionRef<void(const Attribute &)> fn) const
{
for (const std::unique_ptr<Attribute> &attribute : this->runtime->attributes) {
fn(*attribute);
}
}
static ImplicitSharingInfo *create_sharing_info_for_array(void *data,
const int64_t size,
const CPPType &type)
{
return MEM_new<ArrayDataImplicitSharing>(__func__, data, size, type);
}
AttrStorageType Attribute::storage_type() const
{
if (std::get_if<Attribute::ArrayData>(&data_)) {
return AttrStorageType::Array;
}
if (std::get_if<Attribute::SingleData>(&data_)) {
return AttrStorageType::Single;
}
BLI_assert_unreachable();
return AttrStorageType::Array;
}
Attribute::DataVariant &Attribute::data_for_write()
{
if (auto *data = std::get_if<Attribute::ArrayData>(&data_)) {
if (data->sharing_info->is_mutable()) {
data->sharing_info->tag_ensured_mutable();
return data_;
}
const CPPType &cpp_type = attribute_type_to_cpp_type(type_);
void *new_data = MEM_malloc_arrayN_aligned(
data->size, cpp_type.size, cpp_type.alignment, __func__);
cpp_type.copy_construct_n(data->data, new_data, data->size);
data->data = new_data;
data->sharing_info = ImplicitSharingPtr<>(
create_sharing_info_for_array(data->data, data->size, cpp_type));
}
else if (std::get_if<Attribute::SingleData>(&data_)) {
/* Not yet implemented because #SingleData isn't used at runtime yet. */
BLI_assert_unreachable();
}
return data_;
}
AttributeStorage::AttributeStorage()
{
this->dna_attributes = nullptr;
this->dna_attributes_num = 0;
this->runtime = MEM_new<AttributeStorageRuntime>(__func__);
}
AttributeStorage::AttributeStorage(const AttributeStorage &other)
{
this->dna_attributes = nullptr;
this->dna_attributes_num = 0;
this->runtime = MEM_new<AttributeStorageRuntime>(__func__);
this->runtime->attributes.reserve(other.runtime->attributes.size());
other.foreach([&](const Attribute &attribute) {
this->runtime->attributes.add_new(std::make_unique<Attribute>(attribute));
});
}
AttributeStorage &AttributeStorage::operator=(const AttributeStorage &other)
{
if (this == &other) {
return *this;
}
std::destroy_at(this);
new (this) AttributeStorage(other);
return *this;
}
AttributeStorage::AttributeStorage(AttributeStorage &&other)
{
this->dna_attributes = nullptr;
this->dna_attributes_num = 0;
this->runtime = MEM_new<AttributeStorageRuntime>(__func__, std::move(*other.runtime));
}
AttributeStorage &AttributeStorage::operator=(AttributeStorage &&other)
{
if (this == &other) {
return *this;
}
std::destroy_at(this);
new (this) AttributeStorage(std::move(other));
return *this;
}
AttributeStorage::~AttributeStorage()
{
MEM_delete(this->runtime);
}
const Attribute *AttributeStorage::lookup(const StringRef name) const
{
const std::unique_ptr<blender::bke::Attribute> *attribute =
this->runtime->attributes.lookup_key_ptr_as(name);
if (!attribute) {
return nullptr;
}
return attribute->get();
}
Attribute *AttributeStorage::lookup(const StringRef name)
{
const std::unique_ptr<blender::bke::Attribute> *attribute =
this->runtime->attributes.lookup_key_ptr_as(name);
if (!attribute) {
return nullptr;
}
return attribute->get();
}
Attribute &AttributeStorage::add(std::string name,
const AttrDomain domain,
const AttrType data_type,
Attribute::DataVariant data)
{
BLI_assert(!this->lookup(name));
std::unique_ptr<Attribute> ptr = std::make_unique<Attribute>();
Attribute &attribute = *ptr;
attribute.name_ = std::move(name);
attribute.domain_ = domain;
attribute.type_ = data_type;
attribute.data_ = std::move(data);
this->runtime->attributes.add_new(std::move(ptr));
return attribute;
}
bool AttributeStorage::remove(const StringRef name)
{
return this->runtime->attributes.remove_as(name);
}
std::string AttributeStorage::unique_name_calc(const StringRef name)
{
return BLI_uniquename_cb(
[&](const StringRef check_name) { return this->lookup(check_name) != nullptr; }, '.', name);
}
static void read_array_data(BlendDataReader &reader,
const int8_t dna_attr_type,
const int64_t size,
void **data)
{
switch (dna_attr_type) {
case int8_t(AttrType::Bool):
static_assert(sizeof(bool) == sizeof(int8_t));
BLO_read_int8_array(&reader, size, reinterpret_cast<int8_t **>(data));
return;
case int8_t(AttrType::Int8):
BLO_read_int8_array(&reader, size, reinterpret_cast<int8_t **>(data));
return;
case int8_t(AttrType::Int16_2D):
BLO_read_int16_array(&reader, size * 2, reinterpret_cast<int16_t **>(data));
return;
case int8_t(AttrType::Int32):
BLO_read_int32_array(&reader, size, reinterpret_cast<int32_t **>(data));
return;
case int8_t(AttrType::Int32_2D):
BLO_read_int32_array(&reader, size * 2, reinterpret_cast<int32_t **>(data));
return;
case int8_t(AttrType::Float):
BLO_read_float_array(&reader, size, reinterpret_cast<float **>(data));
return;
case int8_t(AttrType::Float2):
BLO_read_float_array(&reader, size * 2, reinterpret_cast<float **>(data));
return;
case int8_t(AttrType::Float3):
BLO_read_float3_array(&reader, size, reinterpret_cast<float **>(data));
return;
case int8_t(AttrType::Float4x4):
BLO_read_float_array(&reader, size * 16, reinterpret_cast<float **>(data));
return;
case int8_t(AttrType::ColorByte):
BLO_read_uint8_array(&reader, size * 4, reinterpret_cast<uint8_t **>(data));
return;
case int8_t(AttrType::ColorFloat):
BLO_read_float_array(&reader, size * 4, reinterpret_cast<float **>(data));
return;
case int8_t(AttrType::Quaternion):
BLO_read_float_array(&reader, size * 4, reinterpret_cast<float **>(data));
return;
case int8_t(AttrType::String):
BLO_read_struct_array(
&reader, MStringProperty, size, reinterpret_cast<MStringProperty **>(data));
return;
default:
*data = nullptr;
return;
}
}
static void read_shared_array(BlendDataReader &reader,
const int8_t dna_attr_type,
const int64_t size,
void **data,
const ImplicitSharingInfo **sharing_info)
{
const char *func = __func__;
*sharing_info = BLO_read_shared(&reader, data, [&]() -> const ImplicitSharingInfo * {
read_array_data(reader, dna_attr_type, size, data);
if (*data == nullptr) {
return nullptr;
}
const CPPType &cpp_type = attribute_type_to_cpp_type(AttrType(dna_attr_type));
return MEM_new<ArrayDataImplicitSharing>(func, *data, size, cpp_type);
});
}
static std::optional<Attribute::DataVariant> read_attr_data(BlendDataReader &reader,
const int8_t dna_storage_type,
const int8_t dna_attr_type,
::Attribute &dna_attr)
{
switch (dna_storage_type) {
case int8_t(AttrStorageType::Array): {
BLO_read_struct(&reader, AttributeArray, &dna_attr.data);
auto &data = *static_cast<::AttributeArray *>(dna_attr.data);
read_shared_array(reader, dna_attr_type, data.size, &data.data, &data.sharing_info);
if (!data.data) {
return std::nullopt;
}
return Attribute::ArrayData{data.data, data.size, ImplicitSharingPtr<>(data.sharing_info)};
}
case int8_t(AttrStorageType::Single): {
BLO_read_struct(&reader, AttributeSingle, &dna_attr.data);
auto &data = *static_cast<::AttributeSingle *>(dna_attr.data);
read_shared_array(reader, dna_attr_type, 1, &data.data, &data.sharing_info);
if (!data.data) {
return std::nullopt;
}
return Attribute::SingleData{data.data, ImplicitSharingPtr<>(data.sharing_info)};
}
default:
return std::nullopt;
}
}
void AttributeStorage::count_memory(MemoryCounter &memory) const
{
for (const std::unique_ptr<Attribute> &attr : this->runtime->attributes) {
const CPPType &type = attribute_type_to_cpp_type(attr->data_type());
if (const auto *data = std::get_if<Attribute::ArrayData>(&attr->data())) {
memory.add_shared(data->sharing_info.get(), [&](MemoryCounter &shared_memory) {
shared_memory.add(data->size * type.size);
});
}
else if (const auto *data = std::get_if<Attribute::SingleData>(&attr->data())) {
memory.add_shared(data->sharing_info.get(),
[&](MemoryCounter &shared_memory) { shared_memory.add(type.size); });
}
}
}
static std::optional<AttrDomain> read_attr_domain(const int8_t dna_domain)
{
switch (dna_domain) {
case int8_t(AttrDomain::Point):
case int8_t(AttrDomain::Edge):
case int8_t(AttrDomain::Face):
case int8_t(AttrDomain::Corner):
case int8_t(AttrDomain::Curve):
case int8_t(AttrDomain::Instance):
case int8_t(AttrDomain::Layer):
return AttrDomain(dna_domain);
default:
return std::nullopt;
}
}
void AttributeStorage::blend_read(BlendDataReader &reader)
{
this->runtime = MEM_new<AttributeStorageRuntime>(__func__);
this->runtime->attributes.reserve(this->dna_attributes_num);
BLO_read_struct_array(&reader, ::Attribute, this->dna_attributes_num, &this->dna_attributes);
for (const int i : IndexRange(this->dna_attributes_num)) {
::Attribute &dna_attr = this->dna_attributes[i];
BLO_read_string(&reader, &dna_attr.name);
const std::optional<AttrDomain> domain = read_attr_domain(dna_attr.domain);
if (!domain) {
continue;
}
std::optional<Attribute::DataVariant> data = read_attr_data(
reader, dna_attr.storage_type, dna_attr.data_type, dna_attr);
if (!data) {
continue;
}
std::unique_ptr<Attribute> attribute = std::make_unique<Attribute>();
attribute->name_ = dna_attr.name;
attribute->domain_ = *domain;
attribute->type_ = AttrType(dna_attr.data_type);
attribute->data_ = std::move(*data);
if (!this->runtime->attributes.add(std::move(attribute))) {
CLOG_ERROR(&LOG, "Ignoring attribute with duplicate name: \"%s\"", dna_attr.name);
}
MEM_SAFE_FREE(dna_attr.name);
MEM_SAFE_FREE(dna_attr.data);
}
/* These fields are not used at runtime. */
MEM_SAFE_FREE(this->dna_attributes);
this->dna_attributes_num = 0;
}
static void write_array_data(BlendWriter &writer,
const AttrType data_type,
const void *data,
const int64_t size)
{
switch (data_type) {
case AttrType::Bool:
static_assert(sizeof(bool) == sizeof(int8_t));
BLO_write_int8_array(&writer, size, static_cast<const int8_t *>(data));
break;
case AttrType::Int8:
BLO_write_int8_array(&writer, size, static_cast<const int8_t *>(data));
break;
case AttrType::Int16_2D:
BLO_write_int16_array(&writer, size * 2, static_cast<const int16_t *>(data));
break;
case AttrType::Int32:
BLO_write_int32_array(&writer, size, static_cast<const int32_t *>(data));
break;
case AttrType::Int32_2D:
BLO_write_int32_array(&writer, size * 2, static_cast<const int32_t *>(data));
break;
case AttrType::Float:
BLO_write_float_array(&writer, size, static_cast<const float *>(data));
break;
case AttrType::Float2:
BLO_write_float_array(&writer, size * 2, static_cast<const float *>(data));
break;
case AttrType::Float3:
BLO_write_float3_array(&writer, size, static_cast<const float *>(data));
break;
case AttrType::Float4x4:
BLO_write_float_array(&writer, size * 16, static_cast<const float *>(data));
break;
case AttrType::ColorByte:
BLO_write_uint8_array(&writer, size * 4, static_cast<const uint8_t *>(data));
break;
case AttrType::ColorFloat:
BLO_write_float_array(&writer, size * 4, static_cast<const float *>(data));
break;
case AttrType::Quaternion:
BLO_write_float_array(&writer, size * 4, static_cast<const float *>(data));
break;
case AttrType::String:
BLO_write_struct_array(
&writer, MStringProperty, size, static_cast<const MStringProperty *>(data));
break;
}
}
void attribute_storage_blend_write_prepare(
AttributeStorage &data,
const Map<AttrDomain, Vector<CustomDataLayer, 16> *> &layers_to_write,
AttributeStorage::BlendWriteData &write_data)
{
Set<std::string, 16> all_names_written;
for (Vector<CustomDataLayer, 16> *const layers : layers_to_write.values()) {
for (const CustomDataLayer &layer : *layers) {
all_names_written.add(layer.name);
}
}
data.foreach([&](Attribute &attr) {
if (!U.experimental.use_attribute_storage_write) {
/* In version 4.5, all attribute data is written in the #CustomData format (at least when the
* debug option is not enabled), so the #Attribute needs to be converted to a
* #CustomDataLayer in the proper list. This is only relevant when #AttributeStorage is
* actually used at runtime.
*
* When removing this option to always write the new format in 5.0, #BLENDER_FILE_MIN_VERSION
* must be increased. */
if (const std::optional data_type = attr_type_to_custom_data_type(attr.data_type())) {
if (const auto *array_data = std::get_if<Attribute::ArrayData>(&attr.data())) {
CustomDataLayer layer{};
layer.type = *data_type;
layer.data = array_data->data;
layer.sharing_info = array_data->sharing_info.get();
/* Because the #Attribute::name_ `std::string` has no length limit (unlike
* #CustomDataLayer::name), we have to manually make the name unique in case it exceeds
* the limit. */
BLI_uniquename_cb(
[&](const StringRefNull name) { return all_names_written.contains(name); },
attr.name().c_str(),
'.',
layer.name,
MAX_CUSTOMDATA_LAYER_NAME);
all_names_written.add(layer.name);
layers_to_write.lookup(attr.domain())->append(layer);
}
}
return;
}
/* Names within an AttributeStorage are unique. */
all_names_written.add(attr.name());
::Attribute attribute_dna{};
attribute_dna.name = attr.name().c_str();
attribute_dna.data_type = int16_t(attr.data_type());
attribute_dna.domain = int8_t(attr.domain());
attribute_dna.storage_type = int8_t(attr.storage_type());
/* The idea is to use a separate DNA struct for each #AttrStorageType. They each need to have a
* unique address (while writing a specific ID anyway) in order to be identified when
* reading the file, so we add them to the resource scope which outlives this function call.
* Using a #ResourceScope is a simple way to get pointer stability when adding every new data
* struct without the cost of many small allocations or unnecessary overhead of storing a full
* array for every storage type. */
if (const auto *data = std::get_if<Attribute::ArrayData>(&attr.data())) {
auto &array_dna = write_data.scope.construct<::AttributeArray>();
array_dna.data = data->data;
array_dna.sharing_info = data->sharing_info.get();
array_dna.size = data->size;
attribute_dna.data = &array_dna;
}
else if (const auto *data = std::get_if<Attribute::SingleData>(&attr.data())) {
auto &single_dna = write_data.scope.construct<::AttributeSingle>();
single_dna.data = data->value;
single_dna.sharing_info = data->sharing_info.get();
attribute_dna.data = &single_dna;
}
write_data.attributes.append(attribute_dna);
});
}
static void write_shared_array(BlendWriter &writer,
const AttrType data_type,
const void *data,
const int64_t size,
const ImplicitSharingInfo &sharing_info)
{
const CPPType &cpp_type = attribute_type_to_cpp_type(data_type);
BLO_write_shared(&writer, data, cpp_type.size * size, &sharing_info, [&]() {
write_array_data(writer, data_type, data, size);
});
}
void AttributeStorage::blend_write(BlendWriter &writer,
const AttributeStorage::BlendWriteData &write_data)
{
/* Use string argument to avoid confusion with the C++ class with the same name. */
BLO_write_struct_array_by_name(
&writer, "Attribute", write_data.attributes.size(), write_data.attributes.data());
for (const ::Attribute &attr_dna : write_data.attributes) {
BLO_write_string(&writer, attr_dna.name);
switch (AttrStorageType(attr_dna.storage_type)) {
case AttrStorageType::Single: {
::AttributeSingle *single_dna = static_cast<::AttributeSingle *>(attr_dna.data);
BLO_write_struct(&writer, AttributeSingle, single_dna);
write_shared_array(
writer, AttrType(attr_dna.data_type), single_dna->data, 1, *single_dna->sharing_info);
break;
}
case AttrStorageType::Array: {
::AttributeArray *array_dna = static_cast<::AttributeArray *>(attr_dna.data);
BLO_write_struct(&writer, AttributeArray, array_dna);
write_shared_array(writer,
AttrType(attr_dna.data_type),
array_dna->data,
array_dna->size,
*array_dna->sharing_info);
break;
}
}
}
this->dna_attributes = nullptr;
this->dna_attributes_num = 0;
}
} // namespace blender::bke