]> git.cworth.org Git - apitrace/blob - cli/cli_trim.cpp
trim: Add support for display lists.
[apitrace] / cli / cli_trim.cpp
1 /**************************************************************************
2  *
3  * Copyright 2010 VMware, Inc.
4  * Copyright 2011 Intel corporation
5  * All Rights Reserved.
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23  * THE SOFTWARE.
24  *
25  **************************************************************************/
26
27 #include <sstream>
28 #include <string.h>
29 #include <limits.h> // for CHAR_MAX
30 #include <getopt.h>
31
32 #include <GL/gl.h>
33 #include <GL/glext.h>
34
35 #include <set>
36
37 #include "cli.hpp"
38
39 #include "os_string.hpp"
40
41 #include "trace_callset.hpp"
42 #include "trace_parser.hpp"
43 #include "trace_writer.hpp"
44
45 #define MAX(a, b) ((a) > (b) ? (a) : (b))
46 #define STRNCMP_LITERAL(var, literal) strncmp((var), (literal), sizeof (literal) -1)
47
48 static const char *synopsis = "Create a new trace by trimming an existing trace.";
49
50 static void
51 usage(void)
52 {
53     std::cout
54         << "usage: apitrace trim [OPTIONS] TRACE_FILE...\n"
55         << synopsis << "\n"
56         "\n"
57         "    -h, --help               Show detailed help for trim options and exit\n"
58         "        --calls=CALLSET      Include specified calls in the trimmed output.\n"
59         "        --frames=FRAMESET    Include specified frames in the trimmed output.\n"
60         "        --deps               Include additional calls to satisfy dependencies\n"
61         "        --no-deps            Do not include calls from dependency analysis\n"
62         "        --prune              Omit uninteresting calls from the trace output\n"
63         "        --no-prune           Do not prune uninteresting calls from the trace.\n"
64         "    -x, --exact              Trim exactly to calls specified in --calls/--frames\n"
65         "                             Equivalent to both --no-deps and --no-prune\n"
66         "        --print-callset      Print the final set of calls included in output\n"
67         "        --thread=THREAD_ID   Only retain calls from specified thread\n"
68         "    -o, --output=TRACE_FILE  Output trace file\n"
69     ;
70 }
71
72 static void
73 help()
74 {
75     std::cout
76         << "usage: apitrace trim [OPTIONS] TRACE_FILE...\n"
77         << synopsis << "\n"
78         "\n"
79         "    -h, --help               Show this help message and exit\n"
80         "\n"
81         "        --calls=CALLSET      Include specified calls in the trimmed output.\n"
82         "        --frames=FRAMESET    Include specified frames in the trimmed output.\n"
83         "                             Note that due to dependency analysis and pruning\n"
84         "                             of uninteresting calls the resulting trace may\n"
85         "                             include more and less calls than specified.\n"
86         "                             See --no-deps, --no-prune, and --exact to change\n"
87         "                             this behavior.\n"
88         "\n"
89         "        --deps               Perform dependency analysis and include dependent\n"
90         "                             calls as needed, (even if those calls were not\n"
91         "                             explicitly requested with --calls or --frames).\n"
92         "                             This is the default behavior. See --no-deps and\n"
93         "                             --exact to change the behavior.\n"
94         "\n"
95         "        --no-deps            Do not perform dependency analysis. In this mode\n"
96         "                             the trimmed trace will never include calls from\n"
97         "                             outside what is specified in --calls or --frames.\n"
98         "\n"
99         "        --prune              Omit calls with no side effects, even if the call\n"
100         "                             is within the range specified by --calls/--frames.\n"
101         "                             This is the default behavior. See --no-prune.\n"
102         "\n"
103         "        --no-prune           Do not prune uninteresting calls from the trace.\n"
104         "                             In this mode the trimmed trace will never omit\n"
105         "                             any calls within the user-specified range.\n"
106         "\n"
107         "    -x, --exact              Trim the trace to exactly the calls specified in\n"
108         "                             --calls and --frames. This option is equivalent\n"
109         "                             to passing both --no-deps and --no-prune.\n"
110         "\n"
111         "        --print-callset      Print to stdout the final set of calls included\n"
112         "                             in the trim output. This can be useful for\n"
113         "                             debugging trim operations by using a modified\n"
114         "                             callset on the command-line along with --exact.\n"
115         "                             Use --calls=@<file> to read callset from a file.\n"
116         "\n"
117         "        --thread=THREAD_ID   Only retain calls from specified thread\n"
118         "\n"
119         "    -o, --output=TRACE_FILE  Output trace file\n"
120         "\n"
121     ;
122 }
123
124 enum {
125     CALLS_OPT = CHAR_MAX + 1,
126     FRAMES_OPT,
127     DEPS_OPT,
128     NO_DEPS_OPT,
129     PRUNE_OPT,
130     NO_PRUNE_OPT,
131     THREAD_OPT,
132     PRINT_CALLSET_OPT,
133 };
134
135 const static char *
136 shortOptions = "ho:x";
137
138 const static struct option
139 longOptions[] = {
140     {"help", no_argument, 0, 'h'},
141     {"calls", required_argument, 0, CALLS_OPT},
142     {"frames", required_argument, 0, FRAMES_OPT},
143     {"deps", no_argument, 0, DEPS_OPT},
144     {"no-deps", no_argument, 0, NO_DEPS_OPT},
145     {"prune", no_argument, 0, PRUNE_OPT},
146     {"no-prune", no_argument, 0, NO_PRUNE_OPT},
147     {"exact", no_argument, 0, 'x'},
148     {"thread", required_argument, 0, THREAD_OPT},
149     {"output", required_argument, 0, 'o'},
150     {"print-callset", no_argument, 0, PRINT_CALLSET_OPT},
151     {0, 0, 0, 0}
152 };
153
154 struct stringCompare {
155     bool operator() (const char *a, const char *b) const {
156         return strcmp(a, b) < 0;
157     }
158 };
159
160 class TraceAnalyzer {
161     /* Maps for tracking resource dependencies between calls. */
162     std::map<std::string, std::set<unsigned> > resources;
163     std::map<std::string, std::set<std::string> > dependencies;
164
165     /* Maps for tracking OpenGL state. */
166     std::map<GLenum, unsigned> texture_map;
167
168     /* The final set of calls required. This consists of calls added
169      * explicitly with the require() method as well as all calls
170      * implicitly required by those through resource dependencies. */
171     std::set<unsigned> required;
172
173     bool transformFeedbackActive;
174     bool framebufferObjectActive;
175     bool insideBeginEnd;
176     GLuint insideNewEndList;
177     GLuint activeProgram;
178     GLenum activeTextureUnit;
179
180     /* Rendering often has no side effects, but it can in some cases,
181      * (such as when transform feedback is active, or when rendering
182      * targets a framebuffer object). */
183     bool renderingHasSideEffect() {
184         return transformFeedbackActive || framebufferObjectActive;
185     }
186
187     /* Provide: Record that the given call affects the given resource
188      * as a side effect. */
189     void provide(std::string resource, trace::CallNo call_no) {
190         resources[resource].insert(call_no);
191     }
192
193     /* Like provide, but with a simply-formatted string, (appending an
194      * integer to the given string). */
195     void providef(std::string resource, int resource_no, trace::CallNo call_no) {
196         std::stringstream ss;
197         ss << resource << resource_no;
198         provide(ss.str(), call_no);
199     }
200
201     /* Link: Establish a dependency between resource 'resource' and
202      * resource 'dependency'. This dependency is captured by name so
203      * that if the list of calls that provide 'dependency' grows
204      * before 'resource' is consumed, those calls will still be
205      * captured. */
206     void link(std::string resource, std::string dependency) {
207         dependencies[resource].insert(dependency);
208     }
209
210     /* Like link, but with a simply-formatted string, (appending an
211      * integer to the given string). */
212     void linkf(std::string resource, std::string dependency, int dep_no) {
213
214         std::stringstream ss;
215         ss << dependency << dep_no;
216         link(resource, ss.str());
217     }
218
219     /* Unlink: Remove dependency from 'resource' on 'dependency'. */
220     void unlink(std::string resource, std::string dependency) {
221         dependencies[resource].erase(dependency);
222         if (dependencies[resource].size() == 0) {
223             dependencies.erase(resource);
224         }
225     }
226
227     /* Like unlink, but with a simply-formated string, (appending an
228      * integer to the given string). */
229     void unlinkf(std::string resource, std::string dependency, int dep_no) {
230
231         std::stringstream ss;
232         ss << dependency << dep_no;
233         unlink(resource, ss.str());
234     }
235
236     /* Unlink all: Remove dependencies from 'resource' to all other
237      * resources. */
238     void unlinkAll(std::string resource) {
239         dependencies.erase(resource);
240     }
241
242     /* Resolve: Recursively compute all calls providing 'resource',
243      * (including linked dependencies of 'resource' on other
244      * resources). */
245     std::set<unsigned> resolve(std::string resource) {
246         std::set<std::string> *deps;
247         std::set<std::string>::iterator dep;
248
249         std::set<unsigned> *calls;
250         std::set<unsigned>::iterator call;
251
252         std::set<unsigned> result, deps_set;
253
254         /* Recursively chase dependencies. */
255         if (dependencies.count(resource)) {
256             deps = &dependencies[resource];
257             for (dep = deps->begin(); dep != deps->end(); dep++) {
258                 deps_set = resolve(*dep);
259                 for (call = deps_set.begin(); call != deps_set.end(); call++) {
260                     result.insert(*call);
261                 }
262             }
263         }
264
265         /* Also look for calls that directly provide 'resource' */
266         if (resources.count(resource)) {
267             calls = &resources[resource];
268             for (call = calls->begin(); call != calls->end(); call++) {
269                 result.insert(*call);
270             }
271         }
272
273         return result;
274     }
275
276     /* Consume: Resolve all calls that provide the given resource, and
277      * add them to the required list. Then clear the call list for
278      * 'resource' along with any dependencies. */
279     void consume(std::string resource) {
280
281         std::set<unsigned> calls;
282         std::set<unsigned>::iterator call;
283
284         calls = resolve(resource);
285
286         dependencies.erase(resource);
287         resources.erase(resource);
288
289         for (call = calls.begin(); call != calls.end(); call++) {
290             required.insert(*call);
291         }
292     }
293
294     void stateTrackPreCall(trace::Call *call) {
295
296         const char *name = call->name();
297
298         if (strcmp(name, "glBegin") == 0) {
299             insideBeginEnd = true;
300             return;
301         }
302
303         if (strcmp(name, "glBeginTransformFeedback") == 0) {
304             transformFeedbackActive = true;
305             return;
306         }
307
308         if (strcmp(name, "glActiveTexture") == 0) {
309             activeTextureUnit = static_cast<GLenum>(call->arg(0).toSInt());
310             return;
311         }
312
313         if (strcmp(name, "glBindTexture") == 0) {
314             GLenum target;
315             GLuint texture;
316
317             target = static_cast<GLenum>(call->arg(0).toSInt());
318             texture = call->arg(1).toUInt();
319
320             if (texture == 0) {
321                 texture_map.erase(target);
322             } else {
323                 texture_map[target] = texture;
324             }
325
326             return;
327         }
328
329         if (strcmp(name, "glUseProgram") == 0) {
330             activeProgram = call->arg(0).toUInt();
331         }
332
333         if (strcmp(name, "glBindFramebuffer") == 0) {
334             GLenum target;
335             GLuint framebuffer;
336
337             target = static_cast<GLenum>(call->arg(0).toSInt());
338             framebuffer = call->arg(1).toUInt();
339
340             if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) {
341                 if (framebuffer == 0) {
342                     framebufferObjectActive = false;
343                 } else {
344                     framebufferObjectActive = true;
345                 }
346             }
347             return;
348         }
349
350         if (strcmp(name, "glNewList") == 0) {
351             GLuint list = call->arg(0).toUInt();
352
353             insideNewEndList = list;
354         }
355     }
356
357     void stateTrackPostCall(trace::Call *call) {
358
359         const char *name = call->name();
360
361         if (strcmp(name, "glEnd") == 0) {
362             insideBeginEnd = false;
363             return;
364         }
365
366         if (strcmp(name, "glEndTransformFeedback") == 0) {
367             transformFeedbackActive = false;
368             return;
369         }
370
371         /* If this swapbuffers was included in the trace then it will
372          * have already consumed all framebuffer dependencies. If not,
373          * then clear them now so that they don't carry over into the
374          * next frame. */
375         if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
376             call->flags & trace::CALL_FLAG_END_FRAME) {
377             dependencies.erase("framebuffer");
378             resources.erase("framebuffer");
379             return;
380         }
381
382         if (strcmp(name, "glEndList") == 0) {
383             insideNewEndList = 0;
384         }
385     }
386
387     void recordSideEffects(trace::Call *call) {
388
389         const char *name = call->name();
390
391         /* Handle display lists before any other processing. */
392
393         /* FIXME: If we encode the list of commands that are executed
394          * immediately (as opposed to those that are compiled into a
395          * display list) then we could generate a "display-list-X"
396          * resource just as we do for "texture-X" resources and only
397          * emit it in the trace if a glCallList(X) is emitted. For
398          * now, simply punt and include anything within glNewList and
399          * glEndList in the trim output. This guarantees that display
400          * lists will work, but does not trim out unused display
401          * lists. */
402         if (insideNewEndList != 0) {
403             provide("state", call->no);
404             return;
405         }
406
407         /* If call is flagged as no side effects, then we are done here. */
408         if (call->flags & trace::CALL_FLAG_NO_SIDE_EFFECTS) {
409             return;
410         }
411
412         /* Similarly, swap-buffers calls don't have interesting side effects. */
413         if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
414             call->flags & trace::CALL_FLAG_END_FRAME) {
415             return;
416         }
417
418         if (strcmp(name, "glGenTextures") == 0) {
419             const trace::Array *textures = dynamic_cast<const trace::Array *>(&call->arg(1));
420             size_t i;
421             GLuint texture;
422
423             if (textures) {
424                 for (i = 0; i < textures->size(); i++) {
425                     texture = textures->values[i]->toUInt();
426                     providef("texture-", texture, call->no);
427                 }
428             }
429             return;
430         }
431
432         if (strcmp(name, "glBindTexture") == 0) {
433             GLenum target;
434             GLuint texture;
435
436             std::stringstream ss_target, ss_texture;
437
438             target = static_cast<GLenum>(call->arg(0).toSInt());
439             texture = call->arg(1).toUInt();
440
441             ss_target << "texture-unit-" << activeTextureUnit << "-target-" << target;
442             ss_texture << "texture-" << texture;
443
444             resources.erase(ss_target.str());
445             provide(ss_target.str(), call->no);
446
447             unlinkAll(ss_target.str());
448             link(ss_target.str(), ss_texture.str());
449
450             return;
451         }
452
453         /* FIXME: Need to handle glMultTexImage and friends. */
454         if (STRNCMP_LITERAL(name, "glTexImage") == 0 ||
455             STRNCMP_LITERAL(name, "glTexSubImage") == 0 ||
456             STRNCMP_LITERAL(name, "glCopyTexImage") == 0 ||
457             STRNCMP_LITERAL(name, "glCopyTexSubImage") == 0 ||
458             STRNCMP_LITERAL(name, "glCompressedTexImage") == 0 ||
459             STRNCMP_LITERAL(name, "glCompressedTexSubImage") == 0 ||
460             strcmp(name, "glInvalidateTexImage") == 0 ||
461             strcmp(name, "glInvalidateTexSubImage") == 0) {
462
463             std::set<unsigned> *calls;
464             std::set<unsigned>::iterator c;
465             std::stringstream ss_target, ss_texture;
466
467             GLenum target = static_cast<GLenum>(call->arg(0).toSInt());
468
469             ss_target << "texture-unit-" << activeTextureUnit << "-target-" << target;
470             ss_texture << "texture-" << texture_map[target];
471
472             /* The texture resource depends on this call and any calls
473              * providing the given texture target. */
474             provide(ss_texture.str(), call->no);
475
476             if (resources.count(ss_target.str())) {
477                 calls = &resources[ss_target.str()];
478                 for (c = calls->begin(); c != calls->end(); c++) {
479                     provide(ss_texture.str(), *c);
480                 }
481             }
482
483             return;
484         }
485
486         if (strcmp(name, "glEnable") == 0) {
487             GLenum cap;
488
489             cap = static_cast<GLenum>(call->arg(0).toSInt());
490
491             if (cap == GL_TEXTURE_1D ||
492                 cap == GL_TEXTURE_2D ||
493                 cap == GL_TEXTURE_3D ||
494                 cap == GL_TEXTURE_CUBE_MAP)
495             {
496                 std::stringstream ss;
497
498                 ss << "texture-unit-" << activeTextureUnit << "-target-" << cap;
499
500                 link("render-state", ss.str());
501             }
502
503             provide("state", call->no);
504             return;
505         }
506
507         if (strcmp(name, "glDisable") == 0) {
508             GLenum cap;
509
510             cap = static_cast<GLenum>(call->arg(0).toSInt());
511
512             if (cap == GL_TEXTURE_1D ||
513                 cap == GL_TEXTURE_2D ||
514                 cap == GL_TEXTURE_3D ||
515                 cap == GL_TEXTURE_CUBE_MAP)
516             {
517                 std::stringstream ss;
518
519                 ss << "texture-unit-" << activeTextureUnit << "-target-" << cap;
520
521                 unlink("render-state", ss.str());
522             }
523
524             provide("state", call->no);
525             return;
526         }
527
528         if (strcmp(name, "glCreateShader") == 0 ||
529             strcmp(name, "glCreateShaderObjectARB") == 0) {
530
531             GLuint shader = call->ret->toUInt();
532             providef("shader-", shader, call->no);
533             return;
534         }
535
536         if (strcmp(name, "glShaderSource") == 0 ||
537             strcmp(name, "glShaderSourceARB") == 0 ||
538             strcmp(name, "glCompileShader") == 0 ||
539             strcmp(name, "glCompileShaderARB") == 0 ||
540             strcmp(name, "glGetShaderiv") == 0 ||
541             strcmp(name, "glGetShaderInfoLog") == 0) {
542
543             GLuint shader = call->arg(0).toUInt();
544             providef("shader-", shader, call->no);
545             return;
546         }
547
548         if (strcmp(name, "glCreateProgram") == 0 ||
549             strcmp(name, "glCreateProgramObjectARB") == 0) {
550
551             GLuint program = call->ret->toUInt();
552             providef("program-", program, call->no);
553             return;
554         }
555
556         if (strcmp(name, "glAttachShader") == 0 ||
557             strcmp(name, "glAttachObjectARB") == 0) {
558
559             GLuint program, shader;
560             std::stringstream ss_program, ss_shader;
561
562             program = call->arg(0).toUInt();
563             shader = call->arg(1).toUInt();
564
565             ss_program << "program-" << program;
566             ss_shader << "shader-" << shader;
567
568             link(ss_program.str(), ss_shader.str());
569             provide(ss_program.str(), call->no);
570
571             return;
572         }
573
574         if (strcmp(name, "glDetachShader") == 0 ||
575             strcmp(name, "glDetachObjectARB") == 0) {
576
577             GLuint program, shader;
578             std::stringstream ss_program, ss_shader;
579
580             program = call->arg(0).toUInt();
581             shader = call->arg(1).toUInt();
582
583             ss_program << "program-" << program;
584             ss_shader << "shader-" << shader;
585
586             unlink(ss_program.str(), ss_shader.str());
587
588             return;
589         }
590
591         if (strcmp(name, "glUseProgram") == 0 ||
592             strcmp(name, "glUseProgramObjectARB") == 0) {
593
594             GLuint program;
595
596             program = call->arg(0).toUInt();
597
598             unlinkAll("render-program-state");
599
600             if (program == 0) {
601                 unlink("render-state", "render-program-state");
602                 provide("state", call->no);
603             } else {
604                 std::stringstream ss;
605
606                 ss << "program-" << program;
607
608                 link("render-state", "render-program-state");
609                 link("render-program-state", ss.str());
610
611                 provide(ss.str(), call->no);
612             }
613
614             return;
615         }
616
617         if (strcmp(name, "glGetUniformLocation") == 0 ||
618             strcmp(name, "glGetUniformLocationARB") == 0 ||
619             strcmp(name, "glGetFragDataLocation") == 0 ||
620             strcmp(name, "glGetFragDataLocationEXT") == 0 ||
621             strcmp(name, "glGetSubroutineUniformLocation") == 0 ||
622             strcmp(name, "glGetProgramResourceLocation") == 0 ||
623             strcmp(name, "glGetProgramResourceLocationIndex") == 0 ||
624             strcmp(name, "glGetVaryingLocationNV") == 0) {
625
626             GLuint program = call->arg(0).toUInt();
627
628             providef("program-", program, call->no);
629
630             return;
631         }
632
633         /* For any call that accepts 'location' as its first argument,
634          * perform a lookup in our location->program map and add a
635          * dependence on the program we find there. */
636         if (call->sig->num_args > 0 &&
637             strcmp(call->sig->arg_names[0], "location") == 0) {
638
639             providef("program-", activeProgram, call->no);
640
641             /* We can't easily tell if this uniform is being used to
642              * associate a sampler in the shader with a texture
643              * unit. The conservative option is to assume that it is
644              * and create a link from the active program to any bound
645              * textures for the given unit number.
646              *
647              * FIXME: We should be doing the same thing for calls to
648              * glUniform1iv. */
649             if (strcmp(name, "glUniform1i") == 0 ||
650                 strcmp(name, "glUniform1iARB") == 0) {
651
652                 GLint max_unit = MAX(GL_MAX_TEXTURE_COORDS, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
653
654                 GLint unit = call->arg(1).toSInt();
655                 std::stringstream ss_program;
656                 std::stringstream ss_texture;
657
658                 if (unit < max_unit) {
659
660                     ss_program << "program-" << activeProgram;
661
662                     ss_texture << "texture-unit-" << GL_TEXTURE0 + unit << "-target-";
663
664                     /* We don't know what target(s) might get bound to
665                      * this texture unit, so conservatively link to
666                      * all. Only bound textures will actually get inserted
667                      * into the output call stream. */
668                     linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_1D);
669                     linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_2D);
670                     linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_3D);
671                     linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_CUBE_MAP);
672                 }
673             }
674
675             return;
676         }
677
678         /* FIXME: We cut a huge swath by assuming that any unhandled
679          * call that has a first argument named "program" should not
680          * be included in the trimmed output unless the program of
681          * that number is also included.
682          *
683          * This heuristic is correct for many cases, but we should
684          * actually carefully verify if this includes some calls
685          * inappropriately, or if it misses some.
686          */
687         if (strcmp(name, "glLinkProgram") == 0 ||
688             strcmp(name, "glLinkProgramARB") == 0 ||
689             (call->sig->num_args > 0 &&
690              (strcmp(call->sig->arg_names[0], "program") == 0 ||
691               strcmp(call->sig->arg_names[0], "programObj") == 0))) {
692
693             GLuint program = call->arg(0).toUInt();
694             providef("program-", program, call->no);
695             return;
696         }
697
698         /* Handle all rendering operations, (even though only glEnd is
699          * flagged as a rendering operation we treat everything from
700          * glBegin through glEnd as a rendering operation). */
701         if (call->flags & trace::CALL_FLAG_RENDER ||
702             insideBeginEnd) {
703
704             std::set<unsigned> calls;
705             std::set<unsigned>::iterator c;
706
707             provide("framebuffer", call->no);
708
709             calls = resolve("render-state");
710
711             for (c = calls.begin(); c != calls.end(); c++) {
712                 provide("framebuffer", *c);
713             }
714
715             /* In some cases, rendering has side effects beyond the
716              * framebuffer update. */
717             if (renderingHasSideEffect()) {
718                 provide("state", call->no);
719                 for (c = calls.begin(); c != calls.end(); c++) {
720                     provide("state", *c);
721                 }
722             }
723
724             return;
725         }
726
727         /* By default, assume this call affects the state somehow. */
728         resources["state"].insert(call->no);
729     }
730
731     void requireDependencies(trace::Call *call) {
732
733         /* Swap-buffers calls depend on framebuffer state. */
734         if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
735             call->flags & trace::CALL_FLAG_END_FRAME) {
736             consume("framebuffer");
737         }
738
739         /* By default, just assume this call depends on generic state. */
740         consume("state");
741     }
742
743
744 public:
745     TraceAnalyzer(): transformFeedbackActive(false),
746                      framebufferObjectActive(false),
747                      insideBeginEnd(false),
748                      insideNewEndList(0),
749                      activeTextureUnit(GL_TEXTURE0)
750     {}
751
752     ~TraceAnalyzer() {}
753
754     /* Analyze this call by tracking state and recording all the
755      * resources provided by this call as side effects.. */
756     void analyze(trace::Call *call) {
757
758         stateTrackPreCall(call);
759
760         recordSideEffects(call);
761
762         stateTrackPostCall(call);
763     }
764
765     /* Require this call and all of its dependencies to be included in
766      * the final trace. */
767     void require(trace::Call *call) {
768
769         /* First, find and insert all calls that this call depends on. */
770         requireDependencies(call);
771
772         /* Then insert this call itself. */
773         required.insert(call->no);
774     }
775
776     /* Return a set of all the required calls, (both those calls added
777      * explicitly with require() and those implicitly depended
778      * upon. */
779     std::set<unsigned> *get_required(void) {
780         return &required;
781     }
782 };
783
784 struct trim_options {
785     /* Calls to be included in trace. */
786     trace::CallSet calls;
787
788     /* Frames to be included in trace. */
789     trace::CallSet frames;
790
791     /* Whether dependency analysis should be performed. */
792     bool dependency_analysis;
793
794     /* Whether uninteresting calls should be pruned.. */
795     bool prune_uninteresting;
796
797     /* Output filename */
798     std::string output;
799
800     /* Emit only calls from this thread (-1 == all threads) */
801     int thread;
802
803     /* Print resulting callset */
804     int print_callset;
805 };
806
807 static int
808 trim_trace(const char *filename, struct trim_options *options)
809 {
810     trace::ParseBookmark beginning;
811     trace::Parser p;
812     TraceAnalyzer analyzer;
813     std::set<unsigned> *required;
814     unsigned frame;
815     int call_range_first, call_range_last;
816
817     if (!p.open(filename)) {
818         std::cerr << "error: failed to open " << filename << "\n";
819         return 1;
820     }
821
822     /* Mark the beginning so we can return here for pass 2. */
823     p.getBookmark(beginning);
824
825     /* In pass 1, analyze which calls are needed. */
826     frame = 0;
827     trace::Call *call;
828     while ((call = p.parse_call())) {
829
830         /* There's no use doing any work past the last call or frame
831          * requested by the user. */
832         if (call->no > options->calls.getLast() ||
833             frame > options->frames.getLast()) {
834             
835             delete call;
836             break;
837         }
838
839         /* If requested, ignore all calls not belonging to the specified thread. */
840         if (options->thread != -1 && call->thread_id != options->thread) {
841             goto NEXT;
842         }
843
844         /* Also, prune if uninteresting (unless the user asked for no pruning. */
845         if (options->prune_uninteresting && call->flags & trace::CALL_FLAG_UNINTERESTING) {
846             goto NEXT;
847         }
848
849         /* If this call is included in the user-specified call set,
850          * then require it (and all dependencies) in the trimmed
851          * output. */
852         if (options->calls.contains(*call) ||
853             options->frames.contains(frame, call->flags)) {
854
855             analyzer.require(call);
856         }
857
858         /* Regardless of whether we include this call or not, we do
859          * some dependency tracking (unless disabled by the user). We
860          * do this even for calls we have included in the output so
861          * that any state updates get performed. */
862         if (options->dependency_analysis) {
863             analyzer.analyze(call);
864         }
865
866     NEXT:
867         if (call->flags & trace::CALL_FLAG_END_FRAME)
868             frame++;
869
870         delete call;
871     }
872
873     /* Prepare output file and writer for output. */
874     if (options->output.empty()) {
875         os::String base(filename);
876         base.trimExtension();
877
878         options->output = std::string(base.str()) + std::string("-trim.trace");
879     }
880
881     trace::Writer writer;
882     if (!writer.open(options->output.c_str())) {
883         std::cerr << "error: failed to create " << filename << "\n";
884         return 1;
885     }
886
887     /* Reset bookmark for pass 2. */
888     p.setBookmark(beginning);
889
890     /* In pass 2, emit the calls that are required. */
891     required = analyzer.get_required();
892
893     frame = 0;
894     call_range_first = -1;
895     call_range_last = -1;
896     while ((call = p.parse_call())) {
897
898         /* There's no use doing any work past the last call or frame
899          * requested by the user. */
900         if (call->no > options->calls.getLast() ||
901             frame > options->frames.getLast()) {
902
903             break;
904         }
905
906         if (required->find(call->no) != required->end()) {
907             writer.writeCall(call);
908
909             if (options->print_callset) {
910                 if (call_range_first < 0) {
911                     call_range_first = call->no;
912                     printf ("%d", call_range_first);
913                 } else if (call->no != call_range_last + 1) {
914                     if (call_range_last != call_range_first)
915                         printf ("-%d", call_range_last);
916                     call_range_first = call->no;
917                     printf (",%d", call_range_first);
918                 }
919                 call_range_last = call->no;
920             }
921         }
922
923         if (call->flags & trace::CALL_FLAG_END_FRAME) {
924             frame++;
925         }
926
927         delete call;
928     }
929
930     if (options->print_callset) {
931         if (call_range_last != call_range_first)
932             printf ("-%d\n", call_range_last);
933     }
934
935     std::cout << "Trimmed trace is available as " << options->output << "\n";
936
937     return 0;
938 }
939
940 static int
941 command(int argc, char *argv[])
942 {
943     struct trim_options options;
944
945     options.calls = trace::CallSet(trace::FREQUENCY_NONE);
946     options.frames = trace::CallSet(trace::FREQUENCY_NONE);
947     options.dependency_analysis = true;
948     options.prune_uninteresting = true;
949     options.output = "";
950     options.thread = -1;
951     options.print_callset = 0;
952
953     int opt;
954     while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) {
955         switch (opt) {
956         case 'h':
957             help();
958             return 0;
959         case CALLS_OPT:
960             options.calls = trace::CallSet(optarg);
961             break;
962         case FRAMES_OPT:
963             options.frames = trace::CallSet(optarg);
964             break;
965         case DEPS_OPT:
966             options.dependency_analysis = true;
967             break;
968         case NO_DEPS_OPT:
969             options.dependency_analysis = false;
970             break;
971         case PRUNE_OPT:
972             options.prune_uninteresting = true;
973             break;
974         case NO_PRUNE_OPT:
975             options.prune_uninteresting = false;
976             break;
977         case 'x':
978             options.dependency_analysis = false;
979             options.prune_uninteresting = false;
980             break;
981         case THREAD_OPT:
982             options.thread = atoi(optarg);
983             break;
984         case 'o':
985             options.output = optarg;
986             break;
987         case PRINT_CALLSET_OPT:
988             options.print_callset = 1;
989             break;
990         default:
991             std::cerr << "error: unexpected option `" << opt << "`\n";
992             usage();
993             return 1;
994         }
995     }
996
997     /* If neither of --calls nor --frames was set, default to the
998      * entire set of calls. */
999     if (options.calls.empty() && options.frames.empty()) {
1000         options.calls = trace::CallSet(trace::FREQUENCY_ALL);
1001     }
1002
1003     if (optind >= argc) {
1004         std::cerr << "error: apitrace trim requires a trace file as an argument.\n";
1005         usage();
1006         return 1;
1007     }
1008
1009     if (argc > optind + 1) {
1010         std::cerr << "error: extraneous arguments:";
1011         for (int i = optind + 1; i < argc; i++) {
1012             std::cerr << " " << argv[i];
1013         }
1014         std::cerr << "\n";
1015         usage();
1016         return 1;
1017     }
1018
1019     return trim_trace(argv[optind], &options);
1020 }
1021
1022 const Command trim_command = {
1023     "trim",
1024     synopsis,
1025     help,
1026     command
1027 };