]> git.cworth.org Git - apitrace/blob - retrace/retrace_main.cpp
replay: Fix --loop to work when --singlethread is in effect
[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
521         call = parser.parse_call();
522
523         /* If the user wants to loop we need to get a bookmark target. We
524          * usually get this after replaying a call that ends a frame, but
525          * for a trace that has only one frame we need to get it at the
526          * beginning. */
527         if (loopOnFinish) {
528             parser.getBookmark(lastFrameStart);
529         }
530
531         do {
532             bool callEndsFrame = false;
533             static trace::ParseBookmark frameStart;
534
535             if (loopOnFinish && call->flags & trace::CALL_FLAG_END_FRAME) {
536                 callEndsFrame = true;
537                 parser.getBookmark(frameStart);
538             }
539
540             retraceCall(call);
541             delete call;
542             call = parser.parse_call();
543
544             /* Restart last frame if looping is requested. */
545             if (loopOnFinish) {
546                 if (!call) {
547                     parser.setBookmark(lastFrameStart);
548                     call = parser.parse_call();
549                 } else if (callEndsFrame) {
550                     lastFrameStart = frameStart;
551                 }
552             }
553
554         } while (call);
555
556         flushRendering();
557     } else {
558         RelayRace race;
559         race.run();
560     }
561
562     long long endTime = os::getTime();
563     float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
564
565     if ((retrace::verbosity >= -1) || (retrace::profiling)) {
566         std::cout << 
567             "Rendered " << frameNo << " frames"
568             " in " <<  timeInterval << " secs,"
569             " average of " << (frameNo/timeInterval) << " fps\n";
570     }
571
572     if (waitOnFinish) {
573         waitForInput();
574     } else {
575         return;
576     }
577 }
578
579
580 } /* namespace retrace */
581
582
583 static void
584 usage(const char *argv0) {
585     std::cout << 
586         "Usage: " << argv0 << " [OPTION] TRACE [...]\n"
587         "Replay TRACE.\n"
588         "\n"
589         "  -b, --benchmark         benchmark mode (no error checking or warning messages)\n"
590         "      --pcpu              cpu profiling (cpu times per call)\n"
591         "      --pgpu              gpu profiling (gpu times per draw call)\n"
592         "      --ppd               pixels drawn profiling (pixels drawn per draw call)\n"
593         "      --pmem              memory usage profiling (vsize rss per call)\n"
594         "  -c, --compare=PREFIX    compare against snapshots with given PREFIX\n"
595         "  -C, --calls=CALLSET     calls to compare (default is every frame)\n"
596         "      --call-nos[=BOOL]   use call numbers in snapshot filenames\n"
597         "      --core              use core profile\n"
598         "      --db                use a double buffer visual (default)\n"
599         "      --driver=DRIVER     force driver type (`hw`, `sw`, `ref`, `null`, or driver module name)\n"
600         "      --sb                use a single buffer visual\n"
601         "  -s, --snapshot-prefix=PREFIX    take snapshots; `-` for PNM stdout output\n"
602         "  -S, --snapshot=CALLSET  calls to snapshot (default is every frame)\n"
603         "  -v, --verbose           increase output verbosity\n"
604         "  -D, --dump-state=CALL   dump state at specific call no\n"
605         "  -w, --wait              waitOnFinish on final frame\n"
606         "      --loop              continuously loop, replaying final frame.\n"
607         "      --singlethread      use a single thread to replay command stream\n";
608 }
609
610 enum {
611     CALL_NOS_OPT = CHAR_MAX + 1,
612     CORE_OPT,
613     DB_OPT,
614     DRIVER_OPT,
615     PCPU_OPT,
616     PGPU_OPT,
617     PPD_OPT,
618     PMEM_OPT,
619     SB_OPT,
620     LOOP_OPT,
621     SINGLETHREAD_OPT
622 };
623
624 const static char *
625 shortOptions = "bc:C:D:hs:S:vw";
626
627 const static struct option
628 longOptions[] = {
629     {"benchmark", no_argument, 0, 'b'},
630     {"call-nos", optional_argument, 0, CALL_NOS_OPT },
631     {"calls", required_argument, 0, 'C'},
632     {"compare", required_argument, 0, 'c'},
633     {"core", no_argument, 0, CORE_OPT},
634     {"db", no_argument, 0, DB_OPT},
635     {"driver", required_argument, 0, DRIVER_OPT},
636     {"dump-state", required_argument, 0, 'D'},
637     {"help", no_argument, 0, 'h'},
638     {"pcpu", no_argument, 0, PCPU_OPT},
639     {"pgpu", no_argument, 0, PGPU_OPT},
640     {"ppd", no_argument, 0, PPD_OPT},
641     {"pmem", no_argument, 0, PMEM_OPT},
642     {"sb", no_argument, 0, SB_OPT},
643     {"snapshot-prefix", required_argument, 0, 's'},
644     {"snapshot", required_argument, 0, 'S'},
645     {"verbose", no_argument, 0, 'v'},
646     {"wait", no_argument, 0, 'w'},
647     {"loop", no_argument, 0, LOOP_OPT},
648     {"singlethread", no_argument, 0, SINGLETHREAD_OPT},
649     {0, 0, 0, 0}
650 };
651
652
653 static void exceptionCallback(void)
654 {
655     std::cerr << retrace::callNo << ": error: caught an unhandled exception\n";
656 }
657
658
659 extern "C"
660 int main(int argc, char **argv)
661 {
662     using namespace retrace;
663     int i;
664
665     assert(compareFrequency.empty());
666     assert(snapshotFrequency.empty());
667
668     int opt;
669     while  ((opt = getopt_long_only(argc, argv, shortOptions, longOptions, NULL)) != -1) {
670         switch (opt) {
671         case 'h':
672             usage(argv[0]);
673             return 0;
674         case 'b':
675             retrace::debug = false;
676             retrace::verbosity = -1;
677             break;
678         case CALL_NOS_OPT:
679             useCallNos = trace::boolOption(optarg);
680             break;
681         case 'c':
682             comparePrefix = optarg;
683             if (compareFrequency.empty()) {
684                 compareFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
685             }
686             break;
687         case 'C':
688             compareFrequency = trace::CallSet(optarg);
689             if (comparePrefix == NULL) {
690                 comparePrefix = "";
691             }
692             break;
693         case 'D':
694             dumpStateCallNo = atoi(optarg);
695             dumpingState = true;
696             retrace::verbosity = -2;
697             break;
698         case CORE_OPT:
699             retrace::coreProfile = true;
700             break;
701         case DB_OPT:
702             retrace::doubleBuffer = true;
703             break;
704         case DRIVER_OPT:
705             if (strcasecmp(optarg, "hw") == 0) {
706                 driver = DRIVER_HARDWARE;
707             } else if (strcasecmp(optarg, "sw") == 0) {
708                 driver = DRIVER_SOFTWARE;
709             } else if (strcasecmp(optarg, "ref") == 0) {
710                 driver = DRIVER_REFERENCE;
711             } else if (strcasecmp(optarg, "null") == 0) {
712                 driver = DRIVER_NULL;
713             } else {
714                 driver = DRIVER_MODULE;
715                 driverModule = optarg;
716             }
717             break;
718         case SB_OPT:
719             retrace::doubleBuffer = false;
720             break;
721         case SINGLETHREAD_OPT:
722             retrace::singleThread = true;
723             break;
724         case 's':
725             snapshotPrefix = optarg;
726             if (snapshotFrequency.empty()) {
727                 snapshotFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
728             }
729             if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
730                 os::setBinaryMode(stdout);
731                 retrace::verbosity = -2;
732             }
733             break;
734         case 'S':
735             snapshotFrequency = trace::CallSet(optarg);
736             if (snapshotPrefix == NULL) {
737                 snapshotPrefix = "";
738             }
739             break;
740         case 'v':
741             ++retrace::verbosity;
742             break;
743         case 'w':
744             waitOnFinish = true;
745             break;
746         case LOOP_OPT:
747             loopOnFinish = true;
748             break;
749         case PGPU_OPT:
750             retrace::debug = false;
751             retrace::profiling = true;
752             retrace::verbosity = -1;
753
754             retrace::profilingGpuTimes = true;
755             break;
756         case PCPU_OPT:
757             retrace::debug = false;
758             retrace::profiling = true;
759             retrace::verbosity = -1;
760
761             retrace::profilingCpuTimes = true;
762             break;
763         case PPD_OPT:
764             retrace::debug = false;
765             retrace::profiling = true;
766             retrace::verbosity = -1;
767
768             retrace::profilingPixelsDrawn = true;
769             break;
770         case PMEM_OPT:
771             retrace::debug = false;
772             retrace::profiling = true;
773             retrace::verbosity = -1;
774
775             retrace::profilingMemoryUsage = true;
776             break;
777         default:
778             std::cerr << "error: unknown option " << opt << "\n";
779             usage(argv[0]);
780             return 1;
781         }
782     }
783
784     retrace::setUp();
785     if (retrace::profiling) {
786         retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn, retrace::profilingMemoryUsage);
787     }
788
789     os::setExceptionCallback(exceptionCallback);
790
791     for (i = optind; i < argc; ++i) {
792         if (!retrace::parser.open(argv[i])) {
793             return 1;
794         }
795
796         retrace::mainLoop();
797
798         retrace::parser.close();
799     }
800     
801     os::resetExceptionCallback();
802
803     // XXX: X often hangs on XCloseDisplay
804     //retrace::cleanUp();
805
806     return 0;
807 }
808