Files
test/source/blender/modifiers/intern/MOD_nodes.cc
Jacques Lucke b7a1325c3c BLI: use blender::Mutex by default which wraps tbb::mutex
This patch adds a new `BLI_mutex.hh` header which adds `blender::Mutex` as alias
for either `tbb::mutex` or `std::mutex` depending on whether TBB is enabled.

Description copied from the patch:
```
/**
 * blender::Mutex should be used as the default mutex in Blender. It implements a subset of the API
 * of std::mutex but has overall better guaranteed properties. It can be used with RAII helpers
 * like std::lock_guard. However, it is not compatible with e.g. std::condition_variable. So one
 * still has to use std::mutex for that case.
 *
 * The mutex provided by TBB has these properties:
 * - It's as fast as a spin-lock in the non-contended case, i.e. when no other thread is trying to
 *   lock the mutex at the same time.
 * - In the contended case, it spins a couple of times but then blocks to avoid draining system
 *   resources by spinning for a long time.
 * - It's only 1 byte large, compared to e.g. 40 bytes when using the std::mutex of GCC. This makes
 *   it more feasible to have many smaller mutexes which can improve scalability of algorithms
 *   compared to using fewer larger mutexes. Also it just reduces "memory slop" across Blender.
 * - It is *not* a fair mutex, i.e. it's not guaranteed that a thread will ever be able to lock the
 *   mutex when there are always more than one threads that try to lock it. In the majority of
 *   cases, using a fair mutex just causes extra overhead without any benefit. std::mutex is not
 *   guaranteed to be fair either.
 */
 ```

The performance benchmark suggests that the impact is negilible in almost
all cases. The only benchmarks that show interesting behavior are the once
testing foreach zones in Geometry Nodes. These tests are explicitly testing
overhead, which I still have to reduce over time. So it's not unexpected that
changing the mutex has an impact there. What's interesting is that on macos the
performance improves a lot while on linux it gets worse. Since that overhead
should eventually be removed almost entirely, I don't really consider that
blocking.

Links:
* Documentation of different mutex flavors in TBB:
  https://www.intel.com/content/www/us/en/docs/onetbb/developer-guide-api-reference/2021-12/mutex-flavors.html
* Older implementation of a similar mutex by me:
  https://archive.blender.org/developer/differential/0016/0016711/index.html
* Interesting read regarding how a mutex can be this small:
  https://webkit.org/blog/6161/locking-in-webkit/

Pull Request: https://projects.blender.org/blender/blender/pulls/138370
2025-05-07 04:53:16 +02:00

3096 lines
115 KiB
C++

