Merge pull request #7782 from penpot/alotor-wasm-thumbnails

 Render WASM dashboard thumbnails
This commit is contained in:
Aitor Moreno
2025-11-20 13:12:26 +01:00
committed by GitHub
11 changed files with 400 additions and 169 deletions

View File

@@ -96,7 +96,7 @@
;; loading all pages into memory for find the frame set for thumbnail. ;; loading all pages into memory for find the frame set for thumbnail.
(defn get-file-data-for-thumbnail (defn get-file-data-for-thumbnail
[{:keys [::db/conn] :as cfg} {:keys [data id] :as file}] [{:keys [::db/conn] :as cfg} {:keys [data id] :as file} strip-frames-with-thumbnails]
(letfn [;; function responsible on finding the frame marked to be (letfn [;; function responsible on finding the frame marked to be
;; used as thumbnail; the returned frame always have ;; used as thumbnail; the returned frame always have
;; the :page-id set to the page that it belongs. ;; the :page-id set to the page that it belongs.
@@ -173,7 +173,7 @@
;; Assoc the available thumbnails and prune not visible shapes ;; Assoc the available thumbnails and prune not visible shapes
;; for avoid transfer unnecessary data. ;; for avoid transfer unnecessary data.
:always strip-frames-with-thumbnails
(update :objects assoc-thumbnails page-id thumbs))))) (update :objects assoc-thumbnails page-id thumbs)))))
(def ^:private (def ^:private
@@ -186,7 +186,8 @@
[:map {:title "PartialFile"} [:map {:title "PartialFile"}
[:id ::sm/uuid] [:id ::sm/uuid]
[:revn {:min 0} ::sm/int] [:revn {:min 0} ::sm/int]
[:page [:map-of :keyword ::sm/any]]]) [:page [:map-of :keyword ::sm/any]]
[:strip-frames-with-thumbnails {:optional true} ::sm/boolean]])
(sv/defmethod ::get-file-data-for-thumbnail (sv/defmethod ::get-file-data-for-thumbnail
"Retrieves the data for generate the thumbnail of the file. Used "Retrieves the data for generate the thumbnail of the file. Used
@@ -195,7 +196,7 @@
::doc/module :files ::doc/module :files
::sm/params schema:get-file-data-for-thumbnail ::sm/params schema:get-file-data-for-thumbnail
::sm/result schema:partial-file} ::sm/result schema:partial-file}
[cfg {:keys [::rpc/profile-id file-id] :as params}] [cfg {:keys [::rpc/profile-id file-id strip-frames-with-thumbnails] :as params}]
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}] (db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
(files/check-read-permissions! conn profile-id file-id) (files/check-read-permissions! conn profile-id file-id)
@@ -205,14 +206,18 @@
file (bfc/get-file cfg file-id file (bfc/get-file cfg file-id
:realize? true :realize? true
:read-only? true)] :read-only? true)
strip-frames-with-thumbnails
(or (nil? strip-frames-with-thumbnails) ;; if not present, default to true
(true? strip-frames-with-thumbnails))]
(-> (cfeat/get-team-enabled-features cf/flags team) (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-file-features! (:features file))) (cfeat/check-file-features! (:features file)))
{:file-id file-id {:file-id file-id
:revn (:revn file) :revn (:revn file)
:page (get-file-data-for-thumbnail cfg file)})))) :page (get-file-data-for-thumbnail cfg file strip-frames-with-thumbnails)}))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MUTATION COMMANDS ;; MUTATION COMMANDS

View File

