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