/* SPDX-FileCopyrightText: 2005 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup modifiers
*/
#include <cstring>
#include <fmt/format.h>
#include <sstream>
#include <string>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_multi_value_map.hh"
#include "BLI_set.hh"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "DNA_array_utils.hh"
#include "DNA_collection_types.h"
#include "DNA_curves_types.h"
#include "DNA_defaults.h"
#include "DNA_material_types.h"
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_pointcloud_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "DNA_view3d_types.h"
#include "DNA_windowmanager_types.h"
#include "BKE_bake_data_block_map.hh"
#include "BKE_bake_geometry_nodes_modifier.hh"
#include "BKE_compute_context_cache.hh"
#include "BKE_compute_contexts.hh"
#include "BKE_customdata.hh"
#include "BKE_global.hh"
#include "BKE_idprop.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_query.hh"
#include "BKE_main.hh"
#include "BKE_mesh.hh"
#include "BKE_modifier.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_object.hh"
#include "BKE_packedFile.hh"
#include "BKE_pointcloud.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.hh"
#include "BLO_read_write.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "BLT_translation.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.hh"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_query.hh"
#include "DEG_depsgraph_writeback_sync.hh"
#include "MOD_modifiertypes.hh"
#include "MOD_nodes.hh"
#include "MOD_ui_common.hh"
#include "ED_node.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ED_undo.hh"
#include "ED_viewer_path.hh"
#include "NOD_geometry.hh"
#include "NOD_geometry_nodes_dependencies.hh"
#include "NOD_geometry_nodes_execute.hh"
#include "NOD_geometry_nodes_gizmos.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
#include "NOD_node_declaration.hh"
#include "NOD_socket_usage_inference.hh"
namespace lf = blender::fn::lazy_function;
namespace geo_log = blender::nodes::geo_eval_log;
namespace bake = blender::bke::bake;
namespace blender {
static void init_data(ModifierData *md)
{
NodesModifierData *nmd = (NodesModifierData *)md;
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(nmd, modifier));
nmd->modifier.layout_panel_open_flag |= 1 << NODES_MODIFIER_PANEL_WARNINGS;
MEMCPY_STRUCT_AFTER(nmd, DNA_struct_default_get(NodesModifierData), modifier);
nmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
nmd->runtime->cache = std::make_shared<bake::ModifierCache>();
}
static void find_dependencies_from_settings(const NodesModifierSettings &settings,
nodes::GeometryNodesEvalDependencies &deps)
{
IDP_foreach_property(settings.properties, IDP_TYPE_FILTER_ID, [&](IDProperty *property) {
if (ID *id = IDP_Id(property)) {
deps.add_generic_id_full(id);
}
});
}
/* We don't know exactly what attributes from the other object we will need. */
static const CustomData_MeshMasks dependency_data_mask{CD_MASK_PROP_ALL | CD_MASK_MDEFORMVERT,
CD_MASK_PROP_ALL,
CD_MASK_PROP_ALL,
CD_MASK_PROP_ALL,
CD_MASK_PROP_ALL};
static void add_collection_relation(const ModifierUpdateDepsgraphContext *ctx,
Collection &collection)
{
DEG_add_collection_geometry_relation(ctx->node, &collection, "Nodes Modifier");
DEG_add_collection_geometry_customdata_mask(ctx->node, &collection, &dependency_data_mask);
}
static void add_object_relation(
const ModifierUpdateDepsgraphContext *ctx,
Object &object,
const nodes::GeometryNodesEvalDependencies::ObjectDependencyInfo &info)
{
if (info.transform) {
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_TRANSFORM, "Nodes Modifier");
}
if (&(ID &)object == &ctx->object->id) {
return;
}
if (info.geometry) {
if (object.type == OB_EMPTY && object.instance_collection != nullptr) {
add_collection_relation(ctx, *object.instance_collection);
}
else if (DEG_object_has_geometry_component(&object)) {
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_GEOMETRY, "Nodes Modifier");
DEG_add_customdata_mask(ctx->node, &object, &dependency_data_mask);
}
}
if (object.type == OB_CAMERA) {
if (info.camera_parameters) {
DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_PARAMETERS, "Nodes Modifier");
}
}
}
static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
{
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
if (nmd->node_group == nullptr) {
return;
}
if (ID_MISSING(nmd->node_group)) {
return;
}
DEG_add_node_tree_output_relation(ctx->node, nmd->node_group, "Nodes Modifier");
nodes::GeometryNodesEvalDependencies eval_deps =
nodes::gather_geometry_nodes_eval_dependencies_recursive(*nmd->node_group);
/* Create dependencies to data-blocks referenced by the settings in the modifier. */
find_dependencies_from_settings(nmd->settings, eval_deps);
if (ctx->object->type == OB_CURVES) {
Curves *curves_id = static_cast<Curves *>(ctx->object->data);
if (curves_id->surface != nullptr) {
eval_deps.add_object(curves_id->surface);
}
}
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
for (const NodesModifierDataBlock &data_block : Span(bake.data_blocks, bake.data_blocks_num)) {
if (data_block.id) {
eval_deps.add_generic_id_full(data_block.id);
}
}
}
for (ID *id : eval_deps.ids.values()) {
switch ((ID_Type)GS(id->name)) {
case ID_OB: {
Object *object = reinterpret_cast<Object *>(id);
add_object_relation(
ctx, *object, eval_deps.objects_info.lookup_default(object->id.session_uid, {}));
break;
}
case ID_GR: {
Collection *collection = reinterpret_cast<Collection *>(id);
add_collection_relation(ctx, *collection);
break;
}
case ID_IM:
case ID_TE: {
DEG_add_generic_id_relation(ctx->node, id, "Nodes Modifier");
break;
}
default: {
/* Purposefully don't add relations for materials. While there are material sockets,
* the pointers are only passed around as handles rather than dereferenced. */
break;
}
}
}
if (eval_deps.needs_own_transform) {
DEG_add_depends_on_transform_relation(ctx->node, "Nodes Modifier");
}
if (eval_deps.needs_active_camera) {
DEG_add_scene_camera_relation(ctx->node, ctx->scene, DEG_OB_COMP_TRANSFORM, "Nodes Modifier");
}
/* Active camera is a scene parameter that can change, so we need a relation for that, too. */
if (eval_deps.needs_active_camera || eval_deps.needs_scene_render_params) {
DEG_add_scene_relation(ctx->node, ctx->scene, DEG_SCENE_COMP_PARAMETERS, "Nodes Modifier");
}
}
static bool depends_on_time(Scene * /*scene*/, ModifierData *md)
{
const NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
const bNodeTree *tree = nmd->node_group;
if (tree == nullptr) {
return false;
}
if (ID_MISSING(tree)) {
return false;
}
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
if (bake.bake_mode == NODES_MODIFIER_BAKE_MODE_ANIMATION) {
return true;
}
}
nodes::GeometryNodesEvalDependencies eval_deps =
nodes::gather_geometry_nodes_eval_dependencies_recursive(*nmd->node_group);
return eval_deps.time_dependent;
}
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
{
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
walk(user_data, ob, (ID **)&nmd->node_group, IDWALK_CB_USER);
IDP_foreach_property(nmd->settings.properties, IDP_TYPE_FILTER_ID, [&](IDProperty *id_prop) {
walk(user_data, ob, (ID **)&id_prop->data.pointer, IDWALK_CB_USER);
});
for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) {
for (NodesModifierDataBlock &data_block : MutableSpan(bake.data_blocks, bake.data_blocks_num))
{
walk(user_data, ob, &data_block.id, IDWALK_CB_USER);
}
}
}
static void foreach_tex_link(ModifierData *md, Object *ob, TexWalkFunc walk, void *user_data)
{
PointerRNA ptr = RNA_pointer_create_discrete(&ob->id, &RNA_Modifier, md);
PropertyRNA *prop = RNA_struct_find_property(&ptr, "texture");
walk(user_data, ob, md, &ptr, prop);
}
static bool is_disabled(const Scene * /*scene*/, ModifierData *md, bool /*use_render_params*/)
{
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
if (nmd->node_group == nullptr) {
return true;
}
return false;
}
static bool logging_enabled(const ModifierEvalContext *ctx)
{
if (!DEG_is_active(ctx->depsgraph)) {
return false;
}
if ((ctx->flag & MOD_APPLY_ORCO) != 0) {
return false;
}
return true;
}
static void update_id_properties_from_node_group(NodesModifierData *nmd)
{
if (nmd->node_group == nullptr) {
if (nmd->settings.properties) {
IDP_FreeProperty(nmd->settings.properties);
nmd->settings.properties = nullptr;
}
return;
}
IDProperty *old_properties = nmd->settings.properties;
nmd->settings.properties = bke::idprop::create_group("Nodes Modifier Settings").release();
IDProperty *new_properties = nmd->settings.properties;
nodes::update_input_properties_from_node_tree(*nmd->node_group, old_properties, *new_properties);
nodes::update_output_properties_from_node_tree(
*nmd->node_group, old_properties, *new_properties);
if (old_properties != nullptr) {
IDP_FreeProperty(old_properties);
}
}
static void remove_outdated_bake_caches(NodesModifierData &nmd)
{
if (!nmd.runtime->cache) {
if (nmd.bakes_num == 0) {
return;
}
nmd.runtime->cache = std::make_shared<bake::ModifierCache>();
}
bake::ModifierCache &modifier_cache = *nmd.runtime->cache;
std::lock_guard lock{modifier_cache.mutex};
Set<int> existing_bake_ids;
for (const NodesModifierBake &bake : Span{nmd.bakes, nmd.bakes_num}) {
existing_bake_ids.add(bake.id);
}
auto remove_predicate = [&](auto item) { return !existing_bake_ids.contains(item.key); };
modifier_cache.bake_cache_by_id.remove_if(remove_predicate);
modifier_cache.simulation_cache_by_id.remove_if(remove_predicate);
}
static void update_bakes_from_node_group(NodesModifierData &nmd)
{
Map<int, NodesModifierBake *> old_bake_by_id;
for (NodesModifierBake &bake : MutableSpan(nmd.bakes, nmd.bakes_num)) {
old_bake_by_id.add(bake.id, &bake);
}
Vector<int> new_bake_ids;
if (nmd.node_group) {
for (const bNestedNodeRef &ref : nmd.node_group->nested_node_refs_span()) {
const bNode *node = nmd.node_group->find_nested_node(ref.id);
if (node) {
if (ELEM(node->type_legacy, GEO_NODE_SIMULATION_OUTPUT, GEO_NODE_BAKE)) {
new_bake_ids.append(ref.id);
}
}
else if (old_bake_by_id.contains(ref.id)) {
/* Keep baked data in case linked data is missing so that it still exists when the linked
* data has been found. */
new_bake_ids.append(ref.id);
}
}
}
NodesModifierBake *new_bake_data = MEM_calloc_arrayN<NodesModifierBake>(new_bake_ids.size(),
__func__);
for (const int i : new_bake_ids.index_range()) {
const int id = new_bake_ids[i];
NodesModifierBake *old_bake = old_bake_by_id.lookup_default(id, nullptr);
NodesModifierBake &new_bake = new_bake_data[i];
if (old_bake) {
new_bake = *old_bake;
/* The ownership of this data was moved to `new_bake`. */
old_bake->directory = nullptr;
old_bake->data_blocks = nullptr;
old_bake->data_blocks_num = 0;
old_bake->packed = nullptr;
}
else {
new_bake.id = id;
new_bake.frame_start = 1;
new_bake.frame_end = 100;
new_bake.bake_mode = NODES_MODIFIER_BAKE_MODE_STILL;
}
}
for (NodesModifierBake &old_bake : MutableSpan(nmd.bakes, nmd.bakes_num)) {
nodes_modifier_bake_destruct(&old_bake, true);
}
MEM_SAFE_FREE(nmd.bakes);
nmd.bakes = new_bake_data;
nmd.bakes_num = new_bake_ids.size();
remove_outdated_bake_caches(nmd);
}
static void update_panels_from_node_group(NodesModifierData &nmd)
{
Map<int, NodesModifierPanel *> old_panel_by_id;
for (NodesModifierPanel &panel : MutableSpan(nmd.panels, nmd.panels_num)) {
old_panel_by_id.add(panel.id, &panel);
}
Vector<const bNodeTreeInterfacePanel *> interface_panels;
if (nmd.node_group) {
nmd.node_group->ensure_interface_cache();
nmd.node_group->tree_interface.foreach_item([&](const bNodeTreeInterfaceItem &item) {
if (item.item_type != NODE_INTERFACE_PANEL) {
return true;
}
interface_panels.append(reinterpret_cast<const bNodeTreeInterfacePanel *>(&item));
return true;
});
}
NodesModifierPanel *new_panels = MEM_calloc_arrayN<NodesModifierPanel>(interface_panels.size(),
__func__);
for (const int i : interface_panels.index_range()) {
const bNodeTreeInterfacePanel &interface_panel = *interface_panels[i];
const int id = interface_panel.identifier;
NodesModifierPanel *old_panel = old_panel_by_id.lookup_default(id, nullptr);
NodesModifierPanel &new_panel = new_panels[i];
if (old_panel) {
new_panel = *old_panel;
}
else {
new_panel.id = id;
const bool default_closed = interface_panel.flag & NODE_INTERFACE_PANEL_DEFAULT_CLOSED;
SET_FLAG_FROM_TEST(new_panel.flag, !default_closed, NODES_MODIFIER_PANEL_OPEN);
}
}
MEM_SAFE_FREE(nmd.panels);
nmd.panels = new_panels;
nmd.panels_num = interface_panels.size();
}
} // namespace blender
void MOD_nodes_update_interface(Object *object, NodesModifierData *nmd)
{
using namespace blender;
update_id_properties_from_node_group(nmd);
update_bakes_from_node_group(*nmd);
update_panels_from_node_group(*nmd);
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
}
NodesModifierBake *NodesModifierData::find_bake(const int id)
{
return const_cast<NodesModifierBake *>(std::as_const(*this).find_bake(id));
}
const NodesModifierBake *NodesModifierData::find_bake(const int id) const
{
for (const NodesModifierBake &bake : blender::Span{this->bakes, this->bakes_num}) {
if (bake.id == id) {
return &bake;
}
}
return nullptr;
}
namespace blender {
/**
* Setup side effects nodes so that the given node in the given compute context will be executed.
* To make sure that it is executed, all parent group nodes and zones have to be set to have side
* effects as well.
*/
static void try_add_side_effect_node(const ModifierEvalContext &ctx,
const ComputeContext &final_compute_context,
const int final_node_id,
const NodesModifierData &nmd,
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
{
if (nmd.node_group == nullptr) {
return;
}
Vector<const ComputeContext *> compute_context_vec;
for (const ComputeContext *c = &final_compute_context; c; c = c->parent()) {
compute_context_vec.append(c);
}
std::reverse(compute_context_vec.begin(), compute_context_vec.end());
const auto *modifier_compute_context = dynamic_cast<const bke::ModifierComputeContext *>(
compute_context_vec[0]);
if (modifier_compute_context == nullptr) {
return;
}
if (modifier_compute_context->modifier_name() != nmd.modifier.name) {
return;
}
const bNodeTree *current_tree = nmd.node_group;
const bke::bNodeTreeZone *current_zone = nullptr;
/* Write side effect nodes to a new map and only if everything succeeds, move the nodes to the
* caller. This is easier than changing r_side_effect_nodes directly and then undoing changes in
* case of errors. */
nodes::GeoNodesSideEffectNodes local_side_effect_nodes;
for (const ComputeContext *compute_context_generic : compute_context_vec.as_span().drop_front(1))
{
const bke::bNodeTreeZones *current_zones = current_tree->zones();
if (current_zones == nullptr) {
return;
}
const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*current_tree);
if (lf_graph_info == nullptr) {
return;
}
const ComputeContextHash &parent_compute_context_hash =
compute_context_generic->parent()->hash();
if (const auto *compute_context = dynamic_cast<const bke::SimulationZoneComputeContext *>(
compute_context_generic))
{
const bke::bNodeTreeZone *simulation_zone = current_zones->get_zone_by_node(
compute_context->output_node_id());
if (simulation_zone == nullptr) {
return;
}
if (simulation_zone->parent_zone != current_zone) {
return;
}
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
simulation_zone, nullptr);
if (lf_zone_node == nullptr) {
return;
}
const lf::FunctionNode *lf_simulation_output_node =
lf_graph_info->mapping.possible_side_effect_node_map.lookup_default(
simulation_zone->output_node, nullptr);
if (lf_simulation_output_node == nullptr) {
return;
}
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
/* By making the simulation output node a side-effect-node, we can ensure that the simulation
* runs when it contains an active viewer. */
local_side_effect_nodes.nodes_by_context.add(compute_context_generic->hash(),
lf_simulation_output_node);
current_zone = simulation_zone;
}
else if (const auto *compute_context = dynamic_cast<const bke::RepeatZoneComputeContext *>(
compute_context_generic))
{
const bke::bNodeTreeZone *repeat_zone = current_zones->get_zone_by_node(
compute_context->output_node_id());
if (repeat_zone == nullptr) {
return;
}
if (repeat_zone->parent_zone != current_zone) {
return;
}
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
repeat_zone, nullptr);
if (lf_zone_node == nullptr) {
return;
}
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
local_side_effect_nodes.iterations_by_iteration_zone.add(
{parent_compute_context_hash, compute_context->output_node_id()},
compute_context->iteration());
current_zone = repeat_zone;
}
else if (const auto *compute_context =
dynamic_cast<const bke::ForeachGeometryElementZoneComputeContext *>(
compute_context_generic))
{
const bke::bNodeTreeZone *foreach_zone = current_zones->get_zone_by_node(
compute_context->output_node_id());
if (foreach_zone == nullptr) {
return;
}
if (foreach_zone->parent_zone != current_zone) {
return;
}
const lf::FunctionNode *lf_zone_node = lf_graph_info->mapping.zone_node_map.lookup_default(
foreach_zone, nullptr);
if (lf_zone_node == nullptr) {
return;
}
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_zone_node);
local_side_effect_nodes.iterations_by_iteration_zone.add(
{parent_compute_context_hash, compute_context->output_node_id()},
compute_context->index());
current_zone = foreach_zone;
}
else if (const auto *compute_context = dynamic_cast<const bke::GroupNodeComputeContext *>(
compute_context_generic))
{
const bNode *group_node = current_tree->node_by_id(compute_context->node_id());
if (group_node == nullptr) {
return;
}
if (group_node->id == nullptr) {
return;
}
if (group_node->is_muted()) {
return;
}
if (current_zone != current_zones->get_zone_by_node(group_node->identifier)) {
return;
}
const lf::FunctionNode *lf_group_node = lf_graph_info->mapping.group_node_map.lookup_default(
group_node, nullptr);
if (lf_group_node == nullptr) {
return;
}
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_group_node);
current_tree = reinterpret_cast<const bNodeTree *>(group_node->id);
current_zone = nullptr;
}
else if (const auto *compute_context =
dynamic_cast<const bke::EvaluateClosureComputeContext *>(compute_context_generic))
{
const bNode *evaluate_node = current_tree->node_by_id(compute_context->node_id());
if (!evaluate_node) {
return;
}
if (evaluate_node->is_muted()) {
return;
}
if (current_zone != current_zones->get_zone_by_node(evaluate_node->identifier)) {
return;
}
const std::optional<nodes::ClosureSourceLocation> &source_location =
compute_context->closure_source_location();
if (!source_location) {
return;
}
if (!source_location->tree->zones()) {
return;
}
const lf::FunctionNode *lf_evaluate_node =
lf_graph_info->mapping.possible_side_effect_node_map.lookup_default(evaluate_node,
nullptr);
if (!lf_evaluate_node) {
return;
}
/* The tree may sometimes be original and sometimes evaluated, depending on the source of the
* compute context. */
const bNodeTree *eval_closure_tree = DEG_is_evaluated(source_location->tree) ?
source_location->tree :
reinterpret_cast<const bNodeTree *>(
DEG_get_evaluated_id(
ctx.depsgraph, &source_location->tree->id));
const bNode *closure_output_node = eval_closure_tree->node_by_id(
source_location->closure_output_node_id);
if (!closure_output_node) {
return;
}
local_side_effect_nodes.nodes_by_context.add(parent_compute_context_hash, lf_evaluate_node);
current_tree = eval_closure_tree;
current_zone = eval_closure_tree->zones()->get_zone_by_node(closure_output_node->identifier);
}
else {
return;
}
}
const bNode *final_node = current_tree->node_by_id(final_node_id);
if (final_node == nullptr) {
return;
}
const auto *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*current_tree);
if (lf_graph_info == nullptr) {
return;
}
const bke::bNodeTreeZones *tree_zones = current_tree->zones();
if (tree_zones == nullptr) {
return;
}
if (tree_zones->get_zone_by_node(final_node_id) != current_zone) {
return;
}
const lf::FunctionNode *lf_node =
lf_graph_info->mapping.possible_side_effect_node_map.lookup_default(final_node, nullptr);
if (lf_node == nullptr) {
return;
}
local_side_effect_nodes.nodes_by_context.add(final_compute_context.hash(), lf_node);
/* Successfully found all side effect nodes for the viewer path. */
for (const auto item : local_side_effect_nodes.nodes_by_context.items()) {
r_side_effect_nodes.nodes_by_context.add_multiple(item.key, item.value);
}
for (const auto item : local_side_effect_nodes.iterations_by_iteration_zone.items()) {
r_side_effect_nodes.iterations_by_iteration_zone.add_multiple(item.key, item.value);
}
}
static void find_side_effect_nodes_for_viewer_path(
const ViewerPath &viewer_path,
const NodesModifierData &nmd,
const ModifierEvalContext &ctx,
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
{
const std::optional<ed::viewer_path::ViewerPathForGeometryNodesViewer> parsed_path =
ed::viewer_path::parse_geometry_nodes_viewer(viewer_path);
if (!parsed_path.has_value()) {
return;
}
if (parsed_path->object != DEG_get_original(ctx.object)) {
return;
}
if (parsed_path->modifier_name != nmd.modifier.name) {
return;
}
bke::ComputeContextCache compute_context_cache;
const ComputeContext *current = &compute_context_cache.for_modifier(nullptr, nmd);
for (const ViewerPathElem *elem : parsed_path->node_path) {
current = ed::viewer_path::compute_context_for_viewer_path_elem(
*elem, compute_context_cache, current);
if (!current) {
return;
}
}
try_add_side_effect_node(ctx, *current, parsed_path->viewer_node_id, nmd, r_side_effect_nodes);
}
static void find_side_effect_nodes_for_nested_node(
const ModifierEvalContext &ctx,
const NodesModifierData &nmd,
const int root_nested_node_id,
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
{
bke::ComputeContextCache compute_context_cache;
const ComputeContext *compute_context = &compute_context_cache.for_modifier(nullptr, nmd);
int nested_node_id = root_nested_node_id;
const bNodeTree *tree = nmd.node_group;
while (true) {
const bNestedNodeRef *ref = tree->find_nested_node_ref(nested_node_id);
if (!ref) {
return;
}
const bNode *node = tree->node_by_id(ref->path.node_id);
if (!node) {
return;
}
const bke::bNodeTreeZones *zones = tree->zones();
if (!zones) {
return;
}
if (zones->get_zone_by_node(node->identifier) != nullptr) {
/* Only top level nodes are allowed here. */
return;
}
if (node->is_group()) {
if (!node->id) {
return;
}
compute_context = &compute_context_cache.for_group_node(compute_context, *node, *tree);
tree = reinterpret_cast<const bNodeTree *>(node->id);
nested_node_id = ref->path.id_in_node;
}
else {
try_add_side_effect_node(ctx, *compute_context, ref->path.node_id, nmd, r_side_effect_nodes);
return;
}
}
}
/**
* This ensures that nodes that the user wants to bake are actually evaluated. Otherwise they might
* not be if they are not connected to the output.
*/
static void find_side_effect_nodes_for_baking(const NodesModifierData &nmd,
const ModifierEvalContext &ctx,
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes)
{
if (!nmd.runtime->cache) {
return;
}
if (!DEG_is_active(ctx.depsgraph)) {
/* Only the active depsgraph can bake. */
return;
}
bake::ModifierCache &modifier_cache = *nmd.runtime->cache;
for (const bNestedNodeRef &ref : nmd.node_group->nested_node_refs_span()) {
if (!modifier_cache.requested_bakes.contains(ref.id)) {
continue;
}
find_side_effect_nodes_for_nested_node(ctx, nmd, ref.id, r_side_effect_nodes);
}
}
static void find_side_effect_nodes_for_active_gizmos(
const NodesModifierData &nmd,
const ModifierEvalContext &ctx,
const wmWindowManager &wm,
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes,
Set<ComputeContextHash> &r_socket_log_contexts)
{
Object *object_orig = DEG_get_original(ctx.object);
const NodesModifierData &nmd_orig = *reinterpret_cast<const NodesModifierData *>(
BKE_modifier_get_original(ctx.object, const_cast<ModifierData *>(&nmd.modifier)));
bke::ComputeContextCache compute_context_cache;
nodes::gizmos::foreach_active_gizmo_in_modifier(
*object_orig,
nmd_orig,
wm,
compute_context_cache,
[&](const ComputeContext &compute_context,
const bNode &gizmo_node,
const bNodeSocket &gizmo_socket) {
try_add_side_effect_node(
ctx, compute_context, gizmo_node.identifier, nmd, r_side_effect_nodes);
r_socket_log_contexts.add(compute_context.hash());
nodes::gizmos::foreach_compute_context_on_gizmo_path(
compute_context, gizmo_node, gizmo_socket, [&](const ComputeContext &node_context) {
/* Make sure that all intermediate sockets are logged. This is necessary to be able
* to evaluate the nodes in reverse for the gizmo. */
r_socket_log_contexts.add(node_context.hash());
});
});
}
static void find_side_effect_nodes(const NodesModifierData &nmd,
const ModifierEvalContext &ctx,
nodes::GeoNodesSideEffectNodes &r_side_effect_nodes,
Set<ComputeContextHash> &r_socket_log_contexts)
{
Main *bmain = DEG_get_bmain(ctx.depsgraph);
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
if (wm == nullptr) {
return;
}
LISTBASE_FOREACH (const wmWindow *, window, &wm->windows) {
const bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
const WorkSpace *workspace = BKE_workspace_active_get(window->workspace_hook);
find_side_effect_nodes_for_viewer_path(workspace->viewer_path, nmd, ctx, r_side_effect_nodes);
LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {
const SpaceLink *sl = static_cast<SpaceLink *>(area->spacedata.first);
if (sl->spacetype == SPACE_SPREADSHEET) {
const SpaceSpreadsheet &sspreadsheet = *reinterpret_cast<const SpaceSpreadsheet *>(sl);
find_side_effect_nodes_for_viewer_path(
sspreadsheet.viewer_path, nmd, ctx, r_side_effect_nodes);
}
if (sl->spacetype == SPACE_VIEW3D) {
const View3D &v3d = *reinterpret_cast<const View3D *>(sl);
find_side_effect_nodes_for_viewer_path(v3d.viewer_path, nmd, ctx, r_side_effect_nodes);
}
}
}
find_side_effect_nodes_for_baking(nmd, ctx, r_side_effect_nodes);
find_side_effect_nodes_for_active_gizmos(
nmd, ctx, *wm, r_side_effect_nodes, r_socket_log_contexts);
}
static void find_socket_log_contexts(const NodesModifierData &nmd,
const ModifierEvalContext &ctx,
Set<ComputeContextHash> &r_socket_log_contexts)
{
Main *bmain = DEG_get_bmain(ctx.depsgraph);
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
if (wm == nullptr) {
return;
}
bke::ComputeContextCache compute_context_cache;
LISTBASE_FOREACH (const wmWindow *, window, &wm->windows) {
const bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
LISTBASE_FOREACH (const ScrArea *, area, &screen->areabase) {
const SpaceLink *sl = static_cast<SpaceLink *>(area->spacedata.first);
if (sl->spacetype == SPACE_NODE) {
const SpaceNode &snode = *reinterpret_cast<const SpaceNode *>(sl);
if (snode.edittree == nullptr || snode.edittree->type != NTREE_GEOMETRY) {
continue;
}
if (!ed::space_node::node_editor_is_for_geometry_nodes_modifier(snode, *ctx.object, nmd)) {
continue;
}
const Map<const bke::bNodeTreeZone *, ComputeContextHash> hash_by_zone =
geo_log::GeoModifierLog::get_context_hash_by_zone_for_node_editor(
snode, compute_context_cache);
for (const ComputeContextHash &hash : hash_by_zone.values()) {
r_socket_log_contexts.add(hash);
}
}
}
}
}
/**
* \note This could be done in #initialize_group_input, though that would require adding the
* the object as a parameter, so it's likely better to this check as a separate step.
*/
static void check_property_socket_sync(const Object *ob,
const nodes::PropertiesVectorSet &properties,
ModifierData *md)
{
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
int geometry_socket_count = 0;
nmd->node_group->ensure_interface_cache();
for (const int i : nmd->node_group->interface_inputs().index_range()) {
const bNodeTreeInterfaceSocket *socket = nmd->node_group->interface_inputs()[i];
const bke::bNodeSocketType *typeinfo = socket->socket_typeinfo();
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
if (type == SOCK_GEOMETRY) {
geometry_socket_count++;
}
/* The first socket is the special geometry socket for the modifier object. */
if (i == 0 && type == SOCK_GEOMETRY) {
continue;
}
IDProperty *property = properties.lookup_key_default_as(socket->identifier, nullptr);
if (property == nullptr) {
if (!ELEM(type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_BUNDLE, SOCK_CLOSURE)) {
BKE_modifier_set_error(
ob, md, "Missing property for input socket \"%s\"", socket->name ? socket->name : "");
}
continue;
}
if (!nodes::id_property_type_matches_socket(*socket, *property)) {
BKE_modifier_set_error(ob,
md,
"Property type does not match input socket \"(%s)\"",
socket->name ? socket->name : "");
continue;
}
}
if (geometry_socket_count == 1) {
const bNodeTreeInterfaceSocket *first_socket = nmd->node_group->interface_inputs()[0];
const bke::bNodeSocketType *typeinfo = first_socket->socket_typeinfo();
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
if (type != SOCK_GEOMETRY) {
BKE_modifier_set_error(ob, md, "Node group's geometry input must be the first");
}
}
}
class NodesModifierBakeDataBlockMap : public bake::BakeDataBlockMap {
/** Protects access to `new_mappings` which may be added to from multiple threads. */
Mutex mutex_;
public:
Map<bake::BakeDataBlockID, ID *> old_mappings;
Map<bake::BakeDataBlockID, ID *> new_mappings;
ID *lookup_or_remember_missing(const bake::BakeDataBlockID &key) override
{
if (ID *id = this->old_mappings.lookup_default(key, nullptr)) {
return id;
}
if (this->old_mappings.contains(key)) {
/* Don't allow overwriting old mappings. */
return nullptr;
}
std::lock_guard lock{mutex_};
return this->new_mappings.lookup_or_add(key, nullptr);
}
void try_add(ID &id) override
{
bake::BakeDataBlockID key{id};
if (this->old_mappings.contains(key)) {
return;
}
std::lock_guard lock{mutex_};
this->new_mappings.add_overwrite(std::move(key), &id);
}
private:
ID *lookup_in_map(Map<bake::BakeDataBlockID, ID *> &map,
const bake::BakeDataBlockID &key,
const std::optional<ID_Type> &type)
{
ID *id = map.lookup_default(key, nullptr);
if (!id) {
return nullptr;
}
if (type && GS(id->name) != *type) {
return nullptr;
}
return id;
}
};
namespace sim_input = nodes::sim_input;
namespace sim_output = nodes::sim_output;
struct BakeFrameIndices {
std::optional<int> prev;
std::optional<int> current;
std::optional<int> next;
};
static BakeFrameIndices get_bake_frame_indices(
const Span<std::unique_ptr<bake::FrameCache>> frame_caches, const SubFrame frame)
{
BakeFrameIndices frame_indices;
if (!frame_caches.is_empty()) {
const int first_future_frame_index = binary_search::first_if(
frame_caches,
[&](const std::unique_ptr<bake::FrameCache> &value) { return value->frame > frame; });
frame_indices.next = (first_future_frame_index == frame_caches.size()) ?
std::nullopt :
std::optional<int>(first_future_frame_index);
if (first_future_frame_index > 0) {
const int index = first_future_frame_index - 1;
if (frame_caches[index]->frame < frame) {
frame_indices.prev = index;
}
else {
BLI_assert(frame_caches[index]->frame == frame);
frame_indices.current = index;
if (index > 0) {
frame_indices.prev = index - 1;
}
}
}
}
return frame_indices;
}
static void ensure_bake_loaded(bake::NodeBakeCache &bake_cache, bake::FrameCache &frame_cache)
{
if (!frame_cache.state.items_by_id.is_empty()) {
return;
}
if (!frame_cache.meta_data_source.has_value()) {
return;
}
if (bake_cache.memory_blob_reader) {
if (const auto *meta_buffer = std::get_if<Span<std::byte>>(&*frame_cache.meta_data_source)) {
const std::string meta_str{reinterpret_cast<const char *>(meta_buffer->data()),
size_t(meta_buffer->size())};
std::istringstream meta_stream{meta_str};
std::optional<bake::BakeState> bake_state = bake::deserialize_bake(
meta_stream, *bake_cache.memory_blob_reader, *bake_cache.blob_sharing);
if (!bake_state.has_value()) {
return;
}
frame_cache.state = std::move(*bake_state);
return;
}
}
if (!bake_cache.blobs_dir) {
return;
}
const auto *meta_path = std::get_if<std::string>(&*frame_cache.meta_data_source);
if (!meta_path) {
return;
}
bake::DiskBlobReader blob_reader{*bake_cache.blobs_dir};
fstream meta_file{*meta_path};
std::optional<bake::BakeState> bake_state = bake::deserialize_bake(
meta_file, blob_reader, *bake_cache.blob_sharing);
if (!bake_state.has_value()) {
return;
}
frame_cache.state = std::move(*bake_state);
}
static bool try_find_baked_data(const NodesModifierBake &bake,
bake::NodeBakeCache &bake_cache,
const Main &bmain,
const Object &object,
const NodesModifierData &nmd,
const int id)
{
if (bake.packed) {
if (bake.packed->meta_files_num == 0) {
return false;
}
bake_cache.reset();
Map<SubFrame, const NodesModifierBakeFile *> file_by_frame;
for (const NodesModifierBakeFile &meta_file :
Span{bake.packed->meta_files, bake.packed->meta_files_num})
{
const std::optional<SubFrame> frame = bake::file_name_to_frame(meta_file.name);
if (!frame) {
return false;
}
if (!file_by_frame.add(*frame, &meta_file)) {
/* Can only have on file per (sub)frame. */
return false;
}
}
/* Make sure frames processed in the right order. */
Vector<SubFrame> frames;
frames.extend(file_by_frame.keys().begin(), file_by_frame.keys().end());
for (const SubFrame &frame : frames) {
const NodesModifierBakeFile &meta_file = *file_by_frame.lookup(frame);
auto frame_cache = std::make_unique<bake::FrameCache>();
frame_cache->frame = frame;
frame_cache->meta_data_source = meta_file.data();
bake_cache.frames.append(std::move(frame_cache));
}
bake_cache.memory_blob_reader = std::make_unique<bake::MemoryBlobReader>();
for (const NodesModifierBakeFile &blob_file :
Span{bake.packed->blob_files, bake.packed->blob_files_num})
{
bake_cache.memory_blob_reader->add(blob_file.name, blob_file.data());
}
bake_cache.blob_sharing = std::make_unique<bake::BlobReadSharing>();
return true;
}
std::optional<bake::BakePath> bake_path = bake::get_node_bake_path(bmain, object, nmd, id);
if (!bake_path) {
return false;
}
Vector<bake::MetaFile> meta_files = bake::find_sorted_meta_files(bake_path->meta_dir);
if (meta_files.is_empty()) {
return false;
}
bake_cache.reset();
for (const bake::MetaFile &meta_file : meta_files) {
auto frame_cache = std::make_unique<bake::FrameCache>();
frame_cache->frame = meta_file.frame;
frame_cache->meta_data_source = meta_file.path;
bake_cache.frames.append(std::move(frame_cache));
}
bake_cache.blobs_dir = bake_path->blobs_dir;
bake_cache.blob_sharing = std::make_unique<bake::BlobReadSharing>();
return true;
}
class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams {
private:
static constexpr float max_delta_frames = 1.0f;
const NodesModifierData &nmd_;
const ModifierEvalContext &ctx_;
const Main *bmain_;
const Scene *scene_;
SubFrame current_frame_;
bool use_frame_cache_;
bool depsgraph_is_active_;
bake::ModifierCache *modifier_cache_;
float fps_;
bool has_invalid_simulation_ = false;
public:
struct DataPerZone {
nodes::SimulationZoneBehavior behavior;
NodesModifierBakeDataBlockMap data_block_map;
};
mutable Map<int, std::unique_ptr<DataPerZone>> data_by_zone_id;
NodesModifierSimulationParams(NodesModifierData &nmd, const ModifierEvalContext &ctx)
: nmd_(nmd), ctx_(ctx)
{
const Depsgraph *depsgraph = ctx_.depsgraph;
bmain_ = DEG_get_bmain(depsgraph);
current_frame_ = DEG_get_ctime(depsgraph);
const Scene *scene = DEG_get_input_scene(depsgraph);
scene_ = scene;
use_frame_cache_ = ctx_.object->flag & OB_FLAG_USE_SIMULATION_CACHE;
depsgraph_is_active_ = DEG_is_active(depsgraph);
modifier_cache_ = nmd.runtime->cache.get();
fps_ = FPS;
if (!modifier_cache_) {
return;
}
std::lock_guard lock{modifier_cache_->mutex};
if (depsgraph_is_active_) {
/* Invalidate data on user edits. */
if (nmd.modifier.flag & eModifierFlag_UserModified) {
for (std::unique_ptr<bake::SimulationNodeCache> &node_cache :
modifier_cache_->simulation_cache_by_id.values())
{
if (node_cache->cache_status != bake::CacheStatus::Baked) {
node_cache->cache_status = bake::CacheStatus::Invalid;
if (!node_cache->bake.frames.is_empty()) {
if (node_cache->bake.frames.last()->frame == current_frame_) {
/* Remove the last (which is the current) cached frame so that it is simulated
* again. */
node_cache->bake.frames.pop_last();
}
}
}
}
}
this->reset_invalid_node_bakes();
}
for (const std::unique_ptr<bake::SimulationNodeCache> &node_cache_ptr :
modifier_cache_->simulation_cache_by_id.values())
{
const bake::SimulationNodeCache &node_cache = *node_cache_ptr;
if (node_cache.cache_status == bake::CacheStatus::Invalid) {
has_invalid_simulation_ = true;
break;
}
}
}
void reset_invalid_node_bakes()
{
for (auto item : modifier_cache_->simulation_cache_by_id.items()) {
const int id = item.key;
bake::SimulationNodeCache &node_cache = *item.value;
if (node_cache.cache_status != bake::CacheStatus::Invalid) {
continue;
}
const std::optional<IndexRange> sim_frame_range = bake::get_node_bake_frame_range(
*scene_, *ctx_.object, nmd_, id);
if (!sim_frame_range.has_value()) {
continue;
}
const SubFrame start_frame{int(sim_frame_range->start())};
if (current_frame_ <= start_frame) {
node_cache.reset();
}
if (!node_cache.bake.frames.is_empty() &&
current_frame_ < node_cache.bake.frames.first()->frame)
{
node_cache.reset();
}
}
}
nodes::SimulationZoneBehavior *get(const int zone_id) const override
{
if (!modifier_cache_) {
return nullptr;
}
std::lock_guard lock{modifier_cache_->mutex};
return &this->data_by_zone_id
.lookup_or_add_cb(zone_id,
[&]() {
auto data = std::make_unique<DataPerZone>();
data->behavior.data_block_map = &data->data_block_map;
this->init_simulation_info(
zone_id, data->behavior, data->data_block_map);
return data;
})
->behavior;
}
void init_simulation_info(const int zone_id,
nodes::SimulationZoneBehavior &zone_behavior,
NodesModifierBakeDataBlockMap &data_block_map) const
{
bake::SimulationNodeCache &node_cache =
*modifier_cache_->simulation_cache_by_id.lookup_or_add_cb(
zone_id, []() { return std::make_unique<bake::SimulationNodeCache>(); });
const NodesModifierBake &bake = *nmd_.find_bake(zone_id);
const IndexRange sim_frame_range = *bake::get_node_bake_frame_range(
*scene_, *ctx_.object, nmd_, zone_id);
const SubFrame sim_start_frame{int(sim_frame_range.first())};
const SubFrame sim_end_frame{int(sim_frame_range.last())};
if (!modifier_cache_->requested_bakes.contains(zone_id)) {
/* Try load baked data. */
if (!node_cache.bake.failed_finding_bake) {
if (node_cache.cache_status != bake::CacheStatus::Baked) {
if (try_find_baked_data(bake, node_cache.bake, *bmain_, *ctx_.object, nmd_, zone_id)) {
node_cache.cache_status = bake::CacheStatus::Baked;
}
else {
node_cache.bake.failed_finding_bake = true;
}
}
}
}
/* If there are no baked frames, we don't need keep track of the data-blocks. */
if (!node_cache.bake.frames.is_empty() || node_cache.prev_cache.has_value()) {
for (const NodesModifierDataBlock &data_block : Span{bake.data_blocks, bake.data_blocks_num})
{
data_block_map.old_mappings.add(data_block, data_block.id);
}
}
const BakeFrameIndices frame_indices = get_bake_frame_indices(node_cache.bake.frames,
current_frame_);
if (node_cache.cache_status == bake::CacheStatus::Baked) {
this->read_from_cache(frame_indices, node_cache, zone_behavior);
return;
}
if (use_frame_cache_) {
/* If the depsgraph is active, we allow creating new simulation states. Otherwise, the access
* is read-only. */
if (depsgraph_is_active_) {
if (node_cache.bake.frames.is_empty()) {
if (current_frame_ < sim_start_frame || current_frame_ > sim_end_frame) {
/* Outside of simulation frame range, so ignore the simulation if there is no cache. */
this->input_pass_through(zone_behavior);
this->output_pass_through(zone_behavior);
return;
}
/* Initialize the simulation. */
if (current_frame_ > sim_start_frame || has_invalid_simulation_) {
node_cache.cache_status = bake::CacheStatus::Invalid;
}
this->input_pass_through(zone_behavior);
this->output_store_frame_cache(node_cache, zone_behavior);
return;
}
if (frame_indices.prev && !frame_indices.current && !frame_indices.next &&
current_frame_ <= sim_end_frame)
{
/* Read the previous frame's data and store the newly computed simulation state. */
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
const bake::FrameCache &prev_frame_cache = *node_cache.bake.frames[*frame_indices.prev];
const float real_delta_frames = float(current_frame_) - float(prev_frame_cache.frame);
if (real_delta_frames != 1) {
node_cache.cache_status = bake::CacheStatus::Invalid;
}
const float delta_frames = std::min(max_delta_frames, real_delta_frames);
output_copy_info.delta_time = delta_frames / fps_;
output_copy_info.state = prev_frame_cache.state;
this->output_store_frame_cache(node_cache, zone_behavior);
return;
}
}
this->read_from_cache(frame_indices, node_cache, zone_behavior);
return;
}
/* When there is no per-frame cache, check if there is a previous state. */
if (node_cache.prev_cache) {
if (node_cache.prev_cache->frame < current_frame_) {
/* Do a simulation step. */
const float delta_frames = std::min(
max_delta_frames, float(current_frame_) - float(node_cache.prev_cache->frame));
auto &output_move_info = zone_behavior.input.emplace<sim_input::OutputMove>();
output_move_info.delta_time = delta_frames / fps_;
output_move_info.state = std::move(node_cache.prev_cache->state);
this->store_as_prev_items(node_cache, zone_behavior);
return;
}
if (node_cache.prev_cache->frame == current_frame_) {
/* Just read from the previous state if the frame has not changed. */
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
output_copy_info.delta_time = 0.0f;
output_copy_info.state = node_cache.prev_cache->state;
auto &read_single_info = zone_behavior.output.emplace<sim_output::ReadSingle>();
read_single_info.state = node_cache.prev_cache->state;
return;
}
if (!depsgraph_is_active_) {
/* There is no previous state, and it's not possible to initialize the simulation because
* the depsgraph is not active. */
zone_behavior.input.emplace<sim_input::PassThrough>();
zone_behavior.output.emplace<sim_output::PassThrough>();
return;
}
/* Reset the simulation when the scene time moved backwards. */
node_cache.prev_cache.reset();
}
zone_behavior.input.emplace<sim_input::PassThrough>();
if (depsgraph_is_active_) {
/* Initialize the simulation. */
this->store_as_prev_items(node_cache, zone_behavior);
}
else {
zone_behavior.output.emplace<sim_output::PassThrough>();
}
}
void input_pass_through(nodes::SimulationZoneBehavior &zone_behavior) const
{
zone_behavior.input.emplace<sim_input::PassThrough>();
}
void output_pass_through(nodes::SimulationZoneBehavior &zone_behavior) const
{
zone_behavior.output.emplace<sim_output::PassThrough>();
}
void output_store_frame_cache(bake::SimulationNodeCache &node_cache,
nodes::SimulationZoneBehavior &zone_behavior) const
{
auto &store_new_state_info = zone_behavior.output.emplace<sim_output::StoreNewState>();
store_new_state_info.store_fn = [simulation_cache = modifier_cache_,
node_cache = &node_cache,
current_frame = current_frame_](bke::bake::BakeState state) {
std::lock_guard lock{simulation_cache->mutex};
auto frame_cache = std::make_unique<bake::FrameCache>();
frame_cache->frame = current_frame;
frame_cache->state = std::move(state);
node_cache->bake.frames.append(std::move(frame_cache));
};
}
void store_as_prev_items(bake::SimulationNodeCache &node_cache,
nodes::SimulationZoneBehavior &zone_behavior) const
{
auto &store_new_state_info = zone_behavior.output.emplace<sim_output::StoreNewState>();
store_new_state_info.store_fn = [simulation_cache = modifier_cache_,
node_cache = &node_cache,
current_frame = current_frame_](bke::bake::BakeState state) {
std::lock_guard lock{simulation_cache->mutex};
if (!node_cache->prev_cache) {
node_cache->prev_cache.emplace();
}
node_cache->prev_cache->state = std::move(state);
node_cache->prev_cache->frame = current_frame;
};
}
void read_from_cache(const BakeFrameIndices &frame_indices,
bake::SimulationNodeCache &node_cache,
nodes::SimulationZoneBehavior &zone_behavior) const
{
if (frame_indices.prev) {
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
bake::FrameCache &frame_cache = *node_cache.bake.frames[*frame_indices.prev];
const float delta_frames = std::min(max_delta_frames,
float(current_frame_) - float(frame_cache.frame));
output_copy_info.delta_time = delta_frames / fps_;
output_copy_info.state = frame_cache.state;
}
else {
zone_behavior.input.emplace<sim_input::PassThrough>();
}
if (frame_indices.current) {
this->read_single(*frame_indices.current, node_cache, zone_behavior);
}
else if (frame_indices.next) {
if (frame_indices.prev) {
this->read_interpolated(
*frame_indices.prev, *frame_indices.next, node_cache, zone_behavior);
}
else {
this->output_pass_through(zone_behavior);
}
}
else if (frame_indices.prev) {
this->read_single(*frame_indices.prev, node_cache, zone_behavior);
}
else {
this->output_pass_through(zone_behavior);
}
}
void read_single(const int frame_index,
bake::SimulationNodeCache &node_cache,
nodes::SimulationZoneBehavior &zone_behavior) const
{
bake::FrameCache &frame_cache = *node_cache.bake.frames[frame_index];
ensure_bake_loaded(node_cache.bake, frame_cache);
auto &read_single_info = zone_behavior.output.emplace<sim_output::ReadSingle>();
read_single_info.state = frame_cache.state;
}
void read_interpolated(const int prev_frame_index,
const int next_frame_index,
bake::SimulationNodeCache &node_cache,
nodes::SimulationZoneBehavior &zone_behavior) const
{
bake::FrameCache &prev_frame_cache = *node_cache.bake.frames[prev_frame_index];
bake::FrameCache &next_frame_cache = *node_cache.bake.frames[next_frame_index];
ensure_bake_loaded(node_cache.bake, prev_frame_cache);
ensure_bake_loaded(node_cache.bake, next_frame_cache);
auto &read_interpolated_info = zone_behavior.output.emplace<sim_output::ReadInterpolated>();
read_interpolated_info.mix_factor = (float(current_frame_) - float(prev_frame_cache.frame)) /
(float(next_frame_cache.frame) -
float(prev_frame_cache.frame));
read_interpolated_info.prev_state = prev_frame_cache.state;
read_interpolated_info.next_state = next_frame_cache.state;
}
};
class NodesModifierBakeParams : public nodes::GeoNodesBakeParams {
private:
const NodesModifierData &nmd_;
const ModifierEvalContext &ctx_;
Main *bmain_;
SubFrame current_frame_;
bake::ModifierCache *modifier_cache_;
bool depsgraph_is_active_;
public:
struct DataPerNode {
nodes::BakeNodeBehavior behavior;
NodesModifierBakeDataBlockMap data_block_map;
};
mutable Map<int, std::unique_ptr<DataPerNode>> data_by_node_id;
NodesModifierBakeParams(NodesModifierData &nmd, const ModifierEvalContext &ctx)
: nmd_(nmd), ctx_(ctx)
{
const Depsgraph *depsgraph = ctx_.depsgraph;
current_frame_ = DEG_get_ctime(depsgraph);
modifier_cache_ = nmd.runtime->cache.get();
depsgraph_is_active_ = DEG_is_active(depsgraph);
bmain_ = DEG_get_bmain(depsgraph);
}
nodes::BakeNodeBehavior *get(const int id) const override
{
if (!modifier_cache_) {
return nullptr;
}
std::lock_guard lock{modifier_cache_->mutex};
return &this->data_by_node_id
.lookup_or_add_cb(id,
[&]() {
auto data = std::make_unique<DataPerNode>();
data->behavior.data_block_map = &data->data_block_map;
this->init_bake_behavior(
id, data->behavior, data->data_block_map);
return data;
})
->behavior;
return nullptr;
}
private:
void init_bake_behavior(const int id,
nodes::BakeNodeBehavior &behavior,
NodesModifierBakeDataBlockMap &data_block_map) const
{
bake::BakeNodeCache &node_cache = *modifier_cache_->bake_cache_by_id.lookup_or_add_cb(
id, []() { return std::make_unique<bake::BakeNodeCache>(); });
const NodesModifierBake &bake = *nmd_.find_bake(id);
for (const NodesModifierDataBlock &data_block : Span{bake.data_blocks, bake.data_blocks_num}) {
data_block_map.old_mappings.add(data_block, data_block.id);
}
if (depsgraph_is_active_) {
if (modifier_cache_->requested_bakes.contains(id)) {
/* This node is baked during the current evaluation. */
auto &store_info = behavior.behavior.emplace<sim_output::StoreNewState>();
store_info.store_fn = [modifier_cache = modifier_cache_,
node_cache = &node_cache,
current_frame = current_frame_](bake::BakeState state) {
std::lock_guard lock{modifier_cache->mutex};
auto frame_cache = std::make_unique<bake::FrameCache>();
frame_cache->frame = current_frame;
frame_cache->state = std::move(state);
auto &frames = node_cache->bake.frames;
const int insert_index = binary_search::first_if(
frames, [&](const std::unique_ptr<bake::FrameCache> &frame_cache) {
return frame_cache->frame > current_frame;
});
frames.insert(insert_index, std::move(frame_cache));
};
return;
}
}
/* Try load baked data. */
if (node_cache.bake.frames.is_empty()) {
if (!node_cache.bake.failed_finding_bake) {
if (!try_find_baked_data(bake, node_cache.bake, *bmain_, *ctx_.object, nmd_, id)) {
node_cache.bake.failed_finding_bake = true;
}
}
}
if (node_cache.bake.frames.is_empty()) {
behavior.behavior.emplace<sim_output::PassThrough>();
return;
}
const BakeFrameIndices frame_indices = get_bake_frame_indices(node_cache.bake.frames,
current_frame_);
if (frame_indices.current) {
this->read_single(*frame_indices.current, node_cache, behavior);
return;
}
if (frame_indices.prev && frame_indices.next) {
this->read_interpolated(*frame_indices.prev, *frame_indices.next, node_cache, behavior);
return;
}
if (frame_indices.prev) {
this->read_single(*frame_indices.prev, node_cache, behavior);
return;
}
if (frame_indices.next) {
this->read_single(*frame_indices.next, node_cache, behavior);
return;
}
BLI_assert_unreachable();
}
void read_single(const int frame_index,
bake::BakeNodeCache &node_cache,
nodes::BakeNodeBehavior &behavior) const
{
bake::FrameCache &frame_cache = *node_cache.bake.frames[frame_index];
ensure_bake_loaded(node_cache.bake, frame_cache);
if (this->check_read_error(frame_cache, behavior)) {
return;
}
auto &read_single_info = behavior.behavior.emplace<sim_output::ReadSingle>();
read_single_info.state = frame_cache.state;
}
void read_interpolated(const int prev_frame_index,
const int next_frame_index,
bake::BakeNodeCache &node_cache,
nodes::BakeNodeBehavior &behavior) const
{
bake::FrameCache &prev_frame_cache = *node_cache.bake.frames[prev_frame_index];
bake::FrameCache &next_frame_cache = *node_cache.bake.frames[next_frame_index];
ensure_bake_loaded(node_cache.bake, prev_frame_cache);
ensure_bake_loaded(node_cache.bake, next_frame_cache);
if (this->check_read_error(prev_frame_cache, behavior) ||
this->check_read_error(next_frame_cache, behavior))
{
return;
}
auto &read_interpolated_info = behavior.behavior.emplace<sim_output::ReadInterpolated>();
read_interpolated_info.mix_factor = (float(current_frame_) - float(prev_frame_cache.frame)) /
(float(next_frame_cache.frame) -
float(prev_frame_cache.frame));
read_interpolated_info.prev_state = prev_frame_cache.state;
read_interpolated_info.next_state = next_frame_cache.state;
}
[[nodiscard]] bool check_read_error(const bake::FrameCache &frame_cache,
nodes::BakeNodeBehavior &behavior) const
{
if (frame_cache.meta_data_source && frame_cache.state.items_by_id.is_empty()) {
auto &read_error_info = behavior.behavior.emplace<sim_output::ReadError>();
read_error_info.message = RPT_("Cannot load the baked data");
return true;
}
return false;
}
};
static void add_missing_data_block_mappings(
NodesModifierBake &bake,
const Span<bake::BakeDataBlockID> missing,
FunctionRef<ID *(const bake::BakeDataBlockID &)> get_data_block)
{
const int old_num = bake.data_blocks_num;
const int new_num = old_num + missing.size();
bake.data_blocks = reinterpret_cast<NodesModifierDataBlock *>(
MEM_recallocN(bake.data_blocks, sizeof(NodesModifierDataBlock) * new_num));
for (const int i : missing.index_range()) {
NodesModifierDataBlock &data_block = bake.data_blocks[old_num + i];
const blender::bke::bake::BakeDataBlockID &key = missing[i];
data_block.id_name = BLI_strdup(key.id_name.c_str());
if (!key.lib_name.empty()) {
data_block.lib_name = BLI_strdup(key.lib_name.c_str());
}
data_block.id_type = int(key.type);
ID *id = get_data_block(key);
if (id) {
data_block.id = id;
}
}
bake.data_blocks_num = new_num;
}
void nodes_modifier_data_block_destruct(NodesModifierDataBlock *data_block, const bool do_id_user)
{
MEM_SAFE_FREE(data_block->id_name);
MEM_SAFE_FREE(data_block->lib_name);
if (do_id_user) {
id_us_min(data_block->id);
}
}
/**
* During evaluation we might have baked geometry that contains references to other data-blocks
* (such as materials). We need to make sure that those data-blocks stay dependencies of the
* modifier. Otherwise, the data-block references might not work when the baked data is loaded
* again. Therefor, the dependencies are written back to the original modifier.
*/
static void add_data_block_items_writeback(const ModifierEvalContext &ctx,
NodesModifierData &nmd_eval,
NodesModifierData &nmd_orig,
NodesModifierSimulationParams &simulation_params,
NodesModifierBakeParams &bake_params)
{
Depsgraph *depsgraph = ctx.depsgraph;
Main *bmain = DEG_get_bmain(depsgraph);
struct DataPerBake {
bool reset_first = false;
Map<bake::BakeDataBlockID, ID *> new_mappings;
};
Map<int, DataPerBake> writeback_data;
for (auto item : simulation_params.data_by_zone_id.items()) {
DataPerBake data;
NodesModifierBake &bake = *nmd_eval.find_bake(item.key);
if (item.value->data_block_map.old_mappings.size() < bake.data_blocks_num) {
data.reset_first = true;
}
if (bake::SimulationNodeCache *node_cache = nmd_eval.runtime->cache->get_simulation_node_cache(
item.key))
{
/* Only writeback if the bake node has actually baked anything. */
if (!node_cache->bake.frames.is_empty() || node_cache->prev_cache.has_value()) {
data.new_mappings = std::move(item.value->data_block_map.new_mappings);
}
}
if (data.reset_first || !data.new_mappings.is_empty()) {
writeback_data.add(item.key, std::move(data));
}
}
for (auto item : bake_params.data_by_node_id.items()) {
if (bake::BakeNodeCache *node_cache = nmd_eval.runtime->cache->get_bake_node_cache(item.key)) {
/* Only writeback if the bake node has actually baked anything. */
if (!node_cache->bake.frames.is_empty()) {
DataPerBake data;
data.new_mappings = std::move(item.value->data_block_map.new_mappings);
writeback_data.add(item.key, std::move(data));
}
}
}
if (writeback_data.is_empty()) {
/* Nothing to do. */
return;
}
deg::sync_writeback::add(
*depsgraph,
[object_eval = ctx.object,
bmain,
&nmd_orig,
&nmd_eval,
writeback_data = std::move(writeback_data)]() {
for (auto item : writeback_data.items()) {
const int bake_id = item.key;
DataPerBake data = item.value;
NodesModifierBake &bake_orig = *nmd_orig.find_bake(bake_id);
NodesModifierBake &bake_eval = *nmd_eval.find_bake(bake_id);
if (data.reset_first) {
/* Reset data-block list on original data. */
dna::array::clear<NodesModifierDataBlock>(&bake_orig.data_blocks,
&bake_orig.data_blocks_num,
&bake_orig.active_data_block,
[](NodesModifierDataBlock *data_block) {
nodes_modifier_data_block_destruct(
data_block, true);
});
/* Reset data-block list on evaluated data. */
dna::array::clear<NodesModifierDataBlock>(&bake_eval.data_blocks,
&bake_eval.data_blocks_num,
&bake_eval.active_data_block,
[](NodesModifierDataBlock *data_block) {
nodes_modifier_data_block_destruct(
data_block, false);
});
}
Vector<bake::BakeDataBlockID> sorted_new_mappings;
sorted_new_mappings.extend(data.new_mappings.keys().begin(),
data.new_mappings.keys().end());
bool needs_reevaluation = false;
/* Add new data block mappings to the original modifier. This may do a name lookup in
* bmain to find the data block if there is not faster way to get it. */
add_missing_data_block_mappings(
bake_orig, sorted_new_mappings, [&](const bake::BakeDataBlockID &key) -> ID * {
ID *id_orig = nullptr;
if (ID *id_eval = data.new_mappings.lookup_default(key, nullptr)) {
id_orig = DEG_get_original(id_eval);
}
else {
needs_reevaluation = true;
id_orig = BKE_libblock_find_name_and_library(
bmain, short(key.type), key.id_name.c_str(), key.lib_name.c_str());
}
if (id_orig) {
id_us_plus(id_orig);
}
return id_orig;
});
/* Add new data block mappings to the evaluated modifier. In most cases this makes it so
* the evaluated modifier is in the same state as if it were copied from the updated
* original again. The exception is when a missing data block was found that is not in
* the depsgraph currently. */
add_missing_data_block_mappings(
bake_eval, sorted_new_mappings, [&](const bake::BakeDataBlockID &key) -> ID * {
return data.new_mappings.lookup_default(key, nullptr);
});
if (needs_reevaluation) {
Object *object_orig = DEG_get_original(object_eval);
DEG_id_tag_update(&object_orig->id, ID_RECALC_GEOMETRY);
DEG_relations_tag_update(bmain);
}
}
});
}
static void modifyGeometry(ModifierData *md,
const ModifierEvalContext *ctx,
bke::GeometrySet &geometry_set)
{
using namespace blender;
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
if (nmd->node_group == nullptr) {
return;
}
NodesModifierData *nmd_orig = reinterpret_cast<NodesModifierData *>(
BKE_modifier_get_original(ctx->object, &nmd->modifier));
nodes::PropertiesVectorSet properties = nodes::build_properties_vector_set(
nmd->settings.properties);
const bNodeTree &tree = *nmd->node_group;
check_property_socket_sync(ctx->object, properties, md);
tree.ensure_topology_cache();
const bNode *output_node = tree.group_output_node();
if (output_node == nullptr) {
BKE_modifier_set_error(ctx->object, md, "Node group must have a group output node");
geometry_set.clear();
return;
}
Span<const bNodeSocket *> group_outputs = output_node->input_sockets().drop_back(1);
if (group_outputs.is_empty()) {
BKE_modifier_set_error(ctx->object, md, "Node group must have an output socket");
geometry_set.clear();
return;
}
const bNodeSocket *first_output_socket = group_outputs[0];
if (!STREQ(first_output_socket->idname, "NodeSocketGeometry")) {
BKE_modifier_set_error(ctx->object, md, "Node group's first output must be a geometry");
geometry_set.clear();
return;
}
const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info =
nodes::ensure_geometry_nodes_lazy_function_graph(tree);
if (lf_graph_info == nullptr) {
BKE_modifier_set_error(ctx->object, md, "Cannot evaluate node group");
geometry_set.clear();
return;
}
bool use_orig_index_verts = false;
bool use_orig_index_edges = false;
bool use_orig_index_faces = false;
if (const Mesh *mesh = geometry_set.get_mesh()) {
use_orig_index_verts = CustomData_has_layer(&mesh->vert_data, CD_ORIGINDEX);
use_orig_index_edges = CustomData_has_layer(&mesh->edge_data, CD_ORIGINDEX);
use_orig_index_faces = CustomData_has_layer(&mesh->face_data, CD_ORIGINDEX);
}
nodes::GeoNodesCallData call_data;
nodes::GeoNodesModifierData modifier_eval_data{};
modifier_eval_data.depsgraph = ctx->depsgraph;
modifier_eval_data.self_object = ctx->object;
auto eval_log = std::make_unique<geo_log::GeoModifierLog>();
call_data.modifier_data = &modifier_eval_data;
NodesModifierSimulationParams simulation_params(*nmd, *ctx);
call_data.simulation_params = &simulation_params;
NodesModifierBakeParams bake_params{*nmd, *ctx};
call_data.bake_params = &bake_params;
Set<ComputeContextHash> socket_log_contexts;
if (logging_enabled(ctx)) {
call_data.eval_log = eval_log.get();
find_socket_log_contexts(*nmd, *ctx, socket_log_contexts);
call_data.socket_log_contexts = &socket_log_contexts;
}
nodes::GeoNodesSideEffectNodes side_effect_nodes;
find_side_effect_nodes(*nmd, *ctx, side_effect_nodes, socket_log_contexts);
call_data.side_effect_nodes = &side_effect_nodes;
bke::ModifierComputeContext modifier_compute_context{nullptr, *nmd};
geometry_set = nodes::execute_geometry_nodes_on_geometry(
tree, properties, modifier_compute_context, call_data, std::move(geometry_set));
if (logging_enabled(ctx)) {
nmd_orig->runtime->eval_log = std::move(eval_log);
}
if (DEG_is_active(ctx->depsgraph) && !(ctx->flag & MOD_APPLY_TO_ORIGINAL)) {
add_data_block_items_writeback(*ctx, *nmd, *nmd_orig, simulation_params, bake_params);
}
if (use_orig_index_verts || use_orig_index_edges || use_orig_index_faces) {
if (Mesh *mesh = geometry_set.get_mesh_for_write()) {
/* Add #CD_ORIGINDEX layers if they don't exist already. This is required because the
* #eModifierTypeFlag_SupportsMapping flag is set. If the layers did not exist before, it is
* assumed that the output mesh does not have a mapping to the original mesh. */
if (use_orig_index_verts) {
CustomData_add_layer(&mesh->vert_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->verts_num);
}
if (use_orig_index_edges) {
CustomData_add_layer(&mesh->edge_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->edges_num);
}
if (use_orig_index_faces) {
CustomData_add_layer(&mesh->face_data, CD_ORIGINDEX, CD_SET_DEFAULT, mesh->faces_num);
}
}
}
}
static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh)
{
bke::GeometrySet geometry_set = bke::GeometrySet::from_mesh(
mesh, bke::GeometryOwnershipType::Editable);
modifyGeometry(md, ctx, geometry_set);
bke::MeshComponent &mesh_component = geometry_set.get_component_for_write<bke::MeshComponent>();
if (mesh_component.get() != mesh) {
/* If this is the same as the input mesh, it's not necessary to make a copy of it even if it's
* not owned by the geometry set. That's because we know that the caller manages the ownership
* of the mesh. */
mesh_component.ensure_owns_direct_data();
}
Mesh *new_mesh = mesh_component.release();
if (new_mesh == nullptr) {
return BKE_mesh_new_nomain(0, 0, 0, 0);
}
return new_mesh;
}
static void modify_geometry_set(ModifierData *md,
const ModifierEvalContext *ctx,
bke::GeometrySet *geometry_set)
{
modifyGeometry(md, ctx, *geometry_set);
}
struct SocketSearchData {
uint32_t object_session_uid;
char modifier_name[MAX_NAME];
char socket_identifier[MAX_NAME];
bool is_output;
};
/* This class must not have a destructor, since it is used by buttons and freed with #MEM_freeN. */
BLI_STATIC_ASSERT(std::is_trivially_destructible_v<SocketSearchData>, "");
struct DrawGroupInputsContext {
const bContext &C;
NodesModifierData &nmd;
nodes::PropertiesVectorSet properties;
PointerRNA *md_ptr;
PointerRNA *bmain_ptr;
Array<bool> input_usages;
};
static NodesModifierData *get_modifier_data(Main &bmain,
const wmWindowManager &wm,
const SocketSearchData &data)
{
if (ED_screen_animation_playing(&wm)) {
/* Work around an issue where the attribute search exec function has stale pointers when data
* is reallocated when evaluating the node tree, causing a crash. This would be solved by
* allowing the UI search data to own arbitrary memory rather than just referencing it. */
return nullptr;
}
const Object *object = (Object *)BKE_libblock_find_session_uid(
&bmain, ID_OB, data.object_session_uid);
if (object == nullptr) {
return nullptr;
}
ModifierData *md = BKE_modifiers_findby_name(object, data.modifier_name);
if (md == nullptr) {
return nullptr;
}
BLI_assert(md->type == eModifierType_Nodes);
return reinterpret_cast<NodesModifierData *>(md);
}
static geo_log::GeoTreeLog *get_root_tree_log(const NodesModifierData &nmd)
{
if (!nmd.runtime->eval_log) {
return nullptr;
}
bke::ModifierComputeContext compute_context{nullptr, nmd};
return &nmd.runtime->eval_log->get_tree_log(compute_context.hash());
}
static void attribute_search_update_fn(
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
{
SocketSearchData &data = *static_cast<SocketSearchData *>(arg);
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
if (nmd == nullptr) {
return;
}
if (nmd->node_group == nullptr) {
return;
}
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
if (tree_log == nullptr) {
return;
}
tree_log->ensure_existing_attributes();
nmd->node_group->ensure_topology_cache();
Vector<const bNodeSocket *> sockets_to_check;
if (data.is_output) {
for (const bNode *node : nmd->node_group->nodes_by_type("NodeGroupOutput")) {
for (const bNodeSocket *socket : node->input_sockets()) {
if (socket->type == SOCK_GEOMETRY) {
sockets_to_check.append(socket);
}
}
}
}
else {
for (const bNode *node : nmd->node_group->group_input_nodes()) {
for (const bNodeSocket *socket : node->output_sockets()) {
if (socket->type == SOCK_GEOMETRY) {
sockets_to_check.append(socket);
}
}
}
}
Set<StringRef> names;
Vector<const geo_log::GeometryAttributeInfo *> attributes;
for (const bNodeSocket *socket : sockets_to_check) {
const geo_log::ValueLog *value_log = tree_log->find_socket_value_log(*socket);
if (value_log == nullptr) {
continue;
}
if (const auto *geo_log = dynamic_cast<const geo_log::GeometryInfoLog *>(value_log)) {
for (const geo_log::GeometryAttributeInfo &attribute : geo_log->attributes) {
if (names.add(attribute.name)) {
attributes.append(&attribute);
}
}
}
}
ui::attribute_search_add_items(str, data.is_output, attributes.as_span(), items, is_first);
}
static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v)
{
if (item_v == nullptr) {
return;
}
SocketSearchData &data = *static_cast<SocketSearchData *>(data_v);
const auto &item = *static_cast<const geo_log::GeometryAttributeInfo *>(item_v);
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
if (nmd == nullptr) {
return;
}
const std::string attribute_prop_name = data.socket_identifier +
nodes::input_attribute_name_suffix;
IDProperty &name_property = *IDP_GetPropertyFromGroup(nmd->settings.properties,
attribute_prop_name);
IDP_AssignString(&name_property, item.name.c_str());
ED_undo_push(C, "Assign Attribute Name");
}
static void add_attribute_search_button(DrawGroupInputsContext &ctx,
uiLayout *layout,
const StringRefNull rna_path_attribute_name,
const bNodeTreeInterfaceSocket &socket,
const bool is_output)
{
if (!ctx.nmd.runtime->eval_log) {
uiItemR(layout, ctx.md_ptr, rna_path_attribute_name, UI_ITEM_NONE, "", ICON_NONE);
return;
}
uiBlock *block = uiLayoutGetBlock(layout);
uiBut *but = uiDefIconTextButR(block,
UI_BTYPE_SEARCH_MENU,
0,
ICON_NONE,
"",
0,
0,
10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */
UI_UNIT_Y,
ctx.md_ptr,
rna_path_attribute_name,
0,
0.0f,
0.0f,
StringRef(socket.description));
const Object *object = ed::object::context_object(&ctx.C);
BLI_assert(object != nullptr);
if (object == nullptr) {
return;
}
SocketSearchData *data = MEM_callocN<SocketSearchData>(__func__);
data->object_session_uid = object->id.session_uid;
STRNCPY(data->modifier_name, ctx.nmd.modifier.name);
STRNCPY(data->socket_identifier, socket.identifier);
data->is_output = is_output;
UI_but_func_search_set_results_are_suggestions(but, true);
UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
UI_but_func_search_set(but,
nullptr,
attribute_search_update_fn,
static_cast<void *>(data),
true,
nullptr,
attribute_search_exec_fn,
nullptr);
char *attribute_name = RNA_string_get_alloc(
ctx.md_ptr, rna_path_attribute_name.c_str(), nullptr, 0, nullptr);
const bool access_allowed = bke::allow_procedural_attribute_access(attribute_name);
MEM_freeN(attribute_name);
if (!access_allowed) {
UI_but_flag_enable(but, UI_BUT_REDALERT);
}
}
static void add_attribute_search_or_value_buttons(DrawGroupInputsContext &ctx,
uiLayout *layout,
const StringRef socket_id_esc,
const StringRefNull rna_path,
const bNodeTreeInterfaceSocket &socket)
{
const bke::bNodeSocketType *typeinfo = socket.socket_typeinfo();
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
const std::string rna_path_attribute_name = fmt::format(
"[\"{}{}\"]", socket_id_esc, nodes::input_attribute_name_suffix);
/* We're handling this manually in this case. */
uiLayoutSetPropDecorate(layout, false);
uiLayout *split = &layout->split(0.4f, false);
uiLayout *name_row = &split->row(false);
uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT);
uiLayout *prop_row = nullptr;
const std::optional<StringRef> attribute_name = nodes::input_attribute_name_get(ctx.properties,
socket);
if (type == SOCK_BOOLEAN && !attribute_name) {
uiItemL(name_row, "", ICON_NONE);
prop_row = &split->row(true);
}
else {
prop_row = &layout->row(true);
}
if (type == SOCK_BOOLEAN) {
uiLayoutSetPropSep(prop_row, false);
uiLayoutSetAlignment(prop_row, UI_LAYOUT_ALIGN_EXPAND);
}
if (attribute_name) {
uiItemL(name_row, socket.name ? IFACE_(socket.name) : "", ICON_NONE);
prop_row = &split->row(true);
add_attribute_search_button(ctx, prop_row, rna_path_attribute_name, socket, false);
uiItemL(layout, "", ICON_BLANK1);
}
else {
const char *name = socket.name ? IFACE_(socket.name) : "";
uiItemR(prop_row, ctx.md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
uiItemDecoratorR(layout, ctx.md_ptr, rna_path.c_str(), -1);
}
PointerRNA props;
uiItemFullO(prop_row,
"object.geometry_nodes_input_attribute_toggle",
"",
ICON_SPREADSHEET,
nullptr,
WM_OP_INVOKE_DEFAULT,
UI_ITEM_NONE,
&props);
RNA_string_set(&props, "modifier_name", ctx.nmd.modifier.name);
RNA_string_set(&props, "input_name", socket.identifier);
}
static void layer_name_search_update_fn(
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
{
const SocketSearchData &data = *static_cast<SocketSearchData *>(arg);
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
if (nmd == nullptr) {
return;
}
if (nmd->node_group == nullptr) {
return;
}
geo_log::GeoTreeLog *tree_log = get_root_tree_log(*nmd);
if (tree_log == nullptr) {
return;
}
tree_log->ensure_layer_names();
nmd->node_group->ensure_topology_cache();
Vector<const bNodeSocket *> sockets_to_check;
for (const bNode *node : nmd->node_group->group_input_nodes()) {
for (const bNodeSocket *socket : node->output_sockets()) {
if (socket->type == SOCK_GEOMETRY) {
sockets_to_check.append(socket);
}
}
}
Set<StringRef> names;
Vector<const std::string *> layer_names;
for (const bNodeSocket *socket : sockets_to_check) {
const geo_log::ValueLog *value_log = tree_log->find_socket_value_log(*socket);
if (value_log == nullptr) {
continue;
}
if (const auto *geo_log = dynamic_cast<const geo_log::GeometryInfoLog *>(value_log)) {
if (const std::optional<geo_log::GeometryInfoLog::GreasePencilInfo> &grease_pencil_info =
geo_log->grease_pencil_info)
{
for (const std::string &name : grease_pencil_info->layer_names) {
if (names.add(name)) {
layer_names.append(&name);
}
}
}
}
}
BLI_assert(items);
ui::grease_pencil_layer_search_add_items(str, layer_names.as_span(), *items, is_first);
}
static void layer_name_search_exec_fn(bContext *C, void *data_v, void *item_v)
{
const SocketSearchData &data = *static_cast<SocketSearchData *>(data_v);
const std::string *item = static_cast<std::string *>(item_v);
if (item == nullptr) {
return;
}
const NodesModifierData *nmd = get_modifier_data(*CTX_data_main(C), *CTX_wm_manager(C), data);
if (nmd == nullptr) {
return;
}
IDProperty &name_property = *IDP_GetPropertyFromGroup(nmd->settings.properties,
data.socket_identifier);
IDP_AssignString(&name_property, item->c_str());
ED_undo_push(C, "Assign Layer Name");
}
static void add_layer_name_search_button(DrawGroupInputsContext &ctx,
uiLayout *layout,
const StringRefNull socket_id_esc,
const bNodeTreeInterfaceSocket &socket)
{
const std::string rna_path = fmt::format("[\"{}\"]", socket_id_esc);
if (!ctx.nmd.runtime->eval_log) {
uiItemR(layout, ctx.md_ptr, rna_path, UI_ITEM_NONE, "", ICON_NONE);
return;
}
uiLayoutSetPropDecorate(layout, false);
uiLayout *split = &layout->split(0.4f, false);
uiLayout *name_row = &split->row(false);
uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT);
uiItemL(name_row, socket.name ? IFACE_(socket.name) : "", ICON_NONE);
uiLayout *prop_row = &split->row(true);
uiBlock *block = uiLayoutGetBlock(prop_row);
uiBut *but = uiDefIconTextButR(block,
UI_BTYPE_SEARCH_MENU,
0,
ICON_OUTLINER_DATA_GP_LAYER,
"",
0,
0,
10 * UI_UNIT_X, /* Dummy value, replaced by layout system. */
UI_UNIT_Y,
ctx.md_ptr,
rna_path,
0,
0.0f,
0.0f,
StringRef(socket.description));
UI_but_placeholder_set(but, "Layer");
uiItemL(layout, "", ICON_BLANK1);
const Object *object = ed::object::context_object(&ctx.C);
BLI_assert(object != nullptr);
if (object == nullptr) {
return;
}
SocketSearchData *data = MEM_callocN<SocketSearchData>(__func__);
data->object_session_uid = object->id.session_uid;
STRNCPY(data->modifier_name, ctx.nmd.modifier.name);
STRNCPY(data->socket_identifier, socket.identifier);
UI_but_func_search_set_results_are_suggestions(but, true);
UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
UI_but_func_search_set(but,
nullptr,
layer_name_search_update_fn,
static_cast<void *>(data),
true,
nullptr,
layer_name_search_exec_fn,
nullptr);
}
/* Drawing the properties manually with #uiItemR instead of #uiDefAutoButsRNA allows using
* the node socket identifier for the property names, since they are unique, but also having
* the correct label displayed in the UI. */
static void draw_property_for_socket(DrawGroupInputsContext &ctx,
uiLayout *layout,
const bNodeTreeInterfaceSocket &socket)
{
const StringRefNull identifier = socket.identifier;
/* The property should be created in #MOD_nodes_update_interface with the correct type. */
IDProperty *property = ctx.properties.lookup_key_default_as(identifier, nullptr);
/* IDProperties can be removed with python, so there could be a situation where
* there isn't a property for a socket or it doesn't have the correct type. */
if (property == nullptr || !nodes::id_property_type_matches_socket(socket, *property)) {
return;
}
char socket_id_esc[MAX_NAME * 2];
BLI_str_escape(socket_id_esc, identifier.c_str(), sizeof(socket_id_esc));
char rna_path[sizeof(socket_id_esc) + 4];
SNPRINTF(rna_path, "[\"%s\"]", socket_id_esc);
const int input_index = ctx.nmd.node_group->interface_input_index(socket);
uiLayout *row = &layout->row(true);
uiLayoutSetPropDecorate(row, true);
uiLayoutSetActive(row, ctx.input_usages[input_index]);
/* Use #uiItemPointerR to draw pointer properties because #uiItemR would not have enough
* information about what type of ID to select for editing the values. This is because
* pointer IDProperties contain no information about their type. */
const bke::bNodeSocketType *typeinfo = socket.socket_typeinfo();
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
const char *name = socket.name ? IFACE_(socket.name) : "";
switch (type) {
case SOCK_OBJECT: {
uiItemPointerR(row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "objects", name, ICON_OBJECT_DATA);
break;
}
case SOCK_COLLECTION: {
uiItemPointerR(
row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "collections", name, ICON_OUTLINER_COLLECTION);
break;
}
case SOCK_MATERIAL: {
uiItemPointerR(row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "materials", name, ICON_MATERIAL);
break;
}
case SOCK_TEXTURE: {
uiItemPointerR(row, ctx.md_ptr, rna_path, ctx.bmain_ptr, "textures", name, ICON_TEXTURE);
break;
}
case SOCK_IMAGE: {
uiTemplateID(row,
&ctx.C,
ctx.md_ptr,
rna_path,
"image.new",
"image.open",
nullptr,
UI_TEMPLATE_ID_FILTER_ALL,
false,
name);
break;
}
case SOCK_BOOLEAN: {
if (is_layer_selection_field(socket)) {
add_layer_name_search_button(ctx, row, socket_id_esc, socket);
/* Adds a spacing at the end of the row. */
uiItemL(row, "", ICON_BLANK1);
break;
}
ATTR_FALLTHROUGH;
}
default: {
if (nodes::input_has_attribute_toggle(*ctx.nmd.node_group, input_index)) {
add_attribute_search_or_value_buttons(ctx, row, socket_id_esc, rna_path, socket);
}
else {
uiItemR(row, ctx.md_ptr, rna_path, UI_ITEM_NONE, name, ICON_NONE);
}
}
}
if (!nodes::input_has_attribute_toggle(*ctx.nmd.node_group, input_index)) {
uiItemL(row, "", ICON_BLANK1);
}
}
static void draw_property_for_output_socket(DrawGroupInputsContext &ctx,
uiLayout *layout,
const bNodeTreeInterfaceSocket &socket)
{
const StringRefNull identifier = socket.identifier;
char socket_id_esc[MAX_NAME * 2];
BLI_str_escape(socket_id_esc, identifier.c_str(), sizeof(socket_id_esc));
const std::string rna_path_attribute_name = fmt::format(
"[\"{}{}\"]", socket_id_esc, nodes::input_attribute_name_suffix);
uiLayout *split = &layout->split(0.4f, false);
uiLayout *name_row = &split->row(false);
uiLayoutSetAlignment(name_row, UI_LAYOUT_ALIGN_RIGHT);
uiItemL(name_row, socket.name ? socket.name : "", ICON_NONE);
uiLayout *row = &split->row(true);
add_attribute_search_button(ctx, row, rna_path_attribute_name, socket, true);
}
static NodesModifierPanel *find_panel_by_id(NodesModifierData &nmd, const int id)
{
for (const int i : IndexRange(nmd.panels_num)) {
if (nmd.panels[i].id == id) {
return &nmd.panels[i];
}
}
return nullptr;
}
static bool interface_panel_has_socket(const bNodeTreeInterfacePanel &interface_panel)
{
for (const bNodeTreeInterfaceItem *item : interface_panel.items()) {
if (item->item_type == NODE_INTERFACE_SOCKET) {
const bNodeTreeInterfaceSocket &socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(
item);
if ((socket.flag &
(NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER | NODE_INTERFACE_SOCKET_OUTPUT)) == 0)
{
return true;
}
}
if (item->item_type == NODE_INTERFACE_PANEL) {
if (interface_panel_has_socket(*reinterpret_cast<const bNodeTreeInterfacePanel *>(item))) {
return true;
}
}
}
return false;
}
static bool interface_panel_affects_output(DrawGroupInputsContext &ctx,
const bNodeTreeInterfacePanel &panel)
{
for (const bNodeTreeInterfaceItem *item : panel.items()) {
if (item->item_type == NODE_INTERFACE_SOCKET) {
const auto &socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(item);
if (socket.flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER) {
continue;
}
if (!(socket.flag & NODE_INTERFACE_SOCKET_INPUT)) {
continue;
}
const int input_index = ctx.nmd.node_group->interface_input_index(socket);
if (ctx.input_usages[input_index]) {
return true;
}
}
else if (item->item_type == NODE_INTERFACE_PANEL) {
const auto &sub_interface_panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
if (interface_panel_affects_output(ctx, sub_interface_panel)) {
return true;
}
}
}
return false;
}
static void draw_interface_panel_content(DrawGroupInputsContext &ctx,
uiLayout *layout,
const bNodeTreeInterfacePanel &interface_panel,
const bool skip_first = false)
{
for (const bNodeTreeInterfaceItem *item : interface_panel.items().drop_front(skip_first ? 1 : 0))
{
if (item->item_type == NODE_INTERFACE_PANEL) {
const auto &sub_interface_panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
if (!interface_panel_has_socket(sub_interface_panel)) {
continue;
}
NodesModifierPanel *panel = find_panel_by_id(ctx.nmd, sub_interface_panel.identifier);
PointerRNA panel_ptr = RNA_pointer_create_discrete(
ctx.md_ptr->owner_id, &RNA_NodesModifierPanel, panel);
PanelLayout panel_layout;
bool skip_first = false;
/* Check if the panel should have a toggle in the header. */
const bNodeTreeInterfaceSocket *toggle_socket = sub_interface_panel.header_toggle_socket();
if (toggle_socket && !(toggle_socket->flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
const StringRefNull identifier = toggle_socket->identifier;
IDProperty *property = ctx.properties.lookup_key_default_as(identifier, nullptr);
/* IDProperties can be removed with python, so there could be a situation where
* there isn't a property for a socket or it doesn't have the correct type. */
if (property == nullptr ||
!nodes::id_property_type_matches_socket(*toggle_socket, *property))
{
continue;
}
char socket_id_esc[MAX_NAME * 2];
BLI_str_escape(socket_id_esc, identifier.c_str(), sizeof(socket_id_esc));
char rna_path[sizeof(socket_id_esc) + 4];
SNPRINTF(rna_path, "[\"%s\"]", socket_id_esc);
panel_layout = layout->panel_prop_with_bool_header(
&ctx.C, &panel_ptr, "is_open", ctx.md_ptr, rna_path, IFACE_(sub_interface_panel.name));
skip_first = true;
}
else {
panel_layout = layout->panel_prop(&ctx.C, &panel_ptr, "is_open");
uiItemL(panel_layout.header, IFACE_(sub_interface_panel.name), ICON_NONE);
}
if (!interface_panel_affects_output(ctx, sub_interface_panel)) {
uiLayoutSetActive(panel_layout.header, false);
}
uiLayoutSetTooltipFunc(
panel_layout.header,
[](bContext * /*C*/, void *panel_arg, const StringRef /*tip*/) -> std::string {
const auto *panel = static_cast<bNodeTreeInterfacePanel *>(panel_arg);
return StringRef(panel->description);
},
const_cast<bNodeTreeInterfacePanel *>(&sub_interface_panel),
nullptr,
nullptr);
if (panel_layout.body) {
draw_interface_panel_content(ctx, panel_layout.body, sub_interface_panel, skip_first);
}
}
else {
const auto &interface_socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(item);
if (interface_socket.flag & NODE_INTERFACE_SOCKET_INPUT) {
if (!(interface_socket.flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
draw_property_for_socket(ctx, layout, interface_socket);
}
}
}
}
}
static bool has_output_attribute(const NodesModifierData &nmd)
{
if (!nmd.node_group) {
return false;
}
for (const bNodeTreeInterfaceSocket *interface_socket : nmd.node_group->interface_outputs()) {
const bke::bNodeSocketType *typeinfo = interface_socket->socket_typeinfo();
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM;
if (nodes::socket_type_has_attribute_toggle(type)) {
return true;
}
}
return false;
}
static void draw_output_attributes_panel(DrawGroupInputsContext &ctx, uiLayout *layout)
{
if (ctx.nmd.node_group != nullptr && ctx.nmd.settings.properties != nullptr) {
for (const bNodeTreeInterfaceSocket *socket : ctx.nmd.node_group->interface_outputs()) {
const bke::bNodeSocketType *typeinfo = socket->socket_typeinfo();
const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) :
SOCK_CUSTOM;
if (nodes::socket_type_has_attribute_toggle(type)) {
draw_property_for_output_socket(ctx, layout, *socket);
}
}
}
}
static void draw_bake_panel(uiLayout *layout, PointerRNA *modifier_ptr)
{
uiLayout *col = &layout->column(false);
uiLayoutSetPropSep(col, true);
uiLayoutSetPropDecorate(col, false);
uiItemR(col, modifier_ptr, "bake_target", UI_ITEM_NONE, std::nullopt, ICON_NONE);
uiItemR(col, modifier_ptr, "bake_directory", UI_ITEM_NONE, IFACE_("Bake Path"), ICON_NONE);
}
static void draw_named_attributes_panel(uiLayout *layout, NodesModifierData &nmd)
{
if (G.is_rendering) {
/* Avoid accessing this data while baking in a separate thread. */
return;
}
geo_log::GeoTreeLog *tree_log = get_root_tree_log(nmd);
if (tree_log == nullptr) {
return;
}
tree_log->ensure_used_named_attributes();
const Map<StringRefNull, geo_log::NamedAttributeUsage> &usage_by_attribute =
tree_log->used_named_attributes;
if (usage_by_attribute.is_empty()) {
uiItemL(layout, RPT_("No named attributes used"), ICON_INFO);
return;
}
struct NameWithUsage {
StringRefNull name;
geo_log::NamedAttributeUsage usage;
};
Vector<NameWithUsage> sorted_used_attribute;
for (auto &&item : usage_by_attribute.items()) {
sorted_used_attribute.append({item.key, item.value});
}
std::sort(sorted_used_attribute.begin(),
sorted_used_attribute.end(),
[](const NameWithUsage &a, const NameWithUsage &b) {
return BLI_strcasecmp_natural(a.name.c_str(), b.name.c_str()) < 0;
});
for (const NameWithUsage &attribute : sorted_used_attribute) {
const StringRef attribute_name = attribute.name;
const geo_log::NamedAttributeUsage usage = attribute.usage;
/* #uiLayoutRowWithHeading doesn't seem to work in this case. */
uiLayout *split = &layout->split(0.4f, false);
std::stringstream ss;
Vector<std::string> usages;
if ((usage & geo_log::NamedAttributeUsage::Read) != geo_log::NamedAttributeUsage::None) {
usages.append(IFACE_("Read"));
}
if ((usage & geo_log::NamedAttributeUsage::Write) != geo_log::NamedAttributeUsage::None) {
usages.append(IFACE_("Write"));
}
if ((usage & geo_log::NamedAttributeUsage::Remove) != geo_log::NamedAttributeUsage::None) {
usages.append(CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove"));
}
for (const int i : usages.index_range()) {
ss << usages[i];
if (i < usages.size() - 1) {
ss << ", ";
}
}
uiLayout *row = &split->row(false);
uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_RIGHT);
uiLayoutSetActive(row, false);
uiItemL(row, ss.str(), ICON_NONE);
row = &split->row(false);
uiItemL(row, attribute_name, ICON_NONE);
}
}
static void draw_manage_panel(const bContext *C,
uiLayout *layout,
PointerRNA *modifier_ptr,
NodesModifierData &nmd)
{
if (uiLayout *panel_layout = layout->panel_prop(
C, modifier_ptr, "open_bake_panel", IFACE_("Bake")))
{
draw_bake_panel(panel_layout, modifier_ptr);
}
if (uiLayout *panel_layout = layout->panel_prop(
C, modifier_ptr, "open_named_attributes_panel", IFACE_("Named Attributes")))
{
draw_named_attributes_panel(panel_layout, nmd);
}
}
static void draw_warnings(const bContext *C,
const NodesModifierData &nmd,
uiLayout *layout,
PointerRNA *md_ptr)
{
if (G.is_rendering) {
/* Avoid accessing this data while baking in a separate thread. */
return;
}
using namespace geo_log;
GeoTreeLog *tree_log = get_root_tree_log(nmd);
if (!tree_log) {
return;
}
tree_log->ensure_node_warnings(*CTX_data_main(C));
const int warnings_num = tree_log->all_warnings.size();
if (warnings_num == 0) {
return;
}
PanelLayout panel = layout->panel_prop(C, md_ptr, "open_warnings_panel");
uiItemL(panel.header,
fmt::format(fmt::runtime(IFACE_("Warnings ({})")), warnings_num).c_str(),
ICON_NONE);
if (!panel.body) {
return;
}
Vector<const NodeWarning *> warnings(tree_log->all_warnings.size());
for (const int i : warnings.index_range()) {
warnings[i] = &tree_log->all_warnings[i];
}
std::sort(warnings.begin(), warnings.end(), [](const NodeWarning *a, const NodeWarning *b) {
const int severity_a = node_warning_type_severity(a->type);
const int severity_b = node_warning_type_severity(b->type);
if (severity_a > severity_b) {
return true;
}
if (severity_a < severity_b) {
return false;
}
return BLI_strcasecmp_natural(a->message.c_str(), b->message.c_str()) < 0;
});
uiLayout *col = &panel.body->column(false);
for (const NodeWarning *warning : warnings) {
const int icon = node_warning_type_icon(warning->type);
uiItemL(col, warning->message, icon);
}
}
static void panel_draw(const bContext *C, Panel *panel)
{
uiLayout *layout = panel->layout;
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
NodesModifierData *nmd = static_cast<NodesModifierData *>(ptr->data);
uiLayoutSetPropSep(layout, true);
/* Decorators are added manually for supported properties because the
* attribute/value toggle requires a manually built layout anyway. */
uiLayoutSetPropDecorate(layout, false);
if (!(nmd->flag & NODES_MODIFIER_HIDE_DATABLOCK_SELECTOR)) {
const char *newop = (nmd->node_group == nullptr) ? "node.new_geometry_node_group_assign" :
"object.geometry_node_tree_copy_assign";
uiTemplateID(layout, C, ptr, "node_group", newop, nullptr, nullptr);
}
Main *bmain = CTX_data_main(C);
PointerRNA bmain_ptr = RNA_main_pointer_create(bmain);
DrawGroupInputsContext ctx{
*C, *nmd, nodes::build_properties_vector_set(nmd->settings.properties), ptr, &bmain_ptr};
if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) {
nmd->node_group->ensure_interface_cache();
ctx.input_usages.reinitialize(nmd->node_group->interface_inputs().size());
nodes::socket_usage_inference::infer_group_interface_inputs_usage(
*nmd->node_group, ctx.properties, ctx.input_usages);
draw_interface_panel_content(ctx, layout, nmd->node_group->tree_interface.root_panel);
}
modifier_panel_end(layout, ptr);
draw_warnings(C, *nmd, layout, ptr);
if (has_output_attribute(*nmd)) {
if (uiLayout *panel_layout = layout->panel_prop(
C, ptr, "open_output_attributes_panel", IFACE_("Output Attributes")))
{
draw_output_attributes_panel(ctx, panel_layout);
}
}
if (uiLayout *panel_layout = layout->panel_prop(C, ptr, "open_manage_panel", IFACE_("Manage"))) {
draw_manage_panel(C, panel_layout, ptr, *nmd);
}
}
static void panel_register(ARegionType *region_type)
{
using namespace blender;
modifier_panel_register(region_type, eModifierType_Nodes, panel_draw);
}
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
{
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
BLO_write_struct(writer, NodesModifierData, nmd);
BLO_write_string(writer, nmd->bake_directory);
if (nmd->settings.properties != nullptr) {
Map<IDProperty *, IDPropertyUIDataBool *> boolean_props;
if (!BLO_write_is_undo(writer)) {
/* Boolean properties are added automatically for boolean node group inputs. Integer
* properties are automatically converted to boolean sockets where applicable as well.
* However, boolean properties will crash old versions of Blender, so convert them to integer
* properties for writing. The actual value is stored in the same variable for both types */
LISTBASE_FOREACH (IDProperty *, prop, &nmd->settings.properties->data.group) {
if (prop->type == IDP_BOOLEAN) {
boolean_props.add_new(prop, reinterpret_cast<IDPropertyUIDataBool *>(prop->ui_data));
prop->type = IDP_INT;
prop->ui_data = nullptr;
}
}
}
/* Note that the property settings are based on the socket type info
* and don't necessarily need to be written, but we can't just free them. */
IDP_BlendWrite(writer, nmd->settings.properties);
BLO_write_struct_array(writer, NodesModifierBake, nmd->bakes_num, nmd->bakes);
for (const NodesModifierBake &bake : Span(nmd->bakes, nmd->bakes_num)) {
BLO_write_string(writer, bake.directory);
BLO_write_struct_array(
writer, NodesModifierDataBlock, bake.data_blocks_num, bake.data_blocks);
for (const NodesModifierDataBlock &item : Span(bake.data_blocks, bake.data_blocks_num)) {
BLO_write_string(writer, item.id_name);
BLO_write_string(writer, item.lib_name);
}
if (bake.packed) {
BLO_write_struct(writer, NodesModifierPackedBake, bake.packed);
BLO_write_struct_array(
writer, NodesModifierBakeFile, bake.packed->meta_files_num, bake.packed->meta_files);
BLO_write_struct_array(
writer, NodesModifierBakeFile, bake.packed->blob_files_num, bake.packed->blob_files);
const auto write_bake_file = [&](const NodesModifierBakeFile &bake_file) {
BLO_write_string(writer, bake_file.name);
if (bake_file.packed_file) {
BKE_packedfile_blend_write(writer, bake_file.packed_file);
}
};
for (const NodesModifierBakeFile &meta_file :
Span{bake.packed->meta_files, bake.packed->meta_files_num})
{
write_bake_file(meta_file);
}
for (const NodesModifierBakeFile &blob_file :
Span{bake.packed->blob_files, bake.packed->blob_files_num})
{
write_bake_file(blob_file);
}
}
}
BLO_write_struct_array(writer, NodesModifierPanel, nmd->panels_num, nmd->panels);
if (!BLO_write_is_undo(writer)) {
LISTBASE_FOREACH (IDProperty *, prop, &nmd->settings.properties->data.group) {
if (prop->type == IDP_INT) {
if (IDPropertyUIDataBool **ui_data = boolean_props.lookup_ptr(prop)) {
prop->type = IDP_BOOLEAN;
if (ui_data) {
prop->ui_data = reinterpret_cast<IDPropertyUIData *>(*ui_data);
}
}
}
}
}
}
}
static void blend_read(BlendDataReader *reader, ModifierData *md)
{
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
BLO_read_string(reader, &nmd->bake_directory);
if (nmd->node_group == nullptr) {
nmd->settings.properties = nullptr;
}
else {
BLO_read_struct(reader, IDProperty, &nmd->settings.properties);
IDP_BlendDataRead(reader, &nmd->settings.properties);
}
BLO_read_struct_array(reader, NodesModifierBake, nmd->bakes_num, &nmd->bakes);
if (nmd->bakes_num > 0 && nmd->bakes == nullptr) {
/* This case generally shouldn't be allowed to happen. However, there is a bug report with a
* corrupted .blend file (#123974) that triggers this case. Unfortunately, it's not clear how
* that could have happened. For now, handle this case more gracefully in release builds, while
* still crashing in debug builds. */
nmd->bakes_num = 0;
BLI_assert_unreachable();
}
for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) {
BLO_read_string(reader, &bake.directory);
BLO_read_struct_array(reader, NodesModifierDataBlock, bake.data_blocks_num, &bake.data_blocks);
for (NodesModifierDataBlock &data_block : MutableSpan(bake.data_blocks, bake.data_blocks_num))
{
BLO_read_string(reader, &data_block.id_name);
BLO_read_string(reader, &data_block.lib_name);
}
BLO_read_struct(reader, NodesModifierPackedBake, &bake.packed);
if (bake.packed) {
BLO_read_struct_array(
reader, NodesModifierBakeFile, bake.packed->meta_files_num, &bake.packed->meta_files);
BLO_read_struct_array(
reader, NodesModifierBakeFile, bake.packed->blob_files_num, &bake.packed->blob_files);
const auto read_bake_file = [&](NodesModifierBakeFile &bake_file) {
BLO_read_string(reader, &bake_file.name);
if (bake_file.packed_file) {
BKE_packedfile_blend_read(reader, &bake_file.packed_file, "");
}
};
for (NodesModifierBakeFile &meta_file :
MutableSpan{bake.packed->meta_files, bake.packed->meta_files_num})
{
read_bake_file(meta_file);
}
for (NodesModifierBakeFile &blob_file :
MutableSpan{bake.packed->blob_files, bake.packed->blob_files_num})
{
read_bake_file(blob_file);
}
}
}
BLO_read_struct_array(reader, NodesModifierPanel, nmd->panels_num, &nmd->panels);
nmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
nmd->runtime->cache = std::make_shared<bake::ModifierCache>();
}
static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
{
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
NodesModifierData *tnmd = reinterpret_cast<NodesModifierData *>(target);
BKE_modifier_copydata_generic(md, target, flag);
if (nmd->bakes) {
tnmd->bakes = static_cast<NodesModifierBake *>(MEM_dupallocN(nmd->bakes));
for (const int i : IndexRange(nmd->bakes_num)) {
NodesModifierBake &bake = tnmd->bakes[i];
if (bake.directory) {
bake.directory = BLI_strdup(bake.directory);
}
if (bake.data_blocks) {
bake.data_blocks = static_cast<NodesModifierDataBlock *>(MEM_dupallocN(bake.data_blocks));
for (const int i : IndexRange(bake.data_blocks_num)) {
NodesModifierDataBlock &data_block = bake.data_blocks[i];
if (data_block.id_name) {
data_block.id_name = BLI_strdup(data_block.id_name);
}
if (data_block.lib_name) {
data_block.lib_name = BLI_strdup(data_block.lib_name);
}
}
}
if (bake.packed) {
bake.packed = static_cast<NodesModifierPackedBake *>(MEM_dupallocN(bake.packed));
const auto copy_bake_files_inplace = [](NodesModifierBakeFile **bake_files,
const int bake_files_num) {
if (!*bake_files) {
return;
}
*bake_files = static_cast<NodesModifierBakeFile *>(MEM_dupallocN(*bake_files));
for (NodesModifierBakeFile &bake_file : MutableSpan{*bake_files, bake_files_num}) {
bake_file.name = BLI_strdup_null(bake_file.name);
if (bake_file.packed_file) {
bake_file.packed_file = BKE_packedfile_duplicate(bake_file.packed_file);
}
}
};
copy_bake_files_inplace(&bake.packed->meta_files, bake.packed->meta_files_num);
copy_bake_files_inplace(&bake.packed->blob_files, bake.packed->blob_files_num);
}
}
}
if (nmd->panels) {
tnmd->panels = static_cast<NodesModifierPanel *>(MEM_dupallocN(nmd->panels));
}
tnmd->runtime = MEM_new<NodesModifierRuntime>(__func__);
if (flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) {
/* Share the simulation cache between the original and evaluated modifier. */
tnmd->runtime->cache = nmd->runtime->cache;
/* Keep bake path in the evaluated modifier. */
tnmd->bake_directory = nmd->bake_directory ? BLI_strdup(nmd->bake_directory) : nullptr;
}
else {
tnmd->runtime->cache = std::make_shared<bake::ModifierCache>();
/* Clear the bake path when duplicating. */
tnmd->bake_directory = nullptr;
}
if (nmd->settings.properties != nullptr) {
tnmd->settings.properties = IDP_CopyProperty_ex(nmd->settings.properties, flag);
}
}
void nodes_modifier_packed_bake_free(NodesModifierPackedBake *packed_bake)
{
const auto free_packed_files = [](NodesModifierBakeFile *files, const int files_num) {
for (NodesModifierBakeFile &file : MutableSpan{files, files_num}) {
MEM_SAFE_FREE(file.name);
if (file.packed_file) {
BKE_packedfile_free(file.packed_file);
}
}
MEM_SAFE_FREE(files);
};
free_packed_files(packed_bake->meta_files, packed_bake->meta_files_num);
free_packed_files(packed_bake->blob_files, packed_bake->blob_files_num);
MEM_SAFE_FREE(packed_bake);
}
void nodes_modifier_bake_destruct(NodesModifierBake *bake, const bool do_id_user)
{
MEM_SAFE_FREE(bake->directory);
for (NodesModifierDataBlock &data_block : MutableSpan(bake->data_blocks, bake->data_blocks_num))
{
nodes_modifier_data_block_destruct(&data_block, do_id_user);
}
MEM_SAFE_FREE(bake->data_blocks);
if (bake->packed) {
nodes_modifier_packed_bake_free(bake->packed);
}
}
static void free_data(ModifierData *md)
{
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
if (nmd->settings.properties != nullptr) {
IDP_FreeProperty_ex(nmd->settings.properties, false);
nmd->settings.properties = nullptr;
}
for (NodesModifierBake &bake : MutableSpan(nmd->bakes, nmd->bakes_num)) {
nodes_modifier_bake_destruct(&bake, false);
}
MEM_SAFE_FREE(nmd->bakes);
MEM_SAFE_FREE(nmd->panels);
MEM_SAFE_FREE(nmd->bake_directory);
MEM_delete(nmd->runtime);
}
static void required_data_mask(ModifierData * /*md*/, CustomData_MeshMasks *r_cddata_masks)
{
/* We don't know what the node tree will need. If there are vertex groups, it is likely that the
* node tree wants to access them. */
r_cddata_masks->vmask |= CD_MASK_MDEFORMVERT;
r_cddata_masks->vmask |= CD_MASK_PROP_ALL;
}
} // namespace blender
ModifierTypeInfo modifierType_Nodes = {
/*idname*/ "GeometryNodes",
/*name*/ N_("GeometryNodes"),
/*struct_name*/ "NodesModifierData",
/*struct_size*/ sizeof(NodesModifierData),
/*srna*/ &RNA_NodesModifier,
/*type*/ ModifierTypeType::Constructive,
/*flags*/
(eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_AcceptsCVs |
eModifierTypeFlag_SupportsEditmode | eModifierTypeFlag_EnableInEditmode |
eModifierTypeFlag_SupportsMapping | eModifierTypeFlag_AcceptsGreasePencil),
/*icon*/ ICON_GEOMETRY_NODES,
/*copy_data*/ blender::copy_data,
/*deform_verts*/ nullptr,
/*deform_matrices*/ nullptr,
/*deform_verts_EM*/ nullptr,
/*deform_matrices_EM*/ nullptr,
/*modify_mesh*/ blender::modify_mesh,
/*modify_geometry_set*/ blender::modify_geometry_set,
/*init_data*/ blender::init_data,
/*required_data_mask*/ blender::required_data_mask,
/*free_data*/ blender::free_data,
/*is_disabled*/ blender::is_disabled,
/*update_depsgraph*/ blender::update_depsgraph,
/*depends_on_time*/ blender::depends_on_time,
/*depends_on_normals*/ nullptr,
/*foreach_ID_link*/ blender::foreach_ID_link,
/*foreach_tex_link*/ blender::foreach_tex_link,
/*free_runtime_data*/ nullptr,
/*panel_register*/ blender::panel_register,
/*blend_write*/ blender::blend_write,
/*blend_read*/ blender::blend_read,
/*foreach_cache*/ nullptr,
};