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:
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
|
@ -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);
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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});
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -27,6 +27,7 @@ set(INC
|
|||
../../gpu
|
||||
../../makesdna
|
||||
../../makesrna
|
||||
../../nodes
|
||||
../../windowmanager
|
||||
../../../../intern/glew-mx
|
||||
../../../../intern/guardedalloc
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue