aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/library_egl.js2
-rw-r--r--src/library_gl.js83
-rw-r--r--src/library_glut.js252
-rw-r--r--src/preamble.js1
-rw-r--r--src/shell.html3
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>