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