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