Files
test/source/blender/blenkernel/intern/attribute_storage.cc
Hans Goudey 9c9695b103 Refactor: Move ReportList definition from DNA to blenkernel
Ever since [0], ReportList is not actually used in any DNA structs.
That makes sense, since reports are conceptually only necessary
at runtime. Move the definition of the struct to BKE_report.hh, and
fix a bunch of include errors where types were previously available
transitively. Besides adding some clarity, theoretically this change
could reduce compile times because of less header parsing.

[0]: 1bf6d8b0b9

Pull Request: https://projects.blender.org/blender/blender/pulls/138872
2025-07-24 15:59:52 +02:00

656 lines
22 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 "DNA_userdef_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 = {"geom.attribute"};
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;
}
};
Attribute::ArrayData Attribute::ArrayData::from_value(const GPointer &value,
const int64_t domain_size)
{
Attribute::ArrayData data{};
const CPPType &type = *value.type();
const void *value_ptr = value.get();
/* Prefer `calloc` to zeroing after allocation since it is faster. */
if (BLI_memory_is_zero(value_ptr, type.size)) {
data.data = MEM_calloc_arrayN_aligned(domain_size, type.size, type.alignment, __func__);
}
else {
data.data = MEM_malloc_arrayN_aligned(domain_size, type.size, type.alignment, __func__);
type.fill_construct_n(value_ptr, data.data, domain_size);
}
data.size = domain_size;
BLI_assert(type.is_trivially_destructible);
data.sharing_info = ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(data.data));
return data;
}
Attribute::ArrayData Attribute::ArrayData::from_default_value(const CPPType &type,
const int64_t domain_size)
{
return from_value(GPointer(type, type.default_value()), domain_size);
}
Attribute::ArrayData Attribute::ArrayData::from_uninitialized(const CPPType &type,
const int64_t domain_size)
{
Attribute::ArrayData data{};
data.data = MEM_malloc_arrayN_aligned(domain_size, type.size, type.alignment, __func__);
data.size = domain_size;
BLI_assert(type.is_trivially_destructible);
data.sharing_info = ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(data.data));
return data;
}
Attribute::ArrayData Attribute::ArrayData::from_constructed(const CPPType &type,
const int64_t domain_size)
{
Attribute::ArrayData data = Attribute::ArrayData::from_uninitialized(type, domain_size);
type.default_construct_n(data.data, domain_size);
return data;
}
Attribute::SingleData Attribute::SingleData::from_value(const GPointer &value)
{
Attribute::SingleData data{};
const CPPType &type = *value.type();
data.value = MEM_mallocN_aligned(type.size, type.alignment, __func__);
type.copy_construct(value.get(), data.value);
BLI_assert(type.is_trivially_destructible);
data.sharing_info = ImplicitSharingPtr<>(implicit_sharing::info_for_mem_free(data.value));
return data;
}
Attribute::SingleData Attribute::SingleData::from_default_value(const CPPType &type)
{
return from_value(GPointer(type, type.default_value()));
}
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);
}
}
void AttributeStorage::foreach_with_stop(FunctionRef<bool(Attribute &)> fn)
{
for (const std::unique_ptr<Attribute> &attribute : this->runtime->attributes) {
if (!fn(*attribute)) {
break;
}
}
}
void AttributeStorage::foreach_with_stop(FunctionRef<bool(const Attribute &)> fn) const
{
for (const std::unique_ptr<Attribute> &attribute : this->runtime->attributes) {
if (!fn(*attribute)) {
break;
}
}
}
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) {
BLI_assert(data->size == 0);
return data_;
}
if (data->sharing_info->is_mutable()) {
data->sharing_info->tag_ensured_mutable();
return data_;
}
const CPPType &type = attribute_type_to_cpp_type(type_);
ArrayData new_data = ArrayData::from_uninitialized(type, data->size);
type.copy_construct_n(data->data, new_data.data, data->size);
*data = std::move(new_data);
}
else if (auto *data = std::get_if<Attribute::SingleData>(&data_)) {
if (data->sharing_info->is_mutable()) {
data->sharing_info->tag_ensured_mutable();
return data_;
}
const CPPType &type = attribute_type_to_cpp_type(type_);
*data = SingleData::from_value(GPointer(type, data->value));
}
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);
}
int AttributeStorage::count() const
{
return this->runtime->attributes.size();
}
Attribute &AttributeStorage::at_index(int index)
{
return *this->runtime->attributes[index];
}
const Attribute &AttributeStorage::at_index(int index) const
{
return *this->runtime->attributes[index];
}
int AttributeStorage::index_of(StringRef name) const
{
return this->runtime->attributes.index_of_try_as(name);
}
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);
}
void AttributeStorage::rename(const StringRef old_name, std::string new_name)
{
/* The VectorSet must be rebuilt from scratch because the data used to create the hash is
* changed. */
const int index = this->runtime->attributes.index_of_try_as(old_name);
Vector<std::unique_ptr<Attribute>> old_vector = this->runtime->attributes.extract_vector();
old_vector[index]->name_ = std::move(new_name);
this->runtime->attributes.reserve(old_vector.size());
for (std::unique_ptr<Attribute> &attribute : old_vector) {
this->runtime->attributes.add_new(std::move(attribute));
}
}
void AttributeStorage::resize(const AttrDomain domain, const int64_t new_size)
{
this->foreach([&](Attribute &attr) {
if (attr.domain() != domain) {
return;
}
const CPPType &type = attribute_type_to_cpp_type(attr.data_type());
switch (attr.storage_type()) {
case bke::AttrStorageType::Array: {
const auto &data = std::get<bke::Attribute::ArrayData>(attr.data());
const int64_t old_size = data.size;
auto new_data = bke::Attribute::ArrayData::from_uninitialized(type, new_size);
type.copy_construct_n(data.data, new_data.data, std::min(old_size, new_size));
if (old_size < new_size) {
type.default_construct_n(POINTER_OFFSET(new_data.data, type.size * old_size),
new_size - old_size);
}
attr.assign_data(std::move(new_data));
}
case bke::AttrStorageType::Single: {
return;
}
}
});
}
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.size != 0 && !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);
BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(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);
BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(dna_attr.data); });
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);
}
}
/* 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,
AttributeStorage::BlendWriteData &write_data)
{
data.foreach([&](Attribute &attr) {
::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);
});
}
AttributeStorage::BlendWriteData::BlendWriteData(ResourceScope &scope)
: scope(scope), attributes(scope.construct<Vector<::Attribute, 16>>())
{
}
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