]> git.cworth.org Git - apitrace/blob - retrace/retrace_main.cpp
glstate: Fallback to GLX when there is no current EGL context (issue #124).
[apitrace] / retrace / retrace_main.cpp
1 /**************************************************************************
2  *
3  * Copyright 2011 Jose Fonseca
4  * Copyright (C) 2013 Intel Corporation. All rights reversed.
5  * Author: Shuang He <shuang.he@intel.com>
6  * All Rights Reserved.
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a copy
9  * of this software and associated documentation files (the "Software"), to deal
10  * in the Software without restriction, including without limitation the rights
11  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12  * copies of the Software, and to permit persons to whom the Software is
13  * furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included in
16  * all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24  * THE SOFTWARE.
25  *
26  **************************************************************************/
27
28
29 #include <string.h>
30 #include <limits.h> // for CHAR_MAX
31 #include <iostream>
32 #include <getopt.h>
33
34 #include "os_binary.hpp"
35 #include "os_time.hpp"
36 #include "os_thread.hpp"
37 #include "image.hpp"
38 #include "trace_callset.hpp"
39 #include "trace_dump.hpp"
40 #include "trace_option.hpp"
41 #include "retrace.hpp"
42
43
44 static bool waitOnFinish = false;
45 static bool loopOnFinish = false;
46
47 static const char *comparePrefix = NULL;
48 static const char *snapshotPrefix = NULL;
49 static enum {
50     PNM_FMT,
51     RAW_RGB
52 } snapshotFormat = PNM_FMT;
53
54 static trace::CallSet snapshotFrequency;
55 static trace::CallSet compareFrequency;
56 static trace::ParseBookmark lastFrameStart;
57
58 static unsigned dumpStateCallNo = ~0;
59
60 retrace::Retracer retracer;
61
62
63 namespace retrace {
64
65
66 trace::Parser parser;
67 trace::Profiler profiler;
68
69
70 int verbosity = 0;
71 bool debug = true;
72 bool dumpingState = false;
73
74 Driver driver = DRIVER_DEFAULT;
75 const char *driverModule = NULL;
76
77 bool doubleBuffer = true;
78 bool coreProfile = false;
79
80 bool profiling = false;
81 bool profilingGpuTimes = false;
82 bool profilingCpuTimes = false;
83 bool profilingPixelsDrawn = false;
84 bool profilingMemoryUsage = false;
85 bool useCallNos = true;
86 bool singleThread = false;
87
88 unsigned frameNo = 0;
89 unsigned callNo = 0;
90
91
92 void
93 frameComplete(trace::Call &call) {
94     ++frameNo;
95 }
96
97
98 static Dumper defaultDumper;
99
100 Dumper *dumper = &defaultDumper;
101
102
103 /**
104  * Take/compare snapshots.
105  */
106 static void
107 takeSnapshot(unsigned call_no) {
108     static unsigned snapshot_no = 0;
109
110     assert(snapshotPrefix || comparePrefix);
111
112     image::Image *ref = NULL;
113
114     if (comparePrefix) {
115         os::String filename = os::String::format("%s%010u.png", comparePrefix, call_no);
116         ref = image::readPNG(filename);
117         if (!ref) {
118             return;
119         }
120         if (retrace::verbosity >= 0) {
121             std::cout << "Read " << filename << "\n";
122         }
123     }
124
125     image::Image *src = dumper->getSnapshot();
126     if (!src) {
127         std::cerr << call_no << ": warning: failed to get snapshot\n";
128         return;
129     }
130
131     if (snapshotPrefix) {
132         if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
133             char comment[21];
134             snprintf(comment, sizeof comment, "%u",
135                      useCallNos ? call_no : snapshot_no);
136             if (snapshotFormat == RAW_RGB)
137                 src->writeRAW(std::cout);
138             else
139                 src->writePNM(std::cout, comment);
140         } else {
141             os::String filename = os::String::format("%s%010u.png",
142                                                      snapshotPrefix,
143                                                      useCallNos ? call_no : snapshot_no);
144             if (src->writePNG(filename) && retrace::verbosity >= 0) {
145                 std::cout << "Wrote " << filename << "\n";
146             }
147         }
148     }
149
150     if (ref) {
151         std::cout << "Snapshot " << call_no << " average precision of " << src->compare(*ref) << " bits\n";
152         delete ref;
153     }
154
155     delete src;
156
157     snapshot_no++;
158
159     return;
160 }
161
162
163 /**
164  * Retrace one call.
165  *
166  * Take snapshots before/after retracing (as appropriate) and dispatch it to
167  * the respective handler.
168  */
169 static void
170 retraceCall(trace::Call *call) {
171     bool swapRenderTarget = call->flags &
172         trace::CALL_FLAG_SWAP_RENDERTARGET;
173     bool doSnapshot = snapshotFrequency.contains(*call) ||
174         compareFrequency.contains(*call);
175
176     // For calls which cause rendertargets to be swaped, we take the
177     // snapshot _before_ swapping the rendertargets.
178     if (doSnapshot && swapRenderTarget) {
179         if (call->flags & trace::CALL_FLAG_END_FRAME) {
180             // For swapbuffers/presents we still use this
181             // call number, spite not have been executed yet.
182             takeSnapshot(call->no);
183         } else {
184             // Whereas for ordinate fbo/rendertarget changes we
185             // use the previous call's number.
186             takeSnapshot(call->no - 1);
187         }
188     }
189
190     callNo = call->no;
191     retracer.retrace(*call);
192
193     if (doSnapshot && !swapRenderTarget)
194         takeSnapshot(call->no);
195
196     if (call->no >= dumpStateCallNo &&
197         dumper->dumpState(std::cout)) {
198         exit(0);
199     }
200 }
201
202
203 class RelayRunner;
204
205
206 /**
207  * Implement multi-threading by mimicking a relay race.
208  */
209 class RelayRace
210 {
211 private:
212     /**
213      * Runners indexed by the leg they run (i.e, the thread_ids from the
214      * trace).
215      */
216     std::vector<RelayRunner*> runners;
217
218 public:
219     RelayRace();
220
221     ~RelayRace();
222
223     RelayRunner *
224     getRunner(unsigned leg);
225
226     inline RelayRunner *
227     getForeRunner() {
228         return getRunner(0);
229     }
230
231     void
232     run(void);
233
234     void
235     passBaton(trace::Call *call);
236
237     void
238     finishLine();
239
240     void
241     stopRunners();
242 };
243
244
245 /**
246  * Each runner is a thread.
247  *
248  * The fore runner doesn't have its own thread, but instead uses the thread
249  * where the race started.
250  */
251 class RelayRunner
252 {
253 private:
254     friend class RelayRace;
255
256     RelayRace *race;
257
258     unsigned leg;
259     
260     os::mutex mutex;
261     os::condition_variable wake_cond;
262
263     /**
264      * There are protected by the mutex.
265      */
266     bool finished;
267     trace::Call *baton;
268
269     os::thread thread;
270
271     static void *
272     runnerThread(RelayRunner *_this);
273
274 public:
275     RelayRunner(RelayRace *race, unsigned _leg) :
276         race(race),
277         leg(_leg),
278         finished(false),
279         baton(0)
280     {
281         /* The fore runner does not need a new thread */
282         if (leg) {
283             thread = os::thread(runnerThread, this);
284         }
285     }
286
287     /**
288      * Thread main loop.
289      */
290     void
291     runRace(void) {
292         os::unique_lock<os::mutex> lock(mutex);
293
294         while (1) {
295             while (!finished && !baton) {
296                 wake_cond.wait(lock);
297             }
298
299             if (finished) {
300                 break;
301             }
302
303             assert(baton);
304             trace::Call *call = baton;
305             baton = 0;
306
307             runLeg(call);
308         }
309
310         if (0) std::cerr << "leg " << leg << " actually finishing\n";
311
312         if (leg == 0) {
313             race->stopRunners();
314         }
315     }
316
317     /**
318      * Interpret successive calls.
319      */
320     void
321     runLeg(trace::Call *call) {
322
323         /* Consume successive calls for this thread. */
324         do {
325             bool callEndsFrame = false;
326             static trace::ParseBookmark frameStart;
327
328             assert(call);
329             assert(call->thread_id == leg);
330
331             if (loopOnFinish && call->flags & trace::CALL_FLAG_END_FRAME) {
332                 callEndsFrame = true;
333                 parser.getBookmark(frameStart);
334             }
335
336             retraceCall(call);
337             delete call;
338             call = parser.parse_call();
339
340             /* Restart last frame if looping is requested. */
341             if (loopOnFinish) {
342                 if (!call) {
343                     parser.setBookmark(lastFrameStart);
344                     call = parser.parse_call();
345                 } else if (callEndsFrame) {
346                     lastFrameStart = frameStart;
347                 }
348             }
349
350         } while (call && call->thread_id == leg);
351
352         if (call) {
353             /* Pass the baton */
354             assert(call->thread_id != leg);
355             flushRendering();
356             race->passBaton(call);
357         } else {
358             /* Reached the finish line */
359             if (0) std::cerr << "finished on leg " << leg << "\n";
360             if (leg) {
361                 /* Notify the fore runner */
362                 race->finishLine();
363             } else {
364                 /* We are the fore runner */
365                 finished = true;
366             }
367         }
368     }
369
370     /**
371      * Called by other threads when relinquishing the baton.
372      */
373     void
374     receiveBaton(trace::Call *call) {
375         assert (call->thread_id == leg);
376
377         mutex.lock();
378         baton = call;
379         mutex.unlock();
380
381         wake_cond.signal();
382     }
383
384     /**
385      * Called by the fore runner when the race is over.
386      */
387     void
388     finishRace() {
389         if (0) std::cerr << "notify finish to leg " << leg << "\n";
390
391         mutex.lock();
392         finished = true;
393         mutex.unlock();
394
395         wake_cond.signal();
396     }
397 };
398
399
400 void *
401 RelayRunner::runnerThread(RelayRunner *_this) {
402     _this->runRace();
403     return 0;
404 }
405
406
407 RelayRace::RelayRace() {
408     runners.push_back(new RelayRunner(this, 0));
409 }
410
411
412 RelayRace::~RelayRace() {
413     assert(runners.size() >= 1);
414     std::vector<RelayRunner*>::const_iterator it;
415     for (it = runners.begin(); it != runners.end(); ++it) {
416         RelayRunner* runner = *it;
417         if (runner) {
418             delete runner;
419         }
420     }
421 }
422
423
424 /**
425  * Get (or instantiate) a runner for the specified leg.
426  */
427 RelayRunner *
428 RelayRace::getRunner(unsigned leg) {
429     RelayRunner *runner;
430
431     if (leg >= runners.size()) {
432         runners.resize(leg + 1);
433         runner = 0;
434     } else {
435         runner = runners[leg];
436     }
437     if (!runner) {
438         runner = new RelayRunner(this, leg);
439         runners[leg] = runner;
440     }
441     return runner;
442 }
443
444
445 /**
446  * Start the race.
447  */
448 void
449 RelayRace::run(void) {
450     trace::Call *call;
451     call = parser.parse_call();
452     if (!call) {
453         /* Nothing to do */
454         return;
455     }
456
457     /* If the user wants to loop we need to get a bookmark target. We
458      * usually get this after replaying a call that ends a frame, but
459      * for a trace that has only one frame we need to get it at the
460      * beginning. */
461     if (loopOnFinish) {
462         parser.getBookmark(lastFrameStart);
463     }
464
465     RelayRunner *foreRunner = getForeRunner();
466     if (call->thread_id == 0) {
467         /* We are the forerunner thread, so no need to pass baton */
468         foreRunner->baton = call;
469     } else {
470         passBaton(call);
471     }
472
473     /* Start the forerunner thread */
474     foreRunner->runRace();
475 }
476
477
478 /**
479  * Pass the baton (i.e., the call) to the appropriate thread.
480  */
481 void
482 RelayRace::passBaton(trace::Call *call) {
483     if (0) std::cerr << "switching to thread " << call->thread_id << "\n";
484     RelayRunner *runner = getRunner(call->thread_id);
485     runner->receiveBaton(call);
486 }
487
488
489 /**
490  * Called when a runner other than the forerunner reaches the finish line.
491  *
492  * Only the fore runner can finish the race, so inform him that the race is
493  * finished.
494  */
495 void
496 RelayRace::finishLine(void) {
497     RelayRunner *foreRunner = getForeRunner();
498     foreRunner->finishRace();
499 }
500
501
502 /**
503  * Called by the fore runner after finish line to stop all other runners.
504  */
505 void
506 RelayRace::stopRunners(void) {
507     std::vector<RelayRunner*>::const_iterator it;
508     for (it = runners.begin() + 1; it != runners.end(); ++it) {
509         RelayRunner* runner = *it;
510         if (runner) {
511             runner->finishRace();
512         }
513     }
514 }
515
516
517 static void
518 mainLoop() {
519     addCallbacks(retracer);
520
521     long long startTime = 0; 
522     frameNo = 0;
523
524     startTime = os::getTime();
525
526     if (singleThread) {
527         trace::Call *call;
528         while ((call = parser.parse_call())) {
529             retraceCall(call);
530             delete call;
531         };
532         flushRendering();
533     } else {
534         RelayRace race;
535         race.run();
536     }
537
538     long long endTime = os::getTime();
539     float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
540
541     if ((retrace::verbosity >= -1) || (retrace::profiling)) {
542         std::cout << 
543             "Rendered " << frameNo << " frames"
544             " in " <<  timeInterval << " secs,"
545             " average of " << (frameNo/timeInterval) << " fps\n";
546     }
547
548     if (waitOnFinish) {
549         waitForInput();
550     } else {
551         return;
552     }
553 }
554
555
556 } /* namespace retrace */
557
558
559 static void
560 usage(const char *argv0) {
561     std::cout << 
562         "Usage: " << argv0 << " [OPTION] TRACE [...]\n"
563         "Replay TRACE.\n"
564         "\n"
565         "  -b, --benchmark         benchmark mode (no error checking or warning messages)\n"
566         "      --pcpu              cpu profiling (cpu times per call)\n"
567         "      --pgpu              gpu profiling (gpu times per draw call)\n"
568         "      --ppd               pixels drawn profiling (pixels drawn per draw call)\n"
569         "      --pmem              memory usage profiling (vsize rss per call)\n"
570         "  -c, --compare=PREFIX    compare against snapshots with given PREFIX\n"
571         "  -C, --calls=CALLSET     calls to compare (default is every frame)\n"
572         "      --call-nos[=BOOL]   use call numbers in snapshot filenames\n"
573         "      --core              use core profile\n"
574         "      --db                use a double buffer visual (default)\n"
575         "      --driver=DRIVER     force driver type (`hw`, `sw`, `ref`, `null`, or driver module name)\n"
576         "      --sb                use a single buffer visual\n"
577         "  -s, --snapshot-prefix=PREFIX    take snapshots; `-` for PNM stdout output\n"
578         "      --snapshot-format=FMT       use (PNM or RGB; default is PNM) when writing to stdout output\n"
579         "  -S, --snapshot=CALLSET  calls to snapshot (default is every frame)\n"
580         "  -v, --verbose           increase output verbosity\n"
581         "  -D, --dump-state=CALL   dump state at specific call no\n"
582         "  -w, --wait              waitOnFinish on final frame\n"
583         "      --loop              continuously loop, replaying final frame.\n"
584         "      --singlethread      use a single thread to replay command stream\n";
585 }
586
587 enum {
588     CALL_NOS_OPT = CHAR_MAX + 1,
589     CORE_OPT,
590     DB_OPT,
591     DRIVER_OPT,
592     PCPU_OPT,
593     PGPU_OPT,
594     PPD_OPT,
595     PMEM_OPT,
596     SB_OPT,
597     SNAPSHOT_FORMAT_OPT,
598     LOOP_OPT,
599     SINGLETHREAD_OPT
600 };
601
602 const static char *
603 shortOptions = "bc:C:D:hs:S:vw";
604
605 const static struct option
606 longOptions[] = {
607     {"benchmark", no_argument, 0, 'b'},
608     {"call-nos", optional_argument, 0, CALL_NOS_OPT },
609     {"calls", required_argument, 0, 'C'},
610     {"compare", required_argument, 0, 'c'},
611     {"core", no_argument, 0, CORE_OPT},
612     {"db", no_argument, 0, DB_OPT},
613     {"driver", required_argument, 0, DRIVER_OPT},
614     {"dump-state", required_argument, 0, 'D'},
615     {"help", no_argument, 0, 'h'},
616     {"pcpu", no_argument, 0, PCPU_OPT},
617     {"pgpu", no_argument, 0, PGPU_OPT},
618     {"ppd", no_argument, 0, PPD_OPT},
619     {"pmem", no_argument, 0, PMEM_OPT},
620     {"sb", no_argument, 0, SB_OPT},
621     {"snapshot-prefix", required_argument, 0, 's'},
622     {"snapshot-format", required_argument, 0, SNAPSHOT_FORMAT_OPT},
623     {"snapshot", required_argument, 0, 'S'},
624     {"verbose", no_argument, 0, 'v'},
625     {"wait", no_argument, 0, 'w'},
626     {"loop", no_argument, 0, LOOP_OPT},
627     {"singlethread", no_argument, 0, SINGLETHREAD_OPT},
628     {0, 0, 0, 0}
629 };
630
631
632 static void exceptionCallback(void)
633 {
634     std::cerr << retrace::callNo << ": error: caught an unhandled exception\n";
635 }
636
637
638 extern "C"
639 int main(int argc, char **argv)
640 {
641     using namespace retrace;
642     int i;
643
644     assert(compareFrequency.empty());
645     assert(snapshotFrequency.empty());
646
647     int opt;
648     while  ((opt = getopt_long_only(argc, argv, shortOptions, longOptions, NULL)) != -1) {
649         switch (opt) {
650         case 'h':
651             usage(argv[0]);
652             return 0;
653         case 'b':
654             retrace::debug = false;
655             retrace::verbosity = -1;
656             break;
657         case CALL_NOS_OPT:
658             useCallNos = trace::boolOption(optarg);
659             break;
660         case 'c':
661             comparePrefix = optarg;
662             if (compareFrequency.empty()) {
663                 compareFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
664             }
665             break;
666         case 'C':
667             compareFrequency = trace::CallSet(optarg);
668             if (comparePrefix == NULL) {
669                 comparePrefix = "";
670             }
671             break;
672         case 'D':
673             dumpStateCallNo = atoi(optarg);
674             dumpingState = true;
675             retrace::verbosity = -2;
676             break;
677         case CORE_OPT:
678             retrace::coreProfile = true;
679             break;
680         case DB_OPT:
681             retrace::doubleBuffer = true;
682             break;
683         case DRIVER_OPT:
684             if (strcasecmp(optarg, "hw") == 0) {
685                 driver = DRIVER_HARDWARE;
686             } else if (strcasecmp(optarg, "sw") == 0) {
687                 driver = DRIVER_SOFTWARE;
688             } else if (strcasecmp(optarg, "ref") == 0) {
689                 driver = DRIVER_REFERENCE;
690             } else if (strcasecmp(optarg, "null") == 0) {
691                 driver = DRIVER_NULL;
692             } else {
693                 driver = DRIVER_MODULE;
694                 driverModule = optarg;
695             }
696             break;
697         case SB_OPT:
698             retrace::doubleBuffer = false;
699             break;
700         case SINGLETHREAD_OPT:
701             retrace::singleThread = true;
702             break;
703         case 's':
704             snapshotPrefix = optarg;
705             if (snapshotFrequency.empty()) {
706                 snapshotFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
707             }
708             if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
709                 os::setBinaryMode(stdout);
710                 retrace::verbosity = -2;
711             }
712             break;
713         case SNAPSHOT_FORMAT_OPT:
714             if (strcmp(optarg, "RGB") == 0)
715                 snapshotFormat = RAW_RGB;
716             else
717                 snapshotFormat = PNM_FMT;
718             break;
719         case 'S':
720             snapshotFrequency = trace::CallSet(optarg);
721             if (snapshotPrefix == NULL) {
722                 snapshotPrefix = "";
723             }
724             break;
725         case 'v':
726             ++retrace::verbosity;
727             break;
728         case 'w':
729             waitOnFinish = true;
730             break;
731         case LOOP_OPT:
732             loopOnFinish = true;
733             break;
734         case PGPU_OPT:
735             retrace::debug = false;
736             retrace::profiling = true;
737             retrace::verbosity = -1;
738
739             retrace::profilingGpuTimes = true;
740             break;
741         case PCPU_OPT:
742             retrace::debug = false;
743             retrace::profiling = true;
744             retrace::verbosity = -1;
745
746             retrace::profilingCpuTimes = true;
747             break;
748         case PPD_OPT:
749             retrace::debug = false;
750             retrace::profiling = true;
751             retrace::verbosity = -1;
752
753             retrace::profilingPixelsDrawn = true;
754             break;
755         case PMEM_OPT:
756             retrace::debug = false;
757             retrace::profiling = true;
758             retrace::verbosity = -1;
759
760             retrace::profilingMemoryUsage = true;
761             break;
762         default:
763             std::cerr << "error: unknown option " << opt << "\n";
764             usage(argv[0]);
765             return 1;
766         }
767     }
768
769     retrace::setUp();
770     if (retrace::profiling) {
771         retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn, retrace::profilingMemoryUsage);
772     }
773
774     os::setExceptionCallback(exceptionCallback);
775
776     for (i = optind; i < argc; ++i) {
777         if (!retrace::parser.open(argv[i])) {
778             return 1;
779         }
780
781         retrace::mainLoop();
782
783         retrace::parser.close();
784     }
785     
786     os::resetExceptionCallback();
787
788     // XXX: X often hangs on XCloseDisplay
789     //retrace::cleanUp();
790
791     return 0;
792 }
793