Cycles: Use implicit sharing to avoid copying some attributes

This commit extends Cycles to allow using Attribute array implicit
sharing info from Blender. This is used to avoid a copy of the evaluated
attribute arrays before they're added to the final buffer for their type.
We can only do this when the attribute type layout match between Blender
and Cycles, which means float and float2 attributes can skip the copy on
the point or curve domain, and also the face corner and face domains in
the adaptive subdivision case.

The buffer stored in attributes is now a raw pointer rather than a
std::vector. As a side effect, resizing the attribute no longer
zero-initializes new values. That was wasted work in practice anyway.

Unfortunately float color and float4 attributes are not covered here
because Blender does not allocate these types with the necessary
alignment. That can be addressed later; I've started it here: !156035.

Besides that, there are some other next steps:
- Use implicit sharing to avoid copying data that Cycles doesn't process
  as attributes, like topology arrays for adaptive subdivision (and
  maybe investigate removing padding so that float3 can be shared too).
- Add some sort of global cache to avoid creating multiple Cycles arrays
  for attributes that aren't layout compatible or are on non-matching
  domains.
- Add some way to delay the attribute type conversion and flattening
  until constructing the final arrays, to benefit non-layout-compatible
  types.
- Looking much further into the future, completely avoid the "large
  buffer per data type" system that Cycles uses to hold geometry data
  (just mentioning this because "completely avoid all copies of Blender
  data" is an interesting prospect).

Pull Request: https://projects.blender.org/blender/blender/pulls/155970
This commit is contained in:
Hans Goudey 2026-04-01 15:25:01 +02:00 • committed by Hans Goudey
parent c4a0ca59af
commit 197b0c2b41
15 changed files with 520 additions and 127 deletions

View file

@ -12,6 +12,7 @@ struct Image;
void *CCL_python_module_init(void);
void CCL_log_init(void);
void CCL_implicit_sharing_init(void);
/* Texture cache generation. */

View file

@ -17,6 +17,7 @@ set(SRC
device.cpp
display_driver.cpp
image.cpp
implicit_sharing.cpp
geometry.cpp
light.cpp
light_linking.cpp

View file

@ -21,6 +21,7 @@ template<typename BlenderT> struct AttributeConverter {
template<> struct AttributeConverter<float> {
using CyclesT = float;
static constexpr auto type_desc = TypeFloat;
static constexpr bool layout_compatible = true;
static CyclesT convert(const float &value)
{
return value;
@ -29,6 +30,7 @@ template<> struct AttributeConverter<float> {
template<> struct AttributeConverter<int> {
using CyclesT = float;
static constexpr auto type_desc = TypeFloat;
static constexpr bool layout_compatible = false;
static CyclesT convert(const int &value)
{
return float(value);
@ -37,6 +39,7 @@ template<> struct AttributeConverter<int> {
template<> struct AttributeConverter<blender::float2> {
using CyclesT = float2;
static constexpr auto type_desc = TypeFloat2;
static constexpr bool layout_compatible = true;
static CyclesT convert(const blender::float2 &value)
{
return make_float2(value[0], value[1]);
@ -45,6 +48,7 @@ template<> struct AttributeConverter<blender::float2> {
template<> struct AttributeConverter<blender::float3> {
using CyclesT = float3;
static constexpr auto type_desc = TypeVector;
static constexpr bool layout_compatible = false;
static CyclesT convert(const blender::float3 &value)
{
return make_float3(value[0], value[1], value[2]);
@ -53,6 +57,8 @@ template<> struct AttributeConverter<blender::float3> {
template<> struct AttributeConverter<blender::float4> {
using CyclesT = float4;
static constexpr auto type_desc = TypeFloat4;
/* Allocation alignment is not compatible with Cycles */
static constexpr bool layout_compatible = false;
static CyclesT convert(const blender::float4 &value)
{
return make_float4(value[0], value[1], value[2], value[3]);
@ -61,6 +67,8 @@ template<> struct AttributeConverter<blender::float4> {
template<> struct AttributeConverter<blender::ColorGeometry4f> {
using CyclesT = float4;
static constexpr auto type_desc = TypeRGBA;
/* Allocation alignment is not compatible with Cycles */
static constexpr bool layout_compatible = false;
static CyclesT convert(const blender::ColorGeometry4f &value)
{
return make_float4(value[0], value[1], value[2], value[3]);
@ -69,6 +77,7 @@ template<> struct AttributeConverter<blender::ColorGeometry4f> {
template<> struct AttributeConverter<blender::ColorGeometry4b> {
using CyclesT = float4;
static constexpr auto type_desc = TypeRGBA;
static constexpr bool layout_compatible = false;
static CyclesT convert(const blender::ColorGeometry4b &value)
{
return color_srgb_to_linear_v4(make_float4(byte_to_float(value[0]),
@ -80,6 +89,7 @@ template<> struct AttributeConverter<blender::ColorGeometry4b> {
template<> struct AttributeConverter<bool> {
using CyclesT = float;
static constexpr auto type_desc = TypeFloat;
static constexpr bool layout_compatible = false;
static CyclesT convert(const bool &value)
{
return float(value);
@ -88,6 +98,7 @@ template<> struct AttributeConverter<bool> {
template<> struct AttributeConverter<int8_t> {
using CyclesT = float;
static constexpr auto type_desc = TypeFloat;
static constexpr bool layout_compatible = false;
static CyclesT convert(const int8_t &value)
{
return float(value);
@ -96,6 +107,8 @@ template<> struct AttributeConverter<int8_t> {
template<> struct AttributeConverter<blender::math::Quaternion> {
using CyclesT = float4;
static constexpr auto type_desc = TypeFloat4;
/* Allocation alignment is not compatible with Cycles */
static constexpr bool layout_compatible = false;
static CyclesT convert(const blender::math::Quaternion &value)
{
return make_float4(value.w, value.x, value.y, value.z);

View file

@ -807,15 +807,29 @@ static void attr_create_generic(Scene *scene,
using CyclesT = typename Converter::CyclesT;
if constexpr (!std::is_void_v<CyclesT>) {
const blender::VArray<BlenderT> src_varray = b_attr.varray.typed<BlenderT>();
const blender::CommonVArrayInfo info = b_attr.varray.common_info();
if (const std::optional<BlenderT> single_value = src_varray.get_if_single()) {
if (info.type == blender::CommonVArrayInfo::Type::Single) {
const auto &single_value = *static_cast<const BlenderT *>(info.data);
Attribute *attr = attributes.add(name, Converter::type_desc, ATTR_ELEMENT_MESH);
CyclesT *data = reinterpret_cast<CyclesT *>(attr->data_for_write());
*data = Converter::convert(*single_value);
*data = Converter::convert(single_value);
return;
}
const AttributeElement element = blender_domain_to_attr_element(b_attr.domain);
if constexpr (Converter::layout_compatible) {
if (src_varray.is_span() && b_attr.sharing_info) {
attributes.add_shared(name,
Converter::type_desc,
element,
info.data,
src_varray.size(),
b_attr.sharing_info);
return;
}
}
Attribute *attr = attributes.add(name, Converter::type_desc, element);
CyclesT *data = reinterpret_cast<CyclesT *>(attr->data_for_write());

View file

@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "util/implicit_sharing.h"
#include "BLI_implicit_sharing.hh"
#include "blender/CCL_api.h"
namespace blender {
void CCL_implicit_sharing_init()
{
ccl::implicit_sharing_init(
[](ccl::ImplicitSharingInfo data) {
const auto *info = static_cast<const blender::ImplicitSharingInfo *>(data);
info->add_user();
},
[](ccl::ImplicitSharingInfo data) {
const auto *info = static_cast<const blender::ImplicitSharingInfo *>(data);
info->remove_user_and_delete_if_last();
});
}
} // namespace blender

View file

@ -173,18 +173,37 @@ static void attr_create_generic(Scene *scene,
using CyclesT = typename Converter::CyclesT;
if constexpr (!std::is_void_v<CyclesT>) {
const blender::VArray<BlenderT> src_varray = b_attr.varray.typed<BlenderT>();
const blender::CommonVArrayInfo info = b_attr.varray.common_info();
if (const std::optional<BlenderT> single_value = src_varray.get_if_single()) {
if (info.type == blender::CommonVArrayInfo::Type::Single) {
const auto &single_value = *static_cast<const BlenderT *>(info.data);
Attribute *attr = attributes.add(name, Converter::type_desc, ATTR_ELEMENT_MESH);
if (is_render_color) {
attr->std = ATTR_STD_VERTEX_COLOR;
}
CyclesT *data = reinterpret_cast<CyclesT *>(attr->data_for_write());
*data = Converter::convert(*single_value);
*data = Converter::convert(single_value);
return;
}
const AttributeElement element = blender_domain_to_attr_element(b_attr.domain);
if constexpr (Converter::layout_compatible) {
if (Attribute::element_size(mesh, element, attributes.prim) == src_varray.size()) {
if (info.type == blender::CommonVArrayInfo::Type::Span && b_attr.sharing_info) {
Attribute *attr = attributes.add_shared(name,
Converter::type_desc,
element,
info.data,
src_varray.size(),
b_attr.sharing_info);
if (is_render_color) {
attr->std = ATTR_STD_VERTEX_COLOR;
}
return;
}
}
}
Attribute *attr = attributes.add(name, Converter::type_desc, element);
if (is_render_color) {
attr->std = ATTR_STD_VERTEX_COLOR;
@ -324,11 +343,29 @@ static void attr_create_subd_uv_map(Scene *scene,
/* Denotes whether UV map was requested directly. */
const bool need_uv = mesh->need_attribute(scene, uv_name) ||
(active_render && mesh->need_attribute(scene, uv_std));
if (!need_uv) {
continue;
}
Attribute *uv_attr = nullptr;
/* UV map */
if (need_uv) {
const blender::bke::AttributeReader b_uv_map = b_attributes.lookup<blender::float2>(
uv_name.c_str(), blender::bke::AttrDomain::Corner);
const blender::CommonVArrayInfo info = b_uv_map.varray.common_info();
if (b_uv_map.sharing_info && info.type == blender::CommonVArrayInfo::Type::Span) {
if (active_render) {
uv_attr = mesh->subd_attributes.add_shared(
uv_std, uv_name, info.data, b_uv_map.varray.size(), b_uv_map.sharing_info);
}
else {
uv_attr = mesh->subd_attributes.add_shared(uv_name,
TypeFloat2,
ATTR_ELEMENT_CORNER,
info.data,
b_uv_map.varray.size(),
b_uv_map.sharing_info);
}
}
else {
if (active_render) {
uv_attr = mesh->subd_attributes.add(uv_std, uv_name);
}
@ -336,8 +373,6 @@ static void attr_create_subd_uv_map(Scene *scene,
uv_attr = mesh->subd_attributes.add(uv_name, TypeFloat2, ATTR_ELEMENT_CORNER);
}
uv_attr->flags |= ATTR_SUBDIVIDE_SMOOTH_FVAR;
const blender::VArraySpan b_uv_map = *b_attributes.lookup<blender::float2>(
uv_name.c_str(), blender::bke::AttrDomain::Corner);
float2 *fdata = uv_attr->data_float2_for_write();
@ -349,6 +384,8 @@ static void attr_create_subd_uv_map(Scene *scene,
}
}
}
uv_attr->flags |= ATTR_SUBDIVIDE_SMOOTH_FVAR;
}
}

View file

@ -82,14 +82,28 @@ static void copy_attributes(PointCloud *pointcloud,
using CyclesT = typename Converter::CyclesT;
if constexpr (!std::is_void_v<CyclesT>) {
const blender::VArray<BlenderT> src_varray = b_attr.varray.typed<BlenderT>();
const blender::CommonVArrayInfo info = b_attr.varray.common_info();
if (const std::optional<BlenderT> single_value = src_varray.get_if_single()) {
if (info.type == blender::CommonVArrayInfo::Type::Single) {
const auto &single_value = *static_cast<const BlenderT *>(info.data);
Attribute *attr = attributes.add(name, Converter::type_desc, ATTR_ELEMENT_MESH);
CyclesT *data = reinterpret_cast<CyclesT *>(attr->data_for_write());
*data = Converter::convert(*single_value);
*data = Converter::convert(single_value);
return;
}
if constexpr (Converter::layout_compatible) {
if (info.type == blender::CommonVArrayInfo::Type::Span && b_attr.sharing_info) {
attributes.add_shared(name,
Converter::type_desc,
ATTR_ELEMENT_VERTEX,
info.data,
src_varray.size(),
b_attr.sharing_info);
return;
}
}
Attribute *attr = attributes.add(name, Converter::type_desc, ATTR_ELEMENT_VERTEX);
CyclesT *data = reinterpret_cast<CyclesT *>(attr->data_for_write());

View file

@ -8,6 +8,7 @@
#include "scene/mesh.h"
#include "scene/pointcloud.h"
#include "util/guarded_allocator.h"
#include "util/log.h"
#include "util/transform.h"
@ -28,52 +29,134 @@ Attribute::Attribute(ustring name,
type == TypeRGBA);
if (element & ATTR_ELEMENT_VOXEL) {
buffer.resize(sizeof(ImageHandle));
new (buffer.data()) ImageHandle();
auto *data = GuardedAllocator<ImageHandle>().allocate(1);
new (data) ImageHandle();
buffer = data;
size = Attribute::element_size(geom, element, prim);
sharing_info = nullptr;
}
else {
resize(geom, prim);
}
}
Attribute::~Attribute()
Attribute::Attribute(ustring name,
const TypeDesc type,
AttributeElement element,
const void *data,
const int size,
ImplicitSharingInfo sharing_info)
: name(name),
std(ATTR_STD_NONE),
type(type),
size(size),
element(element),
flags(0),
modified(true)
{
assert((element & ATTR_ELEMENT_VOXEL) == 0);
buffer = data;
/* Implicit sharing function pointers should be set if shared attribtues are created. */
assert(g_implicit_sharing_user_add_fn);
assert(g_implicit_sharing_user_remove_fn);
g_implicit_sharing_user_add_fn(sharing_info);
this->sharing_info = sharing_info;
}
void Attribute::free_data()
{
/* For voxel data, we need to free the image handle. */
if (element & ATTR_ELEMENT_VOXEL && !buffer.empty()) {
ImageHandle &handle = data_voxel_for_write();
handle.~ImageHandle();
if (element & ATTR_ELEMENT_VOXEL) {
auto *image = static_cast<ImageHandle *>(const_cast<void *>(buffer));
image->~ImageHandle();
GuardedAllocator<ImageHandle>().deallocate(image, 1);
}
else if (sharing_info) {
g_implicit_sharing_user_remove_fn(sharing_info);
}
else {
GuardedAllocator<char>().deallocate(static_cast<char *>(const_cast<void *>(buffer)),
data_sizeof() * size);
}
}
Attribute::~Attribute()
{
free_data();
}
void Attribute::resize(Geometry *geom, AttributePrimitive prim)
{
if (!(element & ATTR_ELEMENT_VOXEL)) {
buffer.resize(buffer_size(geom, prim), 0);
resize(Attribute::element_size(geom, element, prim));
}
}
void Attribute::resize(const size_t num_elements)
{
if (!(element & ATTR_ELEMENT_VOXEL)) {
buffer.resize(num_elements * data_sizeof(), 0);
const size_t new_size = num_elements;
if (new_size == size) {
return;
}
auto *new_data = GuardedAllocator<char>().allocate(new_size * data_sizeof());
if (buffer) {
assert(size > 0);
memcpy(new_data, buffer, std::min(num_elements, size_t(size)) * data_sizeof());
}
free_data();
buffer = new_data;
size = new_size;
sharing_info = nullptr;
}
}
char *Attribute::data_for_write()
{
if (!buffer) {
assert(size == 0);
return nullptr;
}
if (sharing_info) {
/* Here we assume that the sharing info is not mutable. With the addition of another sharing
* info callback function pointer we could check the user count to avoid unnecessary copies.
* For now that isn't expected to happen in practice though. */
auto *new_data = GuardedAllocator<char>().allocate(data_sizeof() * size);
memcpy(new_data, buffer, data_sizeof() * size);
g_implicit_sharing_user_remove_fn(sharing_info);
sharing_info = nullptr;
buffer = new_data;
}
return const_cast<char *>(reinterpret_cast<const char *>(buffer));
}
void Attribute::set_data_from(Attribute &&other)
{
assert(other.std == std);
assert(other.type == type);
assert(other.element == element);
this->flags = other.flags;
flags = other.flags;
if (this->buffer.size() != other.buffer.size()) {
this->buffer = std::move(other.buffer);
const auto take_data = [&]() {
free_data();
buffer = other.buffer;
sharing_info = other.sharing_info;
size = other.size;
other.buffer = nullptr;
other.sharing_info = nullptr;
other.size = 0;
modified = true;
};
if (size != other.size) {
take_data();
}
else if (memcmp(this->data(), other.data(), other.buffer.size()) != 0) {
this->buffer = std::move(other.buffer);
modified = true;
else if (sharing_info != other.sharing_info) {
take_data();
}
else if (size > 0 && memcmp(buffer, other.buffer, data_sizeof() * size) != 0) {
take_data();
}
}
@ -108,7 +191,9 @@ size_t Attribute::data_sizeof() const
return sizeof(float3);
}
size_t Attribute::element_size(Geometry *geom, AttributePrimitive prim) const
size_t Attribute::element_size(Geometry *geom,
const AttributeElement element,
AttributePrimitive prim)
{
size_t size = 0;
@ -209,7 +294,7 @@ size_t Attribute::element_size(Geometry *geom, AttributePrimitive prim) const
size_t Attribute::buffer_size(Geometry *geom, AttributePrimitive prim) const
{
return element_size(geom, prim) * data_sizeof();
return Attribute::element_size(geom, element, prim) * data_sizeof();
}
bool Attribute::same_storage(const TypeDesc a, const TypeDesc b)
@ -357,7 +442,7 @@ void Attribute::get_uv_tiles(Geometry *geom,
return;
}
const int num = element_size(geom, prim);
const int num = Attribute::element_size(geom, element, prim);
const float2 *uv = data_float2();
for (int i = 0; i < num; i++, uv++) {
const float u = uv->x;
@ -405,8 +490,26 @@ Attribute *AttributeSet::add(ustring name, const TypeDesc type, AttributeElement
remove(name);
}
Attribute new_attr(name, type, element, geometry, prim);
attributes.emplace_back(std::move(new_attr));
attributes.emplace_back(name, type, element, geometry, prim);
tag_modified(attributes.back());
return &attributes.back();
}
Attribute *AttributeSet::add_shared(ustring name,
const TypeDesc type,
AttributeElement element,
const void *data,
const int size,
ImplicitSharingInfo sharing_info)
{
Attribute *attr = find(name);
if (attr) {
/* overwrite attribute with same name but different type/element */
remove(name);
}
attributes.emplace_back(name, type, element, data, size, sharing_info);
tag_modified(attributes.back());
return &attributes.back();
}
@ -438,68 +541,46 @@ void AttributeSet::remove(ustring name)
}
}
Attribute *AttributeSet::add(AttributeStandard std, ustring name)
static TypeDesc find_type_from_geometry_std(Geometry *geometry, AttributeStandard std)
{
Attribute *attr = nullptr;
if (name.empty()) {
name = Attribute::standard_name(std);
}
if (geometry->is_mesh()) {
switch (std) {
case ATTR_STD_VERTEX_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_VERTEX_NORMAL);
break;
return TypeNormal;
case ATTR_STD_NORMAL_UNDISPLACED:
attr = add(name, TypeNormal, ATTR_ELEMENT_VERTEX_NORMAL);
break;
return TypeNormal;
case ATTR_STD_UV:
attr = add(name, TypeFloat2, ATTR_ELEMENT_CORNER);
break;
return TypeFloat2;
case ATTR_STD_UV_TANGENT:
case ATTR_STD_UV_TANGENT_UNDISPLACED:
attr = add(name, TypeVector, ATTR_ELEMENT_CORNER);
break;
return TypeVector;
case ATTR_STD_UV_TANGENT_SIGN:
case ATTR_STD_UV_TANGENT_SIGN_UNDISPLACED:
attr = add(name, TypeFloat, ATTR_ELEMENT_CORNER);
break;
return TypeFloat;
case ATTR_STD_VERTEX_COLOR:
attr = add(name, TypeRGBA, ATTR_ELEMENT_CORNER_BYTE);
break;
return TypeRGBA;
case ATTR_STD_GENERATED:
case ATTR_STD_POSITION_UNDEFORMED:
case ATTR_STD_POSITION_UNDISPLACED:
attr = add(name, TypePoint, ATTR_ELEMENT_VERTEX);
break;
return TypePoint;
case ATTR_STD_MOTION_VERTEX_POSITION:
attr = add(name, TypePoint, ATTR_ELEMENT_VERTEX_MOTION);
break;
return TypePoint;
case ATTR_STD_MOTION_VERTEX_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_VERTEX_NORMAL_MOTION);
break;
return TypeNormal;
case ATTR_STD_CORNER_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_CORNER_NORMAL);
break;
return TypeNormal;
case ATTR_STD_MOTION_CORNER_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_CORNER_NORMAL_MOTION);
break;
return TypeNormal;
case ATTR_STD_PTEX_FACE_ID:
attr = add(name, TypeFloat, ATTR_ELEMENT_FACE);
break;
return TypeFloat;
case ATTR_STD_PTEX_UV:
attr = add(name, TypeFloat2, ATTR_ELEMENT_CORNER);
break;
return TypeFloat2;
case ATTR_STD_GENERATED_TRANSFORM:
attr = add(name, TypeMatrix, ATTR_ELEMENT_MESH);
break;
return TypeMatrix;
case ATTR_STD_POINTINESS:
attr = add(name, TypeFloat, ATTR_ELEMENT_VERTEX);
break;
return TypeFloat;
case ATTR_STD_RANDOM_PER_ISLAND:
attr = add(name, TypeFloat, ATTR_ELEMENT_FACE);
break;
return TypeFloat;
default:
assert(0);
break;
@ -508,20 +589,15 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name)
else if (geometry->is_pointcloud()) {
switch (std) {
case ATTR_STD_UV:
attr = add(name, TypeFloat2, ATTR_ELEMENT_VERTEX);
break;
return TypeFloat2;
case ATTR_STD_GENERATED:
attr = add(name, TypePoint, ATTR_ELEMENT_VERTEX);
break;
return TypePoint;
case ATTR_STD_MOTION_VERTEX_POSITION:
attr = add(name, TypeFloat4, ATTR_ELEMENT_VERTEX_MOTION);
break;
return TypeFloat4;
case ATTR_STD_POINT_RANDOM:
attr = add(name, TypeFloat, ATTR_ELEMENT_VERTEX);
break;
return TypeFloat;
case ATTR_STD_GENERATED_TRANSFORM:
attr = add(name, TypeMatrix, ATTR_ELEMENT_MESH);
break;
return TypeMatrix;
default:
assert(0);
break;
@ -530,11 +606,9 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name)
else if (geometry->is_volume()) {
switch (std) {
case ATTR_STD_VERTEX_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_VERTEX_NORMAL);
break;
return TypeNormal;
case ATTR_STD_CORNER_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_CORNER_NORMAL);
break;
return TypeNormal;
case ATTR_STD_VOLUME_DENSITY:
case ATTR_STD_VOLUME_FLAME:
case ATTR_STD_VOLUME_HEAT:
@ -542,14 +616,11 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name)
case ATTR_STD_VOLUME_VELOCITY_X:
case ATTR_STD_VOLUME_VELOCITY_Y:
case ATTR_STD_VOLUME_VELOCITY_Z:
attr = add(name, TypeFloat, ATTR_ELEMENT_VOXEL);
break;
return TypeFloat;
case ATTR_STD_VOLUME_COLOR:
attr = add(name, TypeColor, ATTR_ELEMENT_VOXEL);
break;
return TypeColor;
case ATTR_STD_VOLUME_VELOCITY:
attr = add(name, TypeVector, ATTR_ELEMENT_VOXEL);
break;
return TypeVector;
default:
assert(0);
break;
@ -558,46 +629,193 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name)
else if (geometry->is_hair()) {
switch (std) {
case ATTR_STD_VERTEX_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_CURVE_KEY_NORMAL);
break;
return TypeNormal;
case ATTR_STD_MOTION_VERTEX_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_CURVE_KEY_NORMAL_MOTION);
break;
return TypeNormal;
case ATTR_STD_UV:
attr = add(name, TypeFloat2, ATTR_ELEMENT_CURVE);
break;
return TypeFloat2;
case ATTR_STD_GENERATED:
attr = add(name, TypePoint, ATTR_ELEMENT_CURVE);
break;
return TypePoint;
case ATTR_STD_MOTION_VERTEX_POSITION:
attr = add(name, TypeFloat4, ATTR_ELEMENT_CURVE_KEY_MOTION);
break;
return TypeFloat4;
case ATTR_STD_CURVE_INTERCEPT:
attr = add(name, TypeFloat, ATTR_ELEMENT_CURVE_KEY);
break;
return TypeFloat;
case ATTR_STD_CURVE_LENGTH:
attr = add(name, TypeFloat, ATTR_ELEMENT_CURVE);
break;
return TypeFloat;
case ATTR_STD_CURVE_RANDOM:
attr = add(name, TypeFloat, ATTR_ELEMENT_CURVE);
break;
return TypeFloat;
case ATTR_STD_GENERATED_TRANSFORM:
attr = add(name, TypeMatrix, ATTR_ELEMENT_MESH);
break;
return TypeMatrix;
case ATTR_STD_POINTINESS:
attr = add(name, TypeFloat, ATTR_ELEMENT_VERTEX);
break;
return TypeFloat;
case ATTR_STD_RANDOM_PER_ISLAND:
attr = add(name, TypeFloat, ATTR_ELEMENT_FACE);
break;
return TypeFloat;
case ATTR_STD_SHADOW_TRANSPARENCY:
attr = add(name, TypeFloat, ATTR_ELEMENT_CURVE_KEY);
break;
return TypeFloat;
default:
assert(0);
break;
}
}
assert(0);
return TypeFloat;
}
static AttributeElement find_element_from_geometry_std(Geometry *geometry, AttributeStandard std)
{
if (geometry->is_mesh()) {
switch (std) {
case ATTR_STD_VERTEX_NORMAL:
return ATTR_ELEMENT_VERTEX_NORMAL;
case ATTR_STD_NORMAL_UNDISPLACED:
return ATTR_ELEMENT_VERTEX_NORMAL;
case ATTR_STD_UV:
return ATTR_ELEMENT_CORNER;
case ATTR_STD_UV_TANGENT:
case ATTR_STD_UV_TANGENT_UNDISPLACED:
return ATTR_ELEMENT_CORNER;
case ATTR_STD_UV_TANGENT_SIGN:
case ATTR_STD_UV_TANGENT_SIGN_UNDISPLACED:
return ATTR_ELEMENT_CORNER;
case ATTR_STD_VERTEX_COLOR:
return ATTR_ELEMENT_CORNER_BYTE;
case ATTR_STD_GENERATED:
case ATTR_STD_POSITION_UNDEFORMED:
case ATTR_STD_POSITION_UNDISPLACED:
return ATTR_ELEMENT_VERTEX;
case ATTR_STD_MOTION_VERTEX_POSITION:
return ATTR_ELEMENT_VERTEX_MOTION;
case ATTR_STD_MOTION_VERTEX_NORMAL:
return ATTR_ELEMENT_VERTEX_NORMAL_MOTION;
case ATTR_STD_CORNER_NORMAL:
return ATTR_ELEMENT_CORNER_NORMAL;
case ATTR_STD_MOTION_CORNER_NORMAL:
return ATTR_ELEMENT_CORNER_NORMAL_MOTION;
case ATTR_STD_PTEX_FACE_ID:
return ATTR_ELEMENT_FACE;
case ATTR_STD_PTEX_UV:
return ATTR_ELEMENT_CORNER;
case ATTR_STD_GENERATED_TRANSFORM:
return ATTR_ELEMENT_MESH;
case ATTR_STD_POINTINESS:
return ATTR_ELEMENT_VERTEX;
case ATTR_STD_RANDOM_PER_ISLAND:
return ATTR_ELEMENT_FACE;
default:
assert(0);
break;
}
}
else if (geometry->is_pointcloud()) {
switch (std) {
case ATTR_STD_UV:
return ATTR_ELEMENT_VERTEX;
case ATTR_STD_GENERATED:
return ATTR_ELEMENT_VERTEX;
case ATTR_STD_MOTION_VERTEX_POSITION:
return ATTR_ELEMENT_VERTEX_MOTION;
case ATTR_STD_POINT_RANDOM:
return ATTR_ELEMENT_VERTEX;
case ATTR_STD_GENERATED_TRANSFORM:
return ATTR_ELEMENT_MESH;
default:
assert(0);
break;
}
}
else if (geometry->is_volume()) {
switch (std) {
case ATTR_STD_VERTEX_NORMAL:
return ATTR_ELEMENT_VERTEX_NORMAL;
case ATTR_STD_CORNER_NORMAL:
return ATTR_ELEMENT_CORNER_NORMAL;
case ATTR_STD_VOLUME_DENSITY:
case ATTR_STD_VOLUME_FLAME:
case ATTR_STD_VOLUME_HEAT:
case ATTR_STD_VOLUME_TEMPERATURE:
case ATTR_STD_VOLUME_VELOCITY_X:
case ATTR_STD_VOLUME_VELOCITY_Y:
case ATTR_STD_VOLUME_VELOCITY_Z:
return ATTR_ELEMENT_VOXEL;
case ATTR_STD_VOLUME_COLOR:
return ATTR_ELEMENT_VOXEL;
case ATTR_STD_VOLUME_VELOCITY:
return ATTR_ELEMENT_VOXEL;
default:
assert(0);
break;
}
}
else if (geometry->is_hair()) {
switch (std) {
case ATTR_STD_VERTEX_NORMAL:
return ATTR_ELEMENT_CURVE_KEY_NORMAL;
case ATTR_STD_MOTION_VERTEX_NORMAL:
return ATTR_ELEMENT_CURVE_KEY_NORMAL_MOTION;
case ATTR_STD_UV:
return ATTR_ELEMENT_CURVE;
case ATTR_STD_GENERATED:
return ATTR_ELEMENT_CURVE;
case ATTR_STD_MOTION_VERTEX_POSITION:
return ATTR_ELEMENT_CURVE_KEY_MOTION;
case ATTR_STD_CURVE_INTERCEPT:
return ATTR_ELEMENT_CURVE_KEY;
case ATTR_STD_CURVE_LENGTH:
return ATTR_ELEMENT_CURVE;
case ATTR_STD_CURVE_RANDOM:
return ATTR_ELEMENT_CURVE;
case ATTR_STD_GENERATED_TRANSFORM:
return ATTR_ELEMENT_MESH;
case ATTR_STD_POINTINESS:
return ATTR_ELEMENT_VERTEX;
case ATTR_STD_RANDOM_PER_ISLAND:
return ATTR_ELEMENT_FACE;
case ATTR_STD_SHADOW_TRANSPARENCY:
return ATTR_ELEMENT_CURVE_KEY;
default:
assert(0);
break;
}
}
assert(0);
return ATTR_ELEMENT_NONE;
}
Attribute *AttributeSet::add(AttributeStandard std, ustring name)
{
Attribute *attr = nullptr;
if (name.empty()) {
name = Attribute::standard_name(std);
}
attr = add(name,
find_type_from_geometry_std(geometry, std),
find_element_from_geometry_std(geometry, std));
attr->std = std;
return attr;
}
Attribute *AttributeSet::add_shared(AttributeStandard std,
ustring name,
const void *data,
const int size,
ImplicitSharingInfo sharing_info)
{
Attribute *attr = nullptr;
if (name.empty()) {
name = Attribute::standard_name(std);
}
attr = add_shared(name,
find_type_from_geometry_std(geometry, std),
find_element_from_geometry_std(geometry, std),
data,
size,
sharing_info);
attr->std = std;

View file

@ -8,6 +8,7 @@
#include "kernel/types.h"
#include "util/implicit_sharing.h"
#include "util/list.h"
#include "util/param.h"
#include "util/set.h"
@ -55,7 +56,13 @@ class Attribute {
AttributeStandard std;
TypeDesc type;
vector<char> buffer;
/**
* Optionally used to share ownership of #buffer, see implicit_sharing.h.
* If this is null, #buffer is wholly owned by this Attribute.
*/
ImplicitSharingInfo sharing_info = nullptr;
const void *buffer = nullptr;
int size = 0;
AttributeElement element;
uint flags; /* enum AttributeFlag */
@ -66,7 +73,14 @@ class Attribute {
AttributeElement element,
Geometry *geom,
AttributePrimitive prim);
Attribute(Attribute &&other) = default;
Attribute(ustring name,
const TypeDesc type,
AttributeElement element,
const void *data,
int size,
ImplicitSharingInfo sharing_info);
Attribute(Attribute &&other) = delete;
Attribute &operator=(Attribute &&other) = delete;
Attribute(const Attribute &other) = delete;
Attribute &operator=(const Attribute &other) = delete;
~Attribute();
@ -76,13 +90,10 @@ class Attribute {
void resize(const size_t num_elements);
size_t data_sizeof() const;
size_t element_size(Geometry *geom, AttributePrimitive prim) const;
static size_t element_size(Geometry *geom, AttributeElement element, AttributePrimitive prim);
size_t buffer_size(Geometry *geom, AttributePrimitive prim) const;
char *data_for_write()
{
return (!buffer.empty()) ? buffer.data() : nullptr;
}
char *data_for_write();
float2 *data_float2_for_write()
{
assert(data_sizeof() == sizeof(float2));
@ -127,7 +138,7 @@ class Attribute {
const char *data() const
{
return (!buffer.empty()) ? buffer.data() : nullptr;
return static_cast<const char *>(buffer);
}
const float2 *data_float2() const
{
@ -174,6 +185,8 @@ class Attribute {
void set_data_from(Attribute &&other);
void free_data();
static bool same_storage(const TypeDesc a, const TypeDesc b);
static const char *standard_name(AttributeStandard std);
static AttributeStandard name_standard(const char *name);
@ -200,10 +213,21 @@ class AttributeSet {
~AttributeSet();
Attribute *add(ustring name, const TypeDesc type, AttributeElement element);
Attribute *add_shared(ustring name,
const TypeDesc type,
AttributeElement element,
const void *data,
int size,
ImplicitSharingInfo sharing_info);
Attribute *find(ustring name) const;
void remove(ustring name);
Attribute *add(AttributeStandard std, ustring name = ustring());
Attribute *add_shared(AttributeStandard std,
ustring name,
const void *data,
int size,
ImplicitSharingInfo sharing_info);
Attribute *find(AttributeStandard std) const;
void remove(AttributeStandard std);

View file

@ -326,7 +326,7 @@ class AttributeTableBuilder {
type = mattr->type;
/* store attribute data in arrays */
const size_t size = mattr->element_size(geom, prim);
const size_t size = Attribute::element_size(geom, mattr->element, prim);
const AttributeElement &element = desc.element;
int &offset = desc.offset;
@ -394,7 +394,7 @@ class AttributeTableBuilder {
return;
}
const size_t size = mattr->element_size(geom, prim);
const size_t size = Attribute::element_size(geom, mattr->element, prim);
if (mattr->element & ATTR_ELEMENT_VOXEL) {
/* pass */
@ -510,8 +510,8 @@ void GeometryManager::device_update_attributes(Device *device,
attributes.add(param.name());
Attribute *attr = values.add(param.name(), param.type(), ATTR_ELEMENT_OBJECT);
assert(param.datasize() == attr->buffer.size());
memcpy(attr->buffer.data(), param.data(), param.datasize());
assert(param.nvalues() == attr->size);
memcpy(attr->data_for_write(), param.data(), param.datasize());
}
}
}

View file

@ -538,8 +538,8 @@ void SubdAttributeInterpolation::setup_attribute_type(const Attribute &subd_attr
case ATTR_ELEMENT_OBJECT:
case ATTR_ELEMENT_MESH: {
/* Uniform attributes don't need interpolation, just copy data. */
assert(mesh_attr.buffer.size() == subd_attr.buffer.size());
memcpy(mesh_attr.data_for_write(), subd_attr.data(), subd_attr.buffer.size());
assert(mesh_attr.size == subd_attr.size);
memcpy(mesh_attr.data_for_write(), subd_attr.data(), subd_attr.data_sizeof());
break;
}
case ATTR_ELEMENT_VERTEX:

View file

@ -17,6 +17,7 @@ set(SRC
ies.cpp
image_maketx.cpp
image_metadata.cpp
implicit_sharing.cpp
log.cpp
math_cdf.cpp
md5.cpp
@ -75,6 +76,7 @@ set(SRC_HEADERS
image_impl.h
image_maketx.h
image_metadata.h
implicit_sharing.h
list.h
log.h
map.h

View file

@ -0,0 +1,18 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: Apache-2.0 */
#include "util/implicit_sharing.h"
CCL_NAMESPACE_BEGIN
ImplicitSharingUserAddFn g_implicit_sharing_user_add_fn = nullptr;
ImplicitSharingUserAddFn g_implicit_sharing_user_remove_fn = nullptr;
void implicit_sharing_init(ImplicitSharingUserAddFn add_fn, ImplicitSharingUserRemoveFn remove_fn)
{
g_implicit_sharing_user_add_fn = add_fn;
g_implicit_sharing_user_remove_fn = remove_fn;
}
CCL_NAMESPACE_END

View file

@ -0,0 +1,24 @@
/* SPDX-FileCopyrightText: 2026 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
CCL_NAMESPACE_BEGIN
using ImplicitSharingInfo = const void *;
using ImplicitSharingUserAddFn = void (*)(ImplicitSharingInfo);
using ImplicitSharingUserRemoveFn = void (*)(ImplicitSharingInfo);
extern ImplicitSharingUserAddFn g_implicit_sharing_user_add_fn;
extern ImplicitSharingUserRemoveFn g_implicit_sharing_user_remove_fn;
/**
* Set function pointers to be called when implicit sharing (use count tracking) is enabled.
* "Sharing info" references that own data like attribute arrays are passed as void pointers to
* various Cycles API functions, and these functions are called to change the reference count.
*/
void implicit_sharing_init(ImplicitSharingUserAddFn add_fn, ImplicitSharingUserRemoveFn remove_fn);
CCL_NAMESPACE_END

View file

@ -549,6 +549,7 @@ int main(int argc,
#ifdef WITH_CYCLES
CCL_log_init();
CCL_implicit_sharing_init();
#endif
/* Must be initialized after #BKE_appdir_init to account for color-management paths. */