Since 34b4487844, attributes are always made mutable when
accessed from the RNA API. This can result in unnecessary copies, which
increases memory usage and reduces performance.
Cycles is the only user of the C++ RNA API, which we'd like to remove
in the future since it doesn't really make sense in the big picture.
Hydra is now a better alternative for external render engines.
To start that change and fix the unnecessary copies, this commit
moves to use Blender headers directly for accessing attribute and
other geometry data. This also removes the few places that still had
overhead from the RNA API after the changes ([0]) in 3.6. In a simple
test with a large grid, I observed a 1.76x performance improvement,
from 1.04 to 0.59 seconds to extract the mesh data to Cycles.
[0]: https://wiki.blender.org/wiki/Reference/Release_Notes/3.6/Cycles#Performance
Pull Request: https://projects.blender.org/blender/blender/pulls/112306
381 lines
11 KiB
C++
381 lines
11 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_curves_types.h"
|
|
#include "DNA_defaults.h"
|
|
#include "DNA_material_types.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "BLI_index_range.hh"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math_base.h"
|
|
#include "BLI_math_matrix.hh"
|
|
#include "BLI_rand.hh"
|
|
#include "BLI_span.hh"
|
|
#include "BLI_string.h"
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_vector.hh"
|
|
|
|
#include "BKE_anim_data.h"
|
|
#include "BKE_curves.hh"
|
|
#include "BKE_customdata.h"
|
|
#include "BKE_geometry_fields.hh"
|
|
#include "BKE_geometry_set.hh"
|
|
#include "BKE_global.h"
|
|
#include "BKE_idtype.h"
|
|
#include "BKE_lib_id.h"
|
|
#include "BKE_lib_query.h"
|
|
#include "BKE_lib_remap.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_modifier.h"
|
|
#include "BKE_object.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "DEG_depsgraph_query.h"
|
|
|
|
#include "BLO_read_write.hh"
|
|
|
|
using blender::float3;
|
|
using blender::IndexRange;
|
|
using blender::MutableSpan;
|
|
using blender::RandomNumberGenerator;
|
|
using blender::Span;
|
|
using blender::Vector;
|
|
|
|
static const char *ATTR_POSITION = "position";
|
|
|
|
static void curves_init_data(ID *id)
|
|
{
|
|
Curves *curves = (Curves *)id;
|
|
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(curves, id));
|
|
|
|
MEMCPY_STRUCT_AFTER(curves, DNA_struct_default_get(Curves), id);
|
|
|
|
new (&curves->geometry) blender::bke::CurvesGeometry();
|
|
}
|
|
|
|
static void curves_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, const int /*flag*/)
|
|
{
|
|
Curves *curves_dst = (Curves *)id_dst;
|
|
const Curves *curves_src = (const Curves *)id_src;
|
|
curves_dst->mat = static_cast<Material **>(MEM_dupallocN(curves_src->mat));
|
|
|
|
new (&curves_dst->geometry) blender::bke::CurvesGeometry(curves_src->geometry.wrap());
|
|
|
|
if (curves_src->surface_uv_map != nullptr) {
|
|
curves_dst->surface_uv_map = BLI_strdup(curves_src->surface_uv_map);
|
|
}
|
|
|
|
curves_dst->batch_cache = nullptr;
|
|
}
|
|
|
|
static void curves_free_data(ID *id)
|
|
{
|
|
Curves *curves = (Curves *)id;
|
|
BKE_animdata_free(&curves->id, false);
|
|
|
|
curves->geometry.wrap().~CurvesGeometry();
|
|
|
|
BKE_curves_batch_cache_free(curves);
|
|
|
|
MEM_SAFE_FREE(curves->mat);
|
|
MEM_SAFE_FREE(curves->surface_uv_map);
|
|
}
|
|
|
|
static void curves_foreach_id(ID *id, LibraryForeachIDData *data)
|
|
{
|
|
Curves *curves = (Curves *)id;
|
|
for (int i = 0; i < curves->totcol; i++) {
|
|
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curves->mat[i], IDWALK_CB_USER);
|
|
}
|
|
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, curves->surface, IDWALK_CB_NOP);
|
|
}
|
|
|
|
static void curves_blend_write(BlendWriter *writer, ID *id, const void *id_address)
|
|
{
|
|
Curves *curves = (Curves *)id;
|
|
|
|
blender::bke::CurvesGeometry::BlendWriteData write_data =
|
|
curves->geometry.wrap().blend_write_prepare();
|
|
|
|
/* Write LibData */
|
|
BLO_write_id_struct(writer, Curves, id_address, &curves->id);
|
|
BKE_id_blend_write(writer, &curves->id);
|
|
|
|
/* Direct data */
|
|
curves->geometry.wrap().blend_write(*writer, curves->id, write_data);
|
|
|
|
BLO_write_string(writer, curves->surface_uv_map);
|
|
|
|
BLO_write_pointer_array(writer, curves->totcol, curves->mat);
|
|
}
|
|
|
|
static void curves_blend_read_data(BlendDataReader *reader, ID *id)
|
|
{
|
|
Curves *curves = (Curves *)id;
|
|
|
|
/* Geometry */
|
|
curves->geometry.wrap().blend_read(*reader);
|
|
|
|
BLO_read_data_address(reader, &curves->surface_uv_map);
|
|
|
|
/* Materials */
|
|
BLO_read_pointer_array(reader, (void **)&curves->mat);
|
|
}
|
|
|
|
IDTypeInfo IDType_ID_CV = {
|
|
/*id_code*/ ID_CV,
|
|
/*id_filter*/ FILTER_ID_CV,
|
|
/*main_listbase_index*/ INDEX_ID_CV,
|
|
/*struct_size*/ sizeof(Curves),
|
|
/*name*/ "Curves",
|
|
/*name_plural*/ "hair_curves",
|
|
/*translation_context*/ BLT_I18NCONTEXT_ID_CURVES,
|
|
/*flags*/ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
|
|
/*asset_type_info*/ nullptr,
|
|
|
|
/*init_data*/ curves_init_data,
|
|
/*copy_data*/ curves_copy_data,
|
|
/*free_data*/ curves_free_data,
|
|
/*make_local*/ nullptr,
|
|
/*foreach_id*/ curves_foreach_id,
|
|
/*foreach_cache*/ nullptr,
|
|
/*foreach_path*/ nullptr,
|
|
/*owner_pointer_get*/ nullptr,
|
|
|
|
/*blend_write*/ curves_blend_write,
|
|
/*blend_read_data*/ curves_blend_read_data,
|
|
/*blend_read_after_liblink*/ nullptr,
|
|
|
|
/*blend_read_undo_preserve*/ nullptr,
|
|
|
|
/*lib_override_apply_post*/ nullptr,
|
|
};
|
|
|
|
void *BKE_curves_add(Main *bmain, const char *name)
|
|
{
|
|
Curves *curves = static_cast<Curves *>(BKE_id_new(bmain, ID_CV, name));
|
|
|
|
return curves;
|
|
}
|
|
|
|
BoundBox *BKE_curves_boundbox_get(Object *ob)
|
|
{
|
|
using namespace blender;
|
|
BLI_assert(ob->type == OB_CURVES);
|
|
const Curves *curves_id = static_cast<const Curves *>(ob->data);
|
|
|
|
if (ob->runtime.bb != nullptr && (ob->runtime.bb->flag & BOUNDBOX_DIRTY) == 0) {
|
|
return ob->runtime.bb;
|
|
}
|
|
|
|
if (ob->runtime.bb == nullptr) {
|
|
ob->runtime.bb = MEM_cnew<BoundBox>(__func__);
|
|
const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
|
|
if (const std::optional<Bounds<float3>> bounds = curves.bounds_min_max()) {
|
|
BKE_boundbox_init_from_minmax(ob->runtime.bb, bounds->min, bounds->max);
|
|
}
|
|
else {
|
|
BKE_boundbox_init_from_minmax(ob->runtime.bb, float3(-1), float3(1));
|
|
}
|
|
}
|
|
|
|
return ob->runtime.bb;
|
|
}
|
|
|
|
bool BKE_curves_attribute_required(const Curves * /*curves*/, const char *name)
|
|
{
|
|
return STREQ(name, ATTR_POSITION);
|
|
}
|
|
|
|
Curves *BKE_curves_copy_for_eval(const Curves *curves_src)
|
|
{
|
|
return reinterpret_cast<Curves *>(
|
|
BKE_id_copy_ex(nullptr, &curves_src->id, nullptr, LIB_ID_COPY_LOCALIZE));
|
|
}
|
|
|
|
static void curves_evaluate_modifiers(Depsgraph *depsgraph,
|
|
Scene *scene,
|
|
Object *object,
|
|
blender::bke::GeometrySet &geometry_set)
|
|
{
|
|
/* Modifier evaluation modes. */
|
|
const bool use_render = (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER);
|
|
int required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime;
|
|
if (BKE_object_is_in_editmode(object)) {
|
|
required_mode = (ModifierMode)(int(required_mode) | eModifierMode_Editmode);
|
|
}
|
|
ModifierApplyFlag apply_flag = use_render ? MOD_APPLY_RENDER : MOD_APPLY_USECACHE;
|
|
const ModifierEvalContext mectx = {depsgraph, object, apply_flag};
|
|
|
|
BKE_modifiers_clear_errors(object);
|
|
|
|
/* Get effective list of modifiers to execute. Some effects like shape keys
|
|
* are added as virtual modifiers before the user created modifiers. */
|
|
VirtualModifierData virtual_modifier_data;
|
|
ModifierData *md = BKE_modifiers_get_virtual_modifierlist(object, &virtual_modifier_data);
|
|
|
|
/* Evaluate modifiers. */
|
|
for (; md; md = md->next) {
|
|
const ModifierTypeInfo *mti = BKE_modifier_get_info(static_cast<ModifierType>(md->type));
|
|
|
|
if (!BKE_modifier_is_enabled(scene, md, required_mode)) {
|
|
continue;
|
|
}
|
|
|
|
blender::bke::ScopedModifierTimer modifier_timer{*md};
|
|
|
|
if (mti->modify_geometry_set != nullptr) {
|
|
mti->modify_geometry_set(md, &mectx, &geometry_set);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_curves_data_update(Depsgraph *depsgraph, Scene *scene, Object *object)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::bke;
|
|
/* Free any evaluated data and restore original data. */
|
|
BKE_object_free_derived_caches(object);
|
|
|
|
/* Evaluate modifiers. */
|
|
Curves *curves = static_cast<Curves *>(object->data);
|
|
GeometrySet geometry_set = GeometrySet::from_curves(curves, GeometryOwnershipType::ReadOnly);
|
|
if (object->mode == OB_MODE_SCULPT_CURVES) {
|
|
/* Try to propagate deformation data through modifier evaluation, so that sculpt mode can work
|
|
* on evaluated curves. */
|
|
GeometryComponentEditData &edit_component =
|
|
geometry_set.get_component_for_write<GeometryComponentEditData>();
|
|
edit_component.curves_edit_hints_ = std::make_unique<CurvesEditHints>(
|
|
*static_cast<const Curves *>(DEG_get_original_object(object)->data));
|
|
}
|
|
curves_evaluate_modifiers(depsgraph, scene, object, geometry_set);
|
|
|
|
/* Assign evaluated object. */
|
|
Curves *curves_eval = const_cast<Curves *>(geometry_set.get_curves());
|
|
if (curves_eval == nullptr) {
|
|
curves_eval = curves_new_nomain(0, 0);
|
|
BKE_object_eval_assign_data(object, &curves_eval->id, true);
|
|
}
|
|
else {
|
|
BKE_object_eval_assign_data(object, &curves_eval->id, false);
|
|
}
|
|
object->runtime.geometry_set_eval = new GeometrySet(std::move(geometry_set));
|
|
}
|
|
|
|
/* Draw Cache */
|
|
|
|
void (*BKE_curves_batch_cache_dirty_tag_cb)(Curves *curves, int mode) = nullptr;
|
|
void (*BKE_curves_batch_cache_free_cb)(Curves *curves) = nullptr;
|
|
|
|
void BKE_curves_batch_cache_dirty_tag(Curves *curves, int mode)
|
|
{
|
|
if (curves->batch_cache) {
|
|
BKE_curves_batch_cache_dirty_tag_cb(curves, mode);
|
|
}
|
|
}
|
|
|
|
void BKE_curves_batch_cache_free(Curves *curves)
|
|
{
|
|
if (curves->batch_cache) {
|
|
BKE_curves_batch_cache_free_cb(curves);
|
|
}
|
|
}
|
|
|
|
namespace blender::bke {
|
|
|
|
Curves *curves_new_nomain(const int points_num, const int curves_num)
|
|
{
|
|
BLI_assert(points_num >= 0);
|
|
BLI_assert(curves_num >= 0);
|
|
Curves *curves_id = static_cast<Curves *>(BKE_id_new_nomain(ID_CV, nullptr));
|
|
CurvesGeometry &curves = curves_id->geometry.wrap();
|
|
curves.resize(points_num, curves_num);
|
|
return curves_id;
|
|
}
|
|
|
|
Curves *curves_new_nomain_single(const int points_num, const CurveType type)
|
|
{
|
|
Curves *curves_id = curves_new_nomain(points_num, 1);
|
|
CurvesGeometry &curves = curves_id->geometry.wrap();
|
|
curves.offsets_for_write().last() = points_num;
|
|
curves.fill_curve_types(type);
|
|
return curves_id;
|
|
}
|
|
|
|
Curves *curves_new_nomain(CurvesGeometry curves)
|
|
{
|
|
Curves *curves_id = static_cast<Curves *>(BKE_id_new_nomain(ID_CV, nullptr));
|
|
curves_id->geometry.wrap() = std::move(curves);
|
|
return curves_id;
|
|
}
|
|
|
|
void curves_copy_parameters(const Curves &src, Curves &dst)
|
|
{
|
|
dst.flag = src.flag;
|
|
dst.attributes_active_index = src.attributes_active_index;
|
|
MEM_SAFE_FREE(dst.mat);
|
|
dst.mat = static_cast<Material **>(MEM_malloc_arrayN(src.totcol, sizeof(Material *), __func__));
|
|
dst.totcol = src.totcol;
|
|
MutableSpan(dst.mat, dst.totcol).copy_from(Span(src.mat, src.totcol));
|
|
dst.symmetry = src.symmetry;
|
|
dst.selection_domain = src.selection_domain;
|
|
dst.surface = src.surface;
|
|
MEM_SAFE_FREE(dst.surface_uv_map);
|
|
if (src.surface_uv_map != nullptr) {
|
|
dst.surface_uv_map = BLI_strdup(src.surface_uv_map);
|
|
}
|
|
}
|
|
|
|
CurvesSurfaceTransforms::CurvesSurfaceTransforms(const Object &curves_ob, const Object *surface_ob)
|
|
{
|
|
this->curves_to_world = float4x4_view(curves_ob.object_to_world);
|
|
this->world_to_curves = math::invert(this->curves_to_world);
|
|
|
|
if (surface_ob != nullptr) {
|
|
this->surface_to_world = float4x4_view(surface_ob->object_to_world);
|
|
this->world_to_surface = math::invert(this->surface_to_world);
|
|
this->surface_to_curves = this->world_to_curves * this->surface_to_world;
|
|
this->curves_to_surface = this->world_to_surface * this->curves_to_world;
|
|
this->surface_to_curves_normal = math::transpose(math::invert(this->surface_to_curves));
|
|
}
|
|
}
|
|
|
|
bool CurvesEditHints::is_valid() const
|
|
{
|
|
const int point_num = this->curves_id_orig.geometry.point_num;
|
|
if (this->positions.has_value()) {
|
|
if (this->positions->size() != point_num) {
|
|
return false;
|
|
}
|
|
}
|
|
if (this->deform_mats.has_value()) {
|
|
if (this->deform_mats->size() != point_num) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void curves_normals_point_domain_calc(const CurvesGeometry &curves, MutableSpan<float3> normals)
|
|
{
|
|
const bke::CurvesFieldContext context(curves, ATTR_DOMAIN_POINT);
|
|
fn::FieldEvaluator evaluator(context, curves.points_num());
|
|
fn::Field<float3> field(std::make_shared<bke::NormalFieldInput>());
|
|
evaluator.add_with_destination(std::move(field), normals);
|
|
evaluator.evaluate();
|
|
}
|
|
|
|
} // namespace blender::bke
|