/* 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 fn) { for (const std::unique_ptr &attribute : this->runtime->attributes) { fn(*attribute); } } void AttributeStorage::foreach(FunctionRef fn) const { for (const std::unique_ptr &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(__func__, data, size, type); } AttrStorageType Attribute::storage_type() const { if (std::get_if(&data_)) { return AttrStorageType::Array; } if (std::get_if(&data_)) { return AttrStorageType::Single; } BLI_assert_unreachable(); return AttrStorageType::Array; } Attribute::DataVariant &Attribute::data_for_write() { if (auto *data = std::get_if(&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(&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(__func__); } AttributeStorage::AttributeStorage(const AttributeStorage &other) { this->dna_attributes = nullptr; this->dna_attributes_num = 0; this->runtime = MEM_new(__func__); this->runtime->attributes.reserve(other.runtime->attributes.size()); other.foreach([&](const Attribute &attribute) { this->runtime->attributes.add_new(std::make_unique(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(__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 *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 *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 ptr = std::make_unique(); 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(data)); return; case int8_t(AttrType::Int8): BLO_read_int8_array(&reader, size, reinterpret_cast(data)); return; case int8_t(AttrType::Int16_2D): BLO_read_int16_array(&reader, size * 2, reinterpret_cast(data)); return; case int8_t(AttrType::Int32): BLO_read_int32_array(&reader, size, reinterpret_cast(data)); return; case int8_t(AttrType::Int32_2D): BLO_read_int32_array(&reader, size * 2, reinterpret_cast(data)); return; case int8_t(AttrType::Float): BLO_read_float_array(&reader, size, reinterpret_cast(data)); return; case int8_t(AttrType::Float2): BLO_read_float_array(&reader, size * 2, reinterpret_cast(data)); return; case int8_t(AttrType::Float3): BLO_read_float3_array(&reader, size, reinterpret_cast(data)); return; case int8_t(AttrType::Float4x4): BLO_read_float_array(&reader, size * 16, reinterpret_cast(data)); return; case int8_t(AttrType::ColorByte): BLO_read_uint8_array(&reader, size * 4, reinterpret_cast(data)); return; case int8_t(AttrType::ColorFloat): BLO_read_float_array(&reader, size * 4, reinterpret_cast(data)); return; case int8_t(AttrType::Quaternion): BLO_read_float_array(&reader, size * 4, reinterpret_cast(data)); return; case int8_t(AttrType::String): BLO_read_struct_array( &reader, MStringProperty, size, reinterpret_cast(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(func, *data, size, cpp_type); }); } static std::optional 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 &attr : this->runtime->attributes) { const CPPType &type = attribute_type_to_cpp_type(attr->data_type()); if (const auto *data = std::get_if(&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(&attr->data())) { memory.add_shared(data->sharing_info.get(), [&](MemoryCounter &shared_memory) { shared_memory.add(type.size); }); } } } static std::optional 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(__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 domain = read_attr_domain(dna_attr.domain); if (!domain) { continue; } std::optional data = read_attr_data( reader, dna_attr.storage_type, dna_attr.data_type, dna_attr); if (!data) { continue; } std::unique_ptr attribute = std::make_unique(); 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(data)); break; case AttrType::Int8: BLO_write_int8_array(&writer, size, static_cast(data)); break; case AttrType::Int16_2D: BLO_write_int16_array(&writer, size * 2, static_cast(data)); break; case AttrType::Int32: BLO_write_int32_array(&writer, size, static_cast(data)); break; case AttrType::Int32_2D: BLO_write_int32_array(&writer, size * 2, static_cast(data)); break; case AttrType::Float: BLO_write_float_array(&writer, size, static_cast(data)); break; case AttrType::Float2: BLO_write_float_array(&writer, size * 2, static_cast(data)); break; case AttrType::Float3: BLO_write_float3_array(&writer, size, static_cast(data)); break; case AttrType::Float4x4: BLO_write_float_array(&writer, size * 16, static_cast(data)); break; case AttrType::ColorByte: BLO_write_uint8_array(&writer, size * 4, static_cast(data)); break; case AttrType::ColorFloat: BLO_write_float_array(&writer, size * 4, static_cast(data)); break; case AttrType::Quaternion: BLO_write_float_array(&writer, size * 4, static_cast(data)); break; case AttrType::String: BLO_write_struct_array( &writer, MStringProperty, size, static_cast(data)); break; } } void attribute_storage_blend_write_prepare( AttributeStorage &data, const Map *> &layers_to_write, AttributeStorage::BlendWriteData &write_data) { Set all_names_written; for (Vector *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(&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(&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(&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