From 7105e49ac295b05071caba346f11efcd3f8268af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Mon, 2 Dec 2024 17:04:44 +0100 Subject: [PATCH 1/4] :recycle: Refactor adding gradient stops --- render-wasm/src/main.rs | 35 +++++++++++---------------------- render-wasm/src/shapes.rs | 6 ++++-- render-wasm/src/shapes/fills.rs | 17 ++++++++++++++++ 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 639607598..964103ad6 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -1,6 +1,6 @@ mod debug; mod math; -pub mod mem; +mod mem; mod render; mod shapes; mod state; @@ -177,33 +177,20 @@ 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_stops(ptr: *mut RawStopData, n_stops: i32) { +pub extern "C" fn add_shape_fill_stops(ptr: *mut shapes::RawStopData, n_stops: u32) { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); + if let Some(shape) = state.current_shape() { + let len = n_stops as usize; + let buffer_size = std::mem::size_of::() * len; + 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::(), - ); + let buffer = Vec::::from_raw_parts(ptr, len, len); + shape + .add_gradient_stops(buffer) + .expect("could not add gradient stops"); + mem::free(ptr as *mut u8, buffer_size); } } } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index c967a4bfe..b60712d8a 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -92,14 +92,16 @@ impl Shape { self.fills.clear(); } - pub fn add_gradient_stop(&mut self, color: skia::Color, offset: f32) -> Result<(), String> { + pub fn add_gradient_stops(&mut self, buffer: Vec) -> Result<(), String> { let fill = self.fills.last_mut().ok_or("Shape has no fills")?; let gradient = match fill { Fill::LinearGradient(g) => Ok(g), _ => Err("Active fill is not a gradient"), }?; - gradient.add_stop(color, offset); + for stop in buffer.into_iter() { + gradient.add_stop(stop.color(), stop.offset()); + } Ok(()) } diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index 0641979e8..fea83a8cc 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -4,6 +4,23 @@ use super::Color; use crate::math; use uuid::Uuid; +#[derive(Debug)] +#[repr(C)] +pub struct RawStopData { + color: [u8; 4], + offset: u8, +} + +impl RawStopData { + pub fn color(&self) -> skia::Color { + skia::Color::from_argb(self.color[3], self.color[0], self.color[1], self.color[2]) + } + + pub fn offset(&self) -> f32 { + self.offset as f32 / 100.0 + } +} + #[derive(Debug, Clone, PartialEq)] pub struct Gradient { colors: Vec, From 58c4e53ee4969192a6bc445c27b5cd9e40ca0093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Tue, 3 Dec 2024 14:34:07 +0100 Subject: [PATCH 2/4] :recycle: Refactor image fills --- render-wasm/src/main.rs | 24 +++++++++--------------- render-wasm/src/render.rs | 18 +++++++++++++++--- render-wasm/src/shapes/fills.rs | 24 +++++++++++++++++------- render-wasm/src/shapes/images.rs | 6 +++--- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 964103ad6..804c6f0a4 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -9,7 +9,6 @@ mod view; use skia_safe as skia; -use crate::shapes::Image; use crate::state::State; use crate::utils::uuid_from_u32_quartet; @@ -201,18 +200,15 @@ pub extern "C" fn store_image(a: u32, b: u32, c: u32, d: u32, ptr: *mut u8, size panic!("Invalid data, null pointer or zero size"); } let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); - let render_state = state.render_state(); let id = uuid_from_u32_quartet(a, b, c, d); + unsafe { let image_bytes = Vec::::from_raw_parts(ptr, size as usize, size as usize); - let image_data = skia::Data::new_copy(&*image_bytes); - match Image::from_encoded(image_data) { - Some(image) => { - render_state.images.insert(id.to_string(), image); - } - None => { - eprintln!("Error on image decoding"); + match state.render_state().add_image(id, &image_bytes) { + Err(msg) => { + eprintln!("{}", msg); } + _ => {} } mem::free(ptr as *mut u8, size as usize * std::mem::size_of::()); } @@ -221,9 +217,8 @@ pub extern "C" fn store_image(a: u32, b: u32, c: u32, d: u32, ptr: *mut u8, size #[no_mangle] pub extern "C" fn is_image_cached(a: u32, b: u32, c: u32, d: u32) -> bool { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); - let render_state = state.render_state(); let id = uuid_from_u32_quartet(a, b, c, d); - render_state.images.contains_key(&id.to_string()) + state.render_state().has_image(&id) } #[no_mangle] @@ -233,8 +228,8 @@ pub extern "C" fn add_shape_image_fill( c: u32, d: u32, alpha: f32, - width: f32, - height: f32, + width: i32, + height: i32, ) { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); let id = uuid_from_u32_quartet(a, b, c, d); @@ -242,8 +237,7 @@ pub extern "C" fn add_shape_image_fill( shape.add_fill(shapes::Fill::new_image_fill( id, (alpha * 0xff as f32).floor() as u8, - height, - width, + (width, height), )); } } diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 8ca2ac935..c895b0285 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -98,7 +98,7 @@ pub(crate) struct RenderState { pub cached_surface_image: Option, options: RenderOptions, pub viewbox: Viewbox, - pub images: HashMap, + images: HashMap, } impl RenderState { @@ -125,6 +125,18 @@ impl RenderState { } } + pub fn add_image(&mut self, id: Uuid, image_data: &[u8]) -> Result<(), String> { + let image_data = skia::Data::new_copy(image_data); + let image = Image::from_encoded(image_data).ok_or("Error decoding image data")?; + + self.images.insert(id, image); + Ok(()) + } + + pub fn has_image(&mut self, id: &Uuid) -> bool { + self.images.contains_key(id) + } + pub fn set_debug_flags(&mut self, debug: u32) { self.options.debug_flags = debug; } @@ -214,12 +226,12 @@ impl RenderState { for fill in shape.fills().rev() { if let Fill::Image(image_fill) = fill { - let image = self.images.get(&image_fill.id.to_string()); + let image = self.images.get(&image_fill.id()); if let Some(image) = image { draw_image_in_container( &self.drawing_surface.canvas(), &image, - (image_fill.width, image_fill.height), + image_fill.size(), shape.selrect, &fill.to_paint(&shape.selrect), ); diff --git a/render-wasm/src/shapes/fills.rs b/render-wasm/src/shapes/fills.rs index fea83a8cc..095b80bee 100644 --- a/render-wasm/src/shapes/fills.rs +++ b/render-wasm/src/shapes/fills.rs @@ -60,10 +60,20 @@ impl Gradient { #[derive(Debug, Clone, PartialEq)] pub struct ImageFill { - pub id: Uuid, - pub alpha: u8, - pub height: f32, - pub width: f32, + id: Uuid, + opacity: u8, + height: i32, + width: i32, +} + +impl ImageFill { + pub fn size(&self) -> (i32, i32) { + (self.width, self.height) + } + + pub fn id(&self) -> Uuid { + self.id + } } #[derive(Debug, Clone, PartialEq)] @@ -84,10 +94,10 @@ impl Fill { }) } - pub fn new_image_fill(id: Uuid, alpha: u8, height: f32, width: f32) -> Self { + pub fn new_image_fill(id: Uuid, opacity: u8, (width, height): (i32, i32)) -> Self { Self::Image(ImageFill { id, - alpha, + opacity, height, width, }) @@ -116,7 +126,7 @@ impl Fill { p.set_style(skia::PaintStyle::Fill); p.set_anti_alias(true); p.set_blend_mode(skia::BlendMode::SrcOver); - p.set_alpha(image_fill.alpha); + p.set_alpha(image_fill.opacity); p } } diff --git a/render-wasm/src/shapes/images.rs b/render-wasm/src/shapes/images.rs index 7ca2a7fc1..d073d4b7e 100644 --- a/render-wasm/src/shapes/images.rs +++ b/render-wasm/src/shapes/images.rs @@ -6,12 +6,12 @@ pub type Image = skia::Image; pub fn draw_image_in_container( canvas: &skia::Canvas, image: &Image, - size: (f32, f32), + size: (i32, i32), container: skia::Rect, paint: &skia::Paint, ) { - let width = size.0; - let height = size.1; + let width = size.0 as f32; + let height = size.1 as f32; let image_aspect_ratio = width / height; // Container size From df6727f1861de1fc92c91149c78392a12da91690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Tue, 3 Dec 2024 14:43:07 +0100 Subject: [PATCH 3/4] :recycle: Refactor rendering fills --- render-wasm/src/render.rs | 41 +++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index c895b0285..15db8de67 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -5,9 +5,8 @@ use std::collections::HashMap; use uuid::Uuid; use crate::debug; -use crate::shapes::Fill; -use crate::shapes::Shape; -use crate::shapes::{draw_image_in_container, Image}; +use crate::math::Rect; +use crate::shapes::{draw_image_in_container, Fill, Image, Shape}; use crate::view::Viewbox; struct GpuState { @@ -225,22 +224,7 @@ impl RenderState { self.drawing_surface.canvas().concat(&matrix); for fill in shape.fills().rev() { - if let Fill::Image(image_fill) = fill { - let image = self.images.get(&image_fill.id()); - if let Some(image) = image { - draw_image_in_container( - &self.drawing_surface.canvas(), - &image, - image_fill.size(), - shape.selrect, - &fill.to_paint(&shape.selrect), - ); - } - } else { - self.drawing_surface - .canvas() - .draw_rect(shape.selrect, &fill.to_paint(&shape.selrect)); - } + self.render_fill(fill, shape.selrect); } let mut paint = skia::Paint::default(); @@ -297,6 +281,25 @@ impl RenderState { self.flush(); } + fn render_fill(&mut self, fill: &Fill, selrect: Rect) { + if let Fill::Image(image_fill) = fill { + let image = self.images.get(&image_fill.id()); + if let Some(image) = image { + draw_image_in_container( + &self.drawing_surface.canvas(), + &image, + image_fill.size(), + selrect, + &fill.to_paint(&selrect), + ); + } + } else { + self.drawing_surface + .canvas() + .draw_rect(selrect, &fill.to_paint(&selrect)); + } + } + fn render_all_from_cache(&mut self) -> Result<(), String> { self.reset_canvas(); From 6623963a7f7a78d4f012bc1e830a320f4c2f1d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Mon, 2 Dec 2024 17:35:49 +0100 Subject: [PATCH 4/4] :sparkles: Use a static Vec to handle shared memory --- frontend/src/app/render_wasm/api.cljs | 1 - render-wasm/src/main.rs | 13 +++++------- render-wasm/src/mem.rs | 30 +++++++++++++++++---------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 33b32fd57..8737fe2cd 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -132,7 +132,6 @@ (aget buffer 1) (aget buffer 2) (aget buffer 3) - image-ptr image-size) true)))))) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 804c6f0a4..f7f082a95 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -182,35 +182,32 @@ pub extern "C" fn add_shape_fill_stops(ptr: *mut shapes::RawStopData, n_stops: u if let Some(shape) = state.current_shape() { let len = n_stops as usize; - let buffer_size = std::mem::size_of::() * len; unsafe { let buffer = Vec::::from_raw_parts(ptr, len, len); shape .add_gradient_stops(buffer) .expect("could not add gradient stops"); - mem::free(ptr as *mut u8, buffer_size); + mem::free_bytes(); } } } #[no_mangle] -pub extern "C" fn store_image(a: u32, b: u32, c: u32, d: u32, ptr: *mut u8, size: u32) { - if ptr.is_null() || size == 0 { - panic!("Invalid data, null pointer or zero size"); - } +pub extern "C" fn store_image(a: u32, b: u32, c: u32, d: u32, size: u32) { let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer"); let id = uuid_from_u32_quartet(a, b, c, d); unsafe { - let image_bytes = Vec::::from_raw_parts(ptr, size as usize, size as usize); + let image_bytes = + Vec::::from_raw_parts(mem::buffer_ptr(), size as usize, size as usize); match state.render_state().add_image(id, &image_bytes) { Err(msg) => { eprintln!("{}", msg); } _ => {} } - mem::free(ptr as *mut u8, size as usize * std::mem::size_of::()); + mem::free_bytes(); } } diff --git a/render-wasm/src/mem.rs b/render-wasm/src/mem.rs index fecd40758..a23010df2 100644 --- a/render-wasm/src/mem.rs +++ b/render-wasm/src/mem.rs @@ -1,17 +1,25 @@ +static mut BUFFERU8: Option>> = None; + #[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); + // TODO: Figure out how to deal with Result from Emscripten + if unsafe { BUFFERU8.is_some() } { + panic!("Bytes already allocated"); + } + + let mut buffer = Box::new(Vec::::with_capacity(len)); + let ptr = buffer.as_mut_ptr(); + + unsafe { BUFFERU8 = Some(buffer) }; return ptr; } -pub fn free(ptr: *mut u8, len: usize) { - unsafe { - let buf = Vec::::from_raw_parts(ptr, len, len); - std::mem::forget(buf); - } +pub fn free_bytes() { + let buffer = unsafe { BUFFERU8.take() }.expect("uninitialized buffer"); + std::mem::drop(buffer); +} + +pub fn buffer_ptr() -> *mut u8 { + let buffer = unsafe { BUFFERU8.as_mut() }.expect("uninitializied buffer"); + buffer.as_mut_ptr() }