diff options
-rw-r--r-- | AUTHORS | 2 | ||||
-rwxr-xr-x | emcc | 7 | ||||
-rw-r--r-- | src/library_browser.js | 21 | ||||
-rw-r--r-- | src/preamble.js | 15 | ||||
-rw-r--r-- | system/include/EGL/eglplatform.h | 6 | ||||
-rw-r--r-- | system/include/emscripten/emscripten.h | 26 | ||||
-rw-r--r-- | tests/browser_gc.cpp | 6 | ||||
-rw-r--r-- | tests/emscripten_api_browser.cpp | 24 | ||||
-rwxr-xr-x | tests/runner.py | 23 | ||||
-rw-r--r-- | tests/sdl_mouse.c | 6 | ||||
-rw-r--r-- | tools/reproduceriter.js | 216 | ||||
-rwxr-xr-x | tools/reproduceriter.py | 327 | ||||
-rw-r--r-- | tools/reproduceriter_shell.js | 871 | ||||
-rw-r--r-- | tools/shared.py | 38 |
14 files changed, 1238 insertions, 350 deletions
@@ -35,4 +35,4 @@ a license to everyone to use it as detailed in LICENSE.) * James Pike <totoro.friend@chilon.net> * Mokhtar Naamani <mokhtar.naamani@gmail.com> * Benjamin Stover <benjamin.stover@gmail.com> - +* Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com> @@ -110,7 +110,7 @@ if DEBUG and LEAVE_INPUTS_RAW: print >> sys.stderr, 'emcc: leaving inputs raw' stdout = PIPE if not DEBUG else None # suppress output of child processes stderr = PIPE if not DEBUG else None # unless we are in DEBUG mode -shared.check_sanity() +shared.check_sanity(force=DEBUG) # Handle some global flags @@ -414,7 +414,10 @@ if TEMP_DIR: shutil.rmtree(temp_dir) # clear it os.makedirs(temp_dir) else: - temp_dir = tempfile.mkdtemp() + temp_root = os.path.join(shared.TEMP_DIR, 'emcc') + if not os.path.exists(temp_root): + os.makedirs(temp_root) + temp_dir = tempfile.mkdtemp(dir=temp_root) def in_temp(name): return os.path.join(temp_dir, os.path.basename(name)) diff --git a/src/library_browser.js b/src/library_browser.js index 43732e5b..5291dcee 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -384,7 +384,7 @@ mergeInto(LibraryManager.library, { if (Browser.mainLoop.queue.length > 0) { var start = Date.now(); var blocker = Browser.mainLoop.queue.shift(); - blocker.func(); + blocker.func(blocker.arg); if (Browser.mainLoop.remainingBlockers) { var remaining = Browser.mainLoop.remainingBlockers; var next = remaining%1 == 0 ? remaining-1 : Math.floor(remaining); @@ -451,13 +451,13 @@ mergeInto(LibraryManager.library, { Browser.mainLoop.resume(); }, - _emscripten_push_main_loop_blocker: function(func, name) { - Browser.mainLoop.queue.push({ func: FUNCTION_TABLE[func], name: Pointer_stringify(name), counted: true }); + _emscripten_push_main_loop_blocker: function(func, arg, name) { + Browser.mainLoop.queue.push({ func: FUNCTION_TABLE[func], arg: arg, name: Pointer_stringify(name), counted: true }); Browser.mainLoop.updateStatus(); }, - _emscripten_push_uncounted_main_loop_blocker: function(func, name) { - Browser.mainLoop.queue.push({ func: FUNCTION_TABLE[func], name: Pointer_stringify(name), counted: false }); + _emscripten_push_uncounted_main_loop_blocker: function(func, arg, name) { + Browser.mainLoop.queue.push({ func: FUNCTION_TABLE[func], arg: arg, name: Pointer_stringify(name), counted: false }); Browser.mainLoop.updateStatus(); }, @@ -467,14 +467,17 @@ mergeInto(LibraryManager.library, { Browser.mainLoop.updateStatus(); }, - emscripten_async_call: function(func, millis) { + emscripten_async_call: function(func, arg, millis) { Module['noExitRuntime'] = true; - var asyncCall = Runtime.getFuncWrapper(func); + function wrapper() { + Runtime.getFuncWrapper(func)(arg); + } + if (millis >= 0) { - setTimeout(asyncCall, millis); + setTimeout(wrapper, millis); } else { - Browser.requestAnimationFrame(asyncCall); + Browser.requestAnimationFrame(wrapper); } }, diff --git a/src/preamble.js b/src/preamble.js index b3cfca85..f1b95958 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -457,8 +457,6 @@ function getValue(ptr, type, noSafe) { } Module['getValue'] = getValue; -// Allocates memory for some data and initializes it properly. - var ALLOC_NORMAL = 0; // Tries to use _malloc() var ALLOC_STACK = 1; // Lives for the duration of the current function call var ALLOC_STATIC = 2; // Cannot be freed @@ -466,6 +464,19 @@ Module['ALLOC_NORMAL'] = ALLOC_NORMAL; Module['ALLOC_STACK'] = ALLOC_STACK; Module['ALLOC_STATIC'] = ALLOC_STATIC; +// allocate(): This is for internal use. You can use it yourself as well, but the interface +// is a little tricky (see docs right below). The reason is that it is optimized +// for multiple syntaxes to save space in generated code. So you should +// normally not use allocate(), and instead allocate memory using _malloc(), +// initialize it with setValue(), and so forth. +// @slab: An array of data, or a number. If a number, then the size of the block to allocate, +// in *bytes* (note that this is sometimes confusing: the next parameter does not +// affect this!) +// @types: Either an array of types, one for each byte (or 0 if no type at that position), +// or a single type which is used for the entire block. This only matters if there +// is initial data - if @slab is a number, then this does not matter at all and is +// ignored. +// @allocator: How to allocate memory, see ALLOC_* function allocate(slab, types, allocator) { var zeroinit, size; if (typeof slab === 'number') { diff --git a/system/include/EGL/eglplatform.h b/system/include/EGL/eglplatform.h index 4512fd84..2db2cc47 100644 --- a/system/include/EGL/eglplatform.h +++ b/system/include/EGL/eglplatform.h @@ -75,6 +75,12 @@ typedef HDC EGLNativeDisplayType; typedef HBITMAP EGLNativePixmapType; typedef HWND EGLNativeWindowType; +#elif defined(EMSCRIPTEN) + +typedef int EGLNativeDisplayType; +typedef int EGLNativeWindowType; +typedef int EGLNativePixmapType; + #elif defined(__WINSCW__) || defined(__SYMBIAN32__) /* Symbian */ typedef int EGLNativeDisplayType; diff --git a/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h index 4d489142..8c42a18a 100644 --- a/system/include/emscripten/emscripten.h +++ b/system/include/emscripten/emscripten.h @@ -62,20 +62,20 @@ extern void emscripten_cancel_main_loop(); * at specific time in the future. */ #if EMSCRIPTEN -extern void _emscripten_push_main_loop_blocker(void (*func)(), const char *name); -extern void _emscripten_push_uncounted_main_loop_blocker(void (*func)(), const char *name); +extern void _emscripten_push_main_loop_blocker(void (*func)(void *), void *arg, const char *name); +extern void _emscripten_push_uncounted_main_loop_blocker(void (*func)(void *), void *arg, const char *name); #else -inline void _emscripten_push_main_loop_blocker(void (*func)(), const char *name) { - func(); +inline void _emscripten_push_main_loop_blocker(void (*func)(void *), void *arg, const char *name) { + func(arg); } -inline void _emscripten_push_uncounted_main_loop_blocker(void (*func)(), const char *name) { - func(); +inline void _emscripten_push_uncounted_main_loop_blocker(void (*func)(void *), void *arg, const char *name) { + func(arg); } #endif -#define emscripten_push_main_loop_blocker(func) \ - _emscripten_push_main_loop_blocker(func, #func) -#define emscripten_push_uncounted_main_loop_blocker(func) \ - _emscripten_push_uncounted_main_loop_blocker(func, #func) +#define emscripten_push_main_loop_blocker(func, arg) \ + _emscripten_push_main_loop_blocker(func, arg, #func) +#define emscripten_push_uncounted_main_loop_blocker(func, arg) \ + _emscripten_push_uncounted_main_loop_blocker(func, arg, #func) /* * Sets the number of blockers remaining until some user-relevant @@ -99,11 +99,11 @@ inline void emscripten_set_main_loop_expected_blockers(int num) {} * mechanism is used. */ #if EMSCRIPTEN -extern void emscripten_async_call(void (*func)(), int millis); +extern void emscripten_async_call(void (*func)(void *), void *arg, int millis); #else -inline void emscripten_async_call(void (*func)(), int millis) { +inline void emscripten_async_call(void (*func)(void *), void *arg, int millis) { if (millis) SDL_Delay(millis); - func(); + func(arg); } #endif diff --git a/tests/browser_gc.cpp b/tests/browser_gc.cpp index 75dea10a..4d6cc712 100644 --- a/tests/browser_gc.cpp +++ b/tests/browser_gc.cpp @@ -16,7 +16,7 @@ void finalizer(void *ptr, void *arg) { int stage = 0; float start = 0; -void waiter() { +void waiter(void*) { if (stage == 0) { // wait for a while, see no GCing assert(global); if (emscripten_get_now() - start > 2100) { @@ -58,7 +58,7 @@ void waiter() { } } - emscripten_async_call(waiter, 100); + emscripten_async_call(waiter, NULL, 100); } int main() { @@ -89,7 +89,7 @@ int main() { void **local2Data = (void**)local2; local2Data[0] = local4; // actually ignored, because local2 is atomic, so 4 is freeable - emscripten_async_call(waiter, 100); + emscripten_async_call(waiter, NULL, 100); return 0; } diff --git a/tests/emscripten_api_browser.cpp b/tests/emscripten_api_browser.cpp index 305265e5..c209550b 100644 --- a/tests/emscripten_api_browser.cpp +++ b/tests/emscripten_api_browser.cpp @@ -11,19 +11,22 @@ extern "C" { bool pre1ed = false; bool pre2ed = false; -void pre1() { +void pre1(void *arg) { assert(!pre1ed); assert(!pre2ed); + assert((int)arg == 123); pre1ed = true; } -void pre2() { +void pre2(void *arg) { assert(pre1ed); assert(!pre2ed); + assert((int)arg == 98); pre2ed = true; } bool fived = false; -void five() { +void five(void *arg) { + assert((int)arg == 55); fived = true; emscripten_resume_main_loop(); } @@ -33,11 +36,11 @@ void mainey() { printf("mainey: %d\n", counter++); if (counter == 20) { emscripten_pause_main_loop(); - emscripten_async_call(five, 1000); + emscripten_async_call(five, (void*)55, 1000); } else if (counter == 22) { // very soon after 20, so without pausing we fail assert(fived); - emscripten_push_main_loop_blocker(pre1); - emscripten_push_main_loop_blocker(pre2); + emscripten_push_main_loop_blocker(pre1, (void*)123); + emscripten_push_main_loop_blocker(pre2, (void*)98); } else if (counter == 23) { assert(pre1ed); assert(pre2ed); @@ -47,7 +50,8 @@ void mainey() { } } -void four() { +void four(void *arg) { + assert((int)arg == 43); printf("four!\n"); emscripten_set_main_loop(mainey, 0); } @@ -56,10 +60,10 @@ void __attribute__((used)) third() { int now = SDL_GetTicks(); printf("thard! %d\n", now); assert(fabs(now - last - 1000) < 500); - emscripten_async_call(four, -1); // triggers requestAnimationFrame + emscripten_async_call(four, (void*)43, -1); // triggers requestAnimationFrame } -void second() { +void second(void *arg) { int now = SDL_GetTicks(); printf("sacond! %d\n", now); assert(fabs(now - last - 500) < 250); @@ -81,7 +85,7 @@ int main() { atexit(never); // should never be called - it is wrong to exit the runtime orderly if we have async calls! - emscripten_async_call(second, 500); + emscripten_async_call(second, (void*)0, 500); return 1; } diff --git a/tests/runner.py b/tests/runner.py index e4aba0c9..792b8939 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -7829,11 +7829,13 @@ elif 'browser' in str(sys.argv): var actual = actualCtx.getImageData(0, 0, actualImage.width, actualImage.height).data; var total = 0; - for (var x = 0; x < img.width; x++) { - for (var y = 0; y < img.height; y++) { - total += Math.abs(expected[y*img.width*4 + x*4 + 0] - actual[y*img.width*4 + x*4 + 0]); - total += Math.abs(expected[y*img.width*4 + x*4 + 1] - actual[y*img.width*4 + x*4 + 1]); - total += Math.abs(expected[y*img.width*4 + x*4 + 2] - actual[y*img.width*4 + x*4 + 2]); + var width = img.width; + var height = img.height; + for (var x = 0; x < width; x++) { + for (var y = 0; y < height; y++) { + total += Math.abs(expected[y*width*4 + x*4 + 0] - actual[y*width*4 + x*4 + 0]); + total += Math.abs(expected[y*width*4 + x*4 + 1] - actual[y*width*4 + x*4 + 1]); + total += Math.abs(expected[y*width*4 + x*4 + 2] - actual[y*width*4 + x*4 + 2]); } } var wrong = Math.floor(total / (img.width*img.height*3)); // floor, to allow some margin of error for antialiasing @@ -8805,7 +8807,7 @@ elif 'sanity' in str(sys.argv): assert (open(CONFIG_FILE).read() == open(path_from_root('settings.py')).read()), 'Settings should be copied from settings.py' # Second run, with bad EM_CONFIG - for settings in ['blah', 'LLVM_ROOT="blah"; JS_ENGINES=[]; COMPILER_ENGINE=NODE_JS=SPIDERMONKEY_ENGINE=[]']: + for settings in ['blah', 'LLVM_ROOT="blarg"; JS_ENGINES=[]; COMPILER_ENGINE=NODE_JS=SPIDERMONKEY_ENGINE=[]']: f = open(CONFIG_FILE, 'w') f.write(settings) f.close() @@ -8901,6 +8903,15 @@ elif 'sanity' in str(sys.argv): self.assertNotContained(SANITY_MESSAGE, output) self.assertNotContained(SANITY_FAIL_MESSAGE, output) + # but with EMCC_DEBUG=1 we should check + assert not os.environ.get('EMCC_DEBUG'), 'do not run sanity checks in debug mode!' + os.environ['EMCC_DEBUG'] = '1' + output = self.check_working(EMCC) + self.assertContained(SANITY_MESSAGE, output) + del os.environ['EMCC_DEBUG'] + output = self.check_working(EMCC) + self.assertNotContained(SANITY_MESSAGE, output) + # But the test runner should output = self.check_working(commands[1]) self.assertContained(SANITY_MESSAGE, output) diff --git a/tests/sdl_mouse.c b/tests/sdl_mouse.c index 0c10198a..f0282ab1 100644 --- a/tests/sdl_mouse.c +++ b/tests/sdl_mouse.c @@ -43,7 +43,7 @@ void one() { } } -void main_2(); +void main_2(void* arg); int main() { SDL_Init(SDL_INIT_VIDEO); @@ -52,12 +52,12 @@ int main() { SDL_Rect rect = { 0, 0, 600, 450 }; SDL_FillRect(screen, &rect, 0x2244ffff); - emscripten_async_call(main_2, 3000); // avoid startup delays and intermittent errors + emscripten_async_call(main_2, NULL, 3000); // avoid startup delays and intermittent errors return 0; } -void main_2() { +void main_2(void* arg) { emscripten_run_script("window.simulateMouseEvent(10, 20, -1)"); // move from 0,0 to 10,20 emscripten_run_script("window.simulateMouseEvent(10, 20, 0)"); // click emscripten_run_script("window.simulateMouseEvent(10, 20, 0)"); // click some more, but this one should be ignored through PeepEvent diff --git a/tools/reproduceriter.js b/tools/reproduceriter.js new file mode 100644 index 00000000..9dce8199 --- /dev/null +++ b/tools/reproduceriter.js @@ -0,0 +1,216 @@ + +var Recorder = (function() { + var recorder; + var init = 'reproduce='; + var initLocation = window.location.search.indexOf(init); + var replaying = initLocation >= 0; + var raf = window['requestAnimationFrame'] || + window['mozRequestAnimationFrame'] || + window['webkitRequestAnimationFrame'] || + window['msRequestAnimationFrame'] || + window['oRequestAnimationFrame']; + if (!replaying) { + // Prepare to record + recorder = {}; + // Start + recorder.frameCounter = 0; // the frame counter is used to know when to replay events + recorder.start = function() { + alert("Starting recording! Don't forget to Recorder.finish() afterwards!"); + function count() { + recorder.frameCounter++; + raf(count); + } + count(); + recorder.started = true; + }; + // Math.random + recorder.randoms = []; + recorder.random = Math.random; + Math.random = function() { + var ret = recorder.random(); + recorder.randoms.push(ret); + return ret; + }; + // Date.now, performance.now + recorder.dnows = []; + recorder.dnow = Date.now; + Date.now = function() { + var ret = recorder.dnow(); + recorder.dnows.push(ret); + return ret; + }; + recorder.pnows = []; + var pnow = performance.now || performance.webkitNow || performance.mozNow || performance.oNow || performance.msNow; + recorder.pnow = function() { return pnow.call(performance) }; + performance.now = function() { + var ret = recorder.pnow(); + recorder.pnows.push(ret); + return ret; + }; + // Events + recorder.devents = []; // document events + recorder.onEvent = function(which, callback) { + document['on' + which] = function(event) { + if (!recorder.started) return true; + event.frameCounter = recorder.frameCounter; + recorder.devents.push(event); + return callback(event); // XXX do we need to record the return value? + }; + }; + recorder.tevents = []; // custom-target events. Currently we assume a single such custom target (aside from document), e.g., a canvas for the game. + recorder.addListener = function(target, which, callback, arg) { + target.addEventListener(which, function(event) { + if (!recorder.started) return true; + event.frameCounter = recorder.frameCounter; + recorder.tevents.push(event); + return callback(event); // XXX do we need to record the return value? + }, arg); + }; + // Finish + recorder.finish = function() { + // Reorder data because pop() is faster than shift() + recorder.randoms.reverse(); + recorder.dnows.reverse(); + recorder.pnows.reverse(); + recorder.devents.reverse(); + recorder.tevents.reverse(); + // Make JSON.stringify work on data from native event objects (and only store relevant ones) + var importantProperties = { + type: 1, + movementX: 1, mozMovementX: 1, webkitMovementX: 1, + movementY: 1, mozMovementY: 1, webkitMovementY: 1, + detail: 1, + wheelDelta: 1, + pageX: 1, + pageY: 1, + button: 1, + keyCode: 1, + frameCounter: 1 + }; + function importantize(event) { + var ret = {}; + for (var prop in importantProperties) { + if (prop in event) { + ret[prop] = event[prop]; + } + } + return ret; + } + recorder.devents = recorder.devents.map(importantize); + recorder.tevents = recorder.tevents.map(importantize); + // Write out + alert('Writing out data, remember to save!'); + setTimeout(function() { + document.open(); + document.write(JSON.stringify(recorder)); + document.close(); + }, 0); + return '.'; + }; + } else { + // Load recording + var dataPath = window.location.search.substring(initLocation + init.length); + var baseURL = window.location.toString().replace('://', 'cheez999').split('?')[0].split('/').slice(0, -1).join('/').replace('cheez999', '://'); + if (baseURL[baseURL.length-1] != '/') baseURL += '/'; + var path = baseURL + dataPath; + alert('Loading replay from ' + path); + var request = new XMLHttpRequest(); + request.open('GET', path, false); + request.send(); + var raw = request.responseText; + raw = raw.substring(raw.indexOf('{'), raw.lastIndexOf('}')+1); // remove <html> etc + recorder = JSON.parse(raw); + // prepare to replay + // Start + recorder.frameCounter = 0; // the frame counter is used to know when to replay events + recorder.start = function() { + function count() { + recorder.frameCounter++; + raf(count); + // replay relevant events for this frame + while (recorder.devents.length && recorder.devents[recorder.devents.length-1].frameCounter <= recorder.frameCounter) { + var event = recorder.devents.pop(); + recorder['on' + event.type](event); + } + while (recorder.tevents.length && recorder.tevents[recorder.tevents.length-1].frameCounter <= recorder.frameCounter) { + var event = recorder.tevents.pop(); + recorder['event' + event.type](event); + } + } + count(); + }; + // Math.random + recorder.random = Math.random; + Math.random = function() { + if (recorder.randoms.length > 0) { + return recorder.randoms.pop(); + } else { + recorder.finish(); + throw 'consuming too many random values!'; + } + }; + // Date.now, performance.now + recorder.dnow = Date.now; + Date.now = function() { + if (recorder.dnows.length > 0) { + return recorder.dnows.pop(); + } else { + recorder.finish(); + throw 'consuming too many Date.now values!'; + } + }; + var pnow = performance.now || performance.webkitNow || performance.mozNow || performance.oNow || performance.msNow; + recorder.pnow = function() { return pnow.call(performance) }; + performance.now = function() { + if (recorder.pnows.length > 0) { + return recorder.pnows.pop(); + } else { + recorder.finish(); + throw 'consuming too many performance.now values!'; + } + }; + // Events + recorder.onEvent = function(which, callback) { + recorder['on' + which] = callback; + }; + recorder.eventCallbacks = {}; + recorder.addListener = function(target, which, callback, arg) { + recorder['event' + which] = callback; + }; + recorder.onFinish = []; + // Benchmarking hooks - emscripten specific + setTimeout(function() { + var totalTime = 0; + var totalSquared = 0; + var iterations = 0; + var maxTime = 0; + var curr = 0; + Module.preMainLoop = function() { + curr = recorder.pnow(); + } + Module.postMainLoop = function() { + var time = recorder.pnow() - curr; + totalTime += time; + totalSquared += time*time; + maxTime = Math.max(maxTime, time); + iterations++; + }; + recorder.onFinish.push(function() { + var mean = totalTime / iterations; + var meanSquared = totalSquared / iterations; + console.log('mean frame : ' + mean + ' ms'); + console.log('frame std dev: ' + Math.sqrt(meanSquared - (mean*mean)) + ' ms'); + console.log('max frame : ' + maxTime + ' ms'); + }); + }); + // Finish + recorder.finish = function() { + recorder.onFinish.forEach(function(finish) { + finish(); + }); + }; + } + recorder.replaying = replaying; + return recorder; +})(); + diff --git a/tools/reproduceriter.py b/tools/reproduceriter.py index 6696915d..a1912396 100755 --- a/tools/reproduceriter.py +++ b/tools/reproduceriter.py @@ -1,9 +1,6 @@ #!/usr/bin/env python ''' - -* This is a work in progress * - Reproducer Rewriter =================== @@ -19,13 +16,19 @@ Usage: 1. Run this script as - reproduceriter.py IN_DIR OUT_DIR FIRST_JS + reproduceriter.py IN_DIR OUT_DIR FIRST_JS [WINDOW_LOCATION] [ON_IDLE] IN_DIR should be the project directory, and OUT_DIR will be created with the instrumented code (OUT_DIR will be overwritten if it exists). FIRST_JS should be a path (relative to IN_DIR) to the first JavaScript file loaded by the project (this tool - will add code to that). + will add code to that). The last two parameters, WINDOW_LOCATION + and ON_IDLE, are relevant for shell builds. If WINDOW_LOCATION is + specified, we will make a build that runs in the shell and not in + a browser. WINDOW_LOCATION is the fake window.location we set in the + fake DOM, and ON_IDLE is code that runs when the fake main browser + event loop runs out of actions. (Note that only a browser build can + do recording, shell builds just replay.) You will need to call @@ -79,7 +82,26 @@ Examples * BananaBread: Unpack into a directory called bb, then one directory up, run - emscripten/tools/reproduceriter.py bb bench js/game-setup.js game.html?low,low,reproduce=repro.data + emscripten/tools/reproduceriter.py bb bench js/game-setup.js game.html?low,low,reproduce=repro.data "function(){ print('triggering click'); document.querySelector('.fullscreen-button.low-res').callEventListeners('click'); window.onIdle = null; }" + + for a shell build, or + + emscripten/tools/reproduceriter.py bb bench js/game-setup.js + + for a browser build. Since only a browser build can do recording, you would normally + make a browser build, record a trace, then make a shell build and copy the trace + there so you can run it. + + The last parameter specifies what to do when the event loop is idle: We fire an event and then set onIdle (which was this function) to null, so this is a one-time occurence. + +Notes + + * Replay can depend on browser state. One case is if you are replaying a fullscreen + game with pointer lock, then you will need to manually allow pointer lock if it + isn't already on for the machine. If you do it too early or too late, the replay + can be different, since mousemove events mean different things depending on + whether the pointer is locked or not. + ''' import os, sys, shutil, re @@ -92,6 +114,11 @@ in_dir = sys.argv[1] out_dir = sys.argv[2] first_js = sys.argv[3] window_location = sys.argv[4] if len(sys.argv) >= 5 else '' +on_idle = sys.argv[5] if len(sys.argv) >= 6 else '' + +shell = not not window_location + +dirs_to_drop = 0 if not os.path.dirname(first_js) else len(os.path.dirname(first_js).split('/')) if os.path.exists(out_dir): shutil.rmtree(out_dir) @@ -121,287 +148,13 @@ for parent, dirs, files in os.walk(out_dir): print 'add boilerplate...' -open(os.path.join(out_dir, first_js), 'w').write(''' - -// environment for shell -if (typeof nagivator == 'undefined') { - var window = { - location: { - toString: function() { - return '%s'; - }, - search: '%s', - }, - rafs: [], - requestAnimationFrame: function(func) { - window.rafs.push(func); - }, - runEventLoop: function() { - while (1) { // run forever until an exception stops this replay - var currRafs = window.rafs; - window.rafs = []; - for (var i = 0; i < currRafs.length; i++) { - currRafs[i](); - } - } - }, - }; - var document = { - getElementById: function(id) { - switch(id) { - case 'canvas': { - return { - getContext: function(which) { - switch(which) { - case 'experimental-webgl': { - return { - getExtension: function() { return 1 }, - requestPointerLock: function() { - throw 'pointerLock'; - }, - }; - } - default: throw 'canvas.getContext: ' + which; - } - }, - }; - } - default: throw 'getElementById: ' + id; - } - }, - querySelector: function() { - return { - classList: { - add: function(){}, - remove: function(){}, - }, - }; - }, - }; - var performance = { - now: function() { - return Date.now(); - }, - }; -} - -var Recorder = (function() { - var recorder; - var init = 'reproduce='; - var initLocation = window.location.search.indexOf(init); - var replaying = initLocation >= 0; - var raf = window['requestAnimationFrame'] || - window['mozRequestAnimationFrame'] || - window['webkitRequestAnimationFrame'] || - window['msRequestAnimationFrame'] || - window['oRequestAnimationFrame']; - if (!replaying) { - // Prepare to record - recorder = {}; - // Start - recorder.frameCounter = 0; // the frame counter is used to know when to replay events - recorder.start = function() { - alert("Starting recording! Don't forget to Recorder.finish() afterwards!"); - function count() { - recorder.frameCounter++; - raf(count); - } - count(); - recorder.started = true; - }; - // Math.random - recorder.randoms = []; - var random = Math.random; - Math.random = function() { - var ret = random(); - recorder.randoms.push(ret); - return ret; - }; - // Date.now, performance.now - recorder.dnows = []; - recorder.dnow = Date.now; - Date.now = function() { - var ret = recorder.dnow(); - recorder.dnows.push(ret); - return ret; - }; - recorder.pnows = []; - recorder.pnow = performance.now; - performance.now = function() { - var ret = recorder.pnow(); - recorder.pnows.push(ret); - return ret; - }; - // Events - recorder.devents = []; // document events - recorder.onEvent = function(which, callback) { - document['on' + which] = function(event) { - if (!recorder.started) return true; - event.frameCounter = recorder.frameCounter; - recorder.devents.push(event); - return callback(event); // XXX do we need to record the return value? - }; - }; - recorder.tevents = []; // custom-target events. Currently we assume a single such custom target (aside from document), e.g., a canvas for the game. - recorder.addListener = function(target, which, callback, arg) { - target.addEventListener(which, function(event) { - if (!recorder.started) return true; - event.frameCounter = recorder.frameCounter; - recorder.tevents.push(event); - return callback(event); // XXX do we need to record the return value? - }, arg); - }; - // Finish - recorder.finish = function() { - // Reorder data because pop() is faster than shift() - recorder.randoms.reverse(); - recorder.dnows.reverse(); - recorder.pnows.reverse(); - recorder.devents.reverse(); - recorder.tevents.reverse(); - // Make JSON.stringify work on data from native event objects (and only store relevant ones) - var importantProperties = { - type: 1, - movementX: 1, mozMovementX: 1, webkitMovementX: 1, - movementY: 1, mozMovementY: 1, webkitMovementY: 1, - detail: 1, - wheelDelta: 1, - pageX: 1, - pageY: 1, - button: 1, - keyCode: 1, - frameCounter: 1 - }; - function importantize(event) { - var ret = {}; - for (var prop in importantProperties) { - if (prop in event) { - ret[prop] = event[prop]; - } - } - return ret; - } - recorder.devents = recorder.devents.map(importantize); - recorder.tevents = recorder.tevents.map(importantize); - // Write out - alert('Writing out data, remember to save!'); - setTimeout(function() { - document.open(); - document.write(JSON.stringify(recorder)); - document.close(); - }, 0); - return '.'; - }; - } else { - // Load recording - var dataPath = window.location.search.substring(initLocation + init.length); - var baseURL = window.location.toString().replace('://', 'cheez999').split('?')[0].split('/').slice(0, -1).join('/').replace('cheez999', '://'); - if (baseURL[baseURL.length-1] != '/') baseURL += '/'; - var path = baseURL + dataPath; - alert('Loading replay from ' + path); - var request = new XMLHttpRequest(); - request.open('GET', path, false); - request.send(); - var raw = request.responseText; - raw = raw.substring(raw.indexOf('{'), raw.lastIndexOf('}')+1); // remove <html> etc - recorder = JSON.parse(raw); - // prepare to replay - // Start - recorder.frameCounter = 0; // the frame counter is used to know when to replay events - recorder.start = function() { - function count() { - recorder.frameCounter++; - raf(count); - // replay relevant events for this frame - while (recorder.devents.length && recorder.devents[recorder.devents.length-1].frameCounter <= recorder.frameCounter) { - var event = recorder.devents.pop(); - recorder['on' + event.type](event); - } - while (recorder.tevents.length && recorder.tevents[recorder.tevents.length-1].frameCounter <= recorder.frameCounter) { - var event = recorder.tevents.pop(); - recorder['event' + event.type](event); - } - } - count(); - }; - // Math.random - Math.random = function() { - if (recorder.randoms.length > 0) { - return recorder.randoms.pop(); - } else { - recorder.finish(); - throw 'consuming too many values!'; - } - }; - // Date.now, performance.now - recorder.dnow = Date.now; - Date.now = function() { - if (recorder.dnows.length > 0) { - return recorder.dnows.pop(); - } else { - recorder.finish(); - throw 'consuming too many values!'; - } - }; - var pnow = performance.now || performance.webkitNow || performance.mozNow || performance.oNow || performance.msNow || dnow; - recorder.pnow = function() { return pnow.call(performance) }; - performance.now = function() { - if (recorder.pnows.length > 0) { - return recorder.pnows.pop(); - } else { - recorder.finish(); - throw 'consuming too many values!'; - } - }; - // Events - recorder.onEvent = function(which, callback) { - recorder['on' + which] = callback; - }; - recorder.eventCallbacks = {}; - recorder.addListener = function(target, which, callback, arg) { - recorder['event' + which] = callback; - }; - recorder.onFinish = []; - // Benchmarking hooks - emscripten specific - setTimeout(function() { - var totalTime = 0; - var totalSquared = 0; - var iterations = 0; - var maxTime = 0; - var curr = 0; - Module.preMainLoop = function() { - curr = recorder.pnow(); - } - Module.postMainLoop = function() { - var time = recorder.pnow() - curr; - totalTime += time; - totalSquared += time*time; - maxTime = Math.max(maxTime, time); - iterations++; - }; - recorder.onFinish.push(function() { - var mean = totalTime / iterations; - var meanSquared = totalSquared / iterations; - console.log('mean frame : ' + mean + ' ms'); - console.log('frame std dev: ' + Math.sqrt(meanSquared - (mean*mean)) + ' ms'); - console.log('max frame : ' + maxTime + ' ms'); - }); - }); - // Finish - recorder.finish = function() { - recorder.onFinish.forEach(function(finish) { - finish(); - }); - }; - } - recorder.replaying = replaying; - return recorder; -})(); -''' % (window_location, window_location.split('?')[-1]) + open(os.path.join(in_dir, first_js)).read() + ''' -if (typeof nagivator == 'undefined') { - window.runEventLoop(); -} -''') +open(os.path.join(out_dir, first_js), 'w').write( + (open(os.path.join(os.path.dirname(__file__), 'reproduceriter_shell.js')).read() % ( + window_location, window_location.split('?')[-1], on_idle or 'null', dirs_to_drop + ) if shell else '') + + open(os.path.join(os.path.dirname(__file__), 'reproduceriter.js')).read() + + open(os.path.join(in_dir, first_js)).read() + ('\nwindow.runEventLoop();\n' if shell else '') +) print 'done!' diff --git a/tools/reproduceriter_shell.js b/tools/reproduceriter_shell.js new file mode 100644 index 00000000..f425df82 --- /dev/null +++ b/tools/reproduceriter_shell.js @@ -0,0 +1,871 @@ + +var window = { + location: { + toString: function() { + return '%s'; + }, + search: '?%s', + }, + fakeNow: 0, // we don't use Date.now() + rafs: [], + timeouts: [], + uid: 0, + requestAnimationFrame: function(func) { + func.uid = window.uid++; + print('adding raf ' + func.uid); + window.rafs.push(func); + }, + setTimeout: function(func, ms) { + func.uid = window.uid++; + print('adding timeout ' + func.uid); + window.timeouts.push({ + func: func, + when: window.fakeNow + (ms || 0) + }); + window.timeouts.sort(function(x, y) { return y.when - x.when }); + }, + onIdle: %s, + runEventLoop: function() { + // run forever until an exception stops this replay + var iter = 0; + while (1) { + var start = Recorder.dnow(); + print('event loop: ' + (iter++)); + if (window.rafs.length == 0 && window.timeouts.length == 0) { + if (window.onIdle) { + window.onIdle(); + } else { + throw 'main loop is idle!'; + } + } + // rafs + var currRafs = window.rafs; + window.rafs = []; + for (var i = 0; i < currRafs.length; i++) { + var raf = currRafs[i]; + print('calling raf: ' + raf.uid);// + ': ' + raf.toString().substring(0, 50)); + raf(); + } + // timeouts + var now = window.fakeNow; + var timeouts = window.timeouts; + window.timeouts = []; + while (timeouts.length && timeouts[timeouts.length-1].when <= now) { + var timeout = timeouts.pop(); + print('calling timeout: ' + timeout.func.uid);// + ': ' + timeout.func.toString().substring(0, 50)); + timeout.func(); + } + // increment 'time' + window.fakeNow += 16.666; + print('main event loop iteration took ' + (Recorder.dnow() - start) + ' ms'); + } + }, + URL: { + createObjectURL: function(x) { + return x; // the blob itself is returned + }, + revokeObjectURL: function(x) {}, + }, +}; +var setTimeout = window.setTimeout; +var document = { + eventListeners: {}, + addEventListener: function(id, func) { + var listeners = this.eventListeners[id]; + if (!listeners) { + listeners = this.eventListeners[id] = []; + } + listeners.push(func); + }, + callEventListeners: function(id) { + var listeners = this.eventListeners[id]; + if (listeners) { + listeners.forEach(function(listener) { listener() }); + } + }, + getElementById: function(id) { + switch(id) { + case 'canvas': { + if (this.canvas) return this.canvas; + return this.canvas = { + getContext: function(which) { + switch(which) { + case 'experimental-webgl': { + return { + /* ClearBufferMask */ + DEPTH_BUFFER_BIT : 0x00000100, + STENCIL_BUFFER_BIT : 0x00000400, + COLOR_BUFFER_BIT : 0x00004000, + + /* BeginMode */ + POINTS : 0x0000, + LINES : 0x0001, + LINE_LOOP : 0x0002, + LINE_STRIP : 0x0003, + TRIANGLES : 0x0004, + TRIANGLE_STRIP : 0x0005, + TRIANGLE_FAN : 0x0006, + + /* AlphaFunction (not supported in ES20) */ + /* NEVER */ + /* LESS */ + /* EQUAL */ + /* LEQUAL */ + /* GREATER */ + /* NOTEQUAL */ + /* GEQUAL */ + /* ALWAYS */ + + /* BlendingFactorDest */ + ZERO : 0, + ONE : 1, + SRC_COLOR : 0x0300, + ONE_MINUS_SRC_COLOR : 0x0301, + SRC_ALPHA : 0x0302, + ONE_MINUS_SRC_ALPHA : 0x0303, + DST_ALPHA : 0x0304, + ONE_MINUS_DST_ALPHA : 0x0305, + + /* BlendingFactorSrc */ + /* ZERO */ + /* ONE */ + DST_COLOR : 0x0306, + ONE_MINUS_DST_COLOR : 0x0307, + SRC_ALPHA_SATURATE : 0x0308, + /* SRC_ALPHA */ + /* ONE_MINUS_SRC_ALPHA */ + /* DST_ALPHA */ + /* ONE_MINUS_DST_ALPHA */ + + /* BlendEquationSeparate */ + FUNC_ADD : 0x8006, + BLEND_EQUATION : 0x8009, + BLEND_EQUATION_RGB : 0x8009, /* same as BLEND_EQUATION */ + BLEND_EQUATION_ALPHA : 0x883D, + + /* BlendSubtract */ + FUNC_SUBTRACT : 0x800A, + FUNC_REVERSE_SUBTRACT : 0x800B, + + /* Separate Blend Functions */ + BLEND_DST_RGB : 0x80C8, + BLEND_SRC_RGB : 0x80C9, + BLEND_DST_ALPHA : 0x80CA, + BLEND_SRC_ALPHA : 0x80CB, + CONSTANT_COLOR : 0x8001, + ONE_MINUS_CONSTANT_COLOR : 0x8002, + CONSTANT_ALPHA : 0x8003, + ONE_MINUS_CONSTANT_ALPHA : 0x8004, + BLEND_COLOR : 0x8005, + + /* Buffer Objects */ + ARRAY_BUFFER : 0x8892, + ELEMENT_ARRAY_BUFFER : 0x8893, + ARRAY_BUFFER_BINDING : 0x8894, + ELEMENT_ARRAY_BUFFER_BINDING : 0x8895, + + STREAM_DRAW : 0x88E0, + STATIC_DRAW : 0x88E4, + DYNAMIC_DRAW : 0x88E8, + + BUFFER_SIZE : 0x8764, + BUFFER_USAGE : 0x8765, + + CURRENT_VERTEX_ATTRIB : 0x8626, + + /* CullFaceMode */ + FRONT : 0x0404, + BACK : 0x0405, + FRONT_AND_BACK : 0x0408, + + /* DepthFunction */ + /* NEVER */ + /* LESS */ + /* EQUAL */ + /* LEQUAL */ + /* GREATER */ + /* NOTEQUAL */ + /* GEQUAL */ + /* ALWAYS */ + + /* EnableCap */ + /* TEXTURE_2D */ + CULL_FACE : 0x0B44, + BLEND : 0x0BE2, + DITHER : 0x0BD0, + STENCIL_TEST : 0x0B90, + DEPTH_TEST : 0x0B71, + SCISSOR_TEST : 0x0C11, + POLYGON_OFFSET_FILL : 0x8037, + SAMPLE_ALPHA_TO_COVERAGE : 0x809E, + SAMPLE_COVERAGE : 0x80A0, + + /* ErrorCode */ + NO_ERROR : 0, + INVALID_ENUM : 0x0500, + INVALID_VALUE : 0x0501, + INVALID_OPERATION : 0x0502, + OUT_OF_MEMORY : 0x0505, + + /* FrontFaceDirection */ + CW : 0x0900, + CCW : 0x0901, + + /* GetPName */ + LINE_WIDTH : 0x0B21, + ALIASED_POINT_SIZE_RANGE : 0x846D, + ALIASED_LINE_WIDTH_RANGE : 0x846E, + CULL_FACE_MODE : 0x0B45, + FRONT_FACE : 0x0B46, + DEPTH_RANGE : 0x0B70, + DEPTH_WRITEMASK : 0x0B72, + DEPTH_CLEAR_VALUE : 0x0B73, + DEPTH_FUNC : 0x0B74, + STENCIL_CLEAR_VALUE : 0x0B91, + STENCIL_FUNC : 0x0B92, + STENCIL_FAIL : 0x0B94, + STENCIL_PASS_DEPTH_FAIL : 0x0B95, + STENCIL_PASS_DEPTH_PASS : 0x0B96, + STENCIL_REF : 0x0B97, + STENCIL_VALUE_MASK : 0x0B93, + STENCIL_WRITEMASK : 0x0B98, + STENCIL_BACK_FUNC : 0x8800, + STENCIL_BACK_FAIL : 0x8801, + STENCIL_BACK_PASS_DEPTH_FAIL : 0x8802, + STENCIL_BACK_PASS_DEPTH_PASS : 0x8803, + STENCIL_BACK_REF : 0x8CA3, + STENCIL_BACK_VALUE_MASK : 0x8CA4, + STENCIL_BACK_WRITEMASK : 0x8CA5, + VIEWPORT : 0x0BA2, + SCISSOR_BOX : 0x0C10, + /* SCISSOR_TEST */ + COLOR_CLEAR_VALUE : 0x0C22, + COLOR_WRITEMASK : 0x0C23, + UNPACK_ALIGNMENT : 0x0CF5, + PACK_ALIGNMENT : 0x0D05, + MAX_TEXTURE_SIZE : 0x0D33, + MAX_VIEWPORT_DIMS : 0x0D3A, + SUBPIXEL_BITS : 0x0D50, + RED_BITS : 0x0D52, + GREEN_BITS : 0x0D53, + BLUE_BITS : 0x0D54, + ALPHA_BITS : 0x0D55, + DEPTH_BITS : 0x0D56, + STENCIL_BITS : 0x0D57, + POLYGON_OFFSET_UNITS : 0x2A00, + /* POLYGON_OFFSET_FILL */ + POLYGON_OFFSET_FACTOR : 0x8038, + TEXTURE_BINDING_2D : 0x8069, + SAMPLE_BUFFERS : 0x80A8, + SAMPLES : 0x80A9, + SAMPLE_COVERAGE_VALUE : 0x80AA, + SAMPLE_COVERAGE_INVERT : 0x80AB, + + /* GetTextureParameter */ + /* TEXTURE_MAG_FILTER */ + /* TEXTURE_MIN_FILTER */ + /* TEXTURE_WRAP_S */ + /* TEXTURE_WRAP_T */ + + COMPRESSED_TEXTURE_FORMATS : 0x86A3, + + /* HintMode */ + DONT_CARE : 0x1100, + FASTEST : 0x1101, + NICEST : 0x1102, + + /* HintTarget */ + GENERATE_MIPMAP_HINT : 0x8192, + + /* DataType */ + BYTE : 0x1400, + UNSIGNED_BYTE : 0x1401, + SHORT : 0x1402, + UNSIGNED_SHORT : 0x1403, + INT : 0x1404, + UNSIGNED_INT : 0x1405, + FLOAT : 0x1406, + + /* PixelFormat */ + DEPTH_COMPONENT : 0x1902, + ALPHA : 0x1906, + RGB : 0x1907, + RGBA : 0x1908, + LUMINANCE : 0x1909, + LUMINANCE_ALPHA : 0x190A, + + /* PixelType */ + /* UNSIGNED_BYTE */ + UNSIGNED_SHORT_4_4_4_4 : 0x8033, + UNSIGNED_SHORT_5_5_5_1 : 0x8034, + UNSIGNED_SHORT_5_6_5 : 0x8363, + + /* Shaders */ + FRAGMENT_SHADER : 0x8B30, + VERTEX_SHADER : 0x8B31, + MAX_VERTEX_ATTRIBS : 0x8869, + MAX_VERTEX_UNIFORM_VECTORS : 0x8DFB, + MAX_VARYING_VECTORS : 0x8DFC, + MAX_COMBINED_TEXTURE_IMAGE_UNITS : 0x8B4D, + MAX_VERTEX_TEXTURE_IMAGE_UNITS : 0x8B4C, + MAX_TEXTURE_IMAGE_UNITS : 0x8872, + MAX_FRAGMENT_UNIFORM_VECTORS : 0x8DFD, + SHADER_TYPE : 0x8B4F, + DELETE_STATUS : 0x8B80, + LINK_STATUS : 0x8B82, + VALIDATE_STATUS : 0x8B83, + ATTACHED_SHADERS : 0x8B85, + ACTIVE_UNIFORMS : 0x8B86, + ACTIVE_ATTRIBUTES : 0x8B89, + SHADING_LANGUAGE_VERSION : 0x8B8C, + CURRENT_PROGRAM : 0x8B8D, + + /* StencilFunction */ + NEVER : 0x0200, + LESS : 0x0201, + EQUAL : 0x0202, + LEQUAL : 0x0203, + GREATER : 0x0204, + NOTEQUAL : 0x0205, + GEQUAL : 0x0206, + ALWAYS : 0x0207, + + /* StencilOp */ + /* ZERO */ + KEEP : 0x1E00, + REPLACE : 0x1E01, + INCR : 0x1E02, + DECR : 0x1E03, + INVERT : 0x150A, + INCR_WRAP : 0x8507, + DECR_WRAP : 0x8508, + + /* StringName */ + VENDOR : 0x1F00, + RENDERER : 0x1F01, + VERSION : 0x1F02, + + /* TextureMagFilter */ + NEAREST : 0x2600, + LINEAR : 0x2601, + + /* TextureMinFilter */ + /* NEAREST */ + /* LINEAR */ + NEAREST_MIPMAP_NEAREST : 0x2700, + LINEAR_MIPMAP_NEAREST : 0x2701, + NEAREST_MIPMAP_LINEAR : 0x2702, + LINEAR_MIPMAP_LINEAR : 0x2703, + + /* TextureParameterName */ + TEXTURE_MAG_FILTER : 0x2800, + TEXTURE_MIN_FILTER : 0x2801, + TEXTURE_WRAP_S : 0x2802, + TEXTURE_WRAP_T : 0x2803, + + /* TextureTarget */ + TEXTURE_2D : 0x0DE1, + TEXTURE : 0x1702, + + TEXTURE_CUBE_MAP : 0x8513, + TEXTURE_BINDING_CUBE_MAP : 0x8514, + TEXTURE_CUBE_MAP_POSITIVE_X : 0x8515, + TEXTURE_CUBE_MAP_NEGATIVE_X : 0x8516, + TEXTURE_CUBE_MAP_POSITIVE_Y : 0x8517, + TEXTURE_CUBE_MAP_NEGATIVE_Y : 0x8518, + TEXTURE_CUBE_MAP_POSITIVE_Z : 0x8519, + TEXTURE_CUBE_MAP_NEGATIVE_Z : 0x851A, + MAX_CUBE_MAP_TEXTURE_SIZE : 0x851C, + + /* TextureUnit */ + TEXTURE0 : 0x84C0, + TEXTURE1 : 0x84C1, + TEXTURE2 : 0x84C2, + TEXTURE3 : 0x84C3, + TEXTURE4 : 0x84C4, + TEXTURE5 : 0x84C5, + TEXTURE6 : 0x84C6, + TEXTURE7 : 0x84C7, + TEXTURE8 : 0x84C8, + TEXTURE9 : 0x84C9, + TEXTURE10 : 0x84CA, + TEXTURE11 : 0x84CB, + TEXTURE12 : 0x84CC, + TEXTURE13 : 0x84CD, + TEXTURE14 : 0x84CE, + TEXTURE15 : 0x84CF, + TEXTURE16 : 0x84D0, + TEXTURE17 : 0x84D1, + TEXTURE18 : 0x84D2, + TEXTURE19 : 0x84D3, + TEXTURE20 : 0x84D4, + TEXTURE21 : 0x84D5, + TEXTURE22 : 0x84D6, + TEXTURE23 : 0x84D7, + TEXTURE24 : 0x84D8, + TEXTURE25 : 0x84D9, + TEXTURE26 : 0x84DA, + TEXTURE27 : 0x84DB, + TEXTURE28 : 0x84DC, + TEXTURE29 : 0x84DD, + TEXTURE30 : 0x84DE, + TEXTURE31 : 0x84DF, + ACTIVE_TEXTURE : 0x84E0, + + /* TextureWrapMode */ + REPEAT : 0x2901, + CLAMP_TO_EDGE : 0x812F, + MIRRORED_REPEAT : 0x8370, + + /* Uniform Types */ + FLOAT_VEC2 : 0x8B50, + FLOAT_VEC3 : 0x8B51, + FLOAT_VEC4 : 0x8B52, + INT_VEC2 : 0x8B53, + INT_VEC3 : 0x8B54, + INT_VEC4 : 0x8B55, + BOOL : 0x8B56, + BOOL_VEC2 : 0x8B57, + BOOL_VEC3 : 0x8B58, + BOOL_VEC4 : 0x8B59, + FLOAT_MAT2 : 0x8B5A, + FLOAT_MAT3 : 0x8B5B, + FLOAT_MAT4 : 0x8B5C, + SAMPLER_2D : 0x8B5E, + SAMPLER_CUBE : 0x8B60, + + /* Vertex Arrays */ + VERTEX_ATTRIB_ARRAY_ENABLED : 0x8622, + VERTEX_ATTRIB_ARRAY_SIZE : 0x8623, + VERTEX_ATTRIB_ARRAY_STRIDE : 0x8624, + VERTEX_ATTRIB_ARRAY_TYPE : 0x8625, + VERTEX_ATTRIB_ARRAY_NORMALIZED : 0x886A, + VERTEX_ATTRIB_ARRAY_POINTER : 0x8645, + VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : 0x889F, + + /* Shader Source */ + COMPILE_STATUS : 0x8B81, + + /* Shader Precision-Specified Types */ + LOW_FLOAT : 0x8DF0, + MEDIUM_FLOAT : 0x8DF1, + HIGH_FLOAT : 0x8DF2, + LOW_INT : 0x8DF3, + MEDIUM_INT : 0x8DF4, + HIGH_INT : 0x8DF5, + + /* Framebuffer Object. */ + FRAMEBUFFER : 0x8D40, + RENDERBUFFER : 0x8D41, + + RGBA4 : 0x8056, + RGB5_A1 : 0x8057, + RGB565 : 0x8D62, + DEPTH_COMPONENT16 : 0x81A5, + STENCIL_INDEX : 0x1901, + STENCIL_INDEX8 : 0x8D48, + DEPTH_STENCIL : 0x84F9, + + RENDERBUFFER_WIDTH : 0x8D42, + RENDERBUFFER_HEIGHT : 0x8D43, + RENDERBUFFER_INTERNAL_FORMAT : 0x8D44, + RENDERBUFFER_RED_SIZE : 0x8D50, + RENDERBUFFER_GREEN_SIZE : 0x8D51, + RENDERBUFFER_BLUE_SIZE : 0x8D52, + RENDERBUFFER_ALPHA_SIZE : 0x8D53, + RENDERBUFFER_DEPTH_SIZE : 0x8D54, + RENDERBUFFER_STENCIL_SIZE : 0x8D55, + + FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE : 0x8CD0, + FRAMEBUFFER_ATTACHMENT_OBJECT_NAME : 0x8CD1, + FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL : 0x8CD2, + FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE : 0x8CD3, + + COLOR_ATTACHMENT0 : 0x8CE0, + DEPTH_ATTACHMENT : 0x8D00, + STENCIL_ATTACHMENT : 0x8D20, + DEPTH_STENCIL_ATTACHMENT : 0x821A, + + NONE : 0, + + FRAMEBUFFER_COMPLETE : 0x8CD5, + FRAMEBUFFER_INCOMPLETE_ATTACHMENT : 0x8CD6, + FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : 0x8CD7, + FRAMEBUFFER_INCOMPLETE_DIMENSIONS : 0x8CD9, + FRAMEBUFFER_UNSUPPORTED : 0x8CDD, + + FRAMEBUFFER_BINDING : 0x8CA6, + RENDERBUFFER_BINDING : 0x8CA7, + MAX_RENDERBUFFER_SIZE : 0x84E8, + + INVALID_FRAMEBUFFER_OPERATION : 0x0506, + + /* WebGL-specific enums */ + UNPACK_FLIP_Y_WEBGL : 0x9240, + UNPACK_PREMULTIPLY_ALPHA_WEBGL : 0x9241, + CONTEXT_LOST_WEBGL : 0x9242, + UNPACK_COLORSPACE_CONVERSION_WEBGL : 0x9243, + BROWSER_DEFAULT_WEBGL : 0x9244, + + items: {}, + id: 0, + getExtension: function() { return 1 }, + createBuffer: function() { + var id = this.id++; + this.items[id] = { + which: 'buffer', + }; + return id; + }, + deleteBuffer: function(){}, + bindBuffer: function(){}, + bufferData: function(){}, + getParameter: function(pname) { + switch(pname) { + case /* GL_VENDOR */ 0x1F00: return 'FakeShellGLVendor'; + case /* GL_RENDERER */ 0x1F01: return 'FakeShellGLRenderer'; + case /* GL_VERSION */ 0x1F02: return '0.0.1'; + case /* GL_MAX_TEXTURE_SIZE */ 0x0D33: return 16384; + case /* GL_MAX_CUBE_MAP_TEXTURE_SIZE */ 0x851C: return 16384; + case /* GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT */ 0x84FF: return 16; + case /* GL_MAX_TEXTURE_IMAGE_UNITS_NV */ 0x8872: return 16; + case /* GL_MAX_VERTEX_UNIFORM_VECTORS */ 0x8DFB: return 4096; + case /* GL_MAX_FRAGMENT_UNIFORM_VECTORS */ 0x8DFD: return 4096; + case /* GL_MAX_VARYING_VECTORS */ 0x8DFC: return 32; + case /* GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS */ 0x8B4D: return 32; + default: throw 'getParameter ' + pname; + } + }, + getSupportedExtensions: function() { + return ["OES_texture_float", "OES_standard_derivatives", "EXT_texture_filter_anisotropic", "MOZ_EXT_texture_filter_anisotropic", "MOZ_WEBGL_lose_context", "MOZ_WEBGL_compressed_texture_s3tc", "MOZ_WEBGL_depth_texture"]; + }, + createShader: function(type) { + var id = this.id++; + this.items[id] = { + which: 'shader', + type: type, + }; + return id; + }, + getShaderParameter: function(shader, pname) { + switch(pname) { + case /* GL_SHADER_TYPE */ 0x8B4F: return this.items[shader].type; + case /* GL_COMPILE_STATUS */ 0x8B81: return true; + default: throw 'getShaderParameter ' + pname; + } + }, + shaderSource: function(){}, + compileShader: function(){}, + createProgram: function() { + var id = this.id++; + this.items[id] = { + which: 'program', + shaders: [], + }; + return id; + }, + attachShader: function(program, shader) { + this.items[program].shaders.push(shader); + }, + bindAttribLocation: function(){}, + linkProgram: function(){}, + getProgramParameter: function(program, pname) { + switch(pname) { + case /* LINK_STATUS */ 0x8B82: return true; + case /* ACTIVE_UNIFORMS */ 0x8B86: return 4; + default: throw 'getProgramParameter ' + pname; + } + }, + deleteShader: function(){}, + deleteProgram: function(){}, + viewport: function(){}, + clearColor: function(){}, + clearDepth: function(){}, + depthFunc: function(){}, + enable: function(){}, + disable: function(){}, + frontFace: function(){}, + cullFace: function(){}, + activeTexture: function(){}, + createTexture: function() { + var id = this.id++; + this.items[id] = { + which: 'texture', + }; + return id; + }, + deleteTexture: function(){}, + boundTextures: {}, + bindTexture: function(target, texture) { + this.boundTextures[target] = texture; + }, + texParameteri: function(){}, + pixelStorei: function(){}, + texImage2D: function(){}, + compressedTexImage2D: function(){}, + useProgram: function(){}, + getUniformLocation: function() { + return null; + }, + getActiveUniform: function(program, index) { + return { + size: 1, + type: /* INT_VEC3 */ 0x8B54, + name: 'activeUniform' + index, + }; + }, + clear: function(){}, + uniform4fv: function(){}, + uniform1i: function(){}, + getAttribLocation: function() { return 1 }, + vertexAttribPointer: function(){}, + enableVertexAttribArray: function(){}, + disableVertexAttribArray: function(){}, + drawElements: function(){}, + drawArrays: function(){}, + depthMask: function(){}, + depthRange: function(){}, + bufferSubData: function(){}, + blendFunc: function(){}, + createFramebuffer: function() { + var id = this.id++; + this.items[id] = { + which: 'framebuffer', + shaders: [], + }; + return id; + }, + bindFramebuffer: function(){}, + framebufferTexture2D: function(){}, + checkFramebufferStatus: function() { + return /* FRAMEBUFFER_COMPLETE */ 0x8CD5; + }, + createRenderbuffer: function() { + var id = this.id++; + this.items[id] = { + which: 'renderbuffer', + shaders: [], + }; + return id; + }, + bindRenderbuffer: function(){}, + renderbufferStorage: function(){}, + framebufferRenderbuffer: function(){}, + scissor: function(){}, + colorMask: function(){}, + lineWidth: function(){}, + }; + } + case '2d': { + return { + drawImage: function(){}, + getImageData: function(x, y, w, h) { + return { + width: w, + height: h, + data: new Uint8ClampedArray(w*h), + }; + }, + }; + } + default: throw 'canvas.getContext: ' + which; + } + }, + requestPointerLock: function() { + document.pointerLockElement = document.getElementById('canvas'); + window.setTimeout(function() { + document.callEventListeners('pointerlockchange'); + }); + }, + style: {}, + eventListeners: {}, + addEventListener: document.addEventListener, + callEventListeners: document.callEventListeners, + requestFullScreen: function() { + document.fullscreenElement = document.getElementById('canvas'); + window.setTimeout(function() { + document.callEventListeners('fullscreenchange'); + }); + }, + offsetTop: 0, + offsetLeft: 0, + }; + } + case 'status-text': case 'progress': { + return {}; + } + default: throw 'getElementById: ' + id; + } + }, + createElement: function(what) { + switch (what) { + case 'canvas': return document.getElementById(what); + case 'script': { + var ret = {}; + window.setTimeout(function() { + print('loading script: ' + ret.src); + load(ret.src); + print(' script loaded.'); + if (ret.onload) { + window.setTimeout(function() { + ret.onload(); // yeah yeah this might vanish + }); + } + }); + return ret; + } + default: throw 'createElement ' + what; + } + }, + elements: {}, + querySelector: function(id) { + if (!document.elements[id]) { + document.elements[id] = { + classList: { + add: function(){}, + remove: function(){}, + }, + eventListeners: {}, + addEventListener: document.addEventListener, + callEventListeners: document.callEventListeners, + }; + }; + return document.elements[id]; + }, + styleSheets: [{ + cssRules: [], + insertRule: function(){}, + }], + body: { + appendChild: function(){}, + }, +}; +var alert = function(x) { + print(x); +}; +var originalDateNow = Date.now; +var performance = { + now: function() { + return originalDateNow.call(Date); + }, +}; +function fixPath(path) { + if (path[0] == '/') path = path.substring(1); + var dirsToDrop = %d; // go back to root dir if first_js is in a subdir + for (var i = 0; i < dirsToDrop; i++) { + path = '../' + path; + } + return path +} +var XMLHttpRequest = function() { + return { + open: function(mode, path, async) { + path = fixPath(path); + this.mode = mode; + this.path = path; + this.async = async; + }, + send: function() { + if (!this.async) { + this.doSend(); + } else { + var that = this; + window.setTimeout(function() { + that.doSend(); + if (that.onload) that.onload(); + }, 0); + } + }, + doSend: function() { + if (this.responseType == 'arraybuffer') { + this.response = read(this.path, 'binary'); + } else { + this.responseText = read(this.path); + } + }, + }; +}; +var Audio = function() { + return { + play: function(){}, + pause: function(){}, + cloneNode: function() { + return this; + }, + }; +}; +var Image = function() { + var that = this; + window.setTimeout(function() { + that.complete = true; + that.width = 64; + that.height = 64; + if (that.onload) that.onload(); + }); +}; +var Worker = function(workerPath) { + workerPath = fixPath(workerPath); + var workerCode = read(workerPath); + workerCode = workerCode.replace(/Module/g, 'zzModuleyy' + (Worker.id++)). // prevent collision with the global Module object. Note that this becomes global, so we need unique ids + replace(/Date.now/g, 'Recorder.dnow'). // recorded values are just for the "main thread" - workers were not recorded, and should not consume + replace(/performance.now/g, 'Recorder.pnow'). + replace(/Math.random/g, 'Recorder.random'). + replace(/\nonmessage = /, '\nvar onmessage = '); // workers commonly do "onmessage = ", we need to varify that to sandbox + print('loading worker ' + workerPath + ' : ' + workerCode.substring(0, 50)); + eval(workerCode); // will implement onmessage() + + function duplicateJSON(json) { + function handleTypedArrays(key, value) { + if (value && value.toString && value.toString().substring(0, 8) == '[object ' && value.length && value.byteLength) { + return Array.prototype.slice.call(value); + } + return value; + } + return JSON.parse(JSON.stringify(json, handleTypedArrays)) + } + this.terminate = function(){}; + this.postMessage = function(msg) { + msg.messageId = Worker.messageId++; + print('main thread sending message ' + msg.messageId + ' to worker ' + workerPath); + window.setTimeout(function() { + print('worker ' + workerPath + ' receiving message ' + msg.messageId); + onmessage({ data: duplicateJSON(msg) }); + }); + }; + var thisWorker = this; + var postMessage = function(msg) { + msg.messageId = Worker.messageId++; + print('worker ' + workerPath + ' sending message ' + msg.messageId); + window.setTimeout(function() { + print('main thread receiving message ' + msg.messageId + ' from ' + workerPath); + thisWorker.onmessage({ data: duplicateJSON(msg) }); + }); + }; +}; +Worker.id = 0; +Worker.messageId = 0; +var screen = { // XXX these values may need to be adjusted + width: 2100, + height: 1313, + availWidth: 2100, + availHeight: 1283, +}; +var console = { + log: function(x) { + print(x); + }, +}; +var MozBlobBuilder = function() { + this.data = new Uint8Array(0); + this.append = function(buffer) { + var data = new Uint8Array(buffer); + var combined = new Uint8Array(this.data.length + data.length); + combined.set(this.data); + combined.set(data, this.data.length); + this.data = combined; + }; + this.getBlob = function() { + return this.data.buffer; // return the buffer as a "blob". XXX We might need to change this if it is not opaque + }; +}; + diff --git a/tools/shared.py b/tools/shared.py index 2149df0a..ed019999 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -95,7 +95,7 @@ def check_sanity(force=False): print >> sys.stderr, 'FATAL: Node.js (%s) does not seem to work, check the paths in %s' % (NODE_JS, EM_CONFIG) sys.exit(0) - for cmd in [CLANG, LLVM_DIS]: + for cmd in [CLANG, LLVM_LINK, LLVM_AR, LLVM_OPT, LLVM_AS, LLVM_DIS, LLVM_NM]: if not os.path.exists(cmd) and not os.path.exists(cmd + '.exe'): # .exe extension required for Windows print >> sys.stderr, 'FATAL: Cannot find %s, check the paths in %s' % (cmd, EM_CONFIG) sys.exit(0) @@ -122,18 +122,28 @@ def check_sanity(force=False): # Tools/paths +LLVM_ADD_VERSION = os.getenv('LLVM_ADD_VERSION') + +# Some distributions ship with multiple llvm versions so they add +# the version to the binaries, cope with that +def build_llvm_tool_path(tool): + if LLVM_ADD_VERSION: + return os.path.join(LLVM_ROOT, tool + "-" + LLVM_ADD_VERSION) + else: + return os.path.join(LLVM_ROOT, tool) + CLANG_CC=os.path.expanduser(os.path.join(LLVM_ROOT, 'clang')) CLANG_CPP=os.path.expanduser(os.path.join(LLVM_ROOT, 'clang++')) CLANG=CLANG_CPP -LLVM_LINK=os.path.join(LLVM_ROOT, 'llvm-link') -LLVM_AR=os.path.join(LLVM_ROOT, 'llvm-ar') -LLVM_OPT=os.path.expanduser(os.path.join(LLVM_ROOT, 'opt')) -LLVM_AS=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-as')) -LLVM_DIS=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-dis')) -LLVM_NM=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-nm')) -LLVM_INTERPRETER=os.path.expanduser(os.path.join(LLVM_ROOT, 'lli')) -LLVM_COMPILER=os.path.expanduser(os.path.join(LLVM_ROOT, 'llc')) -LLVM_EXTRACT=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-extract')) +LLVM_LINK=build_llvm_tool_path('llvm-link') +LLVM_AR=build_llvm_tool_path('llvm-ar') +LLVM_OPT=os.path.expanduser(build_llvm_tool_path('opt')) +LLVM_AS=os.path.expanduser(build_llvm_tool_path('llvm-as')) +LLVM_DIS=os.path.expanduser(build_llvm_tool_path('llvm-dis')) +LLVM_NM=os.path.expanduser(build_llvm_tool_path('llvm-nm')) +LLVM_INTERPRETER=os.path.expanduser(build_llvm_tool_path('lli')) +LLVM_COMPILER=os.path.expanduser(build_llvm_tool_path('llc')) +LLVM_EXTRACT=os.path.expanduser(build_llvm_tool_path('llvm-extract')) COFFEESCRIPT = path_from_root('tools', 'eliminator', 'node_modules', 'coffee-script', 'bin', 'coffee') EMSCRIPTEN = path_from_root('emscripten.py') @@ -169,11 +179,11 @@ if os.environ.get('EMCC_DEBUG'): EMSCRIPTEN_TEMP_DIR = CANONICAL_TEMP_DIR if not os.path.exists(EMSCRIPTEN_TEMP_DIR): os.makedirs(EMSCRIPTEN_TEMP_DIR) - except: - print >> sys.stderr, 'Could not create canonical temp dir. Check definition of TEMP_DIR in ~/.emscripten' + except Exception, e: + print >> sys.stderr, e, 'Could not create canonical temp dir. Check definition of TEMP_DIR in ~/.emscripten' if not EMSCRIPTEN_TEMP_DIR: - EMSCRIPTEN_TEMP_DIR = tempfile.mkdtemp(prefix='emscripten_temp_') + EMSCRIPTEN_TEMP_DIR = tempfile.mkdtemp(prefix='emscripten_temp_', dir=TEMP_DIR) def clean_temp(): try_delete(EMSCRIPTEN_TEMP_DIR) atexit.register(clean_temp) @@ -483,7 +493,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e .replace('$EMSCRIPTEN_ROOT', path_from_root('').replace('\\', '/')) \ .replace('$CFLAGS', env['CFLAGS']) \ .replace('$CXXFLAGS', env['CFLAGS']) - toolchainFile = mkstemp(suffix='.txt')[1] + toolchainFile = mkstemp(suffix='.cmaketoolchain.txt', dir=TEMP_DIR)[1] open(toolchainFile, 'w').write(CMakeToolchain) args.append('-DCMAKE_TOOLCHAIN_FILE=%s' % os.path.abspath(toolchainFile)) return args |