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) {
83 * Take/compare snapshots.
86 takeSnapshot(unsigned call_no) {
87 assert(snapshotPrefix || comparePrefix);
89 image::Image *ref = NULL;
92 os::String filename = os::String::format("%s%010u.png", comparePrefix, call_no);
93 ref = image::readPNG(filename);
97 if (retrace::verbosity >= 0) {
98 std::cout << "Read " << filename << "\n";
102 image::Image *src = getSnapshot();
107 if (snapshotPrefix) {
108 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
110 snprintf(comment, sizeof comment, "%u", call_no);
111 src->writePNM(std::cout, comment);
113 os::String filename = os::String::format("%s%010u.png", snapshotPrefix, call_no);
114 if (src->writePNG(filename) && retrace::verbosity >= 0) {
115 std::cout << "Wrote " << filename << "\n";
121 std::cout << "Snapshot " << call_no << " average precision of " << src->compare(*ref) << " bits\n";
134 * Take snapshots before/after retracing (as appropriate) and dispatch it to
135 * the respective handler.
138 retraceCall(trace::Call *call) {
139 bool swapRenderTarget = call->flags &
140 trace::CALL_FLAG_SWAP_RENDERTARGET;
141 bool doSnapshot = snapshotFrequency.contains(*call) ||
142 compareFrequency.contains(*call);
144 // For calls which cause rendertargets to be swaped, we take the
145 // snapshot _before_ swapping the rendertargets.
146 if (doSnapshot && swapRenderTarget) {
147 if (call->flags & trace::CALL_FLAG_END_FRAME) {
148 // For swapbuffers/presents we still use this
149 // call number, spite not have been executed yet.
150 takeSnapshot(call->no);
152 // Whereas for ordinate fbo/rendertarget changes we
153 // use the previous call's number.
154 takeSnapshot(call->no - 1);
159 retracer.retrace(*call);
161 if (doSnapshot && !swapRenderTarget)
162 takeSnapshot(call->no);
164 if (call->no >= dumpStateCallNo &&
165 dumpState(std::cout)) {
175 * Implement multi-threading by mimicking a relay race.
181 * Runners indexed by the leg they run (i.e, the thread_ids from the
184 std::vector<RelayRunner*> runners;
192 getRunner(unsigned leg);
203 passBaton(trace::Call *call);
214 * Each runner is a thread.
216 * The fore runner doesn't have its own thread, but instead uses the thread
217 * where the race started.
222 friend class RelayRace;
229 os::condition_variable wake_cond;
232 * There are protected by the mutex.
240 runnerThread(RelayRunner *_this);
243 RelayRunner(RelayRace *race, unsigned _leg) :
249 /* The fore runner does not need a new thread */
251 thread = os::thread(runnerThread, this);
260 os::unique_lock<os::mutex> lock(mutex);
263 while (!finished && !baton) {
264 wake_cond.wait(lock);
272 trace::Call *call = baton;
278 if (0) std::cerr << "leg " << leg << " actually finishing\n";
286 * Interpret successive calls.
289 runLeg(trace::Call *call) {
290 /* Consume successive calls for this thread. */
293 assert(call->thread_id == leg);
296 call = parser.parse_call();
297 } while (call && call->thread_id == leg);
301 assert(call->thread_id != leg);
303 race->passBaton(call);
305 /* Reached the finish line */
306 if (0) std::cerr << "finished on leg " << leg << "\n";
308 /* Notify the fore runner */
311 /* We are the fore runner */
318 * Called by other threads when relinquishing the baton.
321 receiveBaton(trace::Call *call) {
322 assert (call->thread_id == leg);
332 * Called by the fore runner when the race is over.
336 if (0) std::cerr << "notify finish to leg " << leg << "\n";
348 RelayRunner::runnerThread(RelayRunner *_this) {
354 RelayRace::RelayRace() {
355 runners.push_back(new RelayRunner(this, 0));
359 RelayRace::~RelayRace() {
360 assert(runners.size() >= 1);
361 std::vector<RelayRunner*>::const_iterator it;
362 for (it = runners.begin(); it != runners.end(); ++it) {
363 RelayRunner* runner = *it;
372 * Get (or instantiate) a runner for the specified leg.
375 RelayRace::getRunner(unsigned leg) {
378 if (leg >= runners.size()) {
379 runners.resize(leg + 1);
382 runner = runners[leg];
385 runner = new RelayRunner(this, leg);
386 runners[leg] = runner;
396 RelayRace::run(void) {
398 call = parser.parse_call();
404 RelayRunner *foreRunner = getForeRunner();
405 if (call->thread_id == 0) {
406 /* We are the forerunner thread, so no need to pass baton */
407 foreRunner->baton = call;
412 /* Start the forerunner thread */
413 foreRunner->runRace();
418 * Pass the baton (i.e., the call) to the appropriate thread.
421 RelayRace::passBaton(trace::Call *call) {
422 if (0) std::cerr << "switching to thread " << call->thread_id << "\n";
423 RelayRunner *runner = getRunner(call->thread_id);
424 runner->receiveBaton(call);
429 * Called when a runner other than the forerunner reaches the finish line.
431 * Only the fore runner can finish the race, so inform him that the race is
435 RelayRace::finishLine(void) {
436 RelayRunner *foreRunner = getForeRunner();
437 foreRunner->finishRace();
442 * Called by the fore runner after finish line to stop all other runners.
445 RelayRace::stopRunners(void) {
446 std::vector<RelayRunner*>::const_iterator it;
447 for (it = runners.begin() + 1; it != runners.end(); ++it) {
448 RelayRunner* runner = *it;
450 runner->finishRace();
458 addCallbacks(retracer);
460 long long startTime = 0;
463 startTime = os::getTime();
468 long long endTime = os::getTime();
469 float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
471 if ((retrace::verbosity >= -1) || (retrace::profiling)) {
473 "Rendered " << frameNo << " frames"
474 " in " << timeInterval << " secs,"
475 " average of " << (frameNo/timeInterval) << " fps\n";
486 } /* namespace retrace */
490 usage(const char *argv0) {
492 "Usage: " << argv0 << " [OPTION] TRACE [...]\n"
495 " -b, --benchmark benchmark mode (no error checking or warning messages)\n"
496 " --pcpu cpu profiling (cpu times per call)\n"
497 " --pgpu gpu profiling (gpu times per draw call)\n"
498 " --ppd pixels drawn profiling (pixels drawn per draw call)\n"
499 " -c, --compare=PREFIX compare against snapshots with given PREFIX\n"
500 " -C, --calls=CALLSET calls to compare (default is every frame)\n"
501 " --core use core profile\n"
502 " --db use a double buffer visual (default)\n"
503 " --sb use a single buffer visual\n"
504 " -s, --snapshot-prefix=PREFIX take snapshots; `-` for PNM stdout output\n"
505 " -S, --snapshot=CALLSET calls to snapshot (default is every frame)\n"
506 " -v, --verbose increase output verbosity\n"
507 " -D, --dump-state=CALL dump state at specific call no\n"
508 " -w, --wait waitOnFinish on final frame\n";
512 CORE_OPT = CHAR_MAX + 1,
521 shortOptions = "bc:C:D:hs:S:vw";
523 const static struct option
525 {"benchmark", no_argument, 0, 'b'},
526 {"calls", required_argument, 0, 'C'},
527 {"compare", required_argument, 0, 'c'},
528 {"core", no_argument, 0, CORE_OPT},
529 {"db", no_argument, 0, DB_OPT},
530 {"dump-state", required_argument, 0, 'D'},
531 {"help", no_argument, 0, 'h'},
532 {"pcpu", no_argument, 0, PCPU_OPT},
533 {"pgpu", no_argument, 0, PGPU_OPT},
534 {"ppd", no_argument, 0, PPD_OPT},
535 {"sb", no_argument, 0, SB_OPT},
536 {"snapshot-prefix", required_argument, 0, 's'},
537 {"snapshot", required_argument, 0, 'S'},
538 {"verbose", no_argument, 0, 'v'},
539 {"wait", no_argument, 0, 'w'},
544 int main(int argc, char **argv)
546 using namespace retrace;
549 assert(compareFrequency.empty());
550 assert(snapshotFrequency.empty());
553 while ((opt = getopt_long_only(argc, argv, shortOptions, longOptions, NULL)) != -1) {
559 retrace::debug = false;
560 retrace::verbosity = -1;
563 comparePrefix = optarg;
564 if (compareFrequency.empty()) {
565 compareFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
569 compareFrequency = trace::CallSet(optarg);
570 if (comparePrefix == NULL) {
575 dumpStateCallNo = atoi(optarg);
577 retrace::verbosity = -2;
580 retrace::coreProfile = true;
583 retrace::doubleBuffer = true;
586 retrace::doubleBuffer = false;
589 snapshotPrefix = optarg;
590 if (snapshotFrequency.empty()) {
591 snapshotFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
593 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
594 os::setBinaryMode(stdout);
595 retrace::verbosity = -2;
599 snapshotFrequency = trace::CallSet(optarg);
600 if (snapshotPrefix == NULL) {
605 ++retrace::verbosity;
611 retrace::debug = false;
612 retrace::profiling = true;
613 retrace::verbosity = -1;
615 retrace::profilingGpuTimes = true;
618 retrace::debug = false;
619 retrace::profiling = true;
620 retrace::verbosity = -1;
622 retrace::profilingCpuTimes = true;
625 retrace::debug = false;
626 retrace::profiling = true;
627 retrace::verbosity = -1;
629 retrace::profilingPixelsDrawn = true;
632 std::cerr << "error: unknown option " << opt << "\n";
639 if (retrace::profiling) {
640 retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn);
643 for (i = optind; i < argc; ++i) {
644 if (!retrace::parser.open(argv[i])) {
645 std::cerr << "error: failed to open " << argv[i] << "\n";
651 retrace::parser.close();
654 // XXX: X often hangs on XCloseDisplay
655 //retrace::cleanUp();