blender/intern/cycles/render/alembic.cpp
Kévin Dietrich 9bfc47c933 Alembic Procedural: basic cache control settings
This adds a setting to enable data caching, and another one to set the
maximum cache size in megabytes.

When caching is enabled we load the data for the entire animation in
memory, as we already do, however, if the data exceeds the memory limit,
render is aborted.

When caching is disabled, we simply load the data for the current frame
in memory.

Ref D10197

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D11163
2021-08-20 14:34:43 +02:00

1432 lines
42 KiB
C++

/*
* Copyright 2011-2018 Blender Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "render/alembic.h"
#include "render/alembic_read.h"
#include "render/camera.h"
#include "render/curves.h"
#include "render/mesh.h"
#include "render/object.h"
#include "render/scene.h"
#include "render/shader.h"
#include "util/util_foreach.h"
#include "util/util_logging.h"
#include "util/util_progress.h"
#include "util/util_transform.h"
#include "util/util_vector.h"
#ifdef WITH_ALEMBIC
using namespace Alembic::AbcGeom;
CCL_NAMESPACE_BEGIN
/* TODO(kevindietrich): motion blur support. */
template<typename SchemaType>
static vector<FaceSetShaderIndexPair> parse_face_sets_for_shader_assignment(
SchemaType &schema, const array<Node *> &used_shaders)
{
vector<FaceSetShaderIndexPair> result;
std::vector<std::string> face_set_names;
schema.getFaceSetNames(face_set_names);
if (face_set_names.empty()) {
return result;
}
for (const std::string &face_set_name : face_set_names) {
int shader_index = 0;
for (Node *node : used_shaders) {
if (node->name == face_set_name) {
break;
}
++shader_index;
}
if (shader_index >= used_shaders.size()) {
/* use the first shader instead if none was found */
shader_index = 0;
}
const Alembic::AbcGeom::IFaceSet face_set = schema.getFaceSet(face_set_name);
if (!face_set.valid()) {
continue;
}
result.push_back({face_set, shader_index});
}
return result;
}
void CachedData::clear()
{
attributes.clear();
curve_first_key.clear();
curve_keys.clear();
curve_radius.clear();
curve_shader.clear();
num_ngons.clear();
shader.clear();
subd_creases_edge.clear();
subd_creases_weight.clear();
subd_face_corners.clear();
subd_num_corners.clear();
subd_ptex_offset.clear();
subd_smooth.clear();
subd_start_corner.clear();
transforms.clear();
triangles.clear();
uv_loops.clear();
vertices.clear();
for (CachedAttribute &attr : attributes) {
attr.data.clear();
}
attributes.clear();
}
CachedData::CachedAttribute &CachedData::add_attribute(const ustring &name,
const TimeSampling &time_sampling)
{
for (auto &attr : attributes) {
if (attr.name == name) {
return attr;
}
}
CachedAttribute &attr = attributes.emplace_back();
attr.name = name;
attr.data.set_time_sampling(time_sampling);
return attr;
}
bool CachedData::is_constant() const
{
# define CHECK_IF_CONSTANT(data) \
if (!data.is_constant()) { \
return false; \
}
CHECK_IF_CONSTANT(curve_first_key)
CHECK_IF_CONSTANT(curve_keys)
CHECK_IF_CONSTANT(curve_radius)
CHECK_IF_CONSTANT(curve_shader)
CHECK_IF_CONSTANT(num_ngons)
CHECK_IF_CONSTANT(shader)
CHECK_IF_CONSTANT(subd_creases_edge)
CHECK_IF_CONSTANT(subd_creases_weight)
CHECK_IF_CONSTANT(subd_face_corners)
CHECK_IF_CONSTANT(subd_num_corners)
CHECK_IF_CONSTANT(subd_ptex_offset)
CHECK_IF_CONSTANT(subd_smooth)
CHECK_IF_CONSTANT(subd_start_corner)
CHECK_IF_CONSTANT(transforms)
CHECK_IF_CONSTANT(triangles)
CHECK_IF_CONSTANT(uv_loops)
CHECK_IF_CONSTANT(vertices)
for (const CachedAttribute &attr : attributes) {
if (!attr.data.is_constant()) {
return false;
}
}
return true;
# undef CHECK_IF_CONSTANT
}
void CachedData::invalidate_last_loaded_time(bool attributes_only)
{
if (attributes_only) {
for (CachedAttribute &attr : attributes) {
attr.data.invalidate_last_loaded_time();
}
return;
}
curve_first_key.invalidate_last_loaded_time();
curve_keys.invalidate_last_loaded_time();
curve_radius.invalidate_last_loaded_time();
curve_shader.invalidate_last_loaded_time();
num_ngons.invalidate_last_loaded_time();
shader.invalidate_last_loaded_time();
subd_creases_edge.invalidate_last_loaded_time();
subd_creases_weight.invalidate_last_loaded_time();
subd_face_corners.invalidate_last_loaded_time();
subd_num_corners.invalidate_last_loaded_time();
subd_ptex_offset.invalidate_last_loaded_time();
subd_smooth.invalidate_last_loaded_time();
subd_start_corner.invalidate_last_loaded_time();
transforms.invalidate_last_loaded_time();
triangles.invalidate_last_loaded_time();
uv_loops.invalidate_last_loaded_time();
vertices.invalidate_last_loaded_time();
}
void CachedData::set_time_sampling(TimeSampling time_sampling)
{
curve_first_key.set_time_sampling(time_sampling);
curve_keys.set_time_sampling(time_sampling);
curve_radius.set_time_sampling(time_sampling);
curve_shader.set_time_sampling(time_sampling);
num_ngons.set_time_sampling(time_sampling);
shader.set_time_sampling(time_sampling);
subd_creases_edge.set_time_sampling(time_sampling);
subd_creases_weight.set_time_sampling(time_sampling);
subd_face_corners.set_time_sampling(time_sampling);
subd_num_corners.set_time_sampling(time_sampling);
subd_ptex_offset.set_time_sampling(time_sampling);
subd_smooth.set_time_sampling(time_sampling);
subd_start_corner.set_time_sampling(time_sampling);
transforms.set_time_sampling(time_sampling);
triangles.set_time_sampling(time_sampling);
uv_loops.set_time_sampling(time_sampling);
vertices.set_time_sampling(time_sampling);
for (CachedAttribute &attr : attributes) {
attr.data.set_time_sampling(time_sampling);
}
}
size_t CachedData::memory_used() const
{
size_t mem_used = 0;
mem_used += curve_first_key.memory_used();
mem_used += curve_keys.memory_used();
mem_used += curve_radius.memory_used();
mem_used += curve_shader.memory_used();
mem_used += num_ngons.memory_used();
mem_used += shader.memory_used();
mem_used += subd_creases_edge.memory_used();
mem_used += subd_creases_weight.memory_used();
mem_used += subd_face_corners.memory_used();
mem_used += subd_num_corners.memory_used();
mem_used += subd_ptex_offset.memory_used();
mem_used += subd_smooth.memory_used();
mem_used += subd_start_corner.memory_used();
mem_used += transforms.memory_used();
mem_used += triangles.memory_used();
mem_used += uv_loops.memory_used();
mem_used += vertices.memory_used();
for (const CachedAttribute &attr : attributes) {
mem_used += attr.data.memory_used();
}
return mem_used;
}
static M44d convert_yup_zup(const M44d &mtx, float scale_mult)
{
V3d scale, shear, rotation, translation;
extractSHRT(mtx,
scale,
shear,
rotation,
translation,
true,
IMATH_INTERNAL_NAMESPACE::Euler<double>::XZY);
M44d rot_mat, scale_mat, trans_mat;
rot_mat.setEulerAngles(V3d(rotation.x, -rotation.z, rotation.y));
scale_mat.setScale(V3d(scale.x, scale.z, scale.y));
trans_mat.setTranslation(V3d(translation.x, -translation.z, translation.y));
M44d temp_mat = scale_mat * rot_mat * trans_mat;
scale_mat.setScale(static_cast<double>(scale_mult));
return temp_mat * scale_mat;
}
static void transform_decompose(
const M44d &mat, V3d &scale, V3d &shear, Quatd &rotation, V3d &translation)
{
M44d mat_remainder(mat);
/* extract scale and shear */
Imath::extractAndRemoveScalingAndShear(mat_remainder, scale, shear);
/* extract translation */
translation.x = mat_remainder[3][0];
translation.y = mat_remainder[3][1];
translation.z = mat_remainder[3][2];
/* extract rotation */
rotation = extractQuat(mat_remainder);
}
static M44d transform_compose(const V3d &scale,
const V3d &shear,
const Quatd &rotation,
const V3d &translation)
{
M44d scale_mat, shear_mat, rot_mat, trans_mat;
scale_mat.setScale(scale);
shear_mat.setShear(shear);
rot_mat = rotation.toMatrix44();
trans_mat.setTranslation(translation);
return scale_mat * shear_mat * rot_mat * trans_mat;
}
/* get the matrix for the specified time, or return the identity matrix if there is no exact match
*/
static M44d get_matrix_for_time(const MatrixSampleMap &samples, chrono_t time)
{
MatrixSampleMap::const_iterator iter = samples.find(time);
if (iter != samples.end()) {
return iter->second;
}
return M44d();
}
/* get the matrix for the specified time, or interpolate between samples if there is no exact match
*/
static M44d get_interpolated_matrix_for_time(const MatrixSampleMap &samples, chrono_t time)
{
if (samples.empty()) {
return M44d();
}
/* see if exact match */
MatrixSampleMap::const_iterator iter = samples.find(time);
if (iter != samples.end()) {
return iter->second;
}
if (samples.size() == 1) {
return samples.begin()->second;
}
if (time <= samples.begin()->first) {
return samples.begin()->second;
}
if (time >= samples.rbegin()->first) {
return samples.rbegin()->second;
}
/* find previous and next time sample to interpolate */
chrono_t prev_time = samples.begin()->first;
chrono_t next_time = samples.rbegin()->first;
for (MatrixSampleMap::const_iterator I = samples.begin(); I != samples.end(); ++I) {
chrono_t current_time = (*I).first;
if (current_time > prev_time && current_time <= time) {
prev_time = current_time;
}
if (current_time > next_time && current_time >= time) {
next_time = current_time;
}
}
const M44d prev_mat = get_matrix_for_time(samples, prev_time);
const M44d next_mat = get_matrix_for_time(samples, next_time);
V3d prev_scale, next_scale;
V3d prev_shear, next_shear;
V3d prev_translation, next_translation;
Quatd prev_rotation, next_rotation;
transform_decompose(prev_mat, prev_scale, prev_shear, prev_rotation, prev_translation);
transform_decompose(next_mat, next_scale, next_shear, next_rotation, next_translation);
chrono_t t = (time - prev_time) / (next_time - prev_time);
/* Ensure rotation around the shortest angle. */
if ((prev_rotation ^ next_rotation) < 0) {
next_rotation = -next_rotation;
}
return transform_compose(Imath::lerp(prev_scale, next_scale, t),
Imath::lerp(prev_shear, next_shear, t),
Imath::slerp(prev_rotation, next_rotation, t),
Imath::lerp(prev_translation, next_translation, t));
}
static void concatenate_xform_samples(const MatrixSampleMap &parent_samples,
const MatrixSampleMap &local_samples,
MatrixSampleMap &output_samples)
{
set<chrono_t> union_of_samples;
for (const std::pair<chrono_t, M44d> pair : parent_samples) {
union_of_samples.insert(pair.first);
}
for (const std::pair<chrono_t, M44d> pair : local_samples) {
union_of_samples.insert(pair.first);
}
foreach (chrono_t time, union_of_samples) {
M44d parent_matrix = get_interpolated_matrix_for_time(parent_samples, time);
M44d local_matrix = get_interpolated_matrix_for_time(local_samples, time);
output_samples[time] = local_matrix * parent_matrix;
}
}
static Transform make_transform(const M44d &a, float scale)
{
M44d m = convert_yup_zup(a, scale);
Transform trans;
for (int j = 0; j < 3; j++) {
for (int i = 0; i < 4; i++) {
trans[j][i] = static_cast<float>(m[i][j]);
}
}
return trans;
}
NODE_DEFINE(AlembicObject)
{
NodeType *type = NodeType::add("alembic_object", create);
SOCKET_STRING(path, "Alembic Path", ustring());
SOCKET_NODE_ARRAY(used_shaders, "Used Shaders", Shader::get_node_type());
SOCKET_BOOLEAN(ignore_subdivision, "Ignore Subdivision", true);
SOCKET_INT(subd_max_level, "Max Subdivision Level", 1);
SOCKET_FLOAT(subd_dicing_rate, "Subdivision Dicing Rate", 1.0f);
SOCKET_FLOAT(radius_scale, "Radius Scale", 1.0f);
return type;
}
AlembicObject::AlembicObject() : Node(get_node_type())
{
schema_type = INVALID;
}
AlembicObject::~AlembicObject()
{
}
void AlembicObject::set_object(Object *object_)
{
object = object_;
}
Object *AlembicObject::get_object()
{
return object;
}
bool AlembicObject::has_data_loaded() const
{
return data_loaded;
}
void AlembicObject::load_data_in_cache(CachedData &cached_data,
AlembicProcedural *proc,
IPolyMeshSchema &schema,
Progress &progress)
{
/* Only load data for the original Geometry. */
if (instance_of) {
return;
}
cached_data.clear();
PolyMeshSchemaData data;
data.topology_variance = schema.getTopologyVariance();
data.time_sampling = schema.getTimeSampling();
data.positions = schema.getPositionsProperty();
data.face_counts = schema.getFaceCountsProperty();
data.face_indices = schema.getFaceIndicesProperty();
data.normals = schema.getNormalsParam();
data.num_samples = schema.getNumSamples();
data.shader_face_sets = parse_face_sets_for_shader_assignment(schema, get_used_shaders());
read_geometry_data(proc, cached_data, data, progress);
if (progress.get_cancel()) {
return;
}
/* Use the schema as the base compound property to also be able to look for top level properties.
*/
read_attributes(
proc, cached_data, schema, schema.getUVsParam(), get_requested_attributes(), progress);
if (progress.get_cancel()) {
return;
}
cached_data.invalidate_last_loaded_time(true);
data_loaded = true;
}
void AlembicObject::load_data_in_cache(CachedData &cached_data,
AlembicProcedural *proc,
ISubDSchema &schema,
Progress &progress)
{
/* Only load data for the original Geometry. */
if (instance_of) {
return;
}
cached_data.clear();
if (this->get_ignore_subdivision()) {
PolyMeshSchemaData data;
data.topology_variance = schema.getTopologyVariance();
data.time_sampling = schema.getTimeSampling();
data.positions = schema.getPositionsProperty();
data.face_counts = schema.getFaceCountsProperty();
data.face_indices = schema.getFaceIndicesProperty();
data.num_samples = schema.getNumSamples();
data.velocities = schema.getVelocitiesProperty();
data.shader_face_sets = parse_face_sets_for_shader_assignment(schema, get_used_shaders());
read_geometry_data(proc, cached_data, data, progress);
if (progress.get_cancel()) {
return;
}
/* Use the schema as the base compound property to also be able to look for top level
* properties. */
read_attributes(
proc, cached_data, schema, schema.getUVsParam(), get_requested_attributes(), progress);
cached_data.invalidate_last_loaded_time(true);
data_loaded = true;
return;
}
SubDSchemaData data;
data.time_sampling = schema.getTimeSampling();
data.num_samples = schema.getNumSamples();
data.topology_variance = schema.getTopologyVariance();
data.face_counts = schema.getFaceCountsProperty();
data.face_indices = schema.getFaceIndicesProperty();
data.positions = schema.getPositionsProperty();
data.face_varying_interpolate_boundary = schema.getFaceVaryingInterpolateBoundaryProperty();
data.face_varying_propagate_corners = schema.getFaceVaryingPropagateCornersProperty();
data.interpolate_boundary = schema.getInterpolateBoundaryProperty();
data.crease_indices = schema.getCreaseIndicesProperty();
data.crease_lengths = schema.getCreaseLengthsProperty();
data.crease_sharpnesses = schema.getCreaseSharpnessesProperty();
data.corner_indices = schema.getCornerIndicesProperty();
data.corner_sharpnesses = schema.getCornerSharpnessesProperty();
data.holes = schema.getHolesProperty();
data.subdivision_scheme = schema.getSubdivisionSchemeProperty();
data.velocities = schema.getVelocitiesProperty();
data.shader_face_sets = parse_face_sets_for_shader_assignment(schema, get_used_shaders());
read_geometry_data(proc, cached_data, data, progress);
if (progress.get_cancel()) {
return;
}
/* Use the schema as the base compound property to also be able to look for top level properties.
*/
read_attributes(
proc, cached_data, schema, schema.getUVsParam(), get_requested_attributes(), progress);
cached_data.invalidate_last_loaded_time(true);
data_loaded = true;
}
void AlembicObject::load_data_in_cache(CachedData &cached_data,
AlembicProcedural *proc,
const ICurvesSchema &schema,
Progress &progress)
{
/* Only load data for the original Geometry. */
if (instance_of) {
return;
}
cached_data.clear();
CurvesSchemaData data;
data.positions = schema.getPositionsProperty();
data.position_weights = schema.getPositionWeightsProperty();
data.normals = schema.getNormalsParam();
data.knots = schema.getKnotsProperty();
data.orders = schema.getOrdersProperty();
data.widths = schema.getWidthsParam();
data.velocities = schema.getVelocitiesProperty();
data.time_sampling = schema.getTimeSampling();
data.topology_variance = schema.getTopologyVariance();
data.num_samples = schema.getNumSamples();
data.num_vertices = schema.getNumVerticesProperty();
data.default_radius = proc->get_default_radius();
data.radius_scale = get_radius_scale();
read_geometry_data(proc, cached_data, data, progress);
if (progress.get_cancel()) {
return;
}
/* Use the schema as the base compound property to also be able to look for top level properties.
*/
read_attributes(
proc, cached_data, schema, schema.getUVsParam(), get_requested_attributes(), progress);
cached_data.invalidate_last_loaded_time(true);
data_loaded = true;
}
void AlembicObject::setup_transform_cache(CachedData &cached_data, float scale)
{
cached_data.transforms.clear();
cached_data.transforms.invalidate_last_loaded_time();
if (scale == 0.0f) {
scale = 1.0f;
}
if (xform_time_sampling) {
cached_data.transforms.set_time_sampling(*xform_time_sampling);
}
if (xform_samples.size() == 0) {
Transform tfm = transform_scale(make_float3(scale));
cached_data.transforms.add_data(tfm, 0.0);
}
else {
/* It is possible for a leaf node of the hierarchy to have multiple samples for its transforms
* if a sibling has animated transforms. So check if we indeed have animated transformations.
*/
M44d first_matrix = xform_samples.begin()->first;
bool has_animation = false;
for (const std::pair<chrono_t, M44d> pair : xform_samples) {
if (pair.second != first_matrix) {
has_animation = true;
break;
}
}
if (!has_animation) {
Transform tfm = make_transform(first_matrix, scale);
cached_data.transforms.add_data(tfm, 0.0);
}
else {
for (const std::pair<chrono_t, M44d> pair : xform_samples) {
Transform tfm = make_transform(pair.second, scale);
cached_data.transforms.add_data(tfm, pair.first);
}
}
}
}
AttributeRequestSet AlembicObject::get_requested_attributes()
{
AttributeRequestSet requested_attributes;
Geometry *geometry = object->get_geometry();
assert(geometry);
foreach (Node *node, geometry->get_used_shaders()) {
Shader *shader = static_cast<Shader *>(node);
foreach (const AttributeRequest &attr, shader->attributes.requests) {
if (attr.name != "") {
requested_attributes.add(attr.name);
}
}
}
return requested_attributes;
}
/* Update existing attributes and remove any attribute not in the cached_data, those attributes
* were added by Cycles (e.g. face normals) */
static void update_attributes(AttributeSet &attributes, CachedData &cached_data, double frame_time)
{
set<Attribute *> cached_attributes;
for (CachedData::CachedAttribute &attribute : cached_data.attributes) {
const CacheLookupResult<array<char>> result = attribute.data.data_for_time(frame_time);
if (result.has_no_data_for_time()) {
continue;
}
Attribute *attr = nullptr;
if (attribute.std != ATTR_STD_NONE) {
attr = attributes.add(attribute.std, attribute.name);
}
else {
attr = attributes.add(attribute.name, attribute.type_desc, attribute.element);
}
assert(attr);
cached_attributes.insert(attr);
if (!result.has_new_data()) {
continue;
}
const ccl::array<char> &attr_data = result.get_data();
/* weak way of detecting if the topology has changed
* todo: reuse code from device_update patch */
if (attr->buffer.size() != attr_data.size()) {
attr->buffer.resize(attr_data.size());
}
memcpy(attr->data(), attr_data.data(), attr_data.size());
attr->modified = true;
}
/* remove any attributes not in cached_attributes */
list<Attribute>::iterator it;
for (it = attributes.attributes.begin(); it != attributes.attributes.end();) {
if (cached_attributes.find(&(*it)) == cached_attributes.end()) {
attributes.remove(it++);
continue;
}
it++;
}
}
NODE_DEFINE(AlembicProcedural)
{
NodeType *type = NodeType::add("alembic", create);
SOCKET_STRING(filepath, "Filename", ustring());
SOCKET_FLOAT(frame, "Frame", 1.0f);
SOCKET_FLOAT(start_frame, "Start Frame", 1.0f);
SOCKET_FLOAT(end_frame, "End Frame", 1.0f);
SOCKET_FLOAT(frame_rate, "Frame Rate", 24.0f);
SOCKET_FLOAT(frame_offset, "Frame Offset", 0.0f);
SOCKET_FLOAT(default_radius, "Default Radius", 0.01f);
SOCKET_FLOAT(scale, "Scale", 1.0f);
SOCKET_NODE_ARRAY(objects, "Objects", AlembicObject::get_node_type());
SOCKET_BOOLEAN(use_prefetch, "Use Prefetch", true);
SOCKET_INT(prefetch_cache_size, "Prefetch Cache Size", 4096);
return type;
}
AlembicProcedural::AlembicProcedural() : Procedural(get_node_type())
{
objects_loaded = false;
scene_ = nullptr;
}
AlembicProcedural::~AlembicProcedural()
{
ccl::set<Geometry *> geometries_set;
ccl::set<Object *> objects_set;
ccl::set<AlembicObject *> abc_objects_set;
foreach (Node *node, objects) {
AlembicObject *abc_object = static_cast<AlembicObject *>(node);
if (abc_object->get_object()) {
objects_set.insert(abc_object->get_object());
if (abc_object->get_object()->get_geometry()) {
geometries_set.insert(abc_object->get_object()->get_geometry());
}
}
delete_node(abc_object);
}
/* We may delete a Procedural before rendering started, so scene_ can be null. */
if (!scene_) {
assert(geometries_set.empty());
assert(objects_set.empty());
return;
}
scene_->delete_nodes(geometries_set, this);
scene_->delete_nodes(objects_set, this);
}
void AlembicProcedural::generate(Scene *scene, Progress &progress)
{
assert(scene_ == nullptr || scene_ == scene);
scene_ = scene;
if (frame < start_frame || frame > end_frame) {
clear_modified();
return;
}
bool need_shader_updates = false;
bool need_data_updates = false;
foreach (Node *object_node, objects) {
AlembicObject *object = static_cast<AlembicObject *>(object_node);
if (object->is_modified()) {
need_data_updates = true;
}
/* Check if the shaders were modified. */
if (object->used_shaders_is_modified() && object->get_object() &&
object->get_object()->get_geometry()) {
Geometry *geometry = object->get_object()->get_geometry();
array<Node *> used_shaders = object->get_used_shaders();
geometry->set_used_shaders(used_shaders);
need_shader_updates = true;
}
/* Check for changes in shaders (e.g. newly requested attributes). */
foreach (Node *shader_node, object->get_used_shaders()) {
Shader *shader = static_cast<Shader *>(shader_node);
if (shader->need_update_geometry()) {
object->need_shader_update = true;
need_shader_updates = true;
}
}
}
if (!is_modified() && !need_shader_updates && !need_data_updates) {
return;
}
if (!archive.valid()) {
Alembic::AbcCoreFactory::IFactory factory;
factory.setPolicy(Alembic::Abc::ErrorHandler::kQuietNoopPolicy);
archive = factory.getArchive(filepath.c_str());
if (!archive.valid()) {
/* avoid potential infinite update loops in viewport synchronization */
filepath.clear();
clear_modified();
return;
}
}
if (!objects_loaded || objects_is_modified()) {
load_objects(progress);
objects_loaded = true;
}
const chrono_t frame_time = (chrono_t)((frame - frame_offset) / frame_rate);
/* Clear the subdivision caches as the data is stored differently. */
for (Node *node : objects) {
AlembicObject *object = static_cast<AlembicObject *>(node);
if (object->schema_type != AlembicObject::SUBD) {
continue;
}
if (object->ignore_subdivision_is_modified()) {
object->clear_cache();
}
}
if (use_prefetch_is_modified()) {
if (!use_prefetch) {
for (Node *node : objects) {
AlembicObject *object = static_cast<AlembicObject *>(node);
object->clear_cache();
}
}
}
if (prefetch_cache_size_is_modified()) {
/* Check whether the current memory usage fits in the new requested size,
* abort the render if it is any higher. */
size_t memory_used = 0ul;
for (Node *node : objects) {
AlembicObject *object = static_cast<AlembicObject *>(node);
memory_used += object->get_cached_data().memory_used();
}
if (memory_used > get_prefetch_cache_size_in_bytes()) {
progress.set_error("Error: Alembic Procedural memory limit reached");
return;
}
}
build_caches(progress);
foreach (Node *node, objects) {
AlembicObject *object = static_cast<AlembicObject *>(node);
if (progress.get_cancel()) {
return;
}
/* skip constant objects */
if (object->is_constant() && !object->is_modified() && !object->need_shader_update &&
!scale_is_modified()) {
continue;
}
if (object->schema_type == AlembicObject::POLY_MESH) {
read_mesh(object, frame_time);
}
else if (object->schema_type == AlembicObject::CURVES) {
read_curves(object, frame_time);
}
else if (object->schema_type == AlembicObject::SUBD) {
read_subd(object, frame_time);
}
object->need_shader_update = false;
object->clear_modified();
}
clear_modified();
}
void AlembicProcedural::add_object(AlembicObject *object)
{
objects.push_back_slow(object);
tag_objects_modified();
}
void AlembicProcedural::tag_update(Scene *scene)
{
scene->procedural_manager->tag_update();
}
AlembicObject *AlembicProcedural::get_or_create_object(const ustring &path)
{
foreach (Node *node, objects) {
AlembicObject *object = static_cast<AlembicObject *>(node);
if (object->get_path() == path) {
return object;
}
}
AlembicObject *object = create_node<AlembicObject>();
object->set_path(path);
add_object(object);
return object;
}
void AlembicProcedural::load_objects(Progress &progress)
{
unordered_map<string, AlembicObject *> object_map;
foreach (Node *node, objects) {
AlembicObject *object = static_cast<AlembicObject *>(node);
/* only consider newly added objects */
if (object->get_object() == nullptr) {
object_map.insert({object->get_path().c_str(), object});
}
}
IObject root = archive.getTop();
for (size_t i = 0; i < root.getNumChildren(); ++i) {
walk_hierarchy(root, root.getChildHeader(i), {}, object_map, progress);
}
/* Create nodes in the scene. */
for (std::pair<string, AlembicObject *> pair : object_map) {
AlembicObject *abc_object = pair.second;
Geometry *geometry = nullptr;
if (!abc_object->instance_of) {
if (abc_object->schema_type == AlembicObject::CURVES) {
geometry = scene_->create_node<Hair>();
}
else if (abc_object->schema_type == AlembicObject::POLY_MESH ||
abc_object->schema_type == AlembicObject::SUBD) {
geometry = scene_->create_node<Mesh>();
}
else {
continue;
}
geometry->set_owner(this);
geometry->name = abc_object->iobject.getName();
array<Node *> used_shaders = abc_object->get_used_shaders();
geometry->set_used_shaders(used_shaders);
}
Object *object = scene_->create_node<Object>();
object->set_owner(this);
object->set_geometry(geometry);
object->name = abc_object->iobject.getName();
abc_object->set_object(object);
}
/* Share geometries between instances. */
foreach (Node *node, objects) {
AlembicObject *abc_object = static_cast<AlembicObject *>(node);
if (abc_object->instance_of) {
abc_object->get_object()->set_geometry(
abc_object->instance_of->get_object()->get_geometry());
abc_object->schema_type = abc_object->instance_of->schema_type;
}
}
}
void AlembicProcedural::read_mesh(AlembicObject *abc_object, Abc::chrono_t frame_time)
{
CachedData &cached_data = abc_object->get_cached_data();
/* update sockets */
Object *object = abc_object->get_object();
cached_data.transforms.copy_to_socket(frame_time, object, object->get_tfm_socket());
if (object->is_modified()) {
object->tag_update(scene_);
}
/* Only update sockets for the original Geometry. */
if (abc_object->instance_of) {
return;
}
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
/* Make sure shader ids are also updated. */
if (mesh->used_shaders_is_modified()) {
mesh->tag_shader_modified();
}
cached_data.vertices.copy_to_socket(frame_time, mesh, mesh->get_verts_socket());
cached_data.shader.copy_to_socket(frame_time, mesh, mesh->get_shader_socket());
array<int3> *triangle_data = cached_data.triangles.data_for_time(frame_time).get_data_or_null();
if (triangle_data) {
array<int> triangles;
array<bool> smooth;
triangles.reserve(triangle_data->size() * 3);
smooth.reserve(triangle_data->size());
for (size_t i = 0; i < triangle_data->size(); ++i) {
int3 tri = (*triangle_data)[i];
triangles.push_back_reserved(tri.x);
triangles.push_back_reserved(tri.y);
triangles.push_back_reserved(tri.z);
smooth.push_back_reserved(1);
}
mesh->set_triangles(triangles);
mesh->set_smooth(smooth);
}
/* update attributes */
update_attributes(mesh->attributes, cached_data, frame_time);
if (mesh->is_modified()) {
bool need_rebuild = mesh->triangles_is_modified();
mesh->tag_update(scene_, need_rebuild);
}
}
void AlembicProcedural::read_subd(AlembicObject *abc_object, Abc::chrono_t frame_time)
{
if (abc_object->get_ignore_subdivision()) {
read_mesh(abc_object, frame_time);
return;
}
CachedData &cached_data = abc_object->get_cached_data();
/* Update sockets. */
Object *object = abc_object->get_object();
cached_data.transforms.copy_to_socket(frame_time, object, object->get_tfm_socket());
if (object->is_modified()) {
object->tag_update(scene_);
}
/* Only update sockets for the original Geometry. */
if (abc_object->instance_of) {
return;
}
if (abc_object->subd_max_level_is_modified() || abc_object->subd_dicing_rate_is_modified()) {
/* need to reset the current data is something changed */
cached_data.invalidate_last_loaded_time();
}
Mesh *mesh = static_cast<Mesh *>(object->get_geometry());
/* Make sure shader ids are also updated. */
if (mesh->used_shaders_is_modified()) {
mesh->tag_shader_modified();
}
/* Cycles overwrites the original triangles when computing displacement, so we always have to
* repass the data if something is animated (vertices most likely) to avoid buffer overflows. */
if (!cached_data.is_constant()) {
cached_data.invalidate_last_loaded_time();
/* remove previous triangles, if any */
array<int> triangles;
mesh->set_triangles(triangles);
}
mesh->clear_non_sockets();
/* Alembic is OpenSubDiv compliant, there is no option to set another subdivision type. */
mesh->set_subdivision_type(Mesh::SubdivisionType::SUBDIVISION_CATMULL_CLARK);
mesh->set_subd_max_level(abc_object->get_subd_max_level());
mesh->set_subd_dicing_rate(abc_object->get_subd_dicing_rate());
cached_data.vertices.copy_to_socket(frame_time, mesh, mesh->get_verts_socket());
/* cached_data.shader is also used for subd_shader */
cached_data.shader.copy_to_socket(frame_time, mesh, mesh->get_subd_shader_socket());
cached_data.subd_start_corner.copy_to_socket(
frame_time, mesh, mesh->get_subd_start_corner_socket());
cached_data.subd_num_corners.copy_to_socket(
frame_time, mesh, mesh->get_subd_num_corners_socket());
cached_data.subd_smooth.copy_to_socket(frame_time, mesh, mesh->get_subd_smooth_socket());
cached_data.subd_ptex_offset.copy_to_socket(
frame_time, mesh, mesh->get_subd_ptex_offset_socket());
cached_data.subd_face_corners.copy_to_socket(
frame_time, mesh, mesh->get_subd_face_corners_socket());
cached_data.num_ngons.copy_to_socket(frame_time, mesh, mesh->get_num_ngons_socket());
cached_data.subd_creases_edge.copy_to_socket(
frame_time, mesh, mesh->get_subd_creases_edge_socket());
cached_data.subd_creases_weight.copy_to_socket(
frame_time, mesh, mesh->get_subd_creases_weight_socket());
mesh->set_num_subd_faces(mesh->get_subd_shader().size());
/* Update attributes. */
update_attributes(mesh->subd_attributes, cached_data, frame_time);
if (mesh->is_modified()) {
bool need_rebuild = (mesh->triangles_is_modified()) ||
(mesh->subd_num_corners_is_modified()) ||
(mesh->subd_shader_is_modified()) || (mesh->subd_smooth_is_modified()) ||
(mesh->subd_ptex_offset_is_modified()) ||
(mesh->subd_start_corner_is_modified()) ||
(mesh->subd_face_corners_is_modified());
mesh->tag_update(scene_, need_rebuild);
}
}
void AlembicProcedural::read_curves(AlembicObject *abc_object, Abc::chrono_t frame_time)
{
CachedData &cached_data = abc_object->get_cached_data();
/* update sockets */
Object *object = abc_object->get_object();
cached_data.transforms.copy_to_socket(frame_time, object, object->get_tfm_socket());
if (object->is_modified()) {
object->tag_update(scene_);
}
/* Only update sockets for the original Geometry. */
if (abc_object->instance_of) {
return;
}
Hair *hair = static_cast<Hair *>(object->get_geometry());
/* Make sure shader ids are also updated. */
if (hair->used_shaders_is_modified()) {
hair->tag_curve_shader_modified();
}
cached_data.curve_keys.copy_to_socket(frame_time, hair, hair->get_curve_keys_socket());
cached_data.curve_radius.copy_to_socket(frame_time, hair, hair->get_curve_radius_socket());
cached_data.curve_shader.copy_to_socket(frame_time, hair, hair->get_curve_shader_socket());
cached_data.curve_first_key.copy_to_socket(frame_time, hair, hair->get_curve_first_key_socket());
/* update attributes */
update_attributes(hair->attributes, cached_data, frame_time);
const bool rebuild = (hair->curve_keys_is_modified() || hair->curve_radius_is_modified());
hair->tag_update(scene_, rebuild);
}
void AlembicProcedural::walk_hierarchy(
IObject parent,
const ObjectHeader &header,
MatrixSamplesData matrix_samples_data,
const unordered_map<std::string, AlembicObject *> &object_map,
Progress &progress)
{
if (progress.get_cancel()) {
return;
}
IObject next_object;
MatrixSampleMap concatenated_xform_samples;
if (IXform::matches(header)) {
IXform xform(parent, header.getName());
IXformSchema &xs = xform.getSchema();
if (xs.getNumOps() > 0) {
TimeSamplingPtr ts = xs.getTimeSampling();
MatrixSampleMap local_xform_samples;
MatrixSampleMap *temp_xform_samples = nullptr;
if (matrix_samples_data.samples == nullptr) {
/* If there is no parent transforms, fill the map directly. */
temp_xform_samples = &concatenated_xform_samples;
}
else {
/* use a temporary map */
temp_xform_samples = &local_xform_samples;
}
for (size_t i = 0; i < xs.getNumSamples(); ++i) {
chrono_t sample_time = ts->getSampleTime(index_t(i));
XformSample sample = xs.getValue(ISampleSelector(sample_time));
temp_xform_samples->insert({sample_time, sample.getMatrix()});
}
if (matrix_samples_data.samples != nullptr) {
concatenate_xform_samples(
*matrix_samples_data.samples, local_xform_samples, concatenated_xform_samples);
}
matrix_samples_data.samples = &concatenated_xform_samples;
matrix_samples_data.time_sampling = ts;
}
next_object = xform;
}
else if (ISubD::matches(header)) {
ISubD subd(parent, header.getName());
unordered_map<std::string, AlembicObject *>::const_iterator iter;
iter = object_map.find(subd.getFullName());
if (iter != object_map.end()) {
AlembicObject *abc_object = iter->second;
abc_object->iobject = subd;
abc_object->schema_type = AlembicObject::SUBD;
if (matrix_samples_data.samples) {
abc_object->xform_samples = *matrix_samples_data.samples;
abc_object->xform_time_sampling = matrix_samples_data.time_sampling;
}
}
next_object = subd;
}
else if (IPolyMesh::matches(header)) {
IPolyMesh mesh(parent, header.getName());
unordered_map<std::string, AlembicObject *>::const_iterator iter;
iter = object_map.find(mesh.getFullName());
if (iter != object_map.end()) {
AlembicObject *abc_object = iter->second;
abc_object->iobject = mesh;
abc_object->schema_type = AlembicObject::POLY_MESH;
if (matrix_samples_data.samples) {
abc_object->xform_samples = *matrix_samples_data.samples;
abc_object->xform_time_sampling = matrix_samples_data.time_sampling;
}
}
next_object = mesh;
}
else if (ICurves::matches(header)) {
ICurves curves(parent, header.getName());
unordered_map<std::string, AlembicObject *>::const_iterator iter;
iter = object_map.find(curves.getFullName());
if (iter != object_map.end()) {
AlembicObject *abc_object = iter->second;
abc_object->iobject = curves;
abc_object->schema_type = AlembicObject::CURVES;
if (matrix_samples_data.samples) {
abc_object->xform_samples = *matrix_samples_data.samples;
abc_object->xform_time_sampling = matrix_samples_data.time_sampling;
}
}
next_object = curves;
}
else if (IFaceSet::matches(header)) {
// ignore the face set, it will be read along with the data
}
else if (IPoints::matches(header)) {
// unsupported for now
}
else if (INuPatch::matches(header)) {
// unsupported for now
}
else {
next_object = parent.getChild(header.getName());
if (next_object.isInstanceRoot()) {
unordered_map<std::string, AlembicObject *>::const_iterator iter;
/* Was this object asked to be rendered? */
iter = object_map.find(next_object.getFullName());
if (iter != object_map.end()) {
AlembicObject *abc_object = iter->second;
/* Only try to render an instance if the original object is also rendered. */
iter = object_map.find(next_object.instanceSourcePath());
if (iter != object_map.end()) {
abc_object->iobject = next_object;
abc_object->instance_of = iter->second;
if (matrix_samples_data.samples) {
abc_object->xform_samples = *matrix_samples_data.samples;
abc_object->xform_time_sampling = matrix_samples_data.time_sampling;
}
}
}
}
}
if (next_object.valid()) {
for (size_t i = 0; i < next_object.getNumChildren(); ++i) {
walk_hierarchy(
next_object, next_object.getChildHeader(i), matrix_samples_data, object_map, progress);
}
}
}
void AlembicProcedural::build_caches(Progress &progress)
{
size_t memory_used = 0;
for (Node *node : objects) {
AlembicObject *object = static_cast<AlembicObject *>(node);
if (progress.get_cancel()) {
return;
}
if (object->schema_type == AlembicObject::POLY_MESH) {
if (!object->has_data_loaded()) {
IPolyMesh polymesh(object->iobject, Alembic::Abc::kWrapExisting);
IPolyMeshSchema schema = polymesh.getSchema();
object->load_data_in_cache(object->get_cached_data(), this, schema, progress);
}
else if (object->need_shader_update) {
IPolyMesh polymesh(object->iobject, Alembic::Abc::kWrapExisting);
IPolyMeshSchema schema = polymesh.getSchema();
read_attributes(this,
object->get_cached_data(),
schema,
schema.getUVsParam(),
object->get_requested_attributes(),
progress);
}
}
else if (object->schema_type == AlembicObject::CURVES) {
if (!object->has_data_loaded() || default_radius_is_modified() ||
object->radius_scale_is_modified()) {
ICurves curves(object->iobject, Alembic::Abc::kWrapExisting);
ICurvesSchema schema = curves.getSchema();
object->load_data_in_cache(object->get_cached_data(), this, schema, progress);
}
}
else if (object->schema_type == AlembicObject::SUBD) {
if (!object->has_data_loaded()) {
ISubD subd_mesh(object->iobject, Alembic::Abc::kWrapExisting);
ISubDSchema schema = subd_mesh.getSchema();
object->load_data_in_cache(object->get_cached_data(), this, schema, progress);
}
else if (object->need_shader_update) {
ISubD subd_mesh(object->iobject, Alembic::Abc::kWrapExisting);
ISubDSchema schema = subd_mesh.getSchema();
read_attributes(this,
object->get_cached_data(),
schema,
schema.getUVsParam(),
object->get_requested_attributes(),
progress);
}
}
if (scale_is_modified() || object->get_cached_data().transforms.size() == 0) {
object->setup_transform_cache(object->get_cached_data(), scale);
}
memory_used += object->get_cached_data().memory_used();
if (use_prefetch) {
if (memory_used > get_prefetch_cache_size_in_bytes()) {
progress.set_error("Error: Alembic Procedural memory limit reached");
return;
}
}
}
VLOG(1) << "AlembicProcedural memory usage : " << string_human_readable_size(memory_used);
}
CCL_NAMESPACE_END
#endif