96325fb38227f65580f6778966bdfe8353793736
[blender.git] / source / blender / depsgraph / intern / debug / deg_debug_graphviz.cc
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2014 Blender Foundation.
19  * All rights reserved.
20  *
21  * Original Author: Lukas Toenne
22  * Contributor(s): None Yet
23  *
24  * ***** END GPL LICENSE BLOCK *****
25  */
26
27 /** \file blender/depsgraph/intern/debug/deg_debug_graphviz.cc
28  *  \ingroup depsgraph
29  *
30  * Implementation of tools for debugging the depsgraph
31  */
32
33 #include "BLI_utildefines.h"
34 #include "BLI_ghash.h"
35
36 extern "C" {
37 #include "DNA_listBase.h"
38
39 #include "DEG_depsgraph.h"
40 #include "DEG_depsgraph_debug.h"
41 }  /* extern "C" */
42
43 #include "intern/depsgraph_intern.h"
44 #include "util/deg_util_foreach.h"
45
46 /* ****************** */
47 /* Graphviz Debugging */
48
49 namespace DEG {
50
51 #define NL "\r\n"
52
53 /* Only one should be enabled, defines whether graphviz nodes
54  * get colored by individual types or classes.
55  */
56 #define COLOR_SCHEME_NODE_CLASS 1
57 //#define COLOR_SCHEME_NODE_TYPE  2
58
59 static const char *deg_debug_graphviz_fontname = "helvetica";
60 static float deg_debug_graphviz_graph_label_size = 20.0f;
61 static float deg_debug_graphviz_node_label_size = 14.0f;
62 static const int deg_debug_max_colors = 12;
63 #ifdef COLOR_SCHEME_NODE_TYPE
64 static const char *deg_debug_colors[] = {
65     "#a6cee3", "#1f78b4", "#b2df8a",
66     "#33a02c", "#fb9a99", "#e31a1c",
67     "#fdbf6f", "#ff7f00", "#cab2d6",
68     "#6a3d9a", "#ffff99", "#b15928",
69 };
70 #endif
71 static const char *deg_debug_colors_light[] = {
72     "#8dd3c7", "#ffffb3", "#bebada",
73     "#fb8072", "#80b1d3", "#fdb462",
74     "#b3de69", "#fccde5", "#d9d9d9",
75     "#bc80bd", "#ccebc5", "#ffed6f",
76 };
77
78 #ifdef COLOR_SCHEME_NODE_TYPE
79 static const int deg_debug_node_type_color_map[][2] = {
80     {DEPSNODE_TYPE_ROOT,         0},
81     {DEPSNODE_TYPE_TIMESOURCE,   1},
82     {DEPSNODE_TYPE_ID_REF,       2},
83
84     /* Outer Types */
85     {DEPSNODE_TYPE_PARAMETERS,   3},
86     {DEPSNODE_TYPE_PROXY,        4},
87     {DEPSNODE_TYPE_ANIMATION,    5},
88     {DEPSNODE_TYPE_TRANSFORM,    6},
89     {DEPSNODE_TYPE_GEOMETRY,     7},
90     {DEPSNODE_TYPE_SEQUENCER,    8},
91     {DEPSNODE_TYPE_SHADING,      9},
92     {DEPSNODE_TYPE_CACHE,        10},
93     {-1,                         0}
94 };
95 #endif
96
97 static int deg_debug_node_color_index(const DepsNode *node)
98 {
99 #ifdef COLOR_SCHEME_NODE_CLASS
100         /* Some special types. */
101         switch (node->type) {
102                 case DEPSNODE_TYPE_ID_REF:
103                         return 5;
104                 case DEPSNODE_TYPE_OPERATION:
105                 {
106                         OperationDepsNode *op_node = (OperationDepsNode *)node;
107                         if (op_node->is_noop())
108                                 return 8;
109                         break;
110                 }
111
112                 default:
113                         break;
114         }
115         /* Do others based on class. */
116         switch (node->tclass) {
117                 case DEPSNODE_CLASS_OPERATION:
118                         return 4;
119                 case DEPSNODE_CLASS_COMPONENT:
120                         return 1;
121                 default:
122                         return 9;
123         }
124 #endif
125
126 #ifdef COLOR_SCHEME_NODE_TYPE
127         const int (*pair)[2];
128         for (pair = deg_debug_node_type_color_map; (*pair)[0] >= 0; ++pair) {
129                 if ((*pair)[0] == node->type) {
130                         return (*pair)[1];
131                 }
132         }
133         return -1;
134 #endif
135 }
136
137 struct DebugContext {
138         FILE *file;
139         bool show_tags;
140         bool show_eval_priority;
141 };
142
143 static void deg_debug_fprintf(const DebugContext &ctx, const char *fmt, ...) ATTR_PRINTF_FORMAT(2, 3);
144 static void deg_debug_fprintf(const DebugContext &ctx, const char *fmt, ...)
145 {
146         va_list args;
147         va_start(args, fmt);
148         vfprintf(ctx.file, fmt, args);
149         va_end(args);
150 }
151
152 static void deg_debug_graphviz_legend_color(const DebugContext &ctx,
153                                             const char *name,
154                                             const char *color)
155 {
156         deg_debug_fprintf(ctx, "<TR>");
157         deg_debug_fprintf(ctx, "<TD>%s</TD>", name);
158         deg_debug_fprintf(ctx, "<TD BGCOLOR=\"%s\"></TD>", color);
159         deg_debug_fprintf(ctx, "</TR>" NL);
160 }
161
162 static void deg_debug_graphviz_legend(const DebugContext &ctx)
163 {
164         deg_debug_fprintf(ctx, "{" NL);
165         deg_debug_fprintf(ctx, "rank = sink;" NL);
166         deg_debug_fprintf(ctx, "Legend [shape=none, margin=0, label=<" NL);
167         deg_debug_fprintf(ctx, "  <TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">" NL);
168         deg_debug_fprintf(ctx, "<TR><TD COLSPAN=\"2\"><B>Legend</B></TD></TR>" NL);
169
170 #ifdef COLOR_SCHEME_NODE_CLASS
171         const char **colors = deg_debug_colors_light;
172         deg_debug_graphviz_legend_color(ctx, "Operation", colors[4]);
173         deg_debug_graphviz_legend_color(ctx, "Component", colors[1]);
174         deg_debug_graphviz_legend_color(ctx, "ID Node", colors[5]);
175         deg_debug_graphviz_legend_color(ctx, "NOOP", colors[8]);
176 #endif
177
178 #ifdef COLOR_SCHEME_NODE_TYPE
179         const int (*pair)[2];
180         for (pair = deg_debug_node_type_color_map; (*pair)[0] >= 0; ++pair) {
181                 DepsNodeFactory *nti = DEG_get_node_factory((eDepsNode_Type)(*pair)[0]);
182                 deg_debug_graphviz_legend_color(ctx,
183                                                 nti->tname().c_str(),
184                                                 deg_debug_colors_light[(*pair)[1] % deg_debug_max_colors]);
185         }
186 #endif
187
188         deg_debug_fprintf(ctx, "</TABLE>" NL);
189         deg_debug_fprintf(ctx, ">" NL);
190         deg_debug_fprintf(ctx, ",fontname=\"%s\"", deg_debug_graphviz_fontname);
191         deg_debug_fprintf(ctx, "];" NL);
192         deg_debug_fprintf(ctx, "}" NL);
193 }
194
195 static void deg_debug_graphviz_node_color(const DebugContext &ctx,
196                                           const DepsNode *node)
197 {
198         const char *color_default = "black";
199         const char *color_modified = "orangered4";
200         const char *color_update = "dodgerblue3";
201         const char *color = color_default;
202         if (ctx.show_tags) {
203                 if (node->tclass == DEPSNODE_CLASS_OPERATION) {
204                         OperationDepsNode *op_node = (OperationDepsNode *)node;
205                         if (op_node->flag & DEPSOP_FLAG_DIRECTLY_MODIFIED) {
206                                 color = color_modified;
207                         }
208                         else if (op_node->flag & DEPSOP_FLAG_NEEDS_UPDATE) {
209                                 color = color_update;
210                         }
211                 }
212         }
213         deg_debug_fprintf(ctx, "\"%s\"", color);
214 }
215
216 static void deg_debug_graphviz_node_penwidth(const DebugContext &ctx,
217                                              const DepsNode *node)
218 {
219         float penwidth_default = 1.0f;
220         float penwidth_modified = 4.0f;
221         float penwidth_update = 4.0f;
222         float penwidth = penwidth_default;
223         if (ctx.show_tags) {
224                 if (node->tclass == DEPSNODE_CLASS_OPERATION) {
225                         OperationDepsNode *op_node = (OperationDepsNode *)node;
226                         if (op_node->flag & DEPSOP_FLAG_DIRECTLY_MODIFIED) {
227                                 penwidth = penwidth_modified;
228                         }
229                         else if (op_node->flag & DEPSOP_FLAG_NEEDS_UPDATE) {
230                                 penwidth = penwidth_update;
231                         }
232                 }
233         }
234         deg_debug_fprintf(ctx, "\"%f\"", penwidth);
235 }
236
237 static void deg_debug_graphviz_node_fillcolor(const DebugContext &ctx,
238                                               const DepsNode *node)
239 {
240         const char *defaultcolor = "gainsboro";
241         int color_index = deg_debug_node_color_index(node);
242         const char *fillcolor = color_index < 0 ? defaultcolor : deg_debug_colors_light[color_index % deg_debug_max_colors];
243         deg_debug_fprintf(ctx, "\"%s\"", fillcolor);
244 }
245
246 static void deg_debug_graphviz_relation_color(const DebugContext &ctx,
247                                               const DepsRelation *rel)
248 {
249         const char *color_default = "black";
250         const char *color_error = "red4";
251         const char *color = color_default;
252         if (rel->flag & DEPSREL_FLAG_CYCLIC) {
253                 color = color_error;
254         }
255         deg_debug_fprintf(ctx, "%s", color);
256 }
257
258 static void deg_debug_graphviz_node_style(const DebugContext &ctx, const DepsNode *node)
259 {
260         const char *base_style = "filled"; /* default style */
261         if (ctx.show_tags) {
262                 if (node->tclass == DEPSNODE_CLASS_OPERATION) {
263                         OperationDepsNode *op_node = (OperationDepsNode *)node;
264                         if (op_node->flag & (DEPSOP_FLAG_DIRECTLY_MODIFIED | DEPSOP_FLAG_NEEDS_UPDATE)) {
265                                 base_style = "striped";
266                         }
267                 }
268         }
269         switch (node->tclass) {
270                 case DEPSNODE_CLASS_GENERIC:
271                         deg_debug_fprintf(ctx, "\"%s\"", base_style);
272                         break;
273                 case DEPSNODE_CLASS_COMPONENT:
274                         deg_debug_fprintf(ctx, "\"%s\"", base_style);
275                         break;
276                 case DEPSNODE_CLASS_OPERATION:
277                         deg_debug_fprintf(ctx, "\"%s,rounded\"", base_style);
278                         break;
279         }
280 }
281
282 static void deg_debug_graphviz_node_single(const DebugContext &ctx,
283                                            const DepsNode *node)
284 {
285         const char *shape = "box";
286         string name = node->identifier();
287         float priority = -1.0f;
288         if (node->type == DEPSNODE_TYPE_ID_REF) {
289                 IDDepsNode *id_node = (IDDepsNode *)node;
290                 char buf[256];
291                 BLI_snprintf(buf, sizeof(buf), " (Layers: %u)", id_node->layers);
292                 name += buf;
293         }
294         if (ctx.show_eval_priority && node->tclass == DEPSNODE_CLASS_OPERATION) {
295                 priority = ((OperationDepsNode *)node)->eval_priority;
296         }
297         deg_debug_fprintf(ctx, "// %s\n", name.c_str());
298         deg_debug_fprintf(ctx, "\"node_%p\"", node);
299         deg_debug_fprintf(ctx, "[");
300 //      deg_debug_fprintf(ctx, "label=<<B>%s</B>>", name);
301         if (priority >= 0.0f) {
302                 deg_debug_fprintf(ctx, "label=<%s<BR/>(<I>%.2f</I>)>",
303                                  name.c_str(),
304                                  priority);
305         }
306         else {
307                 deg_debug_fprintf(ctx, "label=<%s>", name.c_str());
308         }
309         deg_debug_fprintf(ctx, ",fontname=\"%s\"", deg_debug_graphviz_fontname);
310         deg_debug_fprintf(ctx, ",fontsize=%f", deg_debug_graphviz_node_label_size);
311         deg_debug_fprintf(ctx, ",shape=%s", shape);
312         deg_debug_fprintf(ctx, ",style="); deg_debug_graphviz_node_style(ctx, node);
313         deg_debug_fprintf(ctx, ",color="); deg_debug_graphviz_node_color(ctx, node);
314         deg_debug_fprintf(ctx, ",fillcolor="); deg_debug_graphviz_node_fillcolor(ctx, node);
315         deg_debug_fprintf(ctx, ",penwidth="); deg_debug_graphviz_node_penwidth(ctx, node);
316         deg_debug_fprintf(ctx, "];" NL);
317         deg_debug_fprintf(ctx, NL);
318 }
319
320 static void deg_debug_graphviz_node_cluster_begin(const DebugContext &ctx,
321                                                   const DepsNode *node)
322 {
323         string name = node->identifier();
324         if (node->type == DEPSNODE_TYPE_ID_REF) {
325                 IDDepsNode *id_node = (IDDepsNode *)node;
326                 char buf[256];
327                 BLI_snprintf(buf, sizeof(buf), " (Layers: %u)", id_node->layers);
328                 name += buf;
329         }
330         deg_debug_fprintf(ctx, "// %s\n", name.c_str());
331         deg_debug_fprintf(ctx, "subgraph \"cluster_%p\" {" NL, node);
332 //      deg_debug_fprintf(ctx, "label=<<B>%s</B>>;" NL, name);
333         deg_debug_fprintf(ctx, "label=<%s>;" NL, name.c_str());
334         deg_debug_fprintf(ctx, "fontname=\"%s\";" NL, deg_debug_graphviz_fontname);
335         deg_debug_fprintf(ctx, "fontsize=%f;" NL, deg_debug_graphviz_node_label_size);
336         deg_debug_fprintf(ctx, "margin=\"%d\";" NL, 16);
337         deg_debug_fprintf(ctx, "style="); deg_debug_graphviz_node_style(ctx, node); deg_debug_fprintf(ctx, ";" NL);
338         deg_debug_fprintf(ctx, "color="); deg_debug_graphviz_node_color(ctx, node); deg_debug_fprintf(ctx, ";" NL);
339         deg_debug_fprintf(ctx, "fillcolor="); deg_debug_graphviz_node_fillcolor(ctx, node); deg_debug_fprintf(ctx, ";" NL);
340         deg_debug_fprintf(ctx, "penwidth="); deg_debug_graphviz_node_penwidth(ctx, node); deg_debug_fprintf(ctx, ";" NL);
341         /* dummy node, so we can add edges between clusters */
342         deg_debug_fprintf(ctx, "\"node_%p\"", node);
343         deg_debug_fprintf(ctx, "[");
344         deg_debug_fprintf(ctx, "shape=%s", "point");
345         deg_debug_fprintf(ctx, ",style=%s", "invis");
346         deg_debug_fprintf(ctx, "];" NL);
347         deg_debug_fprintf(ctx, NL);
348 }
349
350 static void deg_debug_graphviz_node_cluster_end(const DebugContext &ctx)
351 {
352         deg_debug_fprintf(ctx, "}" NL);
353         deg_debug_fprintf(ctx, NL);
354 }
355
356 static void deg_debug_graphviz_graph_nodes(const DebugContext &ctx,
357                                            const Depsgraph *graph);
358 static void deg_debug_graphviz_graph_relations(const DebugContext &ctx,
359                                                const Depsgraph *graph);
360
361 static void deg_debug_graphviz_node(const DebugContext &ctx,
362                                     const DepsNode *node)
363 {
364         switch (node->type) {
365                 case DEPSNODE_TYPE_ID_REF:
366                 {
367                         const IDDepsNode *id_node = (const IDDepsNode *)node;
368                         if (BLI_ghash_size(id_node->components) == 0) {
369                                 deg_debug_graphviz_node_single(ctx, node);
370                         }
371                         else {
372                                 deg_debug_graphviz_node_cluster_begin(ctx, node);
373                                 GHASH_FOREACH_BEGIN(const ComponentDepsNode *, comp, id_node->components)
374                                 {
375                                         deg_debug_graphviz_node(ctx, comp);
376                                 }
377                                 GHASH_FOREACH_END();
378                                 deg_debug_graphviz_node_cluster_end(ctx);
379                         }
380                         break;
381                 }
382                 case DEPSNODE_TYPE_PARAMETERS:
383                 case DEPSNODE_TYPE_ANIMATION:
384                 case DEPSNODE_TYPE_TRANSFORM:
385                 case DEPSNODE_TYPE_PROXY:
386                 case DEPSNODE_TYPE_GEOMETRY:
387                 case DEPSNODE_TYPE_SEQUENCER:
388                 case DEPSNODE_TYPE_EVAL_POSE:
389                 case DEPSNODE_TYPE_BONE:
390                 case DEPSNODE_TYPE_SHADING:
391                 case DEPSNODE_TYPE_CACHE:
392                 case DEPSNODE_TYPE_EVAL_PARTICLES:
393                 {
394                         ComponentDepsNode *comp_node = (ComponentDepsNode *)node;
395                         if (!comp_node->operations.empty()) {
396                                 deg_debug_graphviz_node_cluster_begin(ctx, node);
397                                 foreach (DepsNode *op_node, comp_node->operations) {
398                                         deg_debug_graphviz_node(ctx, op_node);
399                                 }
400                                 deg_debug_graphviz_node_cluster_end(ctx);
401                         }
402                         else {
403                                 deg_debug_graphviz_node_single(ctx, node);
404                         }
405                         break;
406                 }
407                 default:
408                         deg_debug_graphviz_node_single(ctx, node);
409                         break;
410         }
411 }
412
413 static bool deg_debug_graphviz_is_cluster(const DepsNode *node)
414 {
415         switch (node->type) {
416                 case DEPSNODE_TYPE_ID_REF:
417                 {
418                         const IDDepsNode *id_node = (const IDDepsNode *)node;
419                         return BLI_ghash_size(id_node->components) > 0;
420                 }
421                 case DEPSNODE_TYPE_PARAMETERS:
422                 case DEPSNODE_TYPE_ANIMATION:
423                 case DEPSNODE_TYPE_TRANSFORM:
424                 case DEPSNODE_TYPE_PROXY:
425                 case DEPSNODE_TYPE_GEOMETRY:
426                 case DEPSNODE_TYPE_SEQUENCER:
427                 case DEPSNODE_TYPE_EVAL_POSE:
428                 case DEPSNODE_TYPE_BONE:
429                 {
430                         ComponentDepsNode *comp_node = (ComponentDepsNode *)node;
431                         return !comp_node->operations.empty();
432                 }
433                 default:
434                         return false;
435         }
436 }
437
438 static bool deg_debug_graphviz_is_owner(const DepsNode *node,
439                                         const DepsNode *other)
440 {
441         switch (node->tclass) {
442                 case DEPSNODE_CLASS_COMPONENT:
443                 {
444                         ComponentDepsNode *comp_node = (ComponentDepsNode *)node;
445                         if (comp_node->owner == other)
446                                 return true;
447                         break;
448                 }
449                 case DEPSNODE_CLASS_OPERATION:
450                 {
451                         OperationDepsNode *op_node = (OperationDepsNode *)node;
452                         if (op_node->owner == other)
453                                 return true;
454                         else if (op_node->owner->owner == other)
455                                 return true;
456                         break;
457                 }
458                 default: break;
459         }
460         return false;
461 }
462
463 static void deg_debug_graphviz_node_relations(const DebugContext &ctx,
464                                               const DepsNode *node)
465 {
466         foreach (DepsRelation *rel, node->inlinks) {
467                 float penwidth = 2.0f;
468
469                 const DepsNode *tail = rel->to; /* same as node */
470                 const DepsNode *head = rel->from;
471                 deg_debug_fprintf(ctx, "// %s -> %s\n",
472                                  head->identifier().c_str(),
473                                  tail->identifier().c_str());
474                 deg_debug_fprintf(ctx, "\"node_%p\"", head);
475                 deg_debug_fprintf(ctx, " -> ");
476                 deg_debug_fprintf(ctx, "\"node_%p\"", tail);
477
478                 deg_debug_fprintf(ctx, "[");
479                 /* Note: without label an id seem necessary to avoid bugs in graphviz/dot */
480                 deg_debug_fprintf(ctx, "id=\"%s\"", rel->name);
481                 deg_debug_fprintf(ctx, ",color="); deg_debug_graphviz_relation_color(ctx, rel);
482                 deg_debug_fprintf(ctx, ",penwidth=\"%f\"", penwidth);
483                 /* NOTE: edge from node to own cluster is not possible and gives graphviz
484                  * warning, avoid this here by just linking directly to the invisible
485                  * placeholder node
486                  */
487                 if (deg_debug_graphviz_is_cluster(tail) && !deg_debug_graphviz_is_owner(head, tail)) {
488                         deg_debug_fprintf(ctx, ",ltail=\"cluster_%p\"", tail);
489                 }
490                 if (deg_debug_graphviz_is_cluster(head) && !deg_debug_graphviz_is_owner(tail, head)) {
491                         deg_debug_fprintf(ctx, ",lhead=\"cluster_%p\"", head);
492                 }
493                 deg_debug_fprintf(ctx, "];" NL);
494                 deg_debug_fprintf(ctx, NL);
495         }
496 }
497
498 static void deg_debug_graphviz_graph_nodes(const DebugContext &ctx,
499                                            const Depsgraph *graph)
500 {
501         if (graph->root_node) {
502                 deg_debug_graphviz_node(ctx, graph->root_node);
503         }
504         GHASH_FOREACH_BEGIN (DepsNode *, node, graph->id_hash)
505         {
506                 deg_debug_graphviz_node(ctx, node);
507         }
508         GHASH_FOREACH_END();
509         TimeSourceDepsNode *time_source = graph->find_time_source();
510         if (time_source != NULL) {
511                 deg_debug_graphviz_node(ctx, time_source);
512         }
513 }
514
515 static void deg_debug_graphviz_graph_relations(const DebugContext &ctx,
516                                                const Depsgraph *graph)
517 {
518         GHASH_FOREACH_BEGIN(IDDepsNode *, id_node, graph->id_hash)
519         {
520                 GHASH_FOREACH_BEGIN(ComponentDepsNode *, comp_node, id_node->components)
521                 {
522                         foreach (OperationDepsNode *op_node, comp_node->operations) {
523                                 deg_debug_graphviz_node_relations(ctx, op_node);
524                         }
525                 }
526                 GHASH_FOREACH_END();
527         }
528         GHASH_FOREACH_END();
529
530         TimeSourceDepsNode *time_source = graph->find_time_source();
531         if (time_source != NULL) {
532                 deg_debug_graphviz_node_relations(ctx, time_source);
533         }
534 }
535
536 }  // namespace DEG
537
538 void DEG_debug_graphviz(const Depsgraph *graph, FILE *f, const char *label, bool show_eval)
539 {
540         if (!graph) {
541                 return;
542         }
543
544         const DEG::Depsgraph *deg_graph = reinterpret_cast<const DEG::Depsgraph *>(graph);
545
546         DEG::DebugContext ctx;
547         ctx.file = f;
548         ctx.show_tags = show_eval;
549         ctx.show_eval_priority = show_eval;
550
551         DEG::deg_debug_fprintf(ctx, "digraph depgraph {" NL);
552         DEG::deg_debug_fprintf(ctx, "rankdir=LR;" NL);
553         DEG::deg_debug_fprintf(ctx, "graph [");
554         DEG::deg_debug_fprintf(ctx, "compound=true");
555         DEG::deg_debug_fprintf(ctx, ",labelloc=\"t\"");
556         DEG::deg_debug_fprintf(ctx, ",fontsize=%f", DEG::deg_debug_graphviz_graph_label_size);
557         DEG::deg_debug_fprintf(ctx, ",fontname=\"%s\"", DEG::deg_debug_graphviz_fontname);
558         DEG::deg_debug_fprintf(ctx, ",label=\"%s\"", label);
559         DEG::deg_debug_fprintf(ctx, ",splines=ortho");
560         DEG::deg_debug_fprintf(ctx, ",overlap=scalexy"); // XXX: only when using neato
561         DEG::deg_debug_fprintf(ctx, "];" NL);
562
563         DEG::deg_debug_graphviz_graph_nodes(ctx, deg_graph);
564         DEG::deg_debug_graphviz_graph_relations(ctx, deg_graph);
565
566         DEG::deg_debug_graphviz_legend(ctx);
567
568         DEG::deg_debug_fprintf(ctx, "}" NL);
569 }
570
571 #undef NL