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 **************************************************************************/
30 #include "os_binary.hpp"
31 #include "os_time.hpp"
32 #include "os_thread.hpp"
34 #include "trace_callset.hpp"
35 #include "trace_dump.hpp"
36 #include "retrace.hpp"
39 static bool waitOnFinish = false;
41 static const char *comparePrefix = NULL;
42 static const char *snapshotPrefix = NULL;
43 static trace::CallSet snapshotFrequency;
44 static trace::CallSet compareFrequency;
46 static unsigned dumpStateCallNo = ~0;
48 retrace::Retracer retracer;
55 trace::Profiler profiler;
60 bool dumpingState = false;
62 bool doubleBuffer = true;
63 bool coreProfile = false;
65 bool profiling = false;
66 bool profilingGpuTimes = false;
67 bool profilingCpuTimes = false;
68 bool profilingPixelsDrawn = false;
75 frameComplete(trace::Call &call) {
81 * Take/compare snapshots.
84 takeSnapshot(unsigned call_no) {
85 assert(snapshotPrefix || comparePrefix);
87 image::Image *ref = NULL;
90 os::String filename = os::String::format("%s%010u.png", comparePrefix, call_no);
91 ref = image::readPNG(filename);
95 if (retrace::verbosity >= 0) {
96 std::cout << "Read " << filename << "\n";
100 image::Image *src = getSnapshot();
105 if (snapshotPrefix) {
106 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
108 snprintf(comment, sizeof comment, "%u", call_no);
109 src->writePNM(std::cout, comment);
111 os::String filename = os::String::format("%s%010u.png", snapshotPrefix, call_no);
112 if (src->writePNG(filename) && retrace::verbosity >= 0) {
113 std::cout << "Wrote " << filename << "\n";
119 std::cout << "Snapshot " << call_no << " average precision of " << src->compare(*ref) << " bits\n";
132 * Take snapshots before/after retracing (as appropriate) and dispatch it to
133 * the respective handler.
136 retraceCall(trace::Call *call) {
137 bool swapRenderTarget = call->flags &
138 trace::CALL_FLAG_SWAP_RENDERTARGET;
139 bool doSnapshot = snapshotFrequency.contains(*call) ||
140 compareFrequency.contains(*call);
142 // For calls which cause rendertargets to be swaped, we take the
143 // snapshot _before_ swapping the rendertargets.
144 if (doSnapshot && swapRenderTarget) {
145 if (call->flags & trace::CALL_FLAG_END_FRAME) {
146 // For swapbuffers/presents we still use this
147 // call number, spite not have been executed yet.
148 takeSnapshot(call->no);
150 // Whereas for ordinate fbo/rendertarget changes we
151 // use the previous call's number.
152 takeSnapshot(call->no - 1);
157 retracer.retrace(*call);
159 if (doSnapshot && !swapRenderTarget)
160 takeSnapshot(call->no);
162 if (call->no >= dumpStateCallNo &&
163 dumpState(std::cout)) {
173 * Implement multi-threading by mimicking a relay race.
179 * Runners indexed by the leg they run (i.e, the thread_ids from the
182 std::vector<RelayRunner*> runners;
190 getRunner(unsigned leg);
201 passBaton(trace::Call *call);
212 * Each runner is a thread.
214 * The fore runner doesn't have its own thread, but instead uses the thread
215 * where the race started.
220 friend class RelayRace;
227 os::condition_variable wake_cond;
230 * There are protected by the mutex.
238 runnerThread(RelayRunner *_this);
241 RelayRunner(RelayRace *race, unsigned _leg) :
247 /* The fore runner does not need a new thread */
249 thread = os::thread(runnerThread, this);
258 os::unique_lock<os::mutex> lock(mutex);
261 while (!finished && !baton) {
262 wake_cond.wait(lock);
270 trace::Call *call = baton;
276 if (0) std::cerr << "leg " << leg << " actually finishing\n";
284 * Interpret successive calls.
287 runLeg(trace::Call *call) {
288 /* Consume successive calls for this thread. */
291 assert(call->thread_id == leg);
294 call = parser.parse_call();
295 } while (call && call->thread_id == leg);
299 assert(call->thread_id != leg);
301 race->passBaton(call);
303 /* Reached the finish line */
304 if (0) std::cerr << "finished on leg " << leg << "\n";
306 /* Notify the fore runner */
309 /* We are the fore runner */
316 * Called by other threads when relinquishing the baton.
319 receiveBaton(trace::Call *call) {
320 assert (call->thread_id == leg);
330 * Called by the fore runner when the race is over.
334 if (0) std::cerr << "notify finish to leg " << leg << "\n";
346 RelayRunner::runnerThread(RelayRunner *_this) {
352 RelayRace::RelayRace() {
353 runners.push_back(new RelayRunner(this, 0));
357 RelayRace::~RelayRace() {
358 assert(runners.size() >= 1);
359 std::vector<RelayRunner*>::const_iterator it;
360 for (it = runners.begin(); it != runners.end(); ++it) {
361 RelayRunner* runner = *it;
370 * Get (or instantiate) a runner for the specified leg.
373 RelayRace::getRunner(unsigned leg) {
376 if (leg >= runners.size()) {
377 runners.resize(leg + 1);
380 runner = runners[leg];
383 runner = new RelayRunner(this, leg);
384 runners[leg] = runner;
394 RelayRace::run(void) {
396 call = parser.parse_call();
402 RelayRunner *foreRunner = getForeRunner();
403 if (call->thread_id == 0) {
404 /* We are the forerunner thread, so no need to pass baton */
405 foreRunner->baton = call;
410 /* Start the forerunner thread */
411 foreRunner->runRace();
416 * Pass the baton (i.e., the call) to the appropriate thread.
419 RelayRace::passBaton(trace::Call *call) {
420 if (0) std::cerr << "switching to thread " << call->thread_id << "\n";
421 RelayRunner *runner = getRunner(call->thread_id);
422 runner->receiveBaton(call);
427 * Called when a runner other than the forerunner reaches the finish line.
429 * Only the fore runner can finish the race, so inform him that the race is
433 RelayRace::finishLine(void) {
434 RelayRunner *foreRunner = getForeRunner();
435 foreRunner->finishRace();
440 * Called by the fore runner after finish line to stop all other runners.
443 RelayRace::stopRunners(void) {
444 std::vector<RelayRunner*>::const_iterator it;
445 for (it = runners.begin() + 1; it != runners.end(); ++it) {
446 RelayRunner* runner = *it;
448 runner->finishRace();
456 addCallbacks(retracer);
458 long long startTime = 0;
461 startTime = os::getTime();
466 long long endTime = os::getTime();
467 float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
469 if ((retrace::verbosity >= -1) || (retrace::profiling)) {
471 "Rendered " << frameNo << " frames"
472 " in " << timeInterval << " secs,"
473 " average of " << (frameNo/timeInterval) << " fps\n";
484 } /* namespace retrace */
488 usage(const char *argv0) {
490 "Usage: " << argv0 << " [OPTION] TRACE [...]\n"
493 " -b benchmark mode (no error checking or warning messages)\n"
494 " -pcpu cpu profiling (cpu times per call)\n"
495 " -pgpu gpu profiling (gpu times per draw call)\n"
496 " -ppd pixels drawn profiling (pixels drawn per draw call)\n"
497 " -c PREFIX compare against snapshots\n"
498 " -C CALLSET calls to compare (default is every frame)\n"
499 " -core use core profile\n"
500 " -db use a double buffer visual (default)\n"
501 " -sb use a single buffer visual\n"
502 " -s PREFIX take snapshots; `-` for PNM stdout output\n"
503 " -S CALLSET calls to snapshot (default is every frame)\n"
504 " -v increase output verbosity\n"
505 " -D CALLNO dump state at specific call no\n"
506 " -w waitOnFinish on final frame\n";
511 int main(int argc, char **argv)
513 using namespace retrace;
515 assert(compareFrequency.empty());
516 assert(snapshotFrequency.empty());
519 for (i = 1; i < argc; ++i) {
520 const char *arg = argv[i];
526 if (!strcmp(arg, "--")) {
528 } else if (!strcmp(arg, "-b")) {
529 retrace::debug = false;
530 retrace::verbosity = -1;
531 } else if (!strcmp(arg, "-c")) {
532 comparePrefix = argv[++i];
533 if (compareFrequency.empty()) {
534 compareFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
536 } else if (!strcmp(arg, "-C")) {
537 compareFrequency = trace::CallSet(argv[++i]);
538 if (comparePrefix == NULL) {
541 } else if (!strcmp(arg, "-D")) {
542 dumpStateCallNo = atoi(argv[++i]);
544 retrace::verbosity = -2;
545 } else if (!strcmp(arg, "-core")) {
546 retrace::coreProfile = true;
547 } else if (!strcmp(arg, "-db")) {
548 retrace::doubleBuffer = true;
549 } else if (!strcmp(arg, "-sb")) {
550 retrace::doubleBuffer = false;
551 } else if (!strcmp(arg, "--help")) {
554 } else if (!strcmp(arg, "-s")) {
555 snapshotPrefix = argv[++i];
556 if (snapshotFrequency.empty()) {
557 snapshotFrequency = trace::CallSet(trace::FREQUENCY_FRAME);
559 if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) {
560 os::setBinaryMode(stdout);
561 retrace::verbosity = -2;
563 } else if (!strcmp(arg, "-S")) {
564 snapshotFrequency = trace::CallSet(argv[++i]);
565 if (snapshotPrefix == NULL) {
568 } else if (!strcmp(arg, "-v")) {
569 ++retrace::verbosity;
570 } else if (!strcmp(arg, "-w")) {
572 } else if (arg[1] == 'p') {
573 retrace::debug = false;
574 retrace::profiling = true;
575 retrace::verbosity = -1;
577 if (!strcmp(arg, "-pcpu")) {
578 retrace::profilingCpuTimes = true;
579 } else if (!strcmp(arg, "-pgpu")) {
580 retrace::profilingGpuTimes = true;
581 } else if (!strcmp(arg, "-ppd")) {
582 retrace::profilingPixelsDrawn = true;
585 std::cerr << "error: unknown option " << arg << "\n";
592 if (retrace::profiling) {
593 retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn);
596 for ( ; i < argc; ++i) {
597 if (!retrace::parser.open(argv[i])) {
598 std::cerr << "error: failed to open " << argv[i] << "\n";
604 retrace::parser.close();
607 // XXX: X often hangs on XCloseDisplay
608 //retrace::cleanUp();