#include <string.h>
#include <iostream>
+#include "glws.hpp"
#include "os_binary.hpp"
#include "os_time.hpp"
+#include "os_workqueue.hpp"
#include "image.hpp"
#include "trace_callset.hpp"
#include "trace_dump.hpp"
static bool waitOnFinish = false;
+static bool use_threads;
static const char *comparePrefix = NULL;
static const char *snapshotPrefix = NULL;
static unsigned dumpStateCallNo = ~0;
+retrace::Retracer retracer;
+
namespace retrace {
trace::Parser parser;
+trace::Profiler profiler;
+static std::map<unsigned long, os::WorkQueue *> thread_wq_map;
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;
+static bool state_dumped;
+class RenderWork : public os::WorkQueueWork
+{
+ trace::Call *call;
+public:
+ void run(void);
+ RenderWork(trace::Call *_call) { call = _call; }
+ ~RenderWork(void) { delete call; }
+};
+
+class FlushGLWork : public os::WorkQueueWork
+{
+public:
+ void run(void) { flushRendering(); }
+};
void
frameComplete(trace::Call &call) {
return;
}
+void RenderWork::run(void)
+{
+ bool swapRenderTarget = call->flags &
+ trace::CALL_FLAG_SWAP_RENDERTARGET;
+ bool doSnapshot = snapshotFrequency.contains(*call) ||
+ compareFrequency.contains(*call);
-static void
-mainLoop() {
- retrace::Retracer retracer;
+ if (state_dumped)
+ return;
- addCallbacks(retracer);
+ // 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);
+ }
+ }
- long long startTime = 0;
- frameNo = 0;
+ callNo = call->no;
+ retracer.retrace(*call);
- startTime = os::getTime();
- trace::Call *call;
+ if (doSnapshot && !swapRenderTarget)
+ takeSnapshot(call->no);
- 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);
- }
- }
+ if (call->no >= dumpStateCallNo && dumpState(std::cout))
+ state_dumped = true;
+}
- retracer.retrace(*call);
+static os::WorkQueue *get_work_queue(unsigned long thread_id)
+{
+ os::WorkQueue *thread;
+ std::map<unsigned long, os::WorkQueue *>::iterator it;
- if (doSnapshot && !swapRenderTarget) {
- takeSnapshot(call->no);
- }
+ it = thread_wq_map.find(thread_id);
+ if (it == thread_wq_map.end()) {
+ thread = new os::WorkQueue();
+ thread_wq_map[thread_id] = thread;
+ } else {
+ thread = it->second;
+ }
+
+ return thread;
+}
+
+static void exit_work_queues(void)
+{
+ std::map<unsigned long, os::WorkQueue *>::iterator it;
+
+ it = thread_wq_map.begin();
+ while (it != thread_wq_map.end()) {
+ os::WorkQueue *thread_wq = it->second;
- if (call->no >= dumpStateCallNo &&
- dumpState(std::cout)) {
- exit(0);
+ thread_wq->queue_work(new FlushGLWork);
+ thread_wq->flush();
+ thread_wq->destroy();
+ thread_wq_map.erase(it++);
+ }
+}
+
+static void do_all_calls(void)
+{
+ trace::Call *call;
+ int prev_thread_id = -1;
+ os::WorkQueue *thread_wq = NULL;
+
+ while ((call = parser.parse_call())) {
+ RenderWork *render_work = new RenderWork(call);
+
+ if (use_threads) {
+ if (prev_thread_id != call->thread_id) {
+ if (thread_wq)
+ thread_wq->flush();
+ thread_wq = get_work_queue(call->thread_id);
+ prev_thread_id = call->thread_id;
+ }
+
+ thread_wq->queue_work(render_work);
+ } else {
+ render_work->run();
+ delete render_work;
}
- delete call;
+ if (state_dumped)
+ break;
}
- // Reached the end of trace
- flushRendering();
+ exit_work_queues();
+}
+
+
+static void
+mainLoop() {
+ addCallbacks(retracer);
+
+ long long startTime = 0;
+ frameNo = 0;
+
+ startTime = os::getTime();
+
+ do_all_calls();
+
+ if (!use_threads)
+ /*
+ * Reached the end of trace; if using threads we do the flush
+ * when exiting the threads.
+ */
+ flushRendering();
long long endTime = os::getTime();
float timeInterval = (endTime - startTime) * (1.0 / os::timeFrequency);
"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"
" -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 waitOnFinish on final frame\n";
+ " -w waitOnFinish on final frame\n"
+ " -t enable threading\n";
}
extern "C"
int main(int argc, char **argv)
{
+ using namespace retrace;
+
assert(compareFrequency.empty());
assert(snapshotFrequency.empty());
} 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()) {
}
} else if (!strcmp(arg, "-D")) {
dumpStateCallNo = atoi(argv[++i]);
+ dumpingState = true;
retrace::verbosity = -2;
} else if (!strcmp(arg, "-core")) {
retrace::coreProfile = true;
++retrace::verbosity;
} else if (!strcmp(arg, "-w")) {
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 if (!strcmp(arg, "-t")) {
+ use_threads = true;
} else {
std::cerr << "error: unknown option " << arg << "\n";
usage(argv[0]);
}
retrace::setUp();
+ if (retrace::profiling) {
+ retrace::profiler.setup(retrace::profilingCpuTimes, retrace::profilingGpuTimes, retrace::profilingPixelsDrawn);
+ }
for ( ; i < argc; ++i) {
if (!retrace::parser.open(argv[i])) {
retrace::parser.close();
}
- retrace::cleanUp();
+ // XXX: X often hangs on XCloseDisplay
+ //retrace::cleanUp();
return 0;
}