Depsgraph: Make it more clear that we dump relations into graphviz
[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,       2},
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_CACHE,        9},
95     {-1,                         0}
96 };
97 #endif
98
99 static int deg_debug_node_color_index(const DepsNode *node)
100 {
101 #ifdef COLOR_SCHEME_NODE_CLASS
102         /* Some special types. */
103         switch (node->type) {
104                 case DEG_NODE_TYPE_ID_REF:
105                         return 5;
106                 case DEG_NODE_TYPE_OPERATION:
107                 {
108                         OperationDepsNode *op_node = (OperationDepsNode *)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 DEG_NODE_CLASS_OPERATION:
120                         return 4;
121                 case DEG_NODE_CLASS_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 = deg_type_get_factory((eDepsNode_Type)(*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 DepsNode *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() == DEG_NODE_CLASS_OPERATION) {
205                         OperationDepsNode *op_node = (OperationDepsNode *)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 DepsNode *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() == DEG_NODE_CLASS_OPERATION) {
226                         OperationDepsNode *op_node = (OperationDepsNode *)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 DepsNode *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 DepsRelation *rel)
249 {
250         const char *color_default = "black";
251         const char *color_error = "red4";
252         const char *color = color_default;
253         if (rel->flag & DEPSREL_FLAG_CYCLIC) {
254                 color = color_error;
255         }
256         deg_debug_fprintf(ctx, "%s", color);
257 }
258
259 static void deg_debug_graphviz_node_style(const DebugContext &ctx, const DepsNode *node)
260 {
261         const char *base_style = "filled"; /* default style */
262         if (ctx.show_tags) {
263                 if (node->get_class() == DEG_NODE_CLASS_OPERATION) {
264                         OperationDepsNode *op_node = (OperationDepsNode *)node;
265                         if (op_node->flag & (DEPSOP_FLAG_DIRECTLY_MODIFIED | DEPSOP_FLAG_NEEDS_UPDATE)) {
266                                 base_style = "striped";
267                         }
268                 }
269         }
270         switch (node->get_class()) {
271                 case DEG_NODE_CLASS_GENERIC:
272                         deg_debug_fprintf(ctx, "\"%s\"", base_style);
273                         break;
274                 case DEG_NODE_CLASS_COMPONENT:
275                         deg_debug_fprintf(ctx, "\"%s\"", base_style);
276                         break;
277                 case DEG_NODE_CLASS_OPERATION:
278                         deg_debug_fprintf(ctx, "\"%s,rounded\"", base_style);
279                         break;
280         }
281 }
282
283 static void deg_debug_graphviz_node_single(const DebugContext &ctx,
284                                            const DepsNode *node)
285 {
286         const char *shape = "box";
287         string name = node->identifier();
288         if (node->type == DEG_NODE_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         deg_debug_fprintf(ctx, "// %s\n", name.c_str());
295         deg_debug_fprintf(ctx, "\"node_%p\"", node);
296         deg_debug_fprintf(ctx, "[");
297 //      deg_debug_fprintf(ctx, "label=<<B>%s</B>>", name);
298         deg_debug_fprintf(ctx, "label=<%s>", name.c_str());
299         deg_debug_fprintf(ctx, ",fontname=\"%s\"", deg_debug_graphviz_fontname);
300         deg_debug_fprintf(ctx, ",fontsize=%f", deg_debug_graphviz_node_label_size);
301         deg_debug_fprintf(ctx, ",shape=%s", shape);
302         deg_debug_fprintf(ctx, ",style="); deg_debug_graphviz_node_style(ctx, node);
303         deg_debug_fprintf(ctx, ",color="); deg_debug_graphviz_node_color(ctx, node);
304         deg_debug_fprintf(ctx, ",fillcolor="); deg_debug_graphviz_node_fillcolor(ctx, node);
305         deg_debug_fprintf(ctx, ",penwidth="); deg_debug_graphviz_node_penwidth(ctx, node);
306         deg_debug_fprintf(ctx, "];" NL);
307         deg_debug_fprintf(ctx, NL);
308 }
309
310 static void deg_debug_graphviz_node_cluster_begin(const DebugContext &ctx,
311                                                   const DepsNode *node)
312 {
313         string name = node->identifier();
314         if (node->type == DEG_NODE_TYPE_ID_REF) {
315                 IDDepsNode *id_node = (IDDepsNode *)node;
316                 char buf[256];
317                 BLI_snprintf(buf, sizeof(buf), " (Layers: %u)", id_node->layers);
318                 name += buf;
319         }
320         deg_debug_fprintf(ctx, "// %s\n", name.c_str());
321         deg_debug_fprintf(ctx, "subgraph \"cluster_%p\" {" NL, node);
322 //      deg_debug_fprintf(ctx, "label=<<B>%s</B>>;" NL, name);
323         deg_debug_fprintf(ctx, "label=<%s>;" NL, name.c_str());
324         deg_debug_fprintf(ctx, "fontname=\"%s\";" NL, deg_debug_graphviz_fontname);
325         deg_debug_fprintf(ctx, "fontsize=%f;" NL, deg_debug_graphviz_node_label_size);
326         deg_debug_fprintf(ctx, "margin=\"%d\";" NL, 16);
327         deg_debug_fprintf(ctx, "style="); deg_debug_graphviz_node_style(ctx, node); deg_debug_fprintf(ctx, ";" NL);
328         deg_debug_fprintf(ctx, "color="); deg_debug_graphviz_node_color(ctx, node); deg_debug_fprintf(ctx, ";" NL);
329         deg_debug_fprintf(ctx, "fillcolor="); deg_debug_graphviz_node_fillcolor(ctx, node); deg_debug_fprintf(ctx, ";" NL);
330         deg_debug_fprintf(ctx, "penwidth="); deg_debug_graphviz_node_penwidth(ctx, node); deg_debug_fprintf(ctx, ";" NL);
331         /* dummy node, so we can add edges between clusters */
332         deg_debug_fprintf(ctx, "\"node_%p\"", node);
333         deg_debug_fprintf(ctx, "[");
334         deg_debug_fprintf(ctx, "shape=%s", "point");
335         deg_debug_fprintf(ctx, ",style=%s", "invis");
336         deg_debug_fprintf(ctx, "];" NL);
337         deg_debug_fprintf(ctx, NL);
338 }
339
340 static void deg_debug_graphviz_node_cluster_end(const DebugContext &ctx)
341 {
342         deg_debug_fprintf(ctx, "}" NL);
343         deg_debug_fprintf(ctx, NL);
344 }
345
346 static void deg_debug_graphviz_graph_nodes(const DebugContext &ctx,
347                                            const Depsgraph *graph);
348 static void deg_debug_graphviz_graph_relations(const DebugContext &ctx,
349                                                const Depsgraph *graph);
350
351 static void deg_debug_graphviz_node(const DebugContext &ctx,
352                                     const DepsNode *node)
353 {
354         switch (node->type) {
355                 case DEG_NODE_TYPE_ID_REF:
356                 {
357                         const IDDepsNode *id_node = (const IDDepsNode *)node;
358                         if (BLI_ghash_size(id_node->components) == 0) {
359                                 deg_debug_graphviz_node_single(ctx, node);
360                         }
361                         else {
362                                 deg_debug_graphviz_node_cluster_begin(ctx, node);
363                                 GHASH_FOREACH_BEGIN(const ComponentDepsNode *, comp, id_node->components)
364                                 {
365                                         deg_debug_graphviz_node(ctx, comp);
366                                 }
367                                 GHASH_FOREACH_END();
368                                 deg_debug_graphviz_node_cluster_end(ctx);
369                         }
370                         break;
371                 }
372                 case DEG_NODE_TYPE_PARAMETERS:
373                 case DEG_NODE_TYPE_ANIMATION:
374                 case DEG_NODE_TYPE_TRANSFORM:
375                 case DEG_NODE_TYPE_PROXY:
376                 case DEG_NODE_TYPE_GEOMETRY:
377                 case DEG_NODE_TYPE_SEQUENCER:
378                 case DEG_NODE_TYPE_EVAL_POSE:
379                 case DEG_NODE_TYPE_BONE:
380                 case DEG_NODE_TYPE_SHADING:
381                 case DEG_NODE_TYPE_CACHE:
382                 case DEG_NODE_TYPE_EVAL_PARTICLES:
383                 {
384                         ComponentDepsNode *comp_node = (ComponentDepsNode *)node;
385                         if (!comp_node->operations.empty()) {
386                                 deg_debug_graphviz_node_cluster_begin(ctx, node);
387                                 foreach (DepsNode *op_node, comp_node->operations) {
388                                         deg_debug_graphviz_node(ctx, op_node);
389                                 }
390                                 deg_debug_graphviz_node_cluster_end(ctx);
391                         }
392                         else {
393                                 deg_debug_graphviz_node_single(ctx, node);
394                         }
395                         break;
396                 }
397                 default:
398                         deg_debug_graphviz_node_single(ctx, node);
399                         break;
400         }
401 }
402
403 static bool deg_debug_graphviz_is_cluster(const DepsNode *node)
404 {
405         switch (node->type) {
406                 case DEG_NODE_TYPE_ID_REF:
407                 {
408                         const IDDepsNode *id_node = (const IDDepsNode *)node;
409                         return BLI_ghash_size(id_node->components) > 0;
410                 }
411                 case DEG_NODE_TYPE_PARAMETERS:
412                 case DEG_NODE_TYPE_ANIMATION:
413                 case DEG_NODE_TYPE_TRANSFORM:
414                 case DEG_NODE_TYPE_PROXY:
415                 case DEG_NODE_TYPE_GEOMETRY:
416                 case DEG_NODE_TYPE_SEQUENCER:
417                 case DEG_NODE_TYPE_EVAL_POSE:
418                 case DEG_NODE_TYPE_BONE:
419                 {
420                         ComponentDepsNode *comp_node = (ComponentDepsNode *)node;
421                         return !comp_node->operations.empty();
422                 }
423                 default:
424                         return false;
425         }
426 }
427
428 static bool deg_debug_graphviz_is_owner(const DepsNode *node,
429                                         const DepsNode *other)
430 {
431         switch (node->get_class()) {
432                 case DEG_NODE_CLASS_COMPONENT:
433                 {
434                         ComponentDepsNode *comp_node = (ComponentDepsNode *)node;
435                         if (comp_node->owner == other)
436                                 return true;
437                         break;
438                 }
439                 case DEG_NODE_CLASS_OPERATION:
440                 {
441                         OperationDepsNode *op_node = (OperationDepsNode *)node;
442                         if (op_node->owner == other)
443                                 return true;
444                         else if (op_node->owner->owner == other)
445                                 return true;
446                         break;
447                 }
448                 default: break;
449         }
450         return false;
451 }
452
453 static void deg_debug_graphviz_node_relations(const DebugContext &ctx,
454                                               const DepsNode *node)
455 {
456         foreach (DepsRelation *rel, node->inlinks) {
457                 float penwidth = 2.0f;
458
459                 const DepsNode *tail = rel->to; /* same as node */
460                 const DepsNode *head = rel->from;
461                 deg_debug_fprintf(ctx, "// %s -> %s\n",
462                                  head->identifier().c_str(),
463                                  tail->identifier().c_str());
464                 deg_debug_fprintf(ctx, "\"node_%p\"", head);
465                 deg_debug_fprintf(ctx, " -> ");
466                 deg_debug_fprintf(ctx, "\"node_%p\"", tail);
467
468                 deg_debug_fprintf(ctx, "[");
469                 /* Note: without label an id seem necessary to avoid bugs in graphviz/dot */
470                 deg_debug_fprintf(ctx, "id=\"%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