Geometry Nodes: refactor logging during geometry nodes evaluation

Many ui features for geometry nodes need access to information generated
during evaluation:
* Node warnings.
* Attribute search.
* Viewer node.
* Socket inspection (not in master yet).

The way we logged the required information before had some disadvantages:
* Viewer node used a completely separate system from node warnings and
  attribute search.
* Most of the context of logged information is lost when e.g. the same node
  group is used multiple times.
* A global lock was needed every time something is logged.

This new implementation solves these problems:
* All four mentioned ui features use the same underlying logging system.
* All context information for logged values is kept intact.
* Every thread has its own local logger. The logged informatiton is combined
  in the end.

Differential Revision: https://developer.blender.org/D11785
This commit is contained in:
Jacques Lucke 2021-07-07 11:20:19 +02:00
parent 77834aff22
commit 0153e99780
Notes: blender-bot 2023-02-14 10:14:07 +01:00
Referenced by issue #90065, Geometry Nodes: crash using attribute search on a 'Attribute Sample Texture' node (in the Properties Editor)
Referenced by issue #89775, Immediate quit on started render with AO, Mist layers & denoising
Referenced by issue #85652, Implement socket inspection and links values
22 changed files with 829 additions and 549 deletions

View File

@ -1,133 +0,0 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
#include <mutex>
#include "BLI_hash.hh"
#include "BLI_map.hh"
#include "BLI_session_uuid.h"
#include "BLI_set.hh"
#include "DNA_ID.h"
#include "DNA_customdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_session_uuid_types.h"
#include "BKE_attribute.h"
struct ModifierData;
struct Object;
struct bNode;
struct bNodeTree;
struct bContext;
/**
* Contains the context necessary to determine when to display settings for a certain node tree
* that may be used for multiple modifiers and objects. The object name and modifier session UUID
* are used instead of pointers because they are re-allocated between evaluations.
*
* \note This does not yet handle the context of nested node trees.
*/
class NodeTreeEvaluationContext {
private:
std::string object_name_;
SessionUUID modifier_session_uuid_;
public:
NodeTreeEvaluationContext(const Object &object, const ModifierData &modifier)
{
object_name_ = reinterpret_cast<const ID &>(object).name;
modifier_session_uuid_ = modifier.session_uuid;
}
uint64_t hash() const
{
return blender::get_default_hash_2(object_name_, modifier_session_uuid_);
}
friend bool operator==(const NodeTreeEvaluationContext &a, const NodeTreeEvaluationContext &b)
{
return a.object_name_ == b.object_name_ &&
BLI_session_uuid_is_equal(&a.modifier_session_uuid_, &b.modifier_session_uuid_);
}
};
enum class NodeWarningType {
Error,
Warning,
Info,
};
struct NodeWarning {
NodeWarningType type;
std::string message;
};
struct AvailableAttributeInfo {
std::string name;
AttributeDomain domain;
CustomDataType data_type;
uint64_t hash() const
{
return blender::get_default_hash(name);
}
friend bool operator==(const AvailableAttributeInfo &a, const AvailableAttributeInfo &b)
{
return a.name == b.name;
}
};
struct NodeUIStorage {
blender::Vector<NodeWarning> warnings;
blender::Set<AvailableAttributeInfo> attribute_hints;
};
struct NodeTreeUIStorage {
std::mutex mutex;
blender::Map<NodeTreeEvaluationContext, blender::Map<std::string, NodeUIStorage>> context_map;
/**
* Attribute search uses this to store the fake info for the string typed into a node, in order
* to pass the info to the execute callback that sets node socket values. This is mutable since
* we can count on only one attribute search being open at a time, and there is no real data
* stored here.
*/
mutable AvailableAttributeInfo dummy_info_for_search;
};
const NodeUIStorage *BKE_node_tree_ui_storage_get_from_context(const bContext *C,
const bNodeTree &ntree,
const bNode &node);
void BKE_nodetree_ui_storage_free_for_context(bNodeTree &ntree,
const NodeTreeEvaluationContext &context);
void BKE_nodetree_error_message_add(bNodeTree &ntree,
const NodeTreeEvaluationContext &context,
const bNode &node,
const NodeWarningType type,
std::string message);
void BKE_nodetree_attribute_hint_add(bNodeTree &ntree,
const NodeTreeEvaluationContext &context,
const bNode &node,
const blender::StringRef attribute_name,
const AttributeDomain domain,
const CustomDataType data_type);

View File

@ -70,10 +70,6 @@ void BKE_object_free_curve_cache(struct Object *ob);
void BKE_object_free_derived_caches(struct Object *ob);
void BKE_object_free_caches(struct Object *object);
void BKE_object_preview_geometry_set_add(struct Object *ob,
const uint64_t key,
struct GeometrySet *geometry_set);
void BKE_object_modifier_hook_reset(struct Object *ob, struct HookModifierData *hmd);
void BKE_object_modifier_gpencil_hook_reset(struct Object *ob,
struct HookGpencilModifierData *hmd);

View File

@ -215,7 +215,6 @@ set(SRC
intern/multires_versioning.c
intern/nla.c
intern/node.cc
intern/node_ui_storage.cc
intern/object.c
intern/object_deform.c
intern/object_dupli.cc
@ -399,7 +398,6 @@ set(SRC
BKE_multires.h
BKE_nla.h
BKE_node.h
BKE_node_ui_storage.hh
BKE_object.h
BKE_object_deform.h
BKE_object_facemap.h

View File

@ -69,7 +69,6 @@
#include "BKE_lib_query.h"
#include "BKE_main.h"
#include "BKE_node.h"
#include "BKE_node_ui_storage.hh"
#include "BLI_ghash.h"
#include "BLI_threads.h"
@ -220,10 +219,6 @@ static void ntree_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c
/* node tree will generate its own interface type */
ntree_dst->interface_type = nullptr;
/* Don't copy error messages in the runtime struct.
* They should be filled during execution anyway. */
ntree_dst->ui_storage = nullptr;
}
static void ntree_free_data(ID *id)
@ -277,8 +272,6 @@ static void ntree_free_data(ID *id)
if (ntree->id.tag & LIB_TAG_LOCALIZED) {
BKE_libblock_free_data(&ntree->id, true);
}
delete ntree->ui_storage;
}
static void library_foreach_node_socket(LibraryForeachIDData *data, bNodeSocket *sock)
@ -621,7 +614,6 @@ static void ntree_blend_write(BlendWriter *writer, ID *id, const void *id_addres
ntree->interface_type = nullptr;
ntree->progress = nullptr;
ntree->execdata = nullptr;
ntree->ui_storage = nullptr;
BLO_write_id_struct(writer, bNodeTree, id_address, &ntree->id);
@ -653,7 +645,6 @@ void ntreeBlendReadData(BlendDataReader *reader, bNodeTree *ntree)
ntree->progress = nullptr;
ntree->execdata = nullptr;
ntree->ui_storage = nullptr;
BLO_read_data_address(reader, &ntree->adt);
BKE_animdata_blend_read_data(reader, ntree->adt);

View File

@ -1,169 +0,0 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "CLG_log.h"
#include <mutex>
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "BKE_context.h"
#include "BKE_node_ui_storage.hh"
#include "BKE_object.h"
static CLG_LogRef LOG = {"bke.node_ui_storage"};
using blender::Map;
using blender::StringRef;
using blender::Vector;
/* Use a global mutex because otherwise it would have to be stored directly in the
* bNodeTree struct in DNA. This could change if the node tree had a runtime struct. */
static std::mutex global_ui_storage_mutex;
static NodeTreeUIStorage &ui_storage_ensure(bNodeTree &ntree)
{
/* As an optimization, only acquire a lock if the UI storage doesn't exist,
* because it only needs to be allocated once for every node tree. */
if (ntree.ui_storage == nullptr) {
std::lock_guard<std::mutex> lock(global_ui_storage_mutex);
/* Check again-- another thread may have allocated the storage while this one waited. */
if (ntree.ui_storage == nullptr) {
ntree.ui_storage = new NodeTreeUIStorage();
}
}
return *ntree.ui_storage;
}
const NodeUIStorage *BKE_node_tree_ui_storage_get_from_context(const bContext *C,
const bNodeTree &ntree,
const bNode &node)
{
const NodeTreeUIStorage *ui_storage = ntree.ui_storage;
if (ui_storage == nullptr) {
return nullptr;
}
const Object *active_object = CTX_data_active_object(C);
if (active_object == nullptr) {
return nullptr;
}
const ModifierData *active_modifier = BKE_object_active_modifier(active_object);
if (active_modifier == nullptr) {
return nullptr;
}
const NodeTreeEvaluationContext context(*active_object, *active_modifier);
const Map<std::string, NodeUIStorage> *storage = ui_storage->context_map.lookup_ptr(context);
if (storage == nullptr) {
return nullptr;
}
return storage->lookup_ptr_as(StringRef(node.name));
}
/**
* Removes only the UI data associated with a particular evaluation context. The same node tree
* can be used for execution in multiple places, but the entire UI storage can't be removed when
* one execution starts, or all of the data associated with the node tree would be lost.
*/
void BKE_nodetree_ui_storage_free_for_context(bNodeTree &ntree,
const NodeTreeEvaluationContext &context)
{
NodeTreeUIStorage *ui_storage = ntree.ui_storage;
if (ui_storage != nullptr) {
std::lock_guard<std::mutex> lock(ui_storage->mutex);
ui_storage->context_map.remove(context);
}
}
static void node_error_message_log(bNodeTree &ntree,
const bNode &node,
const StringRef message,
const NodeWarningType type)
{
switch (type) {
case NodeWarningType::Error:
CLOG_ERROR(&LOG,
"Node Tree: \"%s\", Node: \"%s\", %s",
ntree.id.name + 2,
node.name,
message.data());
break;
case NodeWarningType::Warning:
CLOG_WARN(&LOG,
"Node Tree: \"%s\", Node: \"%s\", %s",
ntree.id.name + 2,
node.name,
message.data());
break;
case NodeWarningType::Info:
CLOG_INFO(&LOG,
2,
"Node Tree: \"%s\", Node: \"%s\", %s",
ntree.id.name + 2,
node.name,
message.data());
break;
}
}
static NodeUIStorage &node_ui_storage_ensure(NodeTreeUIStorage &locked_ui_storage,
const NodeTreeEvaluationContext &context,
const bNode &node)
{
Map<std::string, NodeUIStorage> &node_tree_ui_storage =
locked_ui_storage.context_map.lookup_or_add_default(context);
NodeUIStorage &node_ui_storage = node_tree_ui_storage.lookup_or_add_default_as(
StringRef(node.name));
return node_ui_storage;
}
void BKE_nodetree_error_message_add(bNodeTree &ntree,
const NodeTreeEvaluationContext &context,
const bNode &node,
const NodeWarningType type,
std::string message)
{
NodeTreeUIStorage &ui_storage = ui_storage_ensure(ntree);
std::lock_guard lock{ui_storage.mutex};
node_error_message_log(ntree, node, message, type);
NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ui_storage, context, node);
node_ui_storage.warnings.append({type, std::move(message)});
}
void BKE_nodetree_attribute_hint_add(bNodeTree &ntree,
const NodeTreeEvaluationContext &context,
const bNode &node,
const StringRef attribute_name,
const AttributeDomain domain,
const CustomDataType data_type)
{
NodeTreeUIStorage &ui_storage = ui_storage_ensure(ntree);
std::lock_guard lock{ui_storage.mutex};
NodeUIStorage &node_ui_storage = node_ui_storage_ensure(ui_storage, context, node);
node_ui_storage.attribute_hints.add_as(
AvailableAttributeInfo{attribute_name, domain, data_type});
}