@@ -83,7 +83,7 @@
:source-map-detail-level :all}}} :source-map-detail-level :all}}}
:worker :worker
{:target :browser {:target :esm
:output-dir "resources/public/js/worker/" :output-dir "resources/public/js/worker/"
:asset-path "/js/worker" :asset-path "/js/worker"
:devtools {:browser-inject :main :devtools {:browser-inject :main

View File

@@ -67,7 +67,7 @@
:height (:height vbox) :height (:height vbox)
:fill color}]) :fill color}])
(defn- calculate-dimensions (defn calculate-dimensions
[objects aspect-ratio] [objects aspect-ratio]
(let [root-objects (ctst/get-root-objects objects)] (let [root-objects (ctst/get-root-objects objects)]
(if (empty? root-objects) (if (empty? root-objects)

View File

@@ -18,6 +18,7 @@
[app.main.data.notifications :as ntf] [app.main.data.notifications :as ntf]
[app.main.data.project :as dpj] [app.main.data.project :as dpj]
[app.main.data.team :as dtm] [app.main.data.team :as dtm]
[app.main.features :as features]
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.main.rasterizer :as thr] [app.main.rasterizer :as thr]
[app.main.refs :as refs] [app.main.refs :as refs]
@@ -46,6 +47,8 @@
(log/set-level! :debug) (log/set-level! :debug)
(def thumbnail-width 252)
;; --- Grid Item Thumbnail ;; --- Grid Item Thumbnail
(defn- persist-thumbnail (defn- persist-thumbnail
@@ -56,15 +59,22 @@
(defn render-thumbnail (defn render-thumbnail
[file-id revn] [file-id revn]
(if (features/active-feature? @st/state "render-wasm/v1")
(->> (mw/ask! {:cmd :thumbnails/generate-for-file-wasm
:revn revn
:file-id file-id
:width thumbnail-width}))
(->> (mw/ask! {:cmd :thumbnails/generate-for-file (->> (mw/ask! {:cmd :thumbnails/generate-for-file
:revn revn :revn revn
:file-id file-id}) :file-id file-id
(rx/mapcat (fn [{:keys [fonts] :as result}] :width thumbnail-width})
(rx/mapcat
(fn [{:keys [fonts] :as result}]
(->> (fonts/render-font-styles fonts) (->> (fonts/render-font-styles fonts)
(rx/map (fn [styles] (rx/map (fn [styles]
(assoc result (-> result
:styles styles (assoc :styles styles
:width 252)))))))) :width thumbnail-width))))))))))
(defn- ask-for-thumbnail (defn- ask-for-thumbnail
"Creates some hooks to handle the files thumbnails cache" "Creates some hooks to handle the files thumbnails cache"

View File

@@ -94,6 +94,23 @@
(set! wasm/internal-frame-id nil) (set! wasm/internal-frame-id nil)
(ug/dispatch! (ug/event "penpot:wasm:render")))) (ug/dispatch! (ug/event "penpot:wasm:render"))))
(defn render-sync
[]
(when wasm/context-initialized?
(h/call wasm/internal-module "_render_sync")
(set! wasm/internal-frame-id nil)))
(defn render-sync-shape
[id]
(when wasm/context-initialized?
(let [buffer (uuid/get-u32 id)]
(h/call wasm/internal-module "_render_sync_shape"
(aget buffer 0)
(aget buffer 1)
(aget buffer 2)
(aget buffer 3))
(set! wasm/internal-frame-id nil))))
(def set-view-render (def set-view-render
(fns/debounce (fns/debounce
(fn [ts] (fn [ts]
@@ -290,6 +307,13 @@
(aset textures new-id texture) (aset textures new-id texture)
new-id)) new-id))
(defn- retrieve-image
[url]
(rx/from
(-> (js/fetch url)
(p/then (fn [^js response] (.blob response)))
(p/then (fn [^js image] (js/createImageBitmap image))))))
(defn- fetch-image (defn- fetch-image
"Loads an image and creates a WebGL texture from it, passing the texture ID to WASM. "Loads an image and creates a WebGL texture from it, passing the texture ID to WASM.
This avoids decoding the image twice (once in browser, once in WASM)." This avoids decoding the image twice (once in browser, once in WASM)."
@@ -297,25 +321,18 @@
(let [url (cf/resolve-file-media {:id image-id} thumbnail?)] (let [url (cf/resolve-file-media {:id image-id} thumbnail?)]
{:key url {:key url
:thumbnail? thumbnail? :thumbnail? thumbnail?
:callback #(->> (p/create :callback
(fn [resolve reject] (fn []
(let [img (js/Image.) (->> (retrieve-image url)
on-load (fn [] (rx/map
(resolve img)) (fn [img]
on-error (fn [err]
(reject err))]
(set! (.-crossOrigin img) "anonymous")
(.addEventListener img "load" on-load)
(.addEventListener img "error" on-error)
(set! (.-src img) url))))
(rx/from)
(rx/map (fn [img]
(when-let [gl (get-webgl-context)] (when-let [gl (get-webgl-context)]
(let [texture (create-webgl-texture-from-image gl img) (let [texture (create-webgl-texture-from-image gl img)
texture-id (get-texture-id-for-gl-object texture) texture-id (get-texture-id-for-gl-object texture)
width (.-width ^js img) width (.-width ^js img)
height (.-height ^js img) height (.-height ^js img)
;; Header: 32 bytes (2 UUIDs) + 4 bytes (thumbnail) + 4 bytes (texture ID) + 8 bytes (dimensions) ;; Header: 32 bytes (2 UUIDs) + 4 bytes (thumbnail)
;; + 4 bytes (texture ID) + 8 bytes (dimensions)
total-bytes 48 total-bytes 48
offset (mem/alloc->offset-32 total-bytes) offset (mem/alloc->offset-32 total-bytes)
heap32 (mem/get-heap-u32)] heap32 (mem/get-heap-u32)]
@@ -340,13 +357,14 @@
(h/call wasm/internal-module "_store_image_from_texture") (h/call wasm/internal-module "_store_image_from_texture")
true)))) true))))
(rx/catch (fn [cause] (rx/catch
(fn [cause]
(log/error :hint "Could not fetch image" (log/error :hint "Could not fetch image"
:image-id image-id :image-id image-id
:thumbnail? thumbnail? :thumbnail? thumbnail?
:url url :url url
:cause cause) :cause cause)
(rx/empty))))})) (rx/empty)))))}))
(defn- get-fill-images (defn- get-fill-images
[leaf] [leaf]
@@ -961,7 +979,9 @@
:dimensions (get-text-dimensions id)}))))) :dimensions (get-text-dimensions id)})))))
(defn process-pending (defn process-pending
[shapes thumbnails full on-complete] ([shapes thumbnails full on-complete]
(process-pending shapes thumbnails full nil on-complete))
([shapes thumbnails full on-render on-complete]
(let [pending-thumbnails (let [pending-thumbnails
(d/index-by :key :callback thumbnails) (d/index-by :key :callback thumbnails)
@@ -978,9 +998,11 @@
(rx/subs! (rx/subs!
(fn [_] (fn [_]
(update-text-layouts shapes) (update-text-layouts shapes)
(request-render "pending-finished")) (if on-render
(on-render)
(request-render "pending-finished")))
noop-fn noop-fn
on-complete)))) on-complete)))))
(defn process-object (defn process-object
[shape] [shape]
@@ -988,7 +1010,9 @@
(process-pending [shape] thumbnails full noop-fn))) (process-pending [shape] thumbnails full noop-fn)))
(defn set-objects (defn set-objects
[objects] ([objects]
(set-objects objects nil))
([objects render-callback]
(perf/begin-measure "set-objects") (perf/begin-measure "set-objects")
(let [shapes (into [] (vals objects)) (let [shapes (into [] (vals objects))
total-shapes (count shapes) total-shapes (count shapes)
@@ -1003,9 +1027,9 @@
(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 shapes thumbnails full (process-pending shapes thumbnails full render-callback
(fn [] (fn []
(ug/dispatch! (ug/event "penpot:wasm:set-objects")))))) (ug/dispatch! (ug/event "penpot:wasm:set-objects")))))))
(defn clear-focus-mode (defn clear-focus-mode
[] []
@@ -1132,14 +1156,16 @@
(request-render "set-modifiers"))))) (request-render "set-modifiers")))))
(defn initialize-viewport (defn initialize-viewport
[base-objects zoom vbox background] ([base-objects zoom vbox background]
(initialize-viewport base-objects zoom vbox background nil))
([base-objects zoom vbox background callback]
(let [rgba (sr-clr/hex->u32argb background 1) (let [rgba (sr-clr/hex->u32argb background 1)
shapes (into [] (vals base-objects)) shapes (into [] (vals base-objects))
total-shapes (count shapes)] total-shapes (count shapes)]
(h/call wasm/internal-module "_set_canvas_background" rgba) (h/call wasm/internal-module "_set_canvas_background" rgba)
(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 "_init_shapes_pool" total-shapes) (h/call wasm/internal-module "_init_shapes_pool" total-shapes)
(set-objects base-objects))) (set-objects base-objects callback))))
(def ^:private default-context-options (def ^:private default-context-options
#js {:antialias false #js {:antialias false
@@ -1160,8 +1186,10 @@
(defn set-canvas-size (defn set-canvas-size
[canvas] [canvas]
(set! (.-width canvas) (* dpr (.-clientWidth ^js canvas))) (let [width (or (.-clientWidth ^js canvas) (.-width ^js canvas))
(set! (.-height canvas) (* dpr (.-clientHeight ^js canvas)))) height (or (.-clientHeight ^js canvas) (.-height ^js canvas))]
(set! (.-width canvas) (* dpr width))
(set! (.-height canvas) (* dpr height))))
(defn- get-browser (defn- get-browser
[] []
@@ -1274,14 +1302,13 @@
(mem/free) (mem/free)
content))) content)))
(defonce module
(delay (defn init-wasm-module
(if (exists? js/dynamicImport) [module]
(let [uri (cf/resolve-static-asset "js/render_wasm.js")] (let [default-fn (unchecked-get module "default")
(->> (js/dynamicImport (str uri)) serializers
(p/mcat (fn [module] #js
(let [default (unchecked-get module "default") {:blur-type (unchecked-get module "RawBlurType")
serializers #js{:blur-type (unchecked-get module "RawBlurType")
:blend-mode (unchecked-get module "RawBlendMode") :blend-mode (unchecked-get module "RawBlendMode")
:bool-type (unchecked-get module "RawBoolType") :bool-type (unchecked-get module "RawBoolType")
:font-style (unchecked-get module "RawFontStyle") :font-style (unchecked-get module "RawFontStyle")
@@ -1314,11 +1341,20 @@
:stroke-linejoin (unchecked-get module "RawStrokeLineJoin") :stroke-linejoin (unchecked-get module "RawStrokeLineJoin")
:fill-rule (unchecked-get module "RawFillRule")}] :fill-rule (unchecked-get module "RawFillRule")}]
(set! wasm/serializers serializers) (set! wasm/serializers serializers)
(default)))) (default-fn)))
(p/fmap (fn [default]
(defonce module
(delay
(if (exists? js/dynamicImport)
(let [uri (cf/resolve-static-asset "js/render_wasm.js")]
(->> (js/dynamicImport (str uri))
(p/mcat init-wasm-module)
(p/fmap
(fn [default]
(set! wasm/internal-module default) (set! wasm/internal-module default)
true)) true))
(p/merr (fn [cause] (p/merr
(fn [cause]
(js/console.error cause) (js/console.error cause)
(p/resolved false))))) (p/resolved false)))))
(p/resolved false)))) (p/resolved false))))

