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