View File

@ -1760,10 +1760,6 @@ void BKE_object_free_derived_caches(Object *ob)
BKE_geometry_set_free(ob->runtime.geometry_set_eval);
ob->runtime.geometry_set_eval = NULL;
}
if (ob->runtime.geometry_set_previews != NULL) {
BLI_ghash_free(ob->runtime.geometry_set_previews, NULL, (GHashValFreeFP)BKE_geometry_set_free);
ob->runtime.geometry_set_previews = NULL;
}
}
void BKE_object_free_caches(Object *object)
@ -1814,24 +1810,6 @@ void BKE_object_free_caches(Object *object)
}
}
/* Can be called from multiple threads. */
void BKE_object_preview_geometry_set_add(Object *ob,
const uint64_t key,
struct GeometrySet *geometry_set)
{
static ThreadMutex mutex = BLI_MUTEX_INITIALIZER;
BLI_mutex_lock(&mutex);
if (ob->runtime.geometry_set_previews == NULL) {
ob->runtime.geometry_set_previews = BLI_ghash_int_new(__func__);
}
BLI_ghash_reinsert(ob->runtime.geometry_set_previews,
POINTER_FROM_UINT(key),
geometry_set,
NULL,
(GHashValFreeFP)BKE_geometry_set_free);
BLI_mutex_unlock(&mutex);
}
/**
* Actual check for internal data, not context or flags.
*/

View File

