Blender V4.5
attribute_storage.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "CLG_log.h"
6
7#include "BLI_assert.h"
10#include "BLI_resource_scope.hh"
11#include "BLI_string_utils.hh"
12#include "BLI_vector_set.hh"
13
14#include "BLO_read_write.hh"
15
16#include "DNA_attribute_types.h"
17#include "DNA_meshdata_types.h"
18
19#include "BKE_attribute.hh"
23
24static CLG_LogRef LOG = {"bke.attribute_storage"};
25
26namespace blender::bke {
27
29 private:
30 void *data_;
31 int64_t size_;
32 const CPPType &type_;
33 /* This struct could also store caches about the array data, like the min and max values. */
34
35 public:
37 : ImplicitSharingInfo(), data_(data), size_(size), type_(type)
38 {
39 }
40
41 private:
42 void delete_self_with_data() override
43 {
44 if (data_ != nullptr) {
45 type_.destruct_n(data_, size_);
46 MEM_freeN(data_);
47 }
48 MEM_delete(this);
49 }
50
51 void delete_data_only() override
52 {
53 type_.destruct_n(data_, size_);
54 MEM_freeN(data_);
55 data_ = nullptr;
56 size_ = 0;
57 }
58};
59
61{
62 for (const std::unique_ptr<Attribute> &attribute : this->runtime->attributes) {
63 fn(*attribute);
64 }
65}
67{
68 for (const std::unique_ptr<Attribute> &attribute : this->runtime->attributes) {
69 fn(*attribute);
70 }
71}
72
74 const int64_t size,
75 const CPPType &type)
76{
77 return MEM_new<ArrayDataImplicitSharing>(__func__, data, size, type);
78}
79
81{
82 if (std::get_if<Attribute::ArrayData>(&data_)) {
84 }
85 if (std::get_if<Attribute::SingleData>(&data_)) {
87 }
90}
91
93{
94 if (auto *data = std::get_if<Attribute::ArrayData>(&data_)) {
95 if (data->sharing_info->is_mutable()) {
96 data->sharing_info->tag_ensured_mutable();
97 return data_;
98 }
99
100 const CPPType &cpp_type = attribute_type_to_cpp_type(type_);
101 void *new_data = MEM_malloc_arrayN_aligned(
102 data->size, cpp_type.size, cpp_type.alignment, __func__);
103 cpp_type.copy_construct_n(data->data, new_data, data->size);
104
105 data->data = new_data;
106 data->sharing_info = ImplicitSharingPtr<>(
107 create_sharing_info_for_array(data->data, data->size, cpp_type));
108 }
109 else if (std::get_if<Attribute::SingleData>(&data_)) {
110 /* Not yet implemented because #SingleData isn't used at runtime yet. */
112 }
113 return data_;
114}
115
117{
118 this->dna_attributes = nullptr;
119 this->dna_attributes_num = 0;
120 this->runtime = MEM_new<AttributeStorageRuntime>(__func__);
121}
122
124{
125 this->dna_attributes = nullptr;
126 this->dna_attributes_num = 0;
127 this->runtime = MEM_new<AttributeStorageRuntime>(__func__);
128 this->runtime->attributes.reserve(other.runtime->attributes.size());
129 other.foreach([&](const Attribute &attribute) {
130 this->runtime->attributes.add_new(std::make_unique<Attribute>(attribute));
131 });
132}
133
135{
136 if (this == &other) {
137 return *this;
138 }
139 std::destroy_at(this);
140 new (this) AttributeStorage(other);
141 return *this;
142}
143
145{
146 this->dna_attributes = nullptr;
147 this->dna_attributes_num = 0;
148 this->runtime = MEM_new<AttributeStorageRuntime>(__func__, std::move(*other.runtime));
149}
150
152{
153 if (this == &other) {
154 return *this;
155 }
156 std::destroy_at(this);
157 new (this) AttributeStorage(std::move(other));
158 return *this;
159}
160
162{
163 MEM_delete(this->runtime);
164}
165
167{
168 const std::unique_ptr<blender::bke::Attribute> *attribute =
169 this->runtime->attributes.lookup_key_ptr_as(name);
170 if (!attribute) {
171 return nullptr;
172 }
173 return attribute->get();
174}
175
177{
178 const std::unique_ptr<blender::bke::Attribute> *attribute =
179 this->runtime->attributes.lookup_key_ptr_as(name);
180 if (!attribute) {
181 return nullptr;
182 }
183 return attribute->get();
184}
185
187 const AttrDomain domain,
188 const AttrType data_type,
190{
191 BLI_assert(!this->lookup(name));
192 std::unique_ptr<Attribute> ptr = std::make_unique<Attribute>();
193 Attribute &attribute = *ptr;
194 attribute.name_ = std::move(name);
195 attribute.domain_ = domain;
196 attribute.type_ = data_type;
197 attribute.data_ = std::move(data);
198 this->runtime->attributes.add_new(std::move(ptr));
199 return attribute;
200}
201
203{
204 return this->runtime->attributes.remove_as(name);
205}
206
208{
209 return BLI_uniquename_cb(
210 [&](const StringRef check_name) { return this->lookup(check_name) != nullptr; }, '.', name);
211}
212
214 const int8_t dna_attr_type,
215 const int64_t size,
216 void **data)
217{
218 switch (dna_attr_type) {
219 case int8_t(AttrType::Bool):
220 static_assert(sizeof(bool) == sizeof(int8_t));
221 BLO_read_int8_array(&reader, size, reinterpret_cast<int8_t **>(data));
222 return;
223 case int8_t(AttrType::Int8):
224 BLO_read_int8_array(&reader, size, reinterpret_cast<int8_t **>(data));
225 return;
226 case int8_t(AttrType::Int16_2D):
227 BLO_read_int16_array(&reader, size * 2, reinterpret_cast<int16_t **>(data));
228 return;
229 case int8_t(AttrType::Int32):
230 BLO_read_int32_array(&reader, size, reinterpret_cast<int32_t **>(data));
231 return;
232 case int8_t(AttrType::Int32_2D):
233 BLO_read_int32_array(&reader, size * 2, reinterpret_cast<int32_t **>(data));
234 return;
235 case int8_t(AttrType::Float):
236 BLO_read_float_array(&reader, size, reinterpret_cast<float **>(data));
237 return;
238 case int8_t(AttrType::Float2):
239 BLO_read_float_array(&reader, size * 2, reinterpret_cast<float **>(data));
240 return;
241 case int8_t(AttrType::Float3):
242 BLO_read_float3_array(&reader, size, reinterpret_cast<float **>(data));
243 return;
244 case int8_t(AttrType::Float4x4):
245 BLO_read_float_array(&reader, size * 16, reinterpret_cast<float **>(data));
246 return;
247 case int8_t(AttrType::ColorByte):
248 BLO_read_uint8_array(&reader, size * 4, reinterpret_cast<uint8_t **>(data));
249 return;
250 case int8_t(AttrType::ColorFloat):
251 BLO_read_float_array(&reader, size * 4, reinterpret_cast<float **>(data));
252 return;
253 case int8_t(AttrType::Quaternion):
254 BLO_read_float_array(&reader, size * 4, reinterpret_cast<float **>(data));
255 return;
256 case int8_t(AttrType::String):
258 &reader, MStringProperty, size, reinterpret_cast<MStringProperty **>(data));
259 return;
260 default:
261 *data = nullptr;
262 return;
263 }
264}
265
267 const int8_t dna_attr_type,
268 const int64_t size,
269 void **data,
270 const ImplicitSharingInfo **sharing_info)
271{
272 const char *func = __func__;
273 *sharing_info = BLO_read_shared(&reader, data, [&]() -> const ImplicitSharingInfo * {
274 read_array_data(reader, dna_attr_type, size, data);
275 if (*data == nullptr) {
276 return nullptr;
277 }
278 const CPPType &cpp_type = attribute_type_to_cpp_type(AttrType(dna_attr_type));
279 return MEM_new<ArrayDataImplicitSharing>(func, *data, size, cpp_type);
280 });
281}
282
283static std::optional<Attribute::DataVariant> read_attr_data(BlendDataReader &reader,
284 const int8_t dna_storage_type,
285 const int8_t dna_attr_type,
286 ::Attribute &dna_attr)
287{
288 switch (dna_storage_type) {
289 case int8_t(AttrStorageType::Array): {
290 BLO_read_struct(&reader, AttributeArray, &dna_attr.data);
291 auto &data = *static_cast<::AttributeArray *>(dna_attr.data);
292 read_shared_array(reader, dna_attr_type, data.size, &data.data, &data.sharing_info);
293 if (data.size != 0 && !data.data) {
294 return std::nullopt;
295 }
296 return Attribute::ArrayData{data.data, data.size, ImplicitSharingPtr<>(data.sharing_info)};
297 }
298 case int8_t(AttrStorageType::Single): {
299 BLO_read_struct(&reader, AttributeSingle, &dna_attr.data);
300 auto &data = *static_cast<::AttributeSingle *>(dna_attr.data);
301 read_shared_array(reader, dna_attr_type, 1, &data.data, &data.sharing_info);
302 if (!data.data) {
303 return std::nullopt;
304 }
305 return Attribute::SingleData{data.data, ImplicitSharingPtr<>(data.sharing_info)};
306 }
307 default:
308 return std::nullopt;
309 }
310}
311
313{
314 for (const std::unique_ptr<Attribute> &attr : this->runtime->attributes) {
315 const CPPType &type = attribute_type_to_cpp_type(attr->data_type());
316 if (const auto *data = std::get_if<Attribute::ArrayData>(&attr->data())) {
317 memory.add_shared(data->sharing_info.get(), [&](MemoryCounter &shared_memory) {
318 shared_memory.add(data->size * type.size);
319 });
320 }
321 else if (const auto *data = std::get_if<Attribute::SingleData>(&attr->data())) {
322 memory.add_shared(data->sharing_info.get(),
323 [&](MemoryCounter &shared_memory) { shared_memory.add(type.size); });
324 }
325 }
326}
327
328static std::optional<AttrDomain> read_attr_domain(const int8_t dna_domain)
329{
330 switch (dna_domain) {
331 case int8_t(AttrDomain::Point):
332 case int8_t(AttrDomain::Edge):
333 case int8_t(AttrDomain::Face):
334 case int8_t(AttrDomain::Corner):
335 case int8_t(AttrDomain::Curve):
336 case int8_t(AttrDomain::Instance):
337 case int8_t(AttrDomain::Layer):
338 return AttrDomain(dna_domain);
339 default:
340 return std::nullopt;
341 }
342}
343
345{
346 this->runtime = MEM_new<AttributeStorageRuntime>(__func__);
347 this->runtime->attributes.reserve(this->dna_attributes_num);
348
350 for (const int i : IndexRange(this->dna_attributes_num)) {
351 ::Attribute &dna_attr = this->dna_attributes[i];
352 BLO_read_string(&reader, &dna_attr.name);
353 BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(dna_attr.name); });
354
355 const std::optional<AttrDomain> domain = read_attr_domain(dna_attr.domain);
356 if (!domain) {
357 continue;
358 }
359
360 std::optional<Attribute::DataVariant> data = read_attr_data(
361 reader, dna_attr.storage_type, dna_attr.data_type, dna_attr);
362 BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(dna_attr.data); });
363 if (!data) {
364 continue;
365 }
366
367 std::unique_ptr<Attribute> attribute = std::make_unique<Attribute>();
368 attribute->name_ = dna_attr.name;
369 attribute->domain_ = *domain;
370 attribute->type_ = AttrType(dna_attr.data_type);
371 attribute->data_ = std::move(*data);
372
373 if (!this->runtime->attributes.add(std::move(attribute))) {
374 CLOG_ERROR(&LOG, "Ignoring attribute with duplicate name: \"%s\"", dna_attr.name);
375 }
376 }
377
378 /* These fields are not used at runtime. */
380 this->dna_attributes_num = 0;
381}
382
383static void write_array_data(BlendWriter &writer,
384 const AttrType data_type,
385 const void *data,
386 const int64_t size)
387{
388 switch (data_type) {
389 case AttrType::Bool:
390 static_assert(sizeof(bool) == sizeof(int8_t));
391 BLO_write_int8_array(&writer, size, static_cast<const int8_t *>(data));
392 break;
393 case AttrType::Int8:
394 BLO_write_int8_array(&writer, size, static_cast<const int8_t *>(data));
395 break;
397 BLO_write_int16_array(&writer, size * 2, static_cast<const int16_t *>(data));
398 break;
399 case AttrType::Int32:
400 BLO_write_int32_array(&writer, size, static_cast<const int32_t *>(data));
401 break;
403 BLO_write_int32_array(&writer, size * 2, static_cast<const int32_t *>(data));
404 break;
405 case AttrType::Float:
406 BLO_write_float_array(&writer, size, static_cast<const float *>(data));
407 break;
408 case AttrType::Float2:
409 BLO_write_float_array(&writer, size * 2, static_cast<const float *>(data));
410 break;
411 case AttrType::Float3:
412 BLO_write_float3_array(&writer, size, static_cast<const float *>(data));
413 break;
415 BLO_write_float_array(&writer, size * 16, static_cast<const float *>(data));
416 break;
418 BLO_write_uint8_array(&writer, size * 4, static_cast<const uint8_t *>(data));
419 break;
421 BLO_write_float_array(&writer, size * 4, static_cast<const float *>(data));
422 break;
424 BLO_write_float_array(&writer, size * 4, static_cast<const float *>(data));
425 break;
426 case AttrType::String:
428 &writer, MStringProperty, size, static_cast<const MStringProperty *>(data));
429 break;
430 }
431}
432
435 const Map<AttrDomain, Vector<CustomDataLayer, 16> *> &layers_to_write,
437{
438 Set<std::string, 16> all_names_written;
439 for (Vector<CustomDataLayer, 16> *const layers : layers_to_write.values()) {
440 for (const CustomDataLayer &layer : *layers) {
441 all_names_written.add(layer.name);
442 }
443 }
444 data.foreach([&](Attribute &attr) {
445 if (!U.experimental.use_attribute_storage_write) {
446 /* In version 4.5, all attribute data is written in the #CustomData format (at least when the
447 * debug option is not enabled), so the #Attribute needs to be converted to a
448 * #CustomDataLayer in the proper list. This is only relevant when #AttributeStorage is
449 * actually used at runtime.
450 *
451 * When removing this option to always write the new format in 5.0, #BLENDER_FILE_MIN_VERSION
452 * must be increased. */
453 if (const std::optional data_type = attr_type_to_custom_data_type(attr.data_type())) {
454 if (const auto *array_data = std::get_if<Attribute::ArrayData>(&attr.data())) {
455 CustomDataLayer layer{};
456 layer.type = *data_type;
457 layer.data = array_data->data;
458 layer.sharing_info = array_data->sharing_info.get();
459
460 /* Because the #Attribute::name_ `std::string` has no length limit (unlike
461 * #CustomDataLayer::name), we have to manually make the name unique in case it exceeds
462 * the limit. */
464 [&](const StringRefNull name) { return all_names_written.contains(name); },
465 attr.name().c_str(),
466 '.',
467 layer.name,
469 all_names_written.add(layer.name);
470
471 layers_to_write.lookup(attr.domain())->append(layer);
472 }
473 }
474 return;
475 }
476
477 /* Names within an AttributeStorage are unique. */
478 all_names_written.add(attr.name());
479 ::Attribute attribute_dna{};
480 attribute_dna.name = attr.name().c_str();
481 attribute_dna.data_type = int16_t(attr.data_type());
482 attribute_dna.domain = int8_t(attr.domain());
483 attribute_dna.storage_type = int8_t(attr.storage_type());
484
485 /* The idea is to use a separate DNA struct for each #AttrStorageType. They each need to have a
486 * unique address (while writing a specific ID anyway) in order to be identified when
487 * reading the file, so we add them to the resource scope which outlives this function call.
488 * Using a #ResourceScope is a simple way to get pointer stability when adding every new data
489 * struct without the cost of many small allocations or unnecessary overhead of storing a full
490 * array for every storage type. */
491
492 if (const auto *data = std::get_if<Attribute::ArrayData>(&attr.data())) {
493 auto &array_dna = write_data.scope.construct<::AttributeArray>();
494 array_dna.data = data->data;
495 array_dna.sharing_info = data->sharing_info.get();
496 array_dna.size = data->size;
497 attribute_dna.data = &array_dna;
498 }
499 else if (const auto *data = std::get_if<Attribute::SingleData>(&attr.data())) {
500 auto &single_dna = write_data.scope.construct<::AttributeSingle>();
501 single_dna.data = data->value;
502 single_dna.sharing_info = data->sharing_info.get();
503 attribute_dna.data = &single_dna;
504 }
505
506 write_data.attributes.append(attribute_dna);
507 });
508}
509
510static void write_shared_array(BlendWriter &writer,
511 const AttrType data_type,
512 const void *data,
513 const int64_t size,
514 const ImplicitSharingInfo &sharing_info)
515{
516 const CPPType &cpp_type = attribute_type_to_cpp_type(data_type);
517 BLO_write_shared(&writer, data, cpp_type.size * size, &sharing_info, [&]() {
518 write_array_data(writer, data_type, data, size);
519 });
520}
521
526
528 const AttributeStorage::BlendWriteData &write_data)
529{
530 /* Use string argument to avoid confusion with the C++ class with the same name. */
532 &writer, "Attribute", write_data.attributes.size(), write_data.attributes.data());
533 for (const ::Attribute &attr_dna : write_data.attributes) {
534 BLO_write_string(&writer, attr_dna.name);
535 switch (AttrStorageType(attr_dna.storage_type)) {
537 ::AttributeSingle *single_dna = static_cast<::AttributeSingle *>(attr_dna.data);
538 BLO_write_struct(&writer, AttributeSingle, single_dna);
540 writer, AttrType(attr_dna.data_type), single_dna->data, 1, *single_dna->sharing_info);
541 break;
542 }
544 ::AttributeArray *array_dna = static_cast<::AttributeArray *>(attr_dna.data);
545 BLO_write_struct(&writer, AttributeArray, array_dna);
546 write_shared_array(writer,
547 AttrType(attr_dna.data_type),
548 array_dna->data,
549 array_dna->size,
550 *array_dna->sharing_info);
551 break;
552 }
553 }
554 }
555
556 this->dna_attributes = nullptr;
557 this->dna_attributes_num = 0;
558}
559
560} // namespace blender::bke
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_SCOPED_DEFER(function_to_defer)
size_t void BLI_uniquename_cb(blender::FunctionRef< bool(blender::StringRefNull)> unique_check, const char *defname, char delim, char *name, size_t name_maxncpy) ATTR_NONNULL(2
void BLO_write_float_array(BlendWriter *writer, int64_t num, const float *data_ptr)
void BLO_read_uint8_array(BlendDataReader *reader, int64_t array_size, uint8_t **ptr_p)
Definition readfile.cc:5284
void BLO_read_float3_array(BlendDataReader *reader, int64_t array_size, float **ptr_p)
Definition readfile.cc:5336
void BLO_write_int32_array(BlendWriter *writer, int64_t num, const int32_t *data_ptr)
void BLO_write_struct_array_by_name(BlendWriter *writer, const char *struct_name, int64_t array_size, const void *data_ptr)
void BLO_read_float_array(BlendDataReader *reader, int64_t array_size, float **ptr_p)
Definition readfile.cc:5326
void BLO_read_int32_array(BlendDataReader *reader, int64_t array_size, int32_t **ptr_p)
Definition readfile.cc:5306
void BLO_write_uint8_array(BlendWriter *writer, int64_t num, const uint8_t *data_ptr)
#define BLO_write_struct(writer, struct_name, data_ptr)
void BLO_write_float3_array(BlendWriter *writer, int64_t num, const float *data_ptr)
void BLO_read_int16_array(BlendDataReader *reader, const int64_t array_size, int16_t **ptr_p)
Definition readfile.cc:5296
void BLO_write_int8_array(BlendWriter *writer, int64_t num, const int8_t *data_ptr)
void BLO_read_string(BlendDataReader *reader, char **ptr_p)
Definition readfile.cc:5351
void BLO_write_string(BlendWriter *writer, const char *data_ptr)
#define BLO_write_struct_array(writer, struct_name, array_size, data_ptr)
void BLO_read_int8_array(BlendDataReader *reader, int64_t array_size, int8_t **ptr_p)
Definition readfile.cc:5290
#define BLO_read_struct_array(reader, struct_name, array_size, ptr_p)
#define BLO_read_struct(reader, struct_name, ptr_p)
void BLO_write_int16_array(BlendWriter *writer, int64_t num, const int16_t *data_ptr)
void BLO_write_shared(BlendWriter *writer, const void *data, size_t approximate_size_in_bytes, const blender::ImplicitSharingInfo *sharing_info, blender::FunctionRef< void()> write_fn)
const blender::ImplicitSharingInfo * BLO_read_shared(BlendDataReader *reader, T **data_ptr, blender::FunctionRef< const blender::ImplicitSharingInfo *()> read_fn)
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:182
#define U
BMesh const char void * data
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
void copy_construct_n(const void *src, void *dst, int64_t n) const
void destruct_n(void *ptr, int64_t n) const
T & construct(Args &&...args)
bool contains(const Key &key) const
Definition BLI_set.hh:310
bool add(const Key &key)
Definition BLI_set.hh:248
constexpr const char * c_str() const
ArrayDataImplicitSharing(void *data, const int64_t size, const CPPType &type)
Attribute & add(std::string name, bke::AttrDomain domain, bke::AttrType data_type, Attribute::DataVariant data)
void count_memory(MemoryCounter &memory) const
Attribute * lookup(StringRef name)
void foreach(FunctionRef< void(Attribute &)> fn)
void blend_read(BlendDataReader &reader)
std::string unique_name_calc(StringRef name)
void blend_write(BlendWriter &writer, const BlendWriteData &write_data)
AttributeStorage & operator=(const AttributeStorage &other)
AttrStorageType storage_type() const
const DataVariant & data() const
std::variant< ArrayData, SingleData > DataVariant
void add_shared(const ImplicitSharingInfo *sharing_info, const FunctionRef< void(MemoryCounter &shared_memory)> count_fn)
#define MAX_CUSTOMDATA_LAYER_NAME
#define MEM_SAFE_FREE(v)
#define LOG(severity)
Definition log.h:32
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
void *(* MEM_malloc_arrayN_aligned)(size_t len, size_t size, size_t alignment, const char *str)
Definition mallocn.cc:55
std::optional< eCustomDataType > attr_type_to_custom_data_type(AttrType attr_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)
static ImplicitSharingInfo * create_sharing_info_for_array(void *data, const int64_t size, const CPPType &type)
static std::optional< AttrDomain > read_attr_domain(const int8_t dna_domain)
static void write_array_data(BlendWriter &writer, const AttrType data_type, const void *data, const int64_t size)
static void write_shared_array(BlendWriter &writer, const AttrType data_type, const void *data, const int64_t size, const ImplicitSharingInfo &sharing_info)
static void read_shared_array(BlendDataReader &reader, const int8_t dna_attr_type, const int64_t size, void **data, const ImplicitSharingInfo **sharing_info)
static void read_array_data(BlendDataReader &reader, const int8_t dna_attr_type, const int64_t size, void **data)
const CPPType & attribute_type_to_cpp_type(AttrType type)
void attribute_storage_blend_write_prepare(AttributeStorage &data, const Map< AttrDomain, Vector< CustomDataLayer, 16 > * > &layers_to_write, AttributeStorage::BlendWriteData &write_data)
const ImplicitSharingInfoHandle * sharing_info
const ImplicitSharingInfoHandle * sharing_info
AttributeStorageRuntimeHandle * runtime
struct Attribute * dna_attributes
const char * name
const ImplicitSharingInfoHandle * sharing_info
i
Definition text_draw.cc:230
PointerRNA * ptr
Definition wm_files.cc:4226