From a4cfaa542c38cf73058bac9ad944de5cde380987 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 2 Dec 2024 12:03:37 +0100 Subject: [PATCH] :tada: Refactor memory managemnt for color linear gradient stops --- frontend/src/app/render_wasm/api.cljs | 40 +++++++++++++++++++++------ render-wasm/src/main.rs | 31 +++++++++++++++++---- render-wasm/src/mem.rs | 17 ++++++++++++ 3 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 render-wasm/src/mem.rs diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 52ee0e08a..65a5a7fc8 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -13,6 +13,7 @@ [app.config :as cf] [app.render-wasm.helpers :as h] [app.util.functions :as fns] + [goog.object :as gobj] [promesa.core :as p])) (defonce internal-frame-id nil) @@ -43,6 +44,17 @@ ;; rgba >>> 0 so we have an unsigned representation (unsigned-bit-shift-right (bit-or (bit-shift-left a 24) rgb) 0))) +(defn- rgba-bytes-from-hex + "Takes a hex color in #rrggbb format, and an opacity value from 0 to 1 and returns an array with its r g b a values" + [hex opacity] + (let [rgb (js/parseInt (subs hex 1) 16) + a (mth/floor (* (or opacity 1) 0xff)) + ;; rgba >>> 0 so we have an unsigned representation + r (bit-shift-right rgb 16) + g (bit-and (bit-shift-right rgb 8) 255) + b (bit-and rgb 255)] + [r g b a])) + (defn cancel-render [] (when internal-frame-id @@ -108,15 +120,25 @@ (let [rgba (rgba-from-hex color opacity)] (h/call internal-module "_add_shape_solid_fill" rgba))) (when (and (some? gradient) (= (:type gradient) :linear)) - (h/call internal-module "_add_shape_linear_fill" - (:start-x gradient) - (:start-y gradient) - (:end-x gradient) - (:end-y gradient) - opacity) - (run! (fn [stop] - (let [rgba (rgba-from-hex (:color stop) (:opacity stop))] - (h/call internal-module "_add_shape_fill_stop" rgba (:offset stop)))) (:stops gradient))))) + (let [stops (:stops gradient) + n-stops (count stops) + mem-size (* 5 n-stops) + stops-ptr (h/call internal-module "_alloc_bytes" mem-size) + heap (gobj/get ^js internal-module "HEAPU8") + mem (js/Uint8Array. (.-buffer heap) stops-ptr mem-size)] + (h/call internal-module "_add_shape_linear_fill" + (:start-x gradient) + (:start-y gradient) + (:end-x gradient) + (:end-y gradient) + opacity) + (.set mem (js/Uint8Array. (clj->js (flatten (map (fn [stop] + (let [[r g b a] (rgba-bytes-from-hex (:color stop) (:opacity stop)) + offset (:offset stop)] + [r g b a (* 100 offset)])) + stops))))) + + (h/call internal-module "_add_shape_fill_stops" stops-ptr n-stops))))) fills)) (defn- translate-blend-mode diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 476a4a1d3..80af60b9c 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -1,6 +1,7 @@ mod debug; mod images; mod math; +pub mod mem; mod render; mod shapes; mod state; @@ -176,14 +177,34 @@ pub extern "C" fn add_shape_linear_fill( } } +#[derive(Debug)] +pub struct RawStopData { + color: [u8; 4], + offset: u8, +} + #[no_mangle] -pub extern "C" fn add_shape_fill_stop(raw_color: u32, offset: f32) { +pub extern "C" fn add_shape_fill_stops(ptr: *mut RawStopData, n_stops: i32) { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); if let Some(shape) = state.current_shape() { - let color = skia::Color::new(raw_color); - shape - .add_gradient_stop(color, offset) - .expect("got no fill or an invalid one"); + unsafe { + let buf = Vec::::from_raw_parts(ptr, n_stops as usize, n_stops as usize); + for raw_stop in buf.iter() { + let color = skia::Color::from_argb( + raw_stop.color[3], + raw_stop.color[0], + raw_stop.color[1], + raw_stop.color[2], + ); + shape + .add_gradient_stop(color, (raw_stop.offset as f32) / 100.) + .expect("got no fill or an invalid one"); + } + mem::free( + ptr as *mut u8, + n_stops as usize * std::mem::size_of::(), + ); + } } } diff --git a/render-wasm/src/mem.rs b/render-wasm/src/mem.rs new file mode 100644 index 000000000..fecd40758 --- /dev/null +++ b/render-wasm/src/mem.rs @@ -0,0 +1,17 @@ +#[no_mangle] +pub extern "C" fn alloc_bytes(len: usize) -> *mut u8 { + // create a new mutable buffer with capacity `len` + let mut buf: Vec = Vec::with_capacity(len); + let ptr = buf.as_mut_ptr(); + // take ownership of the memory block and ensure the its destructor is not + // called when the object goes out of scope at the end of the function + std::mem::forget(buf); + return ptr; +} + +pub fn free(ptr: *mut u8, len: usize) { + unsafe { + let buf = Vec::::from_raw_parts(ptr, len, len); + std::mem::forget(buf); + } +}