View File

@@ -89,7 +89,7 @@
(defn init (defn init
"Return a initialized webworker instance." "Return a initialized webworker instance."
[path on-error] [path on-error]
(let [instance (js/Worker. path) (let [instance (js/Worker. path #js {:type "module"})
bus (rx/subject) bus (rx/subject)
worker (Worker. instance (rx/to-observable bus)) worker (Worker. instance (rx/to-observable bus))

View File

@@ -7,16 +7,24 @@
(ns app.worker.thumbnails (ns app.worker.thumbnails
(:require (:require
["react-dom/server" :as rds] ["react-dom/server" :as rds]
[app.common.data.macros :as dm]
[app.common.geom.rect :as grc]
[app.common.geom.shapes.bounds :as gsb]
[app.common.logging :as log] [app.common.logging :as log]
[app.common.types.color :as cc]
[app.common.uri :as u] [app.common.uri :as u]
[app.config :as cf] [app.config :as cf]
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.main.render :as render] [app.main.render :as render]
[app.render-wasm.api :as wasm.api]
[app.render-wasm.wasm :as wasm]
[app.util.http :as http] [app.util.http :as http]
[app.worker.impl :as impl] [app.worker.impl :as impl]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[okulary.core :as l] [okulary.core :as l]
[rumext.v2 :as mf])) [promesa.core :as p]
[rumext.v2 :as mf]
[shadow.esm :refer (dynamic-import)]))
(log/set-level! :trace) (log/set-level! :trace)
@@ -42,11 +50,11 @@
:http-body body}))) :http-body body})))
(defn- request-data-for-thumbnail (defn- request-data-for-thumbnail
[file-id revn] [file-id revn strip-frames-with-thumbnails]
(let [path "api/main/methods/get-file-data-for-thumbnail" (let [path "api/main/methods/get-file-data-for-thumbnail"
params {:file-id file-id params {:file-id file-id
:revn revn :revn revn
:strip-frames-with-thumbnails true} :strip-frames-with-thumbnails strip-frames-with-thumbnails}
request {:method :get request {:method :get
:uri (u/join cf/public-uri path) :uri (u/join cf/public-uri path)
:credentials "include" :credentials "include"
@@ -86,5 +94,89 @@
(defmethod impl/handler :thumbnails/generate-for-file (defmethod impl/handler :thumbnails/generate-for-file
[{:keys [file-id revn] :as message} _] [{:keys [file-id revn] :as message} _]
(->> (request-data-for-thumbnail file-id revn) (->> (request-data-for-thumbnail file-id revn true)
(rx/map render-thumbnail))) (rx/map render-thumbnail)))
(def init-wasm
(delay
(let [uri (cf/resolve-static-asset "js/render_wasm.js")]
(-> (dynamic-import (str uri))
(p/then #(wasm.api/init-wasm-module %))
(p/then #(set! wasm/internal-module %))))))
(mf/defc svg-wrapper
[{:keys [data-uri background width height]}]
[:svg {:version "1.1"
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:style {:width "100%"
:height "100%"
:background background}
:fill "none"
:viewBox (dm/str "0 0 " width " " height)}
[:image {:xlinkHref data-uri
:width width
:height height}]])
(defn blob->uri
[blob]
(.readAsDataURL (js/FileReaderSync.) blob))
(def thumbnail-aspect-ratio (/ 2 3))
(defmethod impl/handler :thumbnails/generate-for-file-wasm
[{:keys [file-id revn width] :as message} _]
(->> (rx/from @init-wasm)
(rx/mapcat #(request-data-for-thumbnail file-id revn false))
(rx/mapcat
(fn [{:keys [page] :as file}]
(rx/create
(fn [subs]
(try
(let [background-color (or (:background page) cc/canvas)
height (* width thumbnail-aspect-ratio)
canvas (js/OffscreenCanvas. width height)
init? (wasm.api/init-canvas-context canvas)]
(if init?
(let [objects (:objects page)
frame (some->> page :thumbnail-frame-id (get objects))
vbox (if frame
(-> (gsb/get-object-bounds objects frame)
(grc/fix-aspect-ratio thumbnail-aspect-ratio))
(render/calculate-dimensions objects thumbnail-aspect-ratio))
zoom (/ width (:width vbox))]
(wasm.api/initialize-viewport
objects zoom vbox background-color
(fn []
(if frame
(wasm.api/render-sync-shape (:id frame))
(wasm.api/render-sync))
(-> (.convertToBlob canvas)
(p/then
(fn [blob]
(let [data
(rds/renderToStaticMarkup
(mf/element
svg-wrapper
#js {:data-uri (blob->uri blob)
:width width
:height height
:background background-color}))]
(rx/push! subs {:data data :file-id file-id :revn revn}))))
(p/catch #(do (.error js/console %)
(rx/error! subs %)))
(p/finally #(rx/end! subs))))))
(do (rx/error! subs "Error loading webgl context")
(rx/end! subs)))
nil)
(catch :default err
(.error js/console err)
(rx/error! subs err)
(rx/end! subs)))))))))

View File

@@ -1,8 +1,16 @@
addToLibrary({ addToLibrary({
wapi_requestAnimationFrame: function wapi_requestAnimationFrame() { wapi_requestAnimationFrame: function wapi_requestAnimationFrame() {
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
setTimeout(Module._process_animation_frame);
} else {
return window.requestAnimationFrame(Module._process_animation_frame); return window.requestAnimationFrame(Module._process_animation_frame);
}
}, },
wapi_cancelAnimationFrame: function wapi_cancelAnimationFrame(frameId) { wapi_cancelAnimationFrame: function wapi_cancelAnimationFrame(frameId) {
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
clearTimeout(frameId);
} else {
return window.cancelAnimationFrame(frameId); return window.cancelAnimationFrame(frameId);
} }
}
}); });

View File

@@ -149,6 +149,27 @@ pub extern "C" fn render(_: i32) {
}); });
} }
#[no_mangle]
pub extern "C" fn render_sync() {
with_state_mut!(state, {
state.rebuild_tiles();
state
.render_sync(performance::get_time())
.expect("Error rendering");
});
}
#[no_mangle]
pub extern "C" fn render_sync_shape(a: u32, b: u32, c: u32, d: u32) {
with_state_mut!(state, {
let id = uuid_from_u32_quartet(a, b, c, d);
state.rebuild_tiles_from(Some(&id));
state
.render_sync_shape(&id, performance::get_time())
.expect("Error rendering");
});
}
#[no_mangle] #[no_mangle]
pub extern "C" fn render_from_cache(_: i32) { pub extern "C" fn render_from_cache(_: i32) {
with_state_mut!(state, { with_state_mut!(state, {

View File

@@ -867,7 +867,13 @@ impl RenderState {
} }
} }
pub fn start_render_loop(&mut self, tree: ShapesPoolRef, timestamp: i32) -> Result<(), String> { pub fn start_render_loop(
&mut self,
base_object: Option<&Uuid>,
tree: ShapesPoolRef,
timestamp: i32,
sync_render: bool,
) -> Result<(), String> {
let scale = self.get_scale(); let scale = self.get_scale();
self.tile_viewbox.update(self.viewbox, scale); self.tile_viewbox.update(self.viewbox, scale);
@@ -917,20 +923,27 @@ impl RenderState {
self.current_tile = None; self.current_tile = None;
self.render_in_progress = true; self.render_in_progress = true;
self.apply_drawing_to_render_canvas(None); self.apply_drawing_to_render_canvas(None);
self.process_animation_frame(tree, timestamp)?;
if sync_render {
self.render_shape_tree_sync(base_object, tree, timestamp)?;
} else {
self.process_animation_frame(base_object, tree, timestamp)?;
}
performance::end_measure!("start_render_loop"); performance::end_measure!("start_render_loop");
Ok(()) Ok(())
} }
pub fn process_animation_frame( pub fn process_animation_frame(
&mut self, &mut self,
base_object: Option<&Uuid>,
tree: ShapesPoolRef, tree: ShapesPoolRef,
timestamp: i32, timestamp: i32,
) -> Result<(), String> { ) -> Result<(), String> {
performance::begin_measure!("process_animation_frame"); performance::begin_measure!("process_animation_frame");
if self.render_in_progress { if self.render_in_progress {
if tree.len() != 0 { if tree.len() != 0 {
self.render_shape_tree_partial(tree, timestamp)?; self.render_shape_tree_partial(base_object, tree, timestamp, true)?;
} else { } else {
println!("Empty tree"); println!("Empty tree");
} }
@@ -947,6 +960,22 @@ impl RenderState {
Ok(()) Ok(())
} }
pub fn render_shape_tree_sync(
&mut self,
base_object: Option<&Uuid>,
tree: ShapesPoolRef,
timestamp: i32,
) -> Result<(), String> {
if tree.len() != 0 {
self.render_shape_tree_partial(base_object, tree, timestamp, false)?;
} else {
println!("Empty tree");
}
self.flush_and_submit();
Ok(())
}
#[inline] #[inline]
pub fn should_stop_rendering(&self, iteration: i32, timestamp: i32) -> bool { pub fn should_stop_rendering(&self, iteration: i32, timestamp: i32) -> bool {
iteration % NODE_BATCH_THRESHOLD == 0 iteration % NODE_BATCH_THRESHOLD == 0
@@ -1215,6 +1244,7 @@ impl RenderState {
&mut self, &mut self,
tree: ShapesPoolRef, tree: ShapesPoolRef,
timestamp: i32, timestamp: i32,
allow_stop: bool,
) -> Result<(bool, bool), String> { ) -> Result<(bool, bool), String> {
let mut iteration = 0; let mut iteration = 0;
let mut is_empty = true; let mut is_empty = true;
@@ -1495,7 +1525,7 @@ impl RenderState {
} }
// We try to avoid doing too many calls to get_time // We try to avoid doing too many calls to get_time
if self.should_stop_rendering(iteration, timestamp) { if allow_stop && self.should_stop_rendering(iteration, timestamp) {
return Ok((is_empty, true)); return Ok((is_empty, true));
} }
iteration += 1; iteration += 1;
@@ -1505,8 +1535,10 @@ impl RenderState {
pub fn render_shape_tree_partial( pub fn render_shape_tree_partial(
&mut self, &mut self,
base_object: Option<&Uuid>,
tree: ShapesPoolRef, tree: ShapesPoolRef,
timestamp: i32, timestamp: i32,
allow_stop: bool,
) -> Result<(), String> { ) -> Result<(), String> {
let mut should_stop = false; let mut should_stop = false;
while !should_stop { while !should_stop {
@@ -1532,7 +1564,7 @@ impl RenderState {
} else { } else {
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, allow_stop)?;
if early_return { if early_return {
return Ok(()); return Ok(());
@@ -1564,10 +1596,16 @@ impl RenderState {
.canvas(SurfaceId::Current) .canvas(SurfaceId::Current)
.clear(self.background_color); .clear(self.background_color);
let root_ids = {
if let Some(shape_id) = base_object {
vec![*shape_id]
} else {
let Some(root) = tree.get(&Uuid::nil()) else { let Some(root) = tree.get(&Uuid::nil()) else {
return Err(String::from("Root shape not found")); return Err(String::from("Root shape not found"));
}; };
let root_ids = root.children_ids(false); root.children_ids(false)
}
};
// If we finish processing every node rendering is complete // If we finish processing every node rendering is complete
// let's check if there are more pending nodes // let's check if there are more pending nodes
@@ -1711,13 +1749,19 @@ impl RenderState {
performance::end_measure!("rebuild_tiles_shallow"); performance::end_measure!("rebuild_tiles_shallow");
} }
pub fn rebuild_tiles(&mut self, tree: ShapesPoolRef) { pub fn rebuild_tiles_from(&mut self, tree: ShapesPoolRef, base_id: Option<&Uuid>) {
performance::begin_measure!("rebuild_tiles"); performance::begin_measure!("rebuild_tiles");
self.tiles.invalidate(); self.tiles.invalidate();
let mut all_tiles = HashSet::<tiles::Tile>::new(); let mut all_tiles = HashSet::<tiles::Tile>::new();
let mut nodes = vec![Uuid::nil()]; let mut nodes = {
if let Some(base_id) = base_id {
vec![*base_id]
} else {
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) {
@@ -1737,7 +1781,6 @@ impl RenderState {
for tile in all_tiles { for tile in all_tiles {
self.remove_cached_tile(tile); self.remove_cached_tile(tile);
} }
performance::end_measure!("rebuild_tiles"); performance::end_measure!("rebuild_tiles");
} }

View File

@@ -63,15 +63,27 @@ impl<'a> State<'a> {
self.render_state.render_from_cache(&self.shapes); self.render_state.render_from_cache(&self.shapes);
} }
pub fn render_sync(&mut self, timestamp: i32) -> Result<(), String> {
self.render_state
.start_render_loop(None, &self.shapes, timestamp, true)?;
Ok(())
}
pub fn render_sync_shape(&mut self, id: &Uuid, timestamp: i32) -> Result<(), String> {
self.render_state
.start_render_loop(Some(id), &self.shapes, timestamp, true)?;
Ok(())
}
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> { pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
self.render_state self.render_state
.start_render_loop(&self.shapes, timestamp)?; .start_render_loop(None, &self.shapes, timestamp, false)?;
Ok(()) Ok(())
} }
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> { pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
self.render_state self.render_state
.process_animation_frame(&self.shapes, timestamp)?; .process_animation_frame(None, &self.shapes, timestamp)?;
Ok(()) Ok(())
} }
@@ -162,7 +174,11 @@ impl<'a> State<'a> {
} }
pub fn rebuild_tiles(&mut self) { pub fn rebuild_tiles(&mut self) {
self.render_state.rebuild_tiles(&self.shapes); self.render_state.rebuild_tiles_from(&self.shapes, None);
}
pub fn rebuild_tiles_from(&mut self, base_id: Option<&Uuid>) {
self.render_state.rebuild_tiles_from(&self.shapes, base_id);
} }
pub fn rebuild_touched_tiles(&mut self) { pub fn rebuild_touched_tiles(&mut self) {