cmake_minimum_required (VERSION 2.8)
+
+# Use clang on MacOSX. gcc doesn't support __thread key, and Apple has
+# abandoned it for clang. This must be done before the project is defined.
+if (APPLE)
+ set (CMAKE_C_COMPILER "clang")
+ set (CMAKE_CXX_COMPILER "clang++")
+endif ()
+
+
project (apitrace)
##############################################################################
# Find dependencies
+# Ensure __thread is support
+if (NOT MSVC)
+ include (CheckCXXSourceCompiles)
+ check_cxx_source_compiles("__thread int i; int main() { return 0; }" HAVE_COMPILER_TLS)
+ if (NOT HAVE_COMPILER_TLS)
+ if (APPLE)
+ message (FATAL_ERROR "C++ compiler does not support __thread keyword. Please install XCode 4.5 or higher.")
+ else (MINGW32)
+ message (FATAL_ERROR "C++ compiler does not support __thread keyword. Please use MinGW g++ version 4.4 or higher")
+ else ()
+ message (FATAL_ERROR "C++ compiler does not support __thread keyword.")
+ endif ()
+ endif ()
+endif ()
+
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
set (CMAKE_USE_PYTHON_VERSION 2.7 2.6)
common/image_pnm.cpp
common/image_png.cpp
common/${os}
+ common/workqueue.cpp
)
set_target_properties (common PROPERTIES
/**************************************************************************
*
- * Copyright 2011 Jose Fonseca
+ * Copyright 2011-2012 Jose Fonseca
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
#include <pthread.h>
#endif
+
+/**
+ * Compiler TLS.
+ *
+ * See also:
+ * - http://gcc.gnu.org/onlinedocs/gcc-4.6.3/gcc/Thread_002dLocal.html
+ * - http://msdn.microsoft.com/en-us/library/9w1sdazb.aspx
+ */
+#if defined(_MSC_VER)
+# define thread_specific __declspec(thread)
+#elif defined(__GNUC__)
+# define thread_specific __thread
+#else
+# define thread_specific
+# error "Unsupported compiler"
+#endif
+
+
namespace os {
- class recursive_mutex
+ /**
+ * Base class for mutex and recursive_mutex.
+ */
+ class _base_mutex
{
public:
#ifdef _WIN32
typedef pthread_mutex_t native_handle_type;
#endif
- recursive_mutex(void) {
+ _base_mutex(void) {
#ifdef _WIN32
InitializeCriticalSection(&_native_handle);
#else
#endif
}
- ~recursive_mutex() {
+ ~_base_mutex() {
#ifdef _WIN32
DeleteCriticalSection(&_native_handle);
#else
#endif
}
- private:
+ native_handle_type & native_handle() {
+ return _native_handle;
+ }
+
+ protected:
native_handle_type _native_handle;
};
+ /**
+ * Same interface as std::mutex.
+ */
+ class mutex : public _base_mutex
+ {
+ public:
+ inline
+ mutex(void) {
+#ifdef _WIN32
+ InitializeCriticalSection(&_native_handle);
+#else
+ pthread_mutex_init(&_native_handle, NULL);
+#endif
+ }
+ };
+
+
+ /**
+ * Same interface as std::recursive_mutex.
+ */
+ class recursive_mutex : public _base_mutex
+ {
+ public:
+ inline
+ recursive_mutex(void) {
+#ifdef _WIN32
+ InitializeCriticalSection(&_native_handle);
+#else
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&_native_handle, &attr);
+ pthread_mutexattr_destroy(&attr);
+#endif
+ }
+ };
+
+
+ /**
+ * Same interface as std::unique_lock;
+ */
+ template< class Mutex >
+ class unique_lock
+ {
+ public:
+ typedef Mutex mutex_type;
+
+ inline explicit
+ unique_lock(mutex_type & mutex) :
+ _mutex(&mutex)
+ {
+ _mutex->lock();
+ }
+
+ inline
+ ~unique_lock() {
+ _mutex->unlock();
+ }
+
+ inline void
+ lock() {
+ _mutex->lock();
+ }
+
+ inline void
+ unlock() {
+ _mutex->unlock();
+ }
+
+ mutex_type *
+ mutex() const {
+ return _mutex;
+ }
+
+ protected:
+ mutex_type *_mutex;
+ };
+
+
+ /**
+ * Same interface as std::condition_variable
+ */
+ class condition_variable
+ {
+ public:
+#ifdef _WIN32
+ typedef CONDITION_VARIABLE native_handle_type;
+#else
+ typedef pthread_cond_t native_handle_type;
+#endif
+
+ condition_variable() {
+#ifdef _WIN32
+ InitializeConditionVariable(&_native_handle);
+#else
+ pthread_cond_init(&_native_handle, NULL);
+#endif
+ }
+
+ ~condition_variable() {
+#ifdef _WIN32
+ /* No-op */
+#else
+ pthread_cond_destroy(&_native_handle);
+#endif
+ }
+
+ inline void
+ signal(void) {
+#ifdef _WIN32
+ WakeConditionVariable(&_native_handle);
+#else
+ pthread_cond_signal(&_native_handle);
+#endif
+ }
+
+ inline void
+ wait(unique_lock<mutex> & lock) {
+ mutex::native_handle_type & mutex_native_handle = lock.mutex()->native_handle();
+#ifdef _WIN32
+ /* FIXME */
+ SleepConditionVariableCS(&_native_handle, &mutex_native_handle, INFINITE);
+#else
+ pthread_cond_wait(&_native_handle, &mutex_native_handle);
+#endif
+ }
+
+ protected:
+ native_handle_type _native_handle;
+ };
+
+
+ /**
+ * Same interface as boost::thread_specific_ptr.
+ */
template <typename T>
class thread_specific_ptr
{
void reset(T* new_value=0) {
T * old_value = get();
+ set(new_value);
+ if (old_value) {
+ delete old_value;
+ }
+ }
+
+ T* release (void) {
+ T * old_value = get();
+ set(0);
+ return old_value;
+ }
+
+private:
+ void set(T* new_value) {
#ifdef _WIN32
TlsSetValue(dwTlsIndex, new_value);
#else
pthread_setspecific(key, new_value);
#endif
- if (old_value) {
- delete old_value;
- }
}
};
+
+ /**
+ * Same interface as std::thread
+ */
+ class thread {
+ public:
+#ifdef _WIN32
+ typedef HANDLE native_handle_type;
+#else
+ typedef pthread_t native_handle_type;
+#endif
+
+ template< class Function, class Arg >
+ explicit thread( Function& f, Arg arg ) {
+#ifdef _WIN32
+ /* FIXME */
+ DWORD id = 0;
+ _native_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)f, (LPVOID)arg, 0, &id);
+#else
+ pthread_create(&_native_handle, NULL, f, arg);
+#endif
+ }
+
+ inline void
+ join() {
+#ifdef _WIN32
+ WaitForSingleObject(_native_handle, INFINITE);
+#else
+ pthread_join(_native_handle, NULL);
+#endif
+ }
+
+ private:
+ native_handle_type _native_handle;
+
+#if 0
+#ifdef _WIN32
+ template< class Function, class Arg >
+ static DWORD WINAPI
+ ThreadProc(LPVOID lpParameter) {
+
+ );
+#endif
+#endif
+ };
+
} /* namespace os */
#endif /* _OS_THREAD_HPP_ */
--- /dev/null
+#ifndef _OS_WORKQUEUE_HPP_
+#define _OS_WORKQUEUE_HPP_
+
+#include <queue>
+
+#include "os_thread.hpp"
+
+namespace os {
+
+
+class WorkQueue;
+
+class WorkQueueWork {
+protected:
+ friend class WorkQueue;
+
+public:
+ virtual void run(void) = 0;
+ virtual ~WorkQueueWork(void) { }
+};
+
+class WorkQueue {
+ std::queue<WorkQueueWork *> work_queue;
+
+ bool busy;
+ bool exit_workqueue;
+ os::condition_variable wake_cond;
+ os::condition_variable complete_cond;
+
+ os::mutex mutex;
+
+ os::thread thread;
+
+ void wake_up_thread(void);
+ int run_tasks(void);
+public:
+ void thread_entry(void);
+ void queue_work(WorkQueueWork *work);
+ void flush(void);
+ void destroy(void);
+
+ WorkQueue(void);
+ ~WorkQueue();
+};
+
+}
+
+#endif
#endif
}
-static unsigned next_thread_id = 0;
-static os::thread_specific_ptr<unsigned> thread_id_specific_ptr;
+static unsigned next_thread_num = 1;
+static thread_specific unsigned thread_num = 0;
unsigned LocalWriter::beginEnter(const FunctionSig *sig) {
mutex.lock();
open();
}
- unsigned *thread_id_ptr = thread_id_specific_ptr.get();
- unsigned thread_id;
- if (thread_id_ptr) {
- thread_id = *thread_id_ptr;
- } else {
- thread_id = next_thread_id++;
- thread_id_ptr = new unsigned;
- *thread_id_ptr = thread_id;
- thread_id_specific_ptr.reset(thread_id_ptr);
+ unsigned this_thread_num = thread_num;
+ if (!this_thread_num) {
+ this_thread_num = thread_num = next_thread_num++;
}
+ assert(thread_num > 0);
+ unsigned thread_id = thread_num - 1;
return Writer::beginEnter(sig, thread_id);
}
--- /dev/null
+#include <queue>
+#include <assert.h>
+
+#include "os_workqueue.hpp"
+
+namespace os
+{
+
+/**
+ * return 0 on batch complete, -1 on thread exit request.
+ */
+int WorkQueue::run_tasks(void)
+{
+ os::unique_lock<os::mutex> lock(mutex);
+
+ while (work_queue.empty() && !exit_workqueue) {
+ wake_cond.wait(lock);
+ }
+
+ if (exit_workqueue) {
+ return -1;
+ }
+
+ std::queue<WorkQueueWork *> batch;
+ std::swap(work_queue, batch);
+ busy = true;
+
+ lock.unlock();
+
+ assert(!batch.empty());
+ while (!batch.empty()) {
+ WorkQueueWork *task;
+
+ task = batch.front();
+ task->run();
+ batch.pop();
+ delete task;
+ }
+
+ lock.lock();
+
+ busy = false;
+ complete_cond.signal();
+
+ return 0;
+}
+
+/* Must be called with WorkQueue::lock held */
+void WorkQueue::wake_up_thread(void)
+{
+ wake_cond.signal();
+}
+
+void WorkQueue::queue_work(WorkQueueWork *task)
+{
+ mutex.lock();
+ work_queue.push(task);
+ wake_up_thread();
+ mutex.unlock();
+}
+
+void WorkQueue::flush(void)
+{
+ os::unique_lock<os::mutex> lock(mutex);
+ while (!work_queue.empty() || busy) {
+ complete_cond.wait(lock);
+ }
+}
+
+void WorkQueue::thread_entry(void)
+{
+ int err;
+
+ do {
+ err = run_tasks();
+ } while (!err);
+}
+
+void WorkQueue::destroy(void)
+{
+ mutex.lock();
+ exit_workqueue = true;
+ wake_up_thread();
+ mutex.unlock();
+}
+
+static
+#ifdef _WIN32
+DWORD WINAPI
+#else
+void *
+#endif
+WorkQueue__entry_thunk(void *data)
+{
+ WorkQueue *thread = static_cast<WorkQueue *>(data);
+
+ thread->thread_entry();
+
+#ifdef _WIN32
+ return 0;
+#else
+ return NULL;
+#endif
+}
+
+WorkQueue::WorkQueue(void) :
+ busy(false),
+ exit_workqueue(false),
+ thread(WorkQueue__entry_thunk, this)
+{
+}
+
+WorkQueue::~WorkQueue(void)
+{
+ thread.join();
+}
+
+}
{
}
- ~Context()
- {
- delete wsContext;
- }
+ ~Context();
glws::Context* wsContext;
extern bool insideGlBeginEnd;
-extern Context *currentContext;
+Context *
+getCurrentContext(void);
int
print ' glretrace::insideGlBeginEnd = false;'
if function.name.startswith('gl') and not function.name.startswith('glX'):
- print r' if (retrace::debug && !glretrace::currentContext) {'
+ print r' if (retrace::debug && !glretrace::getCurrentContext()) {'
print r' retrace::warning(call) << "no current context\n";'
print r' }'
)
if function.name in ('glUseProgram', 'glUseProgramObjectARB'):
- print r' if (glretrace::currentContext) {'
- print r' glretrace::currentContext->activeProgram = call.arg(0).toUInt();'
+ print r' glretrace::Context *currentContext = glretrace::getCurrentContext();'
+ print r' if (currentContext) {'
+ print r' currentContext->activeProgram = call.arg(0).toUInt();'
print r' }'
# Only profile if not inside a list as the queries get inserted into list
# Error checking
if function.name.startswith('gl'):
# glGetError is not allowed inside glBegin/glEnd
- print ' if (retrace::debug && !glretrace::insideGlBeginEnd) {'
+ print ' if (retrace::debug && !glretrace::insideGlBeginEnd && glretrace::getCurrentContext()) {'
print ' glretrace::checkGlError(call);'
if function.name in ('glProgramStringARB', 'glProgramStringNV'):
print r' GLint error_position = -1;'
print ' GLint program = -1;'
print ' if (glretrace::insideList) {'
print ' // glUseProgram & glUseProgramObjectARB are display-list-able'
- print ' program = _program_map[glretrace::currentContext->activeProgram];'
+ print r' glretrace::Context *currentContext = glretrace::getCurrentContext();'
+ print ' program = _program_map[currentContext->activeProgram];'
print ' } else {'
print ' GLint pipeline = 0;'
print ' if (_pipelineHasBeenBound) {'
Context *context = getContext(ctx);
if (context) {
- if (retrace::doubleBuffer) {
- context->drawable->swapBuffers();
+ if (context->drawable) {
+ if (retrace::doubleBuffer) {
+ context->drawable->swapBuffers();
+ } else {
+ glFlush();
+ }
+ frame_complete(call);
} else {
- glFlush();
+ if (retrace::debug) {
+ retrace::warning(call) << "context has no drawable\n";
+ }
}
-
- frame_complete(call);
}
}
GLvoid * pixels = NULL;
- if (glretrace::currentContext != context) {
+ if (glretrace::getCurrentContext() != context) {
if (retrace::debug) {
retrace::warning(call) << "current context mismatch\n";
}
it = drawable_map.find(orig_surface);
if (it != drawable_map.end()) {
+ glretrace::Context *currentContext = glretrace::getCurrentContext();
if (!currentContext || it->second != currentContext->drawable) {
// TODO: reference count
delete it->second;
void
beginProfile(trace::Call &call, bool isDraw) {
+ glretrace::Context *currentContext = glretrace::getCurrentContext();
+
/* Create call query */
CallQuery query;
query.isDraw = isDraw;
query.call = call.no;
query.sig = call.sig;
- query.program = glretrace::currentContext ? glretrace::currentContext->activeProgram : 0;
+ query.program = currentContext ? currentContext->activeProgram : 0;
/* GPU profiling only for draw calls */
if (isDraw) {
void
initContext() {
+ glretrace::Context *currentContext = glretrace::getCurrentContext();
+
/* Ensure we have adequate extension support */
assert(currentContext);
supportsTimestamp = currentContext->hasExtension("GL_ARB_timer_query");
/* Setup debug message call back */
if (retrace::debug && supportsDebugOutput) {
+ glretrace::Context *currentContext = glretrace::getCurrentContext();
glDebugMessageCallbackARB(&debugOutputCallback, currentContext);
if (DEBUG_OUTPUT_SYNCHRONOUS) {
retrace::frameComplete(call);
+ glretrace::Context *currentContext = glretrace::getCurrentContext();
if (!currentContext) {
return;
}
+ assert(currentContext->drawable);
if (retrace::debug && !currentContext->drawable->visible) {
retrace::warning(call) << "could not infer drawable size (glViewport never called)\n";
}
image::Image *
retrace::getSnapshot(void) {
- if (!glretrace::currentContext) {
+ if (!glretrace::getCurrentContext()) {
return NULL;
}
bool
retrace::dumpState(std::ostream &os)
{
+ glretrace::Context *currentContext = glretrace::getCurrentContext();
+
if (glretrace::insideGlBeginEnd ||
- !glretrace::currentContext) {
+ !currentContext) {
return false;
}
void
retrace::flushRendering(void) {
- glretrace::flushQueries();
- glFlush();
+ glretrace::Context *currentContext = glretrace::getCurrentContext();
+ if (currentContext) {
+ glretrace::flushQueries();
+ glFlush();
+ }
}
void
if (retrace::doubleBuffer) {
if (drawable) {
drawable->swapBuffers();
- } else if (currentContext) {
- currentContext->drawable->swapBuffers();
+ } else {
+ glretrace::Context *currentContext = glretrace::getCurrentContext();
+ if (currentContext) {
+ currentContext->drawable->swapBuffers();
+ }
}
} else {
glFlush();
Context *new_context = glretrace::createContext(share_context);
if (new_context) {
+ glretrace::Context *currentContext = glretrace::getCurrentContext();
if (currentContext == old_context) {
glretrace::makeCurrent(call, currentContext->drawable, new_context);
}
#include <string.h>
+#include "os_thread.hpp"
#include "retrace.hpp"
#include "glproc.hpp"
#include "glstate.hpp"
namespace glretrace {
-Context *currentContext = NULL;
-
-
static glws::Visual *
visuals[glws::PROFILE_MAX];
}
+Context::~Context()
+{
+ //assert(this != getCurrentContext());
+ if (this != getCurrentContext()) {
+ delete wsContext;
+ }
+}
+
+
+static thread_specific Context *
+currentContextPtr;
+
+
bool
makeCurrent(trace::Call &call, glws::Drawable *drawable, Context *context)
{
+ Context *currentContext = currentContextPtr;
glws::Drawable *currentDrawable = currentContext ? currentContext->drawable : NULL;
if (drawable == currentDrawable && context == currentContext) {
return false;
}
- if (currentContext) {
- currentContext->drawable = NULL;
- }
+ currentContextPtr = context;
if (drawable && context) {
- currentContext = context;
- currentContext->drawable = drawable;
+ context->drawable = drawable;
if (!context->used) {
initContext();
context->used = true;
}
- } else {
- currentContext = NULL;
}
return true;
}
+Context *
+getCurrentContext(void) {
+ return currentContextPtr;
+}
/**
*/
void
updateDrawable(int width, int height) {
+ Context *currentContext = getCurrentContext();
if (!currentContext) {
return;
}
* - http://developer.apple.com/library/mac/#samplecode/glut/Introduction/Intro.html
* - http://developer.apple.com/library/mac/#samplecode/GLEssentials/Introduction/Intro.html
* - http://www.glfw.org/
+ * - http://cocoasamurai.blogspot.co.uk/2008/04/guide-to-threading-on-leopard.html
+ * - http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html
*/
#include "glws.hpp"
+/**
+ * Dummy thread to force Cocoa to enter multithreading mode.
+ */
+@interface DummyThread : NSObject
+ + (void)enterMultiThreaded;
+ + (void)dummyThreadMethod:(id)unused;
+@end
+
+@implementation DummyThread
+ + (void)dummyThreadMethod:(id)unused {
+ (void)unused;
+ }
+
+ + (void)enterMultiThreaded {
+ [NSThread detachNewThreadSelector:@selector(dummyThreadMethod:)
+ toTarget:self
+ withObject:nil];
+ }
+@end
+
+
namespace glws {
-NSAutoreleasePool *autoreleasePool = nil;
+static __thread NSAutoreleasePool *
+autoreleasePool = nil;
class CocoaVisual : public Visual
};
+static inline void
+initThread(void) {
+ if (autoreleasePool == nil) {
+ autoreleasePool = [[NSAutoreleasePool alloc] init];
+ }
+}
+
void
init(void) {
// Prevent glproc to load system's OpenGL, so that we can trace glretrace.
_libGlHandle = dlopen("OpenGL", RTLD_LOCAL | RTLD_NOW | RTLD_FIRST);
- [NSApplication sharedApplication];
+ initThread();
+
+ [DummyThread enterMultiThreaded];
- autoreleasePool = [[NSAutoreleasePool alloc] init];
+ bool isMultiThreaded = [NSThread isMultiThreaded];
+ if (!isMultiThreaded) {
+ std::cerr << "error: failed to enable Cocoa multi-threading\n";
+ exit(1);
+ }
+
+ [NSApplication sharedApplication];
[NSApp finishLaunching];
}
Visual *
createVisual(bool doubleBuffer, Profile profile) {
+
+ initThread();
+
if (profile != PROFILE_COMPAT &&
profile != PROFILE_CORE) {
return nil;
Drawable *
createDrawable(const Visual *visual, int width, int height, bool pbuffer)
{
+ initThread();
+
return new CocoaDrawable(visual, width, height, pbuffer);
}
Context *
createContext(const Visual *visual, Context *shareContext, Profile profile, bool debug)
{
+ initThread();
+
NSOpenGLPixelFormat *pixelFormat = static_cast<const CocoaVisual *>(visual)->pixelFormat;
NSOpenGLContext *share_context = nil;
NSOpenGLContext *context;
bool
makeCurrent(Drawable *drawable, Context *context)
{
+ initThread();
+
if (!drawable || !context) {
[NSOpenGLContext clearCurrentContext];
} else {
bool
processEvents(void) {
- NSEvent* event;
+ initThread();
+ NSEvent* event;
do {
event = [NSApp nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantPast]
void
init(void) {
+ XInitThreads();
+
display = XOpenDisplay(NULL);
if (!display) {
std::cerr << "error: unable to open display " << XDisplayName(NULL) << "\n";
#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;
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;
+}
- callNo = call->no;
- 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;
+}
- if (call->no >= dumpStateCallNo &&
- dumpState(std::cout)) {
- exit(0);
+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;
+
+ 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);
+
+ // XXX: Flush immediately to avoid race conditions on unprotected
+ // static/global variables.
+ thread_wq->flush();
+ } 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);
" -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";
}
} 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]);
GLlocation = Handle("location", GLint, key=('program', GLhandleARB))
GLlocationARB = Handle("location", GLint, key=('programObj', GLhandleARB))
-contextKey = ('reinterpret_cast<uintptr_t>(glretrace::currentContext)', UIntPtr)
+contextKey = ('reinterpret_cast<uintptr_t>(glretrace::getCurrentContext())', UIntPtr)
GLprogramARB = Handle("programARB", GLuint)
GLframebuffer = Handle("framebuffer", GLuint)
}
};
-static os::thread_specific_ptr<ThreadState> thread_state;
+static thread_specific ThreadState *thread_state;
static ThreadState *get_ts(void)
{
- ThreadState *ts = thread_state.get();
-
+ ThreadState *ts = thread_state;
if (!ts) {
- ts = new ThreadState;
- thread_state.reset(ts);
+ thread_state = ts = new ThreadState;
}
return ts;