@ -24,6 +24,7 @@ set(INC
../../compositor
../../depsgraph
../../draw
../../functions
../../gpu
../../imbuf
../../makesdna
@ -78,4 +79,20 @@ if(WITH_OPENSUBDIV)
add_definitions(-DWITH_OPENSUBDIV)
endif()
if(WITH_TBB)
add_definitions(-DWITH_TBB)
if(WIN32)
# TBB includes Windows.h which will define min/max macros
# that will collide with the stl versions.
add_definitions(-DNOMINMAX)
endif()
list(APPEND INC_SYS
${TBB_INCLUDE_DIRS}
)
list(APPEND LIB
${TBB_LIBRARIES}
)
endif()
blender_add_lib(bf_editor_space_node "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -48,7 +48,6 @@
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_node.h"
#include "BKE_node_ui_storage.hh"
#include "BKE_object.h"
#include "DEG_depsgraph.h"
@ -76,6 +75,8 @@
#include "RNA_access.h"
#include "NOD_geometry_nodes_eval_log.hh"
#include "node_intern.h" /* own include */
#ifdef WITH_COMPOSITOR
@ -86,6 +87,9 @@ using blender::Map;
using blender::Set;
using blender::Span;
using blender::Vector;
using blender::fn::CPPType;
using blender::fn::GPointer;
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
extern "C" {
/* XXX interface.h */
@ -1185,14 +1189,14 @@ void node_draw_sockets(const View2D *v2d,
}
}
static int node_error_type_to_icon(const NodeWarningType type)
static int node_error_type_to_icon(const geo_log::NodeWarningType type)
{
switch (type) {
case NodeWarningType::Error:
case geo_log::NodeWarningType::Error:
return ICON_ERROR;
case NodeWarningType::Warning:
case geo_log::NodeWarningType::Warning:
return ICON_ERROR;
case NodeWarningType::Info:
case geo_log::NodeWarningType::Info:
return ICON_INFO;
}
@ -1200,14 +1204,14 @@ static int node_error_type_to_icon(const NodeWarningType type)
return ICON_ERROR;
}
static uint8_t node_error_type_priority(const NodeWarningType type)
static uint8_t node_error_type_priority(const geo_log::NodeWarningType type)
{
switch (type) {
case NodeWarningType::Error:
case geo_log::NodeWarningType::Error:
return 3;
case NodeWarningType::Warning:
case geo_log::NodeWarningType::Warning:
return 2;
case NodeWarningType::Info:
case geo_log::NodeWarningType::Info:
return 1;
}
@ -1215,11 +1219,11 @@ static uint8_t node_error_type_priority(const NodeWarningType type)
return 0;
}
static NodeWarningType node_error_highest_priority(Span<NodeWarning> warnings)
static geo_log::NodeWarningType node_error_highest_priority(Span<geo_log::NodeWarning> warnings)
{
uint8_t highest_priority = 0;
NodeWarningType highest_priority_type = NodeWarningType::Info;
for (const NodeWarning &warning : warnings) {
geo_log::NodeWarningType highest_priority_type = geo_log::NodeWarningType::Info;
for (const geo_log::NodeWarning &warning : warnings) {
const uint8_t priority = node_error_type_priority(warning.type);
if (priority > highest_priority) {
highest_priority = priority;
@ -1229,15 +1233,17 @@ static NodeWarningType node_error_highest_priority(Span<NodeWarning> warnings)
return highest_priority_type;
}
struct NodeErrorsTooltipData {
Span<geo_log::NodeWarning> warnings;
};
static char *node_errors_tooltip_fn(bContext *UNUSED(C), void *argN, const char *UNUSED(tip))
{
const NodeUIStorage **storage_pointer_alloc = static_cast<const NodeUIStorage **>(argN);
const NodeUIStorage *node_ui_storage = *storage_pointer_alloc;
Span<NodeWarning> warnings = node_ui_storage->warnings;
NodeErrorsTooltipData &data = *(NodeErrorsTooltipData *)argN;
std::string complete_string;
for (const NodeWarning &warning : warnings.drop_back(1)) {
for (const geo_log::NodeWarning &warning : data.warnings.drop_back(1)) {
complete_string += warning.message;
/* Adding the period is not ideal for multi-line messages, but it is consistent
* with other tooltip implementations in Blender, so it is added here. */
@ -1246,7 +1252,7 @@ static char *node_errors_tooltip_fn(bContext *UNUSED(C), void *argN, const char
}
/* Let the tooltip system automatically add the last period. */
complete_string += warnings.last().message;
complete_string += data.warnings.last().message;
return BLI_strdupn(complete_string.c_str(), complete_string.size());
}
@ -1254,20 +1260,26 @@ static char *node_errors_tooltip_fn(bContext *UNUSED(C), void *argN, const char
#define NODE_HEADER_ICON_SIZE (0.8f * U.widget_unit)
static void node_add_error_message_button(
const bContext *C, bNodeTree &ntree, bNode &node, const rctf &rect, float &icon_offset)
const bContext *C, bNodeTree &UNUSED(ntree), bNode &node, const rctf &rect, float &icon_offset)
{
const NodeUIStorage *node_ui_storage = BKE_node_tree_ui_storage_get_from_context(C, ntree, node);
if (node_ui_storage == nullptr || node_ui_storage->warnings.is_empty()) {
SpaceNode *snode = CTX_wm_space_node(C);
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(*snode,
node);
if (node_log == nullptr) {
return;
}
/* The UI API forces us to allocate memory for each error button, because the
* ownership of #UI_but_func_tooltip_set's argument is transferred to the button. */
const NodeUIStorage **storage_pointer_alloc = (const NodeUIStorage **)MEM_mallocN(
sizeof(NodeUIStorage *), __func__);
*storage_pointer_alloc = node_ui_storage;
Span<geo_log::NodeWarning> warnings = node_log->warnings();
const NodeWarningType display_type = node_error_highest_priority(node_ui_storage->warnings);
if (warnings.is_empty()) {
return;
}
NodeErrorsTooltipData *tooltip_data = (NodeErrorsTooltipData *)MEM_mallocN(
sizeof(NodeErrorsTooltipData), __func__);
tooltip_data->warnings = warnings;
const geo_log::NodeWarningType display_type = node_error_highest_priority(warnings);
icon_offset -= NODE_HEADER_ICON_SIZE;
UI_block_emboss_set(node.block, UI_EMBOSS_NONE);
@ -1285,7 +1297,7 @@ static void node_add_error_message_button(
0,
0,
nullptr);
UI_but_func_tooltip_set(but, node_errors_tooltip_fn, storage_pointer_alloc, MEM_freeN);
UI_but_func_tooltip_set(but, node_errors_tooltip_fn, tooltip_data, MEM_freeN);
UI_block_emboss_set(node.block, UI_EMBOSS);
}

View File

@ -27,7 +27,6 @@
#include "DNA_space_types.h"
#include "BKE_context.h"
#include "BKE_node_ui_storage.hh"
#include "BKE_object.h"
#include "RNA_access.h"
@ -40,17 +39,21 @@
#include "UI_interface.h"
#include "UI_resources.h"
#include "NOD_geometry_nodes_eval_log.hh"
#include "node_intern.h"
using blender::IndexRange;
using blender::Map;
using blender::Set;
using blender::StringRef;
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
using geo_log::GeometryAttributeInfo;
struct AttributeSearchData {
AvailableAttributeInfo &dummy_info_for_search;
const NodeUIStorage &ui_storage;
bNodeSocket &socket;
const bNodeTree *tree;
const bNode *node;
bNodeSocket *socket;
};
/* This class must not have a destructor, since it is used by buttons and freed with #MEM_freeN. */
@ -73,7 +76,7 @@ static StringRef attribute_domain_string(const AttributeDomain domain)
/* Unicode arrow. */
#define MENU_SEP "\xe2\x96\xb6"
static bool attribute_search_item_add(uiSearchItems *items, const AvailableAttributeInfo &item)
static bool attribute_search_item_add(uiSearchItems *items, const GeometryAttributeInfo &item)
{
const StringRef data_type_name = attribute_data_type_string(item.data_type);
const StringRef domain_name = attribute_domain_string(item.domain);
@ -84,31 +87,47 @@ static bool attribute_search_item_add(uiSearchItems *items, const AvailableAttri
items, search_item_text.c_str(), (void *)&item, ICON_NONE, UI_BUT_HAS_SEP_CHAR, 0);
}
static void attribute_search_update_fn(const bContext *UNUSED(C),
void *arg,
const char *str,
uiSearchItems *items,
const bool is_first)
static GeometryAttributeInfo &get_dummy_item_info()
{
static GeometryAttributeInfo info;
return info;
}
static void attribute_search_update_fn(
const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
{
AttributeSearchData *data = static_cast<AttributeSearchData *>(arg);
const Set<AvailableAttributeInfo> &attribute_hints = data->ui_storage.attribute_hints;
SpaceNode *snode = CTX_wm_space_node(C);
const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_node_editor_context(
*snode, *data->node);
if (node_log == nullptr) {
return;
}
blender::Vector<const GeometryAttributeInfo *> infos = node_log->lookup_available_attributes();
GeometryAttributeInfo &dummy_info = get_dummy_item_info();
/* Any string may be valid, so add the current search string along with the hints. */
if (str[0] != '\0') {
/* Note that the attribute domain and data type are dummies, since
* #AvailableAttributeInfo equality is only based on the string. */
if (!attribute_hints.contains(AvailableAttributeInfo{str, ATTR_DOMAIN_AUTO, CD_PROP_BOOL})) {
data->dummy_info_for_search.name = std::string(str);
UI_search_item_add(items, str, &data->dummy_info_for_search, ICON_ADD, 0, 0);
bool contained = false;
for (const GeometryAttributeInfo *attribute_info : infos) {
if (attribute_info->name == str) {
contained = true;
break;
}
}
if (!contained) {
dummy_info.name = str;
UI_search_item_add(items, str, &dummy_info, ICON_ADD, 0, 0);
}
}
if (str[0] == '\0' && !is_first) {
/* Allow clearing the text field when the string is empty, but not on the first pass,
* or opening an attribute field for the first time would show this search item. */
data->dummy_info_for_search.name = std::string(str);
UI_search_item_add(items, str, &data->dummy_info_for_search, ICON_X, 0, 0);
dummy_info.name = str;
UI_search_item_add(items, str, &dummy_info, ICON_X, 0, 0);
}
/* Don't filter when the menu is first opened, but still run the search
@ -116,15 +135,15 @@ static void attribute_search_update_fn(const bContext *UNUSED(C),
const char *string = is_first ? "" : str;
StringSearch *search = BLI_string_search_new();
for (const AvailableAttributeInfo &item : attribute_hints) {
BLI_string_search_add(search, item.name.c_str(), (void *)&item);
for (const GeometryAttributeInfo *item : infos) {
BLI_string_search_add(search, item->name.c_str(), (void *)item);
}
AvailableAttributeInfo **filtered_items;
GeometryAttributeInfo **filtered_items;
const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items);
for (const int i : IndexRange(filtered_amount)) {
const AvailableAttributeInfo *item = filtered_items[i];
const GeometryAttributeInfo *item = filtered_items[i];
if (!attribute_search_item_add(items, *item)) {
break;
}
@ -137,31 +156,21 @@ static void attribute_search_update_fn(const bContext *UNUSED(C),
static void attribute_search_exec_fn(bContext *C, void *data_v, void *item_v)
{
AttributeSearchData *data = static_cast<AttributeSearchData *>(data_v);
AvailableAttributeInfo *item = static_cast<AvailableAttributeInfo *>(item_v);
GeometryAttributeInfo *item = (GeometryAttributeInfo *)item_v;
bNodeSocket &socket = data->socket;
bNodeSocket &socket = *data->socket;
bNodeSocketValueString *value = static_cast<bNodeSocketValueString *>(socket.default_value);
BLI_strncpy(value->value, item->name.c_str(), MAX_NAME);
ED_undo_push(C, "Assign Attribute Name");
}
void node_geometry_add_attribute_search_button(const bContext *C,
void node_geometry_add_attribute_search_button(const bContext *UNUSED(C),
const bNodeTree *node_tree,
const bNode *node,
PointerRNA *socket_ptr,
uiLayout *layout)
{
const NodeUIStorage *ui_storage = BKE_node_tree_ui_storage_get_from_context(
C, *node_tree, *node);
if (ui_storage == nullptr) {
uiItemR(layout, socket_ptr, "default_value", 0, "", 0);
return;
}
const NodeTreeUIStorage *tree_ui_storage = node_tree->ui_storage;
uiBlock *block = uiLayoutGetBlock(layout);
uiBut *but = uiDefIconTextButR(block,
UI_BTYPE_SEARCH_MENU,
@ -181,10 +190,8 @@ void node_geometry_add_attribute_search_button(const bContext *C,
0.0f,
"");
AttributeSearchData *data = OBJECT_GUARDED_NEW(AttributeSearchData,
{tree_ui_storage->dummy_info_for_search,
*ui_storage,
*static_cast<bNodeSocket *>(socket_ptr->data)});
AttributeSearchData *data = OBJECT_GUARDED_NEW(
AttributeSearchData, {node_tree, node, (bNodeSocket *)socket_ptr->data});
UI_but_func_search_set_results_are_suggestions(but, true);
UI_but_func_search_set_sep_string(but, MENU_SEP);

View File

@ -27,6 +27,7 @@ set(INC
../../gpu
../../makesdna
../../makesrna
../../nodes
../../windowmanager
../../../../intern/glew-mx
../../../../intern/guardedalloc

View File

@ -31,11 +31,15 @@
#include "ED_spreadsheet.h"
#include "NOD_geometry_nodes_eval_log.hh"
#include "bmesh.h"
#include "spreadsheet_data_source_geometry.hh"
#include "spreadsheet_intern.hh"
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
namespace blender::ed::spreadsheet {
void GeometryDataSource::foreach_default_column_ids(
@ -438,13 +442,18 @@ GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspread
}
}
else {
if (object_eval->runtime.geometry_set_previews != nullptr) {
GHash *ghash = (GHash *)object_eval->runtime.geometry_set_previews;
const uint64_t key = ED_spreadsheet_context_path_hash(sspreadsheet);
GeometrySet *geometry_set_preview = (GeometrySet *)BLI_ghash_lookup_default(
ghash, POINTER_FROM_UINT(key), nullptr);
if (geometry_set_preview != nullptr) {
geometry_set = *geometry_set_preview;
const geo_log::NodeLog *node_log =
geo_log::ModifierLog::find_node_by_spreadsheet_editor_context(*sspreadsheet);
if (node_log != nullptr) {
for (const geo_log::SocketLog &input_log : node_log->input_logs()) {
if (const geo_log::GeometryValueLog *geo_value_log =
dynamic_cast<const geo_log::GeometryValueLog *>(input_log.value())) {
const GeometrySet *full_geometry = geo_value_log->full_geometry();
if (full_geometry != nullptr) {
geometry_set = *full_geometry;
break;
}
}
}
}
}

View File

@ -2256,6 +2256,10 @@ typedef struct NodesModifierData {
ModifierData modifier;
struct bNodeTree *node_group;
struct NodesModifierSettings settings;
/* Contains logged information from the last evaluation. This can be used to help the user to
* debug a node tree. */
void *runtime_eval_log;
} NodesModifierData;
typedef struct MeshToVolumeModifierData {

View File

@ -37,7 +37,6 @@ struct Collection;
struct ID;
struct Image;
struct ListBase;
struct NodeTreeUIStorage;
struct bGPdata;
struct bNodeInstanceHash;
struct bNodeLink;
@ -516,8 +515,6 @@ typedef struct bNodeTree {
int (*test_break)(void *);
void (*update_draw)(void *);
void *tbh, *prh, *sdh, *udh;
struct NodeTreeUIStorage *ui_storage;
} bNodeTree;
/* ntree->type, index */

View File

@ -171,12 +171,6 @@ typedef struct Object_Runtime {
*/
struct GeometrySet *geometry_set_eval;
/**
* A GHash that contains geometry sets for intermediate stages of evaluation. The keys are just a
* hash and are not owned by the map. The geometry sets are owned.
*/
void *geometry_set_previews;
/**
* Mesh structure created during object evaluation.
* It has deformation only modifiers applied on it.

View File

@ -48,6 +48,7 @@
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h"
#include "BKE_attribute_math.hh"
#include "BKE_customdata.h"
#include "BKE_geometry_set_instances.hh"
#include "BKE_global.h"
@ -56,7 +57,6 @@
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_node_ui_storage.hh"
#include "BKE_object.h"
#include "BKE_pointcloud.h"
#include "BKE_screen.h"
@ -83,8 +83,10 @@
#include "NOD_derived_node_tree.hh"
#include "NOD_geometry.h"
#include "NOD_geometry_nodes_eval_log.hh"
#include "NOD_node_tree_multi_function.hh"
using blender::destruct_ptr;
using blender::float3;
using blender::FunctionRef;
using blender::IndexRange;
@ -97,6 +99,7 @@ using blender::Vector;
using blender::fn::GMutablePointer;
using blender::fn::GPointer;
using blender::nodes::GeoNodeExecParams;
using blender::threading::EnumerableThreadSpecific;
using namespace blender::fn::multi_function_types;
using namespace blender::nodes::derived_node_tree_types;
@ -734,19 +737,6 @@ static void initialize_group_input(NodesModifierData &nmd,
property_type->init_cpp_value(*property, r_value);
}
static void reset_tree_ui_storage(Span<const blender::nodes::NodeTreeRef *> trees,
const Object &object,
const ModifierData &modifier)
{
const NodeTreeEvaluationContext context = {object, modifier};
for (const blender::nodes::NodeTreeRef *tree : trees) {
bNodeTree *btree_cow = tree->btree();
bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow);
BKE_nodetree_ui_storage_free_for_context(*btree_original, context);
}
}
static Vector<SpaceSpreadsheet *> find_spreadsheet_editors(Main *bmain)
{
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
@ -766,8 +756,6 @@ static Vector<SpaceSpreadsheet *> find_spreadsheet_editors(Main *bmain)
return spreadsheets;
}
using PreviewSocketMap = blender::MultiValueMap<DSocket, uint64_t>;
static DSocket try_get_socket_to_preview_for_spreadsheet(SpaceSpreadsheet *sspreadsheet,
NodesModifierData *nmd,
const ModifierEvalContext *ctx,
@ -821,19 +809,10 @@ static DSocket try_get_socket_to_preview_for_spreadsheet(SpaceSpreadsheet *sspre
}
const NodeTreeRef &tree_ref = context->tree();
for (const NodeRef *node_ref : tree_ref.nodes()) {
for (const NodeRef *node_ref : tree_ref.nodes_by_type("GeometryNodeViewer")) {
if (node_ref->name() == last_context->node_name) {
const DNode viewer_node{context, node_ref};
DSocket socket_to_view;
viewer_node.input(0).foreach_origin_socket(
[&](const DSocket socket) { socket_to_view = socket; });
if (!socket_to_view) {
return {};
}
bNodeSocket *bsocket = socket_to_view->bsocket();
if (bsocket->type == SOCK_GEOMETRY && bsocket->flag != SOCK_MULTI_INPUT) {
return socket_to_view;
}
return viewer_node.input(0);
}
}
return {};
@ -842,7 +821,7 @@ static DSocket try_get_socket_to_preview_for_spreadsheet(SpaceSpreadsheet *sspre
static void find_sockets_to_preview(NodesModifierData *nmd,
const ModifierEvalContext *ctx,
const DerivedNodeTree &tree,
PreviewSocketMap &r_sockets_to_preview)
Set<DSocket> &r_sockets_to_preview)
{
Main *bmain = DEG_get_bmain(ctx->depsgraph);
@ -852,51 +831,16 @@ static void find_sockets_to_preview(NodesModifierData *nmd,
for (SpaceSpreadsheet *sspreadsheet : spreadsheets) {
const DSocket socket = try_get_socket_to_preview_for_spreadsheet(sspreadsheet, nmd, ctx, tree);
if (socket) {
const uint64_t key = ED_spreadsheet_context_path_hash(sspreadsheet);
r_sockets_to_preview.add_non_duplicates(socket, key);
r_sockets_to_preview.add(socket);
}
}
}
static void log_preview_socket_value(const Span<GPointer> values,
Object *object,
Span<uint64_t> keys)
static void clear_runtime_data(NodesModifierData *nmd)
{
GeometrySet geometry_set = *(const GeometrySet *)values[0].get();
geometry_set.ensure_owns_direct_data();
for (uint64_t key : keys) {
BKE_object_preview_geometry_set_add(object, key, new GeometrySet(geometry_set));
}
}
static void log_ui_hints(const DSocket socket,
const Span<GPointer> values,
Object *self_object,
NodesModifierData *nmd)
{
const DNode node = socket.node();
if (node->is_reroute_node() || socket->typeinfo()->type != SOCK_GEOMETRY) {
return;
}
bNodeTree *btree_cow = node->btree();
bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow);
const NodeTreeEvaluationContext context{*self_object, nmd->modifier};
for (const GPointer &data : values) {
if (data.type() == &CPPType::get<GeometrySet>()) {
const GeometrySet &geometry_set = *(const GeometrySet *)data.get();
blender::bke::geometry_set_instances_attribute_foreach(
geometry_set,
[&](StringRefNull attribute_name, const AttributeMetaData &meta_data) {
BKE_nodetree_attribute_hint_add(*btree_original,
context,
*node->bnode(),
attribute_name,
meta_data.domain,
meta_data.data_type);
return true;
},
8);
}
if (nmd->runtime_eval_log != nullptr) {
delete (geo_log::ModifierLog *)nmd->runtime_eval_log;
nmd->runtime_eval_log = nullptr;
}
}
@ -952,32 +896,32 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree,
Vector<DInputSocket> group_outputs;
group_outputs.append({root_context, &socket_to_compute});
PreviewSocketMap preview_sockets;
find_sockets_to_preview(nmd, ctx, tree, preview_sockets);
auto log_socket_value = [&](const DSocket socket, const Span<GPointer> values) {
if (!logging_enabled(ctx)) {
return;
}
Span<uint64_t> keys = preview_sockets.lookup(socket);
if (!keys.is_empty()) {
log_preview_socket_value(values, ctx->object, keys);
}
log_ui_hints(socket, values, ctx->object, nmd);
};
std::optional<geo_log::GeoLogger> geo_logger;
blender::modifiers::geometry_nodes::GeometryNodesEvaluationParams eval_params;
if (logging_enabled(ctx)) {
Set<DSocket> preview_sockets;
find_sockets_to_preview(nmd, ctx, tree, preview_sockets);
eval_params.force_compute_sockets.extend(preview_sockets.begin(), preview_sockets.end());
geo_logger.emplace(std::move(preview_sockets));
}
eval_params.input_values = group_inputs;
eval_params.output_sockets = group_outputs;
eval_params.force_compute_sockets.extend(preview_sockets.keys().begin(),
preview_sockets.keys().end());
eval_params.mf_by_node = &mf_by_node;
eval_params.modifier_ = nmd;
eval_params.depsgraph = ctx->depsgraph;
eval_params.self_object = ctx->object;
eval_params.log_socket_value_fn = log_socket_value;
eval_params.geo_logger = geo_logger.has_value() ? &*geo_logger : nullptr;
blender::modifiers::geometry_nodes::evaluate_geometry_nodes(eval_params);
if (geo_logger.has_value()) {
NodesModifierData *nmd_orig = (NodesModifierData *)BKE_modifier_get_original(&nmd->modifier);
clear_runtime_data(nmd_orig);
nmd_orig->runtime_eval_log = new geo_log::ModifierLog(*geo_logger);
}
BLI_assert(eval_params.r_output_values.size() == 1);
GMutablePointer result = eval_params.r_output_values[0];
return result.relocate_out<GeometrySet>();
@ -1067,10 +1011,6 @@ static void modifyGeometry(ModifierData *md,
return;
}
if (logging_enabled(ctx)) {
reset_tree_ui_storage(tree.used_node_tree_refs(), *ctx->object, *md);
}
geometry_set = compute_geometry(
tree, input_nodes, *group_outputs[0], std::move(geometry_set), nmd, ctx);
}
@ -1215,6 +1155,7 @@ static void blendRead(BlendDataReader *reader, ModifierData *md)
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
BLO_read_data_address(reader, &nmd->settings.properties);
IDP_BlendDataRead(reader, &nmd->settings.properties);
nmd->runtime_eval_log = nullptr;
}
static void copyData(const ModifierData *md, ModifierData *target, const int flag)
@ -1224,6 +1165,8 @@ static void copyData(const ModifierData *md, ModifierData *target, const int fla
BKE_modifier_copydata_generic(md, target, flag);
tnmd->runtime_eval_log = nullptr;
if (nmd->settings.properties != nullptr) {
tnmd->settings.properties = IDP_CopyProperty_ex(nmd->settings.properties, flag);
}
@ -1236,6 +1179,8 @@ static void freeData(ModifierData *md)
IDP_FreeProperty_ex(nmd->settings.properties, false);
nmd->settings.properties = nullptr;
}
clear_runtime_data(nmd);
}
static void requiredDataMask(Object *UNUSED(ob),

View File

@ -558,11 +558,11 @@ class GeometryNodesEvaluator {
for (auto &&item : params_.input_values.items()) {
const DOutputSocket socket = item.key;
GMutablePointer value = item.value;
this->log_socket_value(socket, value);
const DNode node = socket.node();
if (!node_states_.contains_as(node)) {
/* The socket is not connected to any output. */
this->log_socket_value({socket}, value);
value.destruct();
continue;
}
@ -780,7 +780,6 @@ class GeometryNodesEvaluator {
/* Checks if all the linked sockets have been provided already. */
if (multi_value.items.size() == multi_value.expected_size) {
input_state.was_ready_for_execution = true;
this->log_socket_value(socket, input_state, multi_value.items);
}
else if (is_required) {
/* The input is required but is not fully provided yet. Therefore the node cannot be
@ -792,7 +791,6 @@ class GeometryNodesEvaluator {
SingleInputValue &single_value = *input_state.value.single;
if (single_value.value != nullptr) {
input_state.was_ready_for_execution = true;
this->log_socket_value(socket, GPointer{input_state.type, single_value.value});
}
else if (is_required) {
/* The input is required but has not been provided yet. Therefore the node cannot be
@ -1170,6 +1168,9 @@ class GeometryNodesEvaluator {
{
BLI_assert(value_to_forward.get() != nullptr);
Vector<DSocket> sockets_to_log_to;
sockets_to_log_to.append(from_socket);
Vector<DInputSocket> to_sockets;
auto handle_target_socket_fn = [&, this](const DInputSocket to_socket) {
if (this->should_forward_to_socket(to_socket)) {
@ -1177,9 +1178,7 @@ class GeometryNodesEvaluator {
}
};
auto handle_skipped_socket_fn = [&, this](const DSocket socket) {
/* Log socket value on intermediate sockets to support e.g. attribute search or spreadsheet
* breadcrumbs on group nodes. */
this->log_socket_value(socket, value_to_forward);
sockets_to_log_to.append(socket);
};
from_socket.foreach_target_socket(handle_target_socket_fn, handle_skipped_socket_fn);
@ -1192,11 +1191,18 @@ class GeometryNodesEvaluator {
if (from_type == to_type) {
/* All target sockets that do not need a conversion will be handled afterwards. */
to_sockets_same_type.append(to_socket);
/* Multi input socket values are logged once all values are available. */
if (!to_socket->is_multi_input_socket()) {
sockets_to_log_to.append(to_socket);
}
continue;
}
this->forward_to_socket_with_different_type(
allocator, value_to_forward, from_socket, to_socket, to_type);
}
this->log_socket_value(sockets_to_log_to, value_to_forward);
this->forward_to_sockets_with_same_type(
allocator, to_sockets_same_type, value_to_forward, from_socket);
}
@ -1227,6 +1233,7 @@ class GeometryNodesEvaluator {
/* Allocate a buffer for the converted value. */
void *buffer = allocator.allocate(to_type.size(), to_type.alignment());
GMutablePointer value{to_type, buffer};
if (conversions_.is_convertible(from_type, to_type)) {
/* Do the conversion if possible. */
@ -1236,7 +1243,11 @@ class GeometryNodesEvaluator {
/* Cannot convert, use default value instead. */
to_type.copy_construct(to_type.default_value(), buffer);
}
this->add_value_to_input_socket(to_socket, from_socket, {to_type, buffer});
/* Multi input socket values are logged once all values are available. */
if (!to_socket->is_multi_input_socket()) {
this->log_socket_value({to_socket}, value);
}
this->add_value_to_input_socket(to_socket, from_socket, value);
}
void forward_to_sockets_with_same_type(LinearAllocator<> &allocator,
@ -1284,6 +1295,10 @@ class GeometryNodesEvaluator {
/* Add a new value to the multi-input. */
MultiInputValue &multi_value = *input_state.value.multi;
multi_value.items.append({origin, value.get()});
if (multi_value.expected_size == multi_value.items.size()) {
this->log_socket_value({socket}, input_state, multi_value.items);
}
}
else {
/* Assign the value to the input. */
@ -1314,10 +1329,14 @@ class GeometryNodesEvaluator {
if (input_socket->is_multi_input_socket()) {
MultiInputValue &multi_value = *input_state.value.multi;
multi_value.items.append({origin_socket, value.get()});
if (multi_value.expected_size == multi_value.items.size()) {
this->log_socket_value({input_socket}, input_state, multi_value.items);
}
}
else {
SingleInputValue &single_value = *input_state.value.single;
single_value.value = value.get();
this->log_socket_value({input_socket}, value);
}
}
@ -1370,29 +1389,27 @@ class GeometryNodesEvaluator {
return *node_states_.lookup_key_as(node).state;
}
void log_socket_value(const DSocket socket, Span<GPointer> values)
void log_socket_value(DSocket socket, InputState &input_state, Span<MultiInputValueItem> values)
{
if (params_.log_socket_value_fn) {
params_.log_socket_value_fn(socket, values);
if (params_.geo_logger == nullptr) {
return;
}
}
void log_socket_value(const DSocket socket,
InputState &input_state,
Span<MultiInputValueItem> values)
{
Vector<GPointer, 16> value_pointers;
value_pointers.reserve(values.size());
const CPPType &type = *input_state.type;
for (const MultiInputValueItem &item : values) {
value_pointers.append({type, item.value});
}
this->log_socket_value(socket, value_pointers);
params_.geo_logger->local().log_multi_value_socket(socket, value_pointers);
}
void log_socket_value(const DSocket socket, GPointer value)
void log_socket_value(Span<DSocket> sockets, GPointer value)
{
this->log_socket_value(socket, Span<GPointer>(&value, 1));
if (params_.geo_logger == nullptr) {
return;
}
params_.geo_logger->local().log_value_for_sockets(sockets, value);
}
/* In most cases when `NodeState` is accessed, the node has to be locked first to avoid race
@ -1431,6 +1448,7 @@ NodeParamsProvider::NodeParamsProvider(GeometryNodesEvaluator &evaluator,
this->self_object = evaluator.params_.self_object;
this->modifier = &evaluator.params_.modifier_->modifier;
this->depsgraph = evaluator.params_.depsgraph;
this->logger = evaluator.params_.geo_logger;
}
bool NodeParamsProvider::can_get_input(StringRef identifier) const
@ -1530,8 +1548,6 @@ void NodeParamsProvider::set_output(StringRef identifier, GMutablePointer value)
const DOutputSocket socket = this->dnode.output_by_identifier(identifier);
BLI_assert(socket);
evaluator_.log_socket_value(socket, value);
OutputState &output_state = node_state_.outputs[socket->index()];
BLI_assert(!output_state.has_been_computed);
evaluator_.forward_output(socket, value);

View File

@ -19,20 +19,21 @@
#include "BLI_map.hh"
#include "NOD_derived_node_tree.hh"
#include "NOD_geometry_nodes_eval_log.hh"
#include "NOD_node_tree_multi_function.hh"
#include "FN_generic_pointer.hh"
#include "DNA_modifier_types.h"
namespace geo_log = blender::nodes::geometry_nodes_eval_log;
namespace blender::modifiers::geometry_nodes {
using namespace nodes::derived_node_tree_types;
using fn::GMutablePointer;
using fn::GPointer;
using LogSocketValueFn = std::function<void(DSocket, Span<GPointer>)>;
struct GeometryNodesEvaluationParams {
blender::LinearAllocator<> allocator;
@ -48,7 +49,7 @@ struct GeometryNodesEvaluationParams {
const NodesModifierData *modifier_;
Depsgraph *depsgraph;
Object *self_object;
LogSocketValueFn log_socket_value_fn;
geo_log::GeoLogger *geo_logger;
Vector<GMutablePointer> r_output_values;
};

View File

@ -165,7 +165,7 @@ set(SRC
geometry/nodes/node_geo_common.cc
geometry/nodes/node_geo_convex_hull.cc
geometry/nodes/node_geo_curve_endpoints.cc
geometry/nodes/node_geo_curve_length.cc
geometry/nodes/node_geo_curve_length.cc
geometry/nodes/node_geo_curve_primitive_bezier_segment.cc
geometry/nodes/node_geo_curve_primitive_circle.cc
geometry/nodes/node_geo_curve_primitive_line.cc
@ -335,6 +335,7 @@ set(SRC
texture/node_texture_util.c
intern/derived_node_tree.cc
intern/geometry_nodes_eval_log.cc
intern/math_functions.cc
intern/node_common.c
intern/node_exec.cc
@ -358,6 +359,7 @@ set(SRC
NOD_function.h
NOD_geometry.h
NOD_geometry_exec.hh
NOD_geometry_nodes_eval_log.hh
NOD_math_functions.hh
NOD_node_tree_multi_function.hh
NOD_node_tree_ref.hh

View File

@ -21,11 +21,11 @@
#include "BKE_attribute_access.hh"
#include "BKE_geometry_set.hh"
#include "BKE_geometry_set_instances.hh"
#include "BKE_node_ui_storage.hh"
#include "DNA_node_types.h"
#include "NOD_derived_node_tree.hh"
#include "NOD_geometry_nodes_eval_log.hh"
struct Depsgraph;
struct ModifierData;
@ -52,10 +52,11 @@ using fn::GVMutableArray;
using fn::GVMutableArray_GSpan;
using fn::GVMutableArray_Typed;
using fn::GVMutableArrayPtr;
using geometry_nodes_eval_log::NodeWarningType;
/**
* This class exists to separate the memory management details of the geometry nodes evaluator from
* the node execution functions and related utilities.
* This class exists to separate the memory management details of the geometry nodes evaluator
* from the node execution functions and related utilities.
*/
class GeoNodeExecParamsProvider {
public:
@ -63,6 +64,7 @@ class GeoNodeExecParamsProvider {
const Object *self_object = nullptr;
const ModifierData *modifier = nullptr;
Depsgraph *depsgraph = nullptr;
geometry_nodes_eval_log::GeoLogger *logger = nullptr;
/**
* Returns true when the node is allowed to get/extract the input value. The identifier is

View File

@ -0,0 +1,291 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/**
* Many geometry nodes related ui features need access to data produced during evaluation. Not only
* is the final output required but also the intermediate results. Those features include
* attribute search, node warnings, socket inspection and the viewer node.
*
* This file provides the framework for logging data during evaluation and accessing the data after
* evaluation.
*
* During logging every thread gets its own local logger to avoid too much locking (logging
* generally happens for every socket). After geometry nodes evaluation is done, the threadlocal
* logging information is combined and postprocessed to make it easier for the ui to lookup
* necessary information.
*/
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_linear_allocator.hh"
#include "BLI_map.hh"
#include "BKE_geometry_set.hh"
#include "FN_generic_pointer.hh"
#include "NOD_derived_node_tree.hh"
struct SpaceNode;
struct SpaceSpreadsheet;
namespace blender::nodes::geometry_nodes_eval_log {
using fn::GMutablePointer;
using fn::GPointer;
/** Contains information about a value that has been computed during geometry nodes evaluation. */
class ValueLog {
public:
virtual ~ValueLog() = default;
};
/** Contains an owned copy of a value of a generic type. */
class GenericValueLog : public ValueLog {
private:
GMutablePointer data_;
public:
GenericValueLog(GMutablePointer data) : data_(data)
{
}
~GenericValueLog()
{
data_.destruct();
}
GPointer value() const
{
return data_;
}
};
struct GeometryAttributeInfo {
std::string name;
AttributeDomain domain;
CustomDataType data_type;
};
/** Contains information about a geometry set. In most cases this does not store the entire
* geometry set as this would require too much memory. */
class GeometryValueLog : public ValueLog {
private:
Vector<GeometryAttributeInfo> attributes_;
Vector<GeometryComponentType> component_types_;
std::unique_ptr<GeometrySet> full_geometry_;
public:
GeometryValueLog(const GeometrySet &geometry_set, bool log_full_geometry);
Span<GeometryAttributeInfo> attributes() const
{
return attributes_;
}
Span<GeometryComponentType> component_types() const
{
return component_types_;
}
const GeometrySet *full_geometry() const
{
return full_geometry_.get();
}
};
enum class NodeWarningType {
Error,
Warning,
Info,
};
struct NodeWarning {
NodeWarningType type;
std::string message;
};
struct NodeWithWarning {
DNode node;
NodeWarning warning;
};
/** The same value can be referenced by multiple sockets when they are linked. */
struct ValueOfSockets {
Span<DSocket> sockets;
destruct_ptr<ValueLog> value;
};
class GeoLogger;
class ModifierLog;
/** Every thread has its own local logger to avoid having to communicate between threads during
* evaluation. After evaluation the individual logs are combined. */
class LocalGeoLogger {
private:
/* Back pointer to the owner of this local logger. */
GeoLogger *main_logger_;
/* Allocator for the many small allocations during logging. This is in a `unique_ptr` so that
* ownership can be transferred later on. */
std::unique_ptr<LinearAllocator<>> allocator_;
Vector<ValueOfSockets> values_;
Vector<NodeWithWarning> node_warnings_;
friend ModifierLog;
public:
LocalGeoLogger(GeoLogger &main_logger) : main_logger_(&main_logger)
{
this->allocator_ = std::make_unique<LinearAllocator<>>();
}
void log_value_for_sockets(Span<DSocket> sockets, GPointer value);
void log_multi_value_socket(DSocket socket, Span<GPointer> values);
void log_node_warning(DNode node, NodeWarningType type, std::string message);
};
/** The root logger class. */
class GeoLogger {
private:
/** The entire geometry of sockets in this set should be cached, because e.g. the spreadsheet
* displays the data. We don't log the entire geometry at all places, because that would require
* way too much memory. */
Set<DSocket> log_full_geometry_sockets_;
threading::EnumerableThreadSpecific<LocalGeoLogger> threadlocals_;
friend LocalGeoLogger;
public:
GeoLogger(Set<DSocket> log_full_geometry_sockets)
: log_full_geometry_sockets_(std::move(log_full_geometry_sockets)),
threadlocals_([this]() { return LocalGeoLogger(*this); })
{
}
LocalGeoLogger &local()
{
return threadlocals_.local();
}
auto begin()
{
return threadlocals_.begin();
}
auto end()
{
return threadlocals_.end();
}
};
/** Contains information that has been logged for one specific socket. */
class SocketLog {
private:
ValueLog *value_ = nullptr;
friend ModifierLog;
public:
const ValueLog *value() const
{
return value_;
}
};
/** Contains information that has been logged for one specific node. */
class NodeLog {
private:
Vector<SocketLog> input_logs_;
Vector<SocketLog> output_logs_;
Vector<NodeWarning, 0> warnings_;
friend ModifierLog;
public:
const SocketLog *lookup_socket_log(eNodeSocketInOut in_out, int index) const;
const SocketLog *lookup_socket_log(const bNode &node, const bNodeSocket &socket) const;
Span<SocketLog> input_logs() const
{
return input_logs_;
}
Span<SocketLog> output_logs() const
{
return output_logs_;
}
Span<NodeWarning> warnings() const
{
return warnings_;
}
Vector<const GeometryAttributeInfo *> lookup_available_attributes() const;
};
/** Contains information that has been logged for one specific tree. */
class TreeLog {
private:
Map<std::string, destruct_ptr<NodeLog>> node_logs_;
Map<std::string, destruct_ptr<TreeLog>> child_logs_;
friend ModifierLog;
public:
const NodeLog *lookup_node_log(StringRef node_name) const;
const NodeLog *lookup_node_log(const bNode &node) const;
const TreeLog *lookup_child_log(StringRef node_name) const;
};
/** Contains information about an entire geometry nodes evaluation. */
class ModifierLog {
private:
LinearAllocator<> allocator_;
/* Allocators of the individual loggers. */
Vector<std::unique_ptr<LinearAllocator<>>> logger_allocators_;
destruct_ptr<TreeLog> root_tree_logs_;
Vector<destruct_ptr<ValueLog>> logged_values_;
public:
ModifierLog(GeoLogger &logger);
const TreeLog &root_tree() const
{
return *root_tree_logs_;
}
/* Utilities to find logged informatiton for a specific context. */
static const ModifierLog *find_root_by_node_editor_context(const SpaceNode &snode);
static const TreeLog *find_tree_by_node_editor_context(const SpaceNode &snode);
static const NodeLog *find_node_by_node_editor_context(const SpaceNode &snode,
const bNode &node);
static const SocketLog *find_socket_by_node_editor_context(const SpaceNode &snode,
const bNode &node,
const bNodeSocket &socket);
static const NodeLog *find_node_by_spreadsheet_editor_context(
const SpaceSpreadsheet &sspreadsheet);
private:
using LogByTreeContext = Map<const DTreeContext *, TreeLog *>;
TreeLog &lookup_or_add_tree_log(LogByTreeContext &log_by_tree_context,
const DTreeContext &tree_context);
NodeLog &lookup_or_add_node_log(LogByTreeContext &log_by_tree_context, DNode node);
SocketLog &lookup_or_add_socket_log(LogByTreeContext &log_by_tree_context, DSocket socket);
};
} // namespace blender::nodes::geometry_nodes_eval_log

View File

@ -0,0 +1,330 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "NOD_geometry_nodes_eval_log.hh"
#include "BKE_geometry_set_instances.hh"
#include "DNA_modifier_types.h"
#include "DNA_space_types.h"
namespace blender::nodes::geometry_nodes_eval_log {
using fn::CPPType;
ModifierLog::ModifierLog(GeoLogger &logger)
{
root_tree_logs_ = allocator_.construct<TreeLog>();
LogByTreeContext log_by_tree_context;
/* Combine all the local loggers that have been used by separate threads. */
for (LocalGeoLogger &local_logger : logger) {
/* Take ownership of the allocator. */
logger_allocators_.append(std::move(local_logger.allocator_));
for (ValueOfSockets &value_of_sockets : local_logger.values_) {
ValueLog *value_log = value_of_sockets.value.get();
/* Take centralized ownership of the logged value. It might be referenced by multiple
* sockets. */
logged_values_.append(std::move(value_of_sockets.value));
for (const DSocket &socket : value_of_sockets.sockets) {
SocketLog &socket_log = this->lookup_or_add_socket_log(log_by_tree_context, socket);
socket_log.value_ = value_log;
}
}
for (NodeWithWarning &node_with_warning : local_logger.node_warnings_) {
NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context,
node_with_warning.node);
node_log.warnings_.append(node_with_warning.warning);
}
}
}
TreeLog &ModifierLog::lookup_or_add_tree_log(LogByTreeContext &log_by_tree_context,
const DTreeContext &tree_context)
{
TreeLog *tree_log = log_by_tree_context.lookup_default(&tree_context, nullptr);
if (tree_log != nullptr) {
return *tree_log;
}
const DTreeContext *parent_context = tree_context.parent_context();
if (parent_context == nullptr) {
return *root_tree_logs_.get();
}
TreeLog &parent_log = this->lookup_or_add_tree_log(log_by_tree_context, *parent_context);
destruct_ptr<TreeLog> owned_tree_log = allocator_.construct<TreeLog>();
tree_log = owned_tree_log.get();
log_by_tree_context.add_new(&tree_context, tree_log);
parent_log.child_logs_.add_new(tree_context.parent_node()->name(), std::move(owned_tree_log));
return *tree_log;
}
NodeLog &ModifierLog::lookup_or_add_node_log(LogByTreeContext &log_by_tree_context, DNode node)
{
TreeLog &tree_log = this->lookup_or_add_tree_log(log_by_tree_context, *node.context());
NodeLog &node_log = *tree_log.node_logs_.lookup_or_add_cb(node->name(), [&]() {
destruct_ptr<NodeLog> node_log = allocator_.construct<NodeLog>();
node_log->input_logs_.resize(node->inputs().size());
node_log->output_logs_.resize(node->outputs().size());
return node_log;
});
return node_log;
}
SocketLog &ModifierLog::lookup_or_add_socket_log(LogByTreeContext &log_by_tree_context,
DSocket socket)
{
NodeLog &node_log = this->lookup_or_add_node_log(log_by_tree_context, socket.node());
MutableSpan<SocketLog> socket_logs = socket->is_input() ? node_log.input_logs_ :
node_log.output_logs_;
SocketLog &socket_log = socket_logs[socket->index()];
return socket_log;
}
const NodeLog *TreeLog::lookup_node_log(StringRef node_name) const
{
const destruct_ptr<NodeLog> *node_log = node_logs_.lookup_ptr_as(node_name);
if (node_log == nullptr) {
return nullptr;
}
return node_log->get();
}
const NodeLog *TreeLog::lookup_node_log(const bNode &node) const
{
return this->lookup_node_log(node.name);
}
const TreeLog *TreeLog::lookup_child_log(StringRef node_name) const
{
const destruct_ptr<TreeLog> *tree_log = child_logs_.lookup_ptr_as(node_name);
if (tree_log == nullptr) {
return nullptr;
}
return tree_log->get();
}
const SocketLog *NodeLog::lookup_socket_log(eNodeSocketInOut in_out, int index) const
{
BLI_assert(index >= 0);
Span<SocketLog> socket_logs = (in_out == SOCK_IN) ? input_logs_ : output_logs_;
if (index >= socket_logs.size()) {
return nullptr;
}
return &socket_logs[index];
}
const SocketLog *NodeLog::lookup_socket_log(const bNode &node, const bNodeSocket &socket) const
{
ListBase sockets = socket.in_out == SOCK_IN ? node.inputs : node.outputs;
int index = BLI_findindex(&sockets, &socket);
return this->lookup_socket_log((eNodeSocketInOut)socket.in_out, index);
}
GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_full_geometry)
{
bke::geometry_set_instances_attribute_foreach(
geometry_set,
[&](StringRefNull attribute_name, const AttributeMetaData &meta_data) {
this->attributes_.append({attribute_name, meta_data.domain, meta_data.data_type});
return true;
},
8);
for (const GeometryComponent *component : geometry_set.get_components_for_read()) {
component_types_.append(component->type());
}
if (log_full_geometry) {
full_geometry_ = std::make_unique<GeometrySet>(geometry_set);
full_geometry_->ensure_owns_direct_data();
}
}
Vector<const GeometryAttributeInfo *> NodeLog::lookup_available_attributes() const
{
Vector<const GeometryAttributeInfo *> attributes;
Set<StringRef> names;
for (const SocketLog &socket_log : input_logs_) {
const ValueLog *value_log = socket_log.value();
if (const GeometryValueLog *geo_value_log = dynamic_cast<const GeometryValueLog *>(
value_log)) {
for (const GeometryAttributeInfo &attribute : geo_value_log->attributes()) {
if (names.add(attribute.name)) {
attributes.append(&attribute);
}
}
}
}
return attributes;
}
const ModifierLog *ModifierLog::find_root_by_node_editor_context(const SpaceNode &snode)
{
if (snode.id == nullptr) {
return nullptr;
}
if (GS(snode.id->name) != ID_OB) {
return nullptr;
}
Object *object = (Object *)snode.id;
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {
NodesModifierData *nmd = (NodesModifierData *)md;
if (nmd->node_group == snode.nodetree) {
return (ModifierLog *)nmd->runtime_eval_log;
}
}
}
return nullptr;
}
const TreeLog *ModifierLog::find_tree_by_node_editor_context(const SpaceNode &snode)
{
const ModifierLog *eval_log = ModifierLog::find_root_by_node_editor_context(snode);
if (eval_log == nullptr) {
return nullptr;
}
Vector<bNodeTreePath *> tree_path_vec = snode.treepath;
if (tree_path_vec.is_empty()) {
return nullptr;
}
TreeLog *current = eval_log->root_tree_logs_.get();
for (bNodeTreePath *path : tree_path_vec.as_span().drop_front(1)) {
destruct_ptr<TreeLog> *tree_log = current->child_logs_.lookup_ptr_as(path->node_name);
if (tree_log == nullptr) {
return nullptr;
}
current = tree_log->get();
}
return current;
}
const NodeLog *ModifierLog::find_node_by_node_editor_context(const SpaceNode &snode,
const bNode &node)
{
const TreeLog *tree_log = ModifierLog::find_tree_by_node_editor_context(snode);
if (tree_log == nullptr) {
return nullptr;
}
return tree_log->lookup_node_log(node);
}
const SocketLog *ModifierLog::find_socket_by_node_editor_context(const SpaceNode &snode,
const bNode &node,
const bNodeSocket &socket)
{
const NodeLog *node_log = ModifierLog::find_node_by_node_editor_context(snode, node);
if (node_log == nullptr) {
return nullptr;
}
return node_log->lookup_socket_log(node, socket);
}
const NodeLog *ModifierLog::find_node_by_spreadsheet_editor_context(
const SpaceSpreadsheet &sspreadsheet)
{
Vector<SpreadsheetContext *> context_path = sspreadsheet.context_path;
if (context_path.size() <= 2) {
return nullptr;
}
if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) {
return nullptr;
}
if (context_path[1]->type != SPREADSHEET_CONTEXT_MODIFIER) {
return nullptr;
}
for (SpreadsheetContext *context : context_path.as_span().drop_front(2)) {
if (context->type != SPREADSHEET_CONTEXT_NODE) {
return nullptr;
}
}
Span<SpreadsheetContextNode *> node_contexts =
context_path.as_span().drop_front(2).cast<SpreadsheetContextNode *>();
Object *object = ((SpreadsheetContextObject *)context_path[0])->object;
StringRefNull modifier_name = ((SpreadsheetContextModifier *)context_path[1])->modifier_name;
if (object == nullptr) {
return nullptr;
}
const ModifierLog *eval_log = nullptr;
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {
if (md->name == modifier_name) {
NodesModifierData *nmd = (NodesModifierData *)md;
eval_log = (const ModifierLog *)nmd->runtime_eval_log;
break;
}
}
}
if (eval_log == nullptr) {
return nullptr;
}
const TreeLog *tree_log = &eval_log->root_tree();
for (SpreadsheetContextNode *context : node_contexts.drop_back(1)) {
tree_log = tree_log->lookup_child_log(context->node_name);
if (tree_log == nullptr) {
return nullptr;
}
}
const NodeLog *node_log = tree_log->lookup_node_log(node_contexts.last()->node_name);
return node_log;
}
void LocalGeoLogger::log_value_for_sockets(Span<DSocket> sockets, GPointer value)
{
const CPPType &type = *value.type();
Span<DSocket> copied_sockets = allocator_->construct_array_copy(sockets);
if (type.is<GeometrySet>()) {
bool log_full_geometry = false;
for (const DSocket &socket : sockets) {
if (main_logger_->log_full_geometry_sockets_.contains(socket)) {
log_full_geometry = true;
break;
}
}
const GeometrySet &geometry_set = *value.get<GeometrySet>();
destruct_ptr<GeometryValueLog> value_log = allocator_->construct<GeometryValueLog>(
geometry_set, log_full_geometry);
values_.append({copied_sockets, std::move(value_log)});
}
else {
void *buffer = allocator_->allocate(type.size(), type.alignment());
type.copy_construct(value.get(), buffer);
destruct_ptr<GenericValueLog> value_log = allocator_->construct<GenericValueLog>(
GMutablePointer{type, buffer});
values_.append({copied_sockets, std::move(value_log)});
}
}
void LocalGeoLogger::log_multi_value_socket(DSocket socket, Span<GPointer> values)
{
/* Doesn't have to be logged currently. */
UNUSED_VARS(socket, values);
}
void LocalGeoLogger::log_node_warning(DNode node, NodeWarningType type, std::string message)
{
node_warnings_.append({node, {type, std::move(message)}});
}
} // namespace blender::nodes::geometry_nodes_eval_log

View File

@ -16,8 +16,6 @@
#include "DNA_modifier_types.h"
#include "BKE_node_ui_storage.hh"
#include "DEG_depsgraph_query.h"
#include "NOD_geometry_exec.hh"
@ -26,21 +24,14 @@
#include "node_geometry_util.hh"
using blender::nodes::geometry_nodes_eval_log::LocalGeoLogger;
namespace blender::nodes {
void GeoNodeExecParams::error_message_add(const NodeWarningType type, std::string message) const
{
bNodeTree *btree_cow = provider_->dnode->btree();
BLI_assert(btree_cow != nullptr);
if (btree_cow == nullptr) {
return;
}
bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow);
const NodeTreeEvaluationContext context(*provider_->self_object, *provider_->modifier);
BKE_nodetree_error_message_add(
*btree_original, context, *provider_->dnode->bnode(), type, std::move(message));
LocalGeoLogger &local_logger = provider_->logger->local();
local_logger.log_node_warning(provider_->dnode, type, std::move(message));
}
const bNodeSocket *GeoNodeExecParams::find_available_socket(const StringRef name) const