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