vc.js 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. // Browser runtime for the Demo Virtual Console
  2. function make_environment(...envs) {
  3. return new Proxy(envs, {
  4. get(target, prop, receiver) {
  5. for (let env of envs) {
  6. if (env.hasOwnProperty(prop)) {
  7. return env[prop];
  8. }
  9. }
  10. return (...args) => {console.error("NOT IMPLEMENTED: "+prop, args)}
  11. }
  12. });
  13. }
  14. const libm = {
  15. "atan2f": Math.atan2,
  16. "cosf": Math.cos,
  17. "sinf": Math.sin,
  18. "sqrtf": Math.sqrt,
  19. };
  20. let iota = 0;
  21. // TODO: nothing in this Canvas "declaration" states that iota's measure units are Uint32
  22. // Which is not useful for all kinds of structures. A more general approach would be to use Uint8 as the measure units.
  23. const CANVAS_PIXELS = iota++;
  24. const CANVAS_WIDTH = iota++;
  25. const CANVAS_HEIGHT = iota++;
  26. const CANVAS_STRIDE = iota++;
  27. const CANVAS_SIZE = iota++;
  28. function readCanvasFromMemory(memory_buffer, canvas_ptr)
  29. {
  30. const canvas_memory = new Uint32Array(memory_buffer, canvas_ptr, CANVAS_SIZE);
  31. return {
  32. pixels: canvas_memory[CANVAS_PIXELS],
  33. width: canvas_memory[CANVAS_WIDTH],
  34. height: canvas_memory[CANVAS_HEIGHT],
  35. stride: canvas_memory[CANVAS_STRIDE],
  36. };
  37. }
  38. async function startDemo(elementId, wasmPath) {
  39. const app = document.getElementById(`app-${elementId}`);
  40. if (app === null) {
  41. console.error(`Could not find element app-${elementId}. Skipping demo ${wasmPath}...`);
  42. return;
  43. }
  44. const sec = document.getElementById(`sec-${elementId}`);
  45. if (sec === null) {
  46. console.error(`Could not find element sec-${elementId}. Skipping demo ${wasmPath}...`);
  47. return;
  48. }
  49. let paused = true;
  50. sec.addEventListener("mouseenter", () => paused = false);
  51. sec.addEventListener("mouseleave", () => paused = true);
  52. const ctx = app.getContext("2d");
  53. const w = await WebAssembly.instantiateStreaming(fetch(wasmPath), {
  54. "env": make_environment(libm)
  55. });
  56. // TODO: if __heap_base not found tell the user to compile their wasm module with -Wl,--export=__heap_base
  57. const heap_base = w.instance.exports.__heap_base.value;
  58. function render(dt) {
  59. const buffer = w.instance.exports.memory.buffer;
  60. w.instance.exports.vc_render(heap_base, dt*0.001);
  61. const canvas = readCanvasFromMemory(buffer, heap_base);
  62. if (canvas.width != canvas.stride) {
  63. // TODO: maybe we can preallocate a Uint8ClampedArray on JavaScript side and just copy the canvas data there to bring width and stride to the same value?
  64. console.error(`Canvas width (${canvas.width}) is not equal to its stride (${canvas.stride}). Unfortunately we can't easily support that in a browser because ImageData simply does not accept stride. Welcome to 2022.`);
  65. return;
  66. }
  67. const image = new ImageData(new Uint8ClampedArray(buffer, canvas.pixels, canvas.width*canvas.height*4), canvas.width);
  68. app.width = canvas.width;
  69. app.height = canvas.height;
  70. ctx.putImageData(image, 0, 0);
  71. }
  72. let prev = null;
  73. function first(timestamp) {
  74. prev = timestamp;
  75. render(0);
  76. window.requestAnimationFrame(loop);
  77. }
  78. function loop(timestamp) {
  79. const dt = timestamp - prev;
  80. prev = timestamp;
  81. if (!paused) render(dt);
  82. window.requestAnimationFrame(loop);
  83. }
  84. window.requestAnimationFrame(first);
  85. }