Geometry: Initial replacement of CustomData with AttributeStorage
As described in #122398, implement read and write support for a new attribute storage system. Currently this is only implemented to support forward compatibility; the format used at runtime isn't changed at all. That can be done one step at a time during the 4.5 and 5.0 development cycles. A new experimental option for testing tells Blender to always save with the new format. The main benefit of the new structure is that it matches the attribute system design, it allows for future attribute storage optimization, and each attribute is an allocated struct, which will give pointer stability for the Python API. The next step is to connect the attribute API and the RNA API to AttributeStorage for the simplest geometry type, point clouds. Pull Request: https://projects.blender.org/blender/blender/pulls/133874
This commit is contained in:
@@ -2872,6 +2872,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel):
|
||||
({"property": "use_new_curves_tools"}, ("blender/blender/issues/68981", "#68981")),
|
||||
({"property": "use_sculpt_texture_paint"}, ("blender/blender/issues/96225", "#96225")),
|
||||
({"property": "write_large_blend_file_blocks"}, ("/blender/blender/issues/129309", "#129309")),
|
||||
({"property": "use_attribute_storage_write"}, ("/blender/blender/issues/122398", "#122398")),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ set(SRC_DNA_INC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_anim_types.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_armature_types.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_asset_types.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_attribute_types.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_boid_types.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_brush_enums.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_brush_types.h
|
||||
|
||||
@@ -32,6 +32,33 @@ class GField;
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/** Some storage types are only relevant for certain attribute types. */
|
||||
enum class AttrStorageType : int8_t {
|
||||
/** #AttributeDataArray. */
|
||||
Array,
|
||||
/** A single value for the whole attribute. */
|
||||
Single,
|
||||
};
|
||||
|
||||
enum class AttrType : int16_t {
|
||||
Bool,
|
||||
Int8,
|
||||
Int16_2D,
|
||||
Int32,
|
||||
Int32_2D,
|
||||
Float,
|
||||
Float2,
|
||||
Float3,
|
||||
Float4x4,
|
||||
ColorByte,
|
||||
ColorFloat,
|
||||
Quaternion,
|
||||
String,
|
||||
};
|
||||
|
||||
const CPPType &attribute_type_to_cpp_type(AttrType type);
|
||||
AttrType cpp_type_to_attribute_type(const CPPType &type);
|
||||
|
||||
enum class AttrDomain : int8_t {
|
||||
/* Used to choose automatically based on other data. */
|
||||
Auto = -1,
|
||||
|
||||
65
source/blender/blenkernel/BKE_attribute_legacy_convert.hh
Normal file
65
source/blender/blenkernel/BKE_attribute_legacy_convert.hh
Normal file
@@ -0,0 +1,65 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DNA_attribute_types.h"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_attribute_storage.hh"
|
||||
|
||||
struct CustomData;
|
||||
namespace blender::bke {
|
||||
class CurvesGeometry;
|
||||
}
|
||||
struct PointCloud;
|
||||
struct GreasePencil;
|
||||
struct Mesh;
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/**
|
||||
* Convert a custom data type to an attribute type. May return `std::nullopt` if the custom data
|
||||
* type isn't used at runtime, is not a generic type that can be stored as an attribute, or is only
|
||||
* used for #BMesh.
|
||||
*/
|
||||
std::optional<AttrType> custom_data_type_to_attr_type(eCustomDataType data_type);
|
||||
|
||||
/**
|
||||
* Convert an attribute type to a legacy custom data type.
|
||||
*/
|
||||
std::optional<eCustomDataType> attr_type_to_custom_data_type(AttrType attr_type);
|
||||
|
||||
/**
|
||||
* Move attributes from the #AttributeStorage to the mesh's #CustomData structs. Used for forward
|
||||
* compatibility: converting newer files written with #AttributeStorage while #CustomData is still
|
||||
* used at runtime.
|
||||
*/
|
||||
void mesh_convert_storage_to_customdata(Mesh &mesh);
|
||||
|
||||
/**
|
||||
* Move generic attributes from #CustomData to #AttributeStorage (not including non-generic layer
|
||||
* types). Use for versioning old files when the newer #AttributeStorage format is used at runtime.
|
||||
*/
|
||||
AttributeStorage mesh_convert_customdata_to_storage(const Mesh &mesh);
|
||||
|
||||
/** See #mesh_convert_storage_to_customdata. */
|
||||
void curves_convert_storage_to_customdata(CurvesGeometry &curves);
|
||||
|
||||
/** See #mesh_convert_customdata_to_storage. */
|
||||
AttributeStorage curves_convert_customdata_to_storage(const CurvesGeometry &curves);
|
||||
|
||||
/** See #mesh_convert_storage_to_customdata. */
|
||||
void pointcloud_convert_storage_to_customdata(PointCloud &pointcloud);
|
||||
|
||||
/** See #mesh_convert_customdata_to_storage. */
|
||||
AttributeStorage pointcloud_convert_customdata_to_storage(const PointCloud &pointcloud);
|
||||
|
||||
/** See #mesh_convert_storage_to_customdata. */
|
||||
void grease_pencil_convert_storage_to_customdata(GreasePencil &grease_pencil);
|
||||
|
||||
/** See #mesh_convert_customdata_to_storage. */
|
||||
AttributeStorage grease_pencil_convert_customdata_to_storage(const GreasePencil &grease_pencil);
|
||||
|
||||
} // namespace blender::bke
|
||||
213
source/blender/blenkernel/BKE_attribute_storage.hh
Normal file
213
source/blender/blenkernel/BKE_attribute_storage.hh
Normal file
@@ -0,0 +1,213 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_implicit_sharing_ptr.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "DNA_attribute_types.h"
|
||||
|
||||
struct BlendDataReader;
|
||||
struct BlendWriter;
|
||||
namespace blender {
|
||||
class ResourceScope;
|
||||
}
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
enum class AttrDomain : int8_t;
|
||||
enum class AttrType : int16_t;
|
||||
enum class AttrStorageType : int8_t;
|
||||
|
||||
/** Data and metadata for a single geometry attribute. */
|
||||
class Attribute {
|
||||
public:
|
||||
/**
|
||||
* Data for an attribute stored as a full contiguous array with a data type exactly matching the
|
||||
* attribute's type. The array referenced must match the size of the domain and the data type.
|
||||
*/
|
||||
struct ArrayData {
|
||||
/* NOTE: Since the shared data pointed to by `sharing_info` knows how to free itself, it often
|
||||
* stores the size and type itself. It may be possible to make use of that fact to avoid
|
||||
* storing it here, or even vice versa. */
|
||||
void *data;
|
||||
/* The number of elements in the array. */
|
||||
int64_t size;
|
||||
ImplicitSharingPtr<> sharing_info;
|
||||
};
|
||||
/** Data for an attribute stored as a single value for the entire domain. */
|
||||
struct SingleData {
|
||||
/* NOTE: For simplicity and to avoid a bit of redundancy, the domain size isn't stored here.
|
||||
* It's not necessary to manage a single value. */
|
||||
void *value;
|
||||
ImplicitSharingPtr<> sharing_info;
|
||||
};
|
||||
using DataVariant = std::variant<ArrayData, SingleData>;
|
||||
friend AttributeStorage;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Because it's used as the custom ID for the attributes vector set, the name cannot be changed
|
||||
* without removing and adding the attribute.
|
||||
*/
|
||||
std::string name_;
|
||||
AttrDomain domain_;
|
||||
AttrType type_;
|
||||
|
||||
DataVariant data_;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Unique name across all domains.
|
||||
* \note Compared to #CustomData, which doesn't enforce uniqueness, across domains on its own,
|
||||
* this is enforced by asserts when adding attributes. See #unique_name_calc() (which is also
|
||||
* called during the conversion process).
|
||||
*/
|
||||
StringRefNull name() const;
|
||||
|
||||
/** Which part of a geometry the attribute corresponds to. */
|
||||
AttrDomain domain() const;
|
||||
|
||||
/**
|
||||
* The data type exposed to the user. Depending on the storage type, the actual internal values
|
||||
* may not be the same type.
|
||||
*/
|
||||
AttrType data_type() const;
|
||||
|
||||
/**
|
||||
* The method used to store the data. This gives flexibility to optimize the internal storage
|
||||
* even though conceptually the attribute is an array of values.
|
||||
*/
|
||||
AttrStorageType storage_type() const;
|
||||
|
||||
/**
|
||||
* Low level access to the data stored for the attribute. The variant's type will correspond to
|
||||
* the storage type.
|
||||
*/
|
||||
const DataVariant &data() const;
|
||||
|
||||
/**
|
||||
* The same as #data(), but if the attribute data is shared initially, it will be unshared and
|
||||
* made mutable.
|
||||
*
|
||||
* \warning Does not yet support attributes stored as a single value (#AttrStorageType::Single).
|
||||
*/
|
||||
DataVariant &data_for_write();
|
||||
};
|
||||
|
||||
class AttributeStorageRuntime {
|
||||
friend AttributeStorage;
|
||||
struct AttributeNameGetter {
|
||||
StringRef operator()(const std::unique_ptr<Attribute> &value) const
|
||||
{
|
||||
return value->name();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* For quick access, the attributes are stored in a vector set, keyed by their name. Attributes
|
||||
* can still be reordered by rebuilding the vector set from scratch. Each attribute is allocated
|
||||
* to give pointer stability across additions and removals.
|
||||
*/
|
||||
CustomIDVectorSet<std::unique_ptr<Attribute>, AttributeNameGetter> attributes;
|
||||
};
|
||||
|
||||
class AttributeStorage : public ::AttributeStorage {
|
||||
public:
|
||||
AttributeStorage();
|
||||
AttributeStorage(const AttributeStorage &other);
|
||||
AttributeStorage(AttributeStorage &&other);
|
||||
AttributeStorage &operator=(const AttributeStorage &other);
|
||||
AttributeStorage &operator=(AttributeStorage &&other);
|
||||
~AttributeStorage();
|
||||
|
||||
/**
|
||||
* Iterate over all attributes, with the order defined by the order of insertion. It is not safe
|
||||
* to add or remove attributes while iterating.
|
||||
*/
|
||||
void foreach(FunctionRef<void(Attribute &)> fn);
|
||||
void foreach(FunctionRef<void(const Attribute &)> fn) const;
|
||||
|
||||
/**
|
||||
* Try to find the attribute with a givin name. The non-const overload does not make the
|
||||
* attribute data itself mutable.
|
||||
*/
|
||||
Attribute *lookup(StringRef name);
|
||||
const Attribute *lookup(StringRef name) const;
|
||||
|
||||
/**
|
||||
* Attempt to remove the attribute with the given name, returning `true` if successful. Should
|
||||
* not be called while iterating over attributes.
|
||||
*/
|
||||
bool remove(StringRef name);
|
||||
|
||||
/**
|
||||
* Add an attribute with the given name, which must not already be used by an existing attribute
|
||||
* or this will invoke undefined behavior.
|
||||
*/
|
||||
Attribute &add(std::string name,
|
||||
bke::AttrDomain domain,
|
||||
bke::AttrType data_type,
|
||||
Attribute::DataVariant data);
|
||||
|
||||
/** Return a possibly changed version of the input name that is unique within existing names. */
|
||||
std::string unique_name_calc(StringRef name);
|
||||
|
||||
/**
|
||||
* Read data owned by the #AttributeStorage struct. This works by converting the DNA-specific
|
||||
* types stored in the files to the runtime data structures.
|
||||
*/
|
||||
void blend_read(BlendDataReader &reader);
|
||||
/**
|
||||
* Temporary data used to write a #AttributeStorage struct embedded in another struct. See
|
||||
* #attribute_storage_blend_write_prepare for more information.
|
||||
*/
|
||||
struct BlendWriteData {
|
||||
ResourceScope &scope;
|
||||
Vector<::Attribute, 16> attributes;
|
||||
};
|
||||
/**
|
||||
* Write the prepared data and the data stored in the DNA fields in
|
||||
* the #AttributeStorage struct.
|
||||
*/
|
||||
void blend_write(BlendWriter &writer, const BlendWriteData &write_data);
|
||||
};
|
||||
|
||||
/** The C++ wrapper needs to be the same size as the DNA struct. */
|
||||
static_assert(sizeof(AttributeStorage) == sizeof(::AttributeStorage));
|
||||
|
||||
inline StringRefNull Attribute::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
inline AttrDomain Attribute::domain() const
|
||||
{
|
||||
return domain_;
|
||||
}
|
||||
|
||||
inline AttrType Attribute::data_type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
inline const Attribute::DataVariant &Attribute::data() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
inline blender::bke::AttributeStorage &AttributeStorage::wrap()
|
||||
{
|
||||
return *reinterpret_cast<blender::bke::AttributeStorage *>(this);
|
||||
}
|
||||
inline const blender::bke::AttributeStorage &AttributeStorage::wrap() const
|
||||
{
|
||||
return *reinterpret_cast<const blender::bke::AttributeStorage *>(this);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_attribute_storage.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
|
||||
#include "DNA_customdata_types.h"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/**
|
||||
* Prepare an #AttributeStorage struct embedded in another struct to be written. This is necessary
|
||||
* because the #AttributeStorage implementation doesn't use the DNA structs at runtime, they are
|
||||
* created just for the writing process. Creating them mutates the struct, which must be done
|
||||
* before writing the struct that embeds it.
|
||||
*/
|
||||
void attribute_storage_blend_write_prepare(
|
||||
AttributeStorage &data,
|
||||
const Map<AttrDomain, Vector<CustomDataLayer, 16> *> &layers_to_write,
|
||||
AttributeStorage::BlendWriteData &write_data);
|
||||
|
||||
} // namespace blender::bke
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "BLI_virtual_array_fwd.hh"
|
||||
|
||||
#include "BKE_attribute_math.hh"
|
||||
#include "BKE_attribute_storage.hh"
|
||||
#include "BKE_curves.h"
|
||||
|
||||
struct BlendDataReader;
|
||||
@@ -495,16 +496,16 @@ class CurvesGeometry : public ::CurvesGeometry {
|
||||
* Helper struct for `CurvesGeometry::blend_write_*` functions.
|
||||
*/
|
||||
struct BlendWriteData {
|
||||
/* The point custom data layers to be written. */
|
||||
Vector<CustomDataLayer, 16> point_layers;
|
||||
/* The curve custom data layers to be written. */
|
||||
Vector<CustomDataLayer, 16> curve_layers;
|
||||
AttributeStorage::BlendWriteData attribute_data;
|
||||
explicit BlendWriteData(ResourceScope &scope) : attribute_data{scope} {}
|
||||
};
|
||||
/**
|
||||
* This function needs to be called before `blend_write` and before the `CurvesGeometry` struct
|
||||
* is written because it can mutate the `CustomData` struct.
|
||||
* is written because it can mutate the `CustomData` and `AttributeStorage` structs.
|
||||
*/
|
||||
BlendWriteData blend_write_prepare();
|
||||
void blend_write_prepare(BlendWriteData &write_data);
|
||||
void blend_write(BlendWriter &writer, ID &id, const BlendWriteData &write_data);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
|
||||
#include "BLI_implicit_sharing.h"
|
||||
#include "BLI_memory_counter_fwd.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_sys_types.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_attribute_storage.hh"
|
||||
#include "BKE_volume_enums.hh"
|
||||
|
||||
#include "DNA_customdata_types.h"
|
||||
@@ -32,6 +32,10 @@ struct CustomDataTransferLayerMap;
|
||||
struct ID;
|
||||
struct MeshPairRemap;
|
||||
|
||||
namespace blender::bke {
|
||||
enum class AttrDomain : int8_t;
|
||||
}
|
||||
|
||||
/* These names are used as prefixes for UV layer names to find the associated boolean
|
||||
* layers. They should never be longer than 2 chars, as #MAX_CUSTOMDATA_LAYER_NAME
|
||||
* has 4 extra bytes above what can be used for the base layer name, and these
|
||||
@@ -731,14 +735,18 @@ void CustomData_data_transfer(const MeshPairRemap *me_remap,
|
||||
*
|
||||
* \param data: The custom-data to tweak for .blend file writing (modified in place).
|
||||
* \param layers_to_write: A reduced set of layers to be written to file.
|
||||
* \param write_data: #AttributeStorage data to write, to support the option for writing the new
|
||||
* format even when it isn't used at runtime.
|
||||
*
|
||||
* \warning This function invalidates the custom data struct by changing the layer counts and the
|
||||
* #layers pointer, and by invalidating the type map. It expects to work on a shallow copy of
|
||||
* the struct.
|
||||
*/
|
||||
void CustomData_blend_write_prepare(CustomData &data,
|
||||
blender::bke::AttrDomain domain,
|
||||
int domain_size,
|
||||
blender::Vector<CustomDataLayer, 16> &layers_to_write,
|
||||
const blender::Set<std::string> &skip_names = {});
|
||||
blender::bke::AttributeStorage::BlendWriteData &write_data);
|
||||
|
||||
/**
|
||||
* \param layers_to_write: Layers created by #CustomData_blend_write_prepare.
|
||||
|
||||
@@ -55,6 +55,8 @@ set(SRC
|
||||
intern/asset_weak_reference.cc
|
||||
intern/attribute.cc
|
||||
intern/attribute_access.cc
|
||||
intern/attribute_legacy_convert.cc
|
||||
intern/attribute_storage.cc
|
||||
intern/attribute_math.cc
|
||||
intern/autoexec.cc
|
||||
intern/bake_data_block_map.cc
|
||||
@@ -333,7 +335,10 @@ set(SRC
|
||||
BKE_attribute.hh
|
||||
BKE_attribute_filter.hh
|
||||
BKE_attribute_filters.hh
|
||||
BKE_attribute_legacy_convert.hh
|
||||
BKE_attribute_math.hh
|
||||
BKE_attribute_storage.hh
|
||||
BKE_attribute_storage_blend_write.hh
|
||||
BKE_autoexec.hh
|
||||
BKE_bake_data_block_id.hh
|
||||
BKE_bake_data_block_map.hh
|
||||
@@ -818,6 +823,7 @@ add_dependencies(bf_blenkernel bf_rna)
|
||||
if(WITH_GTESTS)
|
||||
set(TEST_SRC
|
||||
intern/action_test.cc
|
||||
intern/attribute_storage_test.cc
|
||||
intern/armature_test.cc
|
||||
intern/asset_metadata_test.cc
|
||||
intern/bpath_test.cc
|
||||
|
||||
@@ -34,6 +34,85 @@
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
const CPPType &attribute_type_to_cpp_type(const AttrType type)
|
||||
{
|
||||
switch (type) {
|
||||
case AttrType::Bool:
|
||||
return CPPType::get<bool>();
|
||||
case AttrType::Int8:
|
||||
return CPPType::get<int8_t>();
|
||||
case AttrType::Int16_2D:
|
||||
return CPPType::get<short2>();
|
||||
case AttrType::Int32:
|
||||
return CPPType::get<int>();
|
||||
case AttrType::Int32_2D:
|
||||
return CPPType::get<int2>();
|
||||
case AttrType::Float:
|
||||
return CPPType::get<float>();
|
||||
case AttrType::Float2:
|
||||
return CPPType::get<float2>();
|
||||
case AttrType::Float3:
|
||||
return CPPType::get<float3>();
|
||||
case AttrType::Float4x4:
|
||||
return CPPType::get<float4x4>();
|
||||
case AttrType::ColorByte:
|
||||
return CPPType::get<ColorGeometry4b>();
|
||||
case AttrType::ColorFloat:
|
||||
return CPPType::get<ColorGeometry4f>();
|
||||
case AttrType::Quaternion:
|
||||
return CPPType::get<math::Quaternion>();
|
||||
case AttrType::String:
|
||||
return CPPType::get<MStringProperty>();
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return CPPType::get<bool>();
|
||||
}
|
||||
|
||||
AttrType cpp_type_to_attribute_type(const CPPType &type)
|
||||
{
|
||||
if (type.is<float>()) {
|
||||
return AttrType::Float;
|
||||
}
|
||||
if (type.is<float2>()) {
|
||||
return AttrType::Float2;
|
||||
}
|
||||
if (type.is<float3>()) {
|
||||
return AttrType::Float3;
|
||||
}
|
||||
if (type.is<int>()) {
|
||||
return AttrType::Int32;
|
||||
}
|
||||
if (type.is<int2>()) {
|
||||
return AttrType::Int32_2D;
|
||||
}
|
||||
if (type.is<ColorGeometry4f>()) {
|
||||
return AttrType::ColorFloat;
|
||||
}
|
||||
if (type.is<bool>()) {
|
||||
return AttrType::Bool;
|
||||
}
|
||||
if (type.is<int8_t>()) {
|
||||
return AttrType::Int8;
|
||||
}
|
||||
if (type.is<ColorGeometry4b>()) {
|
||||
return AttrType::ColorByte;
|
||||
}
|
||||
if (type.is<math::Quaternion>()) {
|
||||
return AttrType::Quaternion;
|
||||
}
|
||||
if (type.is<float4x4>()) {
|
||||
return AttrType::Float4x4;
|
||||
}
|
||||
if (type.is<short2>()) {
|
||||
return AttrType::Int16_2D;
|
||||
}
|
||||
if (type.is<MStringProperty>()) {
|
||||
return AttrType::String;
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return AttrType::Bool;
|
||||
}
|
||||
|
||||
const blender::CPPType *custom_data_type_to_cpp_type(const eCustomDataType type)
|
||||
{
|
||||
switch (type) {
|
||||
|
||||
273
source/blender/blenkernel/intern/attribute_legacy_convert.cc
Normal file
273
source/blender/blenkernel/intern/attribute_legacy_convert.cc
Normal file
@@ -0,0 +1,273 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#define DNA_DEPRECATED_ALLOW
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "DNA_grease_pencil_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_pointcloud_types.h"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_customdata.hh"
|
||||
|
||||
#include "BKE_attribute_legacy_convert.hh"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
std::optional<AttrType> custom_data_type_to_attr_type(const eCustomDataType data_type)
|
||||
{
|
||||
switch (data_type) {
|
||||
case CD_AUTO_FROM_NAME:
|
||||
/* This type is not used for actual #CustomData layers. */
|
||||
BLI_assert_unreachable();
|
||||
return std::nullopt;
|
||||
case CD_MVERT:
|
||||
case CD_MSTICKY:
|
||||
case CD_MEDGE:
|
||||
case CD_FACEMAP:
|
||||
case CD_MTEXPOLY:
|
||||
case CD_MLOOPUV:
|
||||
case CD_MPOLY:
|
||||
case CD_MLOOP:
|
||||
case CD_BWEIGHT:
|
||||
case CD_CREASE:
|
||||
case CD_PAINT_MASK:
|
||||
case CD_CUSTOMLOOPNORMAL:
|
||||
case CD_SCULPT_FACE_SETS:
|
||||
case CD_NUMTYPES:
|
||||
/* These types are only used for versioning old files. */
|
||||
return std::nullopt;
|
||||
/* These types are only used for #BMesh. */
|
||||
case CD_SHAPEKEY:
|
||||
case CD_SHAPE_KEYINDEX:
|
||||
case CD_BM_ELEM_PYPTR:
|
||||
return std::nullopt;
|
||||
case CD_MDEFORMVERT:
|
||||
case CD_MFACE:
|
||||
case CD_MTFACE:
|
||||
case CD_MCOL:
|
||||
case CD_ORIGINDEX:
|
||||
case CD_NORMAL:
|
||||
case CD_ORIGSPACE:
|
||||
case CD_ORCO:
|
||||
case CD_TANGENT:
|
||||
case CD_MDISPS:
|
||||
case CD_CLOTH_ORCO:
|
||||
case CD_ORIGSPACE_MLOOP:
|
||||
case CD_GRID_PAINT_MASK:
|
||||
case CD_MVERT_SKIN:
|
||||
case CD_FREESTYLE_EDGE:
|
||||
case CD_FREESTYLE_FACE:
|
||||
case CD_MLOOPTANGENT:
|
||||
case CD_TESSLOOPNORMAL:
|
||||
/* These types are not generic. They will either be moved to some generic data type or
|
||||
* #AttributeStorage will be extended to be able to support a similar format. */
|
||||
return std::nullopt;
|
||||
case CD_PROP_FLOAT:
|
||||
return AttrType::Float;
|
||||
case CD_PROP_INT32:
|
||||
return AttrType::Int32;
|
||||
case CD_PROP_BYTE_COLOR:
|
||||
return AttrType::ColorByte;
|
||||
case CD_PROP_FLOAT4X4:
|
||||
return AttrType::Float4x4;
|
||||
case CD_PROP_INT16_2D:
|
||||
return AttrType::Int16_2D;
|
||||
case CD_PROP_INT8:
|
||||
return AttrType::Int8;
|
||||
case CD_PROP_INT32_2D:
|
||||
return AttrType::Int32_2D;
|
||||
case CD_PROP_COLOR:
|
||||
return AttrType::ColorFloat;
|
||||
case CD_PROP_FLOAT3:
|
||||
return AttrType::Float3;
|
||||
case CD_PROP_FLOAT2:
|
||||
return AttrType::Float2;
|
||||
case CD_PROP_BOOL:
|
||||
return AttrType::Bool;
|
||||
case CD_PROP_STRING:
|
||||
return AttrType::String;
|
||||
case CD_PROP_QUATERNION:
|
||||
return AttrType::Quaternion;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct CustomDataAndSize {
|
||||
const CustomData &data;
|
||||
int size;
|
||||
};
|
||||
|
||||
static AttributeStorage attribute_legacy_convert_customdata_to_storage(
|
||||
const Map<AttrDomain, CustomDataAndSize> &domains)
|
||||
{
|
||||
AttributeStorage storage{};
|
||||
struct AttributeToAdd {
|
||||
std::string name;
|
||||
AttrDomain domain;
|
||||
AttrType type;
|
||||
void *array_data;
|
||||
int array_size;
|
||||
const ImplicitSharingInfo *sharing_info;
|
||||
};
|
||||
Vector<AttributeToAdd> attributes_to_add;
|
||||
for (const auto &item : domains.items()) {
|
||||
const AttrDomain domain = item.key;
|
||||
const CustomData &custom_data = item.value.data;
|
||||
const int domain_size = item.value.size;
|
||||
for (const CustomDataLayer &layer : MutableSpan(custom_data.layers, custom_data.totlayer)) {
|
||||
const std::optional<AttrType> attr_type = custom_data_type_to_attr_type(
|
||||
eCustomDataType(layer.type));
|
||||
if (!attr_type) {
|
||||
continue;
|
||||
}
|
||||
attributes_to_add.append(
|
||||
{layer.name, domain, *attr_type, layer.data, domain_size, layer.sharing_info});
|
||||
layer.sharing_info->add_user();
|
||||
}
|
||||
}
|
||||
|
||||
for (AttributeToAdd &attribute : attributes_to_add) {
|
||||
bke::Attribute::ArrayData array_data;
|
||||
array_data.data = attribute.array_data;
|
||||
array_data.size = attribute.array_size;
|
||||
array_data.sharing_info = ImplicitSharingPtr<>(attribute.sharing_info);
|
||||
storage.add(storage.unique_name_calc(attribute.name),
|
||||
attribute.domain,
|
||||
attribute.type,
|
||||
std::move(array_data));
|
||||
}
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
std::optional<eCustomDataType> attr_type_to_custom_data_type(const AttrType attr_type)
|
||||
{
|
||||
switch (attr_type) {
|
||||
case AttrType::Bool:
|
||||
return CD_PROP_BOOL;
|
||||
case AttrType::Int8:
|
||||
return CD_PROP_INT8;
|
||||
case AttrType::Int16_2D:
|
||||
return CD_PROP_INT16_2D;
|
||||
case AttrType::Int32:
|
||||
return CD_PROP_INT32;
|
||||
case AttrType::Int32_2D:
|
||||
return CD_PROP_INT32_2D;
|
||||
case AttrType::Float:
|
||||
return CD_PROP_FLOAT;
|
||||
case AttrType::Float2:
|
||||
return CD_PROP_FLOAT2;
|
||||
case AttrType::Float3:
|
||||
return CD_PROP_FLOAT3;
|
||||
case AttrType::Float4x4:
|
||||
return CD_PROP_FLOAT4X4;
|
||||
case AttrType::ColorByte:
|
||||
return CD_PROP_BYTE_COLOR;
|
||||
case AttrType::ColorFloat:
|
||||
return CD_PROP_COLOR;
|
||||
case AttrType::Quaternion:
|
||||
return CD_PROP_QUATERNION;
|
||||
case AttrType::String:
|
||||
return CD_PROP_STRING;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct CustomDataAndSizeMutable {
|
||||
CustomData &data;
|
||||
int size;
|
||||
};
|
||||
|
||||
static void convert_storage_to_customdata(
|
||||
AttributeStorage &storage,
|
||||
const Map<AttrDomain, CustomDataAndSizeMutable> &custom_data_domains)
|
||||
{
|
||||
/* Name uniqueness is handled by the #CustomData API. */
|
||||
storage.foreach([&](const Attribute &attribute) {
|
||||
const std::optional<eCustomDataType> data_type = attr_type_to_custom_data_type(
|
||||
attribute.data_type());
|
||||
if (!data_type) {
|
||||
return;
|
||||
}
|
||||
CustomData &custom_data = custom_data_domains.lookup(attribute.domain()).data;
|
||||
const int domain_size = custom_data_domains.lookup(attribute.domain()).size;
|
||||
if (const auto *array_data = std::get_if<Attribute::ArrayData>(&attribute.data())) {
|
||||
BLI_assert(array_data->size == domain_size);
|
||||
CustomData_add_layer_named_with_data(&custom_data,
|
||||
*data_type,
|
||||
array_data->data,
|
||||
array_data->size,
|
||||
attribute.name(),
|
||||
array_data->sharing_info.get());
|
||||
}
|
||||
else if (const auto *single_data = std::get_if<Attribute::SingleData>(&attribute.data())) {
|
||||
const CPPType &cpp_type = *custom_data_type_to_cpp_type(*data_type);
|
||||
auto *value = new ImplicitSharedValue<GArray<>>(cpp_type, domain_size);
|
||||
cpp_type.fill_construct_n(single_data->value, value->data.data(), domain_size);
|
||||
CustomData_add_layer_named_with_data(
|
||||
&custom_data, *data_type, value->data.data(), domain_size, attribute.name(), value);
|
||||
}
|
||||
});
|
||||
storage = {};
|
||||
}
|
||||
|
||||
void mesh_convert_storage_to_customdata(Mesh &mesh)
|
||||
{
|
||||
convert_storage_to_customdata(mesh.attribute_storage.wrap(),
|
||||
{{AttrDomain::Point, {mesh.vert_data, mesh.verts_num}},
|
||||
{AttrDomain::Edge, {mesh.edge_data, mesh.edges_num}},
|
||||
{AttrDomain::Face, {mesh.face_data, mesh.faces_num}},
|
||||
{AttrDomain::Corner, {mesh.corner_data, mesh.corners_num}}});
|
||||
}
|
||||
AttributeStorage mesh_convert_customdata_to_storage(const Mesh &mesh)
|
||||
{
|
||||
return bke::attribute_legacy_convert_customdata_to_storage(
|
||||
{{AttrDomain::Point, {mesh.vert_data, mesh.verts_num}},
|
||||
{AttrDomain::Edge, {mesh.edge_data, mesh.edges_num}},
|
||||
{AttrDomain::Face, {mesh.face_data, mesh.faces_num}},
|
||||
{AttrDomain::Corner, {mesh.corner_data, mesh.corners_num}}});
|
||||
}
|
||||
|
||||
void curves_convert_storage_to_customdata(CurvesGeometry &curves)
|
||||
{
|
||||
convert_storage_to_customdata(curves.attribute_storage.wrap(),
|
||||
{{AttrDomain::Point, {curves.point_data, curves.points_num()}},
|
||||
{AttrDomain::Curve, {curves.curve_data, curves.curves_num()}}});
|
||||
}
|
||||
AttributeStorage curves_convert_customdata_to_storage(const CurvesGeometry &curves)
|
||||
{
|
||||
return attribute_legacy_convert_customdata_to_storage(
|
||||
{{AttrDomain::Point, {curves.point_data, curves.points_num()}},
|
||||
{AttrDomain::Curve, {curves.curve_data, curves.curves_num()}}});
|
||||
}
|
||||
|
||||
void pointcloud_convert_storage_to_customdata(PointCloud &pointcloud)
|
||||
{
|
||||
convert_storage_to_customdata(pointcloud.attribute_storage.wrap(),
|
||||
{{AttrDomain::Point, {pointcloud.pdata, pointcloud.totpoint}}});
|
||||
}
|
||||
|
||||
AttributeStorage pointcloud_convert_customdata_to_storage(const PointCloud &pointcloud)
|
||||
{
|
||||
return attribute_legacy_convert_customdata_to_storage(
|
||||
{{AttrDomain::Point, {pointcloud.pdata, pointcloud.totpoint}}});
|
||||
}
|
||||
|
||||
void grease_pencil_convert_storage_to_customdata(GreasePencil &grease_pencil)
|
||||
{
|
||||
convert_storage_to_customdata(
|
||||
grease_pencil.attribute_storage.wrap(),
|
||||
{{AttrDomain::Layer, {grease_pencil.layers_data, int(grease_pencil.layers().size())}}});
|
||||
}
|
||||
AttributeStorage grease_pencil_convert_customdata_to_storage(const GreasePencil &grease_pencil)
|
||||
{
|
||||
return attribute_legacy_convert_customdata_to_storage(
|
||||
{{AttrDomain::Layer, {grease_pencil.layers_data, int(grease_pencil.layers().size())}}});
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
539
source/blender/blenkernel/intern/attribute_storage.cc
Normal file
539
source/blender/blenkernel/intern/attribute_storage.cc
Normal file
@@ -0,0 +1,539 @@
|
||||
/* 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_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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
187
source/blender/blenkernel/intern/attribute_storage_test.cc
Normal file
187
source/blender/blenkernel/intern/attribute_storage_test.cc
Normal file
@@ -0,0 +1,187 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_attribute_storage.hh"
|
||||
|
||||
namespace blender::bke::tests {
|
||||
|
||||
TEST(attribute_storage, Empty)
|
||||
{
|
||||
AttributeStorage storage;
|
||||
int count = 0;
|
||||
storage.foreach([&](const Attribute & /*attribute*/) { count++; });
|
||||
EXPECT_EQ(count, 0);
|
||||
}
|
||||
|
||||
TEST(attribute_storage, Single)
|
||||
{
|
||||
AttributeStorage storage;
|
||||
|
||||
auto *sharing_info = new ImplicitSharedValue<Array<float>>(Span<float>{1.5f, 1.2f, 1.1f, 1.0f});
|
||||
Attribute::ArrayData data{};
|
||||
data.sharing_info = ImplicitSharingPtr<>(sharing_info);
|
||||
data.data = sharing_info->data.data();
|
||||
data.size = 4;
|
||||
storage.add("foo", AttrDomain::Corner, AttrType::Float, std::move(data));
|
||||
|
||||
EXPECT_TRUE(storage.lookup("foo"));
|
||||
EXPECT_EQ(storage.lookup("foo")->domain(), AttrDomain::Corner);
|
||||
EXPECT_EQ(storage.lookup("foo")->data_type(), AttrType::Float);
|
||||
{
|
||||
const auto &data = std::get<Attribute::ArrayData>(storage.lookup("foo")->data());
|
||||
EXPECT_EQ(data.data, sharing_info->data.data());
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
storage.foreach([&](const Attribute & /*attribute*/) { count++; });
|
||||
EXPECT_EQ(count, 1);
|
||||
}
|
||||
|
||||
TEST(attribute_storage, GetForWrite)
|
||||
{
|
||||
AttributeStorage storage;
|
||||
|
||||
auto *sharing_info = new ImplicitSharedValue<Array<float>>(Span<float>{1.5f, 1.2f, 1.1f, 1.0f});
|
||||
Attribute::ArrayData data{};
|
||||
data.sharing_info = ImplicitSharingPtr<>(sharing_info);
|
||||
data.data = sharing_info->data.data();
|
||||
data.size = 4;
|
||||
storage.add("foo", AttrDomain::Corner, AttrType::Float, std::move(data));
|
||||
{
|
||||
const auto &data = std::get<Attribute::ArrayData>(storage.lookup("foo")->data_for_write());
|
||||
EXPECT_EQ(data.data, sharing_info->data.data());
|
||||
}
|
||||
{
|
||||
sharing_info->add_user();
|
||||
const auto &data = std::get<Attribute::ArrayData>(storage.lookup("foo")->data_for_write());
|
||||
EXPECT_NE(data.data, sharing_info->data.data());
|
||||
const float *data_ptr = static_cast<const float *>(data.data);
|
||||
EXPECT_EQ(data_ptr[0], 1.5f);
|
||||
EXPECT_EQ(data_ptr[1], 1.2f);
|
||||
EXPECT_EQ(data_ptr[2], 1.1f);
|
||||
EXPECT_EQ(data_ptr[3], 1.0f);
|
||||
sharing_info->remove_user_and_delete_if_last();
|
||||
}
|
||||
{
|
||||
const auto &data = std::get<Attribute::ArrayData>(storage.lookup("foo")->data_for_write());
|
||||
const float *data_ptr = static_cast<const float *>(data.data);
|
||||
EXPECT_EQ(data_ptr[0], 1.5f);
|
||||
EXPECT_EQ(data_ptr[1], 1.2f);
|
||||
EXPECT_EQ(data_ptr[2], 1.1f);
|
||||
EXPECT_EQ(data_ptr[3], 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(attribute_storage, MultipleShared)
|
||||
{
|
||||
AttributeStorage storage;
|
||||
|
||||
auto *sharing_info = new ImplicitSharedValue<Array<float>>(Span<float>{1.5f, 1.2f, 1.1f, 1.0f});
|
||||
Attribute::ArrayData data{};
|
||||
data.sharing_info = ImplicitSharingPtr<>(sharing_info);
|
||||
data.data = sharing_info->data.data();
|
||||
data.size = 4;
|
||||
storage.add("we", AttrDomain::Corner, AttrType::Float, data);
|
||||
storage.add("need", AttrDomain::Point, AttrType::Float, data);
|
||||
storage.add("more", AttrDomain::Face, AttrType::Float, data);
|
||||
storage.add("data", AttrDomain::Edge, AttrType::Float, data);
|
||||
|
||||
/* The same data is shared among 4 attributes (as well as the original `data`). */
|
||||
EXPECT_EQ(sharing_info->strong_users(), 5);
|
||||
storage.add("final!", AttrDomain::Edge, AttrType::Float, std::move(data));
|
||||
EXPECT_EQ(sharing_info->strong_users(), 5);
|
||||
|
||||
{
|
||||
const auto &data = std::get<Attribute::ArrayData>(storage.lookup("more")->data_for_write());
|
||||
const float *data_ptr = static_cast<const float *>(data.data);
|
||||
EXPECT_EQ(data_ptr[0], 1.5f);
|
||||
EXPECT_EQ(data_ptr[1], 1.2f);
|
||||
EXPECT_EQ(data_ptr[2], 1.1f);
|
||||
EXPECT_EQ(data_ptr[3], 1.0f);
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
storage.foreach([&](const Attribute & /*attribute*/) { count++; });
|
||||
EXPECT_EQ(count, 5);
|
||||
}
|
||||
|
||||
TEST(attribute_storage, CopyConstruct)
|
||||
{
|
||||
AttributeStorage storage;
|
||||
|
||||
auto *sharing_info = new ImplicitSharedValue<Array<float>>(Span<float>{1.5f, 1.2f, 1.1f, 1.0f});
|
||||
Attribute::ArrayData data{};
|
||||
data.sharing_info = ImplicitSharingPtr<>(sharing_info);
|
||||
data.data = sharing_info->data.data();
|
||||
data.size = 4;
|
||||
storage.add("foo", AttrDomain::Corner, AttrType::Float, std::move(data));
|
||||
|
||||
AttributeStorage copy{storage};
|
||||
|
||||
EXPECT_TRUE(copy.lookup("foo"));
|
||||
EXPECT_EQ(copy.lookup("foo")->domain(), AttrDomain::Corner);
|
||||
EXPECT_EQ(copy.lookup("foo")->data_type(), AttrType::Float);
|
||||
{
|
||||
const auto &data = std::get<Attribute::ArrayData>(copy.lookup("foo")->data());
|
||||
/* The data is shared, so it should be the same as the original. */
|
||||
EXPECT_EQ(data.data, sharing_info->data.data());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(attribute_storage, MoveConstruct)
|
||||
{
|
||||
AttributeStorage storage;
|
||||
|
||||
auto *sharing_info = new ImplicitSharedValue<Array<float>>(Span<float>{1.5f, 1.2f, 1.1f, 1.0f});
|
||||
Attribute::ArrayData data{};
|
||||
data.sharing_info = ImplicitSharingPtr<>(sharing_info);
|
||||
data.data = sharing_info->data.data();
|
||||
data.size = 4;
|
||||
storage.add("foo", AttrDomain::Corner, AttrType::Float, std::move(data));
|
||||
|
||||
AttributeStorage copy{std::move(storage)};
|
||||
|
||||
EXPECT_TRUE(copy.lookup("foo"));
|
||||
EXPECT_EQ(copy.lookup("foo")->domain(), AttrDomain::Corner);
|
||||
EXPECT_EQ(copy.lookup("foo")->data_type(), AttrType::Float);
|
||||
{
|
||||
const auto &data = std::get<Attribute::ArrayData>(copy.lookup("foo")->data());
|
||||
/* The data is shared, so it should be the same as the original. */
|
||||
EXPECT_EQ(data.data, sharing_info->data.data());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(attribute_storage, UniqueNames)
|
||||
{
|
||||
AttributeStorage storage;
|
||||
|
||||
auto create_array_data = []() {
|
||||
auto *sharing_info = new ImplicitSharedValue<Array<float>>(
|
||||
Span<float>{1.5f, 1.2f, 1.1f, 1.0f});
|
||||
Attribute::ArrayData data{};
|
||||
data.sharing_info = ImplicitSharingPtr<>(sharing_info);
|
||||
data.data = sharing_info->data.data();
|
||||
data.size = 4;
|
||||
return data;
|
||||
};
|
||||
|
||||
storage.add("foo", AttrDomain::Corner, AttrType::Float, create_array_data());
|
||||
storage.add("foo_2", AttrDomain::Face, AttrType::Float, create_array_data());
|
||||
storage.add("foo_3", AttrDomain::Point, AttrType::Float, create_array_data());
|
||||
storage.add(
|
||||
storage.unique_name_calc("foo"), AttrDomain::Edge, AttrType::Float, create_array_data());
|
||||
storage.add(
|
||||
storage.unique_name_calc("foo"), AttrDomain::Corner, AttrType::Float, create_array_data());
|
||||
storage.add(
|
||||
storage.unique_name_calc("foo_2"), AttrDomain::Point, AttrType::Float, create_array_data());
|
||||
|
||||
int count = 0;
|
||||
storage.foreach([&](const Attribute & /*attribute*/) { count++; });
|
||||
EXPECT_EQ(count, 6);
|
||||
}
|
||||
|
||||
} // namespace blender::bke::tests
|
||||
@@ -19,13 +19,16 @@
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_rand.hh"
|
||||
#include "BLI_resource_scope.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_anim_data.hh"
|
||||
#include "BKE_attribute_legacy_convert.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_customdata.hh"
|
||||
#include "BKE_geometry_fields.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_idtype.hh"
|
||||
@@ -108,8 +111,9 @@ static void curves_blend_write(BlendWriter *writer, ID *id, const void *id_addre
|
||||
/* Only for forward compatibility. */
|
||||
curves->attributes_active_index_legacy = curves->geometry.attributes_active_index;
|
||||
|
||||
blender::bke::CurvesGeometry::BlendWriteData write_data =
|
||||
curves->geometry.wrap().blend_write_prepare();
|
||||
blender::ResourceScope scope;
|
||||
blender::bke::CurvesGeometry::BlendWriteData write_data(scope);
|
||||
curves->geometry.wrap().blend_write_prepare(write_data);
|
||||
|
||||
/* Write LibData */
|
||||
BLO_write_id_struct(writer, Curves, id_address, &curves->id);
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
#include "DNA_material_types.h"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_attribute_legacy_convert.hh"
|
||||
#include "BKE_attribute_math.hh"
|
||||
#include "BKE_attribute_storage.hh"
|
||||
#include "BKE_attribute_storage_blend_write.hh"
|
||||
#include "BKE_bake_data_block_id.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_curves_utils.hh"
|
||||
@@ -65,6 +68,7 @@ CurvesGeometry::CurvesGeometry(const int point_num, const int curve_num)
|
||||
this->curve_num = curve_num;
|
||||
CustomData_reset(&this->point_data);
|
||||
CustomData_reset(&this->curve_data);
|
||||
new (&this->attribute_storage.wrap()) blender::bke::AttributeStorage();
|
||||
BLI_listbase_clear(&this->vertex_group_names);
|
||||
|
||||
this->attributes_for_write().add<float3>(
|
||||
@@ -108,6 +112,8 @@ CurvesGeometry::CurvesGeometry(const CurvesGeometry &other)
|
||||
CustomData_init_from(&other.point_data, &this->point_data, CD_MASK_ALL, other.point_num);
|
||||
CustomData_init_from(&other.curve_data, &this->curve_data, CD_MASK_ALL, other.curve_num);
|
||||
|
||||
new (&this->attribute_storage.wrap()) AttributeStorage(other.attribute_storage.wrap());
|
||||
|
||||
this->point_num = other.point_num;
|
||||
this->curve_num = other.curve_num;
|
||||
|
||||
@@ -167,6 +173,9 @@ CurvesGeometry::CurvesGeometry(CurvesGeometry &&other)
|
||||
this->curve_data = other.curve_data;
|
||||
CustomData_reset(&other.curve_data);
|
||||
|
||||
new (&this->attribute_storage.wrap())
|
||||
AttributeStorage(std::move(other.attribute_storage.wrap()));
|
||||
|
||||
this->point_num = other.point_num;
|
||||
other.point_num = 0;
|
||||
|
||||
@@ -200,6 +209,7 @@ CurvesGeometry::~CurvesGeometry()
|
||||
{
|
||||
CustomData_free(&this->point_data);
|
||||
CustomData_free(&this->curve_data);
|
||||
this->attribute_storage.wrap().~AttributeStorage();
|
||||
BLI_freelistN(&this->vertex_group_names);
|
||||
if (this->runtime) {
|
||||
implicit_sharing::free_shared_data(&this->curve_offsets,
|
||||
@@ -1844,6 +1854,7 @@ void CurvesGeometry::blend_read(BlendDataReader &reader)
|
||||
|
||||
CustomData_blend_read(&reader, &this->point_data, this->point_num);
|
||||
CustomData_blend_read(&reader, &this->curve_data, this->curve_num);
|
||||
this->attribute_storage.wrap().blend_read(reader);
|
||||
|
||||
if (this->curve_offsets) {
|
||||
this->runtime->curve_offsets_sharing_info = BLO_read_shared(
|
||||
@@ -1853,6 +1864,9 @@ void CurvesGeometry::blend_read(BlendDataReader &reader)
|
||||
});
|
||||
}
|
||||
|
||||
/* Forward compatibility. To be removed when runtime format changes. */
|
||||
curves_convert_storage_to_customdata(*this);
|
||||
|
||||
BLO_read_struct_list(&reader, bDeformGroup, &this->vertex_group_names);
|
||||
|
||||
if (this->custom_knot_num) {
|
||||
@@ -1867,12 +1881,24 @@ void CurvesGeometry::blend_read(BlendDataReader &reader)
|
||||
this->update_curve_types();
|
||||
}
|
||||
|
||||
CurvesGeometry::BlendWriteData CurvesGeometry::blend_write_prepare()
|
||||
void CurvesGeometry::blend_write_prepare(CurvesGeometry::BlendWriteData &write_data)
|
||||
{
|
||||
CurvesGeometry::BlendWriteData write_data;
|
||||
CustomData_blend_write_prepare(this->point_data, write_data.point_layers);
|
||||
CustomData_blend_write_prepare(this->curve_data, write_data.curve_layers);
|
||||
return write_data;
|
||||
attribute_storage_blend_write_prepare(this->attribute_storage.wrap(),
|
||||
{{AttrDomain::Point, &write_data.point_layers},
|
||||
{AttrDomain::Curve, &write_data.curve_layers}},
|
||||
write_data.attribute_data);
|
||||
CustomData_blend_write_prepare(this->point_data,
|
||||
AttrDomain::Point,
|
||||
this->points_num(),
|
||||
write_data.point_layers,
|
||||
write_data.attribute_data);
|
||||
CustomData_blend_write_prepare(this->curve_data,
|
||||
AttrDomain::Curve,
|
||||
this->curves_num(),
|
||||
write_data.curve_layers,
|
||||
write_data.attribute_data);
|
||||
this->attribute_storage.dna_attributes = write_data.attribute_data.attributes.data();
|
||||
this->attribute_storage.dna_attributes_num = write_data.attribute_data.attributes.size();
|
||||
}
|
||||
|
||||
void CurvesGeometry::blend_write(BlendWriter &writer,
|
||||
@@ -1883,6 +1909,7 @@ void CurvesGeometry::blend_write(BlendWriter &writer,
|
||||
&writer, &this->point_data, write_data.point_layers, this->point_num, CD_MASK_ALL, &id);
|
||||
CustomData_blend_write(
|
||||
&writer, &this->curve_data, write_data.curve_layers, this->curve_num, CD_MASK_ALL, &id);
|
||||
this->attribute_storage.wrap().blend_write(writer, write_data.attribute_data);
|
||||
|
||||
if (this->curve_offsets) {
|
||||
BLO_write_shared(
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "BLI_memory_counter.hh"
|
||||
#include "BLI_mempool.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_resource_scope.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string.h"
|
||||
@@ -44,7 +45,9 @@
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_attribute_legacy_convert.hh"
|
||||
#include "BKE_attribute_math.hh"
|
||||
#include "BKE_attribute_storage.hh"
|
||||
#include "BKE_customdata.hh"
|
||||
#include "BKE_customdata_file.h"
|
||||
#include "BKE_deform.hh"
|
||||
@@ -5113,18 +5116,47 @@ static void get_type_file_write_info(const eCustomDataType type,
|
||||
}
|
||||
|
||||
void CustomData_blend_write_prepare(CustomData &data,
|
||||
const blender::bke::AttrDomain domain,
|
||||
const int domain_size,
|
||||
Vector<CustomDataLayer, 16> &layers_to_write,
|
||||
const Set<std::string> &skip_names)
|
||||
blender::bke::AttributeStorage::BlendWriteData &write_data)
|
||||
{
|
||||
using namespace blender::bke;
|
||||
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
|
||||
if (layer.flag & CD_FLAG_NOCOPY) {
|
||||
continue;
|
||||
}
|
||||
if (blender::bke::attribute_name_is_anonymous(layer.name)) {
|
||||
const StringRef name = layer.name;
|
||||
if (attribute_name_is_anonymous(name)) {
|
||||
continue;
|
||||
}
|
||||
if (skip_names.contains(layer.name)) {
|
||||
continue;
|
||||
if (U.experimental.use_attribute_storage_write) {
|
||||
/* When this experimental option is turned on, we always write the data in the new
|
||||
* #AttributeStorage format, even though it's not yet used at runtime. This is meant for
|
||||
* testing the forward compatibility capabilities meant to be shipped in version 4.5, in
|
||||
* other words, the ability to read #AttributeStorage and convert it to #CustomData. This
|
||||
* block should be removed when the new format is used at runtime. */
|
||||
const eCustomDataType data_type = eCustomDataType(layer.type);
|
||||
if (const std::optional<AttrType> type = custom_data_type_to_attr_type(data_type)) {
|
||||
::Attribute attribute_dna{};
|
||||
attribute_dna.name = layer.name;
|
||||
attribute_dna.data_type = int16_t(*type);
|
||||
attribute_dna.domain = int8_t(domain);
|
||||
attribute_dna.storage_type = int8_t(AttrStorageType::Array);
|
||||
|
||||
/* Do not increase the user count; #::AttributeArray does not act as an owner of the
|
||||
* attribute data, since it's only used temporarily for writing files. Changing the user
|
||||
* count would be okay too, but it's unnecessary because none of this data should be
|
||||
* modified while it's being written anyway. */
|
||||
auto &array_dna = write_data.scope.construct<::AttributeArray>();
|
||||
array_dna.data = layer.data;
|
||||
array_dna.sharing_info = layer.sharing_info;
|
||||
array_dna.size = domain_size;
|
||||
attribute_dna.data = &array_dna;
|
||||
|
||||
write_data.attributes.append(attribute_dna);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
layers_to_write.append(layer);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#include "BKE_anim_data.hh"
|
||||
#include "BKE_animsys.h"
|
||||
#include "BKE_asset_edit.hh"
|
||||
#include "BKE_attribute_legacy_convert.hh"
|
||||
#include "BKE_attribute_storage.hh"
|
||||
#include "BKE_attribute_storage_blend_write.hh"
|
||||
#include "BKE_bake_data_block_id.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_customdata.hh"
|
||||
@@ -44,6 +47,7 @@
|
||||
#include "BLI_memarena.h"
|
||||
#include "BLI_memory_utils.hh"
|
||||
#include "BLI_polyfill_2d.h"
|
||||
#include "BLI_resource_scope.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_stack.hh"
|
||||
#include "BLI_string.h"
|
||||
@@ -83,7 +87,9 @@ static const char *ATTR_POSITION = "position";
|
||||
|
||||
/* Forward declarations. */
|
||||
static void read_drawing_array(GreasePencil &grease_pencil, BlendDataReader *reader);
|
||||
static void write_drawing_array(GreasePencil &grease_pencil, BlendWriter *writer);
|
||||
static void write_drawing_array(GreasePencil &grease_pencil,
|
||||
blender::ResourceScope &scope,
|
||||
BlendWriter *writer);
|
||||
static void free_drawing_array(GreasePencil &grease_pencil);
|
||||
|
||||
static void read_layer_tree(GreasePencil &grease_pencil, BlendDataReader *reader);
|
||||
@@ -102,6 +108,7 @@ static void grease_pencil_init_data(ID *id)
|
||||
grease_pencil->set_active_node(nullptr);
|
||||
|
||||
CustomData_reset(&grease_pencil->layers_data);
|
||||
new (&grease_pencil->attribute_storage.wrap()) blender::bke::AttributeStorage();
|
||||
|
||||
grease_pencil->runtime = MEM_new<GreasePencilRuntime>(__func__);
|
||||
}
|
||||
@@ -197,6 +204,8 @@ static void grease_pencil_copy_data(Main * /*bmain*/,
|
||||
&grease_pencil_dst->layers_data,
|
||||
CD_MASK_ALL,
|
||||
grease_pencil_dst->layers().size());
|
||||
new (&grease_pencil_dst->attribute_storage.wrap())
|
||||
blender::bke::AttributeStorage(grease_pencil_src->attribute_storage.wrap());
|
||||
|
||||
BKE_defgroup_copy_list(&grease_pencil_dst->vertex_group_names,
|
||||
&grease_pencil_src->vertex_group_names);
|
||||
@@ -220,6 +229,7 @@ static void grease_pencil_free_data(ID *id)
|
||||
MEM_SAFE_FREE(grease_pencil->material_array);
|
||||
|
||||
CustomData_free(&grease_pencil->layers_data);
|
||||
grease_pencil->attribute_storage.wrap().~AttributeStorage();
|
||||
|
||||
free_drawing_array(*grease_pencil);
|
||||
MEM_delete(&grease_pencil->root_group());
|
||||
@@ -254,10 +264,24 @@ static void grease_pencil_foreach_id(ID *id, LibraryForeachIDData *data)
|
||||
|
||||
static void grease_pencil_blend_write(BlendWriter *writer, ID *id, const void *id_address)
|
||||
{
|
||||
using namespace blender;
|
||||
using namespace blender::bke;
|
||||
GreasePencil *grease_pencil = reinterpret_cast<GreasePencil *>(id);
|
||||
|
||||
blender::ResourceScope scope;
|
||||
|
||||
blender::Vector<CustomDataLayer, 16> layers_data_layers;
|
||||
CustomData_blend_write_prepare(grease_pencil->layers_data, layers_data_layers);
|
||||
blender::bke::AttributeStorage::BlendWriteData attribute_data{scope};
|
||||
attribute_storage_blend_write_prepare(grease_pencil->attribute_storage.wrap(),
|
||||
{{AttrDomain::Layer, &layers_data_layers}},
|
||||
attribute_data);
|
||||
CustomData_blend_write_prepare(grease_pencil->layers_data,
|
||||
AttrDomain::Layer,
|
||||
grease_pencil->layers().size(),
|
||||
layers_data_layers,
|
||||
attribute_data);
|
||||
grease_pencil->attribute_storage.dna_attributes = attribute_data.attributes.data();
|
||||
grease_pencil->attribute_storage.dna_attributes_num = attribute_data.attributes.size();
|
||||
|
||||
/* Write LibData */
|
||||
BLO_write_id_struct(writer, GreasePencil, id_address, &grease_pencil->id);
|
||||
@@ -269,9 +293,10 @@ static void grease_pencil_blend_write(BlendWriter *writer, ID *id, const void *i
|
||||
grease_pencil->layers().size(),
|
||||
CD_MASK_ALL,
|
||||
id);
|
||||
grease_pencil->attribute_storage.wrap().blend_write(*writer, attribute_data);
|
||||
|
||||
/* Write drawings. */
|
||||
write_drawing_array(*grease_pencil, writer);
|
||||
write_drawing_array(*grease_pencil, scope, writer);
|
||||
/* Write layer tree. */
|
||||
write_layer_tree(*grease_pencil, writer);
|
||||
|
||||
@@ -293,6 +318,10 @@ static void grease_pencil_blend_read_data(BlendDataReader *reader, ID *id)
|
||||
read_layer_tree(*grease_pencil, reader);
|
||||
|
||||
CustomData_blend_read(reader, &grease_pencil->layers_data, grease_pencil->layers().size());
|
||||
grease_pencil->attribute_storage.wrap().blend_read(*reader);
|
||||
|
||||
/* Forward compatibility. To be removed when runtime format changes. */
|
||||
blender::bke::grease_pencil_convert_storage_to_customdata(*grease_pencil);
|
||||
|
||||
/* Read materials. */
|
||||
BLO_read_pointer_array(reader,
|
||||
@@ -4212,7 +4241,9 @@ static void read_drawing_array(GreasePencil &grease_pencil, BlendDataReader *rea
|
||||
}
|
||||
}
|
||||
|
||||
static void write_drawing_array(GreasePencil &grease_pencil, BlendWriter *writer)
|
||||
static void write_drawing_array(GreasePencil &grease_pencil,
|
||||
blender::ResourceScope &scope,
|
||||
BlendWriter *writer)
|
||||
{
|
||||
using namespace blender;
|
||||
BLO_write_pointer_array(writer, grease_pencil.drawing_array_num, grease_pencil.drawing_array);
|
||||
@@ -4221,10 +4252,13 @@ static void write_drawing_array(GreasePencil &grease_pencil, BlendWriter *writer
|
||||
switch (GreasePencilDrawingType(drawing_base->type)) {
|
||||
case GP_DRAWING: {
|
||||
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
|
||||
bke::CurvesGeometry::BlendWriteData write_data =
|
||||
drawing->wrap().strokes_for_write().blend_write_prepare();
|
||||
bke::CurvesGeometry &curves = drawing->wrap().strokes_for_write();
|
||||
|
||||
bke::CurvesGeometry::BlendWriteData write_data(scope);
|
||||
curves.blend_write_prepare(write_data);
|
||||
|
||||
BLO_write_struct(writer, GreasePencilDrawing, drawing);
|
||||
drawing->wrap().strokes_for_write().blend_write(*writer, grease_pencil.id, write_data);
|
||||
curves.blend_write(*writer, grease_pencil.id, write_data);
|
||||
break;
|
||||
}
|
||||
case GP_DRAWING_REFERENCE: {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_memory_counter.hh"
|
||||
#include "BLI_resource_scope.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string.h"
|
||||
@@ -43,6 +44,9 @@
|
||||
#include "BKE_anim_data.hh"
|
||||
#include "BKE_anonymous_attribute_id.hh"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_attribute_legacy_convert.hh"
|
||||
#include "BKE_attribute_storage.hh"
|
||||
#include "BKE_attribute_storage_blend_write.hh"
|
||||
#include "BKE_bake_data_block_id.hh"
|
||||
#include "BKE_bpath.hh"
|
||||
#include "BKE_deform.hh"
|
||||
@@ -93,6 +97,7 @@ static void mesh_init_data(ID *id)
|
||||
CustomData_reset(&mesh->face_data);
|
||||
CustomData_reset(&mesh->corner_data);
|
||||
|
||||
new (&mesh->attribute_storage.wrap()) blender::bke::AttributeStorage();
|
||||
mesh->runtime = new blender::bke::MeshRuntime();
|
||||
|
||||
mesh->face_sets_color_seed = BLI_hash_int(BLI_time_now_seconds_i() & UINT_MAX);
|
||||
@@ -205,6 +210,8 @@ static void mesh_copy_data(Main *bmain,
|
||||
&mesh_src->corner_data, &mesh_dst->corner_data, mask.lmask, mesh_dst->corners_num);
|
||||
CustomData_init_from(
|
||||
&mesh_src->face_data, &mesh_dst->face_data, mask.pmask, mesh_dst->faces_num);
|
||||
new (&mesh_dst->attribute_storage.wrap())
|
||||
blender::bke::AttributeStorage(mesh_src->attribute_storage.wrap());
|
||||
blender::implicit_sharing::copy_shared_pointer(mesh_src->face_offset_indices,
|
||||
mesh_src->runtime->face_offsets_sharing_info,
|
||||
&mesh_dst->face_offset_indices,
|
||||
@@ -241,6 +248,7 @@ static void mesh_free_data(ID *id)
|
||||
BLI_freelistN(&mesh->vertex_group_names);
|
||||
MEM_SAFE_FREE(mesh->active_color_attribute);
|
||||
MEM_SAFE_FREE(mesh->default_color_attribute);
|
||||
mesh->attribute_storage.wrap().~AttributeStorage();
|
||||
if (mesh->face_offset_indices) {
|
||||
blender::implicit_sharing::free_shared_data(&mesh->face_offset_indices,
|
||||
&mesh->runtime->face_offsets_sharing_info);
|
||||
@@ -330,10 +338,12 @@ static void mesh_blend_write(BlendWriter *writer, ID *id, const void *id_address
|
||||
Mesh *mesh = reinterpret_cast<Mesh *>(id);
|
||||
const bool is_undo = BLO_write_is_undo(writer);
|
||||
|
||||
ResourceScope scope;
|
||||
Vector<CustomDataLayer, 16> vert_layers;
|
||||
Vector<CustomDataLayer, 16> edge_layers;
|
||||
Vector<CustomDataLayer, 16> loop_layers;
|
||||
Vector<CustomDataLayer, 16> face_layers;
|
||||
bke::AttributeStorage::BlendWriteData attribute_data{scope};
|
||||
|
||||
/* Cache only - don't write. */
|
||||
mesh->mface = nullptr;
|
||||
@@ -356,10 +366,22 @@ static void mesh_blend_write(BlendWriter *writer, ID *id, const void *id_address
|
||||
mesh->face_offset_indices = nullptr;
|
||||
}
|
||||
else {
|
||||
CustomData_blend_write_prepare(mesh->vert_data, vert_layers, {});
|
||||
CustomData_blend_write_prepare(mesh->edge_data, edge_layers, {});
|
||||
CustomData_blend_write_prepare(mesh->corner_data, loop_layers, {});
|
||||
CustomData_blend_write_prepare(mesh->face_data, face_layers, {});
|
||||
attribute_storage_blend_write_prepare(mesh->attribute_storage.wrap(),
|
||||
{{AttrDomain::Point, &vert_layers},
|
||||
{AttrDomain::Edge, &edge_layers},
|
||||
{AttrDomain::Face, &face_layers},
|
||||
{AttrDomain::Corner, &loop_layers}},
|
||||
attribute_data);
|
||||
CustomData_blend_write_prepare(
|
||||
mesh->vert_data, AttrDomain::Point, mesh->verts_num, vert_layers, attribute_data);
|
||||
CustomData_blend_write_prepare(
|
||||
mesh->edge_data, AttrDomain::Edge, mesh->edges_num, edge_layers, attribute_data);
|
||||
CustomData_blend_write_prepare(
|
||||
mesh->face_data, AttrDomain::Face, mesh->faces_num, face_layers, attribute_data);
|
||||
CustomData_blend_write_prepare(
|
||||
mesh->corner_data, AttrDomain::Corner, mesh->corners_num, loop_layers, attribute_data);
|
||||
mesh->attribute_storage.dna_attributes = attribute_data.attributes.data();
|
||||
mesh->attribute_storage.dna_attributes_num = attribute_data.attributes.size();
|
||||
if (!is_undo) {
|
||||
/* Write forward compatible format. To be removed in 5.0. */
|
||||
rename_seam_layer_to_old_name(
|
||||
@@ -394,6 +416,8 @@ static void mesh_blend_write(BlendWriter *writer, ID *id, const void *id_address
|
||||
CustomData_blend_write(
|
||||
writer, &mesh->face_data, face_layers, mesh->faces_num, CD_MASK_MESH.pmask, &mesh->id);
|
||||
|
||||
mesh->attribute_storage.wrap().blend_write(*writer, attribute_data);
|
||||
|
||||
if (mesh->face_offset_indices) {
|
||||
BLO_write_shared(
|
||||
writer,
|
||||
@@ -432,6 +456,7 @@ static void mesh_blend_read_data(BlendDataReader *reader, ID *id)
|
||||
CustomData_blend_read(reader, &mesh->fdata_legacy, mesh->totface_legacy);
|
||||
CustomData_blend_read(reader, &mesh->corner_data, mesh->corners_num);
|
||||
CustomData_blend_read(reader, &mesh->face_data, mesh->faces_num);
|
||||
mesh->attribute_storage.wrap().blend_read(*reader);
|
||||
if (mesh->deform_verts().is_empty()) {
|
||||
/* Vertex group data was also an owning pointer in old Blender versions.
|
||||
* Don't read them again if they were read as part of #CustomData. */
|
||||
@@ -440,6 +465,9 @@ static void mesh_blend_read_data(BlendDataReader *reader, ID *id)
|
||||
BLO_read_string(reader, &mesh->active_color_attribute);
|
||||
BLO_read_string(reader, &mesh->default_color_attribute);
|
||||
|
||||
/* Forward compatibility. To be removed when runtime format changes. */
|
||||
blender::bke::mesh_convert_storage_to_customdata(*mesh);
|
||||
|
||||
mesh->texspace_flag &= ~ME_TEXSPACE_FLAG_AUTO_EVALUATED;
|
||||
|
||||
mesh->runtime = new blender::bke::MeshRuntime();
|
||||
@@ -629,6 +657,7 @@ static void mesh_clear_geometry(Mesh &mesh)
|
||||
CustomData_free(&mesh.fdata_legacy);
|
||||
CustomData_free(&mesh.corner_data);
|
||||
CustomData_free(&mesh.face_data);
|
||||
mesh.attribute_storage.wrap() = blender::bke::AttributeStorage();
|
||||
if (mesh.face_offset_indices) {
|
||||
blender::implicit_sharing::free_shared_data(&mesh.face_offset_indices,
|
||||
&mesh.runtime->face_offsets_sharing_info);
|
||||
|
||||
@@ -18,11 +18,15 @@
|
||||
#include "BLI_bounds.hh"
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_rand.h"
|
||||
#include "BLI_resource_scope.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_anim_data.hh"
|
||||
#include "BKE_attribute_legacy_convert.hh"
|
||||
#include "BKE_attribute_storage.hh"
|
||||
#include "BKE_attribute_storage_blend_write.hh"
|
||||
#include "BKE_bake_data_block_id.hh"
|
||||
#include "BKE_customdata.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
@@ -63,6 +67,7 @@ static void pointcloud_init_data(ID *id)
|
||||
|
||||
MEMCPY_STRUCT_AFTER(pointcloud, DNA_struct_default_get(PointCloud), id);
|
||||
|
||||
new (&pointcloud->attribute_storage.wrap()) blender::bke::AttributeStorage();
|
||||
pointcloud->runtime = new blender::bke::PointCloudRuntime();
|
||||
|
||||
CustomData_reset(&pointcloud->pdata);
|
||||
@@ -82,6 +87,8 @@ static void pointcloud_copy_data(Main * /*bmain*/,
|
||||
|
||||
CustomData_init_from(
|
||||
&pointcloud_src->pdata, &pointcloud_dst->pdata, CD_MASK_ALL, pointcloud_dst->totpoint);
|
||||
new (&pointcloud_dst->attribute_storage.wrap())
|
||||
blender::bke::AttributeStorage(pointcloud_src->attribute_storage.wrap());
|
||||
|
||||
pointcloud_dst->runtime = new blender::bke::PointCloudRuntime();
|
||||
pointcloud_dst->runtime->bounds_cache = pointcloud_src->runtime->bounds_cache;
|
||||
@@ -103,6 +110,7 @@ static void pointcloud_free_data(ID *id)
|
||||
BKE_animdata_free(&pointcloud->id, false);
|
||||
BKE_pointcloud_batch_cache_free(pointcloud);
|
||||
CustomData_free(&pointcloud->pdata);
|
||||
pointcloud->attribute_storage.wrap().~AttributeStorage();
|
||||
MEM_SAFE_FREE(pointcloud->mat);
|
||||
delete pointcloud->runtime;
|
||||
}
|
||||
@@ -117,10 +125,19 @@ static void pointcloud_foreach_id(ID *id, LibraryForeachIDData *data)
|
||||
|
||||
static void pointcloud_blend_write(BlendWriter *writer, ID *id, const void *id_address)
|
||||
{
|
||||
using namespace blender;
|
||||
using namespace blender::bke;
|
||||
PointCloud *pointcloud = (PointCloud *)id;
|
||||
|
||||
ResourceScope scope;
|
||||
Vector<CustomDataLayer, 16> point_layers;
|
||||
CustomData_blend_write_prepare(pointcloud->pdata, point_layers);
|
||||
bke::AttributeStorage::BlendWriteData attribute_data{scope};
|
||||
attribute_storage_blend_write_prepare(
|
||||
pointcloud->attribute_storage.wrap(), {{AttrDomain::Point, &point_layers}}, attribute_data);
|
||||
CustomData_blend_write_prepare(
|
||||
pointcloud->pdata, AttrDomain::Point, pointcloud->totpoint, point_layers, attribute_data);
|
||||
pointcloud->attribute_storage.dna_attributes = attribute_data.attributes.data();
|
||||
pointcloud->attribute_storage.dna_attributes_num = attribute_data.attributes.size();
|
||||
|
||||
/* Write LibData */
|
||||
BLO_write_id_struct(writer, PointCloud, id_address, &pointcloud->id);
|
||||
@@ -133,6 +150,7 @@ static void pointcloud_blend_write(BlendWriter *writer, ID *id, const void *id_a
|
||||
pointcloud->totpoint,
|
||||
CD_MASK_ALL,
|
||||
&pointcloud->id);
|
||||
pointcloud->attribute_storage.wrap().blend_write(*writer, attribute_data);
|
||||
|
||||
BLO_write_pointer_array(writer, pointcloud->totcol, pointcloud->mat);
|
||||
}
|
||||
@@ -143,6 +161,10 @@ static void pointcloud_blend_read_data(BlendDataReader *reader, ID *id)
|
||||
|
||||
/* Geometry */
|
||||
CustomData_blend_read(reader, &pointcloud->pdata, pointcloud->totpoint);
|
||||
pointcloud->attribute_storage.wrap().blend_read(*reader);
|
||||
|
||||
/* Forward compatibility. To be removed when runtime format changes. */
|
||||
blender::bke::pointcloud_convert_storage_to_customdata(*pointcloud);
|
||||
|
||||
/* Materials */
|
||||
BLO_read_pointer_array(reader, pointcloud->totcol, (void **)&pointcloud->mat);
|
||||
|
||||
69
source/blender/makesdna/DNA_attribute_types.h
Normal file
69
source/blender/makesdna/DNA_attribute_types.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "BLI_implicit_sharing.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
namespace blender::bke {
|
||||
class AttributeStorage;
|
||||
class AttributeStorageRuntime;
|
||||
} // namespace blender::bke
|
||||
|
||||
using AttributeStorageRuntimeHandle = blender::bke::AttributeStorageRuntime;
|
||||
#else
|
||||
typedef struct AttributeStorageRuntimeHandle AttributeStorageRuntimeHandle;
|
||||
#endif
|
||||
|
||||
/** DNA data for bke::Attribute::ArrayData. */
|
||||
struct AttributeArray {
|
||||
void *data;
|
||||
const ImplicitSharingInfoHandle *sharing_info;
|
||||
/* The number of elements in the array. */
|
||||
int64_t size;
|
||||
};
|
||||
|
||||
/** DNA data for bke::Attribute::SingleData. */
|
||||
struct AttributeSingle {
|
||||
void *data;
|
||||
const ImplicitSharingInfoHandle *sharing_info;
|
||||
};
|
||||
|
||||
/** DNA data for bke::Attribute. */
|
||||
struct Attribute {
|
||||
const char *name;
|
||||
/* bke::AttrType. */
|
||||
int16_t data_type;
|
||||
/* bke::AttrDomain. */
|
||||
int8_t domain;
|
||||
/* bke::AttrStorageType */
|
||||
int8_t storage_type;
|
||||
char _pad[4];
|
||||
|
||||
/** Type depends on storage type. */
|
||||
void *data;
|
||||
};
|
||||
|
||||
/**
|
||||
* The DNA type for storing attribute values. Logic at runtime is handled by the runtime struct.
|
||||
* The DNA data here is created just for file reading and writing.
|
||||
*/
|
||||
struct AttributeStorage {
|
||||
/* Array only used in files, otherwise #AttributeStorageRuntime::attributes is used. */
|
||||
struct Attribute *dna_attributes;
|
||||
int dna_attributes_num;
|
||||
|
||||
char _pad[4];
|
||||
|
||||
AttributeStorageRuntimeHandle *runtime;
|
||||
|
||||
#ifdef __cplusplus
|
||||
blender::bke::AttributeStorage &wrap();
|
||||
const blender::bke::AttributeStorage &wrap() const;
|
||||
#endif
|
||||
};
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "DNA_ID.h"
|
||||
#include "DNA_attribute_types.h"
|
||||
#include "DNA_customdata_types.h"
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
@@ -116,6 +117,12 @@ typedef struct CurvesGeometry {
|
||||
*/
|
||||
int *curve_offsets;
|
||||
|
||||
/**
|
||||
* Curve and point domain attributes. Currently unused at runtime, but used for forward
|
||||
* compatibility when reading files (see #122398).
|
||||
*/
|
||||
struct AttributeStorage attribute_storage;
|
||||
|
||||
/**
|
||||
* All attributes stored on control points (#AttrDomain::Point).
|
||||
* This might not contain a layer for positions if there are no points.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "DNA_ID.h"
|
||||
#include "DNA_attribute_types.h"
|
||||
#include "DNA_curve_types.h"
|
||||
#include "DNA_curves_types.h"
|
||||
#include "DNA_listBase.h"
|
||||
@@ -466,6 +467,13 @@ typedef struct GreasePencil {
|
||||
* All attributes stored on the grease pencil layers (#AttrDomain::Layer).
|
||||
*/
|
||||
CustomData layers_data;
|
||||
|
||||
/**
|
||||
* Layer domain attributes. Currently unused at runtime, but used for forward
|
||||
* compatibility when reading files (see #122398).
|
||||
*/
|
||||
struct AttributeStorage attribute_storage;
|
||||
|
||||
/**
|
||||
* The index of the active attribute in the UI.
|
||||
*
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "DNA_ID.h"
|
||||
#include "DNA_attribute_types.h"
|
||||
#include "DNA_customdata_types.h"
|
||||
#include "DNA_defs.h"
|
||||
#include "DNA_session_uid_types.h"
|
||||
@@ -93,6 +94,12 @@ typedef struct Mesh {
|
||||
*/
|
||||
int *face_offset_indices;
|
||||
|
||||
/**
|
||||
* Vertex, edge, face, and corner generic attributes. Currently unused at runtime, but used for
|
||||
* forward compatibility when reading files (see #122398).
|
||||
*/
|
||||
struct AttributeStorage attribute_storage;
|
||||
|
||||
CustomData vert_data;
|
||||
CustomData edge_data;
|
||||
CustomData face_data;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "DNA_ID.h"
|
||||
#include "DNA_attribute_types.h"
|
||||
#include "DNA_customdata_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -50,6 +51,12 @@ typedef struct PointCloud {
|
||||
/* Geometry */
|
||||
int totpoint;
|
||||
|
||||
/**
|
||||
* Storage for generic attributes. Currently unused at runtime, but used for forward
|
||||
* compatibility when reading files (see #122398).
|
||||
*/
|
||||
struct AttributeStorage attribute_storage;
|
||||
|
||||
/* Custom Data */
|
||||
struct CustomData pdata;
|
||||
/** Set to -1 when none is active. */
|
||||
|
||||
@@ -759,6 +759,7 @@ typedef struct UserDef_Experimental {
|
||||
char use_extensions_debug;
|
||||
char use_recompute_usercount_on_save_debug;
|
||||
char write_large_blend_file_blocks;
|
||||
char use_attribute_storage_write;
|
||||
char SANITIZE_AFTER_HERE;
|
||||
/* The following options are automatically sanitized (set to 0)
|
||||
* when the release cycle is not alpha. */
|
||||
@@ -769,7 +770,7 @@ typedef struct UserDef_Experimental {
|
||||
char use_new_volume_nodes;
|
||||
char use_shader_node_previews;
|
||||
char use_bundle_and_closure_nodes;
|
||||
char _pad[5];
|
||||
char _pad[4];
|
||||
} UserDef_Experimental;
|
||||
|
||||
#define USER_EXPERIMENTAL_TEST(userdef, member) \
|
||||
|
||||
@@ -7635,6 +7635,12 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
|
||||
"Recompute all ID usercounts before saving to a blendfile. Allows to "
|
||||
"work around invalid usercount handling in code that may lead to loss "
|
||||
"of data due to wrongly detected unused data-blocks");
|
||||
|
||||
prop = RNA_def_property(srna, "use_attribute_storage_write", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_ui_text(prop,
|
||||
"Write New Attribute Storage Format",
|
||||
"Instead of writing with the older \"CustomData\" format for forward "
|
||||
"compatibility, use the new \"AttributeStorage\" format");
|
||||
}
|
||||
|
||||
static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
|
||||
Reference in New Issue
Block a user