Improve tile rendering updating

This commit is contained in:
alonso.torres 2025-11-04 14:33:04 +01:00
parent bb65782d08
commit a230d2fcf6
18 changed files with 299 additions and 209 deletions

View File

@ -58,7 +58,9 @@
([{:keys [id points selrect] :as shape} content] ([{:keys [id points selrect] :as shape} content]
(wasm.api/use-shape id) (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) (let [dimension (wasm.api/get-text-dimensions)
resize-v (gpt/point resize-v (gpt/point
(/ (:width dimension) (-> selrect :width)) (/ (:width dimension) (-> selrect :width))

View File

@ -98,7 +98,6 @@
(doseq [id ids] (doseq [id ids]
(wasm.api/use-shape id) (wasm.api/use-shape id)
(wasm.api/set-shape-blend-mode value) (wasm.api/set-shape-blend-mode value)
(wasm.api/update-shape-tiles)
(wasm.api/request-render "preview-blend-mode"))) (wasm.api/request-render "preview-blend-mode")))
(st/emit! (dw/trigger-bounding-box-cloaking ids)) (st/emit! (dw/trigger-bounding-box-cloaking ids))

View File

@ -311,7 +311,6 @@
(wasm.api/set-shape-text-content edition content) (wasm.api/set-shape-text-content edition content)
(let [dimension (wasm.api/get-text-dimensions)] (let [dimension (wasm.api/get-text-dimensions)]
(st/emit! (dwt/resize-text-editor edition dimension)) (st/emit! (dwt/resize-text-editor edition dimension))
(wasm.api/clear-drawing-cache)
(wasm.api/request-render "content")))))) (wasm.api/request-render "content"))))))
(mf/with-effect [vport] (mf/with-effect [vport]

View File

@ -10,6 +10,7 @@
["react-dom/server" :as rds] ["react-dom/server" :as rds]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.math :as mth] [app.common.math :as mth]
[app.common.types.fills :as types.fills] [app.common.types.fills :as types.fills]
[app.common.types.fills.impl :as types.fills.impl] [app.common.types.fills.impl :as types.fills.impl]
@ -81,13 +82,19 @@
;; This should never be called from the outside. ;; This should never be called from the outside.
(defn- render (defn- render
[timestamp] [timestamp]
(h/call wasm/internal-module "_render" timestamp) (when wasm/context-initialized?
(set! wasm/internal-frame-id nil) (h/call wasm/internal-module "_render" timestamp)
;; emit custom event (set! wasm/internal-frame-id nil)
(let [event (js/CustomEvent. "wasm:render")] ;; emit custom event
(js/document.dispatchEvent ^js 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)) (defonce pending-render (atom false))
@ -290,13 +297,15 @@
(fetch-image shape-id id thumbnail?))))) (fetch-image shape-id id thumbnail?)))))
(defn set-shape-text-images (defn set-shape-text-images
[shape-id content thumbnail?] ([shape-id content]
(let [paragraph-set (first (get content :children)) (set-shape-text-images shape-id content false))
paragraphs (get paragraph-set :children)] ([shape-id content thumbnail?]
(->> paragraphs (let [paragraph-set (first (get content :children))
(mapcat :children) paragraphs (get paragraph-set :children)]
(mapcat get-fill-images) (->> paragraphs
(map #(process-fill-image shape-id % thumbnail?))))) (mapcat :children)
(mapcat get-fill-images)
(map #(process-fill-image shape-id % thumbnail?))))))
(defn set-shape-fills (defn set-shape-fills
[shape-id fills thumbnail?] [shape-id fills thumbnail?]
@ -734,12 +743,6 @@
result))))) 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 (defn set-shape-grow-type
[grow-type] [grow-type]
(h/call wasm/internal-module "_set_shape_grow_type" (sr/translate-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 (defn set-view-box
[zoom vbox] [zoom vbox]
(h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) (h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox)))
(h/call wasm/internal-module "_render_from_cache") (set-view-render))
(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"))
(defn set-object (defn set-object
[objects shape] [objects shape]
@ -850,42 +846,47 @@
(set-shape-selrect selrect) (set-shape-selrect selrect)
(let [pending_thumbnails (into [] (concat (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-fills id fills true)
(set-shape-strokes id strokes true))) (set-shape-strokes id strokes true)))
pending_full (into [] (concat 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-fills id fills false)
(set-shape-strokes id strokes false)))] (set-shape-strokes id strokes false)))]
(perf/end-measure "set-object") (perf/end-measure "set-object")
{:thumbnails pending_thumbnails {:thumbnails pending_thumbnails
:full pending_full}))) :full pending_full})))
(defn update-text-layouts
[shapes]
(->> shapes
(filter cfh/text-shape?)
(map :id)
(run! f/update-text-layout)))
(defn process-pending (defn process-pending
[thumbnails full] [shapes thumbnails full]
(let [event (js/CustomEvent. "wasm:set-objects-finished") (let [event (js/CustomEvent. "wasm:set-objects-finished")
pending-thumbnails (-> (d/index-by :key :callback thumbnails) vals) pending-thumbnails (-> (d/index-by :key :callback thumbnails) vals)
pending-full (-> (d/index-by :key :callback full) vals)] pending-full (-> (d/index-by :key :callback full) vals)]
(->> (rx/from pending-thumbnails) (->> (rx/concat
(rx/merge-map (fn [callback] (callback))) (->> (rx/from pending-thumbnails)
(rx/reduce conj []) (rx/merge-map (fn [callback] (callback)))
(rx/merge-map (fn [_] (rx/reduce conj [])
(clear-drawing-cache) (rx/tap #(.dispatchEvent ^js js/document event)))
(request-render "pending-thumbnails-finished") (->> (rx/from pending-full)
(h/call wasm/internal-module "_update_shape_text_layout_for_all") (rx/mapcat (fn [callback] (callback)))
(.dispatchEvent ^js js/document event) (rx/reduce conj [])))
;; After thumbnails are done, process full images (rx/subs!
(rx/from pending-full))) (fn [_]
(rx/merge-map (fn [callback] (callback))) (update-text-layouts shapes)
(rx/reduce conj []) (request-render "pending-finished"))))))
(rx/subs! (fn [_]
(clear-drawing-cache)
(request-render "pending-full-finished"))))))
(defn process-object (defn process-object
[shape] [shape]
(let [{:keys [thumbnails full]} (set-object [] shape)] (let [{:keys [thumbnails full]} (set-object [] shape)]
(process-pending thumbnails full))) (process-pending [shape] thumbnails full)))
(defn set-objects (defn set-objects
[objects] [objects]
@ -903,12 +904,11 @@
(into full-acc full))) (into full-acc full)))
{:thumbnails thumbnails-acc :full full-acc}))] {:thumbnails thumbnails-acc :full full-acc}))]
(perf/end-measure "set-objects") (perf/end-measure "set-objects")
(process-pending thumbnails full))) (process-pending shapes thumbnails full)))
(defn clear-focus-mode (defn clear-focus-mode
[] []
(h/call wasm/internal-module "_clear_focus_mode") (h/call wasm/internal-module "_clear_focus_mode")
(clear-drawing-cache)
(request-render "clear-focus-mode")) (request-render "clear-focus-mode"))
(defn set-focus-mode (defn set-focus-mode
@ -924,7 +924,6 @@
entries) entries)
(h/call wasm/internal-module "_set_focus_mode") (h/call wasm/internal-module "_set_focus_mode")
(clear-drawing-cache)
(request-render "set-focus-mode")))) (request-render "set-focus-mode"))))
(defn set-structure-modifiers (defn set-structure-modifiers

View File

@ -76,6 +76,15 @@
(let [variant (font-db-data font-id font-variant-id)] (let [variant (font-db-data font-id font-variant-id)]
(:ttf-url variant)))) (: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. ;; IMPORTANT: It should be noted that only TTF fonts can be stored.
(defn- store-font-buffer (defn- store-font-buffer
[shape-id font-data font-array-buffer emoji? fallback?] [shape-id font-data font-array-buffer emoji? fallback?]
@ -100,11 +109,7 @@
emoji? emoji?
fallback?) fallback?)
(h/call wasm/internal-module "_update_shape_text_layout_for" (update-text-layout shape-id)
(aget shape-id-buffer 0)
(aget shape-id-buffer 1)
(aget shape-id-buffer 2)
(aget shape-id-buffer 3))
true)) true))

View File

@ -187,7 +187,8 @@
(api/set-shape-svg-raw-content (api/get-static-markup shape)) (api/set-shape-svg-raw-content (api/get-static-markup shape))
(= (:type shape) :text) (= (: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 :grow-type
(api/set-shape-grow-type v) (api/set-shape-grow-type v)
@ -256,11 +257,7 @@
[objects shape-changes] [objects shape-changes]
(->> (rx/from shape-changes) (->> (rx/from shape-changes)
(rx/mapcat (fn [[shape-id props]] (process-shape! (get objects shape-id) props))) (rx/mapcat (fn [[shape-id props]] (process-shape! (get objects shape-id) props)))
(rx/subs! (rx/subs! #(api/request-render "set-wasm-attrs"))))
(fn [_]
(when wasm/context-initialized?
(api/update-shape-tiles)
(api/request-render "set-wasm-attrs"))))))
;; `conj` empty set initialization ;; `conj` empty set initialization
(def conj* (fnil conj #{})) (def conj* (fnil conj #{}))

View File

@ -7,7 +7,8 @@
(ns app.util.functions (ns app.util.functions
"A functions helpers" "A functions helpers"
(:require (: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 ;; NOTE: this is needed because depending on the type of the build and
;; target execution evironment (browser, esm), the real export can be ;; target execution evironment (browser, esm), the real export can be
@ -29,3 +30,9 @@
(debounce f 0)) (debounce f 0))
([f timeout] ([f timeout]
(ext-debounce f timeout #{:leading false :trailing true}))) (ext-debounce f timeout #{:leading false :trailing true})))
(defn throttle
([f]
(throttle-fn 0 f))
([f timeout]
(throttle-fn timeout f)))

View File

@ -198,7 +198,7 @@
(defn ^:export dump-object (defn ^:export dump-object
[name] [name]
(get-object @st/state name)) (clj->js (get-object @st/state name)))
(defn get-selected (defn get-selected
[state] [state]

View File

@ -58,6 +58,9 @@ macro_rules! with_current_shape_mut {
STATE.as_mut() STATE.as_mut()
} }
.expect("Got an invalid state pointer"); .expect("Got an invalid state pointer");
$state.touch_current();
if let Some($shape) = $state.current_shape_mut() { if let Some($shape) = $state.current_shape_mut() {
$block $block
} }
@ -103,17 +106,16 @@ pub extern "C" fn init(width: i32, height: i32) {
#[no_mangle] #[no_mangle]
pub extern "C" fn clean_up() { 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 } unsafe { STATE = None }
mem::free_bytes(); mem::free_bytes();
} }
#[no_mangle]
pub extern "C" fn clear_drawing_cache() {
with_state_mut!(state, {
state.rebuild_tiles();
});
}
#[no_mangle] #[no_mangle]
pub extern "C" fn set_render_options(debug: u32, dpr: f32) { pub extern "C" fn set_render_options(debug: u32, dpr: f32) {
with_state_mut!(state, { with_state_mut!(state, {
@ -128,13 +130,14 @@ pub extern "C" fn set_canvas_background(raw_color: u32) {
with_state_mut!(state, { with_state_mut!(state, {
let color = skia::Color::new(raw_color); let color = skia::Color::new(raw_color);
state.set_background_color(color); state.set_background_color(color);
state.rebuild_tiles(); state.rebuild_tiles_shallow();
}); });
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn render(_: i32) { pub extern "C" fn render(_: i32) {
with_state_mut!(state, { with_state_mut!(state, {
state.rebuild_touched_tiles();
state state
.start_render_loop(performance::get_time()) .start_render_loop(performance::get_time())
.expect("Error rendering"); .expect("Error rendering");
@ -189,15 +192,20 @@ pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) {
with_state_mut!(state, { with_state_mut!(state, {
let render_state = state.render_state_mut(); let render_state = state.render_state_mut();
render_state.viewbox.set_all(zoom, x, y); render_state.viewbox.set_all(zoom, x, y);
with_state_mut!(state, { state.render_from_cache();
// We can have renders in progress });
state.render_state.cancel_animation_frame(); }
if state.render_state.options.is_profile_rebuild_tiles() {
state.rebuild_tiles(); #[no_mangle]
} else { pub extern "C" fn set_view_end() {
state.rebuild_tiles_shallow(); 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() { fn main() {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
init_gl!(); init_gl!();

View File

@ -238,6 +238,7 @@ pub(crate) struct RenderState {
pub nested_blurs: Vec<Option<Blur>>, // FIXME: why is this an option? pub nested_blurs: Vec<Option<Blur>>, // FIXME: why is this an option?
pub show_grid: Option<Uuid>, pub show_grid: Option<Uuid>,
pub focus_mode: FocusMode, pub focus_mode: FocusMode,
pub touched_ids: HashSet<Uuid>,
} }
pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize { pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize {
@ -307,6 +308,7 @@ impl RenderState {
nested_blurs: vec![], nested_blurs: vec![],
show_grid: None, show_grid: None,
focus_mode: FocusMode::new(), focus_mode: FocusMode::new(),
touched_ids: HashSet::default(),
} }
} }
@ -849,7 +851,8 @@ impl RenderState {
// debug::render_debug_tiles_for_viewbox(self); // debug::render_debug_tiles_for_viewbox(self);
performance::begin_measure!("tile_cache"); 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"); performance::end_measure!("tile_cache");
self.pending_nodes.clear(); self.pending_nodes.clear();
@ -1174,10 +1177,9 @@ impl RenderState {
node_render_state.id node_render_state.id
))?; ))?;
// If the shape is not in the tile set, then we update // If the shape is not in the tile set, then we add them.
// it.
if self.tiles.get_tiles_of(node_id).is_none() { 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 { if visited_children {
@ -1431,6 +1433,7 @@ impl RenderState {
performance::begin_measure!("render_shape_tree::uncached"); performance::begin_measure!("render_shape_tree::uncached");
let (is_empty, early_return) = let (is_empty, early_return) =
self.render_shape_tree_partial_uncached(tree, timestamp)?; self.render_shape_tree_partial_uncached(tree, timestamp)?;
if early_return { if early_return {
return Ok(()); return Ok(());
} }
@ -1513,50 +1516,77 @@ impl RenderState {
Ok(()) 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 { pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: ShapesPoolRef) -> TileRect {
let extrect = shape.extrect(tree); let extrect = shape.extrect(tree);
let tile_size = tiles::get_tile_size(self.get_scale()); let tile_size = tiles::get_tile_size(self.get_scale());
tiles::get_tiles_for_rect(extrect, tile_size) 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<tiles::Tile> {
let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree); let TileRect(rsx, rsy, rex, rey) = self.get_tiles_for_shape(shape, tree);
let old_tiles: HashSet<tiles::Tile> = self
let old_tiles = self
.tiles .tiles
.get_tiles_of(shape.id) .get_tiles_of(shape.id)
.map_or(HashSet::new(), |tiles| tiles.iter().cloned().collect()); .map_or(Vec::new(), |tiles| tiles.iter().copied().collect());
let new_tiles: HashSet<tiles::Tile> = (rsx..=rex)
.flat_map(|x| (rsy..=rey).map(move |y| tiles::Tile(x, y))) let new_tiles = (rsx..=rex).flat_map(|x| (rsy..=rey).map(move |y| tiles::Tile(x, y)));
.collect();
let mut result = HashSet::<tiles::Tile>::new();
// First, remove the shape from all tiles where it was previously located // First, remove the shape from all tiles where it was previously located
for tile in old_tiles { 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 // Then, add the shape to the new tiles
for tile in new_tiles { for tile in new_tiles {
self.remove_cached_tile_shape(tile, shape.id);
self.tiles.add_shape_at(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<tiles::Tile> {
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); let rect = self.get_aligned_tile_bounds(tile);
self.surfaces self.surfaces
.remove_cached_tile_surface(tile, rect, self.background_color); .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) { pub fn rebuild_tiles_shallow(&mut self, tree: ShapesPoolRef) {
performance::begin_measure!("rebuild_tiles_shallow"); performance::begin_measure!("rebuild_tiles_shallow");
self.tiles.invalidate();
self.surfaces.remove_cached_tiles(self.background_color); let mut all_tiles = HashSet::<tiles::Tile>::new();
let mut nodes = vec![Uuid::nil()]; let mut nodes = vec![Uuid::nil()];
while let Some(shape_id) = nodes.pop() { while let Some(shape_id) = nodes.pop() {
if let Some(shape) = tree.get(&shape_id) { if let Some(shape) = tree.get(&shape_id) {
if shape_id != Uuid::nil() { if shape_id != Uuid::nil() {
self.update_tile_for(shape, tree); all_tiles.extend(self.update_shape_tiles(shape, tree));
} else { } else {
// We only need to rebuild tiles from the first level. // We only need to rebuild tiles from the first level.
for child_id in shape.children_ids_iter(false) { 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"); performance::end_measure!("rebuild_tiles_shallow");
} }
pub fn rebuild_tiles(&mut self, tree: ShapesPoolRef) { pub fn rebuild_tiles(&mut self, tree: ShapesPoolRef) {
performance::begin_measure!("rebuild_tiles"); performance::begin_measure!("rebuild_tiles");
self.tiles.invalidate(); self.tiles.invalidate();
self.surfaces.remove_cached_tiles(self.background_color);
let mut all_tiles = HashSet::<tiles::Tile>::new();
let mut nodes = vec![Uuid::nil()]; let mut nodes = vec![Uuid::nil()];
while let Some(shape_id) = nodes.pop() { while let Some(shape_id) = nodes.pop() {
if let Some(shape) = tree.get(&shape_id) { if let Some(shape) = tree.get(&shape_id) {
if shape_id != Uuid::nil() { 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) { 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"); 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::<tiles::Tile>::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 /// Invalidates extended rectangles and updates tiles for a set of shapes
/// ///
/// This function takes a set of shape IDs and for each one: /// 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, /// 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). /// regardless of their relationship to other shapes (e.g., ancestors, descendants, or any other collection).
pub fn invalidate_and_update_tiles( pub fn update_tiles_shapes(&mut self, shape_ids: &IndexSet<Uuid>, tree: ShapesPoolMutRef<'_>) {
&mut self, performance::begin_measure!("invalidate_and_update_tiles");
shape_ids: &IndexSet<Uuid>, let mut all_tiles = HashSet::<tiles::Tile>::new();
tree: ShapesPoolMutRef<'_>,
) {
for shape_id in shape_ids { for shape_id in shape_ids {
if let Some(shape) = tree.get(shape_id) { if let Some(shape) = tree.get(shape_id) {
if !shape.id.is_nil() { all_tiles.extend(self.update_shape_tiles(shape, tree));
self.update_tile_for(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 /// 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. /// This is crucial for frames and groups that contain transformed children.
pub fn rebuild_modifier_tiles(&mut self, tree: ShapesPoolMutRef<'_>, ids: Vec<Uuid>) { pub fn rebuild_modifier_tiles(&mut self, tree: ShapesPoolMutRef<'_>, ids: Vec<Uuid>) {
let ancestors = all_with_ancestors(&ids, tree, false); 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 { pub fn get_scale(&self) -> f32 {
@ -1627,4 +1704,12 @@ impl RenderState {
pub fn get_cached_scale(&self) -> f32 { pub fn get_cached_scale(&self) -> f32 {
self.cached_viewbox.zoom() * self.options.dpr() 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();
}
} }

View File

@ -62,6 +62,41 @@ pub struct ImageStore {
context: Box<DirectContext>, context: Box<DirectContext>,
} }
// Decode and upload to GPU
fn decode_image(context: &mut Box<DirectContext>, raw_data: &[u8]) -> Option<Image> {
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 { impl ImageStore {
pub fn new(context: DirectContext) -> Self { pub fn new(context: DirectContext) -> Self {
Self { Self {
@ -77,8 +112,13 @@ impl ImageStore {
return Err("Image already exists".to_string()); return Err("Image already exists".to_string());
} }
self.images let raw_data = image_data.to_vec();
.insert(key, StoredImage::Raw(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(()) Ok(())
} }
@ -103,48 +143,9 @@ impl ImageStore {
match entry { match entry {
StoredImage::Gpu(ref img) => Some(img), StoredImage::Gpu(ref img) => Some(img),
StoredImage::Raw(raw_data) => { StoredImage::Raw(raw_data) => {
// Decode and upload to GPU let gpu_image = decode_image(&mut self.context, raw_data)?;
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
*entry = StoredImage::Gpu(gpu_image); *entry = StoredImage::Gpu(gpu_image);
if let StoredImage::Gpu(ref img) = entry { if let StoredImage::Gpu(ref img) = entry {
Some(img) Some(img)
} else { } else {

View File

@ -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) 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) self.grid.contains_key(&tile)
} }

View File

@ -105,7 +105,8 @@ impl<'a> State<'a> {
for x in rsx..=rex { for x in rsx..=rex {
for y in rsy..=rey { for y in rsy..=rey {
let tile = tiles::Tile(x, y); 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) { pub fn rebuild_tiles_shallow(&mut self) {
self.render_state.rebuild_tiles_shallow(&self.shapes); self.render_state.rebuild_tiles_shallow(&self.shapes);
} }
@ -170,6 +154,10 @@ impl<'a> State<'a> {
self.render_state.rebuild_tiles(&self.shapes); 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<Uuid>) { pub fn rebuild_modifier_tiles(&mut self, ids: Vec<Uuid>) {
// SAFETY: We're extending the lifetime of the mutable borrow to 'a. // SAFETY: We're extending the lifetime of the mutable borrow to 'a.
// This is safe because: // This is safe because:
@ -215,4 +203,14 @@ impl<'a> State<'a> {
pub fn set_modifiers(&mut self, modifiers: HashMap<Uuid, skia::Matrix>) { pub fn set_modifiers(&mut self, modifiers: HashMap<Uuid, skia::Matrix>) {
self.shapes.set_modifiers(modifiers); 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);
}
} }

View File

@ -265,6 +265,7 @@ impl<'a> ShapesPoolImpl<'a> {
self.shapes.iter() self.shapes.iter()
} }
#[allow(dead_code)]
pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> { pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Shape> {
self.shapes.iter_mut() self.shapes.iter_mut()
} }

View File

@ -1,3 +1,4 @@
use crate::render::Surfaces;
use crate::uuid::Uuid; use crate::uuid::Uuid;
use crate::view::Viewbox; use crate::view::Viewbox;
use skia_safe as skia; 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(); self.list.clear();
let columns = tile_viewbox.interest_rect.width(); let columns = tile_viewbox.interest_rect.width();
@ -225,6 +226,17 @@ impl PendingTiles {
current += 1; current += 1;
} }
self.list.reverse(); 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<Tile> { pub fn pop(&mut self) -> Option<Tile> {

View File

@ -84,10 +84,7 @@ pub extern "C" fn store_image() {
{ {
eprintln!("{}", msg); eprintln!("{}", msg);
} }
}); state.touch_shape(ids.shape_id);
with_state_mut!(state, {
state.update_tile_for_shape(ids.shape_id);
}); });
mem::free_bytes(); mem::free_bytes();

View File

@ -57,11 +57,9 @@ pub extern "C" fn store_font(
.add(family, &font_bytes, is_emoji, is_fallback); .add(family, &font_bytes, is_emoji, is_fallback);
mem::free_bytes(); mem::free_bytes();
});
with_state_mut!(state, {
let shape_id = uuid_from_u32_quartet(a1, b1, c1, d1); let shape_id = uuid_from_u32_quartet(a1, b1, c1, d1);
state.update_tile_for_shape(shape_id); state.touch_shape(shape_id);
}); });
} }

View File

@ -4,7 +4,7 @@ use super::{fills::RawFillData, fonts::RawFontStyle};
use crate::math::{Matrix, Point}; use crate::math::{Matrix, Point};
use crate::mem; use crate::mem;
use crate::shapes::{ 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::utils::{uuid_from_u32, uuid_from_u32_quartet};
use crate::{with_current_shape_mut, with_state_mut, with_state_mut_current_shape, STATE}; 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 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] #[no_mangle]
pub extern "C" fn update_shape_text_layout() { pub extern "C" fn update_shape_text_layout() {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
shape.invalidate_extrect(); update_text_layout(shape);
if let Type::Text(text_content) = &mut shape.shape_type {
text_content.update_layout(shape.selrect);
}
}); });
} }
@ -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, { with_state_mut!(state, {
let shape_id = uuid_from_u32_quartet(a, b, c, d); let shape_id = uuid_from_u32_quartet(a, b, c, d);
if let Some(shape) = state.shapes.get_mut(&shape_id) { if let Some(shape) = state.shapes.get_mut(&shape_id) {
shape.invalidate_extrect(); update_text_layout(shape);
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);
}
} }
}); });
} }