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
188 lines
6.4 KiB
C++
188 lines
6.4 KiB
C++
/* 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
|