diff options
author | Mitya Selivanov <automainint@guattari.tech> | 2025-01-16 23:26:35 +0100 |
---|---|---|
committer | Mitya Selivanov <automainint@guattari.tech> | 2025-01-16 23:26:35 +0100 |
commit | 4ff31ff39b562eacaffbe3ed5c22cf564ed65fb5 (patch) | |
tree | 91e7a12c2f4513d88c80847e3e24489c2f6bb690 /juliaset/index.htm | |
parent | de476aa919244df699d4223fe2ebac5fb1f2ea28 (diff) | |
download | static-4ff31ff39b562eacaffbe3ed5c22cf564ed65fb5.zip |
Add Julia Set wasm program
Diffstat (limited to 'juliaset/index.htm')
-rw-r--r-- | juliaset/index.htm | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/juliaset/index.htm b/juliaset/index.htm new file mode 100644 index 0000000..8da8454 --- /dev/null +++ b/juliaset/index.htm @@ -0,0 +1,603 @@ +<!DOCTYPE html> +<html style="height: 100%; overflow: hidden; overflow-x: hidden;"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <link rel='icon' type='image/gif' href='/favicon.gif'> + <title> Reduced System Layer </title> + </head> + <body style="margin: 0; height: 100%; overflow: hidden; overflow-x: hidden;"> + <canvas style="margin: 0; width: 100%; height: 100%;" id="frame" oncontextmenu="event.preventDefault()"></canvas> + <script type="worklet"> + registerProcessor( + "Sound_Node", + class Sound_Node extends AudioWorkletProcessor { + constructor(options) { + super(); + + this.frame_width = 0; + this.frame_height = 0; + this.pixels_size = 0; + + let event_handlers = { + wasm : (ev) => { + WebAssembly.instantiate( + ev.wasm, + { + wasi_snapshot_preview1 : { + clock_time_get : () => { return Date.now(); }, + args_sizes_get : () => { throw new Error("Unexpected args_sizes_get call"); }, + args_get : () => { throw new Error("Unexpected args_get call"); }, + proc_exit : () => { throw new Error("Unexpected proc_exit call"); }, + fd_close : () => { throw new Error("Unexpected fd_close call"); }, + fd_write : () => { throw new Error("Unexpected fd_write call"); }, + fd_seek : () => { throw new Error("Unexpected fd_seek call"); }, + }, + env : { + p_clipboard_write_impl : (size, text) => { + let text_buffer = new ArrayBuffer(size); + new Uint8Array(text_buffer).set(new Uint8Array(this.memory_buffer, text, size)); + + this.port.postMessage({ + id : "clipboard", + text_buffer : text_buffer, + }); + }, + p_time_impl : Date.now, + + floor : Math.floor, + ceil : Math.ceil, + sqrt : Math.sqrt, + cbrt : Math.cbrt, + pow : Math.pow, + log : Math.log, + log2 : Math.log2, + log10 : Math.log10, + exp : Math.exp, + sin : Math.sin, + cos : Math.cos, + tan : Math.tan, + asin : Math.asin, + acos : Math.acos, + atan : Math.atan, + atan2 : Math.atan2, + }, + } + ).then((program) => { + this.memory_buffer = program.exports.memory.buffer; + + let href_bytes = options.processorOptions.href; + let href_address = program.exports.js_href(); + let href_len = Math.min(program.exports.js_href_size(), href_bytes.length); + new Uint8Array(this.memory_buffer, href_address, href_len).set(href_bytes.subarray(0, href_len)); + + program.exports.js_main(); + + this.sound_buffer_address = program.exports.js_sound_buffer(); + this.pixels_address = program.exports.js_pixels(); + this.frames = new Float32Array(this.memory_buffer, this.sound_buffer_address, program.exports.js_max_num_sound_frames()); + + let title_address = program.exports.js_title(); + + let title_buffer; + + if (title_address != 0) { + let bytes = new Uint8Array(this.memory_buffer); + let title_len = 0; + while (bytes[title_address + title_len] != 0) + ++title_len; + title_buffer = new ArrayBuffer(title_len); + new Uint8Array(title_buffer).set(bytes.subarray(title_address, title_address + title_len)); + } else + title_buffer = undefined; + + this.port.postMessage({ + id : "init", + title_buffer : title_buffer, + }); + + this.program = program; + }); + }, + + resize : (ev) => { + this.frame_width = ev.width; + this.frame_height = ev.height; + this.pixels_size = ev.width * ev.height * 4; + }, + + mousedown : (ev) => { + if (this.program === undefined) + return; + + this.program.exports.js_mousedown(ev.buttons); + }, + + mouseup : (ev) => { + if (this.program === undefined) + return; + + this.program.exports.js_mouseup(ev.buttons); + }, + + mousemove : (ev) => { + if (this.program === undefined) + return; + + this.program.exports.js_mousemove(ev.x, ev.y); + }, + + keydown : (ev) => { + if (this.program === undefined) + return; + + this.program.exports.js_keydown(ev.key, ev.mod, ev.ch); + }, + + keyup : (ev) => { + if (this.program === undefined) + return; + + this.program.exports.js_keyup(ev.key, ev.mod); + }, + + wheel : (ev) => { + if (this.program === undefined) + return; + + this.program.exports.js_wheel(ev.x * -0.01, ev.y * -0.01); + }, + + paste : (ev) => { + if (this.program === undefined) + return; + + let address = this.program.exports.js_clipboard_buffer(ev.bytes.length); + let len = this.program.exports.js_clipboard_size(); + + new Uint8Array(this.memory_buffer, address, len).set(ev.bytes.subarray(0, len)); + + this.program.exports.js_keydown(ev.key, ev.mod, ev.ch); + }, + }; + + this.port.onmessage = (ev) => { + if (ev.data.id in event_handlers) + event_handlers[ev.data.id](ev.data); + }; + } + + process(input, output, parameters) { + if (this.program === undefined) + return true; + + let num_channels = output[0].length; + let num_samples = output[0][0].length; + let num_frames = num_samples * num_channels; + + this.program.exports.js_frame(this.frame_width, this.frame_height, num_samples); + + let pixels_buffer = new ArrayBuffer(this.pixels_size); + new Uint8Array(pixels_buffer).set(new Uint8Array(this.memory_buffer, this.pixels_address, this.pixels_size)); + + this.port.postMessage({ + id : "frame", + pixels_buffer : pixels_buffer, + }); + + for (let j = 0; j < num_channels; ++j) + for (let i = 0; i < num_samples; ++i) + output[0][j][i] = this.frames[j * num_samples + i]; + + return true; + } + } + ); + </script> + <script type="text/javascript"> + let key_map = {}; + + function key_from_code(code) { + if (code in key_map) + return key_map[code]; + return 0; + }; + + function mod_from_event(ev) { + let mod = 0; + if (ev.ctrlKey) mod |= 1; + if (ev.shiftKey) mod |= 2; + if (ev.altKey) mod |= 4; + if (ev.metaKey) mod |= 8; + return mod; + }; + + function char_from_event(ev) { + if (ev.key.length != 1) + return 0; + return ev.key.charCodeAt(0); + } + + async function obtain_defaults(wasm) { + let program = await WebAssembly.instantiate( + wasm, + { + wasi_snapshot_preview1 : { + clock_time_get : () => {}, + args_sizes_get : () => {}, + args_get : () => {}, + proc_exit : () => {}, + fd_close : () => {}, + fd_write : () => {}, + fd_seek : () => {}, + }, + env : { + p_clipboard_write_impl : () => {}, + p_time_impl : () => {}, + + floor : () => {}, + ceil : () => {}, + sqrt : () => {}, + cbrt : () => {}, + pow : () => {}, + log : () => {}, + log2 : () => {}, + log10 : () => {}, + exp : () => {}, + sin : () => {}, + cos : () => {}, + tan : () => {}, + asin : () => {}, + acos : () => {}, + atan : () => {}, + atan2 : () => {}, + }, + } + ); + + let num = program.exports.js_max_num_keys(); + let address = program.exports.js_key_map(); + let keys = new Uint16Array(program.exports.memory.buffer, address, num); + + let n = 0; + + key_map["Backspace"] = keys[n++]; + key_map["Tab"] = keys[n++]; + key_map["Enter"] = keys[n++]; + key_map["ControlLeft"] = keys[n++]; + key_map["ControlRight"] = keys[n++]; + key_map["ShiftLeft"] = keys[n++]; + key_map["ShiftRight"] = keys[n++]; + key_map["AltLeft"] = keys[n++]; + key_map["AltRight"] = keys[n++]; + key_map["ArrowLeft"] = keys[n++]; + key_map["ArrowRight"] = keys[n++]; + key_map["ArrowUp"] = keys[n++]; + key_map["ArrowDown"] = keys[n++]; + key_map["Pause"] = keys[n++]; + key_map["Insert"] = keys[n++]; + key_map["Home"] = keys[n++]; + key_map["End"] = keys[n++]; + key_map["PageUp"] = keys[n++]; + key_map["PageDown"] = keys[n++]; + key_map["Escape"] = keys[n++]; + key_map["PrintScreen"] = keys[n++]; + key_map["Space"] = keys[n++]; + key_map["MetaLeft"] = keys[n++]; + key_map["MetaRight"] = keys[n++]; + key_map["Quote"] = keys[n++]; + key_map["Comma"] = keys[n++]; + key_map["Minus"] = keys[n++]; + key_map["Period"] = keys[n++]; + key_map["Slash"] = keys[n++]; + key_map["Digit0"] = keys[n++]; + key_map["Digit1"] = keys[n++]; + key_map["Digit2"] = keys[n++]; + key_map["Digit3"] = keys[n++]; + key_map["Digit4"] = keys[n++]; + key_map["Digit5"] = keys[n++]; + key_map["Digit6"] = keys[n++]; + key_map["Digit7"] = keys[n++]; + key_map["Digit8"] = keys[n++]; + key_map["Digit9"] = keys[n++]; + key_map["Semicolon"] = keys[n++]; + key_map["Equal"] = keys[n++]; + key_map["BracketLeft"] = keys[n++]; + key_map["Backslash"] = keys[n++]; + key_map["BracketRight"] = keys[n++]; + key_map["Backquote"] = keys[n++]; + key_map["KeyA"] = keys[n++]; + key_map["KeyB"] = keys[n++]; + key_map["KeyC"] = keys[n++]; + key_map["KeyD"] = keys[n++]; + key_map["KeyE"] = keys[n++]; + key_map["KeyF"] = keys[n++]; + key_map["KeyG"] = keys[n++]; + key_map["KeyH"] = keys[n++]; + key_map["KeyI"] = keys[n++]; + key_map["KeyJ"] = keys[n++]; + key_map["KeyK"] = keys[n++]; + key_map["KeyL"] = keys[n++]; + key_map["KeyM"] = keys[n++]; + key_map["KeyN"] = keys[n++]; + key_map["KeyO"] = keys[n++]; + key_map["KeyP"] = keys[n++]; + key_map["KeyQ"] = keys[n++]; + key_map["KeyR"] = keys[n++]; + key_map["KeyS"] = keys[n++]; + key_map["KeyT"] = keys[n++]; + key_map["KeyU"] = keys[n++]; + key_map["KeyV"] = keys[n++]; + key_map["KeyW"] = keys[n++]; + key_map["KeyX"] = keys[n++]; + key_map["KeyY"] = keys[n++]; + key_map["KeyZ"] = keys[n++]; + key_map["Delete"] = keys[n++]; + key_map["F1"] = keys[n++]; + key_map["F2"] = keys[n++]; + key_map["F3"] = keys[n++]; + key_map["F4"] = keys[n++]; + key_map["F5"] = keys[n++]; + key_map["F6"] = keys[n++]; + key_map["F7"] = keys[n++]; + key_map["F8"] = keys[n++]; + key_map["F9"] = keys[n++]; + key_map["F10"] = keys[n++]; + key_map["F11"] = keys[n++]; + key_map["F12"] = keys[n++]; + key_map["F13"] = keys[n++]; + key_map["F14"] = keys[n++]; + key_map["F15"] = keys[n++]; + key_map["F16"] = keys[n++]; + key_map["F17"] = keys[n++]; + key_map["F18"] = keys[n++]; + key_map["F19"] = keys[n++]; + key_map["F20"] = keys[n++]; + key_map["F21"] = keys[n++]; + key_map["F22"] = keys[n++]; + key_map["F23"] = keys[n++]; + key_map["F24"] = keys[n++]; + + let defaults = { + sound_sample_rate : program.exports.js_sound_sample_rate(), + num_sound_channels : program.exports.js_num_sound_channels(), + key_paste : key_map["KeyV"], + }; + + return defaults; + } + + async function run(attrs) { + let wasm = await WebAssembly.compileStreaming(attrs.wasm); + + let { sound_sample_rate, num_sound_channels, key_paste } = await obtain_defaults(wasm); + + let frame_width = 0; + let frame_height = 0; + + let canvas; + let context; + let pixels_buffer; + let animation_frame; + + let sound_ready = false; + let sound_init; + let sound_context; + let sound_node; + + let paste_mod = 0; + let paste_ch = 0; + + canvas = attrs.canvas; + context = canvas.getContext("2d"); + + context.fillStyle = "#000"; + context.fillRect(0, 0, canvas.width, canvas.height); + + context.fillStyle = "#fff"; + context.font = "1.7em serif"; + context.textAlign = "center"; + context.textBaseline = "middle"; + context.fillText("Click to run", canvas.width / 2.0, canvas.height / 2.0); + + animation_frame = () => { + if (attrs.fit_window) { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + } + + if (frame_width != canvas.width || frame_height != canvas.height) { + frame_width = canvas.width; + frame_height = canvas.height; + + sound_node.port.postMessage({ + id : "resize", + width : frame_width, + height : frame_height, + }); + } + + let num_bytes = 4 * canvas.width * canvas.height; + + if (pixels_buffer !== undefined && pixels_buffer.byteLength >= num_bytes) { + let pixels = new Uint8ClampedArray(pixels_buffer, 0, num_bytes); + + let data = new ImageData( + pixels, + canvas.width, + canvas.height + ); + + context.putImageData(data, 0, 0); + } else { + context.fillStyle = "#000"; + context.fillRect(0, 0, canvas.width, canvas.height); + } + + window.requestAnimationFrame(animation_frame); + }; + + sound_init = async () => { + sound_context = new AudioContext({ + sampleRate : sound_sample_rate, + }); + + let blob = new Blob( + [ document.querySelector("script[type=worklet]").innerText ], + { type : "application/javascript", } + ); + + await sound_context.audioWorklet.addModule(URL.createObjectURL(blob)); + + sound_node = new AudioWorkletNode( + sound_context, + "Sound_Node", + { numberOfInputs : 0, + outputChannelCount : [ num_sound_channels ], + processorOptions : { href : new TextEncoder("utf8").encode(document.location.href) }, } + ); + + sound_node.connect(sound_context.destination); + + let message_handlers = { + init : (ev) => { + if (ev.title_buffer != undefined) + document.title = new TextDecoder("utf8").decode(new Uint8Array(ev.title_buffer)); + + sound_ready = true; + + window.requestAnimationFrame(animation_frame); + }, + + clipboard : (ev) => { + navigator.clipboard.writeText(new TextDecoder("utf8").decode(new Uint8Array(ev.text_buffer))); + }, + + frame : (ev) => { + pixels_buffer = ev.pixels_buffer; + } + }; + + sound_node.port.onmessage = (ev) => { + if (ev.data.id in message_handlers) + message_handlers[ev.data.id](ev.data); + }; + + sound_node.port.postMessage({ + id : "wasm", + wasm : wasm, + }); + + wasm = undefined; + }; + + canvas.addEventListener("mousedown", (ev) => { + if (!sound_ready) { + sound_init(); + sound_init = () => {}; + return; + } + + sound_node.port.postMessage({ + id : "mousedown", + buttons : ev.buttons, + }); + }); + + canvas.addEventListener("mouseup", (ev) => { + if (!sound_ready) + return; + + sound_node.port.postMessage({ + id : "mouseup", + buttons : ev.buttons, + }); + }); + + canvas.addEventListener("mousemove", (ev) => { + if (!sound_ready) + return; + + sound_node.port.postMessage({ + id : "mousemove", + x : ev.clientX, + y : ev.clientY, + }); + }); + + window.addEventListener("keydown", (ev) => { + if (!sound_ready) + return; + if (ev.repeat) + return; + let key = key_from_code(ev.code); + if (key == 0) + return; + let mod = mod_from_event(ev); + let ch = char_from_event(ev); + + if (ev.ctrlKey && key == key_paste) { + paste_mod = mod; + paste_ch = ch; + } else + sound_node.port.postMessage({ + id : "keydown", + mod : mod, + key : key, + ch : ch, + }); + }); + + window.addEventListener("keyup", (ev) => { + ev.preventDefault(); + if (!sound_ready) + return; + if (ev.repeat) + return; + let key = key_from_code(ev.code); + if (key == 0) + return; + let mod = mod_from_event(ev); + + sound_node.port.postMessage({ + id : "keyup", + key : key, + mod : mod, + }); + }); + + window.addEventListener("wheel", (ev) => { + ev.preventDefault(); + if (!sound_ready) + return; + + sound_node.port.postMessage({ + id : "wheel", + x : ev.deltaX, + y : ev.deltaY, + }); + }); + + window.addEventListener("paste", (ev) => { + ev.preventDefault(); + if (!sound_ready) + return; + + sound_node.port.postMessage({ + id : "paste", + bytes : new TextEncoder("utf8").encode(ev.clipboardData.getData("text")), + mod : paste_mod, + key : key_paste, + ch : paste_ch, + }); + }); + } + + run({ + canvas : document.getElementById("frame"), + wasm : fetch("julia_set.wasm"), + fit_window : true, + }).catch((e) => console.error(e)); + </script> + </body> +</html> |