diff options
-rwxr-xr-x | emcc | 23 | ||||
-rw-r--r-- | src/jsifier.js | 1 | ||||
-rw-r--r-- | src/library_browser.js | 180 | ||||
-rw-r--r-- | src/library_gl.js | 19 | ||||
-rw-r--r-- | src/library_glut.js | 7 | ||||
-rw-r--r-- | src/library_sdl.js | 47 | ||||
-rw-r--r-- | src/proxyClient.js | 49 | ||||
-rw-r--r-- | src/proxyWorker.js | 123 | ||||
-rw-r--r-- | src/settings.js | 5 | ||||
-rw-r--r-- | src/webGLClient.js | 234 | ||||
-rw-r--r-- | src/webGLWorker.js | 910 | ||||
-rw-r--r-- | tests/gles2_uniform_arrays.cpp | 29 | ||||
-rw-r--r-- | tests/hello_world_gles_proxy.c | 765 | ||||
-rwxr-xr-x | tests/runner.py | 32 | ||||
-rw-r--r-- | tests/sdlglshader2.c | 168 | ||||
-rw-r--r-- | tests/test_browser.py | 45 |
16 files changed, 2508 insertions, 129 deletions
@@ -503,8 +503,13 @@ Options that are modified or new in %s include: to hide these warnings and acknowledge that the explicit use of absolute paths is intentional. - --proxy-to-worker Generates both html and js files. The main - program is in js, and the html proxies to/from it. + --proxy-to-worker Runs the main application code in a worker, proxying + events to it and output from it. + If emitting htmlL, this emits an html and a js file, + with the js to be run in a worker. If emitting + js, the target filename contains the part to be run + on the main thread, while a second js file with + suffix ".worker.js" will contain the worker portion. --emrun Enables the generated output to be aware of the emrun command line tool. This allows stdout, stderr @@ -1832,7 +1837,7 @@ try: js_target = unsuffixed(target) + '.js' base_js_target = os.path.basename(js_target) if proxy_to_worker: - html.write(shell.replace('{{{ SCRIPT }}}', '<script>' + open(shared.path_from_root('src', 'proxyClient.js')).read().replace('{{{ filename }}}', target_basename) + '</script>')) + html.write(shell.replace('{{{ SCRIPT }}}', '<script>' + open(shared.path_from_root('src', 'webGLClient.js')).read() + '\n' + open(shared.path_from_root('src', 'proxyClient.js')).read().replace('{{{ filename }}}', shared.Settings.PROXY_TO_WORKER_FILENAME or target_basename) + '</script>')) shutil.move(final, js_target) elif not Compression.on: # Normal code generation path @@ -1977,8 +1982,13 @@ try { split_javascript_file(final, unsuffixed(target), split_js_file) else: if debug_level >= 4: generate_source_map(target) - # copy final JS to output - shutil.move(final, target) + if proxy_to_worker: + worker_target_basename = target_basename + '.worker' + open(target, 'w').write(open(shared.path_from_root('src', 'webGLClient.js')).read() + '\n' + open(shared.path_from_root('src', 'proxyClient.js')).read().replace('{{{ filename }}}', shared.Settings.PROXY_TO_WORKER_FILENAME or worker_target_basename)) + shutil.move(final, target[:-3] + '.worker.js') + else: + # copy final JS to output normally + shutil.move(final, target) log_time('final emitting') @@ -1993,6 +2003,3 @@ finally: else: logging.info('emcc saved files are in:' + temp_dir) -#//eliminate a = a in js opt. will kill STACKTOP = STACKTOP in funcs that do not use the C stack! add test for no STACKTOP or sp in such a func -#// minify if into ?: ? - diff --git a/src/jsifier.js b/src/jsifier.js index d1001dac..1f6440dd 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1899,6 +1899,7 @@ function JSify(data, functionsOnly) { print('}'); } if (PROXY_TO_WORKER) { + print(read('webGLWorker.js')); print(read('proxyWorker.js')); } if (DETERMINISTIC) { diff --git a/src/library_browser.js b/src/library_browser.js index 1740eaed..a3e68209 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -41,6 +41,26 @@ mergeInto(LibraryManager.library, { Module['setStatus'](''); } } + }, + runIter: function(func) { + if (ABORT) return; + if (Module['preMainLoop']) { + var preRet = Module['preMainLoop'](); + if (preRet === false) { + return; // |return false| skips a frame + } + } + try { + func(); + } catch (e) { + if (e instanceof ExitStatus) { + return; + } else { + if (e && typeof e === 'object' && e.stack) Module.printErr('exception thrown: ' + [e, e.stack]); + throw e; + } + } + if (Module['postMainLoop']) Module['postMainLoop'](); } }, isFullScreen: false, @@ -51,7 +71,7 @@ mergeInto(LibraryManager.library, { init: function() { if (!Module["preloadPlugins"]) Module["preloadPlugins"] = []; // needs to exist even in workers - if (Browser.initted || ENVIRONMENT_IS_WORKER) return; + if (Browser.initted || ENVIRONMENT_IS_WORKER) return; // workers do not support Image and Audio elements Browser.initted = true; try { @@ -242,6 +262,8 @@ mergeInto(LibraryManager.library, { return null; } #endif + if (useWebGL && Module.ctx) return Module.ctx; // no need to recreate singleton GL context + var ctx; var errorInfo = '?'; function onContextCreationError(event) { @@ -282,49 +304,98 @@ mergeInto(LibraryManager.library, { } if (useWebGL) { #if GL_DEBUG - // Useful to debug native webgl apps: var Module = { printErr: function(x) { console.log(x) } }; - var tempCtx = ctx; - var wrapper = {}; - for (var prop in tempCtx) { - (function(prop) { - switch (typeof tempCtx[prop]) { - case 'function': { - wrapper[prop] = function gl_wrapper() { - if (GL.debug) { - var printArgs = Array.prototype.slice.call(arguments).map(Runtime.prettyPrint); - Module.printErr('[gl_f:' + prop + ':' + printArgs + ']'); - } - var ret = tempCtx[prop].apply(tempCtx, arguments); - if (GL.debug && typeof ret != 'undefined') { - Module.printErr('[ gl:' + prop + ':return:' + Runtime.prettyPrint(ret) + ']'); - } - return ret; - } - break; + function wrapDebugGL(ctx) { + + var printObjectList = []; + + function prettyPrint(arg) { + if (typeof arg == 'undefined') return '!UNDEFINED!'; + if (typeof arg == 'boolean') arg = arg + 0; + if (!arg) return arg; + var index = printObjectList.indexOf(arg); + if (index >= 0) return '<' + arg + '|'; // + index + '>'; + if (arg.toString() == '[object HTMLImageElement]') { + return arg + '\n\n'; + } + if (arg.byteLength) { + return '{' + Array.prototype.slice.call(arg, 0, Math.min(arg.length, 400)) + '}'; // Useful for correct arrays, less so for compiled arrays, see the code below for that + var buf = new ArrayBuffer(32); + var i8buf = new Int8Array(buf); + var i16buf = new Int16Array(buf); + var f32buf = new Float32Array(buf); + switch(arg.toString()) { + case '[object Uint8Array]': + i8buf.set(arg.subarray(0, 32)); + break; + case '[object Float32Array]': + f32buf.set(arg.subarray(0, 5)); + break; + case '[object Uint16Array]': + i16buf.set(arg.subarray(0, 16)); + break; + default: + alert('unknown array for debugging: ' + arg); + throw 'see alert'; } - case 'number': case 'string': { - wrapper.__defineGetter__(prop, function() { - //Module.printErr('[gl_g:' + prop + ':' + tempCtx[prop] + ']'); - return tempCtx[prop]; - }); - wrapper.__defineSetter__(prop, function(value) { - if (GL.debug) { - Module.printErr('[gl_s:' + prop + ':' + value + ']'); + var ret = '{' + arg.byteLength + ':\n'; + var arr = Array.prototype.slice.call(i8buf); + ret += 'i8:' + arr.toString().replace(/,/g, ',') + '\n'; + arr = Array.prototype.slice.call(f32buf, 0, 8); + ret += 'f32:' + arr.toString().replace(/,/g, ',') + '}'; + return ret; + } + if (typeof arg == 'object') { + printObjectList.push(arg); + return '<' + arg + '|'; // + (printObjectList.length-1) + '>'; + } + if (typeof arg == 'number') { + if (arg > 0) return '0x' + arg.toString(16) + ' (' + arg + ')'; + } + return arg; + } + + var wrapper = {}; + for (var prop in ctx) { + (function(prop) { + switch (typeof ctx[prop]) { + case 'function': { + wrapper[prop] = function gl_wrapper() { + var printArgs = Array.prototype.slice.call(arguments).map(prettyPrint); + dump('[gl_f:' + prop + ':' + printArgs + ']\n'); + var ret = ctx[prop].apply(ctx, arguments); + if (typeof ret != 'undefined') { + dump('[ gl:' + prop + ':return:' + prettyPrint(ret) + ']\n'); + } + return ret; } - tempCtx[prop] = value; - }); - break; + break; + } + case 'number': case 'string': { + wrapper.__defineGetter__(prop, function() { + //dump('[gl_g:' + prop + ':' + ctx[prop] + ']\n'); + return ctx[prop]; + }); + wrapper.__defineSetter__(prop, function(value) { + dump('[gl_s:' + prop + ':' + value + ']\n'); + ctx[prop] = value; + }); + break; + } } - } - })(prop); + })(prop); + } + return wrapper; } - ctx = wrapper; #endif + // possible GL_DEBUG entry point: ctx = wrapDebugGL(ctx); + // Set the background of the WebGL canvas to black canvas.style.backgroundColor = "black"; } if (setInModule) { - GLctx = Module.ctx = ctx; + if (!useWebGL) assert(typeof GLctx === 'undefined', 'cannot set in module if GLctx is used, but we are a non-GL context that would replace it'); + Module.ctx = ctx; + if (useWebGL) GLctx = ctx; Module.useWebGL = useWebGL; Browser.moduleContextCreatedCallbacks.forEach(function(callback) { callback() }); Browser.init(); @@ -395,9 +466,25 @@ mergeInto(LibraryManager.library, { canvasContainer.requestFullScreen(); }, + nextRAF: 0, + + fakeRequestAnimationFrame: function(func) { + // try to keep 60fps between calls to here + var now = Date.now(); + if (Browser.nextRAF === 0) { + Browser.nextRAF = now + 1000/60; + } else { + while (now + 2 >= Browser.nextRAF) { // fudge a little, to avoid timer jitter causing us to do lots of delay:0 + Browser.nextRAF += 1000/60; + } + } + var delay = Math.max(Browser.nextRAF - now, 0); + setTimeout(func, delay); + }, + requestAnimationFrame: function requestAnimationFrame(func) { if (typeof window === 'undefined') { // Provide fallback to setTimeout if window is undefined (e.g. in Node.js) - setTimeout(func, 1000/60); + Browser.fakeRequestAnimationFrame(func); } else { if (!window.requestAnimationFrame) { window.requestAnimationFrame = window['requestAnimationFrame'] || @@ -405,7 +492,7 @@ mergeInto(LibraryManager.library, { window['webkitRequestAnimationFrame'] || window['msRequestAnimationFrame'] || window['oRequestAnimationFrame'] || - window['setTimeout']; + Browser.fakeRequestAnimationFrame; } window.requestAnimationFrame(func); } @@ -954,28 +1041,13 @@ mergeInto(LibraryManager.library, { Browser.mainLoop.method = ''; // just warn once per call to set main loop } - if (Module['preMainLoop']) { - Module['preMainLoop'](); - } - - try { + Browser.mainLoop.runIter(function() { if (typeof arg !== 'undefined') { Runtime.dynCall('vi', func, [arg]); } else { Runtime.dynCall('v', func); } - } catch (e) { - if (e instanceof ExitStatus) { - return; - } else { - if (e && typeof e === 'object' && e.stack) Module.printErr('exception thrown: ' + [e, e.stack]); - throw e; - } - } - - if (Module['postMainLoop']) { - Module['postMainLoop'](); - } + }); if (Browser.mainLoop.shouldPause) { // catch pauses from the main loop itself diff --git a/src/library_gl.js b/src/library_gl.js index 2659a9d9..e67ec29b 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -833,6 +833,7 @@ var LibraryGL = { for (var i = 0; i < n; i++) { var id = {{{ makeGetValue('textures', 'i*4', 'i32') }}}; var texture = GL.textures[id]; + if (!texture) continue; GLctx.deleteTexture(texture); texture.name = 0; GL.textures[id] = null; @@ -1316,7 +1317,7 @@ var LibraryGL = { #endif location = GL.uniforms[location]; var view; - if (count == 1) { + if (count === 1) { // avoid allocation for the common case of uploading one uniform view = GL.miniTempBufferViews[0]; view[0] = {{{ makeGetValue('value', '0', 'float') }}}; @@ -1333,7 +1334,7 @@ var LibraryGL = { #endif location = GL.uniforms[location]; var view; - if (count == 1) { + if (count === 1) { // avoid allocation for the common case of uploading one uniform view = GL.miniTempBufferViews[1]; view[0] = {{{ makeGetValue('value', '0', 'float') }}}; @@ -1351,7 +1352,7 @@ var LibraryGL = { #endif location = GL.uniforms[location]; var view; - if (count == 1) { + if (count === 1) { // avoid allocation for the common case of uploading one uniform view = GL.miniTempBufferViews[2]; view[0] = {{{ makeGetValue('value', '0', 'float') }}}; @@ -1370,7 +1371,7 @@ var LibraryGL = { #endif location = GL.uniforms[location]; var view; - if (count == 1) { + if (count === 1) { // avoid allocation for the common case of uploading one uniform view = GL.miniTempBufferViews[3]; view[0] = {{{ makeGetValue('value', '0', 'float') }}}; @@ -1390,7 +1391,7 @@ var LibraryGL = { #endif location = GL.uniforms[location]; var view; - if (count == 1) { + if (count === 1) { // avoid allocation for the common case of uploading one uniform matrix view = GL.miniTempBufferViews[3]; for (var i = 0; i < 4; i++) { @@ -1409,7 +1410,7 @@ var LibraryGL = { #endif location = GL.uniforms[location]; var view; - if (count == 1) { + if (count === 1) { // avoid allocation for the common case of uploading one uniform matrix view = GL.miniTempBufferViews[8]; for (var i = 0; i < 9; i++) { @@ -1428,7 +1429,7 @@ var LibraryGL = { #endif location = GL.uniforms[location]; var view; - if (count == 1) { + if (count === 1) { // avoid allocation for the common case of uploading one uniform matrix view = GL.miniTempBufferViews[15]; for (var i = 0; i < 16; i++) { @@ -4347,6 +4348,10 @@ var LibraryGL = { } break; } + case 0x84FF: { // GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + {{{ makeSetValue('params', '0', '0', 'i32') }}}; // no valid value to return here + return; + } } glGetIntegerv(pname, params); }; diff --git a/src/library_glut.js b/src/library_glut.js index 445e08a4..d6293d85 100644 --- a/src/library_glut.js +++ b/src/library_glut.js @@ -383,7 +383,7 @@ var LibraryGLUT = { function callback() { if (GLUT.idleFunc) { Runtime.dynCall('v', GLUT.idleFunc); - Browser.safeSetTimeout(callback, 0); + Browser.safeSetTimeout(callback, 4); // HTML spec specifies a 4ms minimum delay on the main thread; workers might get more, but we standardize here } } if (!GLUT.idleFunc) { @@ -489,8 +489,9 @@ var LibraryGLUT = { GLUT.requestedAnimationFrame = true; Browser.requestAnimationFrame(function() { GLUT.requestedAnimationFrame = false; - if (ABORT) return; - Runtime.dynCall('v', GLUT.displayFunc); + Browser.mainLoop.runIter(function() { + Runtime.dynCall('v', GLUT.displayFunc); + }); }); } }, diff --git a/src/library_sdl.js b/src/library_sdl.js index 07a618a3..eaa2b5bb 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -1314,20 +1314,14 @@ var LibrarySDL = { Module['canvas'].addEventListener(event, SDL.receiveEvent, true); }); + var canvas = Module['canvas']; + // (0,0) means 'use fullscreen' in native; in Emscripten, use the current canvas size. if (width == 0 && height == 0) { - var canvas = Module['canvas']; width = canvas.width; height = canvas.height; } - Browser.setCanvasSize(width, height, true); - // Free the old surface first. - if (SDL.screen) { - SDL.freeSurface(SDL.screen); - assert(!SDL.screen); - } - SDL.screen = SDL.makeSurface(width, height, flags, true, 'screen'); if (!SDL.addedResizeListener) { SDL.addedResizeListener = true; Browser.resizeListeners.push(function(w, h) { @@ -1338,6 +1332,21 @@ var LibrarySDL = { }); }); } + + if (width !== canvas.width || height !== canvas.height) { + Browser.setCanvasSize(width, height); + } + + // Free the old surface first if there is one + if (SDL.screen) { + SDL.freeSurface(SDL.screen); + assert(!SDL.screen); + } + + if (SDL.GL) flags = flags | 0x04000000; // SDL_OPENGL - if we are using GL, then later calls to SetVideoMode may not mention GL, but we do need it. Once in GL mode, we never leave it. + + SDL.screen = SDL.makeSurface(width, height, flags, true, 'screen'); + return SDL.screen; }, @@ -1946,8 +1955,10 @@ var LibrarySDL = { } else { var imageData = surfData.ctx.getImageData(0, 0, surfData.width, surfData.height); if (raw.bpp == 4) { + // rgba imageData.data.set({{{ makeHEAPView('U8', 'raw.data', 'raw.data+raw.size') }}}); } else if (raw.bpp == 3) { + // rgb var pixels = raw.size/3; var data = imageData.data; var sourcePtr = raw.data; @@ -1958,6 +1969,19 @@ var LibrarySDL = { data[destPtr++] = {{{ makeGetValue('sourcePtr++', 0, 'i8', null, 1) }}}; data[destPtr++] = 255; } + } else if (raw.bpp == 1) { + // grayscale + var pixels = raw.size; + var data = imageData.data; + var sourcePtr = raw.data; + var destPtr = 0; + for (var i = 0; i < pixels; i++) { + var value = {{{ makeGetValue('sourcePtr++', 0, 'i8', null, 1) }}}; + data[destPtr++] = value; + data[destPtr++] = value; + data[destPtr++] = value; + data[destPtr++] = 255; + } } else { Module.printErr('cannot handle bpp ' + raw.bpp); return 0; @@ -2883,7 +2907,12 @@ var LibrarySDL = { return _emscripten_GetProcAddress(name_); }, - SDL_GL_SwapBuffers: function() {}, + SDL_GL_SwapBuffers: function() { +#if PROXY_TO_WORKER + // postMainLoop is where the proxy code listens, to know when to proxy buffered render commands + if (Module['postMainLoop']) Module['postMainLoop'](); +#endif + }, // SDL 2 diff --git a/src/proxyClient.js b/src/proxyClient.js index 2d1c76fe..94d8161a 100644 --- a/src/proxyClient.js +++ b/src/proxyClient.js @@ -1,8 +1,6 @@ // proxy to/from worker -Module.ctx = Module.canvas.getContext('2d'); - // render var renderFrameData = null; @@ -24,13 +22,41 @@ window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequest window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || renderFrame; +/* +var trueRAF = window.requestAnimationFrame; +var lastRAF = 0; +var meanFPS = 0; +window.requestAnimationFrame = function(func) { + trueRAF(function() { + var now = performance.now(); + if (lastRAF > 0) { + var diff = now - lastRAF; + var fps = 1000/diff; + meanFPS = 0.95*meanFPS + 0.05*fps; + dump('client fps ' + meanFPS + '\n'); + } + lastRAF = now; + func(); + }); +} +*/ + // end render +// Frame throttling + +var frameId = 0; + +// Worker + var worker = new Worker('{{{ filename }}}.js'); +WebGLClient.prefetch(); // XXX not guaranteed to be before worker main() + var workerResponded = false; worker.onmessage = function worker_onmessage(event) { + //dump('\nclient got ' + JSON.stringify(event.data).substr(0, 150) + '\n'); if (!workerResponded) { workerResponded = true; if (Module.setStatus) Module.setStatus(''); @@ -52,10 +78,18 @@ worker.onmessage = function worker_onmessage(event) { } case 'canvas': { switch (data.op) { + case 'getContext': { + Module.ctx = Module.canvas.getContext(data.type, data.attributes); + if (data.type !== '2d') { + // possible GL_DEBUG entry point: Module.ctx = wrapDebugGL(Module.ctx); + Module.glClient = new WebGLClient(); + } + break; + } case 'resize': { Module.canvas.width = data.width; Module.canvas.height = data.height; - Module.canvasData = Module.ctx.getImageData(0, 0, data.width, data.height); + if (Module.ctx && Module.ctx.getImageData) Module.canvasData = Module.ctx.getImageData(0, 0, data.width, data.height); worker.postMessage({ target: 'canvas', boundingClientRect: cloneObject(Module.canvas.getBoundingClientRect()) }); break; } @@ -74,6 +108,15 @@ worker.onmessage = function worker_onmessage(event) { } break; } + case 'gl': { + Module.glClient.onmessage(data); + break; + } + case 'tick': { + frameId = data.id; + worker.postMessage({ target: 'tock', id: frameId }); + break; + } default: throw 'what?'; } }; diff --git a/src/proxyWorker.js b/src/proxyWorker.js index cfe0c26e..4c397cbb 100644 --- a/src/proxyWorker.js +++ b/src/proxyWorker.js @@ -1,3 +1,7 @@ +function PropertyBag() { + this.addProperty = function(){}; + this.removeProperty = function(){}; +}; function EventListener() { this.listeners = {}; @@ -7,6 +11,14 @@ function EventListener() { this.listeners[event].push(func); }; + this.removeEventListener = function(event, func) { + var list = this.listeners[event]; + if (!list) return; + var me = list.indexOf(func); + if (me < 0) return; + list.splice(me, 1); + }; + this.fireEvent = function fireEvent(event) { event.preventDefault = function(){}; @@ -26,6 +38,10 @@ window.close = function window_close() { postMessage({ target: 'window', method: 'close' }); }; +window.scrollX = window.scrollY = 0; // TODO: proxy these + +var webGLWorker = new WebGLWorker(); + var document = new EventListener(); document.createElement = function document_createElement(what) { @@ -39,28 +55,38 @@ document.createElement = function document_createElement(what) { height: canvas.height, data: new Uint8Array(canvas.width*canvas.height*4) }; - postMessage({ target: 'canvas', op: 'resize', width: canvas.width, height: canvas.height }); + if (canvas === Module['canvas']) { + postMessage({ target: 'canvas', op: 'resize', width: canvas.width, height: canvas.height }); + } } }; - canvas.getContext = function canvas_getContext(type) { - assert(type == '2d'); - return { - getImageData: function(x, y, w, h) { - assert(x == 0 && y == 0 && w == canvas.width && h == canvas.height); - canvas.ensureData(); - return { - width: canvas.data.width, - height: canvas.data.height, - data: new Uint8Array(canvas.data.data) // TODO: can we avoid this copy? - }; - }, - putImageData: function(image, x, y) { - canvas.ensureData(); - assert(x == 0 && y == 0 && image.width == canvas.width && image.height == canvas.height); - canvas.data.data.set(image.data); // TODO: can we avoid this copy? - postMessage({ target: 'canvas', op: 'render', image: canvas.data }); - } - }; + canvas.getContext = function canvas_getContext(type, attributes) { + if (canvas === Module['canvas']) { + postMessage({ target: 'canvas', op: 'getContext', type: type, attributes: attributes }); + } + if (type === '2d') { + return { + getImageData: function(x, y, w, h) { + assert(x == 0 && y == 0 && w == canvas.width && h == canvas.height); + canvas.ensureData(); + return { + width: canvas.data.width, + height: canvas.data.height, + data: new Uint8Array(canvas.data.data) // TODO: can we avoid this copy? + }; + }, + putImageData: function(image, x, y) { + canvas.ensureData(); + assert(x == 0 && y == 0 && image.width == canvas.width && image.height == canvas.height); + canvas.data.data.set(image.data); // TODO: can we avoid this copy? + if (canvas === Module['canvas']) { + postMessage({ target: 'canvas', op: 'render', image: canvas.data }); + } + } + }; + } else { + return webGLWorker; + } }; canvas.boundingClientRect = {}; canvas.getBoundingClientRect = function canvas_getBoundingClientRect() { @@ -73,17 +99,42 @@ document.createElement = function document_createElement(what) { right: canvas.boundingClientRect.right }; }; + canvas.style = new PropertyBag(); + canvas.exitPointerLock = function(){}; return canvas; } default: throw 'document.createElement ' + what; } }; +document.documentElement = {}; + +document.styleSheets = [{ + cssRules: [], // TODO: forward to client + insertRule: function(rule, i) { + this.cssRules.splice(i, 0, rule); + } +}]; + +function Audio() { + Runtime.warnOnce('faking Audio elements, no actual sound will play'); +} + +Audio.prototype.play = function(){}; +Audio.prototype.pause = function(){}; + +Audio.prototype.cloneNode = function() { + return new Audio; +} + if (typeof console === 'undefined') { var console = { log: function(x) { Module.printErr(x); - } + }, + error: function(x) { + Module.printErr(x); + }, }; } @@ -92,12 +143,33 @@ Module.canvas = document.createElement('canvas'); Module.setStatus = function(){}; Module.print = function Module_print(x) { + //dump('OUT: ' + x + '\n'); postMessage({ target: 'stdout', content: x }); }; Module.printErr = function Module_printErr(x) { + //dump('ERR: ' + x + '\n'); postMessage({ target: 'stderr', content: x }); }; +// Browser hooks + +Browser.resizeListeners.push(function(width, height) { + postMessage({ target: 'canvas', op: 'resize', width: width, height: height }); +}); + +// Frame throttling + +var frameId = 0; +var clientFrameId = 0; + +var postMainLoop = Module['postMainLoop']; +Module['postMainLoop'] = function() { + if (postMainLoop) postMainLoop(); + // frame complete, send a frame id + postMessage({ target: 'tick', id: frameId++ }); + commandBuffer = []; +}; + // buffer messages until the program starts to run var messageBuffer = null; @@ -122,6 +194,7 @@ onmessage = function onmessage(message) { } messageBuffer.push(message); } + //dump('worker got ' + JSON.stringify(message.data) + '\n'); switch (message.data.target) { case 'document': { document.fireEvent(message.data.event); @@ -139,6 +212,14 @@ onmessage = function onmessage(message) { } else throw 'ey?'; break; } + case 'gl': { + webGLWorker.onmessage(message.data); + break; + } + case 'tock': { + clientFrameId = message.data.id; + break; + } default: throw 'wha? ' + message.data.target; } }; diff --git a/src/settings.js b/src/settings.js index 26883051..14077efd 100644 --- a/src/settings.js +++ b/src/settings.js @@ -422,9 +422,14 @@ var RUNTIME_LINKED_LIBS = []; // If this is a main file (BUILD_AS_SHARED_LIB == // linked libraries can break things. var BUILD_AS_WORKER = 0; // If set to 1, this is a worker library, a special kind of library // that is run in a worker. See emscripten.h + var PROXY_TO_WORKER = 0; // If set to 1, we build the project into a js file that will run // in a worker, and generate an html file that proxies input and // output to/from it. +var PROXY_TO_WORKER_FILENAME = ''; // If set, the script file name the main thread loads. + // Useful if your project doesn't run the main emscripten- + // generated script immediately but does some setup before + var LINKABLE = 0; // If set to 1, this file can be linked with others, either as a shared // library or as the main file that calls a shared library. To enable that, // we will not internalize all symbols and cull the unused ones, in other diff --git a/src/webGLClient.js b/src/webGLClient.js new file mode 100644 index 00000000..397cfa8c --- /dev/null +++ b/src/webGLClient.js @@ -0,0 +1,234 @@ +// WebGLWorker client code + +function assert(x) { + if (!x) throw 'failed assert'; +} + +function WebGLClient() { + var objects = {}; + + var ctx = null; + var buffer = null; + var i = 0; + + function func0(name) { + ctx[name](); + } + function func1(name) { + ctx[name](buffer[i]); + i++; + } + function func2(name) { + ctx[name](buffer[i], buffer[i+1]); + i += 2; + } + function func3(name) { + ctx[name](buffer[i], buffer[i+1], buffer[i+2]); + i += 3; + } + function func4(name) { + ctx[name](buffer[i], buffer[i+1], buffer[i+2], buffer[i+3]); + i += 4; + } + function func5(name) { + ctx[name](buffer[i], buffer[i+1], buffer[i+2], buffer[i+3], buffer[i+4]); + i += 5; + } + function func6(name) { + ctx[name](buffer[i], buffer[i+1], buffer[i+2], buffer[i+3], buffer[i+4], buffer[i+5]); + i += 6; + } + function func7(name) { + ctx[name](buffer[i], buffer[i+1], buffer[i+2], buffer[i+3], buffer[i+4], buffer[i+5], buffer[i+6]); + i += 7; + } + function func9(name) { + ctx[name](buffer[i], buffer[i+1], buffer[i+2], buffer[i+3], buffer[i+4], buffer[i+5], buffer[i+6], buffer[i+7], buffer[i+8]); + i += 9; + } + + // lookuppers, convert integer ids to cached objects for some args + function func1L0(name) { + ctx[name](objects[buffer[i]]); + i++; + } + function func2L0(name) { + ctx[name](objects[buffer[i]], buffer[i+1]); + i += 2; + } + function func2L0L1(name) { + ctx[name](objects[buffer[i]], objects[buffer[i+1]]); + i += 2; + } + function func2L1_(name) { + ctx[name](buffer[i], buffer[i+1] ? objects[buffer[i+1]] : null); + i += 2; + } + function func3L0(name) { + ctx[name](objects[buffer[i]], buffer[i+1], buffer[i+2]); + i += 3; + } + function func4L3_(name) { + ctx[name](buffer[i], buffer[i+1], buffer[i+2], buffer[i+3] ? objects[buffer[i+3]] : null); + i += 4; + } + function func5L3_(name) { + ctx[name](buffer[i], buffer[i+1], buffer[i+2], buffer[i+3] ? objects[buffer[i+3]] : null, buffer[i+4]); + i += 5; + } + + // constructors, last argument is the id to save as + function funcC0(name) { + var object = ctx[name](); + var id = buffer[i++]; + objects[id] = object; + } + function funcC1(name) { + var object = ctx[name](buffer[i++]); + var id = buffer[i++]; + objects[id] = object; + } + function funcC2(name) { + var object = ctx[name](buffer[i++], buffer[i++]); + var id = buffer[i++]; + objects[id] = object; + } + function funcC2L0(name) { + var object = ctx[name](objects[buffer[i++]], buffer[i++]); + var id = buffer[i++]; + objects[id] = object; + } + + // destructors, stop holding on to the object in the cache + function funcD0(name) { + var id = buffer[i++]; + var object = objects[id]; + objects[id] = null; + ctx[name](object); + } + + var calls = { + 0: { name: 'NULL', func: func0 }, + 1: { name: 'getExtension', func: func1 }, + 2: { name: 'enable', func: func1 }, + 3: { name: 'disable', func: func1 }, + 4: { name: 'clear', func: func1 }, + 5: { name: 'clearColor', func: func4 }, + 6: { name: 'createShader', func: funcC1 }, + 7: { name: 'deleteShader', func: funcD0 }, + 8: { name: 'shaderSource', func: func2L0 }, + 9: { name: 'compileShader', func: func1L0 }, + 10: { name: 'createProgram', func: funcC0 }, + 11: { name: 'deleteProgram', func: funcD0 }, + 12: { name: 'attachShader', func: func2L0L1 }, + 13: { name: 'bindAttribLocation', func: func3L0 }, + 14: { name: 'linkProgram', func: func1L0 }, + 15: { name: 'getProgramParameter', func: function() { assert(ctx.getProgramParameter(objects[buffer[i++]], buffer[i++]), 'we cannot handle errors, we are async proxied WebGL'); } }, + 16: { name: 'getUniformLocation', func: funcC2L0 }, + 17: { name: 'useProgram', func: func1L0 }, + 18: { name: 'uniform1i', func: func2L0 }, + 19: { name: 'uniform1f', func: func2L0 }, + 20: { name: 'uniform3fv', func: func2L0 }, + 21: { name: 'uniform4fv', func: func2L0 }, + 22: { name: 'uniformMatrix4fv', func: func3L0 }, + 23: { name: 'vertexAttrib4fv', func: func2 }, + 24: { name: 'createBuffer', func: funcC0 }, + 25: { name: 'deleteBuffer', func: funcD0 }, + 26: { name: 'bindBuffer', func: func2L1_ }, + 27: { name: 'bufferData', func: func3 }, + 28: { name: 'bufferSubData', func: func3 }, + 29: { name: 'viewport', func: func4 }, + 30: { name: 'vertexAttribPointer', func: func6 }, + 31: { name: 'enableVertexAttribArray', func: func1 }, + 32: { name: 'disableVertexAttribArray', func: func1 }, + 33: { name: 'drawArrays', func: func3 }, + 34: { name: 'drawElements', func: func4 }, + 35: { name: 'getError', func: function() { assert(ctx.getError() === ctx.NO_ERROR, 'we cannot handle errors, we are async proxied WebGL') } }, + 36: { name: 'createTexture', func: funcC0 }, + 37: { name: 'deleteTexture', func: funcD0 }, + 38: { name: 'bindTexture', func: func2L1_ }, + 39: { name: 'texParameteri', func: func3 }, + 40: { name: 'texImage2D', func: func9 }, + 41: { name: 'compressedTexImage2D', func: func7 }, + 42: { name: 'activeTexture', func: func1 }, + 43: { name: 'getShaderParameter', func: function() { assert(ctx.getShaderParameter(objects[buffer[i++]], buffer[i++]), 'we cannot handle errors, we are async proxied WebGL'); } }, + 44: { name: 'clearDepth', func: func1 }, + 45: { name: 'depthFunc', func: func1 }, + 46: { name: 'frontFace', func: func1 }, + 47: { name: 'cullFace', func: func1 }, + 48: { name: 'pixelStorei', func: func2 }, + 49: { name: 'depthMask', func: func1 }, + 50: { name: 'depthRange', func: func2 }, + 51: { name: 'blendFunc', func: func2 }, + 52: { name: 'scissor', func: func4 }, + 53: { name: 'colorMask', func: func4 }, + 54: { name: 'lineWidth', func: func1 }, + 55: { name: 'createFramebuffer', func: funcC0 }, + 56: { name: 'deleteFramebuffer', func: funcD0 }, + 57: { name: 'bindFramebuffer', func: func2L1_ }, + 58: { name: 'framebufferTexture2D', func: func5L3_ }, + 59: { name: 'createRenderbuffer', func: funcC0 }, + 60: { name: 'deleteRenderbuffer', func: funcD0 }, + 61: { name: 'bindRenderbuffer', func: func2L1_ }, + 62: { name: 'renderbufferStorage', func: func4 }, + 63: { name: 'framebufferRenderbuffer', func: func4L3_ }, + 64: { name: 'debugPrint', func: func1 }, + }; + + function renderCommands(buf) { + ctx = Module.ctx; + i = 0; + buffer = buf; + var len = buffer.length; + //dump('issuing commands, buffer len: ' + len + '\n'); + while (i < len) { + var info = calls[buffer[i++]]; + var name = info.name; + info.func(name); + //var err; + //while ((err = ctx.getError()) !== ctx.NO_ERROR) { + // dump('warning: GL error ' + err + ', after ' + [command, numArgs] + '\n'); + //} + assert(i <= len); + } + } + + var commandBuffers = []; + + function renderAllCommands() { + // TODO: we can avoid running commands from buffers that are not the last, if they + // have no side effects, as each buffer is from a different frame + //if (commandBuffers.length > 1) dump('extra buffs: ' + (commandBuffers.length-1) + '\n'); + for (var i = 0; i < commandBuffers.length; i++) { + renderCommands(commandBuffers[i]); + } + commandBuffers.length = 0; + } + + this.onmessage = function(msg) { + //dump('client GL got ' + JSON.stringify(msg) + '\n'); + switch(msg.op) { + case 'render': { + if (commandBuffers.length === 0) { + // requestion a new frame, we will clear the buffers after rendering them + window.requestAnimationFrame(renderAllCommands); + } + commandBuffers.push(msg.commandBuffer); + break; + } + default: throw 'weird gl onmessage ' + JSON.stringify(msg); + } + }; +} + +WebGLClient.prefetch = function() { + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('webgl-experimental') || canvas.getContext('webgl'); + if (!ctx) return; + var parameters = {}; + ['MAX_VERTEX_ATTRIBS', 'MAX_TEXTURE_IMAGE_UNITS', 'MAX_TEXTURE_SIZE', 'MAX_CUBE_MAP_TEXTURE_SIZE', 'MAX_VERTEX_UNIFORM_VECTORS', 'MAX_FRAGMENT_UNIFORM_VECTORS', 'MAX_VARYING_VECTORS', 'MAX_COMBINED_TEXTURE_IMAGE_UNITS', 'VENDOR', 'RENDERER', 'VERSION'].forEach(function(name) { + parameters[ctx[name]] = ctx.getParameter(ctx[name]); + }); + worker.postMessage({ target: 'gl', op: 'setPrefetched', parameters: parameters, extensions: ctx.getSupportedExtensions() }); +}; + diff --git a/src/webGLWorker.js b/src/webGLWorker.js new file mode 100644 index 00000000..90a1bed9 --- /dev/null +++ b/src/webGLWorker.js @@ -0,0 +1,910 @@ +// WebGLWorker worker code + +function WebGLBuffer(id) { + this.what = 'buffer'; + this.id = id; +} +function WebGLProgram(id) { + this.what = 'program'; + this.id = id; + this.shaders = []; + this.attributes = {}; + this.attributeVec = []; +} +function WebGLFramebuffer(id) { + this.what = 'frameBuffer'; + this.id = id; +} +function WebGLRenderbuffer(id) { + this.what = 'renderBuffer'; + this.id = id; +} +function WebGLTexture(id) { + this.what = 'texture'; + this.id = id; + this.binding = 0; +} + +function WebGLWorker() { + //======= + // State + //======= + + var commandBuffer = []; + + var nextId = 1; // valid ids are > 0 + + var bindings = { + texture2D: null, + arrayBuffer: null, + elementArrayBuffer: null, + program: null + }; + + //=========== + // Constants + //=========== + + /* ClearBufferMask */ + this.DEPTH_BUFFER_BIT = 0x00000100; + this.STENCIL_BUFFER_BIT = 0x00000400; + this.COLOR_BUFFER_BIT = 0x00004000; + + /* BeginMode */ + this.POINTS = 0x0000; + this.LINES = 0x0001; + this.LINE_LOOP = 0x0002; + this.LINE_STRIP = 0x0003; + this.TRIANGLES = 0x0004; + this.TRIANGLE_STRIP = 0x0005; + this.TRIANGLE_FAN = 0x0006; + + /* AlphaFunction (not supported in ES20) */ + /* NEVER */ + /* LESS */ + /* EQUAL */ + /* LEQUAL */ + /* GREATER */ + /* NOTEQUAL */ + /* GEQUAL */ + /* ALWAYS */ + + /* BlendingFactorDest */ + this.ZERO = 0; + this.ONE = 1; + this.SRC_COLOR = 0x0300; + this.ONE_MINUS_SRC_COLOR = 0x0301; + this.SRC_ALPHA = 0x0302; + this.ONE_MINUS_SRC_ALPHA = 0x0303; + this.DST_ALPHA = 0x0304; + this.ONE_MINUS_DST_ALPHA = 0x0305; + + /* BlendingFactorSrc */ + /* ZERO */ + /* ONE */ + this.DST_COLOR = 0x0306; + this.ONE_MINUS_DST_COLOR = 0x0307; + this.SRC_ALPHA_SATURATE = 0x0308; + /* SRC_ALPHA */ + /* ONE_MINUS_SRC_ALPHA */ + /* DST_ALPHA */ + /* ONE_MINUS_DST_ALPHA */ + + /* BlendEquationSeparate */ + this.FUNC_ADD = 0x8006; + this.BLEND_EQUATION = 0x8009; + this.BLEND_EQUATION_RGB = 0x8009; /* same as BLEND_EQUATION */ + this.BLEND_EQUATION_ALPHA = 0x883D; + + /* BlendSubtract */ + this.FUNC_SUBTRACT = 0x800A; + this.FUNC_REVERSE_SUBTRACT = 0x800B; + + /* Separate Blend Functions */ + this.BLEND_DST_RGB = 0x80C8; + this.BLEND_SRC_RGB = 0x80C9; + this.BLEND_DST_ALPHA = 0x80CA; + this.BLEND_SRC_ALPHA = 0x80CB; + this.CONSTANT_COLOR = 0x8001; + this.ONE_MINUS_CONSTANT_COLOR = 0x8002; + this.CONSTANT_ALPHA = 0x8003; + this.ONE_MINUS_CONSTANT_ALPHA = 0x8004; + this.BLEND_COLOR = 0x8005; + + /* Buffer Objects */ + this.ARRAY_BUFFER = 0x8892; + this.ELEMENT_ARRAY_BUFFER = 0x8893; + this.ARRAY_BUFFER_BINDING = 0x8894; + this.ELEMENT_ARRAY_BUFFER_BINDING = 0x8895; + + this.STREAM_DRAW = 0x88E0; + this.STATIC_DRAW = 0x88E4; + this.DYNAMIC_DRAW = 0x88E8; + + this.BUFFER_SIZE = 0x8764; + this.BUFFER_USAGE = 0x8765; + + this.CURRENT_VERTEX_ATTRIB = 0x8626; + + /* CullFaceMode */ + this.FRONT = 0x0404; + this.BACK = 0x0405; + this.FRONT_AND_BACK = 0x0408; + + /* DepthFunction */ + /* NEVER */ + /* LESS */ + /* EQUAL */ + /* LEQUAL */ + /* GREATER */ + /* NOTEQUAL */ + /* GEQUAL */ + /* ALWAYS */ + + /* EnableCap */ + /* TEXTURE_2D */ + this.CULL_FACE = 0x0B44; + this.BLEND = 0x0BE2; + this.DITHER = 0x0BD0; + this.STENCIL_TEST = 0x0B90; + this.DEPTH_TEST = 0x0B71; + this.SCISSOR_TEST = 0x0C11; + this.POLYGON_OFFSET_FILL = 0x8037; + this.SAMPLE_ALPHA_TO_COVERAGE = 0x809E; + this.SAMPLE_COVERAGE = 0x80A0; + + /* ErrorCode */ + this.NO_ERROR = 0; + this.INVALID_ENUM = 0x0500; + this.INVALID_VALUE = 0x0501; + this.INVALID_OPERATION = 0x0502; + this.OUT_OF_MEMORY = 0x0505; + + /* FrontFaceDirection */ + this.CW = 0x0900; + this.CCW = 0x0901; + + /* GetPName */ + this.LINE_WIDTH = 0x0B21; + this.ALIASED_POINT_SIZE_RANGE = 0x846D; + this.ALIASED_LINE_WIDTH_RANGE = 0x846E; + this.CULL_FACE_MODE = 0x0B45; + this.FRONT_FACE = 0x0B46; + this.DEPTH_RANGE = 0x0B70; + this.DEPTH_WRITEMASK = 0x0B72; + this.DEPTH_CLEAR_VALUE = 0x0B73; + this.DEPTH_FUNC = 0x0B74; + this.STENCIL_CLEAR_VALUE = 0x0B91; + this.STENCIL_FUNC = 0x0B92; + this.STENCIL_FAIL = 0x0B94; + this.STENCIL_PASS_DEPTH_FAIL = 0x0B95; + this.STENCIL_PASS_DEPTH_PASS = 0x0B96; + this.STENCIL_REF = 0x0B97; + this.STENCIL_VALUE_MASK = 0x0B93; + this.STENCIL_WRITEMASK = 0x0B98; + this.STENCIL_BACK_FUNC = 0x8800; + this.STENCIL_BACK_FAIL = 0x8801; + this.STENCIL_BACK_PASS_DEPTH_FAIL = 0x8802; + this.STENCIL_BACK_PASS_DEPTH_PASS = 0x8803; + this.STENCIL_BACK_REF = 0x8CA3; + this.STENCIL_BACK_VALUE_MASK = 0x8CA4; + this.STENCIL_BACK_WRITEMASK = 0x8CA5; + this.VIEWPORT = 0x0BA2; + this.SCISSOR_BOX = 0x0C10; + /* SCISSOR_TEST */ + this.COLOR_CLEAR_VALUE = 0x0C22; + this.COLOR_WRITEMASK = 0x0C23; + this.UNPACK_ALIGNMENT = 0x0CF5; + this.PACK_ALIGNMENT = 0x0D05; + this.MAX_TEXTURE_SIZE = 0x0D33; + this.MAX_VIEWPORT_DIMS = 0x0D3A; + this.SUBPIXEL_BITS = 0x0D50; + this.RED_BITS = 0x0D52; + this.GREEN_BITS = 0x0D53; + this.BLUE_BITS = 0x0D54; + this.ALPHA_BITS = 0x0D55; + this.DEPTH_BITS = 0x0D56; + this.STENCIL_BITS = 0x0D57; + this.POLYGON_OFFSET_UNITS = 0x2A00; + /* POLYGON_OFFSET_FILL */ + this.POLYGON_OFFSET_FACTOR = 0x8038; + this.TEXTURE_BINDING_2D = 0x8069; + this.SAMPLE_BUFFERS = 0x80A8; + this.SAMPLES = 0x80A9; + this.SAMPLE_COVERAGE_VALUE = 0x80AA; + this.SAMPLE_COVERAGE_INVERT = 0x80AB; + + /* GetTextureParameter */ + /* TEXTURE_MAG_FILTER */ + /* TEXTURE_MIN_FILTER */ + /* TEXTURE_WRAP_S */ + /* TEXTURE_WRAP_T */ + + this.COMPRESSED_TEXTURE_FORMATS = 0x86A3; + + /* HintMode */ + this.DONT_CARE = 0x1100; + this.FASTEST = 0x1101; + this.NICEST = 0x1102; + + /* HintTarget */ + this.GENERATE_MIPMAP_HINT = 0x8192; + + /* DataType */ + this.BYTE = 0x1400; + this.UNSIGNED_BYTE = 0x1401; + this.SHORT = 0x1402; + this.UNSIGNED_SHORT = 0x1403; + this.INT = 0x1404; + this.UNSIGNED_INT = 0x1405; + this.FLOAT = 0x1406; + + /* PixelFormat */ + this.DEPTH_COMPONENT = 0x1902; + this.ALPHA = 0x1906; + this.RGB = 0x1907; + this.RGBA = 0x1908; + this.LUMINANCE = 0x1909; + this.LUMINANCE_ALPHA = 0x190A; + + /* PixelType */ + /* UNSIGNED_BYTE */ + this.UNSIGNED_SHORT_4_4_4_4 = 0x8033; + this.UNSIGNED_SHORT_5_5_5_1 = 0x8034; + this.UNSIGNED_SHORT_5_6_5 = 0x8363; + + /* Shaders */ + this.FRAGMENT_SHADER = 0x8B30; + this.VERTEX_SHADER = 0x8B31; + this.MAX_VERTEX_ATTRIBS = 0x8869; + this.MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB; + this.MAX_VARYING_VECTORS = 0x8DFC; + this.MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0x8B4D; + this.MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C; + this.MAX_TEXTURE_IMAGE_UNITS = 0x8872; + this.MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD; + this.SHADER_TYPE = 0x8B4F; + this.DELETE_STATUS = 0x8B80; + this.LINK_STATUS = 0x8B82; + this.VALIDATE_STATUS = 0x8B83; + this.ATTACHED_SHADERS = 0x8B85; + this.ACTIVE_UNIFORMS = 0x8B86; + this.ACTIVE_ATTRIBUTES = 0x8B89; + this.SHADING_LANGUAGE_VERSION = 0x8B8C; + this.CURRENT_PROGRAM = 0x8B8D; + + /* StencilFunction */ + this.NEVER = 0x0200; + this.LESS = 0x0201; + this.EQUAL = 0x0202; + this.LEQUAL = 0x0203; + this.GREATER = 0x0204; + this.NOTEQUAL = 0x0205; + this.GEQUAL = 0x0206; + this.ALWAYS = 0x0207; + + /* StencilOp */ + /* ZERO */ + this.KEEP = 0x1E00; + this.REPLACE = 0x1E01; + this.INCR = 0x1E02; + this.DECR = 0x1E03; + this.INVERT = 0x150A; + this.INCR_WRAP = 0x8507; + this.DECR_WRAP = 0x8508; + + /* StringName */ + this.VENDOR = 0x1F00; + this.RENDERER = 0x1F01; + this.VERSION = 0x1F02; + + /* TextureMagFilter */ + this.NEAREST = 0x2600; + this.LINEAR = 0x2601; + + /* TextureMinFilter */ + /* NEAREST */ + /* LINEAR */ + this.NEAREST_MIPMAP_NEAREST = 0x2700; + this.LINEAR_MIPMAP_NEAREST = 0x2701; + this.NEAREST_MIPMAP_LINEAR = 0x2702; + this.LINEAR_MIPMAP_LINEAR = 0x2703; + + /* TextureParameterName */ + this.TEXTURE_MAG_FILTER = 0x2800; + this.TEXTURE_MIN_FILTER = 0x2801; + this.TEXTURE_WRAP_S = 0x2802; + this.TEXTURE_WRAP_T = 0x2803; + + /* TextureTarget */ + this.TEXTURE_2D = 0x0DE1; + this.TEXTURE = 0x1702; + + this.TEXTURE_CUBE_MAP = 0x8513; + this.TEXTURE_BINDING_CUBE_MAP = 0x8514; + this.TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; + this.TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; + this.TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; + this.TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; + this.TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; + this.TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A; + this.MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C; + + /* TextureUnit */ + this.TEXTURE0 = 0x84C0; + this.TEXTURE1 = 0x84C1; + this.TEXTURE2 = 0x84C2; + this.TEXTURE3 = 0x84C3; + this.TEXTURE4 = 0x84C4; + this.TEXTURE5 = 0x84C5; + this.TEXTURE6 = 0x84C6; + this.TEXTURE7 = 0x84C7; + this.TEXTURE8 = 0x84C8; + this.TEXTURE9 = 0x84C9; + this.TEXTURE10 = 0x84CA; + this.TEXTURE11 = 0x84CB; + this.TEXTURE12 = 0x84CC; + this.TEXTURE13 = 0x84CD; + this.TEXTURE14 = 0x84CE; + this.TEXTURE15 = 0x84CF; + this.TEXTURE16 = 0x84D0; + this.TEXTURE17 = 0x84D1; + this.TEXTURE18 = 0x84D2; + this.TEXTURE19 = 0x84D3; + this.TEXTURE20 = 0x84D4; + this.TEXTURE21 = 0x84D5; + this.TEXTURE22 = 0x84D6; + this.TEXTURE23 = 0x84D7; + this.TEXTURE24 = 0x84D8; + this.TEXTURE25 = 0x84D9; + this.TEXTURE26 = 0x84DA; + this.TEXTURE27 = 0x84DB; + this.TEXTURE28 = 0x84DC; + this.TEXTURE29 = 0x84DD; + this.TEXTURE30 = 0x84DE; + this.TEXTURE31 = 0x84DF; + this.ACTIVE_TEXTURE = 0x84E0; + + /* TextureWrapMode */ + this.REPEAT = 0x2901; + this.CLAMP_TO_EDGE = 0x812F; + this.MIRRORED_REPEAT = 0x8370; + + /* Uniform Types */ + this.FLOAT_VEC2 = 0x8B50; + this.FLOAT_VEC3 = 0x8B51; + this.FLOAT_VEC4 = 0x8B52; + this.INT_VEC2 = 0x8B53; + this.INT_VEC3 = 0x8B54; + this.INT_VEC4 = 0x8B55; + this.BOOL = 0x8B56; + this.BOOL_VEC2 = 0x8B57; + this.BOOL_VEC3 = 0x8B58; + this.BOOL_VEC4 = 0x8B59; + this.FLOAT_MAT2 = 0x8B5A; + this.FLOAT_MAT3 = 0x8B5B; + this.FLOAT_MAT4 = 0x8B5C; + this.SAMPLER_2D = 0x8B5E; + this.SAMPLER_CUBE = 0x8B60; + + /* Vertex Arrays */ + this.VERTEX_ATTRIB_ARRAY_ENABLED = 0x8622; + this.VERTEX_ATTRIB_ARRAY_SIZE = 0x8623; + this.VERTEX_ATTRIB_ARRAY_STRIDE = 0x8624; + this.VERTEX_ATTRIB_ARRAY_TYPE = 0x8625; + this.VERTEX_ATTRIB_ARRAY_NORMALIZED = 0x886A; + this.VERTEX_ATTRIB_ARRAY_POINTER = 0x8645; + this.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING = 0x889F; + + /* Read Format */ + this.IMPLEMENTATION_COLOR_READ_TYPE = 0x8B9A; + this.IMPLEMENTATION_COLOR_READ_FORMAT = 0x8B9B; + + /* Shader Source */ + this.COMPILE_STATUS = 0x8B81; + + /* Shader Precision-Specified Types */ + this.LOW_FLOAT = 0x8DF0; + this.MEDIUM_FLOAT = 0x8DF1; + this.HIGH_FLOAT = 0x8DF2; + this.LOW_INT = 0x8DF3; + this.MEDIUM_INT = 0x8DF4; + this.HIGH_INT = 0x8DF5; + + /* Framebuffer Object. */ + this.FRAMEBUFFER = 0x8D40; + this.RENDERBUFFER = 0x8D41; + + this.RGBA4 = 0x8056; + this.RGB5_A1 = 0x8057; + this.RGB565 = 0x8D62; + this.DEPTH_COMPONENT16 = 0x81A5; + this.STENCIL_INDEX = 0x1901; + this.STENCIL_INDEX8 = 0x8D48; + this.DEPTH_STENCIL = 0x84F9; + + this.RENDERBUFFER_WIDTH = 0x8D42; + this.RENDERBUFFER_HEIGHT = 0x8D43; + this.RENDERBUFFER_INTERNAL_FORMAT = 0x8D44; + this.RENDERBUFFER_RED_SIZE = 0x8D50; + this.RENDERBUFFER_GREEN_SIZE = 0x8D51; + this.RENDERBUFFER_BLUE_SIZE = 0x8D52; + this.RENDERBUFFER_ALPHA_SIZE = 0x8D53; + this.RENDERBUFFER_DEPTH_SIZE = 0x8D54; + this.RENDERBUFFER_STENCIL_SIZE = 0x8D55; + + this.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x8CD0; + this.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x8CD1; + this.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL = 0x8CD2; + this.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE = 0x8CD3; + + this.COLOR_ATTACHMENT0 = 0x8CE0; + this.DEPTH_ATTACHMENT = 0x8D00; + this.STENCIL_ATTACHMENT = 0x8D20; + this.DEPTH_STENCIL_ATTACHMENT = 0x821A; + + this.NONE = 0; + + this.FRAMEBUFFER_COMPLETE = 0x8CD5; + this.FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x8CD6; + this.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x8CD7; + this.FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x8CD9; + this.FRAMEBUFFER_UNSUPPORTED = 0x8CDD; + + this.FRAMEBUFFER_BINDING = 0x8CA6; + this.RENDERBUFFER_BINDING = 0x8CA7; + this.MAX_RENDERBUFFER_SIZE = 0x84E8; + + this.INVALID_FRAMEBUFFER_OPERATION = 0x0506; + + /* WebGL-specific enums */ + this.UNPACK_FLIP_Y_WEBGL = 0x9240; + this.UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241; + this.CONTEXT_LOST_WEBGL = 0x9242; + this.UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243; + this.BROWSER_DEFAULT_WEBGL = 0x9244; + + //========== + // Functions + //========== + + var that = this; + + // Helpers + + this.onmessage = function(msg) { + //dump('worker GL got ' + JSON.stringify(msg) + '\n'); + switch(msg.op) { + case 'setPrefetched': { + WebGLWorker.prototype.prefetchedParameters = msg.parameters; + WebGLWorker.prototype.prefetchedExtensions = msg.extensions; + break; + } + default: throw 'weird gl onmessage ' + JSON.stringify(msg); + } + }; + + function revname(name) { + for (var x in that) if (that[x] === name) return x; + return null; + } + + // GL + + this.getParameter = function(name) { + assert(name); + if (name in this.prefetchedParameters) return this.prefetchedParameters[name]; + switch (name) { + case this.TEXTURE_BINDING_2D: { + return bindings.texture2D; + } + case this.ARRAY_BUFFER_BINDING: { + return bindings.arrayBuffer; + } + case this.ELEMENT_ARRAY_BUFFER_BINDING: { + return bindings.elementArrayBuffer; + } + case this.CURRENT_PROGRAM: { + return bindings.program; + } + default: throw 'TODO: get parameter ' + name + ' : ' + revname(name); + } + }; + this.getExtension = function(name) { + var i = this.prefetchedExtensions.indexOf(name); + if (i < 0) return null; + commandBuffer.push(1, name); + return true; // TODO: return an object here + }; + this.getSupportedExtensions = function() { + return this.prefetchedExtensions; + }; + this.enable = function(cap) { + commandBuffer.push(2, cap); + }; + this.disable = function(cap) { + commandBuffer.push(3, cap); + }; + this.clear = function(mask) { + commandBuffer.push(4, mask); + }; + this.clearColor = function(r, g, b, a) { + commandBuffer.push(5, r, g, b, a); + }; + this.createShader = function(type) { + var id = nextId++; + commandBuffer.push(6, type, id); + return { id: id, what: 'shader', type: type }; + }; + this.deleteShader = function(shader) { + if (!shader) return; + commandBuffer.push(7, shader.id); + }; + this.shaderSource = function(shader, source) { + shader.source = source; + commandBuffer.push(8, shader.id, source); + }; + this.compileShader = function(shader) { + commandBuffer.push(9, shader.id); + }; + this.getShaderInfoLog = function(shader) { + return ''; // optimistic assumption of success; no proxying + }; + this.createProgram = function() { + var id = nextId++; + commandBuffer.push(10, id); + return new WebGLProgram(id); + }; + this.deleteProgram = function(program) { + if (!program) return; + commandBuffer.push(11, program.id); + }; + this.attachShader = function(program, shader) { + program.shaders.push(shader); + commandBuffer.push(12, program.id, shader.id); + }; + this.bindAttribLocation = function(program, index, name) { + program.attributes[name] = { what: 'attribute', name: name, size: -1, location: index }; // fill in size later + program.attributeVec[index] = name; + commandBuffer.push(13, program.id, index, name); + }; + this.getAttribLocation = function(program, name) { + // all existing attribs are cached locally + if (name in program.attributes) return program.attributes[name].location; + return -1; + }; + this.linkProgram = function(program) { + // parse shader sources + function parseElementType(shader, type, obj, vec) { + var source = shader.source; + source = source.replace(/\n/g, '|\n'); // barrier between lines, to make regexing easier + var newItems = source.match(new RegExp(type + '\\s+\\w+\\s+[\\w,\\s\[\\]]+;', 'g')); + if (!newItems) return; + newItems.forEach(function(item) { + var m = new RegExp(type + '\\s+\\w+\\s+([\\w,\\s\[\\]]+);').exec(item); + assert(m); + m[1].split(',').map(function(name) { name = name.trim(); return name.search(/\s/) >= 0 ? '' : name }).filter(function(name) { return !!name }).forEach(function(name) { + var size = 1; + var open = name.indexOf('['); + var fullname = name; + if (open >= 0) { + var close = name.indexOf(']'); + size = parseInt(name.substring(open+1, close)); + name = name.substr(0, open); + fullname = name + '[0]'; + } + if (!obj[name]) { + obj[name] = { what: type, name: fullname, size: size, location: -1 }; + if (vec) vec.push(name); + } + }); + }); + } + + program.uniforms = {}; + program.uniformVec = []; + + var existingAttributes = {}; + + program.shaders.forEach(function(shader) { + parseElementType(shader, 'uniform', program.uniforms, program.uniformVec); + parseElementType(shader, 'attribute', existingAttributes, null); + }); + + // bind not-yet bound attributes + for (var attr in existingAttributes) { + if (!(attr in program.attributes)) { + var index = program.attributeVec.length; + this.bindAttribLocation(program, index, attr); + } + program.attributes[attr].size = existingAttributes[attr].size; + } + + commandBuffer.push(14, program.id); + }; + this.getProgramParameter = function(program, name) { + switch (name) { + case this.ACTIVE_UNIFORMS: return program.uniformVec.length; + case this.ACTIVE_ATTRIBUTES: return program.attributeVec.length; + case this.LINK_STATUS: { + // optimisticaly return success; client will abort on an actual error. we assume an error-free async workflow + commandBuffer.push(15, program.id, name); + return true; + } + default: throw 'bad getProgramParameter ' + revname(name); + } + }; + this.getActiveAttrib = function(program, index) { + var name = program.attributeVec[index]; + if (!name) return null; + return program.attributes[name]; + }; + this.getActiveUniform = function(program, index) { + var name = program.uniformVec[index]; + if (!name) return null; + return program.uniforms[name]; + }; + this.getUniformLocation = function(program, name) { + var fullname = name; + var index = -1; + var open = name.indexOf('['); + if (open >= 0) { + var close = name.indexOf(']'); + index = parseInt(name.substring(open+1, close)); + name = name.substr(0, open); + } + if (!(name in program.uniforms)) return null; + var id = nextId++; + commandBuffer.push(16, program.id, fullname, id); + return { what: 'location', uniform: program.uniforms[name], id: id, index: index }; + }; + this.getProgramInfoLog = function(shader) { + return ''; // optimistic assumption of success; no proxying + }; + this.useProgram = function(program) { + commandBuffer.push(17, program ? program.id : 0); + bindings.program = program; + }; + this.uniform1i = function(location, data) { + if (!location) return; + commandBuffer.push(18, location.id, data); + }; + this.uniform1f = function(location, data) { + if (!location) return; + commandBuffer.push(19, location.id, data); + }; + this.uniform3fv = function(location, data) { + if (!location) return; + commandBuffer.push(20, location.id, new Float32Array(data)); + }; + this.uniform4fv = function(location, data) { + if (!location) return; + commandBuffer.push(21, location.id, new Float32Array(data)); + }; + this.uniformMatrix4fv = function(location, transpose, data) { + if (!location) return; + commandBuffer.push(22, location.id, transpose, new Float32Array(data)); + }; + this.vertexAttrib4fv = function(index, values) { + commandBuffer.push(23, index, new Float32Array(values)); + }; + this.createBuffer = function() { + var id = nextId++; + commandBuffer.push(24, id); + return new WebGLBuffer(id); + }; + this.deleteBuffer = function(buffer) { + if (!buffer) return; + commandBuffer.push(25, buffer.id); + }; + this.bindBuffer = function(target, buffer) { + commandBuffer.push(26, target, buffer ? buffer.id : 0); + switch (target) { + case this.ARRAY_BUFFER_BINDING: { + bindings.arrayBuffer = buffer; + break; + } + case this.ELEMENT_ARRAY_BUFFER_BINDING: { + bindings.elementArrayBuffer = buffer; + break; + } + } + }; + this.bufferData = function(target, something, usage) { + if (typeof something !== 'number') something = new something.constructor(something); + commandBuffer.push(27, target, something, usage); + }; + this.bufferSubData = function(target, offset, something) { + if (typeof something !== 'number') something = new something.constructor(something); + commandBuffer.push(28, target, offset, something); + }; + this.viewport = function(x, y, w, h) { + commandBuffer.push(29, x, y, w, h); + }; + this.vertexAttribPointer = function(index, size, type, normalized, stride, offset) { + commandBuffer.push(30, index, size, type, normalized, stride, offset); + }; + this.enableVertexAttribArray = function(index) { + commandBuffer.push(31, index); + }; + this.disableVertexAttribArray = function(index) { + commandBuffer.push(32, index); + }; + this.drawArrays = function(mode, first, count) { + commandBuffer.push(33, mode, first, count); + }; + this.drawElements = function(mode, count, type, offset) { + commandBuffer.push(34, mode, count, type, offset); + }; + this.getError = function() { + // optimisticaly return success; client will abort on an actual error. we assume an error-free async workflow + commandBuffer.push(35); + return this.NO_ERROR; + }; + this.createTexture = function() { + var id = nextId++; + commandBuffer.push(36, id); + return new WebGLTexture(id); + }; + this.deleteTexture = function(texture) { + if (!texture) return; + commandBuffer.push(37, texture.id); + texture.id = 0; + }; + this.isTexture = function(texture) { + return texture && texture.what === 'texture' && texture.id > 0 && texture.binding; + }; + this.bindTexture = function(target, texture) { + switch (target) { + case that.TEXTURE_2D: { + bindings.texture2D = texture; + break; + } + } + texture.binding = target; + commandBuffer.push(38, target, texture ? texture.id : 0); + }; + this.texParameteri = function(target, pname, param) { + commandBuffer.push(39, target, pname, param); + }; + this.texImage2D = function(target, level, internalformat, width, height, border, format, type, pixels) { + assert(pixels || pixels === null); // we do not support the overloads that have fewer params + commandBuffer.push(40, target, level, internalformat, width, height, border, format, type, pixels ? new pixels.constructor(pixels) : pixels); + }; + this.compressedTexImage2D = function(target, level, internalformat, width, height, border, pixels) { + commandBuffer.push(41, target, level, internalformat, width, height, border, new pixels.constructor(pixels)); + }; + this.activeTexture = function(texture) { + commandBuffer.push(42, texture); + }; + this.getShaderParameter = function(shader, pname) { + switch (pname) { + case this.SHADER_TYPE: return shader.type; + case this.COMPILE_STATUS: { + // optimisticaly return success; client will abort on an actual error. we assume an error-free async workflow + commandBuffer.push(43, shader.id, pname); + return true; + } + default: throw 'unsupported getShaderParameter ' + pname; + } + }; + this.clearDepth = function(depth) { + commandBuffer.push(44, depth); + }; + this.depthFunc = function(depth) { + commandBuffer.push(45, depth); + }; + this.frontFace = function(depth) { + commandBuffer.push(46, depth); + }; + this.cullFace = function(depth) { + commandBuffer.push(47, depth); + }; + this.readPixels = function(depth) { + abort('readPixels is impossible, we are async GL'); + }; + this.pixelStorei = function(pname, param) { + commandBuffer.push(48, pname, param); + }; + this.depthMask = function(flag) { + commandBuffer.push(49, flag); + }; + this.depthRange = function(near, far) { + commandBuffer.push(50, near, far); + }; + this.blendFunc = function(sfactor, dfactor) { + commandBuffer.push(51, sfactor, dfactor); + }; + this.scissor = function(x, y, width, height) { + commandBuffer.push(52, x, y, width, height); + }; + this.colorMask = function(red, green, blue, alpha) { + commandBuffer.push(53, red, green, blue, alpha); + }; + this.lineWidth = function(width) { + commandBuffer.push(54, width); + }; + this.createFramebuffer = function() { + var id = nextId++; + commandBuffer.push(55, id); + return new WebGLFramebuffer(id); + }; + this.deleteFramebuffer = function(framebuffer) { + if (!framebuffer) return; + commandBuffer.push(56, framebuffer.id); + }; + this.bindFramebuffer = function(target, framebuffer) { + commandBuffer.push(57, target, framebuffer ? framebuffer.id : 0); + }; + this.framebufferTexture2D = function(target, attachment, textarget, texture, level) { + commandBuffer.push(58, target, attachment, textarget, texture ? texture.id : 0, level); + }; + this.checkFramebufferStatus = function(target) { + return this.FRAMEBUFFER_COMPLETE; // XXX totally wrong + }; + this.createRenderbuffer = function() { + var id = nextId++; + commandBuffer.push(59, id); + return new WebGLRenderbuffer(id); + }; + this.deleteRenderbuffer = function(renderbuffer) { + if (!renderbuffer) return; + commandBuffer.push(60, renderbuffer.id); + }; + this.bindRenderbuffer = function(target, renderbuffer) { + commandBuffer.push(61, target, renderbuffer ? renderbuffer.id : 0); + }; + this.renderbufferStorage = function(target, internalformat, width, height) { + commandBuffer.push(62, target, internalformat, width, height); + }; + this.framebufferRenderbuffer = function(target, attachment, renderbuffertarget, renderbuffer) { + commandBuffer.push(63, target, attachment, renderbuffertarget, renderbuffer ? renderbuffer.id : 0); + }; + //this.debugPrint = function(text) { // useful to interleave debug output properly with client GL commands + // commandBuffer.push(64, text); + //}; + + // Setup + var dropped = 0; + var average = 0; + var last = 0; + var meanDiff = 0; + var preMainLoop = Module['preMainLoop']; + Module['preMainLoop'] = function() { + /* + var now = Date.now(); + if (last > 0) { + var diff = now - last; + meanDiff = 0.95*meanDiff + 0.05*diff; + if (clientFrameId % 10 === 0) dump('server fps ' + (1000/meanDiff) + ' (ignoring client throttling)\n'); + } + last = now; + */ + if (preMainLoop) { + var ret = preMainLoop(); + if (ret === false) return ret; + } + // if too many frames in queue, skip a main loop iter + if (Math.abs(frameId - clientFrameId) >= 3) { + //dropped++; + //if (dropped % 10 === 0) dump('dropped: ' + [dropped, frameId, Math.round(100*dropped/(frameId + dropped)) + '%\n']); + return false; + } + }; + var postMainLoop = Module['postMainLoop']; + Module['postMainLoop'] = function() { + if (postMainLoop) postMainLoop(); + if (commandBuffer.length > 0) { + //average = (average + commandBuffer.length)/2; + //dump('buffer size: ' + Math.round(average) + '\n'); + postMessage({ target: 'gl', op: 'render', commandBuffer: commandBuffer }); + commandBuffer = []; + } + }; +} + +// share prefetched data among all instances + +WebGLWorker.prototype.prefetchedParameters = {}; +WebGLWorker.prototype.prefetchedExtensions = {}; + diff --git a/tests/gles2_uniform_arrays.cpp b/tests/gles2_uniform_arrays.cpp index 7293f9a9..b6be5348 100644 --- a/tests/gles2_uniform_arrays.cpp +++ b/tests/gles2_uniform_arrays.cpp @@ -4,6 +4,7 @@ #include <stdio.h> #include <string.h> #include <assert.h> +#include <emscripten.h> void RunTest(int testVariant) { @@ -57,6 +58,7 @@ void RunTest(int testVariant) // so to exhibit extra issues in old code (and to keep new code from regressing), must test both with and without excess glGetUniformLocation calls. if ((testVariant&1) != 0) { + printf("check glGetUniformLocation with indexes\n"); // Deliberately check in odd order to make sure any kind of lazy operations won't affect the indices we get. assert(glGetUniformLocation(program, "colors[2]") == loc+2); assert(glGetUniformLocation(program, "colors[0]") == loc); @@ -65,6 +67,7 @@ void RunTest(int testVariant) assert(glGetUniformLocation(program, "colors[]") == loc); assert(glGetUniformLocation(program, "colors[-100]") == -1); assert(glGetUniformLocation(program, "colors[bleh]") == -1); + printf(" ...ok\n"); } float colors[4*3] = { 1,0,0, 0,0.5,0, 0,0,0.2, 1,1,1 }; @@ -95,13 +98,20 @@ void RunTest(int testVariant) glDrawArrays(GL_TRIANGLES, 0, 6); - unsigned char pixel[4]; - glReadPixels(1,1,1,1,GL_RGBA,GL_UNSIGNED_BYTE, pixel); - //printf("%d,%d,%d,%d\n", pixel[0], pixel[1], pixel[2], pixel[3]); - assert(pixel[0] == 255); - assert(pixel[1] == 178); - assert(pixel[2] == 102); - assert(pixel[3] == 255); + int in_worker = EM_ASM_INT_V({ + return typeof importScripts !== 'undefined' + }); + + if (!in_worker) { + printf("Doing readpixels check\n"); + unsigned char pixel[4]; + glReadPixels(1,1,1,1,GL_RGBA,GL_UNSIGNED_BYTE, pixel); + //printf("%d,%d,%d,%d\n", pixel[0], pixel[1], pixel[2], pixel[3]); + assert(pixel[0] == 255); + assert(pixel[1] == 178); + assert(pixel[2] == 102); + assert(pixel[3] == 255); + } printf("OK: Case %d passed.\n", testVariant); // Lazy, don't clean up afterwards. @@ -126,5 +136,10 @@ int main(int argc, char *argv[]) for(int i = 0; i < 4; ++i) RunTest(i); +#ifdef REPORT_RESULT + int result = 1; + REPORT_RESULT(); +#endif + return 0; } diff --git a/tests/hello_world_gles_proxy.c b/tests/hello_world_gles_proxy.c new file mode 100644 index 00000000..1a8e5f61 --- /dev/null +++ b/tests/hello_world_gles_proxy.c @@ -0,0 +1,765 @@ +/* + * Copyright (C) 1999-2001 Brian Paul All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Ported to GLES2. + * Kristian Høgsberg <krh@bitplanet.net> + * May 3, 2010 + * + * Improve GLES2 port: + * * Refactor gear drawing. + * * Use correct normals for surfaces. + * * Improve shader. + * * Use perspective projection transformation. + * * Add FPS count. + * * Add comments. + * Alexandros Frantzis <alexandros.frantzis@linaro.org> + * Jul 13, 2010 + */ + +#define GL_GLEXT_PROTOTYPES +#define EGL_EGLEXT_PROTOTYPES + +#define _GNU_SOURCE + +#include <math.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/time.h> +#include <unistd.h> +#ifdef __APPLE__ +#include <OpenGL/gl.h> +#include <Glut/glut.h> +#else +#include <GL/gl.h> +#include <GL/glut.h> +#endif + +#define STRIPS_PER_TOOTH 7 +#define VERTICES_PER_TOOTH 34 +#define GEAR_VERTEX_STRIDE 6 + +#ifndef HAVE_BUILTIN_SINCOS +#define sincos _sincos +static void +sincos (double a, double *s, double *c) +{ + *s = sin (a); + *c = cos (a); +} +#endif + +/** + * Struct describing the vertices in triangle strip + */ +struct vertex_strip { + /** The first vertex in the strip */ + GLint first; + /** The number of consecutive vertices in the strip after the first */ + GLint count; +}; + +/* Each vertex consist of GEAR_VERTEX_STRIDE GLfloat attributes */ +typedef GLfloat GearVertex[GEAR_VERTEX_STRIDE]; + +/** + * Struct representing a gear. + */ +struct gear { + /** The array of vertices comprising the gear */ + GearVertex *vertices; + /** The number of vertices comprising the gear */ + int nvertices; + /** The array of triangle strips comprising the gear */ + struct vertex_strip *strips; + /** The number of triangle strips comprising the gear */ + int nstrips; + /** The Vertex Buffer Object holding the vertices in the graphics card */ + GLuint vbo; +}; + +/** The view rotation [x, y, z] */ +static GLfloat view_rot[3] = { 20.0, 30.0, 0.0 }; +/** The gears */ +static struct gear *gear1, *gear2, *gear3; +/** The current gear rotation angle */ +static GLfloat angle = 0.0; +/** The location of the shader uniforms */ +static GLuint ModelViewProjectionMatrix_location, + NormalMatrix_location, + LightSourcePosition_location, + MaterialColor_location; +/** The projection matrix */ +static GLfloat ProjectionMatrix[16]; +/** The direction of the directional light for the scene */ +static const GLfloat LightSourcePosition[4] = { 5.0, 5.0, 10.0, 1.0}; + +/** + * Fills a gear vertex. + * + * @param v the vertex to fill + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coortinate + * @param n pointer to the normal table + * + * @return the operation error code + */ +static GearVertex * +vert(GearVertex *v, GLfloat x, GLfloat y, GLfloat z, GLfloat n[3]) +{ + v[0][0] = x; + v[0][1] = y; + v[0][2] = z; + v[0][3] = n[0]; + v[0][4] = n[1]; + v[0][5] = n[2]; + + return v + 1; +} + +/** + * Create a gear wheel. + * + * @param inner_radius radius of hole at center + * @param outer_radius radius at center of teeth + * @param width width of gear + * @param teeth number of teeth + * @param tooth_depth depth of tooth + * + * @return pointer to the constructed struct gear + */ +static struct gear * +create_gear(GLfloat inner_radius, GLfloat outer_radius, GLfloat width, + GLint teeth, GLfloat tooth_depth) +{ + GLfloat r0, r1, r2; + GLfloat da; + GearVertex *v; + struct gear *gear; + double s[5], c[5]; + GLfloat normal[3]; + int cur_strip = 0; + int i; + + /* Allocate memory for the gear */ + gear = malloc(sizeof *gear); + if (gear == NULL) + return NULL; + + /* Calculate the radii used in the gear */ + r0 = inner_radius; + r1 = outer_radius - tooth_depth / 2.0; + r2 = outer_radius + tooth_depth / 2.0; + + da = 2.0 * M_PI / teeth / 4.0; + + /* Allocate memory for the triangle strip information */ + gear->nstrips = STRIPS_PER_TOOTH * teeth; + gear->strips = calloc(gear->nstrips, sizeof (*gear->strips)); + + /* Allocate memory for the vertices */ + gear->vertices = calloc(VERTICES_PER_TOOTH * teeth, sizeof(*gear->vertices)); + v = gear->vertices; + + for (i = 0; i < teeth; i++) { + /* Calculate needed sin/cos for varius angles */ + sincos(i * 2.0 * M_PI / teeth, &s[0], &c[0]); + sincos(i * 2.0 * M_PI / teeth + da, &s[1], &c[1]); + sincos(i * 2.0 * M_PI / teeth + da * 2, &s[2], &c[2]); + sincos(i * 2.0 * M_PI / teeth + da * 3, &s[3], &c[3]); + sincos(i * 2.0 * M_PI / teeth + da * 4, &s[4], &c[4]); + + /* A set of macros for making the creation of the gears easier */ +#define GEAR_POINT(r, da) { (r) * c[(da)], (r) * s[(da)] } +#define SET_NORMAL(x, y, z) do { \ + normal[0] = (x); normal[1] = (y); normal[2] = (z); \ +} while(0) + +#define GEAR_VERT(v, point, sign) vert((v), p[(point)].x, p[(point)].y, (sign) * width * 0.5, normal) + +#define START_STRIP do { \ + gear->strips[cur_strip].first = v - gear->vertices; \ +} while(0); + +#define END_STRIP do { \ + int _tmp = (v - gear->vertices); \ + gear->strips[cur_strip].count = _tmp - gear->strips[cur_strip].first; \ + cur_strip++; \ +} while (0) + +#define QUAD_WITH_NORMAL(p1, p2) do { \ + SET_NORMAL((p[(p1)].y - p[(p2)].y), -(p[(p1)].x - p[(p2)].x), 0); \ + v = GEAR_VERT(v, (p1), -1); \ + v = GEAR_VERT(v, (p1), 1); \ + v = GEAR_VERT(v, (p2), -1); \ + v = GEAR_VERT(v, (p2), 1); \ +} while(0) + + struct point { + GLfloat x; + GLfloat y; + }; + + /* Create the 7 points (only x,y coords) used to draw a tooth */ + struct point p[7] = { + GEAR_POINT(r2, 1), // 0 + GEAR_POINT(r2, 2), // 1 + GEAR_POINT(r1, 0), // 2 + GEAR_POINT(r1, 3), // 3 + GEAR_POINT(r0, 0), // 4 + GEAR_POINT(r1, 4), // 5 + GEAR_POINT(r0, 4), // 6 + }; + + /* Front face */ + START_STRIP; + SET_NORMAL(0, 0, 1.0); + v = GEAR_VERT(v, 0, +1); + v = GEAR_VERT(v, 1, +1); + v = GEAR_VERT(v, 2, +1); + v = GEAR_VERT(v, 3, +1); + v = GEAR_VERT(v, 4, +1); + v = GEAR_VERT(v, 5, +1); + v = GEAR_VERT(v, 6, +1); + END_STRIP; + + /* Inner face */ + START_STRIP; + QUAD_WITH_NORMAL(4, 6); + END_STRIP; + + /* Back face */ + START_STRIP; + SET_NORMAL(0, 0, -1.0); + v = GEAR_VERT(v, 6, -1); + v = GEAR_VERT(v, 5, -1); + v = GEAR_VERT(v, 4, -1); + v = GEAR_VERT(v, 3, -1); + v = GEAR_VERT(v, 2, -1); + v = GEAR_VERT(v, 1, -1); + v = GEAR_VERT(v, 0, -1); + END_STRIP; + + /* Outer face */ + START_STRIP; + QUAD_WITH_NORMAL(0, 2); + END_STRIP; + + START_STRIP; + QUAD_WITH_NORMAL(1, 0); + END_STRIP; + + START_STRIP; + QUAD_WITH_NORMAL(3, 1); + END_STRIP; + + START_STRIP; + QUAD_WITH_NORMAL(5, 3); + END_STRIP; + } + + gear->nvertices = (v - gear->vertices); + + /* Store the vertices in a vertex buffer object (VBO) */ + glGenBuffers(1, &gear->vbo); + glBindBuffer(GL_ARRAY_BUFFER, gear->vbo); + glBufferData(GL_ARRAY_BUFFER, gear->nvertices * sizeof(GearVertex), + gear->vertices, GL_STATIC_DRAW); + + return gear; +} + +/** + * Multiplies two 4x4 matrices. + * + * The result is stored in matrix m. + * + * @param m the first matrix to multiply + * @param n the second matrix to multiply + */ +static void +multiply(GLfloat *m, const GLfloat *n) +{ + GLfloat tmp[16]; + const GLfloat *row, *column; + div_t d; + int i, j; + + for (i = 0; i < 16; i++) { + tmp[i] = 0; + d = div(i, 4); + row = n + d.quot * 4; + column = m + d.rem; + for (j = 0; j < 4; j++) + tmp[i] += row[j] * column[j * 4]; + } + memcpy(m, &tmp, sizeof tmp); +} + +/** + * Rotates a 4x4 matrix. + * + * @param[in,out] m the matrix to rotate + * @param angle the angle to rotate + * @param x the x component of the direction to rotate to + * @param y the y component of the direction to rotate to + * @param z the z component of the direction to rotate to + */ +static void +rotate(GLfloat *m, GLfloat angle, GLfloat x, GLfloat y, GLfloat z) +{ + double s, c; + + sincos(angle, &s, &c); + GLfloat r[16] = { + x * x * (1 - c) + c, y * x * (1 - c) + z * s, x * z * (1 - c) - y * s, 0, + x * y * (1 - c) - z * s, y * y * (1 - c) + c, y * z * (1 - c) + x * s, 0, + x * z * (1 - c) + y * s, y * z * (1 - c) - x * s, z * z * (1 - c) + c, 0, + 0, 0, 0, 1 + }; + + multiply(m, r); +} + + +/** + * Translates a 4x4 matrix. + * + * @param[in,out] m the matrix to translate + * @param x the x component of the direction to translate to + * @param y the y component of the direction to translate to + * @param z the z component of the direction to translate to + */ +static void +translate(GLfloat *m, GLfloat x, GLfloat y, GLfloat z) +{ + GLfloat t[16] = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1 }; + + multiply(m, t); +} + +/** + * Creates an identity 4x4 matrix. + * + * @param m the matrix make an identity matrix + */ +static void +identity(GLfloat *m) +{ + GLfloat t[16] = { + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + }; + + memcpy(m, t, sizeof(t)); +} + +/** + * Transposes a 4x4 matrix. + * + * @param m the matrix to transpose + */ +static void +transpose(GLfloat *m) +{ + GLfloat t[16] = { + m[0], m[4], m[8], m[12], + m[1], m[5], m[9], m[13], + m[2], m[6], m[10], m[14], + m[3], m[7], m[11], m[15]}; + + memcpy(m, t, sizeof(t)); +} + +/** + * Inverts a 4x4 matrix. + * + * This function can currently handle only pure translation-rotation matrices. + * Read http://www.gamedev.net/community/forums/topic.asp?topic_id=425118 + * for an explanation. + */ +static void +invert(GLfloat *m) +{ + GLfloat t[16]; + identity(t); + + // Extract and invert the translation part 't'. The inverse of a + // translation matrix can be calculated by negating the translation + // coordinates. + t[12] = -m[12]; t[13] = -m[13]; t[14] = -m[14]; + + // Invert the rotation part 'r'. The inverse of a rotation matrix is + // equal to its transpose. + m[12] = m[13] = m[14] = 0; + transpose(m); + + // inv(m) = inv(r) * inv(t) + multiply(m, t); +} + +/** + * Calculate a perspective projection transformation. + * + * @param m the matrix to save the transformation in + * @param fovy the field of view in the y direction + * @param aspect the view aspect ratio + * @param zNear the near clipping plane + * @param zFar the far clipping plane + */ +void perspective(GLfloat *m, GLfloat fovy, GLfloat aspect, GLfloat zNear, GLfloat zFar) +{ + GLfloat tmp[16]; + identity(tmp); + + double sine, cosine, cotangent, deltaZ; + GLfloat radians = fovy / 2 * M_PI / 180; + + deltaZ = zFar - zNear; + sincos(radians, &sine, &cosine); + + if ((deltaZ == 0) || (sine == 0) || (aspect == 0)) + return; + + cotangent = cosine / sine; + + tmp[0] = cotangent / aspect; + tmp[5] = cotangent; + tmp[10] = -(zFar + zNear) / deltaZ; + tmp[11] = -1; + tmp[14] = -2 * zNear * zFar / deltaZ; + tmp[15] = 0; + + memcpy(m, tmp, sizeof(tmp)); +} + +/** + * Draws a gear. + * + * @param gear the gear to draw + * @param transform the current transformation matrix + * @param x the x position to draw the gear at + * @param y the y position to draw the gear at + * @param angle the rotation angle of the gear + * @param color the color of the gear + */ +static void +draw_gear(struct gear *gear, GLfloat *transform, + GLfloat x, GLfloat y, GLfloat angle, const GLfloat color[4]) +{ + GLfloat model_view[16]; + GLfloat normal_matrix[16]; + GLfloat model_view_projection[16]; + + /* Translate and rotate the gear */ + memcpy(model_view, transform, sizeof (model_view)); + translate(model_view, x, y, 0); + rotate(model_view, 2 * M_PI * angle / 360.0, 0, 0, 1); + + /* Create and set the ModelViewProjectionMatrix */ + memcpy(model_view_projection, ProjectionMatrix, sizeof(model_view_projection)); + multiply(model_view_projection, model_view); + + glUniformMatrix4fv(ModelViewProjectionMatrix_location, 1, GL_FALSE, + model_view_projection); + + /* + * Create and set the NormalMatrix. It's the inverse transpose of the + * ModelView matrix. + */ + memcpy(normal_matrix, model_view, sizeof (normal_matrix)); + invert(normal_matrix); + transpose(normal_matrix); + glUniformMatrix4fv(NormalMatrix_location, 1, GL_FALSE, normal_matrix); + + /* Set the gear color */ + glUniform4fv(MaterialColor_location, 1, color); + + /* Set the vertex buffer object to use */ + glBindBuffer(GL_ARRAY_BUFFER, gear->vbo); + + /* Set up the position of the attributes in the vertex buffer object */ + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, + 6 * sizeof(GLfloat), NULL); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, + 6 * sizeof(GLfloat), (GLfloat *) 0 + 3); + + /* Enable the attributes */ + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + /* Draw the triangle strips that comprise the gear */ + int n; + for (n = 0; n < gear->nstrips; n++) + glDrawArrays(GL_TRIANGLE_STRIP, gear->strips[n].first, gear->strips[n].count); + + /* Disable the attributes */ + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(0); +} + +/** + * Draws the gears. + */ +static void +gears_draw(void) +{ +#ifdef __EMSCRIPTEN__ + #include <emscripten.h> + glutIdleFunc (NULL); + glutReshapeFunc(NULL); + glutDisplayFunc(NULL); + glutSpecialFunc(NULL); +#endif + + const static GLfloat red[4] = { 0.8, 0.1, 0.0, 1.0 }; + const static GLfloat green[4] = { 0.0, 0.8, 0.2, 1.0 }; + const static GLfloat blue[4] = { 0.2, 0.2, 1.0, 1.0 }; + GLfloat transform[16]; + identity(transform); + + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + /* Translate and rotate the view */ + translate(transform, 0, 0, -20); + rotate(transform, 2 * M_PI * view_rot[0] / 360.0, 1, 0, 0); + rotate(transform, 2 * M_PI * view_rot[1] / 360.0, 0, 1, 0); + rotate(transform, 2 * M_PI * view_rot[2] / 360.0, 0, 0, 1); + + /* Draw the gears */ + draw_gear(gear1, transform, -3.0, -2.0, angle, red); + draw_gear(gear2, transform, 3.1, -2.0, -2 * angle - 9.0, green); + draw_gear(gear3, transform, -3.1, 4.2, -2 * angle - 25.0, blue); + + glutSwapBuffers(); + +#ifdef LONGTEST + glutPostRedisplay(); // check for issues with not throttling calls +#endif + + EM_ASM(dump('close!!!\n'); window.close()); +} + +/** + * Handles a new window size or exposure. + * + * @param width the window width + * @param height the window height + */ +static void +gears_reshape(int width, int height) +{ + /* Update the projection matrix */ + perspective(ProjectionMatrix, 60.0, width / (float)height, 1.0, 1024.0); + + /* Set the viewport */ + glViewport(0, 0, (GLint) width, (GLint) height); +} + +/** + * Handles special glut events. + * + * @param special the event to handle. + */ +static void +gears_special(int special, int crap, int morecrap) +{ + switch (special) { + case GLUT_KEY_LEFT: + view_rot[1] += 5.0; + break; + case GLUT_KEY_RIGHT: + view_rot[1] -= 5.0; + break; + case GLUT_KEY_UP: + view_rot[0] += 5.0; + break; + case GLUT_KEY_DOWN: + view_rot[0] -= 5.0; + break; + case GLUT_KEY_F11: + glutFullScreen(); + break; + } +} + +static void +gears_idle(void) +{ + static int frames = 0; + static double tRot0 = -1.0, tRate0 = -1.0; + double dt, t = glutGet(GLUT_ELAPSED_TIME) / 1000.0; + + if (tRot0 < 0.0) + tRot0 = t; + dt = t - tRot0; + tRot0 = t; + + /* advance rotation for next frame */ + angle += 70.0 * dt; /* 70 degrees per second */ + if (angle > 3600.0) + angle -= 3600.0; + + glutPostRedisplay(); + frames++; + + if (tRate0 < 0.0) + tRate0 = t; + if (t - tRate0 >= 5.0) { + GLfloat seconds = t - tRate0; + GLfloat fps = frames / seconds; + printf("%d frames in %3.1f seconds = %6.3f FPS\n", frames, seconds, + fps); + tRate0 = t; + frames = 0; +#ifdef LONGTEST + static runs = 0; + runs++; + if (runs == 4) { + int result = fps; + REPORT_RESULT(); + } +#endif + } +} + +static const char vertex_shader[] = +"attribute vec3 position;\n" +"attribute vec3 normal;\n" +"\n" +"uniform mat4 ModelViewProjectionMatrix;\n" +"uniform mat4 NormalMatrix;\n" +"uniform vec4 LightSourcePosition;\n" +"uniform vec4 MaterialColor;\n" +"\n" +"varying vec4 Color;\n" +"\n" +"void main(void)\n" +"{\n" +" // Transform the normal to eye coordinates\n" +" vec3 N = normalize(vec3(NormalMatrix * vec4(normal, 1.0)));\n" +"\n" +" // The LightSourcePosition is actually its direction for directional light\n" +" vec3 L = normalize(LightSourcePosition.xyz);\n" +"\n" +" // Multiply the diffuse value by the vertex color (which is fixed in this case)\n" +" // to get the actual color that we will use to draw this vertex with\n" +" float diffuse = max(dot(N, L), 0.0);\n" +" Color = diffuse * MaterialColor;\n" +"\n" +" // Transform the position to clip coordinates\n" +" gl_Position = ModelViewProjectionMatrix * vec4(position, 1.0);\n" +"}"; + +static const char fragment_shader[] = +"#ifdef GL_ES\n" +"precision mediump float;\n" +"#endif\n" +"varying vec4 Color;\n" +"\n" +"void main(void)\n" +"{\n" +" gl_FragColor = Color;\n" +"}"; + +static void +gears_init(void) +{ + GLuint v, f, program; + const char *p; + char msg[512]; + + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + + /* Compile the vertex shader */ + p = vertex_shader; + v = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(v, 1, &p, NULL); + glCompileShader(v); + glGetShaderInfoLog(v, sizeof msg, NULL, msg); + printf("vertex shader info: %s\n", msg); + + /* Compile the fragment shader */ + p = fragment_shader; + f = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(f, 1, &p, NULL); + glCompileShader(f); + glGetShaderInfoLog(f, sizeof msg, NULL, msg); + printf("fragment shader info: %s\n", msg); + + /* Create and link the shader program */ + program = glCreateProgram(); + glAttachShader(program, v); + glAttachShader(program, f); + glBindAttribLocation(program, 0, "position"); + glBindAttribLocation(program, 1, "normal"); + + glLinkProgram(program); + glGetProgramInfoLog(program, sizeof msg, NULL, msg); + printf("info: %s\n", msg); + + /* Enable the shaders */ + glUseProgram(program); + + /* Get the locations of the uniforms so we can access them */ + ModelViewProjectionMatrix_location = glGetUniformLocation(program, "ModelViewProjectionMatrix"); + NormalMatrix_location = glGetUniformLocation(program, "NormalMatrix"); + LightSourcePosition_location = glGetUniformLocation(program, "LightSourcePosition"); + MaterialColor_location = glGetUniformLocation(program, "MaterialColor"); + + /* Set the LightSourcePosition uniform which is constant throught the program */ + glUniform4fv(LightSourcePosition_location, 1, LightSourcePosition); + + /* make the gears */ + gear1 = create_gear(1.0, 4.0, 1.0, 20, 0.7); + gear2 = create_gear(0.5, 2.0, 2.0, 10, 0.7); + gear3 = create_gear(1.3, 2.0, 0.5, 10, 0.7); +} + +int +main(int argc, char *argv[]) +{ + /* Initialize the window */ + glutInit(&argc, argv); + glutInitWindowSize(300, 300); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); + + glutCreateWindow("es2gears"); + + /* Set up glut callback functions */ + glutIdleFunc (gears_idle); + glutReshapeFunc(gears_reshape); + glutDisplayFunc(gears_draw); + glutSpecialFunc(gears_special); + + /* Initialize the gears */ + gears_init(); + + glutMainLoop(); + + return 0; +} diff --git a/tests/runner.py b/tests/runner.py index 87f8a036..8d9de0a3 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -662,15 +662,26 @@ class BrowserCore(RunnerCore): Module['preRun'].push(function() { setTimeout(doReftest, 1000); // if run() throws an exception and postRun is not called, this will kick in }); + + if (typeof WebGLClient !== 'undefined') { + // trigger reftest from RAF as well, needed for workers where there is no pre|postRun on the main thread + var realRAF = window.requestAnimationFrame; + window.requestAnimationFrame = function(func) { + realRAF(func); + setTimeout(doReftest, 1000); + }; + } + ''' % basename) def btest(self, filename, expected=None, reference=None, force_c=False, reference_slack=0, manual_reference=False, post_build=None, - args=[], outfile='test.html', message='.'): # TODO: use in all other tests + args=[], outfile='test.html', message='.', also_proxied=False): # TODO: use in all other tests # if we are provided the source and not a path, use that filename_is_src = '\n' in filename src = filename if filename_is_src else '' filepath = path_from_root('tests', filename) if not filename_is_src else ('main.c' if force_c else 'main.cpp') temp_filepath = os.path.join(self.get_dir(), os.path.basename(filepath)) + original_args = args[:] if filename_is_src: with open(temp_filepath, 'w') as f: f.write(src) if not reference: @@ -678,16 +689,33 @@ class BrowserCore(RunnerCore): with open(filepath) as f: src = f.read() with open(temp_filepath, 'w') as f: f.write(self.with_report_result(src)) else: + self.reference = reference expected = [str(i) for i in range(0, reference_slack+1)] shutil.copyfile(filepath, temp_filepath) self.reftest(path_from_root('tests', reference)) if not manual_reference: args = args + ['--pre-js', 'reftest.js', '-s', 'GL_TESTING=1'] - Popen([PYTHON, EMCC, temp_filepath, '-o', outfile] + args).communicate() + all_args = [PYTHON, EMCC, temp_filepath, '-o', outfile] + args + #print 'all args:', all_args + Popen(all_args).communicate() assert os.path.exists(outfile) if post_build: post_build() if type(expected) is str: expected = [expected] self.run_browser(outfile, message, ['/report_result?' + e for e in expected]) + if also_proxied: + print 'proxied...' + # save non-proxied + if not os.path.exists('normal'): + os.mkdir('normal') + shutil.copyfile('test.html', os.path.join('normal', 'test.html')) + shutil.copyfile('test.js', os.path.join('normal', 'test.js')) + if reference: + assert not manual_reference + manual_reference = True + assert not post_build + post_build = self.post_manual_reftest + # run proxied + self.btest(filename, expected, reference, force_c, reference_slack, manual_reference, post_build, original_args + ['--proxy-to-worker', '-s', 'GL_TESTING=1'], outfile, message) ################################################################################################### diff --git a/tests/sdlglshader2.c b/tests/sdlglshader2.c new file mode 100644 index 00000000..c75a0f5f --- /dev/null +++ b/tests/sdlglshader2.c @@ -0,0 +1,168 @@ +/* +THIS WORK, INCLUDING THE SOURCE CODE, DOCUMENTATION +AND RELATED MEDIA AND DATA, IS PLACED INTO THE PUBLIC DOMAIN. + +THE ORIGINAL AUTHOR IS KYLE FOLEY. + +THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY +OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF +MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, +ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE +RESULTING FROM THE USE, MODIFICATION, OR +REDISTRIBUTION OF THIS SOFTWARE. +*/ + +#include "SDL/SDL.h" +#include "SDL/SDL_opengl.h" + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +// GL_ARB_shading_language_100, GL_ARB_shader_objects, GL_ARB_fragment_shader, GL_ARB_vertex_shader +PFNGLCREATEPROGRAMOBJECTARBPROC glCreateProgramObject_ = NULL; +PFNGLDELETEOBJECTARBPROC glDeleteObject_ = NULL; +PFNGLUSEPROGRAMOBJECTARBPROC glUseProgramObject_ = NULL; +PFNGLCREATESHADEROBJECTARBPROC glCreateShaderObject_ = NULL; +PFNGLSHADERSOURCEARBPROC glShaderSource_ = NULL; +PFNGLCOMPILESHADERARBPROC glCompileShader_ = NULL; +PFNGLGETOBJECTPARAMETERIVARBPROC glGetObjectParameteriv_ = NULL; +PFNGLATTACHOBJECTARBPROC glAttachObject_ = NULL; +PFNGLGETINFOLOGARBPROC glGetInfoLog_ = NULL; +PFNGLLINKPROGRAMARBPROC glLinkProgram_ = NULL; +PFNGLGETUNIFORMLOCATIONARBPROC glGetUniformLocation_ = NULL; +PFNGLUNIFORM1FARBPROC glUniform1f_ = NULL; +PFNGLUNIFORM2FARBPROC glUniform2f_ = NULL; +PFNGLUNIFORM3FARBPROC glUniform3f_ = NULL; +PFNGLUNIFORM4FARBPROC glUniform4f_ = NULL; +PFNGLUNIFORM1FVARBPROC glUniform1fv_ = NULL; +PFNGLUNIFORM2FVARBPROC glUniform2fv_ = NULL; +PFNGLUNIFORM3FVARBPROC glUniform3fv_ = NULL; +PFNGLUNIFORM4FVARBPROC glUniform4fv_ = NULL; +PFNGLUNIFORM1IARBPROC glUniform1i_ = NULL; +PFNGLBINDATTRIBLOCATIONARBPROC glBindAttribLocation_ = NULL; +PFNGLGETACTIVEUNIFORMARBPROC glGetActiveUniform_ = NULL; + +void initARB() { + glCreateProgramObject_ = (PFNGLCREATEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glCreateProgramObjectARB"); + glDeleteObject_ = (PFNGLDELETEOBJECTARBPROC) SDL_GL_GetProcAddress("glDeleteObjectARB"); + glUseProgramObject_ = (PFNGLUSEPROGRAMOBJECTARBPROC) SDL_GL_GetProcAddress("glUseProgramObjectARB"); + glCreateShaderObject_ = (PFNGLCREATESHADEROBJECTARBPROC) SDL_GL_GetProcAddress("glCreateShaderObjectARB"); + glShaderSource_ = (PFNGLSHADERSOURCEARBPROC) SDL_GL_GetProcAddress("glShaderSourceARB"); + glCompileShader_ = (PFNGLCOMPILESHADERARBPROC) SDL_GL_GetProcAddress("glCompileShaderARB"); + glGetObjectParameteriv_ = (PFNGLGETOBJECTPARAMETERIVARBPROC) SDL_GL_GetProcAddress("glGetObjectParameterivARB"); + glAttachObject_ = (PFNGLATTACHOBJECTARBPROC) SDL_GL_GetProcAddress("glAttachObjectARB"); + glGetInfoLog_ = (PFNGLGETINFOLOGARBPROC) SDL_GL_GetProcAddress("glGetInfoLogARB"); + glLinkProgram_ = (PFNGLLINKPROGRAMARBPROC) SDL_GL_GetProcAddress("glLinkProgramARB"); + glGetUniformLocation_ = (PFNGLGETUNIFORMLOCATIONARBPROC) SDL_GL_GetProcAddress("glGetUniformLocationARB"); + glUniform1f_ = (PFNGLUNIFORM1FARBPROC) SDL_GL_GetProcAddress("glUniform1fARB"); + glUniform2f_ = (PFNGLUNIFORM2FARBPROC) SDL_GL_GetProcAddress("glUniform2fARB"); + glUniform3f_ = (PFNGLUNIFORM3FARBPROC) SDL_GL_GetProcAddress("glUniform3fARB"); + glUniform4f_ = (PFNGLUNIFORM4FARBPROC) SDL_GL_GetProcAddress("glUniform4fARB"); + glUniform1fv_ = (PFNGLUNIFORM1FVARBPROC) SDL_GL_GetProcAddress("glUniform1fvARB"); + glUniform2fv_ = (PFNGLUNIFORM2FVARBPROC) SDL_GL_GetProcAddress("glUniform2fvARB"); + glUniform3fv_ = (PFNGLUNIFORM3FVARBPROC) SDL_GL_GetProcAddress("glUniform3fvARB"); + glUniform4fv_ = (PFNGLUNIFORM4FVARBPROC) SDL_GL_GetProcAddress("glUniform4fvARB"); + glUniform1i_ = (PFNGLUNIFORM1IARBPROC) SDL_GL_GetProcAddress("glUniform1iARB"); + glBindAttribLocation_ = (PFNGLBINDATTRIBLOCATIONARBPROC) SDL_GL_GetProcAddress("glBindAttribLocationARB"); + glGetActiveUniform_ = (PFNGLGETACTIVEUNIFORMARBPROC) SDL_GL_GetProcAddress("glGetActiveUniformARB"); +} + +void setShaders() { + GLuint v, f, p; + GLint ok; + + const char *vv = "#pragma CUBE2_uniform animdata AnimData 0 16\n" + "uniform vec4 animdata[246];\n" + "void main() \n" + "{ \n" + " gl_Position = ftransform() + animdata[0]; \n" + "}"; + const char *ff = "void main() \n" + "{ \n" + " gl_FragColor = vec4(gl_FragCoord.y/480.0, gl_FragCoord.x/640.0, 0.66, 1.0); \n" + "}"; + + v = glCreateShaderObject_(GL_VERTEX_SHADER); + f = glCreateShaderObject_(GL_FRAGMENT_SHADER); + + glShaderSource_(v, 1, &vv,NULL); + glShaderSource_(f, 1, &ff,NULL); + + glCompileShader_(v); + glGetObjectParameteriv_(v, GL_OBJECT_COMPILE_STATUS_ARB, &ok); + if (!ok) { + char msg[512]; + glGetShaderInfoLog(v, sizeof msg, NULL, msg); + printf("shader compilation issue: %s\n", msg); + } + assert(ok); + + glCompileShader_(f); + glGetObjectParameteriv_(f, GL_OBJECT_COMPILE_STATUS_ARB, &ok); + assert(ok); + + p = glCreateProgramObject_(); + glAttachObject_(p,f); + glAttachObject_(p,v); + + glLinkProgram_(p); + glGetObjectParameteriv_(p, GL_OBJECT_LINK_STATUS_ARB, &ok); + assert(ok); + + int loc = glGetUniformLocation_(p, "animdata"); + printf("loc: %d\n", loc); + assert(loc > 0); + int loc2 = glGetUniformLocation_(p, "animdata[0]"); + printf("loc2: %d\n", loc2); + assert(loc2 == loc); + + glUseProgramObject_(p); +} + +int main(int argc, char *argv[]) +{ + SDL_Surface *screen; + + assert(SDL_Init(SDL_INIT_VIDEO) == 0); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + screen = SDL_SetVideoMode( 640, 480, 16, SDL_OPENGL ); + assert(screen); + + glClearColor(0, 0, 0, 0); + glViewport(0, 0, 640, 480); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, 640, 480, 0, -1, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glClear(GL_COLOR_BUFFER_BIT); + + initARB(); + setShaders(); + + glColor3f(0, 1, 1); // is overridden by the shader, useful for debugging native builds + glBegin( GL_TRIANGLES ); + glTexCoord2i(0, 0); glVertex3f( 10, 10, 0); + glTexCoord2i(1, 0); glVertex3f( 300, 10, 0); + glTexCoord2i(1, 1); glVertex3f( 300, 328, 0); + glEnd(); + + glColor3f(1, 1, 0); // is overridden by the shader, useful for debugging native builds + glBegin( GL_TRIANGLES ); + glTexCoord2f(0, 0.5); glVertex3f(410, 10, 0); + glTexCoord2f(1, 0.5); glVertex3f(600, 10, 0); + glTexCoord2f(1, 1 ); glVertex3f(630, 400, 0); + glEnd(); + + SDL_GL_SwapBuffers(); + +#ifdef REPORT_RESULT + int result = 1; + REPORT_RESULT(); +#endif + + SDL_Quit(); + return 0; +} + diff --git a/tests/test_browser.py b/tests/test_browser.py index 42a1c0a0..80f94f2b 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -719,10 +719,11 @@ If manually bisecting: self.clear() self.btest('sdl_canvas.c', expected='1', args=['-s', 'LEGACY_GL_EMULATION=1', '-O2', '-s', 'SAFE_HEAP=1']) - def test_sdl_canvas_proxy(self): - def post(): - html = open('test.html').read() - html = html.replace('</body>', ''' + def post_manual_reftest(self, reference=None): + self.reftest(path_from_root('tests', self.reference if reference is None else reference)) + + html = open('test.html').read() + html = html.replace('</body>', ''' <script> function assert(x, y) { if (!x) throw 'assertion failed ' + y } @@ -738,11 +739,21 @@ window.close = function() { }; </script> </body>''' % open('reftest.js').read()) - open('test.html', 'w').write(html) + open('test.html', 'w').write(html) + def test_sdl_canvas_proxy(self): open('data.txt', 'w').write('datum') + self.btest('sdl_canvas_proxy.c', reference='sdl_canvas_proxy.png', args=['--proxy-to-worker', '--preload-file', 'data.txt'], manual_reference=True, post_build=self.post_manual_reftest) - self.btest('sdl_canvas_proxy.c', reference='sdl_canvas_proxy.png', args=['--proxy-to-worker', '--preload-file', 'data.txt'], manual_reference=True, post_build=post) + def test_glgears_proxy(self): + self.btest('hello_world_gles_proxy.c', reference='gears.png', args=['--proxy-to-worker', '-s', 'GL_TESTING=1'], manual_reference=True, post_build=self.post_manual_reftest) + + def test_glgears_proxy_jstarget(self): + # test .js target with --proxy-worker; emits 2 js files, client and worker + Popen([PYTHON, EMCC, path_from_root('tests', 'hello_world_gles_proxy.c'), '-o', 'test.js', '--proxy-to-worker', '-s', 'GL_TESTING=1']).communicate() + open('test.html', 'w').write(open(path_from_root('src', 'shell_minimal.html')).read().replace('{{{ SCRIPT }}}', '<script src="test.js"></script>')) + self.post_manual_reftest('gears.png') + self.run_browser('test.html', None, '/report_result?0') def test_sdl_canvas_alpha(self): self.btest('sdl_canvas_alpha.c', reference='sdl_canvas_alpha.png', reference_slack=9) @@ -1307,7 +1318,9 @@ keydown(100);keyup(100); // trigger the end message='You should see animating gears.') def test_glgears_long(self): - self.btest('hello_world_gles.c', expected=map(str, range(30, 1000)), args=['-DHAVE_BUILTIN_SINCOS', '-DLONGTEST']) + for proxy in [0, 1]: + print 'proxy', proxy + self.btest('hello_world_gles.c', expected=map(str, range(30, 500)), args=['-DHAVE_BUILTIN_SINCOS', '-DLONGTEST'] + (['--proxy-to-worker'] if proxy else [])) def test_glgears_animation(self): es2_suffix = ['', '_full', '_full_944'] @@ -1427,6 +1440,9 @@ keydown(100);keyup(100); // trigger the end def test_sdlglshader(self): self.btest('sdlglshader.c', reference='sdlglshader.png', args=['-O2', '--closure', '1', '-s', 'LEGACY_GL_EMULATION=1']) + def test_sdlglshader2(self): + self.btest('sdlglshader2.c', expected='1', args=['-s', 'LEGACY_GL_EMULATION=1'], also_proxied=True) + def test_gl_glteximage(self): self.btest('gl_teximage.c', '1') @@ -1456,9 +1472,8 @@ keydown(100);keyup(100); // trigger the end def test_gl_vertex_buffer(self): self.btest('gl_vertex_buffer.c', reference='gl_vertex_buffer.png', args=['-s', 'GL_UNSAFE_OPTS=0', '-s', 'LEGACY_GL_EMULATION=1'], reference_slack=1) - # Does not pass due to https://bugzilla.mozilla.org/show_bug.cgi?id=924264 so disabled for now. - # def test_gles2_uniform_arrays(self): - # self.btest('gles2_uniform_arrays.cpp', args=['-s', 'GL_ASSERTIONS=1'], expected=['1']) + def test_gles2_uniform_arrays(self): + self.btest('gles2_uniform_arrays.cpp', args=['-s', 'GL_ASSERTIONS=1'], expected=['1'], also_proxied=True) def test_gles2_conformance(self): self.btest('gles2_conformance.cpp', args=['-s', 'GL_ASSERTIONS=1'], expected=['1']) @@ -1476,7 +1491,7 @@ keydown(100);keyup(100); // trigger the end self.btest('cubegeom_pre3.c', reference='cubegeom_pre2.png', args=['-s', 'LEGACY_GL_EMULATION=1']) def test_cubegeom(self): - self.btest('cubegeom.c', reference='cubegeom.png', args=['-O2', '-g', '-s', 'LEGACY_GL_EMULATION=1']) + self.btest('cubegeom.c', reference='cubegeom.png', args=['-O2', '-g', '-s', 'LEGACY_GL_EMULATION=1'], also_proxied=True) def test_cubegeom_proc(self): open('side.c', 'w').write(r''' @@ -1500,10 +1515,10 @@ void *getBindBuffer() { self.btest('cubegeom_color.c', reference='cubegeom_color.png', args=['-s', 'LEGACY_GL_EMULATION=1']) def test_cubegeom_normal(self): - self.btest('cubegeom_normal.c', reference='cubegeom_normal.png', args=['-s', 'LEGACY_GL_EMULATION=1']) + self.btest('cubegeom_normal.c', reference='cubegeom_normal.png', args=['-s', 'LEGACY_GL_EMULATION=1'], also_proxied=True) def test_cubegeom_normal_dap(self): # draw is given a direct pointer to clientside memory, no element array buffer - self.btest('cubegeom_normal_dap.c', reference='cubegeom_normal.png', args=['-s', 'LEGACY_GL_EMULATION=1']) + self.btest('cubegeom_normal_dap.c', reference='cubegeom_normal.png', args=['-s', 'LEGACY_GL_EMULATION=1'], also_proxied=True) def test_cubegeom_normal_dap_far(self): # indices do nto start from 0 self.btest('cubegeom_normal_dap_far.c', reference='cubegeom_normal.png', args=['-s', 'LEGACY_GL_EMULATION=1']) @@ -1521,7 +1536,7 @@ void *getBindBuffer() { self.btest('cubegeom_mt.c', reference='cubegeom_mt.png', args=['-s', 'LEGACY_GL_EMULATION=1']) # multitexture def test_cubegeom_color2(self): - self.btest('cubegeom_color2.c', reference='cubegeom_color2.png', args=['-s', 'LEGACY_GL_EMULATION=1']) + self.btest('cubegeom_color2.c', reference='cubegeom_color2.png', args=['-s', 'LEGACY_GL_EMULATION=1'], also_proxied=True) def test_cubegeom_texturematrix(self): self.btest('cubegeom_texturematrix.c', reference='cubegeom_texturematrix.png', args=['-s', 'LEGACY_GL_EMULATION=1']) @@ -1539,7 +1554,7 @@ void *getBindBuffer() { self.btest('cubegeom_pre2_vao2.c', reference='cubegeom_pre2_vao2.png', args=['-s', 'LEGACY_GL_EMULATION=1']) def test_cube_explosion(self): - self.btest('cube_explosion.c', reference='cube_explosion.png', args=['-s', 'LEGACY_GL_EMULATION=1']) + self.btest('cube_explosion.c', reference='cube_explosion.png', args=['-s', 'LEGACY_GL_EMULATION=1'], also_proxied=True) def test_glgettexenv(self): self.btest('glgettexenv.c', args=['-s', 'LEGACY_GL_EMULATION=1'], expected=['1']) |