diff --git a/source/blender/blenkernel/BKE_simulation_state.hh b/source/blender/blenkernel/BKE_simulation_state.hh index cc9d4505c50..cb1e322d22a 100644 --- a/source/blender/blenkernel/BKE_simulation_state.hh +++ b/source/blender/blenkernel/BKE_simulation_state.hh @@ -122,6 +122,7 @@ class ModifierSimulationState { /** File path to folder containing baked data. */ std::optional bdata_dir_; + SimulationZoneState *get_zone_state(const SimulationZoneID &zone_id); const SimulationZoneState *get_zone_state(const SimulationZoneID &zone_id) const; SimulationZoneState &get_zone_state_for_write(const SimulationZoneID &zone_id); void ensure_bake_loaded(const bNodeTree &ntree) const; diff --git a/source/blender/blenkernel/intern/simulation_state.cc b/source/blender/blenkernel/intern/simulation_state.cc index f1998d51104..d727f871152 100644 --- a/source/blender/blenkernel/intern/simulation_state.cc +++ b/source/blender/blenkernel/intern/simulation_state.cc @@ -185,6 +185,15 @@ StatesAroundFrame ModifierSimulationCache::get_states_around_frame(const SubFram return states_around_frame; } +SimulationZoneState *ModifierSimulationState::get_zone_state(const SimulationZoneID &zone_id) +{ + std::lock_guard lock{mutex_}; + if (auto *ptr = zone_states_.lookup_ptr(zone_id)) { + return ptr->get(); + } + return nullptr; +} + const SimulationZoneState *ModifierSimulationState::get_zone_state( const SimulationZoneID &zone_id) const { diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 2196c0a7749..987945a92a8 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -751,7 +751,7 @@ static void prepare_simulation_states_for_evaluation(const NodesModifierData &nm realtime_cache.prev_frame = realtime_cache.current_frame; realtime_cache.prev_state = std::move(realtime_cache.current_state); if (realtime_cache.prev_state) { - exec_data.prev_simulation_state = realtime_cache.prev_state.get(); + exec_data.prev_simulation_state_mutable = realtime_cache.prev_state.get(); } /* Create a new current state used to pass the data to the next frame. */ diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index c0075b64398..7b4b7203001 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -59,6 +59,12 @@ struct GeoNodesModifierData { bke::sim::ModifierSimulationState *current_simulation_state_for_write = nullptr; float simulation_time_delta = 0.0f; + /** + * The same as #prev_simulation_state, but the cached values can be moved from, + * to keep data managed by implicit sharing mutable. + */ + bke::sim::ModifierSimulationState *prev_simulation_state_mutable = nullptr; + /** * Some nodes should be executed even when their output is not used (e.g. active viewer nodes and * the node groups they are contained in). diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index fc457caac72..6c069db8af2 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -139,15 +139,21 @@ void socket_declarations_for_simulation_items(Span items, NodeDeclaration &r_declaration); const CPPType &get_simulation_item_cpp_type(eNodeSocketDatatype socket_type); const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item); -void values_to_simulation_state(const Span node_simulation_items, - const Span input_values, - bke::sim::SimulationZoneState &r_zone_state); -void simulation_state_to_values(const Span node_simulation_items, - const bke::sim::SimulationZoneState &zone_state, - const Object &self_object, - const ComputeContext &compute_context, - const bNode &sim_output_node, - Span r_output_values); +void move_values_to_simulation_state(const Span node_simulation_items, + const Span input_values, + bke::sim::SimulationZoneState &r_zone_state); +void move_simulation_state_to_values(const Span node_simulation_items, + bke::sim::SimulationZoneState &zone_state, + const Object &self_object, + const ComputeContext &compute_context, + const bNode &sim_output_node, + Span r_output_values); +void copy_simulation_state_to_values(const Span node_simulation_items, + const bke::sim::SimulationZoneState &zone_state, + const Object &self_object, + const ComputeContext &compute_context, + const bNode &sim_output_node, + Span r_output_values); void copy_with_checked_indices(const GVArray &src, const VArray &indices, diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc index 8211309bbf2..bda153b0207 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_input.cc @@ -72,37 +72,74 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { const bke::sim::SimulationZoneID zone_id = get_simulation_zone_id(user_data, output_node_id_); - const bke::sim::SimulationZoneState *prev_zone_state = - modifier_data.prev_simulation_state == nullptr ? - nullptr : - modifier_data.prev_simulation_state->get_zone_state(zone_id); - - std::optional initial_prev_zone_state; - if (prev_zone_state == nullptr) { - Array input_values(simulation_items_.size(), nullptr); - for (const int i : simulation_items_.index_range()) { - input_values[i] = params.try_get_input_data_ptr_or_request(i); - } - if (input_values.as_span().contains(nullptr)) { - /* Wait until all inputs are available. */ + /* When caching is turned off and the old state doesn't need to persist, moving data + * from the last state instead of copying it can avoid copies of geometry data arrays. */ + if (auto *state = modifier_data.prev_simulation_state_mutable) { + if (bke::sim::SimulationZoneState *zone = state->get_zone_state(zone_id)) { + this->output_simulation_state_move(params, user_data, *zone); return; } - - initial_prev_zone_state.emplace(); - values_to_simulation_state(simulation_items_, input_values, *initial_prev_zone_state); - prev_zone_state = &*initial_prev_zone_state; } - Array output_values(simulation_items_.size()); + /* If there is a read-only state from the last frame, output that directly. */ + if (const auto *state = modifier_data.prev_simulation_state) { + if (const bke::sim::SimulationZoneState *zone = state->get_zone_state(zone_id)) { + this->output_simulation_state_copy(params, user_data, *zone); + return; + } + } + + /* When there is no previous state already, create the initial state. */ + Array input_values(simulation_items_.size(), nullptr); for (const int i : simulation_items_.index_range()) { - output_values[i] = params.get_output_data_ptr(i + 1); + input_values[i] = params.try_get_input_data_ptr_or_request(i); } - simulation_state_to_values(simulation_items_, - *prev_zone_state, - *modifier_data.self_object, - *user_data.compute_context, - node_, - output_values); + if (input_values.as_span().contains(nullptr)) { + /* Wait until all inputs are available. */ + return; + } + + /* Instead of outputing the initial values directly, convert them to a simulation state and + * then back. This ensures that the first frame behaves consistently with all other frames + * which are necessarily stored in the simulation cache. */ + bke::sim::SimulationZoneState initial_zone_state; + move_values_to_simulation_state(simulation_items_, input_values, initial_zone_state); + this->output_simulation_state_move(params, user_data, initial_zone_state); + } + + void output_simulation_state_copy(lf::Params ¶ms, + const GeoNodesLFUserData &user_data, + const bke::sim::SimulationZoneState &state) const + { + Array outputs(simulation_items_.size()); + for (const int i : simulation_items_.index_range()) { + outputs[i] = params.get_output_data_ptr(i + 1); + } + copy_simulation_state_to_values(simulation_items_, + state, + *user_data.modifier_data->self_object, + *user_data.compute_context, + node_, + outputs); + for (const int i : simulation_items_.index_range()) { + params.output_set(i + 1); + } + } + + void output_simulation_state_move(lf::Params ¶ms, + const GeoNodesLFUserData &user_data, + bke::sim::SimulationZoneState &state) const + { + Array outputs(simulation_items_.size()); + for (const int i : simulation_items_.index_range()) { + outputs[i] = params.get_output_data_ptr(i + 1); + } + move_simulation_state_to_values(simulation_items_, + state, + *user_data.modifier_data->self_object, + *user_data.compute_context, + node_, + outputs); for (const int i : simulation_items_.index_range()) { params.output_set(i + 1); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc index 767972abbd3..439a5011ea9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation_output.cc @@ -176,103 +176,13 @@ static void cleanup_geometry_for_simulation_state(GeometrySet &main_geometry) }); } -void simulation_state_to_values(const Span node_simulation_items, - const bke::sim::SimulationZoneState &zone_state, - const Object &self_object, - const ComputeContext &compute_context, - const bNode &node, - Span r_output_values) +/** + * Some attributes stored in the simulation state become anonymous attributes in geometry nodes. + * This maps attribute names to their corresponding anonymous attribute ids. + */ +static void rename_attributes(const Span geometries, + const Map &attribute_map) { - /* Some attributes stored in the simulation state become anonymous attributes in geometry nodes. - * This maps attribute names to their corresponding anonymous attribute ids. */ - Map attribute_map; - Vector geometries; - - for (const int i : node_simulation_items.index_range()) { - const NodeSimulationItem &item = node_simulation_items[i]; - const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); - const CPPType &cpp_type = get_simulation_item_cpp_type(socket_type); - - void *r_output_value = r_output_values[i]; - - if (!zone_state.item_by_identifier.contains(item.identifier)) { - cpp_type.value_initialize(r_output_value); - continue; - } - const bke::sim::SimulationStateItem &state_item = *zone_state.item_by_identifier.lookup( - item.identifier); - - switch (socket_type) { - case SOCK_GEOMETRY: { - if (const auto *geo_state_item = - dynamic_cast(&state_item)) - { - GeometrySet *geometry = new (r_output_value) GeometrySet(geo_state_item->geometry); - geometries.append(geometry); - } - else { - cpp_type.value_initialize(r_output_value); - } - break; - } - case SOCK_FLOAT: - case SOCK_VECTOR: - case SOCK_INT: - case SOCK_BOOLEAN: - case SOCK_ROTATION: - case SOCK_RGBA: { - const fn::ValueOrFieldCPPType &value_or_field_type = - *fn::ValueOrFieldCPPType::get_from_self(cpp_type); - if (const auto *primitive_state_item = - dynamic_cast(&state_item)) - { - if (primitive_state_item->type() == value_or_field_type.value) { - value_or_field_type.construct_from_value(r_output_value, - primitive_state_item->value()); - } - else { - cpp_type.value_initialize(r_output_value); - } - } - else if (const auto *attribute_state_item = - dynamic_cast(&state_item)) - { - AnonymousAttributeIDPtr attribute_id = MEM_new( - __func__, - self_object, - compute_context, - node, - std::to_string(item.identifier), - item.name); - GField field{std::make_shared( - attribute_id, value_or_field_type.value, node.label_or_name())}; - value_or_field_type.construct_from_field(r_output_value, std::move(field)); - attribute_map.add(attribute_state_item->name(), std::move(attribute_id)); - } - else { - cpp_type.value_initialize(r_output_value); - } - break; - } - case SOCK_STRING: { - if (const auto *string_state_item = - dynamic_cast(&state_item)) - { - new (r_output_value) ValueOrField(string_state_item->value()); - } - else { - cpp_type.value_initialize(r_output_value); - } - break; - } - default: { - cpp_type.value_initialize(r_output_value); - break; - } - } - } - - /* Make some attributes anonymous. */ for (GeometrySet *geometry : geometries) { for (const GeometryComponent::Type type : {GeometryComponent::Type::Mesh, GeometryComponent::Type::Curve, @@ -282,6 +192,16 @@ void simulation_state_to_values(const Span node_simulation_i if (!geometry->has(type)) { continue; } + /* Avoid write access on the geometry when unnecessary to avoid copying data-blocks. */ + const AttributeAccessor attributes_read_only = + *geometry->get_component_for_read(type)->attributes(); + if (std::none_of(attribute_map.keys().begin(), + attribute_map.keys().end(), + [&](const StringRef name) { return attributes_read_only.contains(name); })) + { + continue; + } + GeometryComponent &component = geometry->get_component_for_write(type); MutableAttributeAccessor attributes = *component.attributes_for_write(); for (const MapItem &attribute_item : @@ -292,9 +212,162 @@ void simulation_state_to_values(const Span node_simulation_i } } -void values_to_simulation_state(const Span node_simulation_items, - const Span input_values, - bke::sim::SimulationZoneState &r_zone_state) +static bool copy_value_or_field_simulation_state_to_value( + const Object &self_object, + const ComputeContext &compute_context, + const bNode &node, + const NodeSimulationItem &sim_item, + const CPPType &cpp_type, + const bke::sim::SimulationStateItem &state_item, + Vector &r_geometries, + Map &r_attribute_map, + void *r_output_value) +{ + switch (eNodeSocketDatatype(sim_item.socket_type)) { + case SOCK_GEOMETRY: { + if (const auto *item = dynamic_cast( + &state_item)) { + GeometrySet *geometry = new (r_output_value) GeometrySet(item->geometry); + r_geometries.append(geometry); + return true; + } + break; + } + case SOCK_FLOAT: + case SOCK_VECTOR: + case SOCK_INT: + case SOCK_BOOLEAN: + case SOCK_ROTATION: + case SOCK_RGBA: { + const fn::ValueOrFieldCPPType &value_or_field_type = *fn::ValueOrFieldCPPType::get_from_self( + cpp_type); + if (const auto *item = dynamic_cast( + &state_item)) { + if (item->type() == value_or_field_type.value) { + value_or_field_type.construct_from_value(r_output_value, item->value()); + return true; + } + } + if (const auto *item = dynamic_cast( + &state_item)) { + AnonymousAttributeIDPtr attribute_id = MEM_new( + __func__, + self_object, + compute_context, + node, + std::to_string(sim_item.identifier), + sim_item.name); + GField field{std::make_shared( + attribute_id, value_or_field_type.value, node.label_or_name())}; + value_or_field_type.construct_from_field(r_output_value, std::move(field)); + r_attribute_map.add(item->name(), std::move(attribute_id)); + return true; + } + break; + } + case SOCK_STRING: { + if (const auto *item = dynamic_cast( + &state_item)) { + new (r_output_value) ValueOrField(item->value()); + return true; + } + break; + } + default: + break; + } + return false; +} + +void move_simulation_state_to_values(const Span node_simulation_items, + bke::sim::SimulationZoneState &zone_state, + const Object &self_object, + const ComputeContext &compute_context, + const bNode &node, + Span r_output_values) +{ + Map attribute_map; + Vector geometries; + + for (const int i : node_simulation_items.index_range()) { + const NodeSimulationItem &sim_item = node_simulation_items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(sim_item.socket_type); + const CPPType &cpp_type = get_simulation_item_cpp_type(socket_type); + const std::unique_ptr *state_item = + zone_state.item_by_identifier.lookup_ptr(sim_item.identifier); + if (!state_item) { + cpp_type.value_initialize(r_output_values[i]); + continue; + } + + if (socket_type == SOCK_GEOMETRY) { + if (auto *item = dynamic_cast(state_item->get())) { + GeometrySet *geometry = new (r_output_values[i]) GeometrySet(std::move(item->geometry)); + geometries.append(geometry); + } + else { + cpp_type.value_initialize(r_output_values[i]); + } + } + else { + if (!copy_value_or_field_simulation_state_to_value(self_object, + compute_context, + node, + sim_item, + cpp_type, + *state_item->get(), + geometries, + attribute_map, + r_output_values[i])) + { + cpp_type.value_initialize(r_output_values[i]); + } + } + } + + rename_attributes(geometries, attribute_map); +} + +void copy_simulation_state_to_values(const Span node_simulation_items, + const bke::sim::SimulationZoneState &zone_state, + const Object &self_object, + const ComputeContext &compute_context, + const bNode &node, + Span r_output_values) +{ + Map attribute_map; + Vector geometries; + + for (const int i : node_simulation_items.index_range()) { + const NodeSimulationItem &sim_item = node_simulation_items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(sim_item.socket_type); + const CPPType &cpp_type = get_simulation_item_cpp_type(socket_type); + const std::unique_ptr *state_item = + zone_state.item_by_identifier.lookup_ptr(sim_item.identifier); + if (!state_item) { + cpp_type.value_initialize(r_output_values[i]); + continue; + } + if (!copy_value_or_field_simulation_state_to_value(self_object, + compute_context, + node, + sim_item, + cpp_type, + *state_item->get(), + geometries, + attribute_map, + r_output_values[i])) + { + cpp_type.value_initialize(r_output_values[i]); + } + } + + rename_attributes(geometries, attribute_map); +} + +void move_values_to_simulation_state(const Span node_simulation_items, + const Span input_values, + bke::sim::SimulationZoneState &r_zone_state) { Vector stored_geometries; @@ -712,8 +785,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { nullptr; if (eval_data.is_first_evaluation && current_zone_state != nullptr) { /* Common case when data is cached already. */ - this->output_cached_state( - params, *modifier_data.self_object, *user_data.compute_context, *current_zone_state); + this->output_cached_state(params, user_data, *current_zone_state); return; } @@ -734,8 +806,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { nullptr; if (next_zone_state == nullptr) { /* Output the last cached simulation state. */ - this->output_cached_state( - params, *modifier_data.self_object, *user_data.compute_context, *prev_zone_state); + this->output_cached_state(params, user_data, *prev_zone_state); return; } /* A previous and next frame is cached already, but the current frame is not. */ @@ -762,22 +833,24 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { /* Wait until all inputs are available. */ return; } - values_to_simulation_state(simulation_items_, input_values, new_zone_state); - this->output_cached_state( - params, *modifier_data.self_object, *user_data.compute_context, new_zone_state); + move_values_to_simulation_state(simulation_items_, input_values, new_zone_state); + this->output_cached_state(params, user_data, new_zone_state); } void output_cached_state(lf::Params ¶ms, - const Object &self_object, - const ComputeContext &compute_context, + GeoNodesLFUserData &user_data, const bke::sim::SimulationZoneState &state) const { Array output_values(simulation_items_.size()); for (const int i : simulation_items_.index_range()) { output_values[i] = params.get_output_data_ptr(i); } - simulation_state_to_values( - simulation_items_, state, self_object, compute_context, node_, output_values); + copy_simulation_state_to_values(simulation_items_, + state, + *user_data.modifier_data->self_object, + *user_data.compute_context, + node_, + output_values); for (const int i : simulation_items_.index_range()) { params.output_set(i); } @@ -794,7 +867,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { for (const int i : simulation_items_.index_range()) { output_values[i] = params.get_output_data_ptr(i); } - simulation_state_to_values( + copy_simulation_state_to_values( simulation_items_, prev_state, self_object, compute_context, node_, output_values); Array next_values(simulation_items_.size()); @@ -803,7 +876,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { const CPPType &type = *outputs_[i].type; next_values[i] = allocator.allocate(type.size(), type.alignment()); } - simulation_state_to_values( + copy_simulation_state_to_values( simulation_items_, next_state, self_object, compute_context, node_, next_values); for (const int i : simulation_items_.index_range()) {