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