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 bool doubleBuffer = true;
65 bool coreProfile = false;
67 bool profiling = false;
68 bool profilingGpuTimes = false;
69 bool profilingCpuTimes = false;
70 bool profilingPixelsDrawn = false;
77 frameComplete(trace::Call &call) {
82 static Dumper defaultDumper;
84 Dumper *dumper = &defaultDumper;
88 * Take/compare snapshots.
91 takeSnapshot(unsigned call_no) {
92 assert(snapshotPrefix || comparePrefix);
94 image::Image *ref = NULL;
97 os::String filename = os::String::format("%s%010u.png", comparePrefix, call_no);
98 ref = image::readPNG(filename);
102 if (retrace::verbosity >= 0) {
103 std::cout << "Read " << filename << "\n";
107 image::Image *src = dumper->getSnapshot();
112 if (snapshotPrefix) {
113 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
115 snprintf(comment, sizeof comment, "%u", call_no);
116 src->writePNM(std::cout, comment);
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";
126 std::cout << "Snapshot " << call_no << " average precision of " << src->compare(*ref) << " bits\n";
139 * Take snapshots before/after retracing (as appropriate) and dispatch it to
140 * the respective handler.
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);
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);
157 // Whereas for ordinate fbo/rendertarget changes we
158 // use the previous call's number.
159 takeSnapshot(call->no - 1);
164 retracer.retrace(*call);
166 if (doSnapshot && !swapRenderTarget)
167 takeSnapshot(call->no);
169 if (call->no >= dumpStateCallNo &&
170 dumper->dumpState(std::cout)) {
180 * Implement multi-threading by mimicking a relay race.
186 * Runners indexed by the leg they run (i.e, the thread_ids from the
189 std::vector<RelayRunner*> runners;
197 getRunner(unsigned leg);
208 passBaton(trace::Call *call);
219 * Each runner is a thread.
221 * The fore runner doesn't have its own thread, but instead uses the thread
222 * where the race started.
227 friend class RelayRace;
234 os::condition_variable wake_cond;
237 * There are protected by the mutex.
245 runnerThread(RelayRunner *_this);
248 RelayRunner(RelayRace *race, unsigned _leg) :
254 /* The fore runner does not need a new thread */
256 thread = os::thread(runnerThread, this);
265 os::unique_lock<os::mutex> lock(mutex);
268 while (!finished && !baton) {
269 wake_cond.wait(lock);
277 trace::Call *call = baton;
283 if (0) std::cerr << "leg " << leg << " actually finishing\n";
291 * Interpret successive calls.
294 runLeg(trace::Call *call) {
295 /* Consume successive calls for this thread. */
298 assert(call->thread_id == leg);
301 call = parser.parse_call();
302 } while (call && call->thread_id == leg);
306 assert(call->thread_id != leg);
308 race->passBaton(call);
310 /* Reached the finish line */
311 if (0) std::cerr << "finished on leg " << leg << "\n";
313 /* Notify the fore runner */
316 /* We are the fore runner */
323 * Called by other threads when relinquishing the baton.
326 receiveBaton(trace::Call *call) {
327 assert (call->thread_id == leg);
337 * Called by the fore runner when the race is over.
341 if (0) std::cerr << "notify finish to leg " << leg << "\n";
353 RelayRunner::runnerThread(RelayRunner *_this) {
359 RelayRace::RelayRace() {
360 runners.push_back(new RelayRunner(this, 0));
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;
377 * Get (or instantiate) a runner for the specified leg.
380 RelayRace::getRunner(unsigned leg) {
383 if (leg >= runners.size()) {
384 runners.resize(leg + 1);
387 runner = runners[leg];
390 runner = new RelayRunner(this, leg);
391 runners[leg] = runner;
401 RelayRace::run(void) {
403 call = parser.parse_call();
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;
417 /* Start the forerunner thread */
418 foreRunner->runRace();
423 * Pass the baton (i.e., the call) to the appropriate thread.
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);
434 * Called when a runner other than the forerunner reaches the finish line.
436 * Only the fore runner can finish the race, so inform him that the race is
440 RelayRace::finishLine(void) {
441 RelayRunner *foreRunner = getForeRunner();
442 foreRunner->finishRace();
447 * Called by the fore runner after finish line to stop all other runners.
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;
455 runner->finishRace();
463 addCallbacks(retracer);
465 long long startTime = 0;
468 startTime = os::getTime();
473 long long endTime = os::getTime();
474 float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
476 if ((retrace::verbosity >= -1) || (retrace::profiling)) {
478 "Rendered " << frameNo << " frames"
479 " in " << timeInterval << " secs,"
480 " average of " << (frameNo/timeInterval) << " fps\n";
491 } /* namespace retrace */
495 usage(const char *argv0) {
497 "Usage: " << argv0 << " [OPTION] TRACE [...]\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";
517 CORE_OPT = CHAR_MAX + 1,
526 shortOptions = "bc:C:D:hs:S:vw";
528 const static struct option
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'},
549 int main(int argc, char **argv)
551 using namespace retrace;
554 assert(compareFrequency.empty());
555 assert(snapshotFrequency.empty());
558 while ((opt = getopt_long_only(argc, argv, shortOptions, longOptions, NULL)) != -1) {
564 retrace::debug = false;
565 retrace::verbosity = -1;
568 comparePrefix = optarg;
569 if (compareFrequency.empty()) {
570 compareFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
574 compareFrequency = trace::CallSet(optarg);
575 if (comparePrefix == NULL) {
580 dumpStateCallNo = atoi(optarg);
582 retrace::verbosity = -2;
585 retrace::coreProfile = true;
588 retrace::doubleBuffer = true;
591 retrace::doubleBuffer = false;
594 snapshotPrefix = optarg;
595 if (snapshotFrequency.empty()) {
596 snapshotFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
598 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
599 os::setBinaryMode(stdout);
600 retrace::verbosity = -2;
604 snapshotFrequency = trace::CallSet(optarg);
605 if (snapshotPrefix == NULL) {
610 ++retrace::verbosity;
616 retrace::debug = false;
617 retrace::profiling = true;
618 retrace::verbosity = -1;
620 retrace::profilingGpuTimes = true;
623 retrace::debug = false;
624 retrace::profiling = true;
625 retrace::verbosity = -1;
627 retrace::profilingCpuTimes = true;
630 retrace::debug = false;
631 retrace::profiling = true;
632 retrace::verbosity = -1;
634 retrace::profilingPixelsDrawn = true;
637 std::cerr << "error: unknown option " << opt << "\n";
644 if (retrace::profiling) {
645 retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn);
648 for (i = optind; i < argc; ++i) {
649 if (!retrace::parser.open(argv[i])) {
655 retrace::parser.close();
658 // XXX: X often hangs on XCloseDisplay
659 //retrace::cleanUp();