]> git.cworth.org Git - apitrace/blob - cli/cli_trim.cpp
trim: Never trim any textures bound within a display list.
[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
405             /* Also, any texture bound inside a display list is
406              * conservatively considered required. */
407             if (strcmp(name, "glBindTexture") == 0) {
408                 GLuint texture = call->arg(1).toUInt();
409
410                 linkf("state", "texture-", texture);
411             }
412
413             return;
414         }
415
416         /* If call is flagged as no side effects, then we are done here. */
417         if (call->flags & trace::CALL_FLAG_NO_SIDE_EFFECTS) {
418             return;
419         }
420
421         /* Similarly, swap-buffers calls don't have interesting side effects. */
422         if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
423             call->flags & trace::CALL_FLAG_END_FRAME) {
424             return;
425         }
426
427         if (strcmp(name, "glGenTextures") == 0) {
428             const trace::Array *textures = dynamic_cast<const trace::Array *>(&call->arg(1));
429             size_t i;
430             GLuint texture;
431
432             if (textures) {
433                 for (i = 0; i < textures->size(); i++) {
434                     texture = textures->values[i]->toUInt();
435                     providef("texture-", texture, call->no);
436                 }
437             }
438             return;
439         }
440
441         if (strcmp(name, "glBindTexture") == 0) {
442             GLenum target;
443             GLuint texture;
444
445             std::stringstream ss_target, ss_texture;
446
447             target = static_cast<GLenum>(call->arg(0).toSInt());
448             texture = call->arg(1).toUInt();
449
450             ss_target << "texture-unit-" << activeTextureUnit << "-target-" << target;
451             ss_texture << "texture-" << texture;
452
453             resources.erase(ss_target.str());
454             provide(ss_target.str(), call->no);
455
456             unlinkAll(ss_target.str());
457             link(ss_target.str(), ss_texture.str());
458
459             return;
460         }
461
462         /* FIXME: Need to handle glMultTexImage and friends. */
463         if (STRNCMP_LITERAL(name, "glTexImage") == 0 ||
464             STRNCMP_LITERAL(name, "glTexSubImage") == 0 ||
465             STRNCMP_LITERAL(name, "glCopyTexImage") == 0 ||
466             STRNCMP_LITERAL(name, "glCopyTexSubImage") == 0 ||
467             STRNCMP_LITERAL(name, "glCompressedTexImage") == 0 ||
468             STRNCMP_LITERAL(name, "glCompressedTexSubImage") == 0 ||
469             strcmp(name, "glInvalidateTexImage") == 0 ||
470             strcmp(name, "glInvalidateTexSubImage") == 0) {
471
472             std::set<unsigned> *calls;
473             std::set<unsigned>::iterator c;
474             std::stringstream ss_target, ss_texture;
475
476             GLenum target = static_cast<GLenum>(call->arg(0).toSInt());
477
478             ss_target << "texture-unit-" << activeTextureUnit << "-target-" << target;
479             ss_texture << "texture-" << texture_map[target];
480
481             /* The texture resource depends on this call and any calls
482              * providing the given texture target. */
483             provide(ss_texture.str(), call->no);
484
485             if (resources.count(ss_target.str())) {
486                 calls = &resources[ss_target.str()];
487                 for (c = calls->begin(); c != calls->end(); c++) {
488                     provide(ss_texture.str(), *c);
489                 }
490             }
491
492             return;
493         }
494
495         if (strcmp(name, "glEnable") == 0) {
496             GLenum cap;
497
498             cap = static_cast<GLenum>(call->arg(0).toSInt());
499
500             if (cap == GL_TEXTURE_1D ||
501                 cap == GL_TEXTURE_2D ||
502                 cap == GL_TEXTURE_3D ||
503                 cap == GL_TEXTURE_CUBE_MAP)
504             {
505                 std::stringstream ss;
506
507                 ss << "texture-unit-" << activeTextureUnit << "-target-" << cap;
508
509                 link("render-state", ss.str());
510             }
511
512             provide("state", call->no);
513             return;
514         }
515
516         if (strcmp(name, "glDisable") == 0) {
517             GLenum cap;
518
519             cap = static_cast<GLenum>(call->arg(0).toSInt());
520
521             if (cap == GL_TEXTURE_1D ||
522                 cap == GL_TEXTURE_2D ||
523                 cap == GL_TEXTURE_3D ||
524                 cap == GL_TEXTURE_CUBE_MAP)
525             {
526                 std::stringstream ss;
527
528                 ss << "texture-unit-" << activeTextureUnit << "-target-" << cap;
529
530                 unlink("render-state", ss.str());
531             }
532
533             provide("state", call->no);
534             return;
535         }
536
537         if (strcmp(name, "glCreateShader") == 0 ||
538             strcmp(name, "glCreateShaderObjectARB") == 0) {
539
540             GLuint shader = call->ret->toUInt();
541             providef("shader-", shader, call->no);
542             return;
543         }
544
545         if (strcmp(name, "glShaderSource") == 0 ||
546             strcmp(name, "glShaderSourceARB") == 0 ||
547             strcmp(name, "glCompileShader") == 0 ||
548             strcmp(name, "glCompileShaderARB") == 0 ||
549             strcmp(name, "glGetShaderiv") == 0 ||
550             strcmp(name, "glGetShaderInfoLog") == 0) {
551
552             GLuint shader = call->arg(0).toUInt();
553             providef("shader-", shader, call->no);
554             return;
555         }
556
557         if (strcmp(name, "glCreateProgram") == 0 ||
558             strcmp(name, "glCreateProgramObjectARB") == 0) {
559
560             GLuint program = call->ret->toUInt();
561             providef("program-", program, call->no);
562             return;
563         }
564
565         if (strcmp(name, "glAttachShader") == 0 ||
566             strcmp(name, "glAttachObjectARB") == 0) {
567
568             GLuint program, shader;
569             std::stringstream ss_program, ss_shader;
570
571             program = call->arg(0).toUInt();
572             shader = call->arg(1).toUInt();
573
574             ss_program << "program-" << program;
575             ss_shader << "shader-" << shader;
576
577             link(ss_program.str(), ss_shader.str());
578             provide(ss_program.str(), call->no);
579
580             return;
581         }
582
583         if (strcmp(name, "glDetachShader") == 0 ||
584             strcmp(name, "glDetachObjectARB") == 0) {
585
586             GLuint program, shader;
587             std::stringstream ss_program, ss_shader;
588
589             program = call->arg(0).toUInt();
590             shader = call->arg(1).toUInt();
591
592             ss_program << "program-" << program;
593             ss_shader << "shader-" << shader;
594
595             unlink(ss_program.str(), ss_shader.str());
596
597             return;
598         }
599
600         if (strcmp(name, "glUseProgram") == 0 ||
601             strcmp(name, "glUseProgramObjectARB") == 0) {
602
603             GLuint program;
604
605             program = call->arg(0).toUInt();
606
607             unlinkAll("render-program-state");
608
609             if (program == 0) {
610                 unlink("render-state", "render-program-state");
611                 provide("state", call->no);
612             } else {
613                 std::stringstream ss;
614
615                 ss << "program-" << program;
616
617                 link("render-state", "render-program-state");
618                 link("render-program-state", ss.str());
619
620                 provide(ss.str(), call->no);
621             }
622
623             return;
624         }
625
626         if (strcmp(name, "glGetUniformLocation") == 0 ||
627             strcmp(name, "glGetUniformLocationARB") == 0 ||
628             strcmp(name, "glGetFragDataLocation") == 0 ||
629             strcmp(name, "glGetFragDataLocationEXT") == 0 ||
630             strcmp(name, "glGetSubroutineUniformLocation") == 0 ||
631             strcmp(name, "glGetProgramResourceLocation") == 0 ||
632             strcmp(name, "glGetProgramResourceLocationIndex") == 0 ||
633             strcmp(name, "glGetVaryingLocationNV") == 0) {
634
635             GLuint program = call->arg(0).toUInt();
636
637             providef("program-", program, call->no);
638
639             return;
640         }
641
642         /* For any call that accepts 'location' as its first argument,
643          * perform a lookup in our location->program map and add a
644          * dependence on the program we find there. */
645         if (call->sig->num_args > 0 &&
646             strcmp(call->sig->arg_names[0], "location") == 0) {
647
648             providef("program-", activeProgram, call->no);
649
650             /* We can't easily tell if this uniform is being used to
651              * associate a sampler in the shader with a texture
652              * unit. The conservative option is to assume that it is
653              * and create a link from the active program to any bound
654              * textures for the given unit number.
655              *
656              * FIXME: We should be doing the same thing for calls to
657              * glUniform1iv. */
658             if (strcmp(name, "glUniform1i") == 0 ||
659                 strcmp(name, "glUniform1iARB") == 0) {
660
661                 GLint max_unit = MAX(GL_MAX_TEXTURE_COORDS, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
662
663                 GLint unit = call->arg(1).toSInt();
664                 std::stringstream ss_program;
665                 std::stringstream ss_texture;
666
667                 if (unit < max_unit) {
668
669                     ss_program << "program-" << activeProgram;
670
671                     ss_texture << "texture-unit-" << GL_TEXTURE0 + unit << "-target-";
672
673                     /* We don't know what target(s) might get bound to
674                      * this texture unit, so conservatively link to
675                      * all. Only bound textures will actually get inserted
676                      * into the output call stream. */
677                     linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_1D);
678                     linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_2D);
679                     linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_3D);
680                     linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_CUBE_MAP);
681                 }
682             }
683
684             return;
685         }
686
687         /* FIXME: We cut a huge swath by assuming that any unhandled
688          * call that has a first argument named "program" should not
689          * be included in the trimmed output unless the program of
690          * that number is also included.
691          *
692          * This heuristic is correct for many cases, but we should
693          * actually carefully verify if this includes some calls
694          * inappropriately, or if it misses some.
695          */
696         if (strcmp(name, "glLinkProgram") == 0 ||
697             strcmp(name, "glLinkProgramARB") == 0 ||
698             (call->sig->num_args > 0 &&
699              (strcmp(call->sig->arg_names[0], "program") == 0 ||
700               strcmp(call->sig->arg_names[0], "programObj") == 0))) {
701
702             GLuint program = call->arg(0).toUInt();
703             providef("program-", program, call->no);
704             return;
705         }
706
707         /* Handle all rendering operations, (even though only glEnd is
708          * flagged as a rendering operation we treat everything from
709          * glBegin through glEnd as a rendering operation). */
710         if (call->flags & trace::CALL_FLAG_RENDER ||
711             insideBeginEnd) {
712
713             std::set<unsigned> calls;
714             std::set<unsigned>::iterator c;
715
716             provide("framebuffer", call->no);
717
718             calls = resolve("render-state");
719
720             for (c = calls.begin(); c != calls.end(); c++) {
721                 provide("framebuffer", *c);
722             }
723
724             /* In some cases, rendering has side effects beyond the
725              * framebuffer update. */
726             if (renderingHasSideEffect()) {
727                 provide("state", call->no);
728                 for (c = calls.begin(); c != calls.end(); c++) {
729                     provide("state", *c);
730                 }
731             }
732
733             return;
734         }
735
736         /* By default, assume this call affects the state somehow. */
737         resources["state"].insert(call->no);
738     }
739
740     void requireDependencies(trace::Call *call) {
741
742         /* Swap-buffers calls depend on framebuffer state. */
743         if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
744             call->flags & trace::CALL_FLAG_END_FRAME) {
745             consume("framebuffer");
746         }
747
748         /* By default, just assume this call depends on generic state. */
749         consume("state");
750     }
751
752
753 public:
754     TraceAnalyzer(): transformFeedbackActive(false),
755                      framebufferObjectActive(false),
756                      insideBeginEnd(false),
757                      insideNewEndList(0),
758                      activeTextureUnit(GL_TEXTURE0)
759     {}
760
761     ~TraceAnalyzer() {}
762
763     /* Analyze this call by tracking state and recording all the
764      * resources provided by this call as side effects.. */
765     void analyze(trace::Call *call) {
766
767         stateTrackPreCall(call);
768
769         recordSideEffects(call);
770
771         stateTrackPostCall(call);
772     }
773
774     /* Require this call and all of its dependencies to be included in
775      * the final trace. */
776     void require(trace::Call *call) {
777
778         /* First, find and insert all calls that this call depends on. */
779         requireDependencies(call);
780
781         /* Then insert this call itself. */
782         required.insert(call->no);
783     }
784
785     /* Return a set of all the required calls, (both those calls added
786      * explicitly with require() and those implicitly depended
787      * upon. */
788     std::set<unsigned> *get_required(void) {
789         return &required;
790     }
791 };
792
793 struct trim_options {
794     /* Calls to be included in trace. */
795     trace::CallSet calls;
796
797     /* Frames to be included in trace. */
798     trace::CallSet frames;
799
800     /* Whether dependency analysis should be performed. */
801     bool dependency_analysis;
802
803     /* Whether uninteresting calls should be pruned.. */
804     bool prune_uninteresting;
805
806     /* Output filename */
807     std::string output;
808
809     /* Emit only calls from this thread (-1 == all threads) */
810     int thread;
811
812     /* Print resulting callset */
813     int print_callset;
814 };
815
816 static int
817 trim_trace(const char *filename, struct trim_options *options)
818 {
819     trace::ParseBookmark beginning;
820     trace::Parser p;
821     TraceAnalyzer analyzer;
822     std::set<unsigned> *required;
823     unsigned frame;
824     int call_range_first, call_range_last;
825
826     if (!p.open(filename)) {
827         std::cerr << "error: failed to open " << filename << "\n";
828         return 1;
829     }
830
831     /* Mark the beginning so we can return here for pass 2. */
832     p.getBookmark(beginning);
833
834     /* In pass 1, analyze which calls are needed. */
835     frame = 0;
836     trace::Call *call;
837     while ((call = p.parse_call())) {
838
839         /* There's no use doing any work past the last call or frame
840          * requested by the user. */
841         if (call->no > options->calls.getLast() ||
842             frame > options->frames.getLast()) {
843             
844             delete call;
845             break;
846         }
847
848         /* If requested, ignore all calls not belonging to the specified thread. */
849         if (options->thread != -1 && call->thread_id != options->thread) {
850             goto NEXT;
851         }
852
853         /* Also, prune if uninteresting (unless the user asked for no pruning. */
854         if (options->prune_uninteresting && call->flags & trace::CALL_FLAG_UNINTERESTING) {
855             goto NEXT;
856         }
857
858         /* If this call is included in the user-specified call set,
859          * then require it (and all dependencies) in the trimmed
860          * output. */
861         if (options->calls.contains(*call) ||
862             options->frames.contains(frame, call->flags)) {
863
864             analyzer.require(call);
865         }
866
867         /* Regardless of whether we include this call or not, we do
868          * some dependency tracking (unless disabled by the user). We
869          * do this even for calls we have included in the output so
870          * that any state updates get performed. */
871         if (options->dependency_analysis) {
872             analyzer.analyze(call);
873         }
874
875     NEXT:
876         if (call->flags & trace::CALL_FLAG_END_FRAME)
877             frame++;
878
879         delete call;
880     }
881
882     /* Prepare output file and writer for output. */
883     if (options->output.empty()) {
884         os::String base(filename);
885         base.trimExtension();
886
887         options->output = std::string(base.str()) + std::string("-trim.trace");
888     }
889
890     trace::Writer writer;
891     if (!writer.open(options->output.c_str())) {
892         std::cerr << "error: failed to create " << filename << "\n";
893         return 1;
894     }
895
896     /* Reset bookmark for pass 2. */
897     p.setBookmark(beginning);
898
899     /* In pass 2, emit the calls that are required. */
900     required = analyzer.get_required();
901
902     frame = 0;
903     call_range_first = -1;
904     call_range_last = -1;
905     while ((call = p.parse_call())) {
906
907         /* There's no use doing any work past the last call or frame
908          * requested by the user. */
909         if (call->no > options->calls.getLast() ||
910             frame > options->frames.getLast()) {
911
912             break;
913         }
914
915         if (required->find(call->no) != required->end()) {
916             writer.writeCall(call);
917
918             if (options->print_callset) {
919                 if (call_range_first < 0) {
920                     call_range_first = call->no;
921                     printf ("%d", call_range_first);
922                 } else if (call->no != call_range_last + 1) {
923                     if (call_range_last != call_range_first)
924                         printf ("-%d", call_range_last);
925                     call_range_first = call->no;
926                     printf (",%d", call_range_first);
927                 }
928                 call_range_last = call->no;
929             }
930         }
931
932         if (call->flags & trace::CALL_FLAG_END_FRAME) {
933             frame++;
934         }
935
936         delete call;
937     }
938
939     if (options->print_callset) {
940         if (call_range_last != call_range_first)
941             printf ("-%d\n", call_range_last);
942     }
943
944     std::cout << "Trimmed trace is available as " << options->output << "\n";
945
946     return 0;
947 }
948
949 static int
950 command(int argc, char *argv[])
951 {
952     struct trim_options options;
953
954     options.calls = trace::CallSet(trace::FREQUENCY_NONE);
955     options.frames = trace::CallSet(trace::FREQUENCY_NONE);
956     options.dependency_analysis = true;
957     options.prune_uninteresting = true;
958     options.output = "";
959     options.thread = -1;
960     options.print_callset = 0;
961
962     int opt;
963     while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) {
964         switch (opt) {
965         case 'h':
966             help();
967             return 0;
968         case CALLS_OPT:
969             options.calls = trace::CallSet(optarg);
970             break;
971         case FRAMES_OPT:
972             options.frames = trace::CallSet(optarg);
973             break;
974         case DEPS_OPT:
975             options.dependency_analysis = true;
976             break;
977         case NO_DEPS_OPT:
978             options.dependency_analysis = false;
979             break;
980         case PRUNE_OPT:
981             options.prune_uninteresting = true;
982             break;
983         case NO_PRUNE_OPT:
984             options.prune_uninteresting = false;
985             break;
986         case 'x':
987             options.dependency_analysis = false;
988             options.prune_uninteresting = false;
989             break;
990         case THREAD_OPT:
991             options.thread = atoi(optarg);
992             break;
993         case 'o':
994             options.output = optarg;
995             break;
996         case PRINT_CALLSET_OPT:
997             options.print_callset = 1;
998             break;
999         default:
1000             std::cerr << "error: unexpected option `" << opt << "`\n";
1001             usage();
1002             return 1;
1003         }
1004     }
1005
1006     /* If neither of --calls nor --frames was set, default to the
1007      * entire set of calls. */
1008     if (options.calls.empty() && options.frames.empty()) {
1009         options.calls = trace::CallSet(trace::FREQUENCY_ALL);
1010     }
1011
1012     if (optind >= argc) {
1013         std::cerr << "error: apitrace trim requires a trace file as an argument.\n";
1014         usage();
1015         return 1;
1016     }
1017
1018     if (argc > optind + 1) {
1019         std::cerr << "error: extraneous arguments:";
1020         for (int i = optind + 1; i < argc; i++) {
1021             std::cerr << " " << argv[i];
1022         }
1023         std::cerr << "\n";
1024         usage();
1025         return 1;
1026     }
1027
1028     return trim_trace(argv[optind], &options);
1029 }
1030
1031 const Command trim_command = {
1032     "trim",
1033     synopsis,
1034     help,
1035     command
1036 };