Files
test2/intern/cycles/blender/pointcloud.cpp
Lukas Stockner ace85d3338 Fix #131218: Cycles: Race condition when syncing motion blur
The issue here is that motion_steps handling is a bit complex, and the
parallel synchronization of geometry does not play well with it.

The obvious result of this was a crash related to the main thread
checking attributes while the geometry sync was changing them, but
there was also another race condition that could result in ending up
with the wrong motion_steps.

Specific changes:
- Change place where `motion_steps` is set to avoid concurrent access
- Change the default `motion_steps` to zero, since they won't be
  explicitly set if there's no motion now
- Don't skip `motion_steps` copy in `sync_X` since it's no longer set
  in `sync_object` and we need to transfer the value in case it was set
  to 3 by the velocity code since that's no longer the default

Pull Request: https://projects.blender.org/blender/blender/pulls/133669
2025-01-29 01:59:38 +01:00

251 lines
9.0 KiB
C++

/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "scene/pointcloud.h"
#include "scene/attribute.h"
#include "scene/scene.h"
#include "util/hash.h"
#include "blender/attribute_convert.h"
#include "blender/sync.h"
#include "blender/util.h"
#include "DNA_pointcloud_types.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_math.hh"
CCL_NAMESPACE_BEGIN
static void attr_create_motion_from_velocity(PointCloud *pointcloud,
const blender::Span<blender::float3> b_attribute,
const float motion_scale)
{
const int num_points = pointcloud->get_points().size();
/* Override motion steps to fixed number. */
pointcloud->set_motion_steps(3);
/* Find or add attribute */
float3 *P = pointcloud->get_points().data();
float *radius = pointcloud->get_radius().data();
Attribute *attr_mP = pointcloud->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
if (!attr_mP) {
attr_mP = pointcloud->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION);
}
/* Only export previous and next frame, we don't have any in between data. */
const float motion_times[2] = {-1.0f, 1.0f};
for (int step = 0; step < 2; step++) {
const float relative_time = motion_times[step] * 0.5f * motion_scale;
float4 *mP = attr_mP->data_float4() + step * num_points;
for (int i = 0; i < num_points; i++) {
const float3 Pi = P[i] +
make_float3(b_attribute[i][0], b_attribute[i][1], b_attribute[i][2]) *
relative_time;
mP[i] = make_float4(Pi, radius[i]);
}
}
}
static void copy_attributes(PointCloud *pointcloud,
const ::PointCloud &b_pointcloud,
const bool need_motion,
const float motion_scale)
{
const blender::bke::AttributeAccessor b_attributes = b_pointcloud.attributes();
if (b_attributes.domain_size(blender::bke::AttrDomain::Point) == 0) {
return;
}
AttributeSet &attributes = pointcloud->attributes;
static const ustring u_velocity("velocity");
b_attributes.foreach_attribute([&](const blender::bke::AttributeIter &iter) {
const ustring name{std::string_view(iter.name)};
if (need_motion && name == u_velocity) {
const blender::VArraySpan b_attr = *iter.get<blender::float3>();
attr_create_motion_from_velocity(pointcloud, b_attr, motion_scale);
}
if (attributes.find(name)) {
return;
}
const blender::bke::GAttributeReader b_attr = iter.get();
blender::bke::attribute_math::convert_to_static_type(b_attr.varray.type(), [&](auto dummy) {
using BlenderT = decltype(dummy);
using Converter = typename ccl::AttributeConverter<BlenderT>;
using CyclesT = typename Converter::CyclesT;
if constexpr (!std::is_void_v<CyclesT>) {
Attribute *attr = attributes.add(name, Converter::type_desc, ATTR_ELEMENT_VERTEX);
CyclesT *data = reinterpret_cast<CyclesT *>(attr->data());
const blender::VArraySpan src = b_attr.varray.typed<BlenderT>();
for (const int i : src.index_range()) {
data[i] = Converter::convert(src[i]);
}
}
});
});
}
static void export_pointcloud(Scene *scene,
PointCloud *pointcloud,
const ::PointCloud &b_pointcloud,
const bool need_motion,
const float motion_scale)
{
const blender::Span<blender::float3> b_positions = b_pointcloud.positions();
const blender::VArraySpan b_radius = *b_pointcloud.attributes().lookup<float>(
"radius", blender::bke::AttrDomain::Point);
pointcloud->resize(b_positions.size());
float3 *points = pointcloud->get_points().data();
for (const int i : b_positions.index_range()) {
points[i] = make_float3(b_positions[i][0], b_positions[i][1], b_positions[i][2]);
}
float *radius = pointcloud->get_radius().data();
if (!b_radius.is_empty()) {
std::copy(b_radius.data(), b_radius.data() + b_positions.size(), radius);
}
else {
std::fill(radius, radius + b_positions.size(), 0.01f);
}
int *shader = pointcloud->get_shader().data();
std::fill(shader, shader + b_positions.size(), 0);
if (pointcloud->need_attribute(scene, ATTR_STD_POINT_RANDOM)) {
Attribute *attr_random = pointcloud->attributes.add(ATTR_STD_POINT_RANDOM);
float *data = attr_random->data_float();
for (const int i : b_positions.index_range()) {
data[i] = hash_uint2_to_float(i, 0);
}
}
copy_attributes(pointcloud, b_pointcloud, need_motion, motion_scale);
}
static void export_pointcloud_motion(PointCloud *pointcloud,
const ::PointCloud &b_pointcloud,
const int motion_step)
{
/* Find or add attribute. */
Attribute *attr_mP = pointcloud->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
bool new_attribute = false;
if (!attr_mP) {
attr_mP = pointcloud->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION);
new_attribute = true;
}
const int num_points = pointcloud->num_points();
/* Point cloud attributes are stored as float4 with the radius in the w element.
* This is explicit now as float3 is no longer interchangeable with float4 as it
* is packed now. */
float4 *mP = attr_mP->data_float4() + motion_step * num_points;
bool have_motion = false;
const array<float3> &pointcloud_points = pointcloud->get_points();
const blender::Span<blender::float3> b_positions = b_pointcloud.positions();
const blender::VArraySpan b_radius = *b_pointcloud.attributes().lookup<float>(
"radius", blender::bke::AttrDomain::Point);
for (int i = 0; i < std::min<int>(num_points, b_positions.size()); i++) {
const float3 P = make_float3(b_positions[i][0], b_positions[i][1], b_positions[i][2]);
const float radius = b_radius.is_empty() ? 0.01f : b_radius[i];
mP[i] = make_float4(P, radius);
have_motion = have_motion || (P != pointcloud_points[i]);
}
/* In case of new attribute, we verify if there really was any motion. */
if (new_attribute) {
if (b_positions.size() != num_points || !have_motion) {
pointcloud->attributes.remove(ATTR_STD_MOTION_VERTEX_POSITION);
}
else if (motion_step > 0) {
/* Motion, fill up previous steps that we might have skipped because
* they had no motion, but we need them anyway now. */
for (int step = 0; step < motion_step; step++) {
pointcloud->copy_center_to_motion_step(step);
}
}
}
/* Export attributes */
copy_attributes(pointcloud, b_pointcloud, false, 0.0f);
}
void BlenderSync::sync_pointcloud(PointCloud *pointcloud, BObjectInfo &b_ob_info)
{
const size_t old_numpoints = pointcloud->num_points();
array<Node *> used_shaders = pointcloud->get_used_shaders();
PointCloud new_pointcloud;
new_pointcloud.set_used_shaders(used_shaders);
/* TODO: add option to filter out points in the view layer. */
const BL::PointCloud b_pointcloud(b_ob_info.object_data);
/* Motion blur attribute is relative to seconds, we need it relative to frames. */
const bool need_motion = object_need_motion_attribute(b_ob_info, scene);
const float motion_scale = (need_motion) ?
scene->motion_shutter_time() /
(b_scene.render().fps() / b_scene.render().fps_base()) :
0.0f;
export_pointcloud(scene,
&new_pointcloud,
*static_cast<const ::PointCloud *>(b_pointcloud.ptr.data),
need_motion,
motion_scale);
pointcloud->clear_non_sockets();
/* Update original sockets. */
for (const SocketType &socket : new_pointcloud.type->inputs) {
/* Those sockets are updated in sync_object, so do not modify them. */
if (socket.name == "use_motion_blur" || socket.name == "used_shaders") {
continue;
}
pointcloud->set_value(socket, new_pointcloud, socket);
}
pointcloud->attributes.update(std::move(new_pointcloud.attributes));
/* Tag update. */
const bool rebuild = (pointcloud && old_numpoints != pointcloud->num_points());
pointcloud->tag_update(scene, rebuild);
}
void BlenderSync::sync_pointcloud_motion(PointCloud *pointcloud,
BObjectInfo &b_ob_info,
const int motion_step)
{
/* Skip if nothing exported. */
if (pointcloud->num_points() == 0) {
return;
}
/* Export deformed coordinates. */
if (ccl::BKE_object_is_deform_modified(b_ob_info, b_scene, preview)) {
/* PointCloud object. */
const BL::PointCloud b_pointcloud(b_ob_info.object_data);
export_pointcloud_motion(
pointcloud, *static_cast<const ::PointCloud *>(b_pointcloud.ptr.data), motion_step);
}
else {
/* No deformation on this frame, copy coordinates if other frames did have it. */
pointcloud->copy_center_to_motion_step(motion_step);
}
}
CCL_NAMESPACE_END