diff options
-rw-r--r-- | AUTHORS | 1 | ||||
-rwxr-xr-x | emscripten.py | 26 | ||||
-rw-r--r-- | src/intertyper.js | 3 | ||||
-rw-r--r-- | src/jsifier.js | 31 | ||||
-rw-r--r-- | src/library.js | 41 | ||||
-rw-r--r-- | src/library_gl.js | 80 | ||||
-rw-r--r-- | src/library_sdl.js | 234 | ||||
-rw-r--r-- | src/modules.js | 48 | ||||
-rw-r--r-- | src/parseTools.js | 99 | ||||
-rw-r--r-- | src/preamble.js | 17 | ||||
-rw-r--r-- | src/runtime.js | 4 | ||||
-rw-r--r-- | src/settings.js | 8 | ||||
-rw-r--r-- | src/struct_info.json | 15 | ||||
-rw-r--r-- | src/utility.js | 3 | ||||
-rw-r--r-- | system/include/emscripten/emscripten.h | 2 | ||||
-rw-r--r-- | tests/cases/storebigfloat.ll | 17 | ||||
-rw-r--r-- | tests/emscripten_get_now.cpp | 6 | ||||
-rw-r--r-- | tests/lua/Makefile | 3 | ||||
-rw-r--r-- | tests/lua/src/Makefile | 3 | ||||
-rwxr-xr-x | tests/runner.py | 2 | ||||
-rw-r--r-- | tests/sdl_joystick.c | 124 | ||||
-rw-r--r-- | tests/test_benchmark.py | 14 | ||||
-rw-r--r-- | tests/test_browser.py | 76 | ||||
-rw-r--r-- | tests/test_core.py | 53 | ||||
-rw-r--r-- | tools/eliminator/eliminator-test-output.js | 3 | ||||
-rw-r--r-- | tools/eliminator/eliminator-test.js | 10 | ||||
-rw-r--r-- | tools/js-optimizer.js | 142 | ||||
-rw-r--r-- | tools/response_file.py | 6 | ||||
-rw-r--r-- | tools/shared.py | 43 |
29 files changed, 879 insertions, 235 deletions
@@ -107,3 +107,4 @@ a license to everyone to use it as detailed in LICENSE.) * Michael Tirado <icetooth333@gmail.com> * Ben Noordhuis <info@bnoordhuis.nl> * Bob Roberts <bobroberts177@gmail.com> +* John Vilk <jvilk@cs.umass.edu> diff --git a/emscripten.py b/emscripten.py index 5576baba..7c9cbdbe 100755 --- a/emscripten.py +++ b/emscripten.py @@ -11,6 +11,7 @@ headers, for the libc implementation in JS). import os, sys, json, optparse, subprocess, re, time, multiprocessing, string, logging +from tools import shared from tools import jsrun, cache as cache_module, tempfiles from tools.response_file import read_response_file @@ -25,7 +26,6 @@ def get_configuration(): if hasattr(get_configuration, 'configuration'): return get_configuration.configuration - from tools import shared configuration = shared.Configuration(environ=os.environ) get_configuration.configuration = configuration return configuration @@ -425,8 +425,8 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, Counter.i += 1 bad = 'b' + str(i) params = ','.join(['p%d' % p for p in range(len(sig)-1)]) - coercions = ';'.join(['p%d = %sp%d%s' % (p, '+' if sig[p+1] != 'i' else '', p, '' if sig[p+1] != 'i' else '|0') for p in range(len(sig)-1)]) + ';' - ret = '' if sig[0] == 'v' else ('return %s0' % ('+' if sig[0] != 'i' else '')) + coercions = ';'.join(['p%d = %s' % (p, shared.JS.make_coercion('p%d' % p, sig[p+1], settings)) for p in range(len(sig)-1)]) + ';' + ret = '' if sig[0] == 'v' else ('return %s' % shared.JS.make_initializer(sig[0], settings)) start = raw.index('[') end = raw.rindex(']') body = raw[start+1:end].split(',') @@ -451,7 +451,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, math_envs = ['Math.min'] # TODO: move min to maths asm_setup += '\n'.join(['var %s = %s;' % (f.replace('.', '_'), f) for f in math_envs]) - if settings['TO_FLOAT32']: maths += ['Math.toFloat32'] + if settings['PRECISE_F32']: maths += ['Math.fround'] basic_funcs = ['abort', 'assert', 'asmPrintInt', 'asmPrintFloat'] + [m.replace('.', '_') for m in math_envs] if settings['RESERVED_FUNCTION_POINTERS'] > 0: basic_funcs.append('jsCall') @@ -476,18 +476,14 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, asm_runtime_funcs = ['stackAlloc', 'stackSave', 'stackRestore', 'setThrew'] + ['setTempRet%d' % i for i in range(10)] # function tables - def asm_coerce(value, sig): - if sig == 'v': return value - return ('+' if sig != 'i' else '') + value + ('|0' if sig == 'i' else '') - function_tables = ['dynCall_' + table for table in last_forwarded_json['Functions']['tables']] function_tables_impls = [] for sig in last_forwarded_json['Functions']['tables'].iterkeys(): args = ','.join(['a' + str(i) for i in range(1, len(sig))]) - arg_coercions = ' '.join(['a' + str(i) + '=' + asm_coerce('a' + str(i), sig[i]) + ';' for i in range(1, len(sig))]) - coerced_args = ','.join([asm_coerce('a' + str(i), sig[i]) for i in range(1, len(sig))]) - ret = ('return ' if sig[0] != 'v' else '') + asm_coerce('FUNCTION_TABLE_%s[index&{{{ FTM_%s }}}](%s)' % (sig, sig, coerced_args), sig[0]) + arg_coercions = ' '.join(['a' + str(i) + '=' + shared.JS.make_coercion('a' + str(i), sig[i], settings) + ';' for i in range(1, len(sig))]) + coerced_args = ','.join([shared.JS.make_coercion('a' + str(i), sig[i], settings) for i in range(1, len(sig))]) + ret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('FUNCTION_TABLE_%s[index&{{{ FTM_%s }}}](%s)' % (sig, sig, coerced_args), sig[0], settings) function_tables_impls.append(''' function dynCall_%s(index%s%s) { index = index|0; @@ -497,7 +493,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, ''' % (sig, ',' if len(sig) > 1 else '', args, arg_coercions, ret)) for i in range(settings['RESERVED_FUNCTION_POINTERS']): - jsret = ('return ' if sig[0] != 'v' else '') + asm_coerce('jsCall(%d%s%s)' % (i, ',' if coerced_args else '', coerced_args), sig[0]) + jsret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('jsCall(%d%s%s)' % (i, ',' if coerced_args else '', coerced_args), sig[0], settings) function_tables_impls.append(''' function jsCall_%s_%s(%s) { %s @@ -505,7 +501,6 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, } ''' % (sig, i, args, arg_coercions, jsret)) - from tools import shared shared.Settings.copy(settings) asm_setup += '\n' + shared.JS.make_invoke(sig) + '\n' basic_funcs.append('invoke_%s' % sig) @@ -585,7 +580,7 @@ var asm = (function(global, env, buffer) { var undef = 0; var tempInt = 0, tempBigInt = 0, tempBigIntP = 0, tempBigIntS = 0, tempBigIntR = 0.0, tempBigIntI = 0, tempBigIntD = 0, tempValue = 0, tempDouble = 0.0; ''' + ''.join([''' - var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs + ''' + var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs] + [' var tempFloat = %s;\n' % ('Math_fround(0)' if settings.get('PRECISE_F32') else '0.0')] + [''' // EMSCRIPTEN_START_FUNCS function stackAlloc(size) { size = size|0; @@ -727,14 +722,12 @@ def main(args, compiler_engine, cache, jcache, relooper, temp_files, DEBUG, DEBU relooper = cache.get_path('relooper.js') settings.setdefault('RELOOPER', relooper) if not os.path.exists(relooper): - from tools import shared shared.Building.ensure_relooper(relooper) settings.setdefault('STRUCT_INFO', cache.get_path('struct_info.compiled.json')) struct_info = settings.get('STRUCT_INFO') if not os.path.exists(struct_info): - from tools import shared shared.Building.ensure_struct_info(struct_info) emscript(args.infile, settings, args.outfile, libraries, compiler_engine=compiler_engine, @@ -833,7 +826,6 @@ WARNING: You should normally never use this! Use emcc instead. temp_files = tempfiles.TempFiles(temp_dir) if keywords.compiler is None: - from tools import shared keywords.compiler = shared.COMPILER_ENGINE if keywords.verbose is None: diff --git a/src/intertyper.js b/src/intertyper.js index fceeb38d..fa53c652 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -680,6 +680,9 @@ function intertyper(lines, sidePass, baseLineNums) { args.push(ident); }); } + item.ident = expandLLVMString(item.ident).replace(/(#[^\n]*)/g, function(m) { + return '/* ' + m.substr(1) + ' */'; // fix asm comments to js comments + }); if (item.assignTo) item.ident = 'return ' + item.ident; item.ident = '(function(' + params + ') { ' + item.ident + ' })(' + args + ');'; return { ret: item, item: item }; diff --git a/src/jsifier.js b/src/jsifier.js index 0da48a8c..97317756 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -756,14 +756,7 @@ function JSify(data, functionsOnly, givenFunctions) { if (func.setjmpTable && !ASM_JS) { ret += ' } catch(e) { if (!e.longjmp || !(e.id in mySetjmpIds)) throw(e); setjmpTable[setjmpLabels[e.id]](e.value) }'; } - if (ASM_JS && func.returnType !== 'void') { - // Add a return - if (func.returnType in Runtime.FLOAT_TYPES) { - ret += ' return +0;\n'; - } else { - ret += ' return 0;\n'; - } - } + if (ASM_JS && func.returnType !== 'void') ret += ' return ' + asmInitializer(func.returnType) + ';\n'; // Add a return } else { ret += (SHOW_LABELS ? indent + '/* ' + block.entries[0] + ' */' : '') + '\n' + getLabelLines(block.labels[0]); } @@ -833,11 +826,7 @@ function JSify(data, functionsOnly, givenFunctions) { var lastReturn = func.JS.lastIndexOf('return '); if ((lastCurly < 0 && lastReturn < 0) || // no control flow, no return (lastCurly >= 0 && lastReturn < lastCurly)) { // control flow, no return past last join - if (func.returnType in Runtime.FLOAT_TYPES) { - func.JS += ' return +0;\n'; - } else { - func.JS += ' return 0;\n'; - } + func.JS += ' return ' + asmInitializer(func.returnType) + ';\n'; } } func.JS += '}\n'; @@ -1337,7 +1326,7 @@ function JSify(data, functionsOnly, givenFunctions) { if (isNumber(item.ident)) { // Direct read from a memory address; this may be an intentional segfault, if not, it is a bug in the source if (ASM_JS) { - return asmCoercion('abort(' + item.ident + ')', item.type); + return asmFFICoercion('abort(' + item.ident + ')', item.type); } else { item.assignTo = null; return 'throw "fault on read from ' + item.ident + '";'; @@ -1514,8 +1503,10 @@ function JSify(data, functionsOnly, givenFunctions) { args = args.map(function(arg, i) { return indexizeFunctions(arg, argsTypes[i]) }); if (ASM_JS) { - if (shortident in Functions.libraryFunctions || simpleIdent in Functions.libraryFunctions || byPointerForced || invoke || extCall || funcData.setjmpTable) { - args = args.map(function(arg, i) { return asmCoercion(arg, argsTypes[i]) }); + var ffiCall = (shortident in Functions.libraryFunctions || simpleIdent in Functions.libraryFunctions || byPointerForced || invoke || extCall || funcData.setjmpTable) && + !(simpleIdent in JS_MATH_BUILTINS); + if (ffiCall) { + args = args.map(function(arg, i) { return asmCoercion(arg, ensureValidFFIType(argsTypes[i])) }); } else { args = args.map(function(arg, i) { return asmEnsureFloat(arg, argsTypes[i]) }); } @@ -1592,7 +1583,7 @@ function JSify(data, functionsOnly, givenFunctions) { returnType = getReturnType(type); if (callIdent in Functions.implementedFunctions) { // LLVM sometimes bitcasts for no reason. We must call using the exact same type as the actual function is generated as - var trueType = Functions.getSignatureReturnType(Functions.implementedFunctions[callIdent]); + var trueType = Functions.getSignatureType(Functions.implementedFunctions[callIdent][0]); if (trueType !== returnType && !isIdenticallyImplemented(trueType, returnType)) { if (VERBOSE) warnOnce('Fixing function call based on return type from signature, on ' + [callIdent, returnType, trueType]); returnType = trueType; @@ -1628,7 +1619,11 @@ function JSify(data, functionsOnly, givenFunctions) { var ret = callIdent + '(' + args.join(',') + ')'; if (ASM_JS) { // TODO: do only when needed (library functions and Math.*?) XXX && simpleIdent in Functions.libraryFunctions) { - ret = asmCoercion(ret, returnType); + if (ffiCall) { + ret = asmFFICoercion(ret, returnType); + } else { + ret = asmCoercion(ret, returnType); + } if (simpleIdent == 'abort' && funcData.returnType != 'void') { ret += '; return ' + asmCoercion('0', funcData.returnType); // special case: abort() can happen without return, breaking the return type of asm functions. ensure a return } diff --git a/src/library.js b/src/library.js index ccf62b27..b25e48ed 100644 --- a/src/library.js +++ b/src/library.js @@ -847,10 +847,7 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.ERANGE); return 0; } else { - for (var i = 0; i < cwd.length; i++) { - {{{ makeSetValue('buf', 'i', 'cwd.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('buf', 'i', '0', 'i8') }}} + writeAsciiToMemory(cwd, buf); return buf; } }, @@ -1293,10 +1290,7 @@ LibraryManager.library = { if (namesize < ret.length + 1) { return ___setErrNo(ERRNO_CODES.ERANGE); } else { - for (var i = 0; i < ret.length; i++) { - {{{ makeSetValue('name', 'i', 'ret.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('name', 'i', '0', 'i8') }}} + writeAsciiToMemory(ret, name); return 0; } }, @@ -2699,10 +2693,7 @@ LibraryManager.library = { var result = dir + '/' + name; if (!_tmpnam.buffer) _tmpnam.buffer = _malloc(256); if (!s) s = _tmpnam.buffer; - for (var i = 0; i < result.length; i++) { - {{{ makeSetValue('s', 'i', 'result.charCodeAt(i)', 'i8') }}}; - } - {{{ makeSetValue('s', 'i', '0', 'i8') }}}; + writeAsciiToMemory(result, s); return s; }, tempnam__deps: ['tmpnam'], @@ -3343,10 +3334,7 @@ LibraryManager.library = { var ptrSize = {{{ Runtime.getNativeTypeSize('i8*') }}}; for (var i = 0; i < strings.length; i++) { var line = strings[i]; - for (var j = 0; j < line.length; j++) { - {{{ makeSetValue('poolPtr', 'j', 'line.charCodeAt(j)', 'i8') }}}; - } - {{{ makeSetValue('poolPtr', 'j', '0', 'i8') }}}; + writeAsciiToMemory(line, poolPtr); {{{ makeSetValue('envPtr', 'i * ptrSize', 'poolPtr', 'i8*') }}}; poolPtr += line.length + 1; } @@ -3976,10 +3964,7 @@ LibraryManager.library = { return ___setErrNo(ERRNO_CODES.ERANGE); } else { var msg = ERRNO_MESSAGES[errnum]; - for (var i = 0; i < msg.length; i++) { - {{{ makeSetValue('strerrbuf', 'i', 'msg.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('strerrbuf', 'i', 0, 'i8') }}} + writeAsciiToMemory(msg, strerrbuf); return 0; } } else { @@ -5067,10 +5052,7 @@ LibraryManager.library = { var layout = {{{ JSON.stringify(C_STRUCTS.utsname) }}}; function copyString(element, value) { var offset = layout[element]; - for (var i = 0; i < value.length; i++) { - {{{ makeSetValue('name', 'offset + i', 'value.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('name', 'offset + i', '0', 'i8') }}} + writeAsciiToMemory(value, name + offset); } if (name === 0) { return -1; @@ -6131,8 +6113,10 @@ LibraryManager.library = { // int nanosleep(const struct timespec *rqtp, struct timespec *rmtp); var seconds = {{{ makeGetValue('rqtp', C_STRUCTS.timespec.tv_sec, 'i32') }}}; var nanoseconds = {{{ makeGetValue('rqtp', C_STRUCTS.timespec.tv_nsec, 'i32') }}}; - {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_sec, '0', 'i32') }}} - {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_nsec, '0', 'i32') }}} + if (rmtp !== 0) { + {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_sec, '0', 'i32') }}} + {{{ makeSetValue('rmtp', C_STRUCTS.timespec.tv_nsec, '0', 'i32') }}} + } return _usleep((seconds * 1e6) + (nanoseconds / 1000)); }, // TODO: Implement these for real. @@ -6572,10 +6556,7 @@ LibraryManager.library = { var me = _nl_langinfo; if (!me.ret) me.ret = _malloc(32); - for (var i = 0; i < result.length; i++) { - {{{ makeSetValue('me.ret', 'i', 'result.charCodeAt(i)', 'i8') }}} - } - {{{ makeSetValue('me.ret', 'i', '0', 'i8') }}} + writeAsciiToMemory(result, me.ret); return me.ret; }, diff --git a/src/library_gl.js b/src/library_gl.js index ecb72f0f..4b6ea579 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -530,7 +530,11 @@ var LibraryGL = { ret = allocate(intArrayFromString('OpenGL ES GLSL 1.00 (WebGL)'), 'i8', ALLOC_NORMAL); break; default: - throw 'Failure: Invalid glGetString value: ' + name_; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetString: Unknown parameter ' + name_ + '!'); +#endif + return 0; } GL.stringCache[name_] = ret; return ret; @@ -561,7 +565,11 @@ var LibraryGL = { {{{ makeSetValue('p', '0', 'result ? 1 : 0', 'i8') }}}; break; case "string": - throw 'Native code calling glGetIntegerv(' + name_ + ') on a name which returns a string!'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Native code calling glGetIntegerv(' + name_ + ') on a name which returns a string!'); +#endif + return; case "object": if (result === null) { {{{ makeSetValue('p', '0', '0', 'i32') }}}; @@ -583,13 +591,19 @@ var LibraryGL = { } else if (result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', 'result.name | 0', 'i32') }}}; } else { - throw 'Unknown object returned from WebGL getParameter'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Unknown object returned from WebGL getParameter(' + name_ + ')!'); +#endif + return; } break; - case "undefined": - throw 'Native code calling glGetIntegerv(' + name_ + ') and it returns undefined'; default: - throw 'Why did we hit the default case?'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetIntegerv: Native code calling glGetIntegerv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } }, @@ -607,7 +621,11 @@ var LibraryGL = { {{{ makeSetValue('p', '0', '0', 'float') }}}; case "object": if (result === null) { - throw 'Native code calling glGetFloatv(' + name_ + ') and it returns null'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns null!'); +#endif + return; } else if (result instanceof Float32Array || result instanceof Uint32Array || result instanceof Int32Array || @@ -626,13 +644,19 @@ var LibraryGL = { } else if (result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', 'result.name | 0', 'float') }}}; } else { - throw 'Unknown object returned from WebGL getParameter'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } break; - case "undefined": - throw 'Native code calling glGetFloatv(' + name_ + ') and it returns undefined'; default: - throw 'Why did we hit the default case?'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetFloatv: Native code calling glGetFloatv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } }, @@ -647,7 +671,11 @@ var LibraryGL = { {{{ makeSetValue('p', '0', 'result != 0', 'i8') }}}; break; case "string": - throw 'Native code calling glGetBooleanv(' + name_ + ') on a name which returns a string!'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Native code calling glGetBooleanv(' + name_ + ') on a name which returns a string!'); +#endif + return; case "object": if (result === null) { {{{ makeSetValue('p', '0', '0', 'i8') }}}; @@ -665,13 +693,19 @@ var LibraryGL = { result instanceof WebGLTexture) { {{{ makeSetValue('p', '0', '1', 'i8') }}}; // non-zero ID is always 1! } else { - throw 'Unknown object returned from WebGL getParameter'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Unknown object returned from WebGL getParameter(' + name_ + ')!'); +#endif + return; } break; - case "undefined": - throw 'Unknown object returned from WebGL getParameter'; default: - throw 'Why did we hit the default case?'; + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetBooleanv: Native code calling glGetBooleanv(' + name_ + ') and it returns ' + result + ' of type ' + typeof(result) + '!'); +#endif + return; } }, @@ -759,7 +793,12 @@ var LibraryGL = { case 0x1908 /* GL_RGBA */: sizePerPixel = 4; break; - default: throw 'unsupported glReadPixels format'; + default: + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glReadPixels: Unsupported format ' + format + '!'); +#endif + return; } var totalSize = width*height*sizePerPixel; Module.ctx.readPixels(x, y, width, height, format, type, HEAPU8.subarray(pixels, pixels + totalSize)); @@ -2191,7 +2230,12 @@ var LibraryGL = { attribute = GLImmediate.clientAttributes[GLImmediate.COLOR]; break; case 0x8092: // GL_TEXTURE_COORD_ARRAY_POINTER attribute = GLImmediate.clientAttributes[GLImmediate.TEXTURE0 + GLImmediate.clientActiveTexture]; break; - default: throw 'TODO: glGetPointerv for ' + name; + default: + GL.recordError(0x0500/*GL_INVALID_ENUM*/); +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glGetPointerv: Unsupported name ' + name + '!'); +#endif + return; } {{{ makeSetValue('p', '0', 'attribute ? attribute.pointer : 0', 'i32') }}}; }, diff --git a/src/library_sdl.js b/src/library_sdl.js index 5b43b7ab..c46364ff 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -75,6 +75,7 @@ var LibrarySDL = { textInput: false, startTime: null, + initFlags: 0, // The flags passed to SDL_Init buttonState: 0, modState: 0, DOMButtons: [0, 0, 0], @@ -639,6 +640,21 @@ var LibrarySDL = { {{{ makeSetValue('ptr', C_STRUCTS.SDL_ResizeEvent.h, 'event.h', 'i32') }}}; break; } + case 'joystick_button_up': case 'joystick_button_down': { + var state = event.type === 'joystick_button_up' ? 0 : 1; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.which, 'event.index', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.button, 'event.button', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyButtonEvent.state, 'state', 'i8') }}}; + break; + } + case 'joystick_axis_motion': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.which, 'event.index', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.axis, 'event.axis', 'i8') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}}; + break; + } default: throw 'Unhandled SDL event: ' + event.type; } }, @@ -695,7 +711,109 @@ var LibrarySDL = { for (var i = 0; i < num; i++) { console.log(' diagonal ' + i + ':' + [data[i*surfData.width*4 + i*4 + 0], data[i*surfData.width*4 + i*4 + 1], data[i*surfData.width*4 + i*4 + 2], data[i*surfData.width*4 + i*4 + 3]]); } - } + }, + + // Joystick helper methods and state + + joystickEventState: 0, + lastJoystickState: {}, // Map from SDL_Joystick* to their last known state. Required to determine if a change has occurred. + // Maps Joystick names to pointers. Allows us to avoid reallocating memory for + // joystick names each time this function is called. + joystickNamePool: {}, + recordJoystickState: function(joystick, state) { + // Standardize button state. + var buttons = new Array(state.buttons.length); + for (var i = 0; i < state.buttons.length; i++) { + buttons[i] = SDL.getJoystickButtonState(state.buttons[i]); + } + + SDL.lastJoystickState[joystick] = { + buttons: buttons, + axes: state.axes.slice(0), + timestamp: state.timestamp, + index: state.index, + id: state.id + }; + }, + // Retrieves the button state of the given gamepad button. + // Abstracts away implementation differences. + // Returns 'true' if pressed, 'false' otherwise. + getJoystickButtonState: function(button) { + if (typeof button === 'object') { + // Current gamepad API editor's draft (Firefox Nightly) + // https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#idl-def-GamepadButton + return button.pressed; + } else { + // Current gamepad API working draft (Firefox / Chrome Stable) + // http://www.w3.org/TR/2012/WD-gamepad-20120529/#gamepad-interface + return button > 0; + } + }, + // Queries for and inserts controller events into the SDL queue. + queryJoysticks: function() { + for (var joystick in SDL.lastJoystickState) { + var state = SDL.getGamepad(joystick - 1); + var prevState = SDL.lastJoystickState[joystick]; + // Check only if the timestamp has differed. + // NOTE: Timestamp is not available in Firefox. + if (typeof state.timestamp !== 'number' || state.timestamp !== prevState.timestamp) { + var i; + for (i = 0; i < state.buttons.length; i++) { + var buttonState = SDL.getJoystickButtonState(state.buttons[i]); + // NOTE: The previous state already has a boolean representation of + // its button, so no need to standardize its button state here. + if (buttonState !== prevState.buttons[i]) { + // Insert button-press event. + SDL.events.push({ + type: buttonState ? 'joystick_button_down' : 'joystick_button_up', + joystick: joystick, + index: joystick - 1, + button: i + }); + } + } + for (i = 0; i < state.axes.length; i++) { + if (state.axes[i] !== prevState.axes[i]) { + // Insert axes-change event. + SDL.events.push({ + type: 'joystick_axis_motion', + joystick: joystick, + index: joystick - 1, + axis: i, + value: state.axes[i] + }); + } + } + + SDL.recordJoystickState(joystick, state); + } + } + }, + // Converts the double-based browser axis value [-1, 1] into SDL's 16-bit + // value [-32768, 32767] + joystickAxisValueConversion: function(value) { + // Ensures that 0 is 0, 1 is 32767, and -1 is 32768. + return Math.ceil(((value+1) * 32767.5) - 32768); + }, + + getGamepads: function() { + var fcn = navigator.getGamepads || navigator.webkitGamepads || navigator.mozGamepads || navigator.gamepads || navigator.webkitGetGamepads; + if (fcn !== undefined) { + // The function must be applied on the navigator object. + return fcn.apply(navigator); + } else { + return []; + } + }, + + // Helper function: Returns the gamepad if available, or null if not. + getGamepad: function(deviceIndex) { + var gamepads = SDL.getGamepads(); + if (gamepads.length > deviceIndex && deviceIndex >= 0) { + return gamepads[deviceIndex]; + } + return null; + }, }, SDL_Linked_Version: function() { @@ -708,8 +826,10 @@ var LibrarySDL = { return SDL.version; }, - SDL_Init: function(what) { + SDL_Init: function(initFlags) { SDL.startTime = Date.now(); + SDL.initFlags = initFlags; + // capture all key events. we just keep down and up, but also capture press to prevent default actions if (!Module['doNotCaptureKeyboard']) { document.addEventListener("keydown", SDL.receiveEvent); @@ -718,6 +838,15 @@ var LibrarySDL = { window.addEventListener("blur", SDL.receiveEvent); document.addEventListener("visibilitychange", SDL.receiveEvent); } + + if (initFlags & 0x200) { + // SDL_INIT_JOYSTICK + // Firefox will not give us Joystick data unless we register this NOP + // callback. + // https://bugzilla.mozilla.org/show_bug.cgi?id=936104 + addEventListener("gamepadconnected", function() {}); + } + window.addEventListener("unload", SDL.receiveEvent); SDL.keyboardState = _malloc(0x10000); // Our SDL needs 512, but 64K is safe for older SDLs _memset(SDL.keyboardState, 0, 0x10000); @@ -730,6 +859,12 @@ var LibrarySDL = { SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */; SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */; SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */; + // These are not technically DOM events; the HTML gamepad API is poll-based. + // However, we define them here, as the rest of the SDL code assumes that + // all SDL events originate as DOM events. + SDL.DOMEventToSDLEvent['joystick_axis_motion'] = 0x600 /* SDL_JOYAXISMOTION */; + SDL.DOMEventToSDLEvent['joystick_button_down'] = 0x603 /* SDL_JOYBUTTONDOWN */; + SDL.DOMEventToSDLEvent['joystick_button_up'] = 0x604 /* SDL_JOYBUTTONUP */; return 0; // success }, @@ -1189,6 +1324,11 @@ var LibrarySDL = { }, SDL_PollEvent: function(ptr) { + if (SDL.initFlags & 0x200 && SDL.joystickEventState) { + // If SDL_INIT_JOYSTICK was supplied AND the joystick system is configured + // to automatically query for events, query for joystick events. + SDL.queryJoysticks(); + } if (SDL.events.length === 0) return 0; if (ptr) { SDL.makeCEvent(SDL.events.shift(), ptr); @@ -2375,37 +2515,103 @@ var LibrarySDL = { // Joysticks - SDL_NumJoysticks: function() { return 0; }, + SDL_NumJoysticks: function() { + var count = 0; + var gamepads = SDL.getGamepads(); + // The length is not the number of gamepads; check which ones are defined. + for (var i = 0; i < gamepads.length; i++) { + if (gamepads[i] !== undefined) count++; + } + return count; + }, - SDL_JoystickName: function(deviceIndex) { return 0; }, + SDL_JoystickName: function(deviceIndex) { + var gamepad = SDL.getGamepad(deviceIndex); + if (gamepad) { + var name = gamepad.id; + if (SDL.joystickNamePool.hasOwnProperty(name)) { + return SDL.joystickNamePool[name]; + } + return SDL.joystickNamePool[name] = allocate(intArrayFromString(name), 'i8', ALLOC_NORMAL); + } + return 0; + }, - SDL_JoystickOpen: function(deviceIndex) { return 0; }, + SDL_JoystickOpen: function(deviceIndex) { + var gamepad = SDL.getGamepad(deviceIndex); + if (gamepad) { + // Use this as a unique 'pointer' for this joystick. + var joystick = deviceIndex+1; + SDL.recordJoystickState(joystick, gamepad); |