diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 8dc0d22210..6a56966829 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -57,7 +57,7 @@ ([{:keys [id points selrect] :as shape} content] (wasm.api/use-shape id) - (wasm.api/set-shape-text content) + (wasm.api/set-shape-text id content) (let [dimension (wasm.api/text-dimensions) resize-v (gpt/point diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 06e7434ebe..8b1eef6307 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -301,7 +301,7 @@ (ted/get-editor-current-content) (ted/export-content))] (wasm.api/use-shape edition) - (wasm.api/set-shape-text-content content) + (wasm.api/set-shape-text-content edition content) (let [dimension (wasm.api/text-dimensions)] (st/emit! (dwt/resize-text-editor edition dimension)) (wasm.api/clear-drawing-cache) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index d4d637899a..75e816b0aa 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -190,9 +190,10 @@ (defn- get-string-length [string] (+ (count string) 1)) (defn- fetch-image - [id] - (let [buffer (uuid/get-u32 id) - url (cf/resolve-file-media {:id id})] + [shape-id image-id] + (let [buffer-shape-id (uuid/get-u32 shape-id) + buffer-image-id (uuid/get-u32 image-id) + url (cf/resolve-file-media {:id image-id})] {:key url :callback #(->> (http/send! {:method :get :uri url @@ -206,10 +207,14 @@ data (js/Uint8Array. image)] (.set heap data offset) (h/call wasm/internal-module "_store_image" - (aget buffer 0) - (aget buffer 1) - (aget buffer 2) - (aget buffer 3)) + (aget buffer-shape-id 0) + (aget buffer-shape-id 1) + (aget buffer-shape-id 2) + (aget buffer-shape-id 3) + (aget buffer-image-id 0) + (aget buffer-image-id 1) + (aget buffer-image-id 2) + (aget buffer-image-id 3)) true))))})) (defn- get-fill-images @@ -217,7 +222,7 @@ (filter :fill-image (:fills leaf))) (defn- process-fill-image - [fill] + [shape-id fill] (rx/from (when-let [image (:fill-image fill)] (let [id (dm/get-prop image :id) @@ -228,19 +233,19 @@ (aget buffer 2) (aget buffer 3))] (when (zero? cached-image?) - (fetch-image id)))))) + (fetch-image shape-id id)))))) (defn set-shape-text-images - [content] + [shape-id content] (let [paragraph-set (first (get content :children)) paragraphs (get paragraph-set :children)] (->> paragraphs (mapcat :children) (mapcat get-fill-images) - (map process-fill-image)))) + (map #(process-fill-image shape-id %))))) (defn set-shape-fills - [fills] + [shape-id fills] (when (not-empty? fills) (let [fills (take types.fill/MAX-FILLS fills) image-fills (filter :fill-image fills) @@ -270,11 +275,11 @@ (aget buffer 2) (aget buffer 3))] (when (zero? cached-image?) - (fetch-image id)))) + (fetch-image shape-id id)))) image-fills)))) (defn set-shape-strokes - [strokes] + [shape-id strokes] (h/call wasm/internal-module "_clear_shape_strokes") (keep (fn [stroke] (let [opacity (or (:stroke-opacity stroke) 1.0) @@ -301,15 +306,15 @@ (h/call wasm/internal-module "_add_shape_stroke_fill")) (some? image) - (let [id (dm/get-prop image :id) - buffer (uuid/get-u32 id) + (let [image-id (dm/get-prop image :id) + buffer (uuid/get-u32 image-id) cached-image? (h/call wasm/internal-module "_is_image_cached" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))] - (sr-fills/write-image-fill! offset dview id opacity + (sr-fills/write-image-fill! offset dview image-id opacity (dm/get-prop image :width) (dm/get-prop image :height)) (h/call wasm/internal-module "_add_shape_stroke_fill") (when (== cached-image? 0) - (fetch-image id))) + (fetch-image shape-id image-id))) (some? color) (do @@ -623,7 +628,7 @@ (declare propagate-apply) (defn set-shape-text-content - [content] + [shape-id content] (h/call wasm/internal-module "_clear_shape_text") (let [paragraph-set (first (dm/get-prop content :children)) paragraphs (dm/get-prop paragraph-set :children) @@ -646,13 +651,13 @@ (-> fonts (cond-> @emoji? (f/add-emoji-font)) (f/add-noto-fonts @languages))] - (f/store-fonts updated-fonts)))) + (f/store-fonts shape-id updated-fonts)))) (defn set-shape-text - [content] + [shape-id content] (concat - (set-shape-text-images content) - (set-shape-text-content content))) + (set-shape-text-images shape-id content) + (set-shape-text-content shape-id content))) (defn set-shape-grow-type [grow-type] @@ -759,9 +764,9 @@ (set-grid-layout shape)) (let [pending (into [] (concat - (set-shape-text content) - (set-shape-fills fills) - (set-shape-strokes strokes)))] + (set-shape-text id content) + (set-shape-fills id fills) + (set-shape-strokes id strokes)))] (perf/end-measure "set-object") pending))) @@ -772,11 +777,10 @@ pending (-> (d/index-by :key :callback pending) vals)] (if (not-empty? pending) (->> (rx/from pending) - (rx/mapcat (fn [callback] (callback))) + (rx/merge-map (fn [callback] (callback))) + (rx/tap (fn [_] (request-render "set-objects"))) (rx/reduce conj []) (rx/subs! (fn [_] - (clear-drawing-cache) - (request-render "set-objects") (.dispatchEvent ^js js/document event)))) (.dispatchEvent ^js js/document event)))) diff --git a/frontend/src/app/render_wasm/api/fonts.cljs b/frontend/src/app/render_wasm/api/fonts.cljs index 4d3044f414..deed99ccf4 100644 --- a/frontend/src/app/render_wasm/api/fonts.cljs +++ b/frontend/src/app/render_wasm/api/fonts.cljs @@ -77,18 +77,23 @@ ;; IMPORTANT: It should be noted that only TTF fonts can be stored. (defn- store-font-buffer - [font-data font-array-buffer emoji? fallback?] - (let [id-buffer (:family-id-buffer font-data) + [shape-id font-data font-array-buffer emoji? fallback?] + (let [font-id-buffer (:family-id-buffer font-data) + shape-id-buffer (uuid/get-u32 shape-id) size (.-byteLength font-array-buffer) ptr (h/call wasm/internal-module "_alloc_bytes" size) heap (gobj/get ^js wasm/internal-module "HEAPU8") mem (js/Uint8Array. (.-buffer heap) ptr size)] (.set mem (js/Uint8Array. font-array-buffer)) (h/call wasm/internal-module "_store_font" - (aget id-buffer 0) - (aget id-buffer 1) - (aget id-buffer 2) - (aget id-buffer 3) + (aget shape-id-buffer 0) + (aget shape-id-buffer 1) + (aget shape-id-buffer 2) + (aget shape-id-buffer 3) + (aget font-id-buffer 0) + (aget font-id-buffer 1) + (aget font-id-buffer 2) + (aget font-id-buffer 3) (:weight font-data) (:style font-data) emoji? @@ -96,13 +101,13 @@ true)) (defn- fetch-font - [font-data font-url emoji? fallback?] + [shape-id font-data font-url emoji? fallback?] {:key font-url :callback #(->> (http/send! {:method :get :uri font-url :response-type :buffer}) (rx/map (fn [{:keys [body]}] - (store-font-buffer font-data body emoji? fallback?))))}) + (store-font-buffer shape-id font-data body emoji? fallback?))))}) (defn- google-font-ttf-url [font-id font-variant-id] @@ -122,7 +127,7 @@ (dm/str (u/join cf/public-uri "fonts/" asset-id)))) (defn- store-font-id - [font-data asset-id emoji? fallback?] + [shape-id font-data asset-id emoji? fallback?] (when asset-id (let [uri (font-id->ttf-url (:font-id font-data) asset-id (:font-variant-id font-data)) id-buffer (uuid/get-u32 (:wasm-id font-data)) @@ -136,7 +141,7 @@ (:style font-data) emoji?))] (when-not font-stored? - (fetch-font font-data uri emoji? fallback?))))) + (fetch-font shape-id font-data uri emoji? fallback?))))) (defn serialize-font-style [font-style] @@ -167,7 +172,7 @@ (js/Number font-weight)) (defn store-font - [font] + [shape-id font] (let [font-id (get font :font-id) font-variant-id (get font :font-variant-id) emoji? (get font :is-emoji false) @@ -184,11 +189,11 @@ :font-variant-id font-variant-id :style style :weight weight}] - (store-font-id font-data asset-id emoji? fallback?))) + (store-font-id shape-id font-data asset-id emoji? fallback?))) (defn store-fonts - [fonts] - (keep (fn [font] (store-font font)) fonts)) + [shape-id fonts] + (keep (fn [font] (store-font shape-id font)) fonts)) (defn add-emoji-font diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index 6a2beefd5d..97d24b75b4 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -112,7 +112,8 @@ (defn set-wasm-single-attr! [shape k] - (let [v (get shape k)] + (let [v (get shape k) + id (get shape :id)] (case k :parent-id (api/set-parent-id v) :type (api/set-shape-type v) @@ -123,8 +124,8 @@ (api/set-shape-clip-content false)) :rotation (api/set-shape-rotation v) :transform (api/set-shape-transform v) - :fills (into [] (api/set-shape-fills v)) - :strokes (into [] (api/set-shape-strokes v)) + :fills (into [] (api/set-shape-fills id v)) + :strokes (into [] (api/set-shape-strokes id v)) :blend-mode (api/set-shape-blend-mode v) :opacity (api/set-shape-opacity v) :hidden (api/set-shape-hidden v) @@ -158,7 +159,7 @@ (api/set-shape-svg-raw-content (api/get-static-markup shape)) (= (:type shape) :text) - (api/set-shape-text v)) + (api/set-shape-text id v)) :grow-type (api/set-shape-grow-type v) diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index ef6e1867f4..e3a486e62d 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -290,17 +290,31 @@ pub extern "C" fn set_children() { } #[no_mangle] -pub extern "C" fn store_image(a: u32, b: u32, c: u32, d: u32) { +pub extern "C" fn store_image( + a1: u32, + b1: u32, + c1: u32, + d1: u32, + a2: u32, + b2: u32, + c2: u32, + d2: u32, +) { with_state!(state, { - let id = uuid_from_u32_quartet(a, b, c, d); + let image_id = uuid_from_u32_quartet(a2, b2, c2, d2); let image_bytes = mem::bytes(); - if let Err(msg) = state.render_state().add_image(id, &image_bytes) { + if let Err(msg) = state.render_state().add_image(image_id, &image_bytes) { eprintln!("{}", msg); } mem::free_bytes(); }); + + with_state!(state, { + let shape_id = uuid_from_u32_quartet(a1, b1, c1, d1); + state.update_tile_for_shape(shape_id); + }); } #[no_mangle] diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 6dd12f982f..7afc51ec13 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -220,14 +220,14 @@ impl RenderState { let tiles = tiles::TileHashMap::new(); RenderState { - gpu_state, + gpu_state: gpu_state.clone(), options: RenderOptions::default(), surfaces, fonts, viewbox, cached_viewbox: Viewbox::new(0., 0.), cached_target_snapshot: None, - images: ImageStore::new(), + images: ImageStore::new(gpu_state.context.clone()), background_color: skia::Color::TRANSPARENT, render_request_id: None, render_in_progress: false, @@ -257,7 +257,7 @@ impl RenderState { } pub fn add_image(&mut self, id: Uuid, image_data: &[u8]) -> Result<(), String> { - self.images.add(id, image_data, &mut self.gpu_state.context) + self.images.add(id, image_data) } pub fn has_image(&mut self, id: &Uuid) -> bool { diff --git a/render-wasm/src/render/gpu_state.rs b/render-wasm/src/render/gpu_state.rs index 02b74f49d2..d4d90faaec 100644 --- a/render-wasm/src/render/gpu_state.rs +++ b/render-wasm/src/render/gpu_state.rs @@ -1,6 +1,7 @@ use skia_safe::gpu::{self, gl::FramebufferInfo, gl::TextureInfo, DirectContext}; use skia_safe::{self as skia, ISize}; +#[derive(Debug, Clone)] pub struct GpuState { pub context: DirectContext, framebuffer_info: FramebufferInfo, diff --git a/render-wasm/src/render/images.rs b/render-wasm/src/render/images.rs index 138fc4a7f8..9b26c222bd 100644 --- a/render-wasm/src/render/images.rs +++ b/render-wasm/src/render/images.rs @@ -7,60 +7,86 @@ use std::collections::HashMap; pub type Image = skia::Image; +enum StoredImage { + Raw(Vec), + Gpu(Image), +} + pub struct ImageStore { - images: HashMap, + images: HashMap, + context: Box, } impl ImageStore { - pub fn new() -> Self { + pub fn new(context: DirectContext) -> Self { Self { images: HashMap::with_capacity(2048), + context: Box::new(context), } } - pub fn add( - &mut self, - id: Uuid, - image_data: &[u8], - context: &mut DirectContext, - ) -> Result<(), String> { - let image_data = unsafe { skia::Data::new_bytes(image_data) }; - let image = Image::from_encoded(image_data).ok_or("Error decoding image data")?; + pub fn add(&mut self, id: Uuid, image_data: &[u8]) -> Result<(), String> { + if self.images.contains_key(&id) { + return Err("Image already exists".to_string()); + } - let width = image.width(); - let height = image.height(); - - let image_info = skia::ImageInfo::new_n32_premul((width, height), None); - let mut surface = surfaces::render_target( - context, - Budgeted::Yes, - &image_info, - None, - None, - None, - None, - false, - ) - .ok_or("Can't create GPU surface")?; - - let dest_rect = MathRect::from_xywh(0.0, 0.0, width as f32, height as f32); - - surface - .canvas() - .draw_image_rect(&image, None, dest_rect, &skia::Paint::default()); - - let gpu_image = surface.image_snapshot(); - - // This way we store the image as a texture - self.images.insert(id, gpu_image); + self.images + .insert(id, StoredImage::Raw(image_data.to_vec())); Ok(()) } - pub fn contains(&mut self, id: &Uuid) -> bool { + pub fn contains(&self, id: &Uuid) -> bool { self.images.contains_key(id) } - pub fn get(&self, id: &Uuid) -> Option<&Image> { - self.images.get(id) + pub fn get(&mut self, id: &Uuid) -> Option<&Image> { + // Use entry API to mutate the HashMap in-place if needed + if let Some(entry) = self.images.get_mut(id) { + match entry { + StoredImage::Gpu(ref img) => Some(img), + StoredImage::Raw(raw_data) => { + // Decode and upload to GPU + let data = unsafe { skia::Data::new_bytes(raw_data) }; + let image = Image::from_encoded(data)?; + + let width = image.width(); + let height = image.height(); + + let image_info = skia::ImageInfo::new_n32_premul((width, height), None); + + let mut surface = surfaces::render_target( + &mut self.context, + Budgeted::Yes, + &image_info, + None, + None, + None, + None, + false, + )?; + + let dest_rect = MathRect::from_xywh(0.0, 0.0, width as f32, height as f32); + + surface.canvas().draw_image_rect( + &image, + None, + dest_rect, + &skia::Paint::default(), + ); + + let gpu_image = surface.image_snapshot(); + + // Replace raw data with GPU image + *entry = StoredImage::Gpu(gpu_image); + if let StoredImage::Gpu(ref img) = entry { + Some(img) + } else { + None + } + } + } + } else { + None + } } } diff --git a/render-wasm/src/render/strokes.rs b/render-wasm/src/render/strokes.rs index 6ae3836a66..debdb1af6e 100644 --- a/render-wasm/src/render/strokes.rs +++ b/render-wasm/src/render/strokes.rs @@ -388,13 +388,13 @@ fn draw_image_stroke_in_container( image_fill: &ImageFill, antialias: bool, ) { + let scale = render_state.get_scale(); let image = render_state.images.get(&image_fill.id()); if image.is_none() { return; } let size = image_fill.size(); - let scale = render_state.get_scale(); let canvas = render_state.surfaces.canvas(SurfaceId::Strokes); let container = &shape.selrect; let path_transform = shape.to_path_transform(); diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index f8d617a26d..cdd5c5bf00 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -191,6 +191,12 @@ impl<'a> State<'a> { } } + pub fn update_tile_for_shape(&mut self, shape_id: Uuid) { + if let Some(shape) = self.shapes.get_mut(&shape_id) { + self.render_state.update_tile_for(shape); + } + } + pub fn update_tile_for_current_shape(&mut self) { match self.current_shape.as_mut() { Some(shape) => { diff --git a/render-wasm/src/wasm/fonts.rs b/render-wasm/src/wasm/fonts.rs index e3e9b3ed26..236d4229d1 100644 --- a/render-wasm/src/wasm/fonts.rs +++ b/render-wasm/src/wasm/fonts.rs @@ -7,17 +7,21 @@ use crate::shapes::FontFamily; #[no_mangle] pub extern "C" fn store_font( - a: u32, - b: u32, - c: u32, - d: u32, + a1: u32, + b1: u32, + c1: u32, + d1: u32, + a2: u32, + b2: u32, + c2: u32, + d2: u32, weight: u32, style: u8, is_emoji: bool, is_fallback: bool, ) { with_state!(state, { - let id = uuid_from_u32_quartet(a, b, c, d); + let id = uuid_from_u32_quartet(a2, b2, c2, d2); let font_bytes = mem::bytes(); let family = FontFamily::new(id, weight, style.into()); @@ -28,6 +32,11 @@ pub extern "C" fn store_font( mem::free_bytes(); }); + + with_state!(state, { + let shape_id = uuid_from_u32_quartet(a1, b1, c1, d1); + state.update_tile_for_shape(shape_id); + }); } #[no_mangle]