]> git.cworth.org Git - apitrace/blobdiff - retrace/retrace_main.cpp
Cleanup and comment the code.
[apitrace] / retrace / retrace_main.cpp
index f91928beb2ee7244e5fa7c3447748dd41e8ef55e..7e171e67bf97f504744ab2268f94d84362a6f7a7 100644 (file)
 
 #include "os_binary.hpp"
 #include "os_time.hpp"
+#include "os_thread.hpp"
 #include "image.hpp"
 #include "trace_callset.hpp"
 #include "trace_dump.hpp"
 #include "retrace.hpp"
 
 
-static bool wait = false;
+static bool waitOnFinish = false;
 
 static const char *comparePrefix = NULL;
 static const char *snapshotPrefix = NULL;
@@ -44,23 +45,30 @@ static trace::CallSet compareFrequency;
 
 static unsigned dumpStateCallNo = ~0;
 
+retrace::Retracer retracer;
+
 
 namespace retrace {
 
 
 trace::Parser parser;
+trace::Profiler profiler;
 
 
 int verbosity = 0;
 bool debug = true;
-bool profiling = false;
-
+bool dumpingState = false;
 
 bool doubleBuffer = true;
 bool coreProfile = false;
 
+bool profiling = false;
+bool profilingGpuTimes = false;
+bool profilingCpuTimes = false;
+bool profilingPixelsDrawn = false;
 
-static unsigned frameNo = 0;
+unsigned frameNo = 0;
+unsigned callNo = 0;
 
 
 void
@@ -69,6 +77,9 @@ frameComplete(trace::Call &call) {
 }
 
 
+/**
+ * Take/compare snapshots.
+ */
 static void
 takeSnapshot(unsigned call_no) {
     assert(snapshotPrefix || comparePrefix);
@@ -115,54 +126,342 @@ takeSnapshot(unsigned call_no) {
 }
 
 
+/**
+ * Retrace one call.
+ *
+ * Take snapshots before/after retracing (as appropriate) and dispatch it to
+ * the respective handler.
+ */
 static void
-mainLoop() {
-    retrace::Retracer retracer;
+retraceCall(trace::Call *call) {
+    bool swapRenderTarget = call->flags &
+        trace::CALL_FLAG_SWAP_RENDERTARGET;
+    bool doSnapshot = snapshotFrequency.contains(*call) ||
+        compareFrequency.contains(*call);
+
+    // For calls which cause rendertargets to be swaped, we take the
+    // snapshot _before_ swapping the rendertargets.
+    if (doSnapshot && swapRenderTarget) {
+        if (call->flags & trace::CALL_FLAG_END_FRAME) {
+            // For swapbuffers/presents we still use this
+            // call number, spite not have been executed yet.
+            takeSnapshot(call->no);
+        } else {
+            // Whereas for ordinate fbo/rendertarget changes we
+            // use the previous call's number.
+            takeSnapshot(call->no - 1);
+        }
+    }
 
-    addCallbacks(retracer);
+    callNo = call->no;
+    retracer.retrace(*call);
 
-    long long startTime = 0; 
+    if (doSnapshot && !swapRenderTarget)
+        takeSnapshot(call->no);
 
-    startTime = os::getTime();
-    trace::Call *call;
+    if (call->no >= dumpStateCallNo &&
+        dumpState(std::cout)) {
+        exit(0);
+    }
+}
 
-    while ((call = retrace::parser.parse_call())) {
-        bool swapRenderTarget = call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET;
-        bool doSnapshot =
-            snapshotFrequency.contains(*call) ||
-            compareFrequency.contains(*call)
-        ;
-
-        // For calls which cause rendertargets to be swaped, we take the
-        // snapshot _before_ swapping the rendertargets.
-        if (doSnapshot && swapRenderTarget) {
-            if (call->flags & trace::CALL_FLAG_END_FRAME) {
-                // For swapbuffers/presents we still use this call number,
-                // spite not have been executed yet.
-                takeSnapshot(call->no);
-            } else {
-                // Whereas for ordinate fbo/rendertarget changes we use the
-                // previous call's number.
-                takeSnapshot(call->no - 1);
+
+class RelayRunner;
+
+
+/**
+ * Implement multi-threading by mimicking a relay race.
+ */
+class RelayRace
+{
+private:
+    /**
+     * Runners indexed by the leg they run (i.e, the thread_ids from the
+     * trace).
+     */
+    std::vector<RelayRunner*> runners;
+
+public:
+    RelayRace();
+
+    ~RelayRace();
+
+    RelayRunner *
+    getRunner(unsigned leg);
+
+    inline RelayRunner *
+    getForeRunner() {
+        return getRunner(0);
+    }
+
+    void
+    run(void);
+
+    void
+    passBaton(trace::Call *call);
+
+    void
+    finishLine();
+
+    void
+    stopRunners();
+};
+
+
+/**
+ * Each runner is a thread.
+ *
+ * The fore runner doesn't have its own thread, but instead uses the thread
+ * where the race started.
+ */
+class RelayRunner
+{
+private:
+    friend class RelayRace;
+
+    RelayRace *race;
+
+    unsigned leg;
+    
+    os::mutex mutex;
+    os::condition_variable wake_cond;
+
+    /**
+     * There are protected by the mutex.
+     */
+    bool finished;
+    trace::Call *baton;
+
+    os::thread thread;
+
+    static void *
+    runnerThread(RelayRunner *_this);
+
+public:
+    RelayRunner(RelayRace *race, unsigned _leg) :
+        race(race),
+        leg(_leg),
+        finished(false),
+        baton(0)
+    {
+        /* The fore runner does not need a new thread */
+        if (leg) {
+            thread = os::thread(runnerThread, this);
+        }
+    }
+
+    /**
+     * Thread main loop.
+     */
+    void
+    runRace(void) {
+        os::unique_lock<os::mutex> lock(mutex);
+
+        while (1) {
+            while (!finished && !baton) {
+                wake_cond.wait(lock);
             }
+
+            if (finished) {
+                break;
+            }
+
+            assert(baton);
+            trace::Call *call = baton;
+            baton = 0;
+
+            runLeg(call);
         }
 
-        retracer.retrace(*call);
+        if (0) std::cerr << "leg " << leg << " actually finishing\n";
 
-        if (doSnapshot && !swapRenderTarget) {
-            takeSnapshot(call->no);
+        if (leg == 0) {
+            race->stopRunners();
         }
+    }
 
-        if (call->no >= dumpStateCallNo &&
-            dumpState(std::cout)) {
-            exit(0);
+    /**
+     * Interpret successive calls.
+     */
+    void
+    runLeg(trace::Call *call) {
+        /* Consume successive calls for this thread. */
+        do {
+            assert(call);
+            assert(call->thread_id == leg);
+            retraceCall(call);
+            delete call;
+            call = parser.parse_call();
+        } while (call && call->thread_id == leg);
+
+        if (call) {
+            /* Pass the baton */
+            assert(call->thread_id != leg);
+            flushRendering();
+            race->passBaton(call);
+        } else {
+            /* Reached the finish line */
+            if (0) std::cerr << "finished on leg " << leg << "\n";
+            if (leg) {
+                /* Notify the fore runner */
+                race->finishLine();
+            } else {
+                /* We are the fore runner */
+                finished = true;
+            }
         }
+    }
+
+    /**
+     * Called by other threads when relinquishing the baton.
+     */
+    void
+    receiveBaton(trace::Call *call) {
+        assert (call->thread_id == leg);
 
-        delete call;
+        mutex.lock();
+        baton = call;
+        mutex.unlock();
+
+        wake_cond.signal();
     }
 
-    // Reached the end of trace
-    flushRendering();
+    /**
+     * Called by the fore runner when the race is over.
+     */
+    void
+    finishRace() {
+        if (0) std::cerr << "notify finish to leg " << leg << "\n";
+
+        mutex.lock();
+        finished = true;
+        mutex.unlock();
+
+        wake_cond.signal();
+    }
+};
+
+
+void *
+RelayRunner::runnerThread(RelayRunner *_this) {
+    _this->runRace();
+    return 0;
+}
+
+
+RelayRace::RelayRace() {
+    runners.push_back(new RelayRunner(this, 0));
+}
+
+
+RelayRace::~RelayRace() {
+    assert(runners.size() >= 1);
+    std::vector<RelayRunner*>::const_iterator it;
+    for (it = runners.begin(); it != runners.end(); ++it) {
+        RelayRunner* runner = *it;
+        if (runner) {
+            delete runner;
+        }
+    }
+}
+
+
+/**
+ * Get (or instantiate) a runner for the specified leg.
+ */
+RelayRunner *
+RelayRace::getRunner(unsigned leg) {
+    RelayRunner *runner;
+
+    if (leg >= runners.size()) {
+        runners.resize(leg + 1);
+        runner = 0;
+    } else {
+        runner = runners[leg];
+    }
+    if (!runner) {
+        runner = new RelayRunner(this, leg);
+        runners[leg] = runner;
+    }
+    return runner;
+}
+
+
+/**
+ * Start the race.
+ */
+void
+RelayRace::run(void) {
+    trace::Call *call;
+    call = parser.parse_call();
+    if (!call) {
+        /* Nothing to do */
+        return;
+    }
+
+    RelayRunner *foreRunner = getForeRunner();
+    if (call->thread_id == 0) {
+        /* We are the forerunner thread, so no need to pass baton */
+        foreRunner->baton = call;
+    } else {
+        passBaton(call);
+    }
+
+    /* Start the forerunner thread */
+    foreRunner->runRace();
+}
+
+
+/**
+ * Pass the baton (i.e., the call) to the appropriate thread.
+ */
+void
+RelayRace::passBaton(trace::Call *call) {
+    if (0) std::cerr << "switching to thread " << call->thread_id << "\n";
+    RelayRunner *runner = getRunner(call->thread_id);
+    runner->receiveBaton(call);
+}
+
+
+/**
+ * Called when a runner other than the forerunner reaches the finish line.
+ *
+ * Only the fore runner can finish the race, so inform him that the race is
+ * finished.
+ */
+void
+RelayRace::finishLine(void) {
+    RelayRunner *foreRunner = getForeRunner();
+    foreRunner->finishRace();
+}
+
+
+/**
+ * Called by the fore runner after finish line to stop all other runners.
+ */
+void
+RelayRace::stopRunners(void) {
+    std::vector<RelayRunner*>::const_iterator it;
+    for (it = runners.begin() + 1; it != runners.end(); ++it) {
+        RelayRunner* runner = *it;
+        if (runner) {
+            runner->finishRace();
+        }
+    }
+}
+
+
+static void
+mainLoop() {
+    addCallbacks(retracer);
+
+    long long startTime = 0; 
+    frameNo = 0;
+
+    startTime = os::getTime();
+
+    RelayRace race;
+    race.run();
 
     long long endTime = os::getTime();
     float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
@@ -174,10 +473,10 @@ mainLoop() {
             " average of " << (frameNo/timeInterval) << " fps\n";
     }
 
-    if (wait) {
+    if (waitOnFinish) {
         waitForInput();
     } else {
-        exit(0);
+        return;
     }
 }
 
@@ -188,11 +487,13 @@ mainLoop() {
 static void
 usage(const char *argv0) {
     std::cout << 
-        "Usage: " << argv0 << " [OPTION] TRACE\n"
+        "Usage: " << argv0 << " [OPTION] TRACE [...]\n"
         "Replay TRACE.\n"
         "\n"
         "  -b           benchmark mode (no error checking or warning messages)\n"
-        "  -p           profiling mode (run whole trace, dump profiling info)\n"
+        "  -pcpu        cpu profiling (cpu times per call)\n"
+        "  -pgpu        gpu profiling (gpu times per draw call)\n"
+        "  -ppd         pixels drawn profiling (pixels drawn per draw call)\n"
         "  -c PREFIX    compare against snapshots\n"
         "  -C CALLSET   calls to compare (default is every frame)\n"
         "  -core        use core profile\n"
@@ -202,13 +503,15 @@ usage(const char *argv0) {
         "  -S CALLSET   calls to snapshot (default is every frame)\n"
         "  -v           increase output verbosity\n"
         "  -D CALLNO    dump state at specific call no\n"
-        "  -w           wait on final frame\n";
+        "  -w           waitOnFinish on final frame\n";
 }
 
 
 extern "C"
 int main(int argc, char **argv)
 {
+    using namespace retrace;
+
     assert(compareFrequency.empty());
     assert(snapshotFrequency.empty());
 
@@ -225,10 +528,6 @@ int main(int argc, char **argv)
         } else if (!strcmp(arg, "-b")) {
             retrace::debug = false;
             retrace::verbosity = -1;
-        } else if (!strcmp(arg, "-p")) {
-            retrace::debug = false;
-            retrace::profiling = true;
-            retrace::verbosity = -1;
         } else if (!strcmp(arg, "-c")) {
             comparePrefix = argv[++i];
             if (compareFrequency.empty()) {
@@ -241,6 +540,7 @@ int main(int argc, char **argv)
             }
         } else if (!strcmp(arg, "-D")) {
             dumpStateCallNo = atoi(argv[++i]);
+            dumpingState = true;
             retrace::verbosity = -2;
         } else if (!strcmp(arg, "-core")) {
             retrace::coreProfile = true;
@@ -268,7 +568,19 @@ int main(int argc, char **argv)
         } else if (!strcmp(arg, "-v")) {
             ++retrace::verbosity;
         } else if (!strcmp(arg, "-w")) {
-            wait = true;
+            waitOnFinish = true;
+        } else if (arg[1] == 'p') {
+            retrace::debug = false;
+            retrace::profiling = true;
+            retrace::verbosity = -1;
+
+            if (!strcmp(arg, "-pcpu")) {
+                retrace::profilingCpuTimes = true;
+            } else if (!strcmp(arg, "-pgpu")) {
+                retrace::profilingGpuTimes = true;
+            } else if (!strcmp(arg, "-ppd")) {
+                retrace::profilingPixelsDrawn = true;
+            }
         } else {
             std::cerr << "error: unknown option " << arg << "\n";
             usage(argv[0]);
@@ -277,6 +589,9 @@ int main(int argc, char **argv)
     }
 
     retrace::setUp();
+    if (retrace::profiling) {
+        retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn);
+    }
 
     for ( ; i < argc; ++i) {
         if (!retrace::parser.open(argv[i])) {
@@ -289,7 +604,8 @@ int main(int argc, char **argv)
         retrace::parser.close();
     }
 
-    retrace::cleanUp();
+    // XXX: X often hangs on XCloseDisplay
+    //retrace::cleanUp();
 
     return 0;
 }