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