aboutsummaryrefslogtreecommitdiff
path: root/core/sys/wasm
diff options
context:
space:
mode:
authorgingerBill <bill@gingerbill.org>2024-09-22 13:13:34 +0100
committergingerBill <bill@gingerbill.org>2024-09-22 13:13:34 +0100
commit634fa7aa306c076a7a63047b0e448e01df156919 (patch)
tree2d6608030b1bd867b909e723ce81848b76bd44d1 /core/sys/wasm
parent096258b5d5978a05d151fbcc1a43b600fff0abd8 (diff)
Move `vendor:wasm/js` to `core:sys/wasm/js`
Diffstat (limited to 'core/sys/wasm')
-rw-r--r--core/sys/wasm/README.md15
-rw-r--r--core/sys/wasm/js/dom.odin93
-rw-r--r--core/sys/wasm/js/dom_all_targets.odin36
-rw-r--r--core/sys/wasm/js/events.odin362
-rw-r--r--core/sys/wasm/js/events_all_targets.odin288
-rw-r--r--core/sys/wasm/js/general.odin12
-rw-r--r--core/sys/wasm/js/memory_all_targets.odin14
-rw-r--r--core/sys/wasm/js/memory_js.odin44
-rw-r--r--core/sys/wasm/js/odin.js1879
9 files changed, 2743 insertions, 0 deletions
diff --git a/core/sys/wasm/README.md b/core/sys/wasm/README.md
new file mode 100644
index 000000000..1aaeaa429
--- /dev/null
+++ b/core/sys/wasm/README.md
@@ -0,0 +1,15 @@
+# WASM on the Web
+
+This directory is for use when targeting the `js_wasm32` target and the packages that rely on it.
+
+The `js_wasm32` target assumes that the WASM output will be ran within a web browser rather than a standalone VM. In the VM cases, either `wasi_wasm32` or `freestanding_wasm32` should be used accordingly.
+
+## Example for `js_wasm32`
+
+```html
+<!-- Copy `core:sys/wasm/js/odin.js` into your web server -->
+<script type="text/javascript" src="odin.js"></script>
+<script type="text/javascript">
+ odin.runWasm(pathToWasm, consolePreElement);
+</script>
+```
diff --git a/core/sys/wasm/js/dom.odin b/core/sys/wasm/js/dom.odin
new file mode 100644
index 000000000..ffc58a9a3
--- /dev/null
+++ b/core/sys/wasm/js/dom.odin
@@ -0,0 +1,93 @@
+#+build js wasm32, js wasm64p32
+package wasm_js_interface
+
+foreign import dom_lib "odin_dom"
+
+@(default_calling_convention="contextless")
+foreign dom_lib {
+ get_element_value_f64 :: proc(id: string) -> f64 ---
+ set_element_value_f64 :: proc(id: string, value: f64) ---
+
+ get_element_key_f64 :: proc(id: string, key: string) -> f64 ---
+ set_element_key_f64 :: proc(id: string, key: string, value: f64) ---
+
+ set_element_value_string :: proc(id: string, value: string) ---
+ get_element_value_string_length :: proc(id: string) -> int ---
+
+ set_element_key_string :: proc(id: string, key: string, value: string) ---
+ get_element_key_string_length :: proc(id: string, key: string, ) -> int ---
+
+ device_pixel_ratio :: proc() -> f64 ---
+
+ window_set_scroll :: proc(x, y: f64) ---
+}
+
+get_element_value_string :: proc "contextless" (id: string, buf: []byte) -> string {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="get_element_value_string")
+ _get_element_value_string :: proc(id: string, buf: []byte) -> int ---
+ }
+ n := _get_element_value_string(id, buf)
+ return string(buf[:n])
+}
+
+get_element_key_string :: proc "contextless" (id: string, key: string, buf: []byte) -> string {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="get_element_key_string")
+ _get_element_key_string :: proc(id: string, key: string, buf: []byte) -> int ---
+ }
+ n := _get_element_key_string(id, key, buf)
+ return string(buf[:n])
+
+}
+
+
+
+get_element_min_max :: proc "contextless" (id: string) -> (min, max: f64) {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="get_element_min_max")
+ _get_element_min_max :: proc(min_max: ^[2]f64, id: string) ---
+ }
+ min_max: [2]f64
+ _get_element_min_max(&min_max, id)
+ return min_max[0], min_max[1]
+}
+
+
+Rect :: struct {
+ x, y, width, height: f64,
+}
+
+get_bounding_client_rect :: proc "contextless" (id: string) -> (rect: Rect) {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="get_bounding_client_rect")
+ _get_bounding_client_rect :: proc(rect: ^Rect, id: string) ---
+ }
+ _get_bounding_client_rect(&rect, id)
+ return
+}
+
+window_get_rect :: proc "contextless" () -> (rect: Rect) {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="window_get_rect")
+ _window_get_rect :: proc(rect: ^Rect) ---
+ }
+ _window_get_rect(&rect)
+ return
+}
+
+window_get_scroll :: proc "contextless" () -> (x, y: f64) {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="window_get_scroll")
+ _window_get_scroll :: proc(scroll: ^[2]f64) ---
+ }
+ scroll: [2]f64
+ _window_get_scroll(&scroll)
+ return scroll.x, scroll.y
+} \ No newline at end of file
diff --git a/core/sys/wasm/js/dom_all_targets.odin b/core/sys/wasm/js/dom_all_targets.odin
new file mode 100644
index 000000000..171deed2f
--- /dev/null
+++ b/core/sys/wasm/js/dom_all_targets.odin
@@ -0,0 +1,36 @@
+#+build !js
+package wasm_js_interface
+
+import "base:runtime"
+
+
+get_element_value_string :: proc "contextless" (id: string, buf: []byte) -> string {
+ context = runtime.default_context()
+ panic("vendor:wasm/js not supported on non JS targets")
+}
+
+
+get_element_min_max :: proc "contextless" (id: string) -> (min, max: f64) {
+ context = runtime.default_context()
+ panic("vendor:wasm/js not supported on non JS targets")
+}
+
+
+Rect :: struct {
+ x, y, width, height: f64,
+}
+
+get_bounding_client_rect :: proc "contextless" (id: string) -> (rect: Rect) {
+ context = runtime.default_context()
+ panic("vendor:wasm/js not supported on non JS targets")
+}
+
+window_get_rect :: proc "contextless" () -> (rect: Rect) {
+ context = runtime.default_context()
+ panic("vendor:wasm/js not supported on non JS targets")
+}
+
+window_get_scroll :: proc "contextless" () -> (x, y: f64) {
+ context = runtime.default_context()
+ panic("vendor:wasm/js not supported on non JS targets")
+}
diff --git a/core/sys/wasm/js/events.odin b/core/sys/wasm/js/events.odin
new file mode 100644
index 000000000..5a63da8ef
--- /dev/null
+++ b/core/sys/wasm/js/events.odin
@@ -0,0 +1,362 @@
+#+build js wasm32, js wasm64p32
+package wasm_js_interface
+
+foreign import dom_lib "odin_dom"
+
+Event_Kind :: enum u32 {
+ Invalid,
+
+ Load,
+ Unload,
+ Error,
+ Resize,
+ Visibility_Change,
+ Fullscreen_Change,
+ Fullscreen_Error,
+
+ Click,
+ Double_Click,
+ Mouse_Move,
+ Mouse_Over,
+ Mouse_Out,
+ Mouse_Up,
+ Mouse_Down,
+
+ Key_Up,
+ Key_Down,
+ Key_Press,
+
+ Scroll,
+ Wheel,
+
+ Focus,
+ Focus_In,
+ Focus_Out,
+ Submit,
+ Blur,
+ Change,
+ HashChange,
+ Select,
+
+ Animation_Start,
+ Animation_End,
+ Animation_Iteration,
+ Animation_Cancel,
+
+ Copy,
+ Cut,
+ Paste,
+
+ // Drag,
+ // Drag_Start,
+ // Drag_End,
+ // Drag_Enter,
+ // Drag_Leave,
+ // Drag_Over,
+ // Drop,
+
+ Pointer_Cancel,
+ Pointer_Down,
+ Pointer_Enter,
+ Pointer_Leave,
+ Pointer_Move,
+ Pointer_Over,
+ Pointer_Up,
+ Got_Pointer_Capture,
+ Lost_Pointer_Capture,
+ Pointer_Lock_Change,
+ Pointer_Lock_Error,
+
+ Selection_Change,
+ Selection_Start,
+
+ Touch_Cancel,
+ Touch_End,
+ Touch_Move,
+ Touch_Start,
+
+ Transition_Start,
+ Transition_End,
+ Transition_Run,
+ Transition_Cancel,
+
+ Context_Menu,
+
+ Custom,
+
+}
+event_kind_string := [Event_Kind]string{
+ .Invalid = "",
+
+ .Load = "load",
+ .Unload = "unload",
+ .Error = "error",
+ .Resize = "resize",
+ .Visibility_Change = "visibilitychange",
+ .Fullscreen_Change = "fullscreenchange",
+ .Fullscreen_Error = "fullscreenerror",
+
+ .Click = "click",
+ .Double_Click = "dblclick",
+ .Mouse_Move = "mousemove",
+ .Mouse_Over = "mouseover",
+ .Mouse_Out = "mouseout",
+ .Mouse_Up = "mouseup",
+ .Mouse_Down = "mousedown",
+
+ .Key_Up = "keyup",
+ .Key_Down = "keydown",
+ .Key_Press = "keypress",
+
+ .Scroll = "scroll",
+ .Wheel = "wheel",
+
+ .Focus = "focus",
+ .Focus_In = "focusin",
+ .Focus_Out = "focusout",
+ .Submit = "submit",
+ .Blur = "blur",
+ .Change = "change",
+ .HashChange = "hashchange",
+ .Select = "select",
+
+ .Animation_Start = "animationstart",
+ .Animation_End = "animationend",
+ .Animation_Iteration = "animationiteration",
+ .Animation_Cancel = "animationcancel",
+
+ .Copy = "copy",
+ .Cut = "cut",
+ .Paste = "paste",
+
+ // .Drag, = "drag",
+ // .Drag_Start, = "dragstart",
+ // .Drag_End, = "dragend",
+ // .Drag_Enter, = "dragenter",
+ // .Drag_Leave, = "dragleave",
+ // .Drag_Over, = "dragover",
+ // .Drop, = "drop",
+
+ .Pointer_Cancel = "pointercancel",
+ .Pointer_Down = "pointerdown",
+ .Pointer_Enter = "pointerenter",
+ .Pointer_Leave = "pointerleave",
+ .Pointer_Move = "pointermove",
+ .Pointer_Over = "pointerover",
+ .Pointer_Up = "pointerup",
+ .Got_Pointer_Capture = "gotpointercapture",
+ .Lost_Pointer_Capture = "lostpointercapture",
+ .Pointer_Lock_Change = "pointerlockchange",
+ .Pointer_Lock_Error = "pointerlockerror",
+
+ .Selection_Change = "selectionchange",
+ .Selection_Start = "selectionstart",
+
+ .Transition_Start = "transitionstart",
+ .Transition_End = "transitionend",
+ .Transition_Run = "transitionrun",
+ .Transition_Cancel = "transitioncancel",
+
+ .Touch_Cancel = "touchcancel",
+ .Touch_End = "touchend",
+ .Touch_Move = "touchmove",
+ .Touch_Start = "touchstart",
+
+ .Context_Menu = "contextmenu",
+
+ .Custom = "?custom?",
+}
+
+Delta_Mode :: enum u32 {
+ Pixel = 0,
+ Line = 1,
+ Page = 2,
+}
+
+Key_Location :: enum u8 {
+ Standard = 0,
+ Left = 1,
+ Right = 2,
+ Numpad = 3,
+}
+
+KEYBOARD_MAX_KEY_SIZE :: 16
+KEYBOARD_MAX_CODE_SIZE :: 16
+
+Event_Target_Kind :: enum u32 {
+ Element = 0,
+ Document = 1,
+ Window = 2,
+}
+
+Event_Phase :: enum u8 {
+ None = 0,
+ Capturing_Phase = 1,
+ At_Target = 2,
+ Bubbling_Phase = 3,
+}
+
+Event_Option :: enum u8 {
+ Bubbles = 0,
+ Cancelable = 1,
+ Composed = 2,
+}
+Event_Options :: distinct bit_set[Event_Option; u8]
+
+Event :: struct {
+ kind: Event_Kind,
+ target_kind: Event_Target_Kind,
+ current_target_kind: Event_Target_Kind,
+ id: string,
+ timestamp: f64,
+
+ phase: Event_Phase,
+ options: Event_Options,
+ is_composing: bool,
+ is_trusted: bool,
+
+ using data: struct #raw_union #align(8) {
+ scroll: struct {
+ delta: [2]f64,
+ },
+ visibility_change: struct {
+ is_visible: bool,
+ },
+ wheel: struct {
+ delta: [3]f64,
+ delta_mode: Delta_Mode,
+ },
+
+ key: struct {
+ key: string,
+ code: string,
+ location: Key_Location,
+
+ ctrl: bool,
+ shift: bool,
+ alt: bool,
+ meta: bool,
+
+ repeat: bool,
+
+ _key_len: int `fmt:"-"`,
+ _code_len: int `fmt:"-"`,
+ _key_buf: [KEYBOARD_MAX_KEY_SIZE]byte `fmt:"-"`,
+ _code_buf: [KEYBOARD_MAX_KEY_SIZE]byte `fmt:"-"`,
+ },
+
+ mouse: struct {
+ screen: [2]i64,
+ client: [2]i64,
+ offset: [2]i64,
+ page: [2]i64,
+ movement: [2]i64,
+
+ ctrl: bool,
+ shift: bool,
+ alt: bool,
+ meta: bool,
+
+ button: i16,
+ buttons: bit_set[0..<16; u16],
+ },
+ },
+
+
+ user_data: rawptr,
+ callback: proc(e: Event),
+}
+
+@(default_calling_convention="contextless")
+foreign dom_lib {
+ event_stop_propagation :: proc() ---
+ event_stop_immediate_propagation :: proc() ---
+ event_prevent_default :: proc() ---
+ dispatch_custom_event :: proc(id: string, name: string, options := Event_Options{}) -> bool ---
+}
+
+add_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="add_event_listener")
+ _add_event_listener :: proc(id: string, name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool ---
+ }
+ // TODO: Pointer_Lock_Change etc related stuff for all different browsers
+ return _add_event_listener(id, event_kind_string[kind], kind, user_data, callback, use_capture)
+}
+
+remove_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="remove_event_listener")
+ _remove_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc "odin" (Event)) -> bool ---
+ }
+ return _remove_event_listener(id, event_kind_string[kind], user_data, callback)
+}
+
+add_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="add_window_event_listener")
+ _add_window_event_listener :: proc(name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool ---
+ }
+ return _add_window_event_listener(event_kind_string[kind], kind, user_data, callback, use_capture)
+}
+
+remove_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="remove_window_event_listener")
+ _remove_window_event_listener :: proc(name: string, user_data: rawptr, callback: proc "odin" (Event)) -> bool ---
+ }
+ return _remove_window_event_listener(event_kind_string[kind], user_data, callback)
+}
+
+remove_event_listener_from_event :: proc(e: Event) -> bool {
+ if e.id == "" {
+ return remove_window_event_listener(e.kind, e.user_data, e.callback)
+ }
+ return remove_event_listener(e.id, e.kind, e.user_data, e.callback)
+}
+
+add_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="add_event_listener")
+ _add_event_listener :: proc(id: string, name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool ---
+ }
+ return _add_event_listener(id, name, .Custom, user_data, callback, use_capture)
+}
+remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event)) -> bool {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ @(link_name="remove_event_listener")
+ _remove_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc "odin" (Event)) -> bool ---
+ }
+ return _remove_event_listener(id, name, user_data, callback)
+}
+
+@(export, link_name="odin_dom_do_event_callback")
+do_event_callback :: proc(user_data: rawptr, callback: proc(e: Event)) {
+ @(default_calling_convention="contextless")
+ foreign dom_lib {
+ init_event_raw :: proc(e: ^Event) ---
+ }
+
+ if callback != nil {
+ event := Event{
+ user_data = user_data,
+ callback = callback,
+ }
+
+
+ init_event_raw(&event)
+
+ if event.kind == .Key_Up || event.kind == .Key_Down || event.kind == .Key_Press {
+ event.key.key = string(event.key._key_buf[:event.key._key_len])
+ event.key.code = string(event.key._code_buf[:event.key._code_len])
+ }
+
+ callback(event)
+ }
+} \ No newline at end of file
diff --git a/core/sys/wasm/js/events_all_targets.odin b/core/sys/wasm/js/events_all_targets.odin
new file mode 100644
index 000000000..ccf39015d
--- /dev/null
+++ b/core/sys/wasm/js/events_all_targets.odin
@@ -0,0 +1,288 @@
+#+build !js
+package wasm_js_interface
+
+
+Event_Kind :: enum u32 {
+ Invalid,
+
+ Load,
+ Unload,
+ Error,
+ Resize,
+ Visibility_Change,
+ Fullscreen_Change,
+ Fullscreen_Error,
+
+ Click,
+ Double_Click,
+ Mouse_Move,
+ Mouse_Over,
+ Mouse_Out,
+ Mouse_Up,
+ Mouse_Down,
+
+ Key_Up,
+ Key_Down,
+ Key_Press,
+
+ Scroll,
+ Wheel,
+
+ Focus,
+ Submit,
+ Blur,
+ Change,
+ Select,
+
+ Animation_Start,
+ Animation_End,
+ Animation_Iteration,
+ Animation_Cancel,
+
+ Copy,
+ Cut,
+ Paste,
+
+ // Drag,
+ // Drag_Start,
+ // Drag_End,
+ // Drag_Enter,
+ // Drag_Leave,
+ // Drag_Over,
+ // Drop,
+
+ Pointer_Cancel,
+ Pointer_Down,
+ Pointer_Enter,
+ Pointer_Leave,
+ Pointer_Move,
+ Pointer_Over,
+ Pointer_Up,
+ Got_Pointer_Capture,
+ Lost_Pointer_Capture,
+ Pointer_Lock_Change,
+ Pointer_Lock_Error,
+
+ Selection_Change,
+ Selection_Start,
+
+ Touch_Cancel,
+ Touch_End,
+ Touch_Move,
+ Touch_Start,
+
+ Transition_Start,
+ Transition_End,
+ Transition_Run,
+ Transition_Cancel,
+
+ Context_Menu,
+
+ Custom,
+
+}
+event_kind_string := [Event_Kind]string{
+ .Invalid = "",
+
+ .Load = "load",
+ .Unload = "unload",
+ .Error = "error",
+ .Resize = "resize",
+ .Visibility_Change = "visibilitychange",
+ .Fullscreen_Change = "fullscreenchange",
+ .Fullscreen_Error = "fullscreenerror",
+
+ .Click = "click",
+ .Double_Click = "dblclick",
+ .Mouse_Move = "mousemove",
+ .Mouse_Over = "mouseover",
+ .Mouse_Out = "mouseout",
+ .Mouse_Up = "mouseup",
+ .Mouse_Down = "mousedown",
+
+ .Key_Up = "keyup",
+ .Key_Down = "keydown",
+ .Key_Press = "keypress",
+
+ .Scroll = "scroll",
+ .Wheel = "wheel",
+
+ .Focus = "focus",
+ .Submit = "submit",
+ .Blur = "blur",
+ .Change = "change",
+ .Select = "select",
+
+ .Animation_Start = "animationstart",
+ .Animation_End = "animationend",
+ .Animation_Iteration = "animationiteration",
+ .Animation_Cancel = "animationcancel",
+
+ .Copy = "copy",
+ .Cut = "cut",
+ .Paste = "paste",
+
+ // .Drag, = "drag",
+ // .Drag_Start, = "dragstart",
+ // .Drag_End, = "dragend",
+ // .Drag_Enter, = "dragenter",
+ // .Drag_Leave, = "dragleave",
+ // .Drag_Over, = "dragover",
+ // .Drop, = "drop",
+
+ .Pointer_Cancel = "pointercancel",
+ .Pointer_Down = "pointerdown",
+ .Pointer_Enter = "pointerenter",
+ .Pointer_Leave = "pointerleave",
+ .Pointer_Move = "pointermove",
+ .Pointer_Over = "pointerover",
+ .Pointer_Up = "pointerup",
+ .Got_Pointer_Capture = "gotpointercapture",
+ .Lost_Pointer_Capture = "lostpointercapture",
+ .Pointer_Lock_Change = "pointerlockchange",
+ .Pointer_Lock_Error = "pointerlockerror",
+
+ .Selection_Change = "selectionchange",
+ .Selection_Start = "selectionstart",
+
+ .Transition_Start = "transitionstart",
+ .Transition_End = "transitionend",
+ .Transition_Run = "transitionrun",
+ .Transition_Cancel = "transitioncancel",
+
+ .Touch_Cancel = "touchcancel",
+ .Touch_End = "touchend",
+ .Touch_Move = "touchmove",
+ .Touch_Start = "touchstart",
+
+ .Context_Menu = "contextmenu",
+
+ .Custom = "?custom?",
+}
+
+Delta_Mode :: enum u32 {
+ Pixel = 0,
+ Line = 1,
+ Page = 2,
+}
+
+Key_Location :: enum u8 {
+ Standard = 0,
+ Left = 1,
+ Right = 2,
+ Numpad = 3,
+}
+
+KEYBOARD_MAX_KEY_SIZE :: 16
+KEYBOARD_MAX_CODE_SIZE :: 16
+
+Event_Target_Kind :: enum u32 {
+ Element = 0,
+ Document = 1,
+ Window = 2,
+}
+
+Event_Phase :: enum u8 {
+ None = 0,
+ Capturing_Phase = 1,
+ At_Target = 2,
+ Bubbling_Phase = 3,
+}
+
+Event_Option :: enum u8 {
+ Bubbles = 0,
+ Cancelable = 1,
+ Composed = 2,
+}
+Event_Options :: distinct bit_set[Event_Option; u8]
+
+Event :: struct {
+ kind: Event_Kind,
+ target_kind: Event_Target_Kind,
+ current_target_kind: Event_Target_Kind,
+ id: string,
+ timestamp: f64,
+
+ phase: Event_Phase,
+ options: Event_Options,
+ is_composing: bool,
+ is_trusted: bool,
+
+ using data: struct #raw_union #align(8) {
+ scroll: struct {
+ delta: [2]f64,
+ },
+ visibility_change: struct {
+ is_visible: bool,
+ },
+ wheel: struct {
+ delta: [3]f64,
+ delta_mode: Delta_Mode,
+ },
+
+ key: struct {
+ key: string,
+ code: string,
+ location: Key_Location,
+
+ ctrl: bool,
+ shift: bool,
+ alt: bool,
+ meta: bool,
+
+ repeat: bool,
+
+ _key_buf: [KEYBOARD_MAX_KEY_SIZE]byte,
+ _code_buf: [KEYBOARD_MAX_KEY_SIZE]byte,
+ },
+
+ mouse: struct {
+ screen: [2]i64,
+ client: [2]i64,
+ offset: [2]i64,
+ page: [2]i64,
+ movement: [2]i64,
+
+ ctrl: bool,
+ shift: bool,
+ alt: bool,
+ meta: bool,
+
+ button: i16,
+ buttons: bit_set[0..<16; u16],
+ },
+ },
+
+
+ user_data: rawptr,
+ callback: proc(e: Event),
+}
+
+
+add_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
+ panic("vendor:wasm/js not supported on non JS targets")
+}
+
+remove_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool {
+ panic("vendor:wasm/js not supported on non JS targets")
+}
+
+add_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
+ panic("vendor:wasm/js not supported on non JS targets")
+}
+
+remove_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool {
+ panic("vendor:wasm/js not supported on non JS targets")
+}
+
+remove_event_listener_from_event :: proc(e: Event) -> bool {
+ panic("vendor:wasm/js not supported on non JS targets")
+}
+
+add_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool {
+ panic("vendor:wasm/js not supported on non JS targets")
+}
+remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event)) -> bool {
+ panic("vendor:wasm/js not supported on non JS targets")
+}
+
diff --git a/core/sys/wasm/js/general.odin b/core/sys/wasm/js/general.odin
new file mode 100644
index 000000000..4ed2ae298
--- /dev/null
+++ b/core/sys/wasm/js/general.odin
@@ -0,0 +1,12 @@
+#+build js wasm32, js wasm64p32
+package wasm_js_interface
+
+foreign import "odin_env"
+
+@(default_calling_convention="contextless")
+foreign odin_env {
+ trap :: proc() -> ! ---
+ abort :: proc() -> ! ---
+ alert :: proc(msg: string) ---
+ evaluate :: proc(str: string) ---
+} \ No newline at end of file
diff --git a/core/sys/wasm/js/memory_all_targets.odin b/core/sys/wasm/js/memory_all_targets.odin
new file mode 100644
index 000000000..e80d13c0b
--- /dev/null
+++ b/core/sys/wasm/js/memory_all_targets.odin
@@ -0,0 +1,14 @@
+#+build !js
+package wasm_js_interface
+
+import "core:mem"
+
+PAGE_SIZE :: 64 * 1024
+page_alloc :: proc(page_count: int) -> (data: []byte, err: mem.Allocator_Error) {
+ panic("vendor:wasm/js not supported on non-js targets")
+}
+
+page_allocator :: proc() -> mem.Allocator {
+ panic("vendor:wasm/js not supported on non-js targets")
+}
+
diff --git a/core/sys/wasm/js/memory_js.odin b/core/sys/wasm/js/memory_js.odin
new file mode 100644
index 000000000..8232cd0c9
--- /dev/null
+++ b/core/sys/wasm/js/memory_js.odin
@@ -0,0 +1,44 @@
+#+build js wasm32, js wasm64p32
+package wasm_js_interface
+
+import "core:mem"
+import "base:intrinsics"
+
+PAGE_SIZE :: 64 * 1024
+page_alloc :: proc(page_count: int) -> (data: []byte, err: mem.Allocator_Error) {
+ prev_page_count := intrinsics.wasm_memory_grow(0, uintptr(page_count))
+ if prev_page_count < 0 {
+ return nil, .Out_Of_Memory
+ }
+
+ ptr := ([^]u8)(uintptr(prev_page_count) * PAGE_SIZE)
+ return ptr[:page_count * PAGE_SIZE], nil
+}
+
+page_allocator :: proc() -> mem.Allocator {
+ procedure :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
+ size, alignment: int,
+ old_memory: rawptr, old_size: int,
+ location := #caller_location) -> ([]byte, mem.Allocator_Error) {
+ switch mode {
+ case .Alloc, .Alloc_Non_Zeroed:
+ assert(size % PAGE_SIZE == 0)
+ return page_alloc(size/PAGE_SIZE)
+ case .Resize, .Free, .Free_All, .Query_Info, .Resize_Non_Zeroed:
+ return nil, .Mode_Not_Implemented
+ case .Query_Features:
+ set := (^mem.Allocator_Mode_Set)(old_memory)
+ if set != nil {
+ set^ = {.Alloc, .Query_Features}
+ }
+ }
+
+ return nil, nil
+ }
+
+ return {
+ procedure = procedure,
+ data = nil,
+ }
+}
+
diff --git a/core/sys/wasm/js/odin.js b/core/sys/wasm/js/odin.js
new file mode 100644
index 000000000..afd9f6dff
--- /dev/null
+++ b/core/sys/wasm/js/odin.js
@@ -0,0 +1,1879 @@
+"use strict";
+
+(function() {
+
+function getElement(name) {
+ if (name) {
+ return document.getElementById(name);
+ }
+ return undefined;
+}
+
+function stripNewline(str) {
+ return str.replace(/\n/, ' ')
+}
+
+class WasmMemoryInterface {
+ constructor() {
+ this.memory = null;
+ this.exports = null;
+ this.listenerMap = {};
+
+ // Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32`
+ this.intSize = 4;
+ }
+
+ setIntSize(size) {
+ this.intSize = size;
+ }
+
+ setMemory(memory) {
+ this.memory = memory;
+ }
+
+ setExports(exports) {
+ this.exports = exports;
+ }
+
+ get mem() {
+ return new DataView(this.memory.buffer);
+ }
+
+
+ loadF32Array(addr, len) {
+ let array = new Float32Array(this.memory.buffer, addr, len);
+ return array;
+ }
+ loadF64Array(addr, len) {
+ let array = new Float64Array(this.memory.buffer, addr, len);
+ return array;
+ }
+ loadU32Array(addr, len) {
+ let array = new Uint32Array(this.memory.buffer, addr, len);
+ return array;
+ }
+ loadI32Array(addr, len) {
+ let array = new Int32Array(this.memory.buffer, addr, len);
+ return array;
+ }
+
+
+ loadU8(addr) { return this.mem.getUint8 (addr); }
+ loadI8(addr) { return this.mem.getInt8 (addr); }
+ loadU16(addr) { return this.mem.getUint16 (addr, true); }
+ loadI16(addr) { return this.mem.getInt16 (addr, true); }
+ loadU32(addr) { return this.mem.getUint32 (addr, true); }
+ loadI32(addr) { return this.mem.getInt32 (addr, true); }
+ loadU64(addr) {
+ const lo = this.mem.getUint32(addr + 0, true);
+ const hi = this.mem.getUint32(addr + 4, true);
+ return lo + hi*4294967296;
+ };
+ loadI64(addr) {
+ const lo = this.mem.getUint32(addr + 0, true);
+ const hi = this.mem.getInt32 (addr + 4, true);
+ return lo + hi*4294967296;
+ };
+ loadF32(addr) { return this.mem.getFloat32(addr, true); }
+ loadF64(addr) { return this.mem.getFloat64(addr, true); }
+ loadInt(addr) {
+ if (this.intSize == 8) {
+ return this.loadI64(addr);
+ } else if (this.intSize == 4) {
+ return this.loadI32(addr);
+ } else {
+ throw new Error('Unhandled `intSize`, expected `4` or `8`');
+ }
+ };
+ loadUint(addr) {
+ if (this.intSize == 8) {
+ return this.loadU64(addr);
+ } else if (this.intSize == 4) {
+ return this.loadU32(addr);
+ } else {
+ throw new Error('Unhandled `intSize`, expected `4` or `8`');
+ }
+ };
+ loadPtr(addr) { return this.loadU32(addr); }
+
+ loadB32(addr) {
+ return this.loadU32(addr) != 0;
+ }
+
+ loadBytes(ptr, len) {
+ return new Uint8Array(this.memory.buffer, ptr, Number(len));
+ }
+
+ loadString(ptr, len) {
+ const bytes = this.loadBytes(ptr, Number(len));
+ return new TextDecoder().decode(bytes);
+ }
+
+ loadCstring(ptr) {
+ const start = this.loadPtr(ptr);
+ if (start == 0) {
+ return null;
+ }
+ let len = 0;
+ for (; this.mem.getUint8(start+len) != 0; len += 1) {}
+ return this.loadString(start, len);
+ }
+
+ storeU8(addr, value) { this.mem.setUint8 (addr, value); }
+ storeI8(addr, value) { this.mem.setInt8 (addr, value); }
+ storeU16(addr, value) { this.mem.setUint16 (addr, value, true); }
+ storeI16(addr, value) { this.mem.setInt16 (addr, value, true); }
+ storeU32(addr, value) { this.mem.setUint32 (addr, value, true); }
+ storeI32(addr, value) { this.mem.setInt32 (addr, value, true); }
+ storeU64(addr, value) {
+ this.mem.setUint32(addr + 0, Number(value), true);
+
+ let div = 4294967296;
+ if (typeof value == 'bigint') {
+ div = BigInt(div);
+ }
+
+ this.mem.setUint32(addr + 4, Math.floor(Number(value / div)), true);
+ }
+ storeI64(addr, value) {
+ this.mem.setUint32(addr + 0, Number(value), true);
+
+ let div = 4294967296;
+ if (typeof value == 'bigint') {
+ div = BigInt(div);
+ }
+
+ this.mem.setInt32(addr + 4, Math.floor(Number(value / div)), true);
+ }
+ storeF32(addr, value) { this.mem.setFloat32(addr, value, true); }
+ storeF64(addr, value) { this.mem.setFloat64(addr, value, true); }
+ storeInt(addr, value) {
+ if (this.intSize == 8) {
+ this.storeI64(addr, value);
+ } else if (this.intSize == 4) {
+ this.storeI32(addr, value);
+ } else {
+ throw new Error('Unhandled `intSize`, expected `4` or `8`');
+ }
+ }
+ storeUint(addr, value) {
+ if (this.intSize == 8) {
+ this.storeU64(addr, value);
+ } else if (this.intSize == 4) {
+ this.storeU32(addr, value);
+ } else {
+ throw new Error('Unhandled `intSize`, expected `4` or `8`');
+ }
+ }
+
+ // Returned length might not be the same as `value.length` if non-ascii strings are given.
+ storeString(addr, value) {
+ const src = new TextEncoder().encode(value);
+ const dst = new Uint8Array(this.memory.buffer, addr, src.length);
+ dst.set(src);
+ return src.length;
+ }
+};
+
+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 (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 stringSize = this.mem.intSize*2;
+ let source = "";
+ for (let i = 0; i < strings_length; i++) {
+ let ptr = this.mem.loadPtr(strings_ptr + i*stringSize);
+ let len = this.mem.loadPtr(strings_ptr + i*stringSize + 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 = getElement(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 = getElement(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, 2);
+ this.mem.storeI32(minor_ptr, 0);
+ },
+
+
+ ActiveTexture: (x) => {
+ this.ctx.activeTexture(x);
+ },
+ AttachShader: (program, shader) => {
+ this.ctx.attachShader(this.programs[program], this.shaders[shader]);
+ },
+ BindAttribLocation: (program, index, name_ptr, name_len) => {
+ let name = this.mem.loadString(name_ptr, name_len);
+ this.ctx.bindAttribLocation(this.programs[program], index, name)
+ },
+ BindBuffer: (target, buffer) => {
+ let bufferObj = buffer ? this.buffers[buffer] : null;
+ if (target == 35051) {
+ this.ctx.currentPixelPackBufferBinding = buffer;
+ } else {
+ if (target == 35052) {
+ this.ctx.currentPixelUnpackBufferBinding = buffer;
+ }
+ this.ctx.bindBuffer(target, bufferObj)
+ }
+ },
+ BindFramebuffer: (target, framebuffer) => {
+ this.ctx.bindFramebuffer(target, framebuffer ? this.framebuffers[framebuffer] : null)
+ },
+ BindTexture: (target, texture) => {
+ this.ctx.bindTexture(target, texture ? this.textures[texture] : null)
+ },
+ BlendColor: (red, green, blue, alpha) => {
+ this.ctx.blendColor(red, green, blue, alpha);
+ },
+ BlendEquation: (mode) => {
+ this.ctx.blendEquation(mode);
+ },
+ BlendFunc: (sfactor, dfactor) => {
+ this.ctx.blendFunc(sfactor, dfactor);
+ },
+ BlendFuncSeparate: (srcRGB, dstRGB, srcAlpha, dstAlpha) => {
+ this.ctx.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
+ },
+
+
+ BufferData: (target, size, data, usage) => {
+ if (data) {
+ this.ctx.bufferData(target, this.mem.loadBytes(data, size), usage);
+ } else {
+ this.ctx.bufferData(target, size, usage);
+ }
+ },
+ BufferSubData: (target, offset, size, data) => {
+ if (data) {
+ this.ctx.bufferSubData(target, offset, this.mem.loadBytes(data, size));
+ } else {
+ this.ctx.bufferSubData(target, offset, null);
+ }
+ },
+
+
+ Clear: (x) => {
+ this.ctx.clear(x);
+ },
+ ClearColor: (r, g, b, a) => {
+ this.ctx.clearColor(r, g, b, a);
+ },
+ ClearDepth: (x) => {
+ this.ctx.clearDepth(x);
+ },
+ ClearStencil: (x) => {
+ this.ctx.clearStencil(x);
+ },
+ ColorMask: (r, g, b, a) => {
+ this.ctx.colorMask(!!r, !!g, !!b, !!a);
+ },
+ CompileShader: (shader) => {
+ this.ctx.compileShader(this.shaders[shader]);
+ },
+
+
+ CompressedTexImage2D: (target, level, internalformat, width, height, border, imageSize, data) => {
+ if (data) {
+ this.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, this.mem.loadBytes(data, imageSize));
+ } else {
+ this.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, null);
+ }
+ },
+ CompressedTexSubImage2D: (target, level, xoffset, yoffset, width, height, format, imageSize, data) => {
+ if (data) {
+ this.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, this.mem.loadBytes(data, imageSize));
+ } else {
+ this.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, null);
+ }
+ },
+
+ CopyTexImage2D: (target, level, internalformat, x, y, width, height, border) => {
+ this.ctx.copyTexImage2D(target, level, internalformat, x, y, width, height, border);
+ },
+ CopyTexSubImage2D: (target, level, xoffset, yoffset, x, y, width, height) => {
+ this.ctx.copyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
+ },
+
+
+ CreateBuffer: () => {
+ let buffer = this.ctx.createBuffer();
+ if (!buffer) {
+ this.recordError(1282);
+ return 0;
+ }
+ let id = this.getNewId(this.buffers);
+ buffer.name = id
+ this.buffers[id] = buffer;
+ return id;
+ },
+ CreateFramebuffer: () => {
+ let buffer = this.ctx.createFramebuffer();
+ let id = this.getNewId(this.framebuffers);
+ buffer.name = id
+ this.framebuffers[id] = buffer;
+ return id;
+ },
+ CreateProgram: () => {
+ let program = this.ctx.createProgram();
+ let id = this.getNewId(this.programs);
+ program.name = id;
+ this.programs[id] = program;
+ return id;
+ },
+ CreateRenderbuffer: () => {
+ let buffer = this.ctx.createRenderbuffer();
+ let id = this.getNewId(this.renderbuffers);
+ buffer.name = id;
+ this.renderbuffers[id] = buffer;
+ return id;
+ },
+ CreateShader: (shaderType) => {
+ let shader = this.ctx.createShader(shaderType);
+ let id = this.getNewId(this.shaders);
+ shader.name = id;
+ this.shaders[id] = shader;
+ return id;
+ },
+ CreateTexture: () => {
+ let texture = this.ctx.createTexture();
+ if (!texture) {
+ this.recordError(1282)
+ return 0;
+ }
+ let id = this.getNewId(this.textures);
+ texture.name = id;
+ this.textures[id] = texture;
+ return id;
+ },
+
+
+ CullFace: (mode) => {
+ this.ctx.cullFace(mode);
+ },
+
+
+ DeleteBuffer: (id) => {
+ let obj = this.buffers[id];
+ if (obj && id != 0) {
+ this.ctx.deleteBuffer(obj);
+ this.buffers[id] = null;
+ }
+ },
+ DeleteFramebuffer: (id) => {
+ let obj = this.framebuffers[id];
+ if (obj && id != 0) {
+ this.ctx.deleteFramebuffer(obj);
+ this.framebuffers[id] = null;
+ }
+ },
+ DeleteProgram: (id) => {
+ let obj = this.programs[id];
+ if (obj && id != 0) {
+ this.ctx.deleteProgram(obj);
+ this.programs[id] = null;
+ }
+ },
+ DeleteRenderbuffer: (id) => {
+ let obj = this.renderbuffers[id];
+ if (obj && id != 0) {
+ this.ctx.deleteRenderbuffer(obj);
+ this.renderbuffers[id] = null;
+ }
+ },
+ DeleteShader: (id) => {
+ let obj = this.shaders[id];
+ if (obj && id != 0) {
+ this.ctx.deleteShader(obj);
+ this.shaders[id] = null;
+ }
+ },
+ DeleteTexture: (id) => {
+ let obj = this.textures[id];
+ if (obj && id != 0) {
+ this.ctx.deleteTexture(obj);
+ this.textures[id] = null;
+ }
+ },
+
+
+ DepthFunc: (func) => {
+ this.ctx.depthFunc(func);
+ },
+ DepthMask: (flag) => {
+ this.ctx.depthMask(!!flag);
+ },
+ DepthRange: (zNear, zFar) => {
+ this.ctx.depthRange(zNear, zFar);
+ },
+ DetachShader: (program, shader) => {
+ this.ctx.detachShader(this.programs[program], this.shaders[shader]);
+ },
+ Disable: (cap) => {
+ this.ctx.disable(cap);
+ },
+ DisableVertexAttribArray: (index) => {
+ this.ctx.disableVertexAttribArray(index);
+ },
+ DrawArrays: (mode, first, count) => {
+ this.ctx.drawArrays(mode, first, count);
+ },
+ DrawElements: (mode, count, type, indices) => {
+ this.ctx.drawElements(mode, count, type, indices);
+ },
+
+
+ Enable: (cap) => {
+ this.ctx.enable(cap);
+ },
+ EnableVertexAttribArray: (index) => {
+ this.ctx.enableVertexAttribArray(index);
+ },
+ Finish: () => {
+ this.ctx.finish();
+ },
+ Flush: () => {
+ this.ctx.flush();
+ },
+ FramebufferRenderbuffer: (target, attachment, renderbuffertarget, renderbuffer) => {
+ this.ctx.framebufferRenderbuffer(target, attachment, renderbuffertarget, this.renderbuffers[renderbuffer]);
+ },
+ FramebufferTexture2D: (target, attachment, textarget, texture, level) => {
+ this.ctx.framebufferTexture2D(target, attachment, textarget, this.textures[texture], level);
+ },
+ FrontFace: (mode) => {
+ this.ctx.frontFace(mode);
+ },
+
+
+ GenerateMipmap: (target) => {
+ this.ctx.generateMipmap(target);
+ },
+
+
+ GetAttribLocation: (program, name_ptr, name_len) => {
+ let name = this.mem.loadString(name_ptr, name_len);
+ return this.ctx.getAttribLocation(this.programs[program], name);
+ },
+
+
+ GetParameter: (pname) => {
+ return this.ctx.getParameter(pname);
+ },
+ GetProgramParameter: (program, pname) => {
+ return this.ctx.getProgramParameter(this.programs[program], pname)
+ },
+ GetProgramInfoLog: (program, buf_ptr, buf_len, length_ptr) => {
+ let log = this.ctx.getProgramInfoLog(this.programs[program]);
+ if (log === null) {
+ log = "(unknown error)";
+ }
+ if (buf_len > 0 && buf_ptr) {
+ let n = Math.min(buf_len, log.length);
+ log = log.substring(0, n);
+ this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(log))
+
+ this.mem.storeInt(length_ptr, n);
+ }
+ },
+ GetShaderInfoLog: (shader, buf_ptr, buf_len, length_ptr) => {
+ let log = this.ctx.getShaderInfoLog(this.shaders[shader]);
+ if (log === null) {
+ log = "(unknown error)";
+ }
+ if (buf_len > 0 && buf_ptr) {
+ let n = Math.min(buf_len, log.length);
+ log = log.substring(0, n);
+ this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(log))
+
+ this.mem.storeInt(length_ptr, n);
+ }
+ },
+ GetShaderiv: (shader, pname, p) => {
+ if (p) {
+ if (pname == 35716) {
+ let log = this.ctx.getShaderInfoLog(this.shaders[shader]);
+ if (log === null) {
+ log = "(unknown error)";
+ }
+ this.mem.storeInt(p, log.length+1);
+ } else if (pname == 35720) {
+ let source = this.ctx.getShaderSource(this.shaders[shader]);
+ let sourceLength = (source === null || source.length == 0) ? 0 : source.length+1;
+ this.mem.storeInt(p, sourceLength);
+ } else {
+ let param = this.ctx.getShaderParameter(this.shaders[shader], pname);
+ this.mem.storeI32(p, param);
+ }
+ } else {
+ this.recordError(1281);
+ }
+ },
+
+
+ GetUniformLocation: (program, name_ptr, name_len) => {
+ let name = this.mem.loadString(name_ptr, name_len);
+ let arrayOffset = 0;
+ if (name.indexOf("]", name.length - 1) !== -1) {
+ let ls = name.lastIndexOf("["),
+ arrayIndex = name.slice(ls + 1, -1);
+ if (arrayIndex.length > 0 && (arrayOffset = parseInt(arrayIndex)) < 0) {
+ return -1;
+ }
+ name = name.slice(0, ls)
+ }
+ var ptable = this.programInfos[program];
+ if (!ptable) {
+ return -1;
+ }
+ var uniformInfo = ptable.uniforms[name];
+ return (uniformInfo && arrayOffset < uniformInfo[0]) ? uniformInfo[1] + arrayOffset : -1
+ },
+
+
+ GetVertexAttribOffset: (index, pname) => {
+ return this.ctx.getVertexAttribOffset(index, pname);
+ },
+
+
+ Hint: (target, mode) => {
+ this.ctx.hint(target, mode);
+ },
+
+
+ IsBuffer: (buffer) => this.ctx.isBuffer(this.buffers[buffer]),
+ IsEnabled: (cap) => this.ctx.isEnabled(cap),
+ IsFramebuffer: (framebuffer) => this.ctx.isFramebuffer(this.framebuffers[framebuffer]),
+ IsProgram: (program) => this.ctx.isProgram(this.programs[program]),
+ IsRenderbuffer: (renderbuffer) => this.ctx.isRenderbuffer(this.renderbuffers[renderbuffer]),
+ IsShader: (shader) => this.ctx.isShader(this.shaders[shader]),
+ IsTexture: (texture) => this.ctx.isTexture(this.textures[texture]),
+
+ LineWidth: (width) => {
+ this.ctx.lineWidth(width);
+ },
+ LinkProgram: (program) => {
+ this.ctx.linkProgram(this.programs[program]);
+ this.programInfos[program] = null;
+ this.populateUniformTable(program);
+ },
+ PixelStorei: (pname, param) => {
+ this.ctx.pixelStorei(pname, param);
+ },
+ PolygonOffset: (factor, units) => {
+ this.ctx.polygonOffset(factor, units);
+ },
+
+
+ ReadnPixels: (x, y, width, height, format, type, bufSize, data) => {
+ this.ctx.readPixels(x, y, width, height, format, type, this.mem.loadBytes(data, bufSize));
+ },
+ RenderbufferStorage: (target, internalformat, width, height) => {
+ this.ctx.renderbufferStorage(target, internalformat, width, height);
+ },
+ SampleCoverage: (value, invert) => {
+ this.ctx.sampleCoverage(value, !!invert);
+ },
+ Scissor: (x, y, width, height) => {
+ this.ctx.scissor(x, y, width, height);
+ },
+ ShaderSource: (shader, strings_ptr, strings_length) => {
+ let source = this.getSource(shader, strings_ptr, strings_length);
+ this.ctx.shaderSource(this.shaders[shader], source);
+ },
+
+ StencilFunc: (func, ref, mask) => {
+ this.ctx.stencilFunc(func, ref, mask);
+ },
+ StencilFuncSeparate: (face, func, ref, mask) => {
+ this.ctx.stencilFuncSeparate(face, func, ref, mask);
+ },
+ StencilMask: (mask) => {
+ this.ctx.stencilMask(mask);
+ },
+ StencilMaskSeparate: (face, mask) => {
+ this.ctx.stencilMaskSeparate(face, mask);
+ },
+ StencilOp: (fail, zfail, zpass) => {
+ this.ctx.stencilOp(fail, zfail, zpass);
+ },
+ StencilOpSeparate: (face, fail, zfail, zpass) => {
+ this.ctx.stencilOpSeparate(face, fail, zfail, zpass);
+ },
+
+
+ TexImage2D: (target, level, internalformat, width, height, border, format, type, size, data) => {
+ if (data) {
+ this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, this.mem.loadBytes(data, size));
+ } else {
+ this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, null);
+ }
+ },
+ TexParameterf: (target, pname, param) => {
+ this.ctx.texParameterf(target, pname, param);
+ },
+ TexParameteri: (target, pname, param) => {
+ this.ctx.texParameteri(target, pname, param);
+ },
+ TexSubImage2D: (target, level, xoffset, yoffset, width, height, format, type, size, data) => {
+ this.ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, this.mem.loadBytes(data, size));
+ },
+
+
+ Uniform1f: (location, v0) => { this.ctx.uniform1f(this.uniforms[location], v0); },
+ Uniform2f: (location, v0, v1) => { this.ctx.uniform2f(this.uniforms[location], v0, v1); },
+ Uniform3f: (location, v0, v1, v2) => { this.ctx.uniform3f(this.uniforms[location], v0, v1, v2); },
+ Uniform4f: (location, v0, v1, v2, v3) => { this.ctx.uniform4f(this.uniforms[location], v0, v1, v2, v3); },
+
+ Uniform1i: (location, v0) => { this.ctx.uniform1i(this.uniforms[location], v0); },
+ Uniform2i: (location, v0, v1) => { this.ctx.uniform2i(this.uniforms[location], v0, v1); },
+ Uniform3i: (location, v0, v1, v2) => { this.ctx.uniform3i(this.uniforms[location], v0, v1, v2); },
+ Uniform4i: (location, v0, v1, v2, v3) => { this.ctx.uniform4i(this.uniforms[location], v0, v1, v2, v3); },
+
+ UniformMatrix2fv: (location, addr) => {
+ let array = this.mem.loadF32Array(addr, 2*2);
+ this.ctx.uniformMatrix2fv(this.uniforms[location], false, array);
+ },
+ UniformMatrix3fv: (location, addr) => {
+ let array = this.mem.loadF32Array(addr, 3*3);
+ this.ctx.uniformMatrix3fv(this.uniforms[location], false, array);
+ },
+ UniformMatrix4fv: (location, addr) => {
+ let array = this.mem.loadF32Array(addr, 4*4);
+ this.ctx.uniformMatrix4fv(this.uniforms[location], false, array);
+ },
+
+ UseProgram: (program) => {
+ if (program) this.ctx.useProgram(this.programs[program]);
+ },
+ ValidateProgram: (program) => {
+ if (program) this.ctx.validateProgram(this.programs[program]);
+ },
+
+
+ VertexAttrib1f: (index, x) => {
+ this.ctx.vertexAttrib1f(index, x);
+ },
+ VertexAttrib2f: (index, x, y) => {
+ this.ctx.vertexAttrib2f(index, x, y);
+ },
+ VertexAttrib3f: (index, x, y, z) => {
+ this.ctx.vertexAttrib3f(index, x, y, z);
+ },
+ VertexAttrib4f: (index, x, y, z, w) => {
+ this.ctx.vertexAttrib4f(index, x, y, z, w);
+ },
+ VertexAttribPointer: (index, size, type, normalized, stride, ptr) => {
+ this.ctx.vertexAttribPointer(index, size, type, !!normalized, stride, ptr);
+ },
+
+ Viewport: (x, y, w, h) => {
+ this.ctx.viewport(x, y, w, h);
+ },
+ };
+ }
+
+ getWebGL2Interface() {
+ return {
+ /* Buffer objects */
+ CopyBufferSubData: (readTarget, writeTarget, readOffset, writeOffset, size) => {
+ this.assertWebGL2();
+ this.ctx.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size);
+ },
+ GetBufferSubData: (target, srcByteOffset, dst_buffer_ptr, dst_buffer_len, dstOffset, length) => {
+ this.assertWebGL2();
+ this.ctx.getBufferSubData(target, srcByteOffset, this.mem.loadBytes(dst_buffer_ptr, dst_buffer_len), dstOffset, length);
+ },
+
+ /* Framebuffer objects */
+ BlitFramebuffer: (srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter) => {
+ this.assertWebGL2();
+ this.ctx.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
+ },
+ FramebufferTextureLayer: (target, attachment, texture, level, layer) => {
+ this.assertWebGL2();
+ this.ctx.framebufferTextureLayer(target, attachment, this.textures[texture], level, layer);
+ },
+ InvalidateFramebuffer: (target, attachments_ptr, attachments_len) => {
+ this.assertWebGL2();
+ let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len);
+ this.ctx.invalidateFramebuffer(target, attachments);
+ },
+ InvalidateSubFramebuffer: (target, attachments_ptr, attachments_len, x, y, width, height) => {
+ this.assertWebGL2();
+ let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len);
+ this.ctx.invalidateSubFramebuffer(target, attachments, x, y, width, height);
+ },
+ ReadBuffer: (src) => {
+ this.assertWebGL2();
+ this.ctx.readBuffer(src);
+ },
+
+ /* Renderbuffer objects */
+ RenderbufferStorageMultisample: (target, samples, internalformat, width, height) => {
+ this.assertWebGL2();
+ this.ctx.renderbufferStorageMultisample(target, samples, internalformat, width, height);
+ },
+
+ /* Texture objects */
+
+ TexStorage3D: (target, levels, internalformat, width, height, depth) => {
+ this.assertWebGL2();
+ this.ctx.texStorage3D(target, levels, internalformat, width, height, depth);
+ },
+ TexImage3D: (target, level, internalformat, width, height, depth, border, format, type, size, data) => {
+ this.assertWebGL2();
+ if (data) {
+ this.ctx.texImage3D(target, level, internalformat, width, height, depth, border, format, type, this.mem.loadBytes(data, size));
+ } else {
+ this.ctx.texImage3D(target, level, internalformat, width, height, depth, border, format, type, null);
+ }
+ },
+ TexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, size, data) => {
+ this.assertWebGL2();
+ this.ctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, this.mem.loadBytes(data, size));
+ },
+ CompressedTexImage3D: (target, level, internalformat, width, height, depth, border, imageSize, data) => {
+ this.assertWebGL2();
+ if (data) {
+ this.ctx.compressedTexImage3D(target, level, internalformat, width, height, depth, border, this.mem.loadBytes(data, imageSize));
+ } else {
+ this.ctx.compressedTexImage3D(target, level, internalformat, width, height, depth, border, null);
+ }
+ },
+ CompressedTexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data) => {
+ this.assertWebGL2();
+ if (data) {
+ this.ctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, this.mem.loadBytes(data, imageSize));
+ } else {
+ this.ctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, null);
+ }
+ },
+
+ CopyTexSubImage3D: (target, level, xoffset, yoffset, zoffset, x, y, width, height) => {
+ this.assertWebGL2();
+ this.ctx.copyTexSubImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height);
+ },
+
+ /* Programs and shaders */
+ GetFragDataLocation: (program, name_ptr, name_len) => {
+ this.assertWebGL2();
+ return this.ctx.getFragDataLocation(this.programs[program], this.mem.loadString(name_ptr, name_len));
+ },
+
+ /* Uniforms */
+ Uniform1ui: (location, v0) => {
+ this.assertWebGL2();
+ this.ctx.uniform1ui(this.uniforms[location], v0);
+ },
+ Uniform2ui: (location, v0, v1) => {
+ this.assertWebGL2();
+ this.ctx.uniform2ui(this.uniforms[location], v0, v1);
+ },
+ Uniform3ui: (location, v0, v1, v2) => {
+ this.assertWebGL2();
+ this.ctx.uniform3ui(this.uniforms[location], v0, v1, v2);
+ },
+ Uniform4ui: (location, v0, v1, v2, v3) => {
+ this.assertWebGL2();
+ this.ctx.uniform4ui(this.uniforms[location], v0, v1, v2, v3);
+ },
+
+ UniformMatrix3x2fv: (location, addr) => {
+ this.assertWebGL2();
+ let array = this.mem.loadF32Array(addr, 3*2);
+ this.ctx.uniformMatrix3x2fv(this.uniforms[location], false, array);
+ },
+ UniformMatrix4x2fv: (location, addr) => {
+ this.assertWebGL2();
+ let array = this.mem.loadF32Array(addr, 4*2);
+ this.ctx.uniformMatrix4x2fv(this.uniforms[location], false, array);
+ },
+ UniformMatrix2x3fv: (location, addr) => {
+ this.assertWebGL2();
+ let array = this.mem.loadF32Array(addr, 2*3);
+ this.ctx.uniformMatrix2x3fv(this.uniforms[location], false, array);
+ },
+ UniformMatrix4x3fv: (location, addr) => {
+ this.assertWebGL2();
+ let array = this.mem.loadF32Array(addr, 4*3);
+ this.ctx.uniformMatrix4x3fv(this.uniforms[location], false, array);
+ },
+ UniformMatrix2x4fv: (location, addr) => {
+ this.assertWebGL2();
+ let array = this.mem.loadF32Array(addr, 2*4);
+ this.ctx.uniformMatrix2x4fv(this.uniforms[location], false, array);
+ },
+ UniformMatrix3x4fv: (location, addr) => {
+ this.assertWebGL2();
+ let array = this.mem.loadF32Array(addr, 3*4);
+ this.ctx.uniformMatrix3x4fv(this.uniforms[location], false, array);
+ },
+
+ /* Vertex attribs */
+ VertexAttribI4i: (index, x, y, z, w) => {
+ this.assertWebGL2();
+ this.ctx.vertexAttribI4i(index, x, y, z, w);
+ },
+ VertexAttribI4ui: (index, x, y, z, w) => {
+ this.assertWebGL2();
+ this.ctx.vertexAttribI4ui(index, x, y, z, w);
+ },
+ VertexAttribIPointer: (index, size, type, stride, offset) => {
+ this.assertWebGL2();
+ this.ctx.vertexAttribIPointer(index, size, type, stride, offset);
+ },
+
+ /* Writing to the drawing buffer */
+ VertexAttribDivisor: (index, divisor) => {
+ this.assertWebGL2();
+ this.ctx.vertexAttribDivisor(index, divisor);
+ },
+ DrawArraysInstanced: (mode, first, count, instanceCount) => {
+ this.assertWebGL2();
+ this.ctx.drawArraysInstanced(mode, first, count, instanceCount);
+ },
+ DrawElementsInstanced: (mode, count, type, offset, instanceCount) => {
+ this.assertWebGL2();
+ this.ctx.drawElementsInstanced(mode, count, type, offset, instanceCount);
+ },
+ 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.queries[id];
+ if (obj && id != 0) {
+ this.ctx.deleteQuery(obj);
+ this.queries[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();
+ const stringSize = this.mem.intSize*2;
+ let varyings = [];
+ for (let i = 0; i < varyings_len; i++) {
+ let ptr = this.mem.loadPtr(varyings_ptr + i*stringSize + 0*4);
+ let len = this.mem.loadPtr(varyings_ptr + i*stringSize + 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.resumeTransformFeedback();
+ },
+
+
+ /* 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 n = Math.min(buf_len, name.length);
+ name = name.substring(0, n);
+ this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(name))
+ this.mem.storeInt(length_ptr, n);
+ },
+ UniformBlockBinding: (program, uniformBlockIndex, uniformBlockBinding) => {
+ this.assertWebGL2();
+ this.ctx.uniformBlockBinding(this.programs[program], uniformBlockIndex, uniformBlockBinding);
+ },
+
+ /* 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]);
+ },
+ };
+ }
+};
+
+
+function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory, eventQueue, event_temp) {
+ const MAX_INFO_CONSOLE_LINES = 512;
+ let infoConsoleLines = new Array();
+ let currentLine = {};
+ currentLine[false] = "";
+ currentLine[true] = "";
+ let prevIsError = false;
+
+ const writeToConsole = (line, isError) => {
+ if (!line) {
+ return;
+ }
+
+ const println = (text, forceIsError) => {
+ let style = [
+ "color: #eee",
+ "background-color: #d20",
+ "padding: 2px 4px",
+ "border-radius: 2px",
+ ].join(";");
+ let doIsError = isError;
+ if (forceIsError !== undefined) {
+ doIsError = forceIsError;
+ }
+
+ if (doIsError) {
+ console.log("%c"+text, style);
+ } else {
+ console.log(text);
+ }
+
+ };
+
+ // Print to console
+ if (line == "\n") {
+ println(currentLine[isError]);
+ currentLine[isError] = "";
+ } else if (!line.includes("\n")) {
+ currentLine[isError] = currentLine[isError].concat(line);
+ } else {
+ let lines = line.split("\n");
+ let printLast = lines.length > 1 && line.endsWith("\n");
+ println(currentLine[isError].concat(lines[0]));
+ currentLine[isError] = "";
+ for (let i = 1; i < lines.length-1; i++) {
+ println(lines[i]);
+ }
+ let last = lines[lines.length-1];
+ if (printLast) {
+ println(last);
+ } else {
+ currentLine[isError] = last;
+ }
+ }
+
+ if (prevIsError != isError) {
+ if (prevIsError) {
+ println(currentLine[prevIsError], prevIsError);
+ currentLine[prevIsError] = "";
+ }
+ }
+ prevIsError = isError;
+
+
+ // HTML based console
+ if (!consoleElement) {
+ return;
+ }
+ const wrap = (x) => {
+ if (isError) {
+ return '<span style="color:#f21">'+x+'</span>';
+ }
+ return x;
+ };
+
+ if (line == "\n") {
+ infoConsoleLines.push(line);
+ } else if (!line.includes("\n")) {
+ let prevLine = "";
+ if (infoConsoleLines.length > 0) {
+ prevLine = infoConsoleLines.pop();
+ }
+ infoConsoleLines.push(prevLine.concat(wrap(line)));
+ } else {
+ let lines = line.split("\n");
+ let lastHasNewline = lines.length > 1 && line.endsWith("\n");
+
+ let prevLine = "";
+ if (infoConsoleLines.length > 0) {
+ prevLine = infoConsoleLines.pop();
+ }
+ infoConsoleLines.push(prevLine.concat(wrap(lines[0]).concat("\n")));
+
+ for (let i = 1; i < lines.length-1; i++) {
+ infoConsoleLines.push(wrap(lines[i]).concat("\n"));
+ }
+ let last = lines[lines.length-1];
+ if (lastHasNewline) {
+ infoConsoleLines.push(last.concat("\n"));
+ } else {
+ infoConsoleLines.push(last);
+ }
+ }
+
+ if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) {
+ infoConsoleLines.shift(MAX_INFO_CONSOLE_LINES);
+ }
+
+ let data = "";
+ for (let i = 0; i < infoConsoleLines.length; i++) {
+ data = data.concat(infoConsoleLines[i]);
+ }
+
+ let info = consoleElement;
+ info.innerHTML = data;
+ info.scrollTop = info.scrollHeight;
+ };
+
+ let webglContext = new WebGLInterface(wasmMemoryInterface);
+
+ const env = {};
+
+ if (memory) {
+ env.memory = memory;
+ }
+
+ return {
+ env,
+ "odin_env": {
+ write: (fd, ptr, len) => {
+ const str = wasmMemoryInterface.loadString(ptr, len);
+ if (fd == 1) {
+ writeToConsole(str, false);
+ return;
+ } else if (fd == 2) {
+ writeToConsole(str, true);
+ 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)); },
+
+ // return a bigint to be converted to i64
+ time_now: () => BigInt(Date.now()),
+ tick_now: () => performance.now(),
+ time_sleep: (duration_ms) => {
+ if (duration_ms > 0) {
+ // TODO(bill): Does this even make any sense?
+ }
+ },
+
+ sqrt: Math.sqrt,
+ sin: Math.sin,
+ cos: Math.cos,
+ pow: Math.pow,
+ fmuladd: (x, y, z) => x*y + z,
+ ln: Math.log,
+ exp: Math.exp,
+ ldexp: (x, exp) => x * Math.pow(2, exp),
+
+ rand_bytes: (ptr, len) => {
+ const view = new Uint8Array(wasmMemoryInterface.memory.buffer, ptr, len)
+ crypto.getRandomValues(view)
+ },
+ },
+ "odin_dom": {
+ init_event_raw: (ep) => {
+ const W = wasmMemoryInterface.intSize;
+ 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 align = (alignment) => {
+ const modulo = offset & (alignment-1);
+ if (modulo != 0) {
+ offset += alignment - modulo
+ }
+ };
+
+ let wmi = wasmMemoryInterface;
+
+ if (!event_temp.data) {
+ return;
+ }
+
+ 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);
+ }
+
+ align(W);
+
+ wmi.storeI32(off(W), event_temp.data.id_ptr);
+ wmi.storeUint(off(W), event_temp.data.id_len);
+
+ align(8);
+ wmi.storeF64(off(8), e.timeStamp*1e-3);
+
+ wmi.storeU8(off(1), e.eventPhase);
+ let options = 0;
+ if (!!e.bubbles) { options |= 1<<0; }
+ if (!!e.cancelable) { options |= 1<<1; }
+ if (!!e.composed) { options |= 1<<2; }
+ wmi.storeU8(off(1), options);
+ wmi.storeU8(off(1), !!e.isComposing);
+ wmi.storeU8(off(1), !!e.isTrusted);
+
+ align(8);
+ 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 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) {
+ // Note: those strings are constructed
+ // on the native side from buffers that
+ // are filled later, so skip them
+ const keyPtr = off(W*2, W);
+ const codePtr = 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);
+
+ wmi.storeI32(off(W), e.key.length)
+ wmi.storeI32(off(W), e.code.length)
+ wmi.storeString(off(16, 1), e.key);
+ wmi.storeString(off(16, 1), e.code);
+ } else if (e.type === 'scroll') {
+ wmi.storeF64(off(8), window.scrollX);
+ wmi.storeF64(off(8), window.scrollY);
+ } else if (e.type === 'visibilitychange') {
+ wmi.storeU8(off(1), !document.hidden);
+ }
+ },
+
+ 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 = getElement(id);
+ if (element == undefined) {
+ return false;
+ }
+
+ let listener = (e) => {
+ let event_data = {};
+ event_data.id_ptr = id_ptr;
+ event_data.id_len = id_len;
+ event_data.event = e;
+ event_data.name_code = name_code;
+
+ eventQueue.push({event_data: event_data, data: data, callback: callback});
+ };
+ wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener;
+ element.addEventListener(name, listener, !!use_capture);
+ 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) => {
+ let event_data = {};
+ event_data.id_ptr = 0;
+ event_data.id_len = 0;
+ event_data.event = e;
+ event_data.name_code = name_code;
+
+ eventQueue.push({event_data: event_data, data: data, callback: callback});
+ };
+ 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 = getElement(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;
+ },
+ remove_window_event_listener: (name_ptr, name_len, data, callback) => {
+ let name = wasmMemoryInterface.loadString(name_ptr, name_len);
+ let element = window;
+ let key = {data: data, callback: callback};
+ let listener = wasmMemoryInterface.listenerMap[key];
+ if (!listener) {
+ return false;
+ }
+ wasmMemoryInterface.listenerMap[key] = undefined;
+
+ element.removeEventListener(name, listener);
+ return true;
+ },
+
+ event_stop_propagation: () => {
+ if (event_temp.data && event_temp.data.event) {
+ event_temp.data.event.stopPropagation();
+ }
+ },
+ event_stop_immediate_propagation: () => {
+ if (event_temp.data && event_temp.data.event) {
+ event_temp.data.event.stopImmediatePropagation();
+ }
+ },
+ event_prevent_default: () => {
+ if (event_temp.data && event_temp.data.event) {
+ event_temp.data.event.preventDefault();
+ }
+ },
+
+ dispatch_custom_event: (id_ptr, id_len, name_ptr, name_len, options_bits) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let name = wasmMemoryInterface.loadString(name_ptr, name_len);
+ let options = {
+ bubbles: (options_bits & (1<<0)) !== 0,
+ cancelable: (options_bits & (1<<1)) !== 0,
+ composed: (options_bits & (1<<2)) !== 0,
+ };
+
+ let element = getElement(id);
+ if (element) {
+ element.dispatchEvent(new Event(name, options));
+ return true;
+ }
+ return false;
+ },
+
+ get_element_value_f64: (id_ptr, id_len) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let element = getElement(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 = getElement(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().encode(str))
+ return n;
+ }
+ }
+ return 0;
+ },
+ get_element_value_string_length: (id_ptr, id_len) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let element = getElement(id);
+ if (element) {
+ return element.value.length;
+ }
+ return 0;
+ },
+ get_element_min_max: (ptr_array2_f64, id_ptr, id_len) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let element = getElement(id);
+ if (element) {
+ let values = wasmMemoryInterface.loadF64Array(ptr_array2_f64, 2);
+ values[0] = element.min;
+ values[1] = element.max;
+ }
+ },
+ set_element_value_f64: (id_ptr, id_len, value) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let element = getElement(id);
+ if (element) {
+ element.value = value;
+ }
+ },
+ set_element_value_string: (id_ptr, id_len, value_ptr, value_len) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let value = wasmMemoryInterface.loadString(value_ptr, value_len);
+ let element = getElement(id);
+ if (element) {
+ element.value = value;
+ }
+ },
+
+ get_element_key_f64: (id_ptr, id_len, key_ptr, key_len) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let key = wasmMemoryInterface.loadString(key_ptr, key_len);
+ let element = getElement(id);
+ return element ? element[key] : 0;
+ },
+ get_element_key_string: (id_ptr, id_len, key_ptr, key_len, buf_ptr, buf_len) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let key = wasmMemoryInterface.loadString(key_ptr, key_len);
+ let element = getElement(id);
+ if (element) {
+ let str = element[key];
+ 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().encode(str))
+ return n;
+ }
+ }
+ return 0;
+ },
+ get_element_key_string_length: (id_ptr, id_len, key_ptr, key_len) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let key = wasmMemoryInterface.loadString(key_ptr, key_len);
+ let element = getElement(id);
+ if (element && element[key]) {
+ return element[key].length;
+ }
+ return 0;
+ },
+
+ set_element_key_f64: (id_ptr, id_len, key_ptr, key_len, value) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let key = wasmMemoryInterface.loadString(key_ptr, key_len);
+ let element = getElement(id);
+ if (element) {
+ element[key] = value;
+ }
+ },
+ set_element_key_string: (id_ptr, id_len, key_ptr, key_len, value_ptr, value_len) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let key = wasmMemoryInterface.loadString(key_ptr, key_len);
+ let value = wasmMemoryInterface.loadString(value_ptr, value_len);
+ let element = getElement(id);
+ if (element) {
+ element[key] = value;
+ }
+ },
+
+
+ get_bounding_client_rect: (rect_ptr, id_ptr, id_len) => {
+ let id = wasmMemoryInterface.loadString(id_ptr, id_len);
+ let element = getElement(id);
+ if (element) {
+ let values = wasmMemoryInterface.loadF64Array(rect_ptr, 4);
+ let rect = element.getBoundingClientRect();
+ values[0] = rect.left;
+ values[1] = rect.top;
+ values[2] = rect.right - rect.left;
+ values[3] = rect.bottom - rect.top;
+ }
+ },
+ window_get_rect: (rect_ptr) => {
+ let values = wasmMemoryInterface.loadF64Array(rect_ptr, 4);
+ values[0] = window.screenX;
+ values[1] = window.screenY;
+ values[2] = window.screen.width;
+ values[3] = window.screen.height;
+ },
+
+ window_get_scroll: (pos_ptr) => {
+ let values = wasmMemoryInterface.loadF64Array(pos_ptr, 2);
+ values[0] = window.scrollX;
+ values[1] = window.scrollY;
+ },
+ window_set_scroll: (x, y) => {
+ window.scroll(x, y);
+ },
+
+ device_pixel_ratio: () => {
+ return window.devicePixelRatio;
+ },
+
+ },
+
+ "webgl": webglContext.getWebGL1Interface(),
+ "webgl2": webglContext.getWebGL2Interface(),
+ };
+};
+
+/**
+ * @param {string} wasmPath - Path to the WASM module to run
+ * @param {?HTMLPreElement} consoleElement - Optional console/pre element to append output to, in addition to the console
+ * @param {any} extraForeignImports - Imports, in addition to the default runtime to provide the module
+ * @param {?WasmMemoryInterface} wasmMemoryInterface - Optional memory to use instead of the defaults
+ * @param {?int} intSize - Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32`
+ */
+async function runWasm(wasmPath, consoleElement, extraForeignImports, wasmMemoryInterface, intSize = 4) {
+ if (!wasmMemoryInterface) {
+ wasmMemoryInterface = new WasmMemoryInterface();
+ }
+ wasmMemoryInterface.setIntSize(intSize);
+
+ let eventQueue = new Array();
+ let event_temp = {};
+
+ let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement, wasmMemoryInterface.memory, eventQueue, event_temp);
+ let exports = {};
+
+ if (extraForeignImports !== undefined) {
+ imports = {
+ ...imports,
+ ...extraForeignImports,
+ };
+ }
+
+ const response = await fetch(wasmPath);
+ const file = await response.arrayBuffer();
+ const wasm = await WebAssembly.instantiate(file, imports);
+ exports = wasm.instance.exports;
+ wasmMemoryInterface.setExports(exports);
+
+ if (exports.memory) {
+ if (wasmMemoryInterface.memory) {
+ console.warn("WASM module exports memory, but `runWasm` was given an interface with existing memory too");
+ }
+ wasmMemoryInterface.setMemory(exports.memory);
+ }
+
+ exports._start();
+
+ // Define a `@export step :: proc(current_type: f64) -> (keep_going: bool) {`
+ // in your app and it will get called every frame.
+ // return `false` to stop the execution of the module.
+ if (exports.step) {
+ const odin_ctx = exports.default_context_ptr();
+
+ let prevTimeStamp = undefined;
+ function step(currTimeStamp) {
+ if (prevTimeStamp == undefined) {
+ prevTimeStamp = currTimeStamp;
+ }
+
+ const dt = (currTimeStamp - prevTimeStamp)*0.001;
+ prevTimeStamp = currTimeStamp;
+
+ while (eventQueue.length > 0) {
+ let e = eventQueue.shift()
+ event_temp.data = e.event_data;
+ exports.odin_dom_do_event_callback(e.data, e.callback, odin_ctx);
+ }
+ event_temp.data = null;
+
+ if (!exports.step(currTimeStamp*0.001, odin_ctx)) {
+ exports._end();
+ return;
+ }
+
+ window.requestAnimationFrame(step);
+ }
+
+ window.requestAnimationFrame(step);
+ } else {
+ exports._end();
+ }
+
+ return;
+};
+
+window.odin = {
+ // Interface Types
+ WasmMemoryInterface: WasmMemoryInterface,
+ WebGLInterface: WebGLInterface,
+
+ // Functions
+ setupDefaultImports: odinSetupDefaultImports,
+ runWasm: runWasm,
+};
+})();