diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index e5fc20de1d..6fca2a7a63 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -58,7 +58,9 @@ ([{:keys [id points selrect] :as shape} content] (wasm.api/use-shape id) - (wasm.api/set-shape-text id content false) + (wasm.api/set-shape-text-content id content) + (wasm.api/set-shape-text-images id content) + (let [dimension (wasm.api/get-text-dimensions) resize-v (gpt/point (/ (:width dimension) (-> selrect :width)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs index a98913a86c..5060b8cc83 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs @@ -98,7 +98,6 @@ (doseq [id ids] (wasm.api/use-shape id) (wasm.api/set-shape-blend-mode value) - (wasm.api/update-shape-tiles) (wasm.api/request-render "preview-blend-mode"))) (st/emit! (dw/trigger-bounding-box-cloaking ids)) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 8872ce9304..ed7167b9f8 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -311,7 +311,6 @@ (wasm.api/set-shape-text-content edition content) (let [dimension (wasm.api/get-text-dimensions)] (st/emit! (dwt/resize-text-editor edition dimension)) - (wasm.api/clear-drawing-cache) (wasm.api/request-render "content")))))) (mf/with-effect [vport] diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 904405ea1d..16a78edab0 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -10,6 +10,7 @@ ["react-dom/server" :as rds] [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.files.helpers :as cfh] [app.common.math :as mth] [app.common.types.fills :as types.fills] [app.common.types.fills.impl :as types.fills.impl] @@ -81,13 +82,19 @@ ;; This should never be called from the outside. (defn- render [timestamp] - (h/call wasm/internal-module "_render" timestamp) - (set! wasm/internal-frame-id nil) - ;; emit custom event - (let [event (js/CustomEvent. "wasm:render")] - (js/document.dispatchEvent ^js event))) + (when wasm/context-initialized? + (h/call wasm/internal-module "_render" timestamp) + (set! wasm/internal-frame-id nil) + ;; emit custom event + (let [event (js/CustomEvent. "wasm:render")] + (js/document.dispatchEvent ^js event)))) -(def debounce-render (fns/debounce render 100)) +(def set-view-render + (fns/debounce + (fn [ts] + (h/call wasm/internal-module "_set_view_end") + (render ts)) + 200)) (defonce pending-render (atom false)) @@ -290,13 +297,15 @@ (fetch-image shape-id id thumbnail?))))) (defn set-shape-text-images - [shape-id content thumbnail?] - (let [paragraph-set (first (get content :children)) - paragraphs (get paragraph-set :children)] - (->> paragraphs - (mapcat :children) - (mapcat get-fill-images) - (map #(process-fill-image shape-id % thumbnail?))))) + ([shape-id content] + (set-shape-text-images shape-id content false)) + ([shape-id content thumbnail?] + (let [paragraph-set (first (get content :children)) + paragraphs (get paragraph-set :children)] + (->> paragraphs + (mapcat :children) + (mapcat get-fill-images) + (map #(process-fill-image shape-id % thumbnail?)))))) (defn set-shape-fills [shape-id fills thumbnail?] @@ -734,12 +743,6 @@ result))))) -(defn set-shape-text - [shape-id content thumbnail?] - (concat - (set-shape-text-images shape-id content thumbnail?) - (set-shape-text-content shape-id content))) - (defn set-shape-grow-type [grow-type] (h/call wasm/internal-module "_set_shape_grow_type" (sr/translate-grow-type grow-type))) @@ -761,14 +764,7 @@ (defn set-view-box [zoom vbox] (h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) - (h/call wasm/internal-module "_render_from_cache") - (debounce-render)) - -(defn clear-drawing-cache [] - (h/call wasm/internal-module "_clear_drawing_cache")) - -(defn update-shape-tiles [] - (h/call wasm/internal-module "_update_shape_tiles")) + (set-view-render)) (defn set-object [objects shape] @@ -850,42 +846,47 @@ (set-shape-selrect selrect) (let [pending_thumbnails (into [] (concat - (set-shape-text id content true) + (set-shape-text-content id content) + (set-shape-text-images id content true) (set-shape-fills id fills true) (set-shape-strokes id strokes true))) pending_full (into [] (concat - (set-shape-text id content false) + (set-shape-text-images id content false) (set-shape-fills id fills false) (set-shape-strokes id strokes false)))] (perf/end-measure "set-object") {:thumbnails pending_thumbnails :full pending_full}))) +(defn update-text-layouts + [shapes] + (->> shapes + (filter cfh/text-shape?) + (map :id) + (run! f/update-text-layout))) + (defn process-pending - [thumbnails full] + [shapes thumbnails full] (let [event (js/CustomEvent. "wasm:set-objects-finished") pending-thumbnails (-> (d/index-by :key :callback thumbnails) vals) pending-full (-> (d/index-by :key :callback full) vals)] - (->> (rx/from pending-thumbnails) - (rx/merge-map (fn [callback] (callback))) - (rx/reduce conj []) - (rx/merge-map (fn [_] - (clear-drawing-cache) - (request-render "pending-thumbnails-finished") - (h/call wasm/internal-module "_update_shape_text_layout_for_all") - (.dispatchEvent ^js js/document event) - ;; After thumbnails are done, process full images - (rx/from pending-full))) - (rx/merge-map (fn [callback] (callback))) - (rx/reduce conj []) - (rx/subs! (fn [_] - (clear-drawing-cache) - (request-render "pending-full-finished")))))) + (->> (rx/concat + (->> (rx/from pending-thumbnails) + (rx/merge-map (fn [callback] (callback))) + (rx/reduce conj []) + (rx/tap #(.dispatchEvent ^js js/document event))) + (->> (rx/from pending-full) + (rx/mapcat (fn [callback] (callback))) + (rx/reduce conj []))) + (rx/subs! + (fn [_] + (update-text-layouts shapes) + (request-render "pending-finished")))))) (defn process-object [shape] (let [{:keys [thumbnails full]} (set-object [] shape)] - (process-pending thumbnails full))) + (process-pending [shape] thumbnails full))) (defn set-objects [objects] @@ -903,12 +904,11 @@ (into full-acc full))) {:thumbnails thumbnails-acc :full full-acc}))] (perf/end-measure "set-objects") - (process-pending thumbnails full))) + (process-pending shapes thumbnails full))) (defn clear-focus-mode [] (h/call wasm/internal-module "_clear_focus_mode") - (clear-drawing-cache) (request-render "clear-focus-mode")) (defn set-focus-mode @@ -924,7 +924,6 @@ entries) (h/call wasm/internal-module "_set_focus_mode") - (clear-drawing-cache) (request-render "set-focus-mode")))) (defn set-structure-modifiers diff --git a/frontend/src/app/render_wasm/api/fonts.cljs b/frontend/src/app/render_wasm/api/fonts.cljs index c6e1f668d8..c8b18edc5e 100644 --- a/frontend/src/app/render_wasm/api/fonts.cljs +++ b/frontend/src/app/render_wasm/api/fonts.cljs @@ -76,6 +76,15 @@ (let [variant (font-db-data font-id font-variant-id)] (:ttf-url variant)))) +(defn update-text-layout + [id] + (let [shape-id-buffer (uuid/get-u32 id)] + (h/call wasm/internal-module "_update_shape_text_layout_for" + (aget shape-id-buffer 0) + (aget shape-id-buffer 1) + (aget shape-id-buffer 2) + (aget shape-id-buffer 3)))) + ;; IMPORTANT: It should be noted that only TTF fonts can be stored. (defn- store-font-buffer [shape-id font-data font-array-buffer emoji? fallback?] @@ -100,11 +109,7 @@ emoji? fallback?) - (h/call wasm/internal-module "_update_shape_text_layout_for" - (aget shape-id-buffer 0) - (aget shape-id-buffer 1) - (aget shape-id-buffer 2) - (aget shape-id-buffer 3)) + (update-text-layout shape-id) true)) diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index 2fe1c6f034..c2e27e408d 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -187,7 +187,8 @@ (api/set-shape-svg-raw-content (api/get-static-markup shape)) (= (:type shape) :text) - (api/set-shape-text id v false)) + (do (api/set-shape-text-content id v) + (api/set-shape-text-images id v))) :grow-type (api/set-shape-grow-type v) @@ -256,11 +257,7 @@ [objects shape-changes] (->> (rx/from shape-changes) (rx/mapcat (fn [[shape-id props]] (process-shape! (get objects shape-id) props))) - (rx/subs! - (fn [_] - (when wasm/context-initialized? - (api/update-shape-tiles) - (api/request-render "set-wasm-attrs")))))) + (rx/subs! #(api/request-render "set-wasm-attrs")))) ;; `conj` empty set initialization (def conj* (fnil conj #{})) diff --git a/frontend/src/app/util/functions.cljs b/frontend/src/app/util/functions.cljs index 2398e371a4..7b073f6bff 100644 --- a/frontend/src/app/util/functions.cljs +++ b/frontend/src/app/util/functions.cljs @@ -7,7 +7,8 @@ (ns app.util.functions "A functions helpers" (:require - ["lodash/debounce.js" :as lodash-debounce])) + ["lodash/debounce.js" :as lodash-debounce] + [app.util.rxops :refer [throttle-fn]])) ;; NOTE: this is needed because depending on the type of the build and ;; target execution evironment (browser, esm), the real export can be @@ -29,3 +30,9 @@ (debounce f 0)) ([f timeout] (ext-debounce f timeout #{:leading false :trailing true}))) + +(defn throttle + ([f] + (throttle-fn 0 f)) + ([f timeout] + (throttle-fn timeout f))) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index b87b4220ca..7f55f10a46 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -198,7 +198,7 @@ (defn ^:export dump-object [name] - (get-object @st/state name)) + (clj->js (get-object @st/state name))) (defn get-selected [state] diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index 38e447e07a..2d1b35bdfc 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -58,6 +58,9 @@ macro_rules! with_current_shape_mut { STATE.as_mut() } .expect("Got an invalid state pointer"); + + $state.touch_current(); + if let Some($shape) = $state.current_shape_mut() { $block } @@ -103,17 +106,16 @@ pub extern "C" fn init(width: i32, height: i32) { #[no_mangle] pub extern "C" fn clean_up() { + with_state_mut!(state, { + // Cancel the current animation frame if it exists so + // it won't try to render without context + let render_state = state.render_state_mut(); + render_state.cancel_animation_frame(); + }); unsafe { STATE = None } mem::free_bytes(); } -#[no_mangle] -pub extern "C" fn clear_drawing_cache() { - with_state_mut!(state, { - state.rebuild_tiles(); - }); -} - #[no_mangle] pub extern "C" fn set_render_options(debug: u32, dpr: f32) { with_state_mut!(state, { @@ -128,13 +130,14 @@ pub extern "C" fn set_canvas_background(raw_color: u32) { with_state_mut!(state, { let color = skia::Color::new(raw_color); state.set_background_color(color); - state.rebuild_tiles(); + state.rebuild_tiles_shallow(); }); } #[no_mangle] pub extern "C" fn render(_: i32) { with_state_mut!(state, { + state.rebuild_touched_tiles(); state .start_render_loop(performance::get_time()) .expect("Error rendering"); @@ -189,15 +192,20 @@ pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) { with_state_mut!(state, { let render_state = state.render_state_mut(); render_state.viewbox.set_all(zoom, x, y); - with_state_mut!(state, { - // We can have renders in progress - state.render_state.cancel_animation_frame(); - if state.render_state.options.is_profile_rebuild_tiles() { - state.rebuild_tiles(); - } else { - state.rebuild_tiles_shallow(); - } - }); + state.render_from_cache(); + }); +} + +#[no_mangle] +pub extern "C" fn set_view_end() { + with_state_mut!(state, { + // We can have renders in progress + state.render_state.cancel_animation_frame(); + if state.render_state.options.is_profile_rebuild_tiles() { + state.rebuild_tiles(); + } else { + state.rebuild_tiles_shallow(); + } }); } @@ -616,13 +624,6 @@ pub extern "C" fn set_modifiers() { }); } -#[no_mangle] -pub extern "C" fn update_shape_tiles() { - with_state_mut!(state, { - state.update_tile_for_current_shape(); - }); -} - fn main() { #[cfg(target_arch = "wasm32")] init_gl!(); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index d76c2a75eb..0c9cea0e6a 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -238,6 +238,7 @@ pub(crate) struct RenderState { pub nested_blurs: Vec>, // FIXME: why is this an option? pub show_grid: Option, pub focus_mode: FocusMode, + pub touched_ids: HashSet, } pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize { @@ -307,6 +308,7 @@ impl RenderState { nested_blurs: vec![], show_grid: None, focus_mode: FocusMode::new(), + touched_ids: HashSet::default(), } } @@ -849,7 +851,8 @@ impl RenderState { // debug::render_debug_tiles_for_viewbox(self); performance::begin_measure!("tile_cache"); - self.pending_tiles.update(&self.tile_viewbox); + self.pending_tiles + .update(&self.tile_viewbox, &self.surfaces); performance::end_measure!("tile_cache"); self.pending_nodes.clear(); @@ -1174,10 +1177,9 @@ impl RenderState { node_render_state.id ))?; - // If the shape is not in the tile set, then we update - // it. + // If the shape is not in the tile set, then we add them. if self.tiles.get_tiles_of(node_id).is_none() { - self.update_tile_for(element, tree); + self.add_shape_tiles(element, tree); } if visited_children { @@ -1431,6 +1433,7 @@ impl RenderState { performance::begin_measure!("render_shape_tree::uncached"); let (is_empty, early_return) = self.render_shape_tree_partial_uncached(tree, timestamp)?; + if early_return { return Ok(()); } @@ -1513,50 +1516,77 @@ impl RenderState { Ok(()) } + /* + * Given a shape returns the TileRect with the range of tiles that the shape is in + */ pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: ShapesPoolRef) -> TileRect { let extrect = shape.extrect(tree); let tile_size = tiles::get_tile_size(self.get_scale()); tiles::get_tiles_for_rect(extrect, tile_size) } - pub fn update_tile_for(&mut self, shape: &Shape, tree: ShapesPoolRef) { + /* + * Given a shape, check the indexes and update it's location in the tile set + * returns the tiles that have changed in the process. + */ + pub fn update_shape_tiles(&mut self, shape: &Shape, tree: ShapesPoolRef) -> Vec { let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree); - let old_tiles: HashSet = self + + let old_tiles = self .tiles .get_tiles_of(shape.id) - .map_or(HashSet::new(), |tiles| tiles.iter().cloned().collect()); - let new_tiles: HashSet = (rsx..=rex) - .flat_map(|x| (rsy..=rey).map(move |y| tiles::Tile(x, y))) - .collect(); + .map_or(Vec::new(), |tiles| tiles.iter().copied().collect()); + + let new_tiles = (rsx..=rex).flat_map(|x| (rsy..=rey).map(move |y| tiles::Tile(x, y))); + + let mut result = HashSet::::new(); // First, remove the shape from all tiles where it was previously located for tile in old_tiles { - self.remove_cached_tile_shape(tile, shape.id); + self.tiles.remove_shape_at(tile, shape.id); + result.insert(tile); } // Then, add the shape to the new tiles for tile in new_tiles { - self.remove_cached_tile_shape(tile, shape.id); self.tiles.add_shape_at(tile, shape.id); + result.insert(tile); } + + result.iter().copied().collect() } - pub fn remove_cached_tile_shape(&mut self, tile: tiles::Tile, id: Uuid) { + /* + * Add the tiles forthe shape to the index. + * returns the tiles that have been updated + */ + pub fn add_shape_tiles(&mut self, shape: &Shape, tree: ShapesPoolRef) -> Vec { + let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree); + let tiles: Vec<_> = (rsx..=rex) + .flat_map(|x| (rsy..=rey).map(move |y| tiles::Tile(x, y))) + .collect(); + + for tile in tiles.iter() { + self.tiles.add_shape_at(*tile, shape.id); + } + tiles + } + + pub fn remove_cached_tile(&mut self, tile: tiles::Tile) { let rect = self.get_aligned_tile_bounds(tile); self.surfaces .remove_cached_tile_surface(tile, rect, self.background_color); - self.tiles.remove_shape_at(tile, id); } pub fn rebuild_tiles_shallow(&mut self, tree: ShapesPoolRef) { performance::begin_measure!("rebuild_tiles_shallow"); - self.tiles.invalidate(); - self.surfaces.remove_cached_tiles(self.background_color); + + let mut all_tiles = HashSet::::new(); let mut nodes = vec![Uuid::nil()]; while let Some(shape_id) = nodes.pop() { if let Some(shape) = tree.get(&shape_id) { if shape_id != Uuid::nil() { - self.update_tile_for(shape, tree); + all_tiles.extend(self.update_shape_tiles(shape, tree)); } else { // We only need to rebuild tiles from the first level. for child_id in shape.children_ids_iter(false) { @@ -1565,18 +1595,29 @@ impl RenderState { } } } + + // Update the changed tiles + self.surfaces.remove_cached_tiles(self.background_color); + for tile in all_tiles { + self.remove_cached_tile(tile); + } + performance::end_measure!("rebuild_tiles_shallow"); } pub fn rebuild_tiles(&mut self, tree: ShapesPoolRef) { performance::begin_measure!("rebuild_tiles"); + self.tiles.invalidate(); - self.surfaces.remove_cached_tiles(self.background_color); + + let mut all_tiles = HashSet::::new(); let mut nodes = vec![Uuid::nil()]; + while let Some(shape_id) = nodes.pop() { if let Some(shape) = tree.get(&shape_id) { if shape_id != Uuid::nil() { - self.update_tile_for(shape, tree); + // We have invalidated the tiles so we only need to add the shape + all_tiles.extend(self.add_shape_tiles(shape, tree)); } for child_id in shape.children_ids_iter(false) { @@ -1584,9 +1625,45 @@ impl RenderState { } } } + + // Update the changed tiles + self.surfaces.remove_cached_tiles(self.background_color); + for tile in all_tiles { + self.remove_cached_tile(tile); + } + performance::end_measure!("rebuild_tiles"); } + /* + * Rebuild the tiles for the shapes that have been modified from the + * last time this was executed. + */ + pub fn rebuild_touched_tiles(&mut self, tree: ShapesPoolRef) { + performance::begin_measure!("rebuild_touched_tiles"); + + let mut all_tiles = HashSet::::new(); + + let ids = self.touched_ids.clone(); + + for shape_id in ids.iter() { + if let Some(shape) = tree.get(shape_id) { + if shape_id != &Uuid::nil() { + all_tiles.extend(self.update_shape_tiles(shape, tree)); + } + } + } + + // Update the changed tiles + for tile in all_tiles { + self.remove_cached_tile(tile); + } + + self.clean_touched(); + + performance::end_measure!("rebuild_touched_tiles"); + } + /// Invalidates extended rectangles and updates tiles for a set of shapes /// /// This function takes a set of shape IDs and for each one: @@ -1595,18 +1672,18 @@ impl RenderState { /// /// This is useful when you have a pre-computed set of shape IDs that need to be refreshed, /// regardless of their relationship to other shapes (e.g., ancestors, descendants, or any other collection). - pub fn invalidate_and_update_tiles( - &mut self, - shape_ids: &IndexSet, - tree: ShapesPoolMutRef<'_>, - ) { + pub fn update_tiles_shapes(&mut self, shape_ids: &IndexSet, tree: ShapesPoolMutRef<'_>) { + performance::begin_measure!("invalidate_and_update_tiles"); + let mut all_tiles = HashSet::::new(); for shape_id in shape_ids { if let Some(shape) = tree.get(shape_id) { - if !shape.id.is_nil() { - self.update_tile_for(shape, tree); - } + all_tiles.extend(self.update_shape_tiles(shape, tree)); } } + for tile in all_tiles { + self.remove_cached_tile(tile); + } + performance::end_measure!("invalidate_and_update_tiles"); } /// Rebuilds tiles for shapes with modifiers and processes their ancestors @@ -1617,7 +1694,7 @@ impl RenderState { /// This is crucial for frames and groups that contain transformed children. pub fn rebuild_modifier_tiles(&mut self, tree: ShapesPoolMutRef<'_>, ids: Vec) { let ancestors = all_with_ancestors(&ids, tree, false); - self.invalidate_and_update_tiles(&ancestors, tree); + self.update_tiles_shapes(&ancestors, tree); } pub fn get_scale(&self) -> f32 { @@ -1627,4 +1704,12 @@ impl RenderState { pub fn get_cached_scale(&self) -> f32 { self.cached_viewbox.zoom() * self.options.dpr() } + + pub fn mark_touched(&mut self, uuid: Uuid) { + self.touched_ids.insert(uuid); + } + + pub fn clean_touched(&mut self) { + self.touched_ids.clear(); + } } diff --git a/render-wasm/src/render/images.rs b/render-wasm/src/render/images.rs index 57b7675312..63f06aead7 100644 --- a/render-wasm/src/render/images.rs +++ b/render-wasm/src/render/images.rs @@ -62,6 +62,41 @@ pub struct ImageStore { context: Box, } +// Decode and upload to GPU +fn decode_image(context: &mut Box, raw_data: &[u8]) -> Option { + let data = unsafe { skia::Data::new_bytes(raw_data) }; + let codec = Codec::from_data(&data)?; + let image = Image::from_encoded(&data)?; + + let mut dimensions = codec.dimensions(); + if codec.origin().swaps_width_height() { + dimensions.width = codec.dimensions().height; + dimensions.height = codec.dimensions().width; + } + + let image_info = skia::ImageInfo::new_n32_premul(dimensions, None); + + let mut surface = surfaces::render_target( + context, + Budgeted::Yes, + &image_info, + None, + None, + None, + true, + false, + )?; + + let dest_rect: MathRect = + MathRect::from_xywh(0.0, 0.0, dimensions.width as f32, dimensions.height as f32); + + surface + .canvas() + .draw_image_rect(&image, None, dest_rect, &skia::Paint::default()); + + Some(surface.image_snapshot()) +} + impl ImageStore { pub fn new(context: DirectContext) -> Self { Self { @@ -77,8 +112,13 @@ impl ImageStore { return Err("Image already exists".to_string()); } - self.images - .insert(key, StoredImage::Raw(image_data.to_vec())); + let raw_data = image_data.to_vec(); + + if let Some(gpu_image) = decode_image(&mut self.context, &raw_data) { + self.images.insert(key, StoredImage::Gpu(gpu_image)); + } else { + self.images.insert(key, StoredImage::Raw(raw_data)); + } Ok(()) } @@ -103,48 +143,9 @@ impl ImageStore { 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 codec = Codec::from_data(data.clone())?; - let image = Image::from_encoded(data.clone())?; - - let mut dimensions = codec.dimensions(); - if codec.origin().swaps_width_height() { - dimensions.width = codec.dimensions().height; - dimensions.height = codec.dimensions().width; - } - - let image_info = skia::ImageInfo::new_n32_premul(dimensions, None); - - let mut surface = surfaces::render_target( - &mut self.context, - Budgeted::Yes, - &image_info, - None, - None, - None, - true, - false, - )?; - - let dest_rect: MathRect = MathRect::from_xywh( - 0.0, - 0.0, - dimensions.width as f32, - dimensions.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 + let gpu_image = decode_image(&mut self.context, raw_data)?; *entry = StoredImage::Gpu(gpu_image); + if let StoredImage::Gpu(ref img) = entry { Some(img) } else { diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index a3e6b8cc12..acb23ececc 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -318,7 +318,7 @@ impl Surfaces { } } - pub fn has_cached_tile_surface(&mut self, tile: Tile) -> bool { + pub fn has_cached_tile_surface(&self, tile: Tile) -> bool { self.tiles.has(tile) } @@ -365,7 +365,7 @@ impl TileTextureCache { } } - pub fn has(&mut self, tile: Tile) -> bool { + pub fn has(&self, tile: Tile) -> bool { self.grid.contains_key(&tile) } diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 7cf42d3c74..57de6b6ca2 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -105,7 +105,8 @@ impl<'a> State<'a> { for x in rsx..=rex { for y in rsy..=rey { let tile = tiles::Tile(x, y); - self.render_state.remove_cached_tile_shape(tile, id); + self.render_state.remove_cached_tile(tile); + self.render_state.tiles.remove_shape_at(tile, shape.id); } } } @@ -145,23 +146,6 @@ impl<'a> State<'a> { } } - pub fn update_tile_for_shape(&mut self, shape_id: Uuid) { - if let Some(shape) = self.shapes.get(&shape_id) { - self.render_state.update_tile_for(shape, &self.shapes); - } - } - - pub fn update_tile_for_current_shape(&mut self) { - let Some(shape) = self.current_shape() else { - panic!("Invalid current shape") - }; - // TODO: Remove this clone - if !shape.id.is_nil() { - self.render_state - .update_tile_for(&shape.clone(), &self.shapes); - } - } - pub fn rebuild_tiles_shallow(&mut self) { self.render_state.rebuild_tiles_shallow(&self.shapes); } @@ -170,6 +154,10 @@ impl<'a> State<'a> { self.render_state.rebuild_tiles(&self.shapes); } + pub fn rebuild_touched_tiles(&mut self) { + self.render_state.rebuild_touched_tiles(&self.shapes); + } + pub fn rebuild_modifier_tiles(&mut self, ids: Vec) { // SAFETY: We're extending the lifetime of the mutable borrow to 'a. // This is safe because: @@ -215,4 +203,14 @@ impl<'a> State<'a> { pub fn set_modifiers(&mut self, modifiers: HashMap) { self.shapes.set_modifiers(modifiers); } + + pub fn touch_current(&mut self) { + if let Some(current_id) = self.current_id { + self.render_state.mark_touched(current_id); + } + } + + pub fn touch_shape(&mut self, id: Uuid) { + self.render_state.mark_touched(id); + } } diff --git a/render-wasm/src/state/shapes_pool.rs b/render-wasm/src/state/shapes_pool.rs index e8537c292c..cdbc8c3caa 100644 --- a/render-wasm/src/state/shapes_pool.rs +++ b/render-wasm/src/state/shapes_pool.rs @@ -265,6 +265,7 @@ impl<'a> ShapesPoolImpl<'a> { self.shapes.iter() } + #[allow(dead_code)] pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> { self.shapes.iter_mut() } diff --git a/render-wasm/src/tiles.rs b/render-wasm/src/tiles.rs index 5f9a0dd98b..be7b056c26 100644 --- a/render-wasm/src/tiles.rs +++ b/render-wasm/src/tiles.rs @@ -1,3 +1,4 @@ +use crate::render::Surfaces; use crate::uuid::Uuid; use crate::view::Viewbox; use skia_safe as skia; @@ -175,7 +176,7 @@ impl PendingTiles { } } - pub fn update(&mut self, tile_viewbox: &TileViewbox) { + pub fn update(&mut self, tile_viewbox: &TileViewbox, surfaces: &Surfaces) { self.list.clear(); let columns = tile_viewbox.interest_rect.width(); @@ -225,6 +226,17 @@ impl PendingTiles { current += 1; } self.list.reverse(); + + // Create a new list where the cached tiles go first + let iter1 = self + .list + .iter() + .filter(|t| surfaces.has_cached_tile_surface(**t)); + let iter2 = self + .list + .iter() + .filter(|t| !surfaces.has_cached_tile_surface(**t)); + self.list = iter1.chain(iter2).copied().collect(); } pub fn pop(&mut self) -> Option { diff --git a/render-wasm/src/wasm/fills/image.rs b/render-wasm/src/wasm/fills/image.rs index ea1fc2267a..fd3e2e1376 100644 --- a/render-wasm/src/wasm/fills/image.rs +++ b/render-wasm/src/wasm/fills/image.rs @@ -84,10 +84,7 @@ pub extern "C" fn store_image() { { eprintln!("{}", msg); } - }); - - with_state_mut!(state, { - state.update_tile_for_shape(ids.shape_id); + state.touch_shape(ids.shape_id); }); mem::free_bytes(); diff --git a/render-wasm/src/wasm/fonts.rs b/render-wasm/src/wasm/fonts.rs index bdc85ac124..43d8d5e3f7 100644 --- a/render-wasm/src/wasm/fonts.rs +++ b/render-wasm/src/wasm/fonts.rs @@ -57,11 +57,9 @@ pub extern "C" fn store_font( .add(family, &font_bytes, is_emoji, is_fallback); mem::free_bytes(); - }); - with_state_mut!(state, { let shape_id = uuid_from_u32_quartet(a1, b1, c1, d1); - state.update_tile_for_shape(shape_id); + state.touch_shape(shape_id); }); } diff --git a/render-wasm/src/wasm/text.rs b/render-wasm/src/wasm/text.rs index 15f2e3fc79..315f92d03a 100644 --- a/render-wasm/src/wasm/text.rs +++ b/render-wasm/src/wasm/text.rs @@ -4,7 +4,7 @@ use super::{fills::RawFillData, fonts::RawFontStyle}; use crate::math::{Matrix, Point}; use crate::mem; use crate::shapes::{ - self, GrowType, TextAlign, TextDecoration, TextDirection, TextTransform, Type, + self, GrowType, Shape, TextAlign, TextDecoration, TextDirection, TextTransform, Type, }; use crate::utils::{uuid_from_u32, uuid_from_u32_quartet}; use crate::{with_current_shape_mut, with_state_mut, with_state_mut_current_shape, STATE}; @@ -329,13 +329,17 @@ pub extern "C" fn get_text_dimensions() -> *mut u8 { ptr } +fn update_text_layout(shape: &mut Shape) { + if let Type::Text(text_content) = &mut shape.shape_type { + text_content.update_layout(shape.selrect); + shape.invalidate_extrect(); + } +} + #[no_mangle] pub extern "C" fn update_shape_text_layout() { with_current_shape_mut!(state, |shape: &mut Shape| { - shape.invalidate_extrect(); - if let Type::Text(text_content) = &mut shape.shape_type { - text_content.update_layout(shape.selrect); - } + update_text_layout(shape); }); } @@ -344,22 +348,7 @@ pub extern "C" fn update_shape_text_layout_for(a: u32, b: u32, c: u32, d: u32) { with_state_mut!(state, { let shape_id = uuid_from_u32_quartet(a, b, c, d); if let Some(shape) = state.shapes.get_mut(&shape_id) { - shape.invalidate_extrect(); - if let Type::Text(text_content) = &mut shape.shape_type { - text_content.update_layout(shape.selrect); - } - } - }); -} - -#[no_mangle] -pub extern "C" fn update_shape_text_layout_for_all() { - with_state_mut!(state, { - for shape in state.shapes.iter_mut() { - shape.invalidate_extrect(); - if let Type::Text(text_content) = &mut shape.shape_type { - text_content.update_layout(shape.selrect); - } + update_text_layout(shape); } }); }