Procházet zdrojové kódy

Change wasm/js/runtime.mjs to a normal .js file; Add interfaces and functions to a global `odin` variable

gingerBill před 3 roky
rodič
revize
c20b5cbd10
1 změnil soubory, kde provedl 708 přidání a 651 odebrání
  1. 708 651
      vendor/wasm/js/runtime.js

+ 708 - 651
vendor/wasm/js/runtime.mjs → vendor/wasm/js/runtime.js

@@ -1,7 +1,9 @@
+(function() {
 class WasmMemoryInterface {
 	constructor() {
 		this.memory = null;
 		this.exports = null;
+		this.listenerMap = {};
 	}
 
 	setMemory(memory) {
@@ -10,7 +12,6 @@ class WasmMemoryInterface {
 
 	setExports(exports) {
 		this.exports = exports;
-		this.listenerMap = {};
 	}
 
 	get mem() {
@@ -90,477 +91,191 @@ class WasmMemoryInterface {
 	storeUint(addr, value) { this.mem.setUint32 (addr, value, true); }
 };
 
-function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) {
-	const MAX_INFO_CONSOLE_LINES = 512;
-	let infoConsoleLines = new Array();
-	const addConsoleLine = (line) => {
-		if (!line) {
-			return;
+class WebGLInterface {
+	constructor(wasmMemoryInterface) {
+		this.wasmMemoryInterface = wasmMemoryInterface;
+		this.ctxElement         = null;
+		this.ctx                = null;
+		this.ctxVersion         = 1.0;
+		this.counter            = 1;
+		this.lastError          = 0;
+		this.buffers            = [];
+		this.mappedBuffers      = {};
+		this.programs           = [];
+		this.framebuffers       = [];
+		this.renderbuffers      = [];
+		this.textures           = [];
+		this.uniforms           = [];
+		this.shaders            = [];
+		this.vaos               = [];
+		this.contexts           = [];
+		this.currentContext     = null;
+		this.offscreenCanvases  = {};
+		this.timerQueriesEXT    = [];
+		this.queries            = [];
+		this.samplers           = [];
+		this.transformFeedbacks = [];
+		this.syncs              = [];
+		this.programInfos       = {};
+	}
+
+	get mem() {
+		return this.wasmMemoryInterface
+	}
+
+	setCurrentContext(element, contextSettings) {
+		if (!element) {
+			return false;
 		}
-		if (line.endsWith("\n")) {
-			line = line.substring(0, line.length-1);
-		} else if (infoConsoleLines.length > 0) {
-			let prev_line = infoConsoleLines.pop();
-			line = prev_line.concat(line);
+		if (this.ctxElement == element) {
+			return true;
 		}
-		infoConsoleLines.push(line);
 
-		if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) {
-			infoConsoleLines.shift();
+		contextSettings = contextSettings ?? {};
+		this.ctx = element.getContext("webgl2", contextSettings) || element.getContext("webgl", contextSettings);
+		if (!this.ctx) {
+			return false;
 		}
+		this.ctxElement = element;
+		if (this.ctx.getParameter(0x1F02).indexOf("WebGL 2.0") !== -1) {
+			this.ctxVersion = 2.0;
+		} else {
+			this.ctxVersion = 1.0;
+		}
+		return true;
+	}
 
-		let data = "";
-		for (let i = 0; i < infoConsoleLines.length; i++) {
-			if (i != 0) {
-				data = data.concat("\n");
+	assertWebGL2() {
+		if (this.ctxVersion < 2) {
+			throw new Error("WebGL2 procedure called in a canvas without a WebGL2 context");
+		}
+	}
+	getNewId(table) {
+		for (var ret = this.counter++, i = table.length; i < ret; i++) {
+			table[i] = null;
+		}
+		return ret;
+	}
+	recordError(errorCode) {
+		this.lastError || (this.lastError = errorCode);
+	}
+	populateUniformTable(program) {
+		let p = this.programs[program];
+		this.programInfos[program] = {
+			uniforms: {},
+			maxUniformLength: 0,
+			maxAttributeLength: -1,
+			maxUniformBlockNameLength: -1,
+		};
+		for (let ptable = this.programInfos[program], utable = ptable.uniforms, numUniforms = this.ctx.getProgramParameter(p, this.ctx.ACTIVE_UNIFORMS), i = 0; i < numUniforms; ++i) {
+			let u = this.ctx.getActiveUniform(p, i);
+			let name = u.name;
+			if (ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length + 1), name.indexOf("]", name.length - 1) !== -1) {
+				name = name.slice(0, name.lastIndexOf("["));
+			}
+			let loc = this.ctx.getUniformLocation(p, name);
+			if (loc !== null) {
+				let id = this.getNewId(this.uniforms);
+				utable[name] = [u.size, id], this.uniforms[id] = loc;
+				for (let j = 1; j < u.size; ++j) {
+					let n = name + "[" + j + "]";
+					let loc = this.ctx.getUniformLocation(p, n);
+					let id = this.getNewId(this.uniforms);
+					this.uniforms[id] = loc;
+				}
 			}
-			data = data.concat(infoConsoleLines[i]);
 		}
-
-		if (consoleElement) {
-			let info = consoleElement;
-			info.innerHTML = data;
-			info.scrollTop = info.scrollHeight;
+	}
+	getSource(shader, strings_ptr, strings_length) {
+		const STRING_SIZE = 2*4;
+		let source = "";
+		for (let i = 0; i < strings_length; i++) {
+			let ptr = this.mem.loadPtr(strings_ptr + i*STRING_SIZE);
+			let len = this.mem.loadPtr(strings_ptr + i*STRING_SIZE + 4);
+			let str = this.mem.loadString(ptr, len);
+			source += str;
 		}
-	};
-
-	let event_temp_data = {};
+		return source;
+	}
 
-	return {
-		"env": {},
-		"odin_env": {
-			write: (fd, ptr, len) => {
-				const str = wasmMemoryInterface.loadString(ptr, len);
-				if (fd == 1) {
-					addConsoleLine(str);
-					return;
-				} else if (fd == 2) {
-					addConsoleLine(str);
-					return;
-				} else {
-					throw new Error("Invalid fd to 'write'" + stripNewline(str));
-				}
+	getWebGL1Interface() {
+		return {
+			SetCurrentContextById: (name_ptr, name_len) => {
+				let name = this.mem.loadString(name_ptr, name_len);
+				let element = document.getElementById(name);
+				return this.setCurrentContext(element, {alpha: true, antialias: true, depth: true, premultipliedAlpha: true});
 			},
-			trap: () => { throw new Error() },
-			alert: (ptr, len) => { alert(wasmMemoryInterface.loadString(ptr, len)) },
-			abort: () => { Module.abort() },
-			evaluate: (str_ptr, str_len) => { eval.call(null, wasmMemoryInterface.loadString(str_ptr, str_len)); },
+			CreateCurrentContextById: (name_ptr, name_len, attributes) => {
+				let name = this.mem.loadString(name_ptr, name_len);
+				let element = document.getElementById(name);
 
-			time_now: () => {
-				return performance.now() * 1e6;
+				let contextSettings = {
+					alpha:                        !(attributes & (1<<0)),
+					antialias:                    !(attributes & (1<<1)),
+					depth:                        !(attributes & (1<<2)),
+					failIfMajorPerformanceCaveat: !!(attributes & (1<<3)),
+					premultipliedAlpha:           !(attributes & (1<<4)),
+					preserveDrawingBuffer:        !!(attributes & (1<<5)),
+					stencil:                      !!(attributes & (1<<6)),
+					desynchronized:               !!(attributes & (1<<7)),
+				};
+
+				return this.setCurrentContext(element, contextSettings);
+			},
+			GetCurrentContextAttributes: () => {
+				if (!this.ctx) {
+					return 0;
+				}
+				let attrs = this.ctx.getContextAttributes();
+				let res = 0;
+				if (!attrs.alpha)                        res |= 1<<0;
+				if (!attrs.antialias)                    res |= 1<<1;
+				if (!attrs.depth)                        res |= 1<<2;
+				if (attrs.failIfMajorPerformanceCaveat)  res |= 1<<3;
+				if (!attrs.premultipliedAlpha)           res |= 1<<4;
+				if (attrs.preserveDrawingBuffer)         res |= 1<<5;
+				if (attrs.stencil)                       res |= 1<<6;
+				if (attrs.desynchronized)                res |= 1<<7;
+				return res;
 			},
 
-			sqrt:    (x) => Math.sqrt(x),
-			sin:     (x) => Math.sin(x),
-			cos:     (x) => Math.cos(x),
-			pow:     (x) => Math.pow(x),
-			fmuladd: (x, y, z) => x*y + z,
-			ln:      (x) => Math.log(x),
-			exp:     (x) => Math.exp(x),
-			ldexp:   (x) => Math.ldexp(x),
-		},
-		"odin_dom": {
-			init_event_raw: (ep) => {
-				const W = 4;
-				let offset = ep;
-				let off = (amount, alignment) => {
-					if (alignment === undefined) {
-						alignment = Math.min(amount, W);
-					}
-					if (offset % alignment != 0) {
-						offset += alignment - (offset%alignment);
-					}
-					let x = offset;
-					offset += amount;
-					return x;
-				};
+			DrawingBufferWidth:  () => this.ctx.drawingBufferWidth,
+			DrawingBufferHeight: () => this.ctx.drawingBufferHeight,
 
-				let wmi = wasmMemoryInterface;
+			IsExtensionSupported: (name_ptr, name_len) => {
+				let name = this.mem.loadString(name_ptr, name_len);
+				let extensions = this.ctx.getSupportedExtensions();
+				return extensions.indexOf(name) !== -1
+			},
 
-				let e = event_temp_data.event;
 
-				wmi.storeU32(off(4), event_temp_data.name_code);
-				if (e.target == document) {
-					wmi.storeU32(off(4), 1);
-				} else if (e.target == window) {
-					wmi.storeU32(off(4), 2);
-				} else {
-					wmi.storeU32(off(4), 0);
+			GetError: () => {
+				let err = this.lastError;
+				this.recordError(0);
+				if (err) {
+					return err;
 				}
-				if (e.currentTarget == document) {
-					wmi.storeU32(off(4), 1);
-				} else if (e.currentTarget == window) {
-					wmi.storeU32(off(4), 2);
-				} else {
-					wmi.storeU32(off(4), 0);
+				return this.ctx.getError();
+			},
+
+			GetWebGLVersion: (major_ptr, minor_ptr) => {
+				let version = this.ctx.getParameter(0x1F02);
+				if (version.indexOf("WebGL 2.0") !== -1) {
+					this.mem.storeI32(major_ptr, 2);
+					this.mem.storeI32(minor_ptr, 0);
+					return;
 				}
 
-				wmi.storeUint(off(W), event_temp_data.id_ptr);
-				wmi.storeUint(off(W), event_temp_data.id_len);
-
-				wmi.storeF64(off(8), e.timeStamp*1e-3);
-
-				wmi.storeU8(off(1), e.eventPhase);
-				wmi.storeU8(off(1), !!e.bubbles);
-				wmi.storeU8(off(1), !!e.cancelable);
-				wmi.storeU8(off(1), !!e.composed);
-				wmi.storeU8(off(1), !!e.isComposing);
-				wmi.storeU8(off(1), !!e.isTrusted);
-
-				let base = off(0, 8);
-				if (e instanceof MouseEvent) {
-					wmi.storeI64(off(8), e.screenX);
-					wmi.storeI64(off(8), e.screenY);
-					wmi.storeI64(off(8), e.clientX);
-					wmi.storeI64(off(8), e.clientY);
-					wmi.storeI64(off(8), e.offsetX);
-					wmi.storeI64(off(8), e.offsetY);
-					wmi.storeI64(off(8), e.pageX);
-					wmi.storeI64(off(8), e.pageY);
-					wmi.storeI64(off(8), e.movementX);
-					wmi.storeI64(off(8), e.movementY);
-
-					wmi.storeU8(off(1), !!e.ctrlKey);
-					wmi.storeU8(off(1), !!e.shiftKey);
-					wmi.storeU8(off(1), !!e.altKey);
-					wmi.storeU8(off(1), !!e.metaKey);
-
-					wmi.storeI16(off(2), e.button);
-					wmi.storeU16(off(2), e.buttons);
-				} else if (e instanceof KeyboardEvent) {
-					let keyOffset = off(W*2, W);
-					let codeOffet = off(W*2, W);
-					wmi.storeU8(off(1), e.location);
-
-					wmi.storeU8(off(1), !!e.ctrlKey);
-					wmi.storeU8(off(1), !!e.shiftKey);
-					wmi.storeU8(off(1), !!e.altKey);
-					wmi.storeU8(off(1), !!e.metaKey);
-
-					wmi.storeU8(off(1), !!e.repeat);
-				} else if (e instanceof WheelEvent) {
-					wmi.storeF64(off(8), e.deltaX);
-					wmi.storeF64(off(8), e.deltaY);
-					wmi.storeF64(off(8), e.deltaZ);
-					wmi.storeU32(off(4), e.deltaMode);
-				} else if (e instanceof Event) {
-					if ('scrollX' in e) {
-						wmi.storeF64(off(8), e.scrollX);
-						wmi.storeF64(off(8), e.scrollY);
-					}
-				}
-			},
-
-			add_event_listener: (id_ptr, id_len, name_ptr, name_len, name_code, data, callback, use_capture) => {
-				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
-				let name = wasmMemoryInterface.loadString(name_ptr, name_len);
-				let element = document.getElementById(id);
-				if (element == undefined) {
-					return false;
-				}
-
-				let listener = (e) => {
-					const odin_ctx = wasmMemoryInterface.exports.default_context_ptr();
-					event_temp_data.id_ptr = id_ptr;
-					event_temp_data.id_len = id_len;
-					event_temp_data.event = e;
-					event_temp_data.name_code = name_code;
-					// console.log(e);
-					wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx);
-				};
-				wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener;
-				element.addEventListener(name, listener, !!use_capture);
-				return true;
-			},
-
-			remove_event_listener: (id_ptr, id_len, name_ptr, name_len, data, callback) => {
-				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
-				let name = wasmMemoryInterface.loadString(name_ptr, name_len);
-				let element = document.getElementById(id);
-				if (element == undefined) {
-					return false;
-				}
-
-				let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}];
-				if (listener == undefined) {
-					return false;
-				}
-				element.removeEventListener(name, listener);
-				return true;
-			},
-
-
-			add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => {
-				let name = wasmMemoryInterface.loadString(name_ptr, name_len);
-				let element = window;
-				let listener = (e) => {
-					const odin_ctx = wasmMemoryInterface.exports.default_context_ptr();
-					event_temp_data.id_ptr = 0;
-					event_temp_data.id_len = 0;
-					event_temp_data.event = e;
-					event_temp_data.name_code = name_code;
-					// console.log(e);
-					wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx);
-				};
-				wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener;
-				element.addEventListener(name, listener, !!use_capture);
-				return true;
-			},
-
-			remove_window_event_listener: (name_ptr, name_len, data, callback) => {
-				let name = wasmMemoryInterface.loadString(name_ptr, name_len);
-				let element = window;
-				let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}];
-				if (listener == undefined) {
-					return false;
-				}
-				element.removeEventListener(name, listener);
-				return true;
-			},
-
-			event_stop_propagation: () => {
-				if (event_temp_data && event_temp_data.event) {
-					event_temp_data.event.eventStopPropagation();
-				}
-			},
-			event_stop_immediate_propagation: () => {
-				if (event_temp_data && event_temp_data.event) {
-					event_temp_data.event.eventStopImmediatePropagation();
-				}
-			},
-			event_prevent_default: () => {
-				if (event_temp_data && event_temp_data.event) {
-					event_temp_data.event.eventPreventDefault();
-				}
-			},
-
-			get_element_value_f64: (id_ptr, id_len) => {
-				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
-				let element = document.getElementById(id);
-				return element ? element.value : 0;
-			},
-			get_element_value_string: (id_ptr, id_len, buf_ptr, buf_len) => {
-				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
-				let element = document.getElementById(id);
-				if (element) {
-					let str = element.value;
-					if (buf_len > 0 && buf_ptr) {
-						let n = Math.min(buf_len, str.length);
-						str = str.substring(0, n);
-						this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(str))
-						return n;
-					}
-				}
-				return 0;
-			},
-			get_element_min_max: (ptr_array2_f64, id_ptr, id_len) => {
-				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
-				let element = document.getElementById(id);
-				if (element) {
-					let values = wasmMemoryInterface.loadF64Array(ptr_array2_f64, 2);
-					values[0] = element.min;
-					values[1] = element.max;
-				}
-			},
-			set_element_value: (id_ptr, id_len, value) => {
-				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
-				let element = document.getElementById(id);
-				if (element) {
-					element.value = value;
-				}
-			},
-		},
-	};
-};
-
-
-class WebGLInterface {
-	constructor(wasmMemoryInterface, canvasElement, contextSettings) {
-		this.wasmMemoryInterface = wasmMemoryInterface;
-		this.ctxElement         = null;
-		this.ctx                = null;
-		this.ctxVersion         = 1.0;
-		this.counter            = 1;
-		this.lastError          = 0;
-		this.buffers            = [];
-		this.mappedBuffers      = {};
-		this.programs           = [];
-		this.framebuffers       = [];
-		this.renderbuffers      = [];
-		this.textures           = [];
-		this.uniforms           = [];
-		this.shaders            = [];
-		this.vaos               = [];
-		this.contexts           = [];
-		this.currentContext     = null;
-		this.offscreenCanvases  = {};
-		this.timerQueriesEXT    = [];
-		this.queries            = [];
-		this.samplers           = [];
-		this.transformFeedbacks = [];
-		this.syncs              = [];
-		this.programInfos       = {};
-
-		this.setCurrentContext(canvasElement, contextSettings);
-	}
-
-	get mem() {
-		return this.wasmMemoryInterface
-	}
-
-	setCurrentContext(element, contextSettings) {
-		if (!element) {
-			return false;
-		}
-		if (this.ctxElement == element) {
-			return true;
-		}
-
-		contextSettings = contextSettings ?? {};
-		this.ctx = element.getContext("webgl2", contextSettings) || element.getContext("webgl", contextSettings);
-		if (!this.ctx) {
-			return false;
-		}
-		this.ctxElement = element;
-		if (this.ctx.getParameter(0x1F02).indexOf("WebGL 2.0") !== -1) {
-			this.ctxVersion = 2.0;
-		} else {
-			this.ctxVersion = 1.0;
-		}
-		return true;
-	}
-
-	assertWebGL2() {
-		if (this.ctxVersion < 2) {
-			throw new Error("WebGL2 procedure called in a canvas without a WebGL2 context");
-		}
-	}
-	getNewId(table) {
-		for (var ret = this.counter++, i = table.length; i < ret; i++) {
-			table[i] = null;
-		}
-		return ret;
-	}
-	recordError(errorCode) {
-		this.lastError || (this.lastError = errorCode);
-	}
-	populateUniformTable(program) {
-		let p = this.programs[program];
-		this.programInfos[program] = {
-			uniforms: {},
-			maxUniformLength: 0,
-			maxAttributeLength: -1,
-			maxUniformBlockNameLength: -1,
-		};
-		for (let ptable = this.programInfos[program], utable = ptable.uniforms, numUniforms = this.ctx.getProgramParameter(p, this.ctx.ACTIVE_UNIFORMS), i = 0; i < numUniforms; ++i) {
-			let u = this.ctx.getActiveUniform(p, i);
-			let name = u.name;
-			if (ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length + 1), name.indexOf("]", name.length - 1) !== -1) {
-				name = name.slice(0, name.lastIndexOf("["));
-			}
-			let loc = this.ctx.getUniformLocation(p, name);
-			if (loc !== null) {
-				let id = this.getNewId(this.uniforms);
-				utable[name] = [u.size, id], this.uniforms[id] = loc;
-				for (let j = 1; j < u.size; ++j) {
-					let n = name + "[" + j + "]";
-					let loc = this.ctx.getUniformLocation(p, n);
-					let id = this.getNewId(this.uniforms);
-					this.uniforms[id] = loc;
-				}
-			}
-		}
-	}
-	getSource(shader, strings_ptr, strings_length) {
-		const STRING_SIZE = 2*4;
-		let source = "";
-		for (let i = 0; i < strings_length; i++) {
-			let ptr = this.mem.loadPtr(strings_ptr + i*STRING_SIZE);
-			let len = this.mem.loadPtr(strings_ptr + i*STRING_SIZE + 4);
-			let str = this.mem.loadString(ptr, len);
-			source += str;
-		}
-		return source;
-	}
-
-	getWebGL1Interface() {
-		return {
-			SetCurrentContextById: (name_ptr, name_len) => {
-				let name = this.mem.loadString(name_ptr, name_len);
-				let element = document.getElementById(name);
-				return this.setCurrentContext(element, {alpha: true, antialias: true, depth: true, premultipliedAlpha: true});
-			},
-			CreateCurrentContextById: (name_ptr, name_len, attributes) => {
-				let name = this.mem.loadString(name_ptr, name_len);
-				let element = document.getElementById(name);
-
-				let contextSettings = {
-					alpha:                        !(attributes & (1<<0)),
-					antialias:                    !(attributes & (1<<1)),
-					depth:                        !(attributes & (1<<2)),
-					failIfMajorPerformanceCaveat: !!(attributes & (1<<3)),
-					premultipliedAlpha:           !(attributes & (1<<4)),
-					preserveDrawingBuffer:        !!(attributes & (1<<5)),
-					stencil:                      !!(attributes & (1<<6)),
-					desynchronized:               !!(attributes & (1<<7)),
-				};
-
-				return this.setCurrentContext(element, contextSettings);
-			},
-			GetCurrentContextAttributes: () => {
-				if (!this.ctx) {
-					return 0;
-				}
-				let attrs = this.ctx.getContextAttributes();
-				let res = 0;
-				if (!attrs.alpha)                        res |= 1<<0;
-				if (!attrs.antialias)                    res |= 1<<1;
-				if (!attrs.depth)                        res |= 1<<2;
-				if (attrs.failIfMajorPerformanceCaveat)  res |= 1<<3;
-				if (!attrs.premultipliedAlpha)           res |= 1<<4;
-				if (attrs.preserveDrawingBuffer)         res |= 1<<5;
-				if (attrs.stencil)                       res |= 1<<6;
-				if (attrs.desynchronized)                res |= 1<<7;
-				return res;
-			},
-
-			DrawingBufferWidth:  () => this.ctx.drawingBufferWidth,
-			DrawingBufferHeight: () => this.ctx.drawingBufferHeight,
-
-			IsExtensionSupported: (name_ptr, name_len) => {
-				let name = this.mem.loadString(name_ptr, name_len);
-				let extensions = this.ctx.getSupportedExtensions();
-				return extensions.indexOf(name) !== -1
-			},
-
-
-			GetError: () => {
-				let err = this.lastError;
-				this.recordError(0);
-				if (err) {
-					return err;
-				}
-				return this.ctx.getError();
-			},
-
-			GetWebGLVersion: (major_ptr, minor_ptr) => {
-				let version = this.ctx.getParameter(0x1F02);
-				if (version.indexOf("WebGL 2.0") !== -1) {
-					this.mem.storeI32(major_ptr, 2);
-					this.mem.storeI32(minor_ptr, 0);
-					return;
-				}
-
-				this.mem.storeI32(major_ptr, 1);
-				this.mem.storeI32(minor_ptr, 0);
-			},
-			GetESVersion: (major_ptr, minor_ptr) => {
-				let version = this.ctx.getParameter(0x1F02);
-				if (version.indexOf("OpenGL ES 3.0") !== -1) {
-					this.mem.storeI32(major_ptr, 3);
-					this.mem.storeI32(minor_ptr, 0);
-					return;
-				}
+				this.mem.storeI32(major_ptr, 1);
+				this.mem.storeI32(minor_ptr, 0);
+			},
+			GetESVersion: (major_ptr, minor_ptr) => {
+				let version = this.ctx.getParameter(0x1F02);
+				if (version.indexOf("OpenGL ES 3.0") !== -1) {
+					this.mem.storeI32(major_ptr, 3);
+					this.mem.storeI32(minor_ptr, 0);
+					return;
+				}
 
 				this.mem.storeI32(major_ptr, 2);
 				this.mem.storeI32(minor_ptr, 0);
@@ -1206,255 +921,597 @@ class WebGLInterface {
 				this.assertWebGL2();
 				this.ctx.drawElementsInstanced(mode, count, type, offset, instanceCount);
 			},
-			DrawRangeElements: (mode, start, end, count, type, offset) => {
+			DrawRangeElements: (mode, start, end, count, type, offset) => {
+				this.assertWebGL2();
+				this.ctx.drawRangeElements(mode, start, end, count, type, offset);
+			},
+
+			/* Multiple Render Targets */
+			DrawBuffers: (buffers_ptr, buffers_len) => {
+				this.assertWebGL2();
+				let array = this.mem.loadU32Array(buffers_ptr, buffers_len);
+				this.ctx.drawBuffers(array);
+			},
+			ClearBufferfv: (buffer, drawbuffer, values_ptr, values_len) => {
+				this.assertWebGL2();
+				let array = this.mem.loadF32Array(values_ptr, values_len);
+				this.ctx.clearBufferfv(buffer, drawbuffer, array);
+			},
+			ClearBufferiv: (buffer, drawbuffer, values_ptr, values_len) => {
+				this.assertWebGL2();
+				let array = this.mem.loadI32Array(values_ptr, values_len);
+				this.ctx.clearBufferiv(buffer, drawbuffer, array);
+			},
+			ClearBufferuiv: (buffer, drawbuffer, values_ptr, values_len) => {
+				this.assertWebGL2();
+				let array = this.mem.loadU32Array(values_ptr, values_len);
+				this.ctx.clearBufferuiv(buffer, drawbuffer, array);
+			},
+			ClearBufferfi: (buffer, drawbuffer, depth, stencil) => {
+				this.assertWebGL2();
+				this.ctx.clearBufferfi(buffer, drawbuffer, depth, stencil);
+			},
+
+			/* Query Objects */
+			CreateQuery: () => {
+				this.assertWebGL2();
+				let query = this.ctx.createQuery();
+				let id = this.getNewId(this.queries);
+				query.name = id;
+				this.queries[id] = query;
+				return id;
+			},
+			DeleteQuery: (id) => {
+				this.assertWebGL2();
+				let obj = this.querys[id];
+				if (obj && id != 0) {
+					this.ctx.deleteQuery(obj);
+					this.querys[id] = null;
+				}
+			},
+			IsQuery: (query) => {
+				this.assertWebGL2();
+				return this.ctx.isQuery(this.queries[query]);
+			},
+			BeginQuery: (target, query) => {
+				this.assertWebGL2();
+				this.ctx.beginQuery(target, this.queries[query])
+			},
+			EndQuery: (target) => {
+				this.assertWebGL2();
+				this.ctx.endQuery(target);
+			},
+			GetQuery: (target, pname) => {
+				this.assertWebGL2();
+				let query = this.ctx.getQuery(target, pname);
+				if (!query) {
+					return 0;
+				}
+				if (this.queries.indexOf(query) !== -1) {
+					return query.name;
+				}
+				let id = this.getNewId(this.queries);
+				query.name = id;
+				this.queries[id] = query;
+				return id;
+			},
+
+			/* Sampler Objects */
+			CreateSampler: () => {
+				this.assertWebGL2();
+				let sampler = this.ctx.createSampler();
+				let id = this.getNewId(this.samplers);
+				sampler.name = id;
+				this.samplers[id] = sampler;
+				return id;
+			},
+			DeleteSampler: (id) => {
+				this.assertWebGL2();
+				let obj = this.samplers[id];
+				if (obj && id != 0) {
+					this.ctx.deleteSampler(obj);
+					this.samplers[id] = null;
+				}
+			},
+			IsSampler: (sampler) => {
+				this.assertWebGL2();
+				return this.ctx.isSampler(this.samplers[sampler]);
+			},
+			BindSampler: (unit, sampler) => {
+				this.assertWebGL2();
+				this.ctx.bindSampler(unit, this.samplers[Sampler]);
+			},
+			SamplerParameteri: (sampler, pname, param) => {
+				this.assertWebGL2();
+				this.ctx.samplerParameteri(this.samplers[sampler], pname, param);
+			},
+			SamplerParameterf: (sampler, pname, param) => {
+				this.assertWebGL2();
+				this.ctx.samplerParameterf(this.samplers[sampler], pname, param);
+			},
+
+			/* Sync objects */
+			FenceSync: (condition, flags) => {
+				this.assertWebGL2();
+				let sync = this.ctx.fenceSync(condition, flags);
+				let id = this.getNewId(this.syncs);
+				sync.name = id;
+				this.syncs[id] = sync;
+				return id;
+			},
+			IsSync: (sync) => {
+				this.assertWebGL2();
+				return this.ctx.isSync(this.syncs[sync]);
+			},
+			DeleteSync: (id) => {
+				this.assertWebGL2();
+				let obj = this.syncs[id];
+				if (obj && id != 0) {
+					this.ctx.deleteSampler(obj);
+					this.syncs[id] = null;
+				}
+			},
+			ClientWaitSync: (sync, flags, timeout) => {
+				this.assertWebGL2();
+				return this.ctx.clientWaitSync(this.syncs[sync], flags, timeout);
+			},
+			WaitSync: (sync, flags, timeout) => {
+				this.assertWebGL2();
+				this.ctx.waitSync(this.syncs[sync], flags, timeout)	;
+			},
+
+
+			/* Transform Feedback */
+			CreateTransformFeedback: () => {
+				this.assertWebGL2();
+				let transformFeedback = this.ctx.createtransformFeedback();
+				let id = this.getNewId(this.transformFeedbacks);
+				transformFeedback.name = id;
+				this.transformFeedbacks[id] = transformFeedback;
+				return id;
+			},
+			DeleteTransformFeedback: (id)  => {
+				this.assertWebGL2();
+				let obj = this.transformFeedbacks[id];
+				if (obj && id != 0) {
+					this.ctx.deleteTransformFeedback(obj);
+					this.transformFeedbacks[id] = null;
+				}
+			},
+			IsTransformFeedback: (tf) => {
+				this.assertWebGL2();
+				return this.ctx.isTransformFeedback(this.transformFeedbacks[tf]);
+			},
+			BindTransformFeedback: (target, tf) => {
+				this.assertWebGL2();
+				this.ctx.bindTransformFeedback(target, this.transformFeedbacks[tf]);
+			},
+			BeginTransformFeedback: (primitiveMode) => {
+				this.assertWebGL2();
+				this.ctx.beginTransformFeedback(primitiveMode);
+			},
+			EndTransformFeedback: () => {
+				this.assertWebGL2();
+				this.ctx.endTransformFeedback();
+			},
+			TransformFeedbackVaryings: (program, varyings_ptr, varyings_len, bufferMode) => {
+				this.assertWebGL2();
+				let varyings = [];
+				for (let i = 0; i < varyings_len; i++) {
+					let ptr = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 0*4);
+					let len = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 1*4);
+					varyings.push(this.mem.loadString(ptr, len));
+				}
+				this.ctx.transformFeedbackVaryings(this.programs[program], varyings, bufferMode);
+			},
+			PauseTransformFeedback: () => {
+				this.assertWebGL2();
+				this.ctx.pauseTransformFeedback();
+			},
+			ResumeTransformFeedback: () => {
 				this.assertWebGL2();
-				this.ctx.drawRangeElements(mode, start, end, count, type, offset);
+				this.ctx.resumeTransformFeedback();
 			},
 
-			/* Multiple Render Targets */
-			DrawBuffers: (buffers_ptr, buffers_len) => {
+
+			/* Uniform Buffer Objects and Transform Feedback Buffers */
+			BindBufferBase: (target, index, buffer) => {
 				this.assertWebGL2();
-				let array = this.mem.loadU32Array(buffers_ptr, buffers_len);
-				this.ctx.drawBuffers(array);
+				this.ctx.bindBufferBase(target, index, this.buffers[buffer]);
 			},
-			ClearBufferfv: (buffer, drawbuffer, values_ptr, values_len) => {
+			BindBufferRange: (target, index, buffer, offset, size) => {
 				this.assertWebGL2();
-				let array = this.mem.loadF32Array(values_ptr, values_len);
-				this.ctx.clearBufferfv(buffer, drawbuffer, array);
+				this.ctx.bindBufferRange(target, index, this.buffers[buffer], offset, size);
 			},
-			ClearBufferiv: (buffer, drawbuffer, values_ptr, values_len) => {
+			GetUniformBlockIndex: (program, uniformBlockName_ptr, uniformBlockName_len) => {
 				this.assertWebGL2();
-				let array = this.mem.loadI32Array(values_ptr, values_len);
-				this.ctx.clearBufferiv(buffer, drawbuffer, array);
+				return this.ctx.getUniformBlockIndex(this.programs[program], this.mem.loadString(uniformBlockName_ptr, uniformBlockName_len));
 			},
-			ClearBufferuiv: (buffer, drawbuffer, values_ptr, values_len) => {
+			// any getActiveUniformBlockParameter(WebGLProgram program, GLuint uniformBlockIndex, GLenum pname);
+			GetActiveUniformBlockName: (program, uniformBlockIndex, buf_ptr, buf_len, length_ptr) => {
 				this.assertWebGL2();
-				let array = this.mem.loadU32Array(values_ptr, values_len);
-				this.ctx.clearBufferuiv(buffer, drawbuffer, array);
+				let name = this.ctx.getActiveUniformBlockName(this.programs[program], uniformBlockIndex);
+
+				let n = Math.min(buf_len, name.length);
+				name = name.substring(0, n);
+				this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(name))
+				this.mem.storeInt(length_ptr, n);
 			},
-			ClearBufferfi: (buffer, drawbuffer, depth, stencil) => {
+			UniformBlockBinding: (program, uniformBlockIndex, uniformBlockBinding) => {
 				this.assertWebGL2();
-				this.ctx.clearBufferfi(buffer, drawbuffer, depth, stencil);
+				this.ctx.uniformBlockBinding(this.programs[program], uniformBlockIndex, uniformBlockBinding);
 			},
 
-			/* Query Objects */
-			CreateQuery: () => {
+			/* Vertex Array Objects */
+			CreateVertexArray: () => {
 				this.assertWebGL2();
-				let query = this.ctx.createQuery();
-				let id = this.getNewId(this.queries);
-				query.name = id;
-				this.queries[id] = query;
+				let vao = this.ctx.createVertexArray();
+				let id = this.getNewId(this.vaos);
+				vao.name = id;
+				this.vaos[id] = vao;
 				return id;
 			},
-			DeleteQuery: (id) => {
+			DeleteVertexArray: (id) => {
 				this.assertWebGL2();
-				let obj = this.querys[id];
+				let obj = this.vaos[id];
 				if (obj && id != 0) {
-					this.ctx.deleteQuery(obj);
-					this.querys[id] = null;
+					this.ctx.deleteVertexArray(obj);
+					this.vaos[id] = null;
 				}
 			},
-			IsQuery: (query) => {
+			IsVertexArray: (vertexArray) => {
 				this.assertWebGL2();
-				return this.ctx.isQuery(this.queries[query]);
+				return this.ctx.isVertexArray(this.vaos[vertexArray]);
 			},
-			BeginQuery: (target, query) => {
+			BindVertexArray: (vertexArray) => {
 				this.assertWebGL2();
-				this.ctx.beginQuery(target, this.queries[query])
+				this.ctx.bindVertexArray(this.vaos[vertexArray]);
 			},
-			EndQuery: (target) => {
-				this.assertWebGL2();
-				this.ctx.endQuery(target);
+		};
+	}
+};
+
+
+
+function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) {
+	const MAX_INFO_CONSOLE_LINES = 512;
+	let infoConsoleLines = new Array();
+	const addConsoleLine = (line) => {
+		if (!line) {
+			return;
+		}
+		if (line.endsWith("\n")) {
+			line = line.substring(0, line.length-1);
+		} else if (infoConsoleLines.length > 0) {
+			let prev_line = infoConsoleLines.pop();
+			line = prev_line.concat(line);
+		}
+		infoConsoleLines.push(line);
+
+		if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) {
+			infoConsoleLines.shift();
+		}
+
+		let data = "";
+		for (let i = 0; i < infoConsoleLines.length; i++) {
+			if (i != 0) {
+				data = data.concat("\n");
+			}
+			data = data.concat(infoConsoleLines[i]);
+		}
+
+		if (consoleElement) {
+			let info = consoleElement;
+			info.innerHTML = data;
+			info.scrollTop = info.scrollHeight;
+		}
+	};
+
+	let event_temp_data = {};
+
+	let webglContext = new WebGLInterface(wasmMemoryInterface);
+	return {
+		"env": {},
+		"odin_env": {
+			write: (fd, ptr, len) => {
+				const str = wasmMemoryInterface.loadString(ptr, len);
+				if (fd == 1) {
+					addConsoleLine(str);
+					return;
+				} else if (fd == 2) {
+					addConsoleLine(str);
+					return;
+				} else {
+					throw new Error("Invalid fd to 'write'" + stripNewline(str));
+				}
+			},
+			trap: () => { throw new Error() },
+			alert: (ptr, len) => { alert(wasmMemoryInterface.loadString(ptr, len)) },
+			abort: () => { Module.abort() },
+			evaluate: (str_ptr, str_len) => { eval.call(null, wasmMemoryInterface.loadString(str_ptr, str_len)); },
+
+			time_now: () => {
+				return performance.now() * 1e6;
+			},
+
+			sqrt:    (x) => Math.sqrt(x),
+			sin:     (x) => Math.sin(x),
+			cos:     (x) => Math.cos(x),
+			pow:     (x) => Math.pow(x),
+			fmuladd: (x, y, z) => x*y + z,
+			ln:      (x) => Math.log(x),
+			exp:     (x) => Math.exp(x),
+			ldexp:   (x) => Math.ldexp(x),
+		},
+		"odin_dom": {
+			init_event_raw: (ep) => {
+				const W = 4;
+				let offset = ep;
+				let off = (amount, alignment) => {
+					if (alignment === undefined) {
+						alignment = Math.min(amount, W);
+					}
+					if (offset % alignment != 0) {
+						offset += alignment - (offset%alignment);
+					}
+					let x = offset;
+					offset += amount;
+					return x;
+				};
+
+				let wmi = wasmMemoryInterface;
+
+				let e = event_temp_data.event;
+
+				wmi.storeU32(off(4), event_temp_data.name_code);
+				if (e.target == document) {
+					wmi.storeU32(off(4), 1);
+				} else if (e.target == window) {
+					wmi.storeU32(off(4), 2);
+				} else {
+					wmi.storeU32(off(4), 0);
+				}
+				if (e.currentTarget == document) {
+					wmi.storeU32(off(4), 1);
+				} else if (e.currentTarget == window) {
+					wmi.storeU32(off(4), 2);
+				} else {
+					wmi.storeU32(off(4), 0);
+				}
+
+				wmi.storeUint(off(W), event_temp_data.id_ptr);
+				wmi.storeUint(off(W), event_temp_data.id_len);
+
+				wmi.storeF64(off(8), e.timeStamp*1e-3);
+
+				wmi.storeU8(off(1), e.eventPhase);
+				wmi.storeU8(off(1), !!e.bubbles);
+				wmi.storeU8(off(1), !!e.cancelable);
+				wmi.storeU8(off(1), !!e.composed);
+				wmi.storeU8(off(1), !!e.isComposing);
+				wmi.storeU8(off(1), !!e.isTrusted);
+
+				let base = off(0, 8);
+				if (e instanceof MouseEvent) {
+					wmi.storeI64(off(8), e.screenX);
+					wmi.storeI64(off(8), e.screenY);
+					wmi.storeI64(off(8), e.clientX);
+					wmi.storeI64(off(8), e.clientY);
+					wmi.storeI64(off(8), e.offsetX);
+					wmi.storeI64(off(8), e.offsetY);
+					wmi.storeI64(off(8), e.pageX);
+					wmi.storeI64(off(8), e.pageY);
+					wmi.storeI64(off(8), e.movementX);
+					wmi.storeI64(off(8), e.movementY);
+
+					wmi.storeU8(off(1), !!e.ctrlKey);
+					wmi.storeU8(off(1), !!e.shiftKey);
+					wmi.storeU8(off(1), !!e.altKey);
+					wmi.storeU8(off(1), !!e.metaKey);
+
+					wmi.storeI16(off(2), e.button);
+					wmi.storeU16(off(2), e.buttons);
+				} else if (e instanceof KeyboardEvent) {
+					let keyOffset = off(W*2, W);
+					let codeOffet = off(W*2, W);
+					wmi.storeU8(off(1), e.location);
+
+					wmi.storeU8(off(1), !!e.ctrlKey);
+					wmi.storeU8(off(1), !!e.shiftKey);
+					wmi.storeU8(off(1), !!e.altKey);
+					wmi.storeU8(off(1), !!e.metaKey);
+
+					wmi.storeU8(off(1), !!e.repeat);
+				} else if (e instanceof WheelEvent) {
+					wmi.storeF64(off(8), e.deltaX);
+					wmi.storeF64(off(8), e.deltaY);
+					wmi.storeF64(off(8), e.deltaZ);
+					wmi.storeU32(off(4), e.deltaMode);
+				} else if (e instanceof Event) {
+					if ('scrollX' in e) {
+						wmi.storeF64(off(8), e.scrollX);
+						wmi.storeF64(off(8), e.scrollY);
+					}
+				}
+			},
+
+			add_event_listener: (id_ptr, id_len, name_ptr, name_len, name_code, data, callback, use_capture) => {
+				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+				let name = wasmMemoryInterface.loadString(name_ptr, name_len);
+				let element = document.getElementById(id);
+				if (element == undefined) {
+					return false;
+				}
+
+				let listener = (e) => {
+					const odin_ctx = wasmMemoryInterface.exports.default_context_ptr();
+					event_temp_data.id_ptr = id_ptr;
+					event_temp_data.id_len = id_len;
+					event_temp_data.event = e;
+					event_temp_data.name_code = name_code;
+					// console.log(e);
+					wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx);
+				};
+				wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener;
+				element.addEventListener(name, listener, !!use_capture);
+				return true;
 			},
-			GetQuery: (target, pname) => {
-				this.assertWebGL2();
-				let query = this.ctx.getQuery(target, pname);
-				if (!query) {
-					return 0;
+
+			remove_event_listener: (id_ptr, id_len, name_ptr, name_len, data, callback) => {
+				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+				let name = wasmMemoryInterface.loadString(name_ptr, name_len);
+				let element = document.getElementById(id);
+				if (element == undefined) {
+					return false;
 				}
-				if (this.queries.indexOf(query) !== -1) {
-					return query.name;
+
+				let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}];
+				if (listener == undefined) {
+					return false;
 				}
-				let id = this.getNewId(this.queries);
-				query.name = id;
-				this.queries[id] = query;
-				return id;
+				element.removeEventListener(name, listener);
+				return true;
 			},
 
-			/* Sampler Objects */
-			CreateSampler: () => {
-				this.assertWebGL2();
-				let sampler = this.ctx.createSampler();
-				let id = this.getNewId(this.samplers);
-				sampler.name = id;
-				this.samplers[id] = sampler;
-				return id;
+
+			add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => {
+				let name = wasmMemoryInterface.loadString(name_ptr, name_len);
+				let element = window;
+				let listener = (e) => {
+					const odin_ctx = wasmMemoryInterface.exports.default_context_ptr();
+					event_temp_data.id_ptr = 0;
+					event_temp_data.id_len = 0;
+					event_temp_data.event = e;
+					event_temp_data.name_code = name_code;
+					// console.log(e);
+					wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx);
+				};
+				wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener;
+				element.addEventListener(name, listener, !!use_capture);
+				return true;
 			},
-			DeleteSampler: (id) => {
-				this.assertWebGL2();
-				let obj = this.samplers[id];
-				if (obj && id != 0) {
-					this.ctx.deleteSampler(obj);
-					this.samplers[id] = null;
+
+			remove_window_event_listener: (name_ptr, name_len, data, callback) => {
+				let name = wasmMemoryInterface.loadString(name_ptr, name_len);
+				let element = window;
+				let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}];
+				if (listener == undefined) {
+					return false;
 				}
-			},
-			IsSampler: (sampler) => {
-				this.assertWebGL2();
-				return this.ctx.isSampler(this.samplers[sampler]);
-			},
-			BindSampler: (unit, sampler) => {
-				this.assertWebGL2();
-				this.ctx.bindSampler(unit, this.samplers[Sampler]);
-			},
-			SamplerParameteri: (sampler, pname, param) => {
-				this.assertWebGL2();
-				this.ctx.samplerParameteri(this.samplers[sampler], pname, param);
-			},
-			SamplerParameterf: (sampler, pname, param) => {
-				this.assertWebGL2();
-				this.ctx.samplerParameterf(this.samplers[sampler], pname, param);
+				element.removeEventListener(name, listener);
+				return true;
 			},
 
-			/* Sync objects */
-			FenceSync: (condition, flags) => {
-				this.assertWebGL2();
-				let sync = this.ctx.fenceSync(condition, flags);
-				let id = this.getNewId(this.syncs);
-				sync.name = id;
-				this.syncs[id] = sync;
-				return id;
-			},
-			IsSync: (sync) => {
-				this.assertWebGL2();
-				return this.ctx.isSync(this.syncs[sync]);
-			},
-			DeleteSync: (id) => {
-				this.assertWebGL2();
-				let obj = this.syncs[id];
-				if (obj && id != 0) {
-					this.ctx.deleteSampler(obj);
-					this.syncs[id] = null;
+			event_stop_propagation: () => {
+				if (event_temp_data && event_temp_data.event) {
+					event_temp_data.event.eventStopPropagation();
 				}
 			},
-			ClientWaitSync: (sync, flags, timeout) => {
-				this.assertWebGL2();
-				return this.ctx.clientWaitSync(this.syncs[sync], flags, timeout);
+			event_stop_immediate_propagation: () => {
+				if (event_temp_data && event_temp_data.event) {
+					event_temp_data.event.eventStopImmediatePropagation();
+				}
 			},
-			WaitSync: (sync, flags, timeout) => {
-				this.assertWebGL2();
-				this.ctx.waitSync(this.syncs[sync], flags, timeout)	;
+			event_prevent_default: () => {
+				if (event_temp_data && event_temp_data.event) {
+					event_temp_data.event.eventPreventDefault();
+				}
 			},
 
-
-			/* Transform Feedback */
-			CreateTransformFeedback: () => {
-				this.assertWebGL2();
-				let transformFeedback = this.ctx.createtransformFeedback();
-				let id = this.getNewId(this.transformFeedbacks);
-				transformFeedback.name = id;
-				this.transformFeedbacks[id] = transformFeedback;
-				return id;
+			get_element_value_f64: (id_ptr, id_len) => {
+				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+				let element = document.getElementById(id);
+				return element ? element.value : 0;
 			},
-			DeleteTransformFeedback: (id)  => {
-				this.assertWebGL2();
-				let obj = this.transformFeedbacks[id];
-				if (obj && id != 0) {
-					this.ctx.deleteTransformFeedback(obj);
-					this.transformFeedbacks[id] = null;
+			get_element_value_string: (id_ptr, id_len, buf_ptr, buf_len) => {
+				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+				let element = document.getElementById(id);
+				if (element) {
+					let str = element.value;
+					if (buf_len > 0 && buf_ptr) {
+						let n = Math.min(buf_len, str.length);
+						str = str.substring(0, n);
+						this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(str))
+						return n;
+					}
 				}
+				return 0;
 			},
-			IsTransformFeedback: (tf) => {
-				this.assertWebGL2();
-				return this.ctx.isTransformFeedback(this.transformFeedbacks[tf]);
-			},
-			BindTransformFeedback: (target, tf) => {
-				this.assertWebGL2();
-				this.ctx.bindTransformFeedback(target, this.transformFeedbacks[tf]);
-			},
-			BeginTransformFeedback: (primitiveMode) => {
-				this.assertWebGL2();
-				this.ctx.beginTransformFeedback(primitiveMode);
-			},
-			EndTransformFeedback: () => {
-				this.assertWebGL2();
-				this.ctx.endTransformFeedback();
-			},
-			TransformFeedbackVaryings: (program, varyings_ptr, varyings_len, bufferMode) => {
-				this.assertWebGL2();
-				let varyings = [];
-				for (let i = 0; i < varyings_len; i++) {
-					let ptr = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 0*4);
-					let len = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 1*4);
-					varyings.push(this.mem.loadString(ptr, len));
+			get_element_min_max: (ptr_array2_f64, id_ptr, id_len) => {
+				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+				let element = document.getElementById(id);
+				if (element) {
+					let values = wasmMemoryInterface.loadF64Array(ptr_array2_f64, 2);
+					values[0] = element.min;
+					values[1] = element.max;
 				}
-				this.ctx.transformFeedbackVaryings(this.programs[program], varyings, bufferMode);
-			},
-			PauseTransformFeedback: () => {
-				this.assertWebGL2();
-				this.ctx.pauseTransformFeedback();
 			},
-			ResumeTransformFeedback: () => {
-				this.assertWebGL2();
-				this.ctx.resumeTransformFeedback();
+			set_element_value: (id_ptr, id_len, value) => {
+				let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+				let element = document.getElementById(id);
+				if (element) {
+					element.value = value;
+				}
 			},
+		},
+
+		"webgl": webglContext.getWebGL1Interface(),
+		"webgl2": webglContext.getWebGL2Interface(),
+	};
+};
 
+async function runWasmCanvas(wasmPath, consoleElement, extraForeignImports) {
+	let wasmMemoryInterface = new WasmMemoryInterface();
 
-			/* Uniform Buffer Objects and Transform Feedback Buffers */
-			BindBufferBase: (target, index, buffer) => {
-				this.assertWebGL2();
-				this.ctx.bindBufferBase(target, index, this.buffers[buffer]);
-			},
-			BindBufferRange: (target, index, buffer, offset, size) => {
-				this.assertWebGL2();
-				this.ctx.bindBufferRange(target, index, this.buffers[buffer], offset, size);
-			},
-			GetUniformBlockIndex: (program, uniformBlockName_ptr, uniformBlockName_len) => {
-				this.assertWebGL2();
-				return this.ctx.getUniformBlockIndex(this.programs[program], this.mem.loadString(uniformBlockName_ptr, uniformBlockName_len));
-			},
-			// any getActiveUniformBlockParameter(WebGLProgram program, GLuint uniformBlockIndex, GLenum pname);
-			GetActiveUniformBlockName: (program, uniformBlockIndex, buf_ptr, buf_len, length_ptr) => {
-				this.assertWebGL2();
-				let name = this.ctx.getActiveUniformBlockName(this.programs[program], uniformBlockIndex);
+	let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement);
+	let exports = {};
 
-				let n = Math.min(buf_len, name.length);
-				name = name.substring(0, n);
-				this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(name))
-				this.mem.storeInt(length_ptr, n);
-			},
-			UniformBlockBinding: (program, uniformBlockIndex, uniformBlockBinding) => {
-				this.assertWebGL2();
-				this.ctx.uniformBlockBinding(this.programs[program], uniformBlockIndex, uniformBlockBinding);
-			},
+	if (extraForeignImports !== undefined) {
+		imports = {
+			...imports,
+			...extraForeignImports,
+		};
+	}
 
-			/* Vertex Array Objects */
-			CreateVertexArray: () => {
-				this.assertWebGL2();
-				let vao = this.ctx.createVertexArray();
-				let id = this.getNewId(this.vaos);
-				vao.name = id;
-				this.vaos[id] = vao;
-				return id;
-			},
-			DeleteVertexArray: (id) => {
-				this.assertWebGL2();
-				let obj = this.vaos[id];
-				if (obj && id != 0) {
-					this.ctx.deleteVertexArray(obj);
-					this.vaos[id] = null;
-				}
-			},
-			IsVertexArray: (vertexArray) => {
-				this.assertWebGL2();
-				return this.ctx.isVertexArray(this.vaos[vertexArray]);
-			},
-			BindVertexArray: (vertexArray) => {
-				this.assertWebGL2();
-				this.ctx.bindVertexArray(this.vaos[vertexArray]);
-			},
+	const response = await fetch(wasmPath);
+	const file = await response.arrayBuffer();
+	const wasm = await WebAssembly.instantiate(file, imports);
+	exports = wasm.instance.exports;
+	wasmMemoryInterface.setExports(exports);
+	wasmMemoryInterface.setMemory(exports.memory);
+
+	exports._start();
+
+	if (exports.step) {
+		const odin_ctx = exports.default_context_ptr();
+
+		let prevTimeStamp = undefined;
+		const step = (currTimeStamp) => {
+			if (prevTimeStamp == undefined) {
+				prevTimeStamp = currTimeStamp;
+			}
+
+			const dt = (currTimeStamp - prevTimeStamp)*0.001;
+			prevTimeStamp = currTimeStamp;
+			exports.step(dt, odin_ctx);
+			window.requestAnimationFrame(step);
 		};
+
+		window.requestAnimationFrame(step);
 	}
+
+	exports._end();
+
+	return;
 };
 
+window.odin = {
+	// Interface Types
+	WasmMemoryInterface: WasmMemoryInterface,
+	WebGLInterface:      WebGLInterface,
 
-export {WasmMemoryInterface, odinSetupDefaultImports, WebGLInterface};
+	// Functions
+	setupDefaultImports: odinSetupDefaultImports,
+	runWasmCanvas:       runWasmCanvas,
+};
+})();