diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/library_egl.js | 2 | ||||
-rw-r--r-- | src/library_gl.js | 83 | ||||
-rw-r--r-- | src/library_glut.js | 252 | ||||
-rw-r--r-- | src/preamble.js | 1 | ||||
-rw-r--r-- | src/shell.html | 3 |
5 files changed, 290 insertions, 51 deletions
diff --git a/src/library_egl.js b/src/library_egl.js index 6ad226f0..f1558f80 100644 --- a/src/library_egl.js +++ b/src/library_egl.js @@ -12,7 +12,7 @@ var LibraryEGL = { return 1; }, - eglMakeCurrent: function(display, surface, surface, context) { return 1 }, + eglMakeCurrent: function(display, surface, surface_, context) { return 1 }, eglSwapBuffers: function() {}, }; diff --git a/src/library_gl.js b/src/library_gl.js index 414520e0..6f99969e 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -24,6 +24,35 @@ var LibraryGL = { } return 0; }, + + // Find a token in a shader source string + findToken: function(source, token) { + function isIdentChar(ch) { + if (ch >= 48 && ch <= 57) // 0-9 + return true; + if (ch >= 65 && ch <= 90) // A-Z + return true; + if (ch >= 97 && ch <= 122) // a-z + return true; + return false; + } + var i = -1; + do { + i = source.indexOf(token, i + 1); + if (i < 0) { + break; + } + if (i > 0 && isIdentChar(source[i - 1])) { + continue; + } + i += token.length; + if (i < source.length - 1 && isIdentChar(source[i + 1])) { + continue; + } + return true; + } while (true); + return false; + }, }, glGetString: function(name_) { @@ -223,7 +252,7 @@ var LibraryGL = { case 0x8033 /* GL_UNSIGNED_SHORT_4_4_4_4 */: case 0x8034 /* GL_UNSIGNED_SHORT_5_5_5_1 */: sizePerPixel = 2; - pixels = new Uint16Array(new ArrayBuffer(Array_copy(pixels, width*height*sizePerPixel*2))); + pixels = new Uint16Array(new ArrayBuffer(Array_copy(pixels, width*height*sizePerPixel))); break; default: throw 'Invalid type (' + type + ') passed to glTexImage2D'; @@ -256,13 +285,13 @@ var LibraryGL = { default: throw 'Invalid format (' + format + ') passed to glTexSubImage2D'; } - pixels = new Uint8Array(Array_copy(pixels, (width-xoffset+1)*(height-yoffset+1)*sizePerPixel)); + pixels = new Uint8Array(Array_copy(pixels, width*height*sizePerPixel)); break; case 0x8363 /* GL_UNSIGNED_SHORT_5_6_5 */: case 0x8033 /* GL_UNSIGNED_SHORT_4_4_4_4 */: case 0x8034 /* GL_UNSIGNED_SHORT_5_5_5_1 */: sizePerPixel = 2; - pixels = new Uint16Array(new ArrayBuffer(Array_copy(pixels, (width-xoffset+1)*(height-yoffset+1)*sizePerPixel*2))); + pixels = new Uint16Array(new ArrayBuffer(Array_copy(pixels, width*height*sizePerPixel))); break; default: throw 'Invalid type (' + type + ') passed to glTexSubImage2D'; @@ -407,6 +436,33 @@ var LibraryGL = { Module.ctx.uniform4i(location, v0, v1, v2, v3); }, + glUniform1iv: function(location, count, value) { + location = GL.uniforms[location]; + value = new Int32Array(TypedArray_copy(value, count*4)); // TODO: optimize + Module.ctx.uniform1iv(location, value); + }, + + glUniform2iv: function(location, count, value) { + location = GL.uniforms[location]; + count *= 2; + value = new Int32Array(TypedArray_copy(value, count*4)); // TODO: optimize + Module.ctx.uniform2iv(location, value); + }, + + glUniform3iv: function(location, count, value) { + location = GL.uniforms[location]; + count *= 3; + value = new Int32Array(TypedArray_copy(value, count*4)); // TODO: optimize + Module.ctx.uniform3iv(location, value); + }, + + glUniform4iv: function(location, count, value) { + location = GL.uniforms[location]; + count *= 4; + value = new Int32Array(TypedArray_copy(value, count*4)); // TODO: optimize + Module.ctx.uniform4iv(location, value); + }, + glUniform1fv: function(location, count, value) { location = GL.uniforms[location]; value = new Float32Array(TypedArray_copy(value, count*4)); // TODO: optimize @@ -529,6 +585,21 @@ var LibraryGL = { } source += frag; } + // Let's see if we need to enable the standard derivatives extension + type = Module.ctx.getShaderParameter(GL.shaders[shader], 0x8B4F /* GL_SHADER_TYPE */); + if (type == 0x8B30 /* GL_FRAGMENT_SHADER */) { + if (GL.findToken(source, "dFdx") || + GL.findToken(source, "dFdy") || + GL.findToken(source, "fwidth")) { + source = "#extension GL_OES_standard_derivatives : enable\n" + source; + var extension = Module.ctx.getExtension("OES_standard_derivatives"); +#if GL_DEBUG + if (!extension) { + Module.printErr("Shader attempts to use the standard derivatives extension which is not available."); + } +#endif + } + } Module.ctx.shaderSource(GL.shaders[shader], source); }, @@ -682,10 +753,10 @@ var LibraryGL = { // Simple pass-through functions [[0, 'shadeModel fogi fogfv getError finish flush'], - [1, 'clearDepth depthFunc enable disable frontFace cullFace clear enableVertexAttribArray disableVertexAttribArray lineWidth clearStencil depthMask stencilMask stencilMaskSeparate checkFramebufferStatus generateMipmap activeTexture'], - [2, 'pixelStorei'], + [1, 'clearDepth depthFunc enable disable frontFace cullFace clear enableVertexAttribArray disableVertexAttribArray lineWidth clearStencil depthMask stencilMask stencilMaskSeparate checkFramebufferStatus generateMipmap activeTexture blendEquation'], + [2, 'pixelStorei blendFunc blendEquationSeparate'], [3, 'texParameteri texParameterf drawArrays vertexAttrib2f'], - [4, 'viewport clearColor scissor vertexAttrib3f colorMask drawElements renderbufferStorage'], + [4, 'viewport clearColor scissor vertexAttrib3f colorMask drawElements renderbufferStorage blendFuncSeparate'], [5, 'vertexAttrib4f'], [6, 'vertexAttribPointer'], [8, 'copyTexImage2D copyTexSubImage2D']].forEach(function(data) { diff --git a/src/library_glut.js b/src/library_glut.js index 0736d5ae..5d9fb672 100644 --- a/src/library_glut.js +++ b/src/library_glut.js @@ -2,6 +2,7 @@ var LibraryGLUT = { $GLUT: { initTime: null, + idleFunc: null, displayFunc: null, keyboardFunc: null, keyboardUpFunc: null, @@ -15,6 +16,14 @@ var LibraryGLUT = { lastY: 0, buttons: 0, modifiers: 0, + initWindowWidth: 256, + initWindowHeight: 256, + + savePosition: function(event) { + /* TODO maybe loop here ala http://www.quirksmode.org/js/findpos.html */ + GLUT.lastX = event['clientX'] - Module['canvas'].offsetLeft; + GLUT.lastY = event['clientY'] - Module['canvas'].offsetTop; + }, saveModifiers: function(event) { GLUT.modifiers = 0; @@ -27,9 +36,8 @@ var LibraryGLUT = { }, onMousemove: function(event) { - GLUT.lastX = event['clientX']; - GLUT.lastY = event['clientY']; - if (GLUT.buttons == 0 && GLUT.passiveMotionFunc) { + GLUT.savePosition(event); + if (GLUT.buttons == 0 && event.target == Module["canvas"] && GLUT.passiveMotionFunc) { event.preventDefault(); GLUT.saveModifiers(event); FUNCTION_TABLE[GLUT.passiveMotionFunc](GLUT.lastX, GLUT.lastY); @@ -68,9 +76,42 @@ var LibraryGLUT = { return key; }, - getASCIIKey: function(keycode) { - // TODO apply modifiers, etc - return keycode; + getASCIIKey: function(event) { + if (event['ctrlKey'] || event['altKey'] || event['metaKey']) return null; + + var keycode = event['keyCode']; + + /* The exact list is soooo hard to find in a canonical place! */ + + if (48 <= keycode && keycode <= 57) + return keycode; // numeric TODO handle shift? + if (65 <= keycode && keycode <= 90) + return event['shiftKey'] ? keycode : keycode + 32; + if (106 <= keycode && keycode <= 111) + return keycode - 106 + 42; // *,+-./ TODO handle shift? + + switch (keycode) { + case 27: // escape + case 32: // space + case 61: // equal + return keycode; + } + + var s = event['shiftKey']; + switch (keycode) { + case 186: return s ? 58 : 59; // colon / semi-colon + case 187: return s ? 43 : 61; // add / equal (these two may be wrong) + case 188: return s ? 60 : 44; // less-than / comma + case 189: return s ? 95 : 45; // dash + case 190: return s ? 62 : 46; // greater-than / period + case 191: return s ? 63 : 47; // forward slash + case 219: return s ? 123 : 91; // open bracket + case 220: return s ? 124 : 47; // back slash + case 221: return s ? 125 : 93; // close braket + case 222: return s ? 34 : 39; // single quote + } + + return null; }, onKeydown: function(event) { @@ -85,11 +126,11 @@ var LibraryGLUT = { } else { - key = GLUT.getASCIIKey(event['keyCode']); + key = GLUT.getASCIIKey(event); if( key !== null && GLUT.keyboardFunc ) { event.preventDefault(); GLUT.saveModifiers(event); - FUNCTION_TABLE[GLUT.keyboardFunc](event['keyCode'], GLUT.lastX, GLUT.lastY); + FUNCTION_TABLE[GLUT.keyboardFunc](key, GLUT.lastX, GLUT.lastY); } } } @@ -107,22 +148,24 @@ var LibraryGLUT = { } else { - key = GLUT.getASCIIKey(event['keyCode']); + key = GLUT.getASCIIKey(event); if( key !== null && GLUT.keyboardUpFunc ) { event.preventDefault (); GLUT.saveModifiers(event); - FUNCTION_TABLE[GLUT.keyboardUpFunc](event['keyCode'], GLUT.lastX, GLUT.lastY); + FUNCTION_TABLE[GLUT.keyboardUpFunc](key, GLUT.lastX, GLUT.lastY); } } } }, onMouseButtonDown: function(event){ - GLUT.lastX = event['clientX']; - GLUT.lastY = event['clientY']; + GLUT.savePosition(event); GLUT.buttons |= (1 << event['button']); - if(GLUT.mouseFunc){ + if(event.target == Module["canvas"] && GLUT.mouseFunc){ + try { + event.target.setCapture(); + } catch (e) {} event.preventDefault(); GLUT.saveModifiers(event); FUNCTION_TABLE[GLUT.mouseFunc](event['button'], 0/*GLUT_DOWN*/, GLUT.lastX, GLUT.lastY); @@ -130,8 +173,7 @@ var LibraryGLUT = { }, onMouseButtonUp: function(event){ - GLUT.lastX = event['clientX']; - GLUT.lastY = event['clientY']; + GLUT.savePosition(event); GLUT.buttons &= ~(1 << event['button']); if(GLUT.mouseFunc) { @@ -140,6 +182,48 @@ var LibraryGLUT = { FUNCTION_TABLE[GLUT.mouseFunc](event['button'], 1/*GLUT_UP*/, GLUT.lastX, GLUT.lastY); } }, + + requestFullScreen: function() { + var RFS = function() {}; + if (Module["canvas"]['requestFullscreen']) { + RFS = Module["canvas"]['requestFullscreen']; + } else if (Module["canvas"]['requestFullScreen']) { + RFS = Module["canvas"]['requestFullScreen']; + } else if (Module["canvas"]['mozRequestFullScreen']) { + RFS = Module["canvas"]['mozRequestFullScreen']; + } else if (Module["canvas"]['webkitRequestFullScreen']) { + RFS = Module["canvas"]['webkitRequestFullScreen']; + } + RFS.apply(Module["canvas"], []); + }, + + cancelFullScreen: function() { + var CFS = function() {}; + if (document['exitFullscreen']) { + CFS = document['exitFullscreen']; + } else if (document['cancelFullScreen']) { + CFS = document['cancelFullScreen']; + } else if (document['mozCancelFullScreen']) { + CFS = document['mozCancelFullScreen']; + } else if (document['webkitCancelFullScreen']) { + CFS = document['webkitCancelFullScreen']; + } + CFS.apply(document, []); + }, + + requestAnimationFrame: function(func) { + var RAF = window['setTimeout']; + if (window['requestAnimationFrame']) { + RAF = window['requestAnimationFrame']; + } else if (window['mozRequestAnimationFrame']) { + RAF = window['mozRequestAnimationFrame']; + } else if (window['webkitRequestAnimationFrame']) { + RAF = window['webkitRequestAnimationFrame']; + } else if (window['msRequestAnimationFrame']) { + RAF = window['msRequestAnimationFrame']; + } + RAF.apply(window, [func]); + }, }, glutGetModifiers: function() { return GLUT.modifiers; }, @@ -147,30 +231,50 @@ var LibraryGLUT = { glutInit: function(argcp, argv) { // Ignore arguments GLUT.initTime = Date.now(); - window.addEventListener("keydown", GLUT.onKeydown, true); - window.addEventListener("keyup", GLUT.onKeyup, true); - window.addEventListener("mousemove", GLUT.onMousemove, true); - window.addEventListener("mousedown", GLUT.onMouseButtonDown, true); - window.addEventListener("mouseup", GLUT.onMouseButtonUp, true); }, glutInitWindowSize: function(width, height) { - Module['canvas'].width = width; - Module['canvas'].height = height; + Module['canvas'].width = GLUT.initWindowWidth = width; + Module['canvas'].height = GLUT.initWindowHeight = height; }, glutGet: function(type) { switch (type) { + case 100: /* GLUT_WINDOW_X */ + return 0; /* TODO */ + case 101: /* GLUT_WINDOW_Y */ + return 0; /* TODO */ + case 102: /* GLUT_WINDOW_WIDTH */ + return Module['canvas'].width; + case 103: /* GLUT_WINDOW_HEIGHT */ + return Module['canvas'].height; + case 500: /* GLUT_INIT_WINDOW_X */ + return 0; /* TODO */ + case 501: /* GLUT_INIT_WINDOW_Y */ + return 0; /* TODO */ + case 502: /* GLUT_INIT_WINDOW_WIDTH */ + return GLUT.initWindowWidth; + case 503: /* GLUT_INIT_WINDOW_HEIGHT */ + return GLUT.initWindowHeight; case 700: /* GLUT_ELAPSED_TIME */ var now = Date.now(); return now - GLUT.initTime; + default: throw "glutGet(" + type + ") not implemented yet"; } }, glutIdleFunc: function(func) { - window.setTimeout(FUNCTION_TABLE[func], 0); + var callback = function() { + if (GLUT.idleFunc) { + FUNCTION_TABLE[GLUT.idleFunc](); + window.setTimeout(callback, 0); + } + } + if (!GLUT.idleFunc) + window.setTimeout(callback, 0); + GLUT.idleFunc = func; }, glutTimerFunc: function(msec, func, value) { @@ -216,9 +320,10 @@ var LibraryGLUT = { glutCreateWindow: function(name) { #if USE_TYPED_ARRAYS try { - var ctx = Module.canvas.getContext('experimental-webgl'); + var ctx = Module["canvas"].getContext('experimental-webgl'); if (!ctx) throw 'Could not create canvas :('; #if GL_DEBUG + // Useful to debug native webgl apps: var Module = { printErr: function(x) { console.log(x) } }; var wrapper = {}; wrapper.objectMap = new WeakMap(); wrapper.objectCounter = 1; @@ -229,7 +334,31 @@ var LibraryGLUT = { wrapper[prop] = function() { var printArgs = Array.prototype.slice.call(arguments).map(function(arg) { if (wrapper.objectMap[arg]) return '<' + arg + '|' + wrapper.objectMap[arg] + '>'; - if (arg.subarray) return '{' + arg + '|' + arg.length /*+ '|' + Array.prototype.slice.call(arg).toString().replace(/,/g, ', ')*/ + '}'; + if (arg.toString() == '[object HTMLImageElement]') { + return arg + '\n\n'; + } + if (arg.byteLength) { + var buf = new ArrayBuffer(32); + var i8buf = new Int8Array(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; + default: + alert('unknown array for debugging: ' + arg); + throw 'see alert'; + } + 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; + } return arg; }); Module.printErr('[gl_f:' + prop + ':' + printArgs + ']'); @@ -264,7 +393,7 @@ var LibraryGLUT = { #endif // Set the background of the canvas to black, because glut gives us a // window which has a black background by default. - Module.canvas.style.backgroundColor = "black"; + Module["canvas"].style.backgroundColor = "black"; } catch (e) { Module.print('(canvas not available)'); } @@ -274,33 +403,70 @@ var LibraryGLUT = { return 1; }, + glutReshapeWindow__deps: ['$GLUT', 'glutPostRedisplay'], + glutReshapeWindow: function(width, height) { + GLUT.cancelFullScreen(); + Module['canvas'].width = width; + Module['canvas'].height = height; + if (GLUT.reshapeFunc) { + FUNCTION_TABLE[GLUT.reshapeFunc](width, height); + } + _glutPostRedisplay(); + }, + + glutPositionWindow__deps: ['$GLUT', 'glutPostRedisplay'], + glutPositionWindow: function(x, y) { + GLUT.cancelFullScreen(); + /* TODO */ + _glutPostRedisplay(); + }, + + glutFullScreen__deps: ['$GLUT', 'glutPostRedisplay'], + glutFullScreen: function() { + var width = screen["width"]; + var height = screen["height"]; + /* Can't call _glutReshapeWindow as that requests cancelling fullscreen. */ + Module['canvas'].width = width; + Module['canvas'].height = height; + if (GLUT.reshapeFunc) { + FUNCTION_TABLE[GLUT.reshapeFunc](width, height); + } + GLUT.requestFullScreen(); + window.setTimeout(function() { + _glutPostRedisplay(); + }, 0); + }, + glutInitDisplayMode: function(mode) {}, glutSwapBuffers: function() {}, glutPostRedisplay: function() { if (GLUT.displayFunc) { - var RAF = window['setTimeout']; - if (window['requestAnimationFrame']) { - RAF = window['requestAnimationFrame']; - } else if (window['mozRequestAnimationFrame']) { - RAF = window['mozRequestAnimationFrame']; - } else if (window['webkitRequestAnimationFrame']) { - RAF = window['webkitRequestAnimationFrame']; - } else if (window['msRequestAnimationFrame']) { - RAF = window['msRequestAnimationFrame']; - } - RAF.apply(window, [FUNCTION_TABLE[GLUT.displayFunc]]); + GLUT.requestAnimationFrame(FUNCTION_TABLE[GLUT.displayFunc]); } }, - glutMainLoop__deps: ['$GLUT', 'glutPostRedisplay'], + glutMainLoop__deps: ['$GLUT', 'glutReshapeWindow', 'glutPostRedisplay'], glutMainLoop: function() { - if (GLUT.reshapeFunc) { - FUNCTION_TABLE[GLUT.reshapeFunc](Module['canvas'].width, - Module['canvas'].height); - } + + window.addEventListener("keydown", GLUT.onKeydown, true); + window.addEventListener("keyup", GLUT.onKeyup, true); + window.addEventListener("mousemove", GLUT.onMousemove, true); + window.addEventListener("mousedown", GLUT.onMouseButtonDown, true); + window.addEventListener("mouseup", GLUT.onMouseButtonUp, true); + + __ATEXIT__.push({ func: function() { + window.removeEventListener("keydown", GLUT.onKeydown, true); + window.removeEventListener("keyup", GLUT.onKeyup, true); + window.removeEventListener("mousemove", GLUT.onMousemove, true); + window.removeEventListener("mousedown", GLUT.onMouseButtonDown, true); + window.removeEventListener("mouseup", GLUT.onMouseButtonUp, true); + Module["canvas"].width = Module["canvas"].height = 1; + } }); + + _glutReshapeWindow(Module['canvas'].width, Module['canvas'].height); _glutPostRedisplay(); - throw 'GLUT mainloop should never return'; + throw 'GLUT mainloop called, simulating infinite loop by throwing so we get right into the JS event loop'; }, }; diff --git a/src/preamble.js b/src/preamble.js index c88a4671..39412bc3 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -368,6 +368,7 @@ function cwrap(ident, returnType, argTypes) { return ccall(ident, returnType, argTypes, Array.prototype.slice.call(arguments)); } } +Module["cwrap"] = cwrap; // Sets a value in memory in a dynamic way at run-time. Uses the // type data. This is the same as makeSetValue, except that diff --git a/src/shell.html b/src/shell.html index 4c7b2147..37509889 100644 --- a/src/shell.html +++ b/src/shell.html @@ -4,7 +4,8 @@ <title>Emscripten-Generated Code</title> <body> <center> - <canvas id='canvas' width='256' height='256'></canvas> + <canvas id='canvas' width='256' height='256' style="border: 1px solid black" + oncontextmenu="event.preventDefault()"></canvas> <hr> <textarea id="output" style="font-family: monospace; width: 80%" rows="8"></textarea> <hr> |