1 /**************************************************************************
3 * Copyright 2011 Jose Fonseca
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:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
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
24 **************************************************************************/
28 #include <limits.h> // for CHAR_MAX
32 #include "os_binary.hpp"
33 #include "os_time.hpp"
34 #include "os_thread.hpp"
36 #include "trace_callset.hpp"
37 #include "trace_dump.hpp"
38 #include "retrace.hpp"
41 static bool waitOnFinish = false;
43 static const char *comparePrefix = NULL;
44 static const char *snapshotPrefix = NULL;
45 static trace::CallSet snapshotFrequency;
46 static trace::CallSet compareFrequency;
48 static unsigned dumpStateCallNo = ~0;
50 retrace::Retracer retracer;
57 trace::Profiler profiler;
62 bool dumpingState = false;
64 Driver driver = DRIVER_DEFAULT;
65 const char *driverModule = NULL;
67 bool doubleBuffer = true;
68 bool coreProfile = false;
70 bool profiling = false;
71 bool profilingGpuTimes = false;
72 bool profilingCpuTimes = false;
73 bool profilingPixelsDrawn = false;
80 frameComplete(trace::Call &call) {
85 static Dumper defaultDumper;
87 Dumper *dumper = &defaultDumper;
91 * Take/compare snapshots.
94 takeSnapshot(unsigned call_no) {
95 assert(snapshotPrefix || comparePrefix);
97 image::Image *ref = NULL;
100 os::String filename = os::String::format("%s%010u.png", comparePrefix, call_no);
101 ref = image::readPNG(filename);
105 if (retrace::verbosity >= 0) {
106 std::cout << "Read " << filename << "\n";
110 image::Image *src = dumper->getSnapshot();
112 std::cout << "Failed to get snapshot\n";
116 if (snapshotPrefix) {
117 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
119 snprintf(comment, sizeof comment, "%u", call_no);
120 src->writePNM(std::cout, comment);
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";
130 std::cout << "Snapshot " << call_no << " average precision of " << src->compare(*ref) << " bits\n";
143 * Take snapshots before/after retracing (as appropriate) and dispatch it to
144 * the respective handler.
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);
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);
161 // Whereas for ordinate fbo/rendertarget changes we
162 // use the previous call's number.
163 takeSnapshot(call->no - 1);
168 retracer.retrace(*call);
170 if (doSnapshot && !swapRenderTarget)
171 takeSnapshot(call->no);
173 if (call->no >= dumpStateCallNo &&
174 dumper->dumpState(std::cout)) {
184 * Implement multi-threading by mimicking a relay race.
190 * Runners indexed by the leg they run (i.e, the thread_ids from the
193 std::vector<RelayRunner*> runners;
201 getRunner(unsigned leg);
212 passBaton(trace::Call *call);
223 * Each runner is a thread.
225 * The fore runner doesn't have its own thread, but instead uses the thread
226 * where the race started.
231 friend class RelayRace;
238 os::condition_variable wake_cond;
241 * There are protected by the mutex.
249 runnerThread(RelayRunner *_this);
252 RelayRunner(RelayRace *race, unsigned _leg) :
258 /* The fore runner does not need a new thread */
260 thread = os::thread(runnerThread, this);
269 os::unique_lock<os::mutex> lock(mutex);
272 while (!finished && !baton) {
273 wake_cond.wait(lock);
281 trace::Call *call = baton;
287 if (0) std::cerr << "leg " << leg << " actually finishing\n";
295 * Interpret successive calls.
298 runLeg(trace::Call *call) {
299 /* Consume successive calls for this thread. */
302 assert(call->thread_id == leg);
305 call = parser.parse_call();
306 } while (call && call->thread_id == leg);
310 assert(call->thread_id != leg);
312 race->passBaton(call);
314 /* Reached the finish line */
315 if (0) std::cerr << "finished on leg " << leg << "\n";
317 /* Notify the fore runner */
320 /* We are the fore runner */
327 * Called by other threads when relinquishing the baton.
330 receiveBaton(trace::Call *call) {
331 assert (call->thread_id == leg);
341 * Called by the fore runner when the race is over.
345 if (0) std::cerr << "notify finish to leg " << leg << "\n";
357 RelayRunner::runnerThread(RelayRunner *_this) {
363 RelayRace::RelayRace() {
364 runners.push_back(new RelayRunner(this, 0));
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;
381 * Get (or instantiate) a runner for the specified leg.
384 RelayRace::getRunner(unsigned leg) {
387 if (leg >= runners.size()) {
388 runners.resize(leg + 1);
391 runner = runners[leg];
394 runner = new RelayRunner(this, leg);
395 runners[leg] = runner;
405 RelayRace::run(void) {
407 call = parser.parse_call();
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;
421 /* Start the forerunner thread */
422 foreRunner->runRace();
427 * Pass the baton (i.e., the call) to the appropriate thread.
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);
438 * Called when a runner other than the forerunner reaches the finish line.
440 * Only the fore runner can finish the race, so inform him that the race is
444 RelayRace::finishLine(void) {
445 RelayRunner *foreRunner = getForeRunner();
446 foreRunner->finishRace();
451 * Called by the fore runner after finish line to stop all other runners.
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;
459 runner->finishRace();
467 addCallbacks(retracer);
469 long long startTime = 0;
472 startTime = os::getTime();
477 long long endTime = os::getTime();
478 float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
480 if ((retrace::verbosity >= -1) || (retrace::profiling)) {
482 "Rendered " << frameNo << " frames"
483 " in " << timeInterval << " secs,"
484 " average of " << (frameNo/timeInterval) << " fps\n";
495 } /* namespace retrace */
499 usage(const char *argv0) {
501 "Usage: " << argv0 << " [OPTION] TRACE [...]\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";
522 CORE_OPT = CHAR_MAX + 1,
532 shortOptions = "bc:C:D:hs:S:vw";
534 const static struct option
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'},
556 static void exceptionCallback(void)
558 std::cerr << retrace::callNo << ": error: caught an unhandled exception\n";
563 int main(int argc, char **argv)
565 using namespace retrace;
568 assert(compareFrequency.empty());
569 assert(snapshotFrequency.empty());
572 while ((opt = getopt_long_only(argc, argv, shortOptions, longOptions, NULL)) != -1) {
578 retrace::debug = false;
579 retrace::verbosity = -1;
582 comparePrefix = optarg;
583 if (compareFrequency.empty()) {
584 compareFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
588 compareFrequency = trace::CallSet(optarg);
589 if (comparePrefix == NULL) {
594 dumpStateCallNo = atoi(optarg);
596 retrace::verbosity = -2;
599 retrace::coreProfile = true;
602 retrace::doubleBuffer = true;
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;
614 driver = DRIVER_MODULE;
615 driverModule = optarg;
619 retrace::doubleBuffer = false;
622 snapshotPrefix = optarg;
623 if (snapshotFrequency.empty()) {
624 snapshotFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
626 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
627 os::setBinaryMode(stdout);
628 retrace::verbosity = -2;
632 snapshotFrequency = trace::CallSet(optarg);
633 if (snapshotPrefix == NULL) {
638 ++retrace::verbosity;
644 retrace::debug = false;
645 retrace::profiling = true;
646 retrace::verbosity = -1;
648 retrace::profilingGpuTimes = true;
651 retrace::debug = false;
652 retrace::profiling = true;
653 retrace::verbosity = -1;
655 retrace::profilingCpuTimes = true;
658 retrace::debug = false;
659 retrace::profiling = true;
660 retrace::verbosity = -1;
662 retrace::profilingPixelsDrawn = true;
665 std::cerr << "error: unknown option " << opt << "\n";
672 if (retrace::profiling) {
673 retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn);
676 os::setExceptionCallback(exceptionCallback);
678 for (i = optind; i < argc; ++i) {
679 if (!retrace::parser.open(argv[i])) {
685 retrace::parser.close();
688 os::resetExceptionCallback();
690 // XXX: X often hangs on XCloseDisplay
691 //retrace::cleanUp();