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