/* * ***** BEGIN GPL LICENSE BLOCK ***** * * 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. * * The Original Code is Copyright (C) 2014 Blender Foundation. * All rights reserved. * * Original Author: Lukas Toenne * Contributor(s): None Yet * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/depsgraph/intern/depsgraph_debug.cc * \ingroup depsgraph * * Implementation of tools for debugging the depsgraph */ //#include #include extern "C" { #include "BLI_utildefines.h" #include "BLI_listbase.h" #include "BLI_ghash.h" #include "BLI_string.h" #include "DNA_scene_types.h" #include "DNA_userdef_types.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_debug.h" #include "DEG_depsgraph_build.h" #include "WM_api.h" #include "WM_types.h" } /* extern "C" */ #include "depsgraph_debug.h" #include "depsnode.h" #include "depsnode_component.h" #include "depsnode_operation.h" #include "depsgraph_intern.h" /* ****************** */ /* Graphviz Debugging */ static SpinLock lock; #define NL "\r\n" /* Only one should be enabled, defines whether graphviz nodes * get colored by individual types or classes. */ #define COLOR_SCHEME_NODE_CLASS 1 //#define COLOR_SCHEME_NODE_TYPE 2 static const char *deg_debug_graphviz_fontname = "helvetica"; static float deg_debug_graphviz_graph_label_size = 20.0f; static float deg_debug_graphviz_node_label_size = 14.0f; static const int deg_debug_max_colors = 12; #if 0 static const char *deg_debug_colors_dark[] = { "#6e8997", "#144f77", "#76945b", "#216a1d", "#a76665", "#971112", "#a87f49", "#0a9540", "#86768e", "#462866", "#a9a965", "#753b1a", }; #endif #ifdef COLOR_SCHEME_NODE_TYPE static const char *deg_debug_colors[] = { "#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c", "#fdbf6f", "#ff7f00", "#cab2d6", "#6a3d9a", "#ffff99", "#b15928", }; #endif static const char *deg_debug_colors_light[] = { "#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9", "#bc80bd", "#ccebc5", "#ffed6f", }; #ifdef COLOR_SCHEME_NODE_TYPE static const int deg_debug_node_type_color_map[][2] = { {DEPSNODE_TYPE_ROOT, 0}, {DEPSNODE_TYPE_TIMESOURCE, 1}, {DEPSNODE_TYPE_ID_REF, 2}, {DEPSNODE_TYPE_SUBGRAPH, 3}, /* Outer Types */ {DEPSNODE_TYPE_PARAMETERS, 4}, {DEPSNODE_TYPE_PROXY, 5}, {DEPSNODE_TYPE_ANIMATION, 6}, {DEPSNODE_TYPE_TRANSFORM, 7}, {DEPSNODE_TYPE_GEOMETRY, 8}, {DEPSNODE_TYPE_SEQUENCER, 9}, {DEPSNODE_TYPE_SHADING, 10}, {-1, 0} }; #endif #if 0 /* unused */ static const int deg_debug_relation_type_color_map[][2] = { {DEPSREL_TYPE_STANDARD, 0}, {DEPSREL_TYPE_ROOT_TO_ACTIVE, 1}, {DEPSREL_TYPE_DATABLOCK, 2}, {DEPSREL_TYPE_TIME, 3}, {DEPSREL_TYPE_COMPONENT_ORDER, 4}, {DEPSREL_TYPE_OPERATION, 5}, {DEPSREL_TYPE_DRIVER, 6}, {DEPSREL_TYPE_DRIVER_TARGET, 7}, {DEPSREL_TYPE_TRANSFORM, 8}, {DEPSREL_TYPE_GEOMETRY_EVAL, 9}, {DEPSREL_TYPE_UPDATE, 10}, {DEPSREL_TYPE_UPDATE_UI, 11}, {-1, 0} }; #endif static int deg_debug_node_color_index(const DepsNode *node) { #ifdef COLOR_SCHEME_NODE_CLASS /* Some special types. */ switch (node->type) { case DEPSNODE_TYPE_ID_REF: return 5; case DEPSNODE_TYPE_OPERATION: { OperationDepsNode *op_node = (OperationDepsNode *)node; if (op_node->is_noop()) return 8; } default: break; } /* Do others based on class. */ switch (node->tclass) { case DEPSNODE_CLASS_OPERATION: return 4; case DEPSNODE_CLASS_COMPONENT: return 1; default: return 9; } #endif #ifdef COLOR_SCHEME_NODE_TYPE const int (*pair)[2]; for (pair = deg_debug_node_type_color_map; (*pair)[0] >= 0; ++pair) { if ((*pair)[0] == node->type) { return (*pair)[1]; } } return -1; #endif } struct DebugContext { FILE *file; bool show_tags; bool show_eval_priority; }; static void deg_debug_fprintf(const DebugContext &ctx, const char *fmt, ...) ATTR_PRINTF_FORMAT(2, 3); static void deg_debug_fprintf(const DebugContext &ctx, const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(ctx.file, fmt, args); va_end(args); } static void deg_debug_graphviz_legend_color(const DebugContext &ctx, const char *name, const char *color) { deg_debug_fprintf(ctx, ""); deg_debug_fprintf(ctx, "%s", name); deg_debug_fprintf(ctx, "", color); deg_debug_fprintf(ctx, "" NL); } #if 0 static void deg_debug_graphviz_legend_line(const DebugContext &ctx, const char *name, const char *color, const char *style) { /* XXX TODO */ deg_debug_fprintf(ctx, "" NL); } static void deg_debug_graphviz_legend_cluster(const DebugContext &ctx, const char *name, const char *color, const char *style) { deg_debug_fprintf(ctx, ""); deg_debug_fprintf(ctx, "%s", name); deg_debug_fprintf(ctx, ""); deg_debug_fprintf(ctx, "", color); deg_debug_fprintf(ctx, "
"); deg_debug_fprintf(ctx, "" NL); } #endif static void deg_debug_graphviz_legend(const DebugContext &ctx) { deg_debug_fprintf(ctx, "{" NL); deg_debug_fprintf(ctx, "rank = sink;" NL); deg_debug_fprintf(ctx, "Legend [shape=none, margin=0, label=<" NL); deg_debug_fprintf(ctx, " " NL); deg_debug_fprintf(ctx, "" NL); #ifdef COLOR_SCHEME_NODE_CLASS const char **colors = deg_debug_colors_light; deg_debug_graphviz_legend_color(ctx, "Operation", colors[4]); deg_debug_graphviz_legend_color(ctx, "Component", colors[1]); deg_debug_graphviz_legend_color(ctx, "ID Node", colors[5]); deg_debug_graphviz_legend_color(ctx, "NOOP", colors[8]); #endif #ifdef COLOR_SCHEME_NODE_TYPE const int (*pair)[2]; for (pair = deg_debug_node_type_color_map; (*pair)[0] >= 0; ++pair) { DepsNodeFactory *nti = DEG_get_node_factory((eDepsNode_Type)(*pair)[0]); deg_debug_graphviz_legend_color(ctx, nti->tname().c_str(), deg_debug_colors_light[(*pair)[1] % deg_debug_max_colors]); } #endif deg_debug_fprintf(ctx, "
Legend
" NL); deg_debug_fprintf(ctx, ">" NL); deg_debug_fprintf(ctx, ",fontname=\"%s\"", deg_debug_graphviz_fontname); deg_debug_fprintf(ctx, "];" NL); deg_debug_fprintf(ctx, "}" NL); } #if 0 /* unused */ static int deg_debug_relation_type_color_index(eDepsRelation_Type type) { const int (*pair)[2]; for (pair = deg_debug_relation_type_color_map; (*pair)[0] >= 0; ++pair) { if ((*pair)[0] == type) { return (*pair)[1]; } } return -1; } #endif static void deg_debug_graphviz_node_color(const DebugContext &ctx, const DepsNode *node) { const char *color_default = "black"; const char *color_modified = "orangered4"; const char *color_update = "dodgerblue3"; const char *color = color_default; if (ctx.show_tags) { if (node->tclass == DEPSNODE_CLASS_OPERATION) { OperationDepsNode *op_node = (OperationDepsNode *)node; if (op_node->flag & DEPSOP_FLAG_DIRECTLY_MODIFIED) { color = color_modified; } else if (op_node->flag & DEPSOP_FLAG_NEEDS_UPDATE) { color = color_update; } } } deg_debug_fprintf(ctx, "\"%s\"", color); } static void deg_debug_graphviz_node_penwidth(const DebugContext &ctx, const DepsNode *node) { float penwidth_default = 1.0f; float penwidth_modified = 4.0f; float penwidth_update = 4.0f; float penwidth = penwidth_default; if (ctx.show_tags) { if (node->tclass == DEPSNODE_CLASS_OPERATION) { OperationDepsNode *op_node = (OperationDepsNode *)node; if (op_node->flag & DEPSOP_FLAG_DIRECTLY_MODIFIED) { penwidth = penwidth_modified; } else if (op_node->flag & DEPSOP_FLAG_NEEDS_UPDATE) { penwidth = penwidth_update; } } } deg_debug_fprintf(ctx, "\"%f\"", penwidth); } static void deg_debug_graphviz_node_fillcolor(const DebugContext &ctx, const DepsNode *node) { const char *defaultcolor = "gainsboro"; int color_index = deg_debug_node_color_index(node); const char *fillcolor = color_index < 0 ? defaultcolor : deg_debug_colors_light[color_index % deg_debug_max_colors]; deg_debug_fprintf(ctx, "\"%s\"", fillcolor); } #if 0 /* implementation using stripes, a bit too noisy ... */ static void deg_debug_graphviz_node_fillcolor(const DebugContext &ctx, const DepsNode *node) { const char *defaultcolor = "gainsboro"; const char *color_needs_update = "orange"; const int num_stripes = 10; int color_index = deg_debug_node_color_index(node); const char *base_color = color_index < 0 ? defaultcolor : deg_debug_colors_light[color_index % deg_debug_max_colors]; if (ctx.show_tags && (node->flag & (DEPSNODE_FLAG_DIRECTLY_MODIFIED | DEPSNODE_FLAG_NEEDS_UPDATE))) { deg_debug_fprintf(ctx, "\""); for (int i = 0; i < num_stripes; ++i) { if (i > 0) { deg_debug_fprintf(ctx, ":"); } deg_debug_fprintf(ctx, "%s:%s", base_color, color_needs_update); } deg_debug_fprintf(ctx, "\""); } else { deg_debug_fprintf(ctx, "\"%s\"", base_color); } } #endif static void deg_debug_graphviz_relation_color(const DebugContext &ctx, const DepsRelation *UNUSED(rel)) { const char *defaultcolor = "black"; #if 0 /* disabled for now, edge colors are hardly distinguishable */ int color = deg_debug_relation_type_color_index(rel->type); if (color < 0) { deg_debug_fprintf(ctx, "%s", defaultcolor); } else { deg_debug_fprintf(ctx, "\"%s\"", deg_debug_colors_dark[color % deg_debug_max_colors]); } #else deg_debug_fprintf(ctx, "%s", defaultcolor); #endif } static void deg_debug_graphviz_node_style(const DebugContext &ctx, const DepsNode *node) { const char *base_style = "filled"; /* default style */ if (ctx.show_tags) { if (node->tclass == DEPSNODE_CLASS_OPERATION) { OperationDepsNode *op_node = (OperationDepsNode *)node; if (op_node->flag & (DEPSOP_FLAG_DIRECTLY_MODIFIED | DEPSOP_FLAG_NEEDS_UPDATE)) { base_style = "striped"; } } } switch (node->tclass) { case DEPSNODE_CLASS_GENERIC: deg_debug_fprintf(ctx, "\"%s\"", base_style); break; case DEPSNODE_CLASS_COMPONENT: deg_debug_fprintf(ctx, "\"%s\"", base_style); break; case DEPSNODE_CLASS_OPERATION: deg_debug_fprintf(ctx, "\"%s,rounded\"", base_style); break; } } static void deg_debug_graphviz_node_single(const DebugContext &ctx, const DepsNode *node) { const char *shape = "box"; string name = node->identifier(); float priority = -1.0f; if (node->type == DEPSNODE_TYPE_ID_REF) { IDDepsNode *id_node = (IDDepsNode *)node; char buf[256]; BLI_snprintf(buf, sizeof(buf), " (Layers: %d)", id_node->layers); name += buf; } if (ctx.show_eval_priority && node->tclass == DEPSNODE_CLASS_OPERATION) { priority = ((OperationDepsNode *)node)->eval_priority; } deg_debug_fprintf(ctx, "// %s\n", name.c_str()); deg_debug_fprintf(ctx, "\"node_%p\"", node); deg_debug_fprintf(ctx, "["); // deg_debug_fprintf(ctx, "label=<%s>", name); if (priority >= 0.0f) { deg_debug_fprintf(ctx, "label=<%s
(%.2f)>", name.c_str(), priority); } else { deg_debug_fprintf(ctx, "label=<%s>", name.c_str()); } deg_debug_fprintf(ctx, ",fontname=\"%s\"", deg_debug_graphviz_fontname); deg_debug_fprintf(ctx, ",fontsize=%f", deg_debug_graphviz_node_label_size); deg_debug_fprintf(ctx, ",shape=%s", shape); deg_debug_fprintf(ctx, ",style="); deg_debug_graphviz_node_style(ctx, node); deg_debug_fprintf(ctx, ",color="); deg_debug_graphviz_node_color(ctx, node); deg_debug_fprintf(ctx, ",fillcolor="); deg_debug_graphviz_node_fillcolor(ctx, node); deg_debug_fprintf(ctx, ",penwidth="); deg_debug_graphviz_node_penwidth(ctx, node); deg_debug_fprintf(ctx, "];" NL); deg_debug_fprintf(ctx, NL); } static void deg_debug_graphviz_node_cluster_begin(const DebugContext &ctx, const DepsNode *node) { string name = node->identifier().c_str(); if (node->type == DEPSNODE_TYPE_ID_REF) { IDDepsNode *id_node = (IDDepsNode *)node; char buf[256]; BLI_snprintf(buf, sizeof(buf), " (Layers: %d)", id_node->layers); name += buf; } deg_debug_fprintf(ctx, "// %s\n", name.c_str()); deg_debug_fprintf(ctx, "subgraph \"cluster_%p\" {" NL, node); // deg_debug_fprintf(ctx, "label=<%s>;" NL, name); deg_debug_fprintf(ctx, "label=<%s>;" NL, name.c_str()); deg_debug_fprintf(ctx, "fontname=\"%s\";" NL, deg_debug_graphviz_fontname); deg_debug_fprintf(ctx, "fontsize=%f;" NL, deg_debug_graphviz_node_label_size); deg_debug_fprintf(ctx, "style="); deg_debug_graphviz_node_style(ctx, node); deg_debug_fprintf(ctx, ";" NL); deg_debug_fprintf(ctx, "color="); deg_debug_graphviz_node_color(ctx, node); deg_debug_fprintf(ctx, ";" NL); deg_debug_fprintf(ctx, "fillcolor="); deg_debug_graphviz_node_fillcolor(ctx, node); deg_debug_fprintf(ctx, ";" NL); deg_debug_fprintf(ctx, "penwidth="); deg_debug_graphviz_node_penwidth(ctx, node); deg_debug_fprintf(ctx, ";" NL); /* dummy node, so we can add edges between clusters */ deg_debug_fprintf(ctx, "\"node_%p\"", node); deg_debug_fprintf(ctx, "["); deg_debug_fprintf(ctx, "shape=%s", "point"); deg_debug_fprintf(ctx, ",style=%s", "invis"); deg_debug_fprintf(ctx, "];" NL); deg_debug_fprintf(ctx, NL); } static void deg_debug_graphviz_node_cluster_end(const DebugContext &ctx) { deg_debug_fprintf(ctx, "}" NL); deg_debug_fprintf(ctx, NL); } static void deg_debug_graphviz_graph_nodes(const DebugContext &ctx, const Depsgraph *graph); static void deg_debug_graphviz_graph_relations(const DebugContext &ctx, const Depsgraph *graph); static void deg_debug_graphviz_node(const DebugContext &ctx, const DepsNode *node) { switch (node->type) { case DEPSNODE_TYPE_ID_REF: { const IDDepsNode *id_node = (const IDDepsNode *)node; if (id_node->components.empty()) { deg_debug_graphviz_node_single(ctx, node); } else { deg_debug_graphviz_node_cluster_begin(ctx, node); for (IDDepsNode::ComponentMap::const_iterator it = id_node->components.begin(); it != id_node->components.end(); ++it) { const ComponentDepsNode *comp = it->second; deg_debug_graphviz_node(ctx, comp); } deg_debug_graphviz_node_cluster_end(ctx); } break; } case DEPSNODE_TYPE_SUBGRAPH: { SubgraphDepsNode *sub_node = (SubgraphDepsNode *)node; if (sub_node->graph) { deg_debug_graphviz_node_cluster_begin(ctx, node); deg_debug_graphviz_graph_nodes(ctx, sub_node->graph); deg_debug_graphviz_node_cluster_end(ctx); } else { deg_debug_graphviz_node_single(ctx, node); } break; } case DEPSNODE_TYPE_PARAMETERS: case DEPSNODE_TYPE_ANIMATION: case DEPSNODE_TYPE_TRANSFORM: case DEPSNODE_TYPE_PROXY: case DEPSNODE_TYPE_GEOMETRY: case DEPSNODE_TYPE_SEQUENCER: case DEPSNODE_TYPE_EVAL_POSE: case DEPSNODE_TYPE_BONE: case DEPSNODE_TYPE_SHADING: { ComponentDepsNode *comp_node = (ComponentDepsNode *)node; if (!comp_node->operations.empty()) { deg_debug_graphviz_node_cluster_begin(ctx, node); for (ComponentDepsNode::OperationMap::const_iterator it = comp_node->operations.begin(); it != comp_node->operations.end(); ++it) { const DepsNode *op_node = it->second; deg_debug_graphviz_node(ctx, op_node); } deg_debug_graphviz_node_cluster_end(ctx); } else { deg_debug_graphviz_node_single(ctx, node); } break; } default: deg_debug_graphviz_node_single(ctx, node); break; } } static bool deg_debug_graphviz_is_cluster(const DepsNode *node) { switch (node->type) { case DEPSNODE_TYPE_ID_REF: { const IDDepsNode *id_node = (const IDDepsNode *)node; return !id_node->components.empty(); } case DEPSNODE_TYPE_SUBGRAPH: { SubgraphDepsNode *sub_node = (SubgraphDepsNode *)node; return sub_node->graph != NULL; } case DEPSNODE_TYPE_PARAMETERS: case DEPSNODE_TYPE_ANIMATION: case DEPSNODE_TYPE_TRANSFORM: case DEPSNODE_TYPE_PROXY: case DEPSNODE_TYPE_GEOMETRY: case DEPSNODE_TYPE_SEQUENCER: case DEPSNODE_TYPE_EVAL_POSE: case DEPSNODE_TYPE_BONE: { ComponentDepsNode *comp_node = (ComponentDepsNode *)node; return !comp_node->operations.empty(); } default: return false; } } static bool deg_debug_graphviz_is_owner(const DepsNode *node, const DepsNode *other) { switch (node->tclass) { case DEPSNODE_CLASS_COMPONENT: { ComponentDepsNode *comp_node = (ComponentDepsNode *)node; if (comp_node->owner == other) return true; break; } case DEPSNODE_CLASS_OPERATION: { OperationDepsNode *op_node = (OperationDepsNode *)node; if (op_node->owner == other) return true; else if (op_node->owner->owner == other) return true; break; } default: break; } return false; } static void deg_debug_graphviz_node_relations(const DebugContext &ctx, const DepsNode *node) { DEPSNODE_RELATIONS_ITER_BEGIN(node->inlinks, rel) { const DepsNode *tail = rel->to; /* same as node */ const DepsNode *head = rel->from; deg_debug_fprintf(ctx, "// %s -> %s\n", head->identifier().c_str(), tail->identifier().c_str()); deg_debug_fprintf(ctx, "\"node_%p\"", head); deg_debug_fprintf(ctx, " -> "); deg_debug_fprintf(ctx, "\"node_%p\"", tail); deg_debug_fprintf(ctx, "["); deg_debug_fprintf(ctx, "label=\"%s\"", rel->name); deg_debug_fprintf(ctx, ",fontname=\"%s\"", deg_debug_graphviz_fontname); deg_debug_fprintf(ctx, ",color="); deg_debug_graphviz_relation_color(ctx, rel); /* NOTE: edge from node to own cluster is not possible and gives graphviz * warning, avoid this here by just linking directly to the invisible * placeholder node */ if (deg_debug_graphviz_is_cluster(tail) && !deg_debug_graphviz_is_owner(head, tail)) { deg_debug_fprintf(ctx, ",ltail=\"cluster_%p\"", tail); } if (deg_debug_graphviz_is_cluster(head) && !deg_debug_graphviz_is_owner(tail, head)) { deg_debug_fprintf(ctx, ",lhead=\"cluster_%p\"", head); } deg_debug_fprintf(ctx, "];" NL); deg_debug_fprintf(ctx, NL); } DEPSNODE_RELATIONS_ITER_END; #if 0 if (node->tclass == DEPSNODE_CLASS_COMPONENT) { const ComponentDepsNode *comp_node = (const ComponentDepsNode *)node; for (ComponentDepsNode::OperationMap::const_iterator it = comp_node->operations.begin(); it != comp_node->operations.end(); ++it) { OperationDepsNode *op_node = it->second; deg_debug_graphviz_node_relations(ctx, op_node); } } else if (node->type == DEPSNODE_TYPE_ID_REF) { const IDDepsNode *id_node = (const IDDepsNode *)node; for (IDDepsNode::ComponentMap::const_iterator it = id_node->components.begin(); it != id_node->components.end(); ++it) { const ComponentDepsNode *comp = it->second; deg_debug_graphviz_node_relations(ctx, comp); } } else if (node->type == DEPSNODE_TYPE_SUBGRAPH) { SubgraphDepsNode *sub_node = (SubgraphDepsNode *)node; if (sub_node->graph) { deg_debug_graphviz_graph_relations(ctx, sub_node->graph); } } #endif } static void deg_debug_graphviz_graph_nodes(const DebugContext &ctx, const Depsgraph *graph) { if (graph->root_node) { deg_debug_graphviz_node(ctx, graph->root_node); } for (Depsgraph::IDNodeMap::const_iterator it = graph->id_hash.begin(); it != graph->id_hash.end(); ++it) { DepsNode *node = it->second; deg_debug_graphviz_node(ctx, node); } TimeSourceDepsNode *time_source = graph->find_time_source(NULL); if (time_source != NULL) { deg_debug_graphviz_node(ctx, time_source); } } static void deg_debug_graphviz_graph_relations(const DebugContext &ctx, const Depsgraph *graph) { #if 0 if (graph->root_node) { deg_debug_graphviz_node_relations(ctx, graph->root_node); } for (Depsgraph::IDNodeMap::const_iterator it = graph->id_hash.begin(); it != graph->id_hash.end(); ++it) { DepsNode *id_node = it->second; deg_debug_graphviz_node_relations(ctx, id_node); } #else /* XXX not in use yet */ // for (Depsgraph::OperationNodes::const_iterator it = graph->all_opnodes.begin(); // it != graph->all_opnodes.end(); // ++it) // { // OperationDepsNode *op_node = *it; // deg_debug_graphviz_node_relations(ctx, op_node); // } for (Depsgraph::IDNodeMap::const_iterator it = graph->id_hash.begin(); it != graph->id_hash.end(); ++it) { IDDepsNode *id_node = it->second; for (IDDepsNode::ComponentMap::const_iterator it = id_node->components.begin(); it != id_node->components.end(); ++it) { ComponentDepsNode *comp_node = it->second; for (ComponentDepsNode::OperationMap::const_iterator it = comp_node->operations.begin(); it != comp_node->operations.end(); ++it) { OperationDepsNode *op_node = it->second; deg_debug_graphviz_node_relations(ctx, op_node); } } } TimeSourceDepsNode *time_source = graph->find_time_source(NULL); if (time_source != NULL) { deg_debug_graphviz_node_relations(ctx, time_source); } #endif } void DEG_debug_graphviz(const Depsgraph *graph, FILE *f, const char *label, bool show_eval) { #if 0 /* generate shaded color set */ static char colors[][3] = {{0xa6, 0xce, 0xe3},{0x1f, 0x78, 0xb4},{0xb2, 0xdf, 0x8a},{0x33, 0xa0, 0x2c}, {0xfb, 0x9a, 0x99},{0xe3, 0x1a, 0x1c},{0xfd, 0xbf, 0x6f},{0xff, 0x7f, 0x00}, {0xca, 0xb2, 0xd6},{0x6a, 0x3d, 0x9a},{0xff, 0xff, 0x99},{0xb1, 0x59, 0x28}}; int i; const float factor = 0.666f; for (i=0; i < 12; ++i) printf("\"#%x%x%x\"\n", (char)(colors[i][0] * factor), (char)(colors[i][1] * factor), (char)(colors[i][2] * factor)); #endif if (!graph) { return; } DebugContext ctx; ctx.file = f; ctx.show_tags = show_eval; ctx.show_eval_priority = show_eval; deg_debug_fprintf(ctx, "digraph depgraph {" NL); deg_debug_fprintf(ctx, "rankdir=LR;" NL); deg_debug_fprintf(ctx, "graph ["); deg_debug_fprintf(ctx, "compound=true"); deg_debug_fprintf(ctx, ",labelloc=\"t\""); deg_debug_fprintf(ctx, ",fontsize=%f", deg_debug_graphviz_graph_label_size); deg_debug_fprintf(ctx, ",fontname=\"%s\"", deg_debug_graphviz_fontname); deg_debug_fprintf(ctx, ",label=\"%s\"", label); deg_debug_fprintf(ctx, ",splines=ortho"); deg_debug_fprintf(ctx, ",overlap=scalexy"); // XXX: only when using neato deg_debug_fprintf(ctx, "];" NL); deg_debug_graphviz_graph_nodes(ctx, graph); deg_debug_graphviz_graph_relations(ctx, graph); deg_debug_graphviz_legend(ctx); deg_debug_fprintf(ctx, "}" NL); } #undef NL /* ************************************************ */ static string get_component_name(eDepsNode_Type type, const string &name = "") { DepsNodeFactory *factory = DEG_get_node_factory(type); if (name.empty()) { return string(factory->tname()); } else { return string(factory->tname()) + " | " + name; } } static void times_clear(DepsgraphStatsTimes ×) { times.duration_last = 0.0f; } static void times_add(DepsgraphStatsTimes ×, float time) { times.duration_last += time; } void DepsgraphDebug::eval_begin(const EvaluationContext *UNUSED(eval_ctx)) { /* TODO(sergey): Stats are currently globally disabled. */ /* verify_stats(); */ reset_stats(); } void DepsgraphDebug::eval_end(const EvaluationContext *UNUSED(eval_ctx)) { WM_main_add_notifier(NC_SPACE | ND_SPACE_INFO_REPORT, NULL); } void DepsgraphDebug::eval_step(const EvaluationContext *UNUSED(eval_ctx), const char *message) { #ifdef DEG_DEBUG_BUILD if (deg_debug_eval_cb) deg_debug_eval_cb(deg_debug_eval_userdata, message); #else (void)message; /* Ignored. */ #endif } void DepsgraphDebug::task_started(Depsgraph *graph, const OperationDepsNode *node) { if (stats) { BLI_spin_lock(&graph->lock); ComponentDepsNode *comp = node->owner; ID *id = comp->owner->id; DepsgraphStatsID *id_stats = get_id_stats(id, true); times_clear(id_stats->times); /* XXX TODO use something like: if (id->flag & ID_DEG_DETAILS) {...} */ if (0) { /* XXX component name usage needs cleanup! currently mixes identifier and description strings! */ DepsgraphStatsComponent *comp_stats = get_component_stats(id, get_component_name(comp->type, comp->name), true); times_clear(comp_stats->times); } BLI_spin_unlock(&graph->lock); } } void DepsgraphDebug::task_completed(Depsgraph *graph, const OperationDepsNode *node, double time) { if (stats) { BLI_spin_lock(&graph->lock); ComponentDepsNode *comp = node->owner; ID *id = comp->owner->id; DepsgraphStatsID *id_stats = get_id_stats(id, true); times_add(id_stats->times, time); /* XXX TODO use something like: if (id->flag & ID_DEG_DETAILS) {...} */ if (0) { /* XXX component name usage needs cleanup! currently mixes identifier and description strings! */ DepsgraphStatsComponent *comp_stats = get_component_stats(id, get_component_name(comp->type, comp->name), true); times_add(comp_stats->times, time); } BLI_spin_unlock(&graph->lock); } } /* ********** */ /* Statistics */ DepsgraphStats *DepsgraphDebug::stats = NULL; /* GHash callback */ static void deg_id_stats_free(void *val) { DepsgraphStatsID *id_stats = (DepsgraphStatsID *)val; if (id_stats) { BLI_freelistN(&id_stats->components); MEM_freeN(id_stats); } } void DepsgraphDebug::stats_init() { if (!stats) { stats = (DepsgraphStats *)MEM_callocN(sizeof(DepsgraphStats), "Depsgraph Stats"); stats->id_stats = BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "Depsgraph ID Stats Hash"); } } void DepsgraphDebug::stats_free() { if (stats) { BLI_ghash_free(stats->id_stats, NULL, deg_id_stats_free); MEM_freeN(stats); stats = NULL; } } void DepsgraphDebug::verify_stats() { stats_init(); } void DepsgraphDebug::reset_stats() { if (!stats) { return; } /* XXX this doesn't work, will immediately clear all info, * since most depsgraph updates have none or very few updates to handle. * * Could consider clearing only zero-user ID blocks here */ // BLI_ghash_clear(stats->id_stats, NULL, deg_id_stats_free); } DepsgraphStatsID *DepsgraphDebug::get_id_stats(ID *id, bool create) { DepsgraphStatsID *id_stats = (DepsgraphStatsID *)BLI_ghash_lookup(stats->id_stats, id); if (!id_stats && create) { id_stats = (DepsgraphStatsID *)MEM_callocN(sizeof(DepsgraphStatsID), "Depsgraph ID Stats"); id_stats->id = id; BLI_ghash_insert(stats->id_stats, id, id_stats); } return id_stats; } DepsgraphStatsComponent *DepsgraphDebug::get_component_stats( DepsgraphStatsID *id_stats, const string &name, bool create) { DepsgraphStatsComponent *comp_stats; for (comp_stats = (DepsgraphStatsComponent *)id_stats->components.first; comp_stats != NULL; comp_stats = comp_stats->next) { if (STREQ(comp_stats->name, name.c_str())) break; } if (!comp_stats && create) { comp_stats = (DepsgraphStatsComponent *)MEM_callocN(sizeof(DepsgraphStatsComponent), "Depsgraph Component Stats"); BLI_strncpy(comp_stats->name, name.c_str(), sizeof(comp_stats->name)); BLI_addtail(&id_stats->components, comp_stats); } return comp_stats; } /* ------------------------------------------------ */ DepsgraphStats *DEG_stats(void) { return DepsgraphDebug::stats; } void DEG_stats_verify() { DepsgraphDebug::verify_stats(); } DepsgraphStatsID *DEG_stats_id(ID *id) { if (!DepsgraphDebug::stats) { return NULL; } return DepsgraphDebug::get_id_stats(id, false); } bool DEG_debug_compare(const struct Depsgraph *graph1, const struct Depsgraph *graph2) { BLI_assert(graph1 != NULL); BLI_assert(graph2 != NULL); if (graph1->operations.size() != graph2->operations.size()) { return false; } /* TODO(sergey): Currently we only do real stupid check, * which is fast but which isn't 100% reliable. * * Would be cool to make it more robust, but it's good enough * for now. Also, proper graph check is actually NP-complex * problem.. */ return true; } bool DEG_debug_scene_relations_validate(Main *bmain, Scene *scene) { Depsgraph *depsgraph = DEG_graph_new(); bool valid = true; DEG_graph_build_from_scene(depsgraph, bmain, scene); if (!DEG_debug_compare(depsgraph, scene->depsgraph)) { fprintf(stderr, "ERROR! Depsgraph wasn't tagged for update when it should have!\n"); BLI_assert(!"This should not happen!"); valid = false; } DEG_graph_free(depsgraph); return valid; } bool DEG_debug_consistency_check(Depsgraph *graph) { /* Validate links exists in both directions. */ for (Depsgraph::OperationNodes::const_iterator it_op = graph->operations.begin(); it_op != graph->operations.end(); ++it_op) { OperationDepsNode *node = *it_op; for (OperationDepsNode::Relations::const_iterator it_rel = node->outlinks.begin(); it_rel != node->outlinks.end(); ++it_rel) { DepsRelation *rel = *it_rel; int counter1 = 0; for (OperationDepsNode::Relations::const_iterator tmp_rel = node->outlinks.begin(); tmp_rel != node->outlinks.end(); ++tmp_rel) { if (*tmp_rel == rel) { ++counter1; } } int counter2 = 0; for (OperationDepsNode::Relations::const_iterator tmp_rel = rel->to->inlinks.begin(); tmp_rel != rel->to->inlinks.end(); ++tmp_rel) { if (*tmp_rel == rel) { ++counter2; } } if (counter1 != counter2) { printf("Relation exists in outgoing direction but not in incoming (%d vs. %d).\n", counter1, counter2); return false; } } } for (Depsgraph::OperationNodes::const_iterator it_op = graph->operations.begin(); it_op != graph->operations.end(); ++it_op) { OperationDepsNode *node = *it_op; for (OperationDepsNode::Relations::const_iterator it_rel = node->inlinks.begin(); it_rel != node->inlinks.end(); ++it_rel) { DepsRelation *rel = *it_rel; int counter1 = 0; for (OperationDepsNode::Relations::const_iterator tmp_rel = node->inlinks.begin(); tmp_rel != node->inlinks.end(); ++tmp_rel) { if (*tmp_rel == rel) { ++counter1; } } int counter2 = 0; for (OperationDepsNode::Relations::const_iterator tmp_rel = rel->from->outlinks.begin(); tmp_rel != rel->from->outlinks.end(); ++tmp_rel) { if (*tmp_rel == rel) { ++counter2; } } if (counter1 != counter2) { printf("Relation exists in incoming direction but not in outcoming (%d vs. %d).\n", counter1, counter2); } } } /* Validate node valency calculated in both directions. */ for (Depsgraph::OperationNodes::const_iterator it_op = graph->operations.begin(); it_op != graph->operations.end(); ++it_op) { OperationDepsNode *node = *it_op; node->num_links_pending = 0; node->done = 0; } for (Depsgraph::OperationNodes::const_iterator it_op = graph->operations.begin(); it_op != graph->operations.end(); ++it_op) { OperationDepsNode *node = *it_op; if (node->done) { printf("Node %s is twice in the operations!\n", node->identifier().c_str()); return false; } for (OperationDepsNode::Relations::const_iterator it_rel = node->outlinks.begin(); it_rel != node->outlinks.end(); ++it_rel) { DepsRelation *rel = *it_rel; if (rel->to->type == DEPSNODE_TYPE_OPERATION) { OperationDepsNode *to = (OperationDepsNode *)rel->to; BLI_assert(to->num_links_pending < to->inlinks.size()); ++to->num_links_pending; } } node->done = 1; } for (Depsgraph::OperationNodes::const_iterator it_op = graph->operations.begin(); it_op != graph->operations.end(); ++it_op) { OperationDepsNode *node = *it_op; int num_links_pending = 0; for (OperationDepsNode::Relations::const_iterator it_rel = node->inlinks.begin(); it_rel != node->inlinks.end(); ++it_rel) { DepsRelation *rel = *it_rel; if (rel->from->type == DEPSNODE_TYPE_OPERATION) { ++num_links_pending; } } if (node->num_links_pending != num_links_pending) { printf("Valency mismatch: %s, %u != %d\n", node->identifier().c_str(), node->num_links_pending, num_links_pending); printf("Number of inlinks: %d\n", (int)node->inlinks.size()); return false; } } return true; } /* ------------------------------------------------ */ /** * Obtain simple statistics about the complexity of the depsgraph * \param[out] r_outer The number of outer nodes in the graph * \param[out] r_operations The number of operation nodes in the graph * \param[out] r_relations The number of relations between (executable) nodes in the graph */ void DEG_stats_simple(const Depsgraph *graph, size_t *r_outer, size_t *r_operations, size_t *r_relations) { /* number of operations */ if (r_operations) { /* All operations should be in this list, allowing us to count the total * number of nodes. */ *r_operations = graph->operations.size(); } /* Count number of outer nodes and/or relations between these. */ if (r_outer || r_relations) { size_t tot_outer = 0; size_t tot_rels = 0; for (Depsgraph::IDNodeMap::const_iterator it = graph->id_hash.begin(); it != graph->id_hash.end(); ++it) { IDDepsNode *id_node = it->second; tot_outer++; for (IDDepsNode::ComponentMap::const_iterator it = id_node->components.begin(); it != id_node->components.end(); ++it) { ComponentDepsNode *comp_node = it->second; tot_outer++; for (ComponentDepsNode::OperationMap::const_iterator it = comp_node->operations.begin(); it != comp_node->operations.end(); ++it) { OperationDepsNode *op_node = it->second; tot_rels += op_node->inlinks.size(); } } } TimeSourceDepsNode *time_source = graph->find_time_source(NULL); if (time_source != NULL) { tot_rels += time_source->inlinks.size(); } if (r_relations) *r_relations = tot_rels; if (r_outer) *r_outer = tot_outer; } }