summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/analyzer.js4
-rw-r--r--src/embind/embind.js620
-rw-r--r--src/embind/emval.js111
-rw-r--r--src/jsifier.js14
-rw-r--r--src/library.js271
-rw-r--r--src/library_browser.js66
-rw-r--r--src/library_egl.js421
-rw-r--r--src/library_gl.js11
-rw-r--r--src/library_jansson.js225
-rw-r--r--src/library_sdl.js37
-rw-r--r--src/modules.js2
-rw-r--r--src/parseTools.js8
-rw-r--r--src/preamble.js8
-rw-r--r--src/runtime.js2
-rw-r--r--src/settings.js9
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