diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/analyzer.js | 4 | ||||
| -rw-r--r-- | src/embind/embind.js | 620 | ||||
| -rw-r--r-- | src/embind/emval.js | 111 | ||||
| -rw-r--r-- | src/jsifier.js | 14 | ||||
| -rw-r--r-- | src/library.js | 271 | ||||
| -rw-r--r-- | src/library_browser.js | 66 | ||||
| -rw-r--r-- | src/library_egl.js | 421 | ||||
| -rw-r--r-- | src/library_gl.js | 11 | ||||
| -rw-r--r-- | src/library_jansson.js | 225 | ||||
| -rw-r--r-- | src/library_sdl.js | 37 | ||||
| -rw-r--r-- | src/modules.js | 2 | ||||
| -rw-r--r-- | src/parseTools.js | 8 | ||||
| -rw-r--r-- | src/preamble.js | 8 | ||||
| -rw-r--r-- | src/runtime.js | 2 | ||||
| -rw-r--r-- | src/settings.js | 9 |
15 files changed, 1762 insertions, 47 deletions
diff --git a/src/analyzer.js b/src/analyzer.js index 69b811f0..9d060a2c 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -1237,7 +1237,7 @@ function analyzer(data, sidePass) { if (phi.intertype == 'phi') { for (var i = 0; i < phi.params.length; i++) { phi.params[i].label = func.labelIds[phi.params[i].label]; - if (!phi.params[i].label) warn('phi refers to nonexistent label on line ' + phi.lineNum); + if (VERBOSE && !phi.params[i].label) warn('phi refers to nonexistent label on line ' + phi.lineNum); } } }); @@ -1316,7 +1316,7 @@ function analyzer(data, sidePass) { if (phi.intertype == 'phi') { for (var i = 0; i < phi.params.length; i++) { var param = phi.params[i]; - if (!param.label) warn('phi refers to nonexistent label on line ' + phi.lineNum); + if (VERBOSE && !param.label) warn('phi refers to nonexistent label on line ' + phi.lineNum); var sourceLabelId = getActualLabelId(param.label); if (sourceLabelId) { var sourceLabel = func.labelsDict[sourceLabelId]; diff --git a/src/embind/embind.js b/src/embind/embind.js new file mode 100644 index 00000000..d40d6ca2 --- /dev/null +++ b/src/embind/embind.js @@ -0,0 +1,620 @@ +/*global Module*/ +/*global _malloc, _free, _memcpy*/ +/*global FUNCTION_TABLE, HEAP32*/ +/*global Pointer_stringify, writeStringToMemory*/ +/*global __emval_register, _emval_handle_array, __emval_decref*/ + +function createNamedFunction(name, body) { + /*jshint evil:true*/ + return new Function( + "body", + "return function " + name + "() {\n" + + " return body.apply(this, arguments);\n" + + "};\n" + )(body); +} + +function _embind_repr(v) { + var t = typeof v; + if (t === 'object' || t === 'array' || t === 'function') { + return v.toString(); + } else { + return '' + v; + } +} + +var typeRegistry = {}; + +function validateType(type, name) { + if (!type) { + throw new BindingError('type "' + name + '" must have a positive integer typeid pointer'); + } + if (undefined !== typeRegistry[type]) { + throw new BindingError('cannot register type "' + name + '" twice'); + } +} + +function __embind_register_void(voidType, name) { + name = Pointer_stringify(name); + validateType(voidType, name); + typeRegistry[voidType] = { + name: name, + fromWireType: function() { + return undefined; + } + }; +} + +function __embind_register_bool(boolType, name, trueValue, falseValue) { + name = Pointer_stringify(name); + validateType(boolType, name); + typeRegistry[boolType] = { + name: name, + toWireType: function(destructors, o) { + return o ? trueValue : falseValue; + }, + fromWireType: function(wt) { + return wt === trueValue; + }, + }; +} + +function __embind_register_integer(primitiveType, name) { + name = Pointer_stringify(name); + validateType(primitiveType, name); + typeRegistry[primitiveType] = { + name: name, + toWireType: function(destructors, value) { + if (typeof value !== "number") { + throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + name); + } + return value | 0; + }, + fromWireType: function(value) { + return value; + } + }; +} + +function __embind_register_float(primitiveType, name) { + name = Pointer_stringify(name); + validateType(primitiveType, name); + typeRegistry[primitiveType] = { + name: name, + toWireType: function(destructors, value) { + if (typeof value !== "number") { + throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + name); + } + return value; + }, + fromWireType: function(value) { + return value; + } + }; +} + +function __embind_register_cstring(stringType, name) { + name = Pointer_stringify(name); + validateType(stringType, name); + typeRegistry[stringType] = { + name: name, + toWireType: function(destructors, value) { + var ptr = _malloc(value.length + 1); + writeStringToMemory(value, ptr); + destructors.push(_free); + destructors.push(ptr); + return ptr; + }, + fromWireType: function(value) { + var rv = Pointer_stringify(value); + _free(value); + return rv; + } + }; +} + +function __embind_register_emval(emvalType, name) { + name = Pointer_stringify(name); + validateType(emvalType, name); + typeRegistry[emvalType] = { + name: name, + toWireType: function(destructors, value) { + return __emval_register(value); + }, + fromWireType: function(handle) { + var rv = _emval_handle_array[handle].value; + __emval_decref(handle); + return rv; + } + }; +} + +var BindingError = Error; +/** @expose */ +Module.BindingError = BindingError; + +function typeName(typeID) { + // could use our carnal knowledge of RTTI but for now just return the pointer... + return typeID; +} + +function requireRegisteredType(type, humanName) { + var impl = typeRegistry[type]; + if (undefined === impl) { + throw new BindingError(humanName + " has unknown type: " + typeName(type)); + } + return impl; +} + +function requireArgumentTypes(argCount, argTypes, name) { + var argTypeImpls = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + var argType = HEAP32[(argTypes >> 2) + i]; + argTypeImpls[i] = requireRegisteredType(argType, name + " parameter " + i); + } + return argTypeImpls; +} + +function runDestructors(destructors) { + while (destructors.length) { + var ptr = destructors.pop(); + var del = destructors.pop(); + del(ptr); + } +} + +function __embind_register_function(name, returnType, argCount, argTypes, invoker, fn) { + name = Pointer_stringify(name); + returnType = requireRegisteredType(returnType, "Function " + name + " return value"); + invoker = FUNCTION_TABLE[invoker]; + argTypes = requireArgumentTypes(argCount, argTypes, name); + + Module[name] = function() { + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding function ' + name + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + var destructors = []; + var args = new Array(argCount + 1); + args[0] = fn; + for (var i = 0; i < argCount; ++i) { + args[i + 1] = argTypes[i].toWireType(destructors, arguments[i]); + } + var rv = returnType.fromWireType(invoker.apply(null, args)); + runDestructors(destructors); + return rv; + }; +} + +function __embind_register_tuple(tupleType, name, constructor, destructor) { + name = Pointer_stringify(name); + constructor = FUNCTION_TABLE[constructor]; + destructor = FUNCTION_TABLE[destructor]; + + var elements = []; + + typeRegistry[tupleType] = { + name: name, + elements: elements, + fromWireType: function(ptr) { + var len = elements.length; + var rv = new Array(len); + for (var i = 0; i < len; ++i) { + rv[i] = elements[i].read(ptr); + } + destructor(ptr); + return rv; + }, + toWireType: function(destructors, o) { + var len = elements.length; + if (len !== o.length) { + throw new TypeError("Incorrect number of tuple elements"); + } + var ptr = constructor(); + for (var i = 0; i < len; ++i) { + elements[i].write(ptr, o[i]); + } + destructors.push(destructor); + destructors.push(ptr); + return ptr; + } + }; +} + +function copyMemberPointer(memberPointer, memberPointerSize) { + var copy = _malloc(memberPointerSize); + if (!copy) { + throw new Error('Failed to allocate member pointer copy'); + } + _memcpy(copy, memberPointer, memberPointerSize); + return copy; +} + +function __embind_register_tuple_element( + tupleType, + elementType, + getter, + setter, + memberPointerSize, + memberPointer +) { + tupleType = requireRegisteredType(tupleType, 'tuple'); + elementType = requireRegisteredType(elementType, "element " + tupleType.name + "[" + tupleType.elements.length + "]"); + getter = FUNCTION_TABLE[getter]; + setter = FUNCTION_TABLE[setter]; + memberPointer = copyMemberPointer(memberPointer, memberPointerSize); + + tupleType.elements.push({ + read: function(ptr) { + return elementType.fromWireType(getter(ptr, memberPointer)); + }, + write: function(ptr, o) { + var destructors = []; + setter(ptr, memberPointer, elementType.toWireType(destructors, o)); + runDestructors(destructors); + } + }); +} + +function __embind_register_tuple_element_accessor( + tupleType, + elementType, + staticGetter, + getterSize, + getter, + staticSetter, + setterSize, + setter +) { + tupleType = requireRegisteredType(tupleType, 'tuple'); + elementType = requireRegisteredType(elementType, "element " + tupleType.name + "[" + tupleType.elements.length + "]"); + staticGetter = FUNCTION_TABLE[staticGetter]; + getter = copyMemberPointer(getter, getterSize); + staticSetter = FUNCTION_TABLE[staticSetter]; + setter = copyMemberPointer(setter, setterSize); + + tupleType.elements.push({ + read: function(ptr) { + return elementType.fromWireType(staticGetter(ptr, HEAP32[getter >> 2])); + }, + write: function(ptr, o) { + var destructors = []; + staticSetter( + ptr, + HEAP32[setter >> 2], + elementType.toWireType(destructors, o)); + runDestructors(destructors); + } + }); +} + +function __embind_register_struct( + structType, + name, + constructor, + destructor +) { + name = Pointer_stringify(name); + constructor = FUNCTION_TABLE[constructor]; + destructor = FUNCTION_TABLE[destructor]; + + typeRegistry[structType] = { + fields: {}, + fromWireType: function(ptr) { + var fields = this.fields; + var rv = {}; + for (var i in fields) { + rv[i] = fields[i].read(ptr); + } + destructor(ptr); + return rv; + }, + toWireType: function(destructors, o) { + var fields = this.fields; + for (var fieldName in fields) { + if (!(fieldName in o)) { + throw new TypeError('Missing field'); + } + } + var ptr = constructor(); + for (var fieldName in fields) { + fields[fieldName].write(ptr, o[fieldName]); + } + destructors.push(destructor); + destructors.push(ptr); + return ptr; + } + }; +} + +function __embind_register_struct_field( + structType, + fieldName, + fieldType, + getter, + setter, + memberPointerSize, + memberPointer +) { + structType = requireRegisteredType(structType, 'struct'); + fieldName = Pointer_stringify(fieldName); + fieldType = requireRegisteredType(fieldType, 'field "' + structType.name + '.' + fieldName + '"'); + getter = FUNCTION_TABLE[getter]; + setter = FUNCTION_TABLE[setter]; + memberPointer = copyMemberPointer(memberPointer, memberPointerSize); + + structType.fields[fieldName] = { + read: function(ptr) { + return fieldType.fromWireType(getter(ptr, memberPointer)); + }, + write: function(ptr, o) { + var destructors = []; + setter(ptr, memberPointer, fieldType.toWireType(destructors, o)); + runDestructors(destructors); + } + }; +} + +function __embind_register_class( + classType, + name, + destructor +) { + name = Pointer_stringify(name); + destructor = FUNCTION_TABLE[destructor]; + + var Handle = createNamedFunction(name, function(ptr) { + this.count = {value: 1}; + this.ptr = ptr; + }); + + Handle.prototype.clone = function() { + if (!this.ptr) { + throw new BindingError(classType.name + ' instance already deleted'); + } + + var clone = Object.create(Handle.prototype); + clone.count = this.count; + clone.ptr = this.ptr; + + clone.count.value += 1; + return clone; + }; + + Handle.prototype.move = function() { + var rv = this.clone(); + this.delete(); + return rv; + }; + + Handle.prototype['delete'] = function() { + if (!this.ptr) { + throw new BindingError(classType.name + ' instance already deleted'); + } + + this.count.value -= 1; + if (0 === this.count.value) { + destructor(this.ptr); + } + this.ptr = undefined; + }; + + var constructor = createNamedFunction(name, function() { + var body = constructor.body; + body.apply(this, arguments); + }); + constructor.prototype = Object.create(Handle.prototype); + + typeRegistry[classType] = { + name: name, + constructor: constructor, + Handle: Handle, + fromWireType: function(ptr) { + return new Handle(ptr); + }, + toWireType: function(destructors, o) { + return o.ptr; + } + }; + + Module[name] = constructor; +} + +function __embind_register_class_constructor( + classType, + argCount, + argTypes, + constructor +) { + classType = requireRegisteredType(classType, 'class'); + var humanName = 'constructor ' + classType.name; + argTypes = requireArgumentTypes(argCount, argTypes, humanName); + constructor = FUNCTION_TABLE[constructor]; + + classType.constructor.body = function() { + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + var destructors = []; + var args = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + args[i] = argTypes[i].toWireType(destructors, arguments[i]); + } + + var ptr = constructor.apply(null, args); + runDestructors(destructors); + classType.Handle.call(this, ptr); + }; +} + +function __embind_register_class_method( + classType, + methodName, + returnType, + argCount, + argTypes, + invoker, + memberFunctionSize, + memberFunction +) { + classType = requireRegisteredType(classType, 'class'); + methodName = Pointer_stringify(methodName); + var humanName = classType.name + '.' + methodName; + returnType = requireRegisteredType(returnType, 'method ' + humanName + ' return value'); + argTypes = requireArgumentTypes(argCount, argTypes, 'method ' + humanName); + invoker = FUNCTION_TABLE[invoker]; + memberFunction = copyMemberPointer(memberFunction, memberFunctionSize); + + classType.Handle.prototype[methodName] = function() { + if (!this.ptr) { + throw new BindingError('cannot call emscripten binding method ' + humanName + ' on deleted object'); + } + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding method ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + + var destructors = []; + var args = new Array(argCount + 2); + args[0] = this.ptr; + args[1] = memberFunction; + for (var i = 0; i < argCount; ++i) { + args[i + 2] = argTypes[i].toWireType(destructors, arguments[i]); + } + + var rv = returnType.fromWireType(invoker.apply(null, args)); + runDestructors(destructors); + return rv; + }; +} + +function __embind_register_class_classmethod( + classType, + methodName, + returnType, + argCount, + argTypes, + method +) { + classType = requireRegisteredType(classType, 'class'); + methodName = Pointer_stringify(methodName); + var humanName = classType.name + '.' + methodName; + returnType = requireRegisteredType(returnType, 'classmethod ' + humanName + ' return value'); + argTypes = requireArgumentTypes(argCount, argTypes, 'classmethod ' + humanName); + method = FUNCTION_TABLE[method]; + + classType.constructor[methodName] = function() { + if (arguments.length !== argCount) { + throw new BindingError('emscripten binding method ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + } + + var destructors = []; + var args = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + args[i] = argTypes[i].toWireType(destructors, arguments[i]); + } + + var rv = returnType.fromWireType(method.apply(null, args)); + runDestructors(destructors); + return rv; + }; +} + +function __embind_register_class_field( + classType, + fieldName, + fieldType, + getter, + setter, + memberPointerSize, + memberPointer +) { + classType = requireRegisteredType(classType, 'class'); + fieldName = Pointer_stringify(fieldName); + var humanName = classType.name + '.' + fieldName; + fieldType = requireRegisteredType(fieldType, 'field ' + humanName); + getter = FUNCTION_TABLE[getter]; + setter = FUNCTION_TABLE[setter]; + memberPointer = copyMemberPointer(memberPointer, memberPointerSize); + + Object.defineProperty(classType.Handle.prototype, fieldName, { + get: function() { + if (!this.ptr) { + throw new BindingError('cannot access emscripten binding field ' + humanName + ' on deleted object'); + } + return fieldType.fromWireType(getter(this.ptr, memberPointer)); + }, + set: function(v) { + if (!this.ptr) { + throw new BindingError('cannot modify emscripten binding field ' + humanName + ' on deleted object'); + } + var destructors = []; + setter(this.ptr, memberPointer, fieldType.toWireType(destructors, v)); + runDestructors(destructors); + }, + enumerable: true + }); +} + +function __embind_register_enum( + enumType, + name +) { + name = Pointer_stringify(name); + + function Enum() { + } + Enum.values = {}; + + typeRegistry[enumType] = { + name: name, + constructor: Enum, + toWireType: function(destructors, c) { + return c.value; + }, + fromWireType: function(c) { + return Enum.values[c]; + }, + }; + + Module[name] = Enum; +} + +function __embind_register_enum_value( + enumType, + name, + enumValue +) { + enumType = requireRegisteredType(enumType, 'enum'); + name = Pointer_stringify(name); + + var Enum = enumType.constructor; + + var Value = Object.create(enumType.constructor.prototype, { + value: {value: enumValue}, + constructor: {value: createNamedFunction(enumType.name + '_' + name, function() {})}, + }); + Enum.values[enumValue] = Value; + Enum[name] = Value; +} + +function __embind_register_interface( + interfaceType, + name, + constructor, + destructor +) { + name = Pointer_stringify(name); + constructor = FUNCTION_TABLE[constructor]; + destructor = FUNCTION_TABLE[destructor]; + + typeRegistry[interfaceType] = { + name: name, + toWireType: function(destructors, o) { + var handle = __emval_register(o); + var ptr = constructor(handle); + destructors.push(destructor); + destructors.push(ptr); + return ptr; + }, + }; +} + diff --git a/src/embind/emval.js b/src/embind/emval.js new file mode 100644 index 00000000..9574ab37 --- /dev/null +++ b/src/embind/emval.js @@ -0,0 +1,111 @@ +/*global Module*/ +/*global HEAP32*/ +/*global Pointer_stringify, writeStringToMemory*/ +/*global requireRegisteredType*/ + +var _emval_handle_array = []; +var _emval_free_list = []; + +// Public JS API + +/** @expose */ +Module.count_emval_handles = function() { + return _emval_handle_array.length; +}; + +// Private C++ API + +function __emval_register(value) { + var handle = _emval_free_list.length ? + _emval_free_list.pop() : + _emval_handle_array.length; + _emval_handle_array[handle] = {refcount: 1, value: value}; + return handle; +} + +function __emval_incref(handle) { + _emval_handle_array[handle].refcount += 1; +} + +function __emval_decref(handle) { + if (0 === --_emval_handle_array[handle].refcount) { + delete _emval_handle_array[handle]; + _emval_free_list.push(handle); + + var actual_length = _emval_handle_array.length; + while (actual_length > 0 && _emval_handle_array[actual_length - 1] === undefined) { + --actual_length; + } + _emval_handle_array.length = actual_length; + } +} + +function __emval_new_object() { + return __emval_register({}); +} + +function __emval_new_long(value) { + return __emval_register(value); +} + +function __emval_new_cstring(str) { + return __emval_register(Pointer_stringify(str)); +} + +function __emval_get_property(handle, k) { + k = Pointer_stringify(k); + return __emval_register(_emval_handle_array[handle].value[k]); +} + +function __emval_get_property_by_long(handle, k) { + return __emval_register(_emval_handle_array[handle].value[k]); +} + +function __emval_get_property_by_unsigned_long(handle, k) { + return __emval_register(_emval_handle_array[handle].value[k]); +} + +function __emval_set_property(handle, k, value) { + k = Pointer_stringify(k); + _emval_handle_array[handle].value[k] = _emval_handle_array[value].value; +} + +function __emval_set_property_by_int(handle, k, value) { + _emval_handle_array[handle].value[k] = _emval_handle_array[value].value; +} + +function __emval_as(handle, returnType) { + returnType = requireRegisteredType(returnType, 'emval::as'); + var destructors = []; + // caller owns destructing + return returnType.toWireType(destructors, _emval_handle_array[handle].value); +} + +function __emval_call(handle, argCount, argTypes) { + var args = Array.prototype.slice.call(arguments, 3); + var fn = _emval_handle_array[handle].value; + var a = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + var argType = requireRegisteredType( + HEAP32[(argTypes >> 2) + i], + "parameter " + i); + a[i] = argType.fromWireType(args[i]); + } + var rv = fn.apply(undefined, a); + return __emval_register(rv); +} + +function __emval_call_method(handle, name, argCount, argTypes) { + name = Pointer_stringify(name); + var args = Array.prototype.slice.call(arguments, 4); + var obj = _emval_handle_array[handle].value; + var a = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + var argType = requireRegisteredType( + HEAP32[(argTypes >> 2) + i], + "parameter " + i); + a[i] = argType.fromWireType(args[i]); + } + var rv = obj[name].apply(obj, a); + return __emval_register(rv); +} diff --git a/src/jsifier.js b/src/jsifier.js index 8021f8a1..02459193 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -358,11 +358,21 @@ function JSify(data, functionsOnly, givenFunctions) { item.JS = 'var ' + item.ident + ';'; // Set the actual value in a postset, since it may be a global variable. We also order by dependencies there var value = Variables.globals[item.ident].resolvedAlias = finalizeLLVMParameter(item.value); + var fix = ''; + if (BUILD_AS_SHARED_LIB == 2 && !item.private_) { + var target = item.ident; + if (isFunctionType(item.type)) { + target = item.value.ident; // the other side does not know this is an alias/function table index. So make it the alias target. + var varData = Variables.globals[target]; + assert(!varData, 'multi-level aliasing does not work yet in shared lib 2 exports'); + } + fix = '\nif (globalScope) { assert(!globalScope["' + item.ident + '"]); globalScope["' + item.ident + '"] = ' + target + ' }' + } ret.push({ intertype: 'GlobalVariablePostSet', ident: item.ident, dependencies: set([value]), - JS: item.ident + ' = ' + value + ';' + JS: item.ident + ' = ' + value + ';' + fix }); return ret; } @@ -1243,7 +1253,7 @@ function JSify(data, functionsOnly, givenFunctions) { // Load runtime-linked libraries RUNTIME_LINKED_LIBS.forEach(function(lib) { - print('eval(read("' + lib + '"))(FUNCTION_TABLE.length, this);'); + print('eval(Module["read"]("' + lib + '"))(FUNCTION_TABLE.length, this);'); }); print(postParts[1]); diff --git a/src/library.js b/src/library.js index 594ba931..69642151 100644 --- a/src/library.js +++ b/src/library.js @@ -382,12 +382,14 @@ LibraryManager.library = { // You can also call this with a typed array instead of a url. It will then // do preloading for the Image/Audio part, as if the typed array were the // result of an XHR that you did manually. - createPreloadedFile: function(parent, name, url, canRead, canWrite, onload, onerror) { + createPreloadedFile: function(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile) { Browser.ensureObjects(); var fullname = FS.joinPath([parent, name], true); function processData(byteArray) { function finish(byteArray) { - FS.createDataFile(parent, name, byteArray, canRead, canWrite); + if (!dontCreateFile) { + FS.createDataFile(parent, name, byteArray, canRead, canWrite); + } if (onload) onload(); removeRunDependency('cp ' + fullname); } @@ -1286,7 +1288,7 @@ LibraryManager.library = { return 0; case {{{ cDefine('F_SETOWN') }}}: case {{{ cDefine('F_GETOWN') }}}: - // These are for sockets. We don't have them implemented (yet?). + // These are for sockets. We don't have them fully implemented yet. ___setErrNo(ERRNO_CODES.EINVAL); return -1; default: @@ -2498,8 +2500,8 @@ LibraryManager.library = { while ((curr < max_ || isNaN(max_)) && next > 0) { if (!(next in __scanString.whiteSpace) && // stop on whitespace (type == 's' || - ((type === 'd' || type == 'u') && ((next >= '0'.charCodeAt(0) && next <= '9'.charCodeAt(0)) || - (first && next == '-'.charCodeAt(0)))) || + ((type === 'd' || type == 'u' || type == 'i') && ((next >= '0'.charCodeAt(0) && next <= '9'.charCodeAt(0)) || + (first && next == '-'.charCodeAt(0)))) || (type === 'x' && (next >= '0'.charCodeAt(0) && next <= '9'.charCodeAt(0) || next >= 'a'.charCodeAt(0) && next <= 'f'.charCodeAt(0) || next >= 'A'.charCodeAt(0) && next <= 'F'.charCodeAt(0)))) && @@ -2518,7 +2520,7 @@ LibraryManager.library = { var argPtr = {{{ makeGetValue('varargs', 'argIndex', 'void*') }}}; argIndex += Runtime.getNativeFieldSize('void*'); switch (type) { - case 'd': case 'u': + case 'd': case 'u': case 'i': if (half) { {{{ makeSetValue('argPtr', 0, 'parseInt(text, 10)', 'i16') }}}; } else { @@ -6398,14 +6400,18 @@ LibraryManager.library = { ntohl: 'htonl', ntohs: 'htons', - inet_pton__deps: ['__setErrNo', '$ERRNO_CODES'], + inet_addr: function(ptr) { + var b = Pointer_stringify(ptr).split("."); + if (b.length !== 4) return -1; // we return -1 for error, and otherwise a uint32. this helps inet_pton differentiate + return (Number(b[0]) | (Number(b[1]) << 8) | (Number(b[2]) << 16) | (Number(b[3]) << 24)) >>> 0; + }, + + inet_pton__deps: ['__setErrNo', '$ERRNO_CODES', 'inet_addr'], inet_pton: function(af, src, dst) { // int af, const char *src, void *dst if ((af ^ {{{ cDefine("AF_INET") }}}) !== 0) { ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); return -1; } - var b = Pointer_stringify(src).split("."); - if (b.length !== 4) return 0; - var ret = Number(b[0]) | (Number(b[1]) << 8) | (Number(b[2]) << 16) | (Number(b[3]) << 24); - if (isNaN(ret)) return 0; + var ret = _inet_addr(src); + if (ret == -1 || isNaN(ret)) return 0; setValue(dst, ret, 'i32'); return 1; }, @@ -6423,7 +6429,62 @@ LibraryManager.library = { }, // ========================================================================== - // sockets + // netdb.h + // ========================================================================== + + // All we can do is alias names to ips. you give this a name, it returns an + // "ip" that we later know to use as a name. There is no way to do actual + // name resolving clientside in a browser. + // we do the aliasing in 172.29.*.*, giving us 65536 possibilities + // note: lots of leaking here! + __hostent_struct_layout: Runtime.generateStructInfo([ + ['i8*', 'h_name'], + ['i8**', 'h_aliases'], + ['i32', 'h_addrtype'], + ['i32', 'h_length'], + ['i8**', 'h_addr_list'], + ]), + gethostbyname__deps: ['__hostent_struct_layout'], + gethostbyname: function(name) { + name = Pointer_stringify(name); + if (!_gethostbyname.id) { + _gethostbyname.id = 1; + _gethostbyname.table = {}; + } + var id = _gethostbyname.id++; + assert(id < 65535); + var fakeAddr = 172 | (29 << 8) | ((id & 0xff) << 16) | ((id & 0xff00) << 24); + _gethostbyname.table[id] = name; + // generate hostent + var ret = _malloc(___hostent_struct_layout.__size__); + var nameBuf = _malloc(name.length+1); + writeStringToMemory(name, nameBuf); + setValue(ret+___hostent_struct_layout.h_name, nameBuf, 'i8*'); + var aliasesBuf = _malloc(4); + setValue(aliasesBuf, 0, 'i8*'); + setValue(ret+___hostent_struct_layout.h_aliases, aliasesBuf, 'i8**'); + setValue(ret+___hostent_struct_layout.h_addrtype, {{{ cDefine("AF_INET") }}}, 'i32'); + setValue(ret+___hostent_struct_layout.h_length, 4, 'i32'); + var addrListBuf = _malloc(12); + setValue(addrListBuf, addrListBuf+8, 'i32*'); + setValue(addrListBuf+4, 0, 'i32*'); + setValue(addrListBuf+8, fakeAddr, 'i32'); + setValue(ret+___hostent_struct_layout.h_addr_list, addrListBuf, 'i8**'); + return ret; + }, + + gethostbyname_r__deps: ['gethostbyname'], + gethostbyname_r: function(name, hostData, buffer, bufferSize, hostEntry, errnum) { + var data = _gethostbyname(name); + _memcpy(hostData, data, ___hostent_struct_layout.__size__); + _free(data); + setValue(errnum, 0, 'i32'); + return 0; + }, + + // ========================================================================== + // sockets. Note that the implementation assumes all sockets are always + // nonblocking // ========================================================================== $Sockets__deps: ['__setErrNo', '$ERRNO_CODES'], @@ -6437,6 +6498,15 @@ LibraryManager.library = { ['i32', 'sin_addr'], ['i64', 'sin_zero'], ]), + msghdr_layout: Runtime.generateStructInfo([ + ['*', 'msg_name'], + ['i32', 'msg_namelen'], + ['*', 'msg_iov'], + ['i32', 'msg_iovlen'], + ['*', 'msg_control'], + ['i32', 'msg_controllen'], + ['i32', 'msg_flags'], + ]), }, socket__deps: ['$Sockets'], @@ -6448,7 +6518,7 @@ LibraryManager.library = { return fd; }, - connect__deps: ['$Sockets', '_inet_ntop_raw', 'ntohs'], + connect__deps: ['$Sockets', '_inet_ntop_raw', 'ntohs', 'gethostbyname'], connect: function(fd, addr, addrlen) { var info = Sockets.fds[fd]; if (!info) return -1; @@ -6456,24 +6526,68 @@ LibraryManager.library = { info.addr = getValue(addr + Sockets.sockaddr_in_layout.sin_addr, 'i32'); info.port = _ntohs(getValue(addr + Sockets.sockaddr_in_layout.sin_port, 'i16')); info.host = __inet_ntop_raw(info.addr); - info.socket = new WebSocket('ws://' + info.host + ':' + info.port, ['arraybuffer']); + // Support 'fake' ips from gethostbyname + var parts = info.host.split('.'); + if (parts[0] == '172' && parts[1] == '29') { + var low = Number(parts[2]); + var high = Number(parts[3]); + info.host = _gethostbyname.table[low + 0xff*high]; + assert(info.host, 'problem translating fake ip ' + parts); + } + console.log('opening ws://' + info.host + ':' + info.port); + info.socket = new WebSocket('ws://' + info.host + ':' + info.port, ['base64']); info.socket.binaryType = 'arraybuffer'; info.buffer = new Uint8Array(Sockets.BUFFER_SIZE); info.bufferWrite = info.bufferRead = 0; info.socket.onmessage = function (event) { var data = event.data; +#if SOCKET_DEBUG + Module.print(['onmessage', window.location, event.data, window.atob(data)]); +#endif if (typeof data == 'string') { var binaryString = window.atob(data); var len = binaryString.length; +#if SOCKET_DEBUG + var out = []; +#endif for (var i = 0; i < len; i++) { - info.buffer[info.bufferWrite++] = binaryString.charCodeAt(i); + var curr = binaryString.charCodeAt(i); + info.buffer[info.bufferWrite++] = curr; +#if SOCKET_DEBUG + out.push(curr); +#endif if (info.bufferWrite == Sockets.BUFFER_SIZE) info.bufferWrite = 0; if (info.bufferWrite == info.bufferRead) throw 'socket buffer overflow'; } +#if SOCKET_DEBUG + Module.print(['onmessage data:', len, ':', out]); +#endif } else { console.log('binary!'); } } + info.sendQueue = []; + info.senderWaiting = false; + info.sender = function(data) { + if (data) { + info.sendQueue.push(data); + } else { + info.senderWaiting = false; // we are a setTimeout callback + if (info.sendQueue.length == 0) return; + } + if (info.socket.readyState != info.socket.OPEN) { + if (!info.senderWaiting) { + console.log('waiting for socket in order to send'); + setTimeout(info.sender, 100); + info.senderWaiting = true; + } + return; + } + for (var i = 0; i < info.sendQueue.length; i++) { + info.socket.send(window.btoa(info.sendQueue[i])); + } + info.sendQueue = []; + } return 0; }, @@ -6486,6 +6600,9 @@ LibraryManager.library = { return 0; // should this be -1 like the spec says? } var ret = 0; +#if SOCKET_DEBUG + Module.print('pre-recv: ' + [len, info.bufferWrite, info.bufferRead]); +#endif while (info.bufferWrite != info.bufferRead && len > 0) { // write out a byte {{{ makeSetValue('buf++', '0', 'info.buffer[info.bufferRead++]', 'i8') }}}; @@ -6493,9 +6610,105 @@ LibraryManager.library = { len--; ret++; } +#if SOCKET_DEBUG + Module.print('recv: ' + ret + ' : ' + Array.prototype.slice.call(HEAPU8.subarray(buf-len, buf))); +#endif + return ret; + }, + + send__deps: ['$Sockets'], + send: function(fd, buf, len, flags) { + var info = Sockets.fds[fd]; + if (!info) return -1; +#if SOCKET_DEBUG + Module.print('send: ' + Array.prototype.slice.call(HEAPU8.subarray(buf, buf+len))); +#endif + info.sender(Pointer_stringify(buf, len)); + return len; + }, + + sendmsg__deps: ['$Sockets', 'connect'], + sendmsg: function(fd, msg, flags) { + var info = Sockets.fds[fd]; + if (!info) return -1; + // if we are not connected, use the address info in the message + if (!info.connected) { + var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; + assert(name, 'sendmsg on non-connected socket, and no name/address in the message'); + _connect(fd, name, {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}); + } + var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; + var data = ''; + for (var i = 0; i < num; i++) { + var currNum = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov+8*i' + '+4', 'i32') }}}; + if (!currNum) continue; + var currBuf = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov+8*i', 'i8*') }}}; +#if SOCKET_DEBUG + Module.print('sendmsg part ' + i + ' : ' + currNum + ' : ' + Array.prototype.slice.call(HEAPU8.subarray(currBuf, currBuf+currNum))); +#endif + data += Pointer_stringify(currBuf, currNum); + } + info.sender(data); + return data.length; + }, + + recvmsg__deps: ['$Sockets', 'connect', 'recv', '__setErrNo', '$ERRNO_CODES'], + recvmsg: function(fd, msg, flags) { +#if SOCKET_DEBUG + Module.print('recvmsg!'); +#endif + var info = Sockets.fds[fd]; + if (!info) return -1; + // if we are not connected, use the address info in the message + if (!info.connected) { +#if SOCKET_DEBUG + Module.print('recvmsg connecting'); +#endif + var name = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_name', '*') }}}; + assert(name, 'sendmsg on non-connected socket, and no name/address in the message'); + _connect(fd, name, {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_namelen', 'i32') }}}); + } + var bytes = info.bufferWrite - info.bufferRead; + if (bytes < 0) bytes += Sockets.BUFFER_SIZE; +#if SOCKET_DEBUG + Module.print('recvmsg bytes: ' + bytes); +#endif + if (bytes == 0) { + ___setErrNo(ERRNO_CODES.EWOULDBLOCK); + return -1; + } + var ret = bytes; + var num = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iovlen', 'i32') }}}; + var data = ''; + for (var i = 0; i < num && bytes > 0; i++) { + var currNum = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov+8*i' + '+4', 'i32') }}}; +#if SOCKET_DEBUG + Module.print('recvmsg loop ' + [i, num, bytes, currNum]); +#endif + if (!currNum) continue; + currNum = Math.min(currNum, bytes); // XXX what should happen when we partially fill a buffer..? + bytes -= currNum; + var currBuf = {{{ makeGetValue('msg', 'Sockets.msghdr_layout.msg_iov+8*i', 'i8*') }}}; +#if SOCKET_DEBUG + Module.print('recvmsg call recv ' + currNum); +#endif + assert(_recv(fd, currBuf, currNum, 0) == currNum); + } return ret; }, + recvfrom__deps: ['$Sockets', 'connect', 'recv'], + recvfrom: function(fd, buf, len, flags, addr, addrlen) { + var info = Sockets.fds[fd]; + if (!info) return -1; + // if we are not connected, use the address info in the message + if (!info.connected) { + //var name = {{{ makeGetValue('addr', '0', '*') }}}; + _connect(fd, addr, addrlen); + } + return _recv(fd, buf, len, flags); + }, + shutdown: function(fd, how) { var info = Sockets.fds[fd]; if (!info) return -1; @@ -6514,6 +6727,34 @@ LibraryManager.library = { return 0; }, + setsockopt: function(d, level, optname, optval, optlen) { + console.log('ignoring setsockopt command'); + return 0; + }, + + bind__deps: ['connect'], + bind: function(fd, addr, addrlen) { + return _connect(fd, addr, addrlen); + }, + + listen: function(fd, backlog) { + return 0; + }, + + accept: function(fd, addr, addrlen) { + // TODO: webrtc queued incoming connections, etc. + // For now, the model is that bind does a connect, and we "accept" that one connection, + // which has host:port the same as ours. We also return the same socket fd. + var info = Sockets.fds[fd]; + if (!info) return -1; + if (addr) { + setValue(addr + Sockets.sockaddr_in_layout.sin_addr, info.addr, 'i32'); + setValue(addr + Sockets.sockaddr_in_layout.sin_port, info.port, 'i32'); + setValue(addrlen, Sockets.sockaddr_in_layout.__size__, 'i32'); + } + return fd; + }, + // ========================================================================== // emscripten.h // ========================================================================== diff --git a/src/library_browser.js b/src/library_browser.js index 27bf4a0c..99106fc3 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -347,11 +347,21 @@ mergeInto(LibraryManager.library, { }); addRunDependency('al ' + url); }, - - setCanvasSize: function(width, height) { + + resizeListeners: [], + + updateResizeListeners: function() { + var canvas = Module['canvas']; + Browser.resizeListeners.forEach(function(listener) { + listener(canvas.width, canvas.height); + }); + }, + + setCanvasSize: function(width, height, noUpdates) { var canvas = Module['canvas']; canvas.width = width; canvas.height = height; + if (!noUpdates) Browser.updateResizeListeners(); } }, @@ -364,14 +374,56 @@ mergeInto(LibraryManager.library, { _file.substr(index +1), _url, true, true, function() { - FUNCTION_TABLE[onload](file); + if (onload) FUNCTION_TABLE[onload](file); }, function() { - FUNCTION_TABLE[onerror](file); + if (onerror) FUNCTION_TABLE[onerror](file); } ); }, + emscripten_async_prepare: function(file, onload, onerror) { + var _file = Pointer_stringify(file); + var data = FS.analyzePath(_file); + if (!data.exists) return -1; + var index = _file.lastIndexOf('/'); + FS.createPreloadedFile( + _file.substr(0, index), + _file.substr(index +1), + new Uint8Array(data.object.contents), true, true, + function() { + if (onload) FUNCTION_TABLE[onload](file); + }, + function() { + if (onerror) FUNCTION_TABLE[onerror](file); + }, + true // don'tCreateFile - it's already there + ); + return 0; + }, + + emscripten_async_prepare_data: function(data, size, suffix, arg, onload, onerror) { + var _suffix = Pointer_stringify(suffix); + if (!Browser.asyncPrepareDataCounter) Browser.asyncPrepareDataCounter = 0; + var name = 'prepare_data_' + (Browser.asyncPrepareDataCounter++) + '.' + _suffix; + var cname = _malloc(name.length+1); + writeStringToMemory(name, cname); + FS.createPreloadedFile( + '', + name, + {{{ makeHEAPView('U8', 'data', 'data + size') }}}, + true, true, + function() { + if (onload) FUNCTION_TABLE[onload](arg, cname); + }, + function() { + if (onerror) FUNCTION_TABLE[onerror](arg); + }, + true // don'tCreateFile - it's already there + ); + return 0; + }, + emscripten_async_run_script__deps: ['emscripten_run_script'], emscripten_async_run_script: function(script, millis) { Module['noExitRuntime'] = true; @@ -382,7 +434,7 @@ mergeInto(LibraryManager.library, { }, millis); }, - emscripten_set_main_loop: function(func, fps) { + emscripten_set_main_loop: function(func, fps, simulateInfiniteLoop) { Module['noExitRuntime'] = true; var jsFunc = FUNCTION_TABLE[func]; @@ -442,6 +494,10 @@ mergeInto(LibraryManager.library, { } } Browser.mainLoop.scheduler(); + + if (simulateInfiniteLoop) { + throw 'emscripten_set_main_loop simulating infinite loop by throwing so we get right into the JS event loop'; + } }, emscripten_cancel_main_loop: function() { diff --git a/src/library_egl.js b/src/library_egl.js index 635e00a7..1c35ddf4 100644 --- a/src/library_egl.js +++ b/src/library_egl.js @@ -1,26 +1,423 @@ - +/* + The EGL implementation supports only one EGLNativeDisplayType, the EGL_DEFAULT_DISPLAY. + This native display type returns the only supported EGLDisplay handle with the magic value 62000. + There is only a single EGLConfig configuration supported, that has the magic value 62002. + The implementation only allows a single EGLContext to be created, that has the magic value of 62004. (multiple creations silently return this same context) + The implementation only creates a single EGLSurface, a handle with the magic value of 62006. (multiple creations silently return the same surface) +*/ + var LibraryEGL = { - eglGetDisplay: function(x_display) { return 3 }, - eglInitialize: function(display, majorVersion, minorVersion) { return 1 }, - eglGetConfigs: function(display, hmm1, hmm2, numConfigs) { return 1 }, - eglChooseConfig: function(display, attribList, config, hmm, numConfigs) { return 1 }, - eglCreateWindowSurface: function(display, config, hWnd, hmm) { return 4 }, + $EGL: { + // This variable tracks the success status of the most recently invoked EGL function call. + eglErrorCode: 0x3000 /* EGL_SUCCESS */, + + setErrorCode: function(code) { + EGL.eglErrorCode = code; + }, + + chooseConfig: function(display, attribList, config, config_size, numConfigs) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + // TODO: read attribList. + if ((!config || !config_size) && !numConfigs) { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + } + if (numConfigs) { + {{{ makeSetValue('numConfigs', '0', '1', 'i32') }}}; // Total number of supported configs: 1. + } + if (config && config_size > 0) { + {{{ makeSetValue('config', '0', '62002' /* Magic ID for the only EGLConfig supported by Emscripten */, 'i32') }}}; + } + + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + }, + + // EGLAPI EGLDisplay EGLAPIENTRY eglGetDisplay(EGLNativeDisplayType display_id); + eglGetDisplay: function(nativeDisplayType) { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + // Note: As a 'conformant' implementation of EGL, we would prefer to init here only if the user + // calls this function with EGL_DEFAULT_DISPLAY. Other display IDs would be preferred to be unsupported + // and EGL_NO_DISPLAY returned. Uncomment the following code lines to do this. + // Instead, an alternative route has been preferred, namely that the Emscripten EGL implementation + // "emulates" X11, and eglGetDisplay is expected to accept/receive a pointer to an X11 Display object. + // Therefore, be lax and allow anything to be passed in, and return the magic handle to our default EGLDisplay object. + +// if (nativeDisplayType == 0 /* EGL_DEFAULT_DISPLAY */) { + return 62000; // Magic ID for Emscripten 'default display' +// } +// else +// return 0; // EGL_NO_DISPLAY + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor); + eglInitialize: function(display, majorVersion, minorVersion) { + if (display == 62000 /* Magic ID for Emscripten 'default display' */) { + if (majorVersion) { + {{{ makeSetValue('majorVersion', '0', '1', 'i32') }}}; // Advertise EGL Major version: '1' + } + if (minorVersion) { + {{{ makeSetValue('minorVersion', '0', '4', 'i32') }}}; // Advertise EGL Minor version: '4' + } + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + } + else { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config); + eglGetConfigs: function(display, configs, config_size, numConfigs) { + return EGL.chooseConfig(display, 0, configs, config_size, numConfigs); + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config); + eglChooseConfig: function(display, attrib_list, configs, config_size, numConfigs) { + return EGL.chooseConfig(display, attrib_list, configs, config_size, numConfigs); + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value); + eglGetConfigAttrib: function(display, config, attribute, value) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (config != 62002 /* Magic ID for the only EGLConfig supported by Emscripten */) { + EGL.setErrorCode(0x3005 /* EGL_BAD_CONFIG */); + return 0; + } + if (!value) { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + } + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + switch(attribute) { + case 0x3020: // EGL_BUFFER_SIZE + {{{ makeSetValue('value', '0', '32' /* 8 bits for each A,R,G,B. */, 'i32') }}}; + return 1; + case 0x3021: // EGL_ALPHA_SIZE + {{{ makeSetValue('value', '0', '8' /* 8 bits for alpha channel. */, 'i32') }}}; + return 1; + case 0x3022: // EGL_BLUE_SIZE + {{{ makeSetValue('value', '0', '8' /* 8 bits for blue channel. */, 'i32') }}}; + return 1; + case 0x3023: // EGL_GREEN_SIZE + {{{ makeSetValue('value', '0', '8' /* 8 bits for green channel. */, 'i32') }}}; + return 1; + case 0x3024: // EGL_RED_SIZE + {{{ makeSetValue('value', '0', '8' /* 8 bits for red channel. */, 'i32') }}}; + return 1; + case 0x3025: // EGL_DEPTH_SIZE + {{{ makeSetValue('value', '0', '24' /* 24 bits for depth buffer. TODO: This is hardcoded, add support for this! */, 'i32') }}}; + return 1; + case 0x3026: // EGL_STENCIL_SIZE + {{{ makeSetValue('value', '0', '8' /* 8 bits for stencil buffer. TODO: This is hardcoded, add support for this! */, 'i32') }}}; + return 1; + case 0x3027: // EGL_CONFIG_CAVEAT + // We can return here one of EGL_NONE (0x3038), EGL_SLOW_CONFIG (0x3050) or EGL_NON_CONFORMANT_CONFIG (0x3051). + {{{ makeSetValue('value', '0', '0x3038' /* EGL_NONE */, 'i32') }}}; + return 1; + case 0x3028: // EGL_CONFIG_ID + {{{ makeSetValue('value', '0', '62002' /* Magic ID for the only EGLConfig supported by Emscripten */, 'i32') }}}; + return 1; + case 0x3029: // EGL_LEVEL + {{{ makeSetValue('value', '0', '0' /* Z order/depth layer for this level. Not applicable for Emscripten. */, 'i32') }}}; + return 1; + case 0x302A: // EGL_MAX_PBUFFER_HEIGHT + {{{ makeSetValue('value', '0', '4096', 'i32') }}}; + return 1; + case 0x302B: // EGL_MAX_PBUFFER_PIXELS + {{{ makeSetValue('value', '0', '16777216' /* 4096 * 4096 */, 'i32') }}}; + return 1; + case 0x302C: // EGL_MAX_PBUFFER_WIDTH + {{{ makeSetValue('value', '0', '4096', 'i32') }}}; + return 1; + case 0x302D: // EGL_NATIVE_RENDERABLE + {{{ makeSetValue('value', '0', '0' /* This config does not allow co-rendering with other 'native' rendering APIs. */, 'i32') }}}; + return 1; + case 0x302E: // EGL_NATIVE_VISUAL_ID + {{{ makeSetValue('value', '0', '0' /* N/A for Emscripten. */, 'i32') }}}; + return 1; + case 0x302F: // EGL_NATIVE_VISUAL_TYPE + {{{ makeSetValue('value', '0', '0x3038' /* EGL_NONE */, 'i32') }}}; + return 1; + case 0x3031: // EGL_SAMPLES + {{{ makeSetValue('value', '0', '0' /* No multisampling. */, 'i32') }}}; + return 1; + case 0x3032: // EGL_SAMPLE_BUFFERS + {{{ makeSetValue('value', '0', '0' /* No multisampling. */, 'i32') }}}; + return 1; + case 0x3033: // EGL_SURFACE_TYPE + {{{ makeSetValue('value', '0', '0x0004' /* EGL_WINDOW_BIT */, 'i32') }}}; + return 1; + case 0x3034: // EGL_TRANSPARENT_TYPE + // If this returns EGL_TRANSPARENT_RGB (0x3052), transparency is used through color-keying. No such thing applies to Emscripten canvas. + {{{ makeSetValue('value', '0', '0x3038' /* EGL_NONE */, 'i32') }}}; + return 1; + case 0x3035: // EGL_TRANSPARENT_BLUE_VALUE + case 0x3036: // EGL_TRANSPARENT_GREEN_VALUE + case 0x3037: // EGL_TRANSPARENT_RED_VALUE + // "If EGL_TRANSPARENT_TYPE is EGL_NONE, then the values for EGL_TRANSPARENT_RED_VALUE, EGL_TRANSPARENT_GREEN_VALUE, and EGL_TRANSPARENT_BLUE_VALUE are undefined." + {{{ makeSetValue('value', '0', '-1' /* Report a "does not apply" value. */, 'i32') }}}; + return 1; + case 0x3039: // EGL_BIND_TO_TEXTURE_RGB + case 0x303A: // EGL_BIND_TO_TEXTURE_RGBA + {{{ makeSetValue('value', '0', '0' /* Only pbuffers would be bindable, but these are not supported. */, 'i32') }}}; + return 1; + case 0x303B: // EGL_MIN_SWAP_INTERVAL + case 0x303C: // EGL_MAX_SWAP_INTERVAL + {{{ makeSetValue('value', '0', '1' /* TODO: Currently this is not strictly true, since user can specify custom presentation interval in JS requestAnimationFrame/emscripten_set_main_loop. */, 'i32') }}}; + return 1; + case 0x303D: // EGL_LUMINANCE_SIZE + case 0x303E: // EGL_ALPHA_MASK_SIZE + {{{ makeSetValue('value', '0', '0' /* N/A in this config. */, 'i32') }}}; + return 1; + case 0x303F: // EGL_COLOR_BUFFER_TYPE + // EGL has two types of buffers: EGL_RGB_BUFFER and EGL_LUMINANCE_BUFFER. + {{{ makeSetValue('value', '0', '0x308E' /* EGL_RGB_BUFFER */, 'i32') }}}; + return 1; + case 0x3040: // EGL_RENDERABLE_TYPE + // A bit combination of EGL_OPENGL_ES_BIT,EGL_OPENVG_BIT,EGL_OPENGL_ES2_BIT and EGL_OPENGL_BIT. + {{{ makeSetValue('value', '0', '0x0004' /* EGL_OPENGL_ES2_BIT */, 'i32') }}}; + return 1; + case 0x3042: // EGL_CONFORMANT + // "EGL_CONFORMANT is a mask indicating if a client API context created with respect to the corresponding EGLConfig will pass the required conformance tests for that API." + {{{ makeSetValue('value', '0', '0' /* EGL_OPENGL_ES2_BIT */, 'i32') }}}; + return 1; + default: + EGL.setErrorCode(0x3004 /* EGL_BAD_ATTRIBUTE */); + return 0; + } + }, + + // EGLAPI EGLSurface EGLAPIENTRY eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list); + eglCreateWindowSurface: function(display, config, win, attrib_list) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (config != 62002 /* Magic ID for the only EGLConfig supported by Emscripten */) { + EGL.setErrorCode(0x3005 /* EGL_BAD_CONFIG */); + return 0; + } + // TODO: Examine attrib_list! Parameters that can be present there are: + // - EGL_RENDER_BUFFER (must be EGL_BACK_BUFFER) + // - EGL_VG_COLORSPACE (can't be set) + // - EGL_VG_ALPHA_FORMAT (can't be set) + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 62006; /* Magic ID for Emscripten 'default surface' */ + }, eglCreateContext__deps: ['glutCreateWindow', '$GL'], + + // EGLAPI EGLContext EGLAPIENTRY eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list); eglCreateContext: function(display, config, hmm, contextAttribs) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + _glutCreateWindow(); - return 1; + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 62004; // Magic ID for Emscripten EGLContext }, // EGLAPI EGLBoolean EGLAPIENTRY eglQuerySurface(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value); - eglQuerySurface: function(display, surface, attribute, value) { return 0 }, + eglQuerySurface: function(display, surface, attribute, value) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + if (surface != 62006 /* Magic ID for Emscripten 'default surface' */) { + EGL.setErrorCode(0x300D /* EGL_BAD_SURFACE */); + return 0; + } + if (!value) { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + } + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + switch(attribute) { + case 0x3028: // EGL_CONFIG_ID + {{{ makeSetValue('value', '0', '62002' /* A magic value for the only EGLConfig configuration ID supported by Emscripten. */, 'i32') }}}; + return 1; + case 0x3058: // EGL_LARGEST_PBUFFER + // Odd EGL API: If surface is not a pbuffer surface, 'value' should not be written to. It's not specified as an error, so true should(?) be returned. + // Existing Android implementation seems to do so at least. + return 1; + case 0x3057: // EGL_WIDTH + // TODO + return 1; + case 0x3056: // EGL_HEIGHT + // TODO + return 1; + case 0x3090: // EGL_HORIZONTAL_RESOLUTION + {{{ makeSetValue('value', '0', '-1' /* EGL_UNKNOWN */, 'i32') }}}; + return 1; + case 0x3091: // EGL_VERTICAL_RESOLUTION + {{{ makeSetValue('value', '0', '-1' /* EGL_UNKNOWN */, 'i32') }}}; + return 1; + case 0x3092: // EGL_PIXEL_ASPECT_RATIO + {{{ makeSetValue('value', '0', '-1' /* EGL_UNKNOWN */, 'i32') }}}; + return 1; + case 0x3086: // EGL_RENDER_BUFFER + // The main surface is bound to the visible canvas window - it's always backbuffered. + // Alternative to EGL_BACK_BUFFER would be EGL_SINGLE_BUFFER. + {{{ makeSetValue('value', '0', '0x3084' /* EGL_BACK_BUFFER */, 'i32') }}}; + return 1; + case 0x3099: // EGL_MULTISAMPLE_RESOLVE + {{{ makeSetValue('value', '0', '0x309A' /* EGL_MULTISAMPLE_RESOLVE_DEFAULT */, 'i32') }}}; + return 1; + case 0x3093: // EGL_SWAP_BEHAVIOR + // The two possibilities are EGL_BUFFER_PRESERVED and EGL_BUFFER_DESTROYED. Slightly unsure which is the + // case for browser environment, but advertise the 'weaker' behavior to be sure. + {{{ makeSetValue('value', '0', '0x3095' /* EGL_BUFFER_DESTROYED */, 'i32') }}}; + return 1; + case 0x3080: // EGL_TEXTURE_FORMAT + case 0x3081: // EGL_TEXTURE_TARGET + case 0x3082: // EGL_MIPMAP_TEXTURE + case 0x3083: // EGL_MIPMAP_LEVEL + // This is a window surface, not a pbuffer surface. Spec: + // "Querying EGL_TEXTURE_FORMAT, EGL_TEXTURE_TARGET, EGL_MIPMAP_TEXTURE, or EGL_MIPMAP_LEVEL for a non-pbuffer surface is not an error, but value is not modified." + // So pass-through. + return 1; + default: + EGL.setErrorCode(0x3004 /* EGL_BAD_ATTRIBUTE */); + return 0; + } + }, + // EGLAPI EGLBoolean EGLAPIENTRY eglQueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value); + eglQueryContext: function(display, context, attribute, value) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. + if (context != 62004 /* Magic ID for Emscripten EGLContext */) { + EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); + return 0; + } + if (!value) { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + } + + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + switch(attribute) { + case 0x3028: // EGL_CONFIG_ID + {{{ makeSetValue('value', '0', '62002' /* A magic value for the only EGLConfig configuration ID supported by Emscripten. */, 'i32') }}}; + return 1; + case 0x3097: // EGL_CONTEXT_CLIENT_TYPE + {{{ makeSetValue('value', '0', '0x30A0' /* EGL_OPENGL_ES_API */, 'i32') }}}; + return 1; + case 0x3098: // EGL_CONTEXT_CLIENT_VERSION + {{{ makeSetValue('value', '0', '2' /* GLES2 context */, 'i32') }}}; // We always report the context to be a GLES2 context (and not a GLES1 context) + return 1; + case 0x3086: // EGL_RENDER_BUFFER + // The context is bound to the visible canvas window - it's always backbuffered. + // Alternative to EGL_BACK_BUFFER would be EGL_SINGLE_BUFFER. + {{{ makeSetValue('value', '0', '0x3084' /* EGL_BACK_BUFFER */, 'i32') }}}; + return 1; + default: + EGL.setErrorCode(0x3004 /* EGL_BAD_ATTRIBUTE */); + return 0; + } + }, + // EGLAPI EGLint EGLAPIENTRY eglGetError(void); - eglGetError: function() { return 0x3000 /* EGL_SUCCESS */ }, + eglGetError: function() { + return EGL.eglErrorCode; + }, - eglMakeCurrent: function(display, surface, surface_, context) { return 1 }, - eglSwapBuffers: function() {}, + eglQueryString: function(display, name) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + switch(name) { + case 0x3053 /* EGL_VENDOR */: return allocate(intArrayFromString("Emscripten"), 'i8', ALLOC_NORMAL); + case 0x3054 /* EGL_VERSION */: return allocate(intArrayFromString("1.4 Emscripten EGL"), 'i8', ALLOC_NORMAL); + case 0x3055 /* EGL_EXTENSIONS */: return allocate(intArrayFromString(""), 'i8', ALLOC_NORMAL); // Currently not supporting any EGL extensions. + case 0x308D /* EGL_CLIENT_APIS */: return allocate(intArrayFromString("OpenGL_ES"), 'i8', ALLOC_NORMAL); + default: + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + } + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglBindAPI(EGLenum api); + eglBindAPI: function(api) { + if (api == 0x30A0 /* EGL_OPENGL_ES_API */) { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + } else { // if (api == 0x30A1 /* EGL_OPENVG_API */ || api == 0x30A2 /* EGL_OPENGL_API */) { + EGL.setErrorCode(0x300C /* EGL_BAD_PARAMETER */); + return 0; + } + }, + + // EGLAPI EGLenum EGLAPIENTRY eglQueryAPI(void); + eglQueryAPI: function() { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 0x30A0; // EGL_OPENGL_ES_API + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglWaitClient(void); + eglWaitClient: function() { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglWaitNative(EGLint engine); + eglWaitNative: function(nativeEngineId) { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval(EGLDisplay dpy, EGLint interval); + eglSwapInterval: function(display, interval) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + // \todo Could we use this function to specify the rate for requestAnimationFrame+main loop? + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + + // EGLAPI EGLBoolean EGLAPIENTRY eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); + eglMakeCurrent: function(display, draw, read, context) { + if (display != 62000 /* Magic ID for Emscripten 'default display' */) { + EGL.setErrorCode(0x3008 /* EGL_BAD_DISPLAY */); + return 0; + } + //\todo An EGL_NOT_INITIALIZED error is generated if EGL is not initialized for dpy. + if (context != 62004 /* Magic ID for Emscripten EGLContext */) { + EGL.setErrorCode(0x3006 /* EGL_BAD_CONTEXT */); + return 0; + } + if (read != 62006 || draw != 62006 /* Magic ID for Emscripten 'default surface' */) { + EGL.setErrorCode(0x300D /* EGL_BAD_SURFACE */); + return 0; + } + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + return 1; + }, + + eglSwapBuffers: function() { + EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); + }, }; -mergeInto(LibraryManager.library, LibraryEGL); +autoAddDeps(LibraryEGL, '$EGL'); +mergeInto(LibraryManager.library, LibraryEGL); diff --git a/src/library_gl.js b/src/library_gl.js index 765b3cdb..99198196 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -19,6 +19,8 @@ var LibraryGL = { uniforms: [], shaders: [], + uniformTable: {}, // name => uniform ID. the uID must be identical until relinking, cannot create a new uID each call to glGetUniformLocation + packAlignment: 4, // default alignment is 4 bytes unpackAlignment: 4, // default alignment is 4 bytes @@ -520,10 +522,15 @@ var LibraryGL = { glGetUniformLocation: function(program, name) { name = Pointer_stringify(name); + var ptable = GL.uniformTable[program]; + if (!ptable) ptable = GL.uniformTable[program] = {}; + var id = ptable[name]; + if (id) return id; var loc = Module.ctx.getUniformLocation(GL.programs[program], name); if (!loc) return -1; - var id = GL.getNewId(GL.uniforms); + id = GL.getNewId(GL.uniforms); GL.uniforms[id] = loc; + ptable[name] = id; return id; }, @@ -826,6 +833,7 @@ var LibraryGL = { glDeleteProgram: function(program) { Module.ctx.deleteProgram(GL.programs[program]); GL.programs[program] = null; + GL.uniformTable[program] = null; }, glAttachShader: function(program, shader) { @@ -842,6 +850,7 @@ var LibraryGL = { glLinkProgram: function(program) { Module.ctx.linkProgram(GL.programs[program]); + GL.uniformTable[program] = {}; // uniforms no longer keep the same names after linking }, glGetProgramInfoLog: function(program, maxLength, length, infoLog) { diff --git a/src/library_jansson.js b/src/library_jansson.js new file mode 100644 index 00000000..8ecb535b --- /dev/null +++ b/src/library_jansson.js @@ -0,0 +1,225 @@ +/* + * Javascript implementation for the Jansson JSON parser. + * Source: https://github.com/akheron/jansson + * + * Implemented and tested with: + * https://github.com/akheron/jansson/commit/cf1074e70ea42a1dea4d6e57b2b29532049bcd28 + */ + +var LibraryJansson = { + + $JANSSON: { + + load: function(string, flags, error) { + var json_obj = eval('(' + string + ')'); + + if (json_obj != null) { + // Create the root node with the nodeID `1` + g_json_context = [null, {"name" : "root", "node" : json_obj}]; + return 1; + } else { + g_json_context = null; + return null; + } + }, + + type: function(nodeID) { + if (!g_json_context) + return null; + + var node = g_json_context[nodeID].node; + if (typeof(node) === 'object' && (node instanceof Array)) + return 'array' + else + return typeof(node); + }, + + getNode: function(parentNodeID) { + if (!g_json_context) + return null; + + var parentNode = g_json_context[parentNodeID]; + + if (!parentNode) { + console.log("Jansson: Node with ID `" + parentNodeID + "` not found."); + return null; + } + + // Add all child nodes of the parent node to the context. + // Consequently we can access these child nodes with an ID (pointer) + // TODO: Here is room for performance improvements + if (!parentNode.begin) { + var childNodeID = g_json_context.length; + for(var childNode in parentNode.node) { + var childNodeID = g_json_context.length; + g_json_context[childNodeID] = {"name" : childNode, "node" : parentNode.node[childNode]}; + if (!parentNode.begin) parentNode.begin = childNodeID; + }; + parentNode.end = childNodeID; + } + + return parentNode; + } + + }, + + json_loads: function(string, flags, error) { + return JANSSON.load(Pointer_stringify(string), flags, error); + }, + + json_loadb: function(buffer, buflen, flags, error) { + return JANSSON.load(Pointer_stringify(buffer).substr(0, buflen), flags, error); + }, + + json_is_object: function(nodeID) { + return (JANSSON.type(nodeID) === 'object'); + }, + + json_is_array: function(nodeID) { + return (JANSSON.type(nodeID) === 'array'); + }, + + json_is_string: function(nodeID) { + return (JANSSON.type(nodeID) === 'string'); + }, + + json_is_integer: function(nodeID) { + return (JANSSON.type(nodeID) === 'number'); + }, + + json_is_real: function(nodeID) { + return (JANSSON.type(nodeID) === 'number'); + }, + + json_is_number: function(nodeID) { + return (JANSSON.type(nodeID) === 'number'); + }, + + json_is_null: function(nodeID) { + var nodeType = JANSSON.type(nodeID); + return (nodeType === 'undefined' || nodeType === 'null'); + }, + + json_object_get: function(parentNodeID, key) { + var key = Pointer_stringify(key); + var parentNode = JANSSON.getNode(parentNodeID); + + if (!parentNode) + return null; + + // Find the ID (pointer) of the requested child node + for (var i=parentNode.begin; i<=parentNode.end; i++) { + if (g_json_context[i].name == key) return i; + } + + return null; + }, + + json_object_iter: function(parentNodeID) { + var parentNode = JANSSON.getNode(parentNodeID); + + if (!parentNode) + return null; + + return parentNode.begin; + }, + + json_object_iter_next: function(parentNodeID, childNodeID) { + var parentNode = JANSSON.getNode(parentNodeID); + + if (!parentNode) + return null; + + if (childNodeID < parentNode.begin || childNodeID >= parentNode.end) + return null; + else + return childNodeID+1; + }, + + json_array_size: function(parentNodeID, index) { + var parentNode = JANSSON.getNode(parentNodeID); + + if (!parentNode) + return 0; + + var size = parentNode.end - parentNode.begin + 1; + + if (size < 0) + size = 0; + + return size; + }, + + json_array_get: function(parentNodeID, index) { + var parentNode = JANSSON.getNode(parentNodeID); + var position = parentNode.begin + index; + + if (position < parentNode.begin || position > parentNode.end) + return null; + else + return position; + }, + + json_object_iter_key: function(nodeID) { + var node = g_json_context[nodeID]; + + if (!node) + return null; + + return allocate(intArrayFromString(node.name), 'i8', ALLOC_NORMAL); + }, + + json_object_iter_value: function(nodeID) { + return nodeID; + }, + + json_string_value: function(nodeID) { + var node = g_json_context[nodeID]; + + if (!node) + return null; + + return allocate(intArrayFromString(node.node), 'i8', ALLOC_NORMAL); + }, + + json_integer_value: function(nodeID) { + var node = g_json_context[nodeID]; + + if (!node) + return null; + + // Convert JSON number to an integer + // c.f. http://stackoverflow.com/questions/596467/how-do-i-convert-a-float-to-an-int-in-javascript + if (node.node<0) + return Math.ceil(node.node); + else + return Math.floor(node.node); + }, + + json_real_value: function(nodeID) { + var node = g_json_context[nodeID]; + + if (!node) + return null; + + return node.node; + }, + + json_number_value: function(nodeID) { + var node = g_json_context[nodeID]; + + if (!node) + return null; + + return node.node; + }, + + json_delete: function(nodeID) { + // We assume, if the root node's reference count is decreased, we can forget the context. + if (nodeID == 1) + g_json_context = null; + } +}; + +autoAddDeps(LibraryJansson, '$JANSSON'); +mergeInto(LibraryManager.library, LibraryJansson); diff --git a/src/library_sdl.js b/src/library_sdl.js index c056c3f1..a77a4668 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -3,6 +3,12 @@ // See browser tests for examples (tests/runner.py, search for sdl_). Run with // python tests/runner.py browser +// Notes: +// SDL_VIDEORESIZE: This is sent when the canvas is resized. Note that the user +// cannot manually do so, so this is only sent when the +// program manually resizes it (emscripten_set_canvas_size +// or otherwise). + var LibrarySDL = { $SDL__deps: ['$FS', '$Browser'], $SDL: { @@ -189,6 +195,11 @@ var LibrarySDL = { ['i32', 'x'], ['i32', 'y'] ]), + ResizeEvent: Runtime.generateStructInfo([ + ['i32', 'type'], + ['i32', 'w'], + ['i32', 'h'] + ]), AudioSpec: Runtime.generateStructInfo([ ['i32', 'freq'], ['i16', 'format'], @@ -411,6 +422,9 @@ var LibrarySDL = { // Force-run a main event loop, since otherwise this event will never be caught! Browser.mainLoop.runner(); return true; + case 'resize': + SDL.events.push(event); + break; } return false; }, @@ -515,6 +529,12 @@ var LibrarySDL = { {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; break; } + case 'resize': { + {{{ makeSetValue('ptr', 'SDL.structs.KeyboardEvent.type', 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', 'SDL.structs.ResizeEvent.w', 'event.w', 'i32') }}}; + {{{ makeSetValue('ptr', 'SDL.structs.ResizeEvent.h', 'event.h', 'i32') }}}; + break; + } default: throw 'Unhandled SDL event: ' + event.type; } }, @@ -598,6 +618,7 @@ var LibrarySDL = { SDL.DOMEventToSDLEvent['mouseup'] = 0x402 /* SDL_MOUSEBUTTONUP */; SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */; SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */; + SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */; return 0; // success }, @@ -659,8 +680,19 @@ var LibrarySDL = { ['mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mouseout'].forEach(function(event) { Module['canvas'].addEventListener(event, SDL.receiveEvent, true); }); - Browser.setCanvasSize(width, height); - return SDL.screen = SDL.makeSurface(width, height, flags, true, 'screen'); + Browser.setCanvasSize(width, height, true); + SDL.screen = SDL.makeSurface(width, height, flags, true, 'screen'); + if (!SDL.addedResizeListener) { + SDL.addedResizeListener = true; + Browser.resizeListeners.push(function(w, h) { + SDL.receiveEvent({ + type: 'resize', + w: w, + h: h + }); + }); + } + return SDL.screen; }, SDL_GetVideoSurface: function() { @@ -1047,6 +1079,7 @@ var LibrarySDL = { }, SDL_LoadBMP: 'IMG_Load', SDL_LoadBMP_RW: 'IMG_Load', + IMG_Load_RW: 'IMG_Load', // SDL_Audio diff --git a/src/modules.js b/src/modules.js index cf1b072e..7e271e90 100644 --- a/src/modules.js +++ b/src/modules.js @@ -264,7 +264,7 @@ var LibraryManager = { load: function() { assert(!this.library); - var libraries = ['library.js', 'library_browser.js', 'library_sdl.js', 'library_gl.js', 'library_glut.js', 'library_xlib.js', 'library_egl.js', 'library_gc.js'].concat(additionalLibraries); + var libraries = ['library.js', 'library_browser.js', 'library_sdl.js', 'library_gl.js', 'library_glut.js', 'library_xlib.js', 'library_egl.js', 'library_gc.js', 'library_jansson.js'].concat(additionalLibraries); for (var i = 0; i < libraries.length; i++) { eval(processMacros(preprocess(read(libraries[i])))); } diff --git a/src/parseTools.js b/src/parseTools.js index 80ba269b..8e919d15 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -905,6 +905,7 @@ function getHeapOffset(offset, type) { // See makeSetValue function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSafe) { + if (UNALIGNED_MEMORY) align = 1; if (isStructType(type)) { var typeData = Types.types[type]; var ret = []; @@ -930,7 +931,7 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa // Special case that we can optimize ret += makeGetValue(ptr, pos, 'i16', noNeedFirst, 2, ignore) + '+' + '(' + makeGetValue(ptr, getFastValue(pos, '+', 2), 'i16', noNeedFirst, 2, ignore) + '<<16)'; - } else if (bytes <= 4) { + } else { // XXX we cannot truly handle > 4... ret = ''; for (var i = 0; i < bytes; i++) { ret += '(' + makeGetValue(ptr, getFastValue(pos, '+', i), 'i8', noNeedFirst, 1, ignore) + (i > 0 ? '<<' + (8*i) : '') + ')'; @@ -983,7 +984,8 @@ function indexizeFunctions(value, type) { //! 'null' means, in the context of SAFE_HEAP, that we should accept all types; //! which means we should write to all slabs, ignore type differences if any on reads, etc. //! @param noNeedFirst Whether to ignore the offset in the pointer itself. -function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, sep) { +function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, sep, forcedAlign) { + if (UNALIGNED_MEMORY && !forcedAlign) align = 1; sep = sep || ';'; if (isStructType(type)) { var typeData = Types.types[type]; @@ -1026,7 +1028,7 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, } } } else { - ret += makeSetValue('tempDoublePtr', 0, value, type, noNeedFirst, ignore, 8) + sep; + ret += makeSetValue('tempDoublePtr', 0, value, type, noNeedFirst, ignore, 8, null, null, true) + sep; ret += makeCopyValues(getFastValue(ptr, '+', pos), 'tempDoublePtr', Runtime.getNativeTypeSize(type), type, null, align, sep); } return ret; diff --git a/src/preamble.js b/src/preamble.js index f1b95958..0ed22d70 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -37,7 +37,7 @@ function SAFE_HEAP_ACCESS(dest, type, store, ignore) { #if USE_TYPED_ARRAYS == 2 return; // It is legitimate to violate the load-store assumption in this case #endif - if (type && type[type.length-1] == '*') type = 'i32'; // pointers are ints, for our purposes here + if (type && type.charAt(type.length-1) == '*') type = 'i32'; // pointers are ints, for our purposes here // Note that this will pass even with unions: You can store X, load X, then store Y and load Y. // You cannot, however, do the nonportable act of store X and load Y! if (store) { @@ -391,7 +391,7 @@ Module["cwrap"] = cwrap; // getValue need LLVM types ('i8', 'i32') - this is a lower-level operation function setValue(ptr, value, type, noSafe) { type = type || 'i8'; - if (type[type.length-1] === '*') type = 'i32'; // pointers are 32-bit + if (type.charAt(type.length-1) === '*') type = 'i32'; // pointers are 32-bit #if SAFE_HEAP if (noSafe) { switch(type) { @@ -425,7 +425,7 @@ Module['setValue'] = setValue; // Parallel to setValue. function getValue(ptr, type, noSafe) { type = type || 'i8'; - if (type[type.length-1] === '*') type = 'i32'; // pointers are 32-bit + if (type.charAt(type.length-1) === '*') type = 'i32'; // pointers are 32-bit #if SAFE_HEAP if (noSafe) { switch(type) { @@ -700,6 +700,8 @@ STACK_MAX = tempDoublePtr + 8; STATICTOP = alignMemoryPage(STACK_MAX); +assert(STATICTOP < TOTAL_MEMORY); // Stack must fit in TOTAL_MEMORY; allocations from here on may enlarge TOTAL_MEMORY + var nullString = allocate(intArrayFromString('(null)'), 'i8', ALLOC_STATIC); function callRuntimeCallbacks(callbacks) { diff --git a/src/runtime.js b/src/runtime.js index 816d864f..dd14e779 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -187,7 +187,7 @@ var Runtime = { "%double": 8 }['%'+type]; // add '%' since float and double confuse Closure compiler as keys, and also spidermonkey as a compiler will remove 's from '_i8' etc if (!size) { - if (type[type.length-1] == '*') { + if (type.charAt(type.length-1) == '*') { size = Runtime.QUANTUM_SIZE; // A pointer } else if (type[0] == 'i') { var bits = parseInt(type.substr(1)); diff --git a/src/settings.js b/src/settings.js index e4899215..4017ad98 100644 --- a/src/settings.js +++ b/src/settings.js @@ -72,6 +72,10 @@ var DOUBLE_MODE = 1; // How to load and store 64-bit doubles. Without typed arra // then load it aligned, and that load-store will make JS engines alter it if it is being // stored to a typed array for security reasons. That will 'fix' the number from being a // NaN or an infinite number. +var UNALIGNED_MEMORY = 0; // If enabled, all memory accesses are assumed to be unaligned. (This only matters in + // typed arrays mode 2 where alignment is relevant.) In unaligned memory mode, you + // can run nonportable code that typically would break in JS (or on ARM for that + // matter, which also cannot do unaligned reads/writes), at the cost of slowness var PRECISE_I64_MATH = 1; // If enabled, i64 addition etc. is emulated - which is slow but precise. If disabled, // we use the 'double trick' which is fast but incurs rounding at high values. // Note that we do not catch 32-bit multiplication by default (which must be done in @@ -124,6 +128,7 @@ var LIBRARY_DEBUG = 0; // Print out when we enter a library call (library*.js). // emscripten_run_script("Runtime.debug = ...;"); var GL_DEBUG = 0; // Print out all calls into WebGL. As with LIBRARY_DEBUG, you can set a runtime // option, in this case GL.debug. +var SOCKET_DEBUG = 0; // Log out socket/network data transfer. var PROFILE_MAIN_LOOP = 0; // Profile the function called in set_main_loop @@ -197,6 +202,10 @@ var INCLUDE_FULL_LIBRARY = 0; // Whether to include the whole library rather tha // functions used by the generated code. This is needed when // dynamically loading modules that make use of runtime // library functions that are not used in the main module. + // Note that this includes js libraries but *not* C. You will + // need the main file to include all needed C libraries. For + // example, if a library uses malloc or new, you will need + // to use those in the main file too to link in dlmalloc. var SHELL_FILE = 0; // set this to a string to override the shell file used |
