diff --git a/frontend/render_v2/rs/build b/frontend/render_v2/rs/build deleted file mode 100755 index e5636ac08..000000000 --- a/frontend/render_v2/rs/build +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -EMCC_CFLAGS="--no-entry -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s MAX_WEBGL_VERSION=2 -s MODULARIZE=1 -s EXPORT_NAME=createRustSkiaModule -s EXPORTED_RUNTIME_METHODS=GL -s ENVIRONMENT=webgl" cargo build --target=wasm32-unknown-emscripten - diff --git a/frontend/render_v2/rs/build.sh b/frontend/render_v2/rs/build.sh new file mode 100755 index 000000000..2c79fd96c --- /dev/null +++ b/frontend/render_v2/rs/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +EMSDK_QUIET=1 . "/usr/local/emsdk/emsdk_env.sh" + +EMCC_CFLAGS="--no-entry -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s MAX_WEBGL_VERSION=2 -s MODULARIZE=1 -s EXPORT_NAME=createRustSkiaModule -s EXPORTED_RUNTIME_METHODS=GL -s ENVIRONMENT=web" cargo build --target=wasm32-unknown-emscripten + diff --git a/frontend/render_v2/rs/src/main.rs b/frontend/render_v2/rs/src/main.rs index 780de2d77..3df297255 100644 --- a/frontend/render_v2/rs/src/main.rs +++ b/frontend/render_v2/rs/src/main.rs @@ -2,9 +2,11 @@ use std::boxed::Box; use skia_safe::{ gpu::{self, gl::FramebufferInfo, DirectContext}, - Color, Paint, PaintStyle, Rect, Surface, + // Color, Paint, PaintStyle, Rect, Surface, }; +use skia_safe as skia; + extern "C" { pub fn emscripten_GetProcAddress( name: *const ::std::os::raw::c_char, @@ -22,15 +24,15 @@ struct GpuState { /// structures are not thread safe, so a state must not be shared between different Web Workers. pub struct State { gpu_state: GpuState, - surface: Surface, + surface: skia::Surface, } impl State { - fn new(gpu_state: GpuState, surface: Surface) -> Self { + fn new(gpu_state: GpuState, surface: skia::Surface) -> Self { State { gpu_state, surface } } - fn set_surface(&mut self, surface: Surface) { + fn set_surface(&mut self, surface: skia::Surface) { self.surface = surface; } } @@ -66,7 +68,7 @@ fn create_gpu_state() -> GpuState { } /// Create the Skia surface that will be used for rendering. -fn create_surface(gpu_state: &mut GpuState, width: i32, height: i32) -> Surface { +fn create_surface(gpu_state: &mut GpuState, width: i32, height: i32) -> skia::Surface { let backend_render_target = gpu::backend_render_targets::make_gl((width, height), 1, 8, gpu_state.framebuffer_info); @@ -81,9 +83,9 @@ fn create_surface(gpu_state: &mut GpuState, width: i32, height: i32) -> Surface .unwrap() } -fn render_rect(surface: &mut Surface, rect: Rect, color: Color) { - let mut paint = Paint::default(); - paint.set_style(PaintStyle::Fill); +fn render_rect(surface: &mut skia::Surface, rect: skia::Rect, color: skia::Color) { + let mut paint = skia::Paint::default(); + paint.set_style(skia::PaintStyle::Fill); paint.set_color(color); paint.set_anti_alias(true); surface.canvas().draw_rect(rect, &paint); @@ -107,24 +109,51 @@ pub unsafe extern "C" fn resize_surface(state: *mut State, width: i32, height: i state.set_surface(surface); } -/// Draw a black rect at the specified coordinates. -/// # Safety #[no_mangle] -pub unsafe extern "C" fn draw_rect( - state: *mut State, - left: i32, - top: i32, - right: i32, - bottom: i32, +pub extern "C" fn make_color(r: i32, g: i32, b: i32, a: f32) -> Box { + Box::new(Color { + r: r as u8, + g: g as u8, + b: b as u8, + a, + }) +} + +#[repr(C)] +pub struct Color { r: u8, g: u8, b: u8, -) { - let state = unsafe { state.as_mut() }.expect("got an invalid state pointer"); - let rect = Rect::new(left as f32, top as f32, right as f32, bottom as f32); - let color = Color::from_rgb(r, g, b); + a: f32, +} - render_rect(&mut state.surface, rect, color); +#[repr(C)] +pub struct Rect { + left: f32, + top: f32, + right: f32, + bottom: f32, +} + +#[no_mangle] +pub extern "C" fn make_rect(left: f32, top: f32, right: f32, bottom: f32) -> Box { + Box::new(Rect { + left, + top, + right, + bottom, + }) +} + +/// Draws a rect at the specified coordinates with the give ncolor +/// # Safety +#[no_mangle] +pub unsafe extern "C" fn draw_rect(state: *mut State, rect: &Rect, color: &Color) { + let state = unsafe { state.as_mut() }.expect("got an invalid state pointer"); + let r = skia::Rect::new(rect.left, rect.top, rect.right, rect.bottom); + let color = skia::Color::from_argb((color.a * 255.0) as u8, color.r, color.g, color.b); + + render_rect(&mut state.surface, r, color); } #[no_mangle] diff --git a/frontend/src/app/render_v2/rs.cljs b/frontend/src/app/render_v2/rs.cljs index 372b26297..b0b9cc6ed 100644 --- a/frontend/src/app/render_v2/rs.cljs +++ b/frontend/src/app/render_v2/rs.cljs @@ -23,6 +23,8 @@ reset-canvas (gobj/get ^js internal-module "_reset_canvas") scale (gobj/get ^js internal-module "_scale") flush (gobj/get ^js internal-module "_flush") + make-color (gobj/get ^js internal-module "_make_color") + make-rect (gobj/get ^js internal-module "_make_rect") supported-shapes (filter (fn [shape] (not= (:type shape) :frame)) (vals objects))] (js/requestAnimationFrame (fn [] @@ -31,9 +33,11 @@ (translate gpu-state (- (:x vbox)) (- (:y vbox))) (doseq [shape supported-shapes] (let [sr (:selrect shape) - [r g b] (cc/hex->rgb (-> shape :fills first :fill-color))] - ;; (js/console.log (clj->js shape)) - (draw-rect gpu-state (:x1 sr) (:y1 sr) (:x2 sr) (:y2 sr) r g b))) + [r g b] (cc/hex->rgb (-> shape :fills first :fill-color)) + alpha (-> shape :fills first :fill-opacity) + color (make-color r g b alpha) + rect (make-rect (:x1 sr) (:y1 sr) (:x2 sr) (:y2 sr))] + (draw-rect gpu-state rect color))) (flush gpu-state))))) (defn set-canvas diff --git a/frontend/src/app/render_v2/rs.js b/frontend/src/app/render_v2/rs.js index 83ebc638e..46c24e869 100644 --- a/frontend/src/app/render_v2/rs.js +++ b/frontend/src/app/render_v2/rs.js @@ -28,7 +28,7 @@ var readyPromise = new Promise((resolve, reject) => { readyPromiseResolve = resolve; readyPromiseReject = reject; }); -["_draw_rect","_flush","_init","_main","_reset_canvas","_resize_surface","_scale","_translate","getExceptionMessage","incrementExceptionRefcount","decrementExceptionRefcount","_memory","___indirect_function_table","onRuntimeInitialized"].forEach((prop) => { +["_draw_rect","_flush","_init","_main","_make_color","_make_rect","_reset_canvas","_resize_surface","_scale","_translate","getExceptionMessage","incrementExceptionRefcount","decrementExceptionRefcount","_memory","___indirect_function_table","onRuntimeInitialized"].forEach((prop) => { if (!Object.getOwnPropertyDescriptor(readyPromise, prop)) { Object.defineProperty(readyPromise, prop, { get: () => abort('You are getting ' + prop + ' on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js'), @@ -700,7 +700,7 @@ function createWasm() { } } - if (!wasmBinaryFile) wasmBinaryFile = findWasmBinary(); + wasmBinaryFile ??= findWasmBinary(); // If instantiation fails, reject the module ready promise. instantiateAsync(wasmBinary, wasmBinaryFile, info, receiveInstantiationResult).catch(readyPromiseReject); @@ -752,45 +752,51 @@ function isExportedByForceFilesystem(name) { name === 'removeRunDependency'; } -function missingGlobal(sym, msg) { - if (typeof globalThis != 'undefined') { +/** + * Intercept access to a global symbol. This enables us to give informative + * warnings/errors when folks attempt to use symbols they did not include in + * their build, or no symbols that no longer exist. + */ +function hookGlobalSymbolAccess(sym, func) { + if (typeof globalThis != 'undefined' && !Object.getOwnPropertyDescriptor(globalThis, sym)) { Object.defineProperty(globalThis, sym, { configurable: true, get() { - warnOnce(`\`${sym}\` is not longer defined by emscripten. ${msg}`); + func(); return undefined; } }); } } +function missingGlobal(sym, msg) { + hookGlobalSymbolAccess(sym, () => { + warnOnce(`\`${sym}\` is not longer defined by emscripten. ${msg}`); + }); +} + missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer'); missingGlobal('asm', 'Please use wasmExports instead'); function missingLibrarySymbol(sym) { - if (typeof globalThis != 'undefined' && !Object.getOwnPropertyDescriptor(globalThis, sym)) { - Object.defineProperty(globalThis, sym, { - configurable: true, - get() { - // Can't `abort()` here because it would break code that does runtime - // checks. e.g. `if (typeof SDL === 'undefined')`. - var msg = `\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`; - // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in - // library.js, which means $name for a JS name with no prefix, or name - // for a JS name like _name. - var librarySymbol = sym; - if (!librarySymbol.startsWith('_')) { - librarySymbol = '$' + sym; - } - msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`; - if (isExportedByForceFilesystem(sym)) { - msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; - } - warnOnce(msg); - return undefined; - } - }); - } + hookGlobalSymbolAccess(sym, () => { + // Can't `abort()` here because it would break code that does runtime + // checks. e.g. `if (typeof SDL === 'undefined')`. + var msg = `\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`; + // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in + // library.js, which means $name for a JS name with no prefix, or name + // for a JS name like _name. + var librarySymbol = sym; + if (!librarySymbol.startsWith('_')) { + librarySymbol = '$' + sym; + } + msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + warnOnce(msg); + }); + // Any symbol that is not included from the JS library is also (by definition) // not exported on the Module object. unexportedRuntimeSymbol(sym); @@ -1305,12 +1311,7 @@ function dbg(...args) { var _emscripten_date_now = () => Date.now(); - var _emscripten_get_now; - // Modern environment where performance.now() is supported: - // N.B. a shorter form "_emscripten_get_now = performance.now;" is - // unfortunately not allowed even in current browsers (e.g. FF Nightly 75). - _emscripten_get_now = () => performance.now(); - ; + var _emscripten_get_now = () => performance.now(); var GLctx; @@ -5641,6 +5642,8 @@ function dbg(...args) { }, filesystems:null, syncFSRequests:0, + readFiles:{ + }, FSStream:class { constructor() { // TODO(https://github.com/emscripten-core/emscripten/issues/21414): @@ -6540,7 +6543,6 @@ function dbg(...args) { stream.stream_ops.open(stream); } if (Module['logReadFiles'] && !(flags & 1)) { - if (!FS.readFiles) FS.readFiles = {}; if (!(path in FS.readFiles)) { FS.readFiles[path] = 1; } @@ -6961,7 +6963,7 @@ function dbg(...args) { createDevice(parent, name, input, output) { var path = PATH.join2(typeof parent == 'string' ? parent : FS.getPath(parent), name); var mode = FS_getMode(!!input, !!output); - if (!FS.createDevice.major) FS.createDevice.major = 64; + FS.createDevice.major ??= 64; var dev = FS.makedev(FS.createDevice.major++, 0); // Create a fake device that a set of stream ops to emulate // the old behavior. @@ -8563,7 +8565,9 @@ var wasmExports = createWasm(); var ___wasm_call_ctors = createExportWrapper('__wasm_call_ctors', 0); var _init = Module['_init'] = createExportWrapper('init', 2); var _resize_surface = Module['_resize_surface'] = createExportWrapper('resize_surface', 3); -var _draw_rect = Module['_draw_rect'] = createExportWrapper('draw_rect', 8); +var _make_color = Module['_make_color'] = createExportWrapper('make_color', 4); +var _make_rect = Module['_make_rect'] = createExportWrapper('make_rect', 4); +var _draw_rect = Module['_draw_rect'] = createExportWrapper('draw_rect', 3); var _flush = Module['_flush'] = createExportWrapper('flush', 1); var _translate = Module['_translate'] = createExportWrapper('translate', 3); var _scale = Module['_scale'] = createExportWrapper('scale', 3); @@ -8613,10 +8617,10 @@ var dynCall_iiiiij = Module['dynCall_iiiiij'] = createExportWrapper('dynCall_iii var dynCall_iiiiijj = Module['dynCall_iiiiijj'] = createExportWrapper('dynCall_iiiiijj', 9); var dynCall_iiiiiijj = Module['dynCall_iiiiiijj'] = createExportWrapper('dynCall_iiiiiijj', 10); -function invoke_vii(index,a1,a2) { +function invoke_iii(index,a1,a2) { var sp = stackSave(); try { - getWasmTableEntry(index)(a1,a2); + return getWasmTableEntry(index)(a1,a2); } catch(e) { stackRestore(sp); if (!(e instanceof EmscriptenEH)) throw e; @@ -8635,6 +8639,17 @@ function invoke_ii(index,a1) { } } +function invoke_vii(index,a1,a2) { + var sp = stackSave(); + try { + getWasmTableEntry(index)(a1,a2); + } catch(e) { + stackRestore(sp); + if (!(e instanceof EmscriptenEH)) throw e; + _setThrew(1, 0); + } +} + function invoke_viii(index,a1,a2,a3) { var sp = stackSave(); try { @@ -8657,10 +8672,10 @@ function invoke_vi(index,a1) { } } -function invoke_iii(index,a1,a2) { +function invoke_iiii(index,a1,a2,a3) { var sp = stackSave(); try { - return getWasmTableEntry(index)(a1,a2); + return getWasmTableEntry(index)(a1,a2,a3); } catch(e) { stackRestore(sp); if (!(e instanceof EmscriptenEH)) throw e; @@ -8690,17 +8705,6 @@ function invoke_iiiiiii(index,a1,a2,a3,a4,a5,a6) { } } -function invoke_iiii(index,a1,a2,a3) { - var sp = stackSave(); - try { - return getWasmTableEntry(index)(a1,a2,a3); - } catch(e) { - stackRestore(sp); - if (!(e instanceof EmscriptenEH)) throw e; - _setThrew(1, 0); - } -} - function invoke_iiiii(index,a1,a2,a3,a4) { var sp = stackSave(); try { @@ -9048,12 +9052,14 @@ var missingLibrarySymbols = [ 'setImmediateWrapped', 'clearImmediateWrapped', 'polyfillSetImmediate', + 'registerPostMainLoop', + 'registerPreMainLoop', 'getPromise', 'makePromise', 'idsToPromises', 'makePromiseCallback', 'Browser_asyncPrepareDataCounter', - 'setMainLoop', + 'safeRequestAnimationFrame', 'isLeapYear', 'ydayFromDate', 'arraySum',