mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
Merge pull request #7782 from penpot/alotor-wasm-thumbnails
✨ Render WASM dashboard thumbnails
This commit is contained in:
@@ -96,7 +96,7 @@
|
||||
;; loading all pages into memory for find the frame set 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
|
||||
;; used as thumbnail; the returned frame always have
|
||||
;; the :page-id set to the page that it belongs.
|
||||
@@ -173,7 +173,7 @@
|
||||
|
||||
;; Assoc the available thumbnails and prune not visible shapes
|
||||
;; for avoid transfer unnecessary data.
|
||||
:always
|
||||
strip-frames-with-thumbnails
|
||||
(update :objects assoc-thumbnails page-id thumbs)))))
|
||||
|
||||
(def ^:private
|
||||
@@ -186,7 +186,8 @@
|
||||
[:map {:title "PartialFile"}
|
||||
[:id ::sm/uuid]
|
||||
[: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
|
||||
"Retrieves the data for generate the thumbnail of the file. Used
|
||||
@@ -195,7 +196,7 @@
|
||||
::doc/module :files
|
||||
::sm/params schema:get-file-data-for-thumbnail
|
||||
::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}]
|
||||
(files/check-read-permissions! conn profile-id file-id)
|
||||
|
||||
@@ -205,14 +206,18 @@
|
||||
|
||||
file (bfc/get-file cfg file-id
|
||||
: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/check-file-features! (:features file)))
|
||||
|
||||
{:file-id file-id
|
||||
: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
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
:source-map-detail-level :all}}}
|
||||
|
||||
:worker
|
||||
{:target :browser
|
||||
{:target :esm
|
||||
:output-dir "resources/public/js/worker/"
|
||||
:asset-path "/js/worker"
|
||||
:devtools {:browser-inject :main
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
:height (:height vbox)
|
||||
:fill color}])
|
||||
|
||||
(defn- calculate-dimensions
|
||||
(defn calculate-dimensions
|
||||
[objects aspect-ratio]
|
||||
(let [root-objects (ctst/get-root-objects objects)]
|
||||
(if (empty? root-objects)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.project :as dpj]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.features :as features]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.rasterizer :as thr]
|
||||
[app.main.refs :as refs]
|
||||
@@ -46,6 +47,8 @@
|
||||
|
||||
(log/set-level! :debug)
|
||||
|
||||
(def thumbnail-width 252)
|
||||
|
||||
;; --- Grid Item Thumbnail
|
||||
|
||||
(defn- persist-thumbnail
|
||||
@@ -56,15 +59,22 @@
|
||||
|
||||
(defn render-thumbnail
|
||||
[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
|
||||
:revn revn
|
||||
:file-id file-id})
|
||||
(rx/mapcat (fn [{:keys [fonts] :as result}]
|
||||
:file-id file-id
|
||||
:width thumbnail-width})
|
||||
(rx/mapcat
|
||||
(fn [{:keys [fonts] :as result}]
|
||||
(->> (fonts/render-font-styles fonts)
|
||||
(rx/map (fn [styles]
|
||||
(assoc result
|
||||
:styles styles
|
||||
:width 252))))))))
|
||||
(-> result
|
||||
(assoc :styles styles
|
||||
:width thumbnail-width))))))))))
|
||||
|
||||
(defn- ask-for-thumbnail
|
||||
"Creates some hooks to handle the files thumbnails cache"
|
||||
|
||||
@@ -94,6 +94,23 @@
|
||||
(set! wasm/internal-frame-id nil)
|
||||
(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
|
||||
(fns/debounce
|
||||
(fn [ts]
|
||||
@@ -290,6 +307,13 @@
|
||||
(aset textures new-id texture)
|
||||
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
|
||||
"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)."
|
||||
@@ -297,25 +321,18 @@
|
||||
(let [url (cf/resolve-file-media {:id image-id} thumbnail?)]
|
||||
{:key url
|
||||
:thumbnail? thumbnail?
|
||||
:callback #(->> (p/create
|
||||
(fn [resolve reject]
|
||||
(let [img (js/Image.)
|
||||
on-load (fn []
|
||||
(resolve 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]
|
||||
:callback
|
||||
(fn []
|
||||
(->> (retrieve-image url)
|
||||
(rx/map
|
||||
(fn [img]
|
||||
(when-let [gl (get-webgl-context)]
|
||||
(let [texture (create-webgl-texture-from-image gl img)
|
||||
texture-id (get-texture-id-for-gl-object texture)
|
||||
width (.-width ^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
|
||||
offset (mem/alloc->offset-32 total-bytes)
|
||||
heap32 (mem/get-heap-u32)]
|
||||
@@ -340,13 +357,14 @@
|
||||
|
||||
(h/call wasm/internal-module "_store_image_from_texture")
|
||||
true))))
|
||||
(rx/catch (fn [cause]
|
||||
(rx/catch
|
||||
(fn [cause]
|
||||
(log/error :hint "Could not fetch image"
|
||||
:image-id image-id
|
||||
:thumbnail? thumbnail?
|
||||
:url url
|
||||
:cause cause)
|
||||
(rx/empty))))}))
|
||||
(rx/empty)))))}))
|
||||
|
||||
(defn- get-fill-images
|
||||
[leaf]
|
||||
@@ -961,7 +979,9 @@
|
||||
:dimensions (get-text-dimensions id)})))))
|
||||
|
||||
(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
|
||||
(d/index-by :key :callback thumbnails)
|
||||
|
||||
@@ -978,9 +998,11 @@
|
||||
(rx/subs!
|
||||
(fn [_]
|
||||
(update-text-layouts shapes)
|
||||
(request-render "pending-finished"))
|
||||
(if on-render
|
||||
(on-render)
|
||||
(request-render "pending-finished")))
|
||||
noop-fn
|
||||
on-complete))))
|
||||
on-complete)))))
|
||||
|
||||
(defn process-object
|
||||
[shape]
|
||||
@@ -988,7 +1010,9 @@
|
||||
(process-pending [shape] thumbnails full noop-fn)))
|
||||
|
||||
(defn set-objects
|
||||
[objects]
|
||||
([objects]
|
||||
(set-objects objects nil))
|
||||
([objects render-callback]
|
||||
(perf/begin-measure "set-objects")
|
||||
(let [shapes (into [] (vals objects))
|
||||
total-shapes (count shapes)
|
||||
@@ -1003,9 +1027,9 @@
|
||||
(into full-acc full)))
|
||||
{:thumbnails thumbnails-acc :full full-acc}))]
|
||||
(perf/end-measure "set-objects")
|
||||
(process-pending shapes thumbnails full
|
||||
(process-pending shapes thumbnails full render-callback
|
||||
(fn []
|
||||
(ug/dispatch! (ug/event "penpot:wasm:set-objects"))))))
|
||||
(ug/dispatch! (ug/event "penpot:wasm:set-objects")))))))
|
||||
|
||||
(defn clear-focus-mode
|
||||
[]
|
||||
@@ -1132,14 +1156,16 @@
|
||||
(request-render "set-modifiers")))))
|
||||
|
||||
(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)
|
||||
shapes (into [] (vals base-objects))
|
||||
total-shapes (count shapes)]
|
||||
(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 "_init_shapes_pool" total-shapes)
|
||||
(set-objects base-objects)))
|
||||
(set-objects base-objects callback))))
|
||||
|
||||
(def ^:private default-context-options
|
||||
#js {:antialias false
|
||||
@@ -1160,8 +1186,10 @@
|
||||
|
||||
(defn set-canvas-size
|
||||
[canvas]
|
||||
(set! (.-width canvas) (* dpr (.-clientWidth ^js canvas)))
|
||||
(set! (.-height canvas) (* dpr (.-clientHeight ^js canvas))))
|
||||
(let [width (or (.-clientWidth ^js canvas) (.-width ^js canvas))
|
||||
height (or (.-clientHeight ^js canvas) (.-height ^js canvas))]
|
||||
(set! (.-width canvas) (* dpr width))
|
||||
(set! (.-height canvas) (* dpr height))))
|
||||
|
||||
(defn- get-browser
|
||||
[]
|
||||
@@ -1274,14 +1302,13 @@
|
||||
(mem/free)
|
||||
content)))
|
||||
|
||||
(defonce module
|
||||
(delay
|
||||
(if (exists? js/dynamicImport)
|
||||
(let [uri (cf/resolve-static-asset "js/render_wasm.js")]
|
||||
(->> (js/dynamicImport (str uri))
|
||||
(p/mcat (fn [module]
|
||||
(let [default (unchecked-get module "default")
|
||||
serializers #js{:blur-type (unchecked-get module "RawBlurType")
|
||||
|
||||
(defn init-wasm-module
|
||||
[module]
|
||||
(let [default-fn (unchecked-get module "default")
|
||||
serializers
|
||||
#js
|
||||
{:blur-type (unchecked-get module "RawBlurType")
|
||||
:blend-mode (unchecked-get module "RawBlendMode")
|
||||
:bool-type (unchecked-get module "RawBoolType")
|
||||
:font-style (unchecked-get module "RawFontStyle")
|
||||
@@ -1314,11 +1341,20 @@
|
||||
:stroke-linejoin (unchecked-get module "RawStrokeLineJoin")
|
||||
:fill-rule (unchecked-get module "RawFillRule")}]
|
||||
(set! wasm/serializers serializers)
|
||||
(default))))
|
||||
(p/fmap (fn [default]
|
||||
(default-fn)))
|
||||
|
||||
(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)
|
||||
true))
|
||||
(p/merr (fn [cause]
|
||||
(p/merr
|
||||
(fn [cause]
|
||||
(js/console.error cause)
|
||||
(p/resolved false)))))
|
||||
(p/resolved false))))
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
(defn init
|
||||
"Return a initialized webworker instance."
|
||||
[path on-error]
|
||||
(let [instance (js/Worker. path)
|
||||
(let [instance (js/Worker. path #js {:type "module"})
|
||||
bus (rx/subject)
|
||||
worker (Worker. instance (rx/to-observable bus))
|
||||
|
||||
|
||||
@@ -7,16 +7,24 @@
|
||||
(ns app.worker.thumbnails
|
||||
(:require
|
||||
["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.types.color :as cc]
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.main.render :as render]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.render-wasm.wasm :as wasm]
|
||||
[app.util.http :as http]
|
||||
[app.worker.impl :as impl]
|
||||
[beicon.v2.core :as rx]
|
||||
[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)
|
||||
|
||||
@@ -42,11 +50,11 @@
|
||||
:http-body body})))
|
||||
|
||||
(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"
|
||||
params {:file-id file-id
|
||||
:revn revn
|
||||
:strip-frames-with-thumbnails true}
|
||||
:strip-frames-with-thumbnails strip-frames-with-thumbnails}
|
||||
request {:method :get
|
||||
:uri (u/join cf/public-uri path)
|
||||
:credentials "include"
|
||||
@@ -86,5 +94,89 @@
|
||||
|
||||
(defmethod impl/handler :thumbnails/generate-for-file
|
||||
[{: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)))
|
||||
|
||||
(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)))))))))
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
addToLibrary({
|
||||
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);
|
||||
}
|
||||
},
|
||||
wapi_cancelAnimationFrame: function wapi_cancelAnimationFrame(frameId) {
|
||||
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
|
||||
clearTimeout(frameId);
|
||||
} else {
|
||||
return window.cancelAnimationFrame(frameId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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]
|
||||
pub extern "C" fn render_from_cache(_: i32) {
|
||||
with_state_mut!(state, {
|
||||
|
||||
@@ -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();
|
||||
self.tile_viewbox.update(self.viewbox, scale);
|
||||
|
||||
@@ -917,20 +923,27 @@ impl RenderState {
|
||||
self.current_tile = None;
|
||||
self.render_in_progress = true;
|
||||
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");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_animation_frame(
|
||||
&mut self,
|
||||
base_object: Option<&Uuid>,
|
||||
tree: ShapesPoolRef,
|
||||
timestamp: i32,
|
||||
) -> Result<(), String> {
|
||||
performance::begin_measure!("process_animation_frame");
|
||||
if self.render_in_progress {
|
||||
if tree.len() != 0 {
|
||||
self.render_shape_tree_partial(tree, timestamp)?;
|
||||
self.render_shape_tree_partial(base_object, tree, timestamp, true)?;
|
||||
} else {
|
||||
println!("Empty tree");
|
||||
}
|
||||
@@ -947,6 +960,22 @@ impl RenderState {
|
||||
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]
|
||||
pub fn should_stop_rendering(&self, iteration: i32, timestamp: i32) -> bool {
|
||||
iteration % NODE_BATCH_THRESHOLD == 0
|
||||
@@ -1215,6 +1244,7 @@ impl RenderState {
|
||||
&mut self,
|
||||
tree: ShapesPoolRef,
|
||||
timestamp: i32,
|
||||
allow_stop: bool,
|
||||
) -> Result<(bool, bool), String> {
|
||||
let mut iteration = 0;
|
||||
let mut is_empty = true;
|
||||
@@ -1495,7 +1525,7 @@ impl RenderState {
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
iteration += 1;
|
||||
@@ -1505,8 +1535,10 @@ impl RenderState {
|
||||
|
||||
pub fn render_shape_tree_partial(
|
||||
&mut self,
|
||||
base_object: Option<&Uuid>,
|
||||
tree: ShapesPoolRef,
|
||||
timestamp: i32,
|
||||
allow_stop: bool,
|
||||
) -> Result<(), String> {
|
||||
let mut should_stop = false;
|
||||
while !should_stop {
|
||||
@@ -1532,7 +1564,7 @@ impl RenderState {
|
||||
} else {
|
||||
performance::begin_measure!("render_shape_tree::uncached");
|
||||
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 {
|
||||
return Ok(());
|
||||
@@ -1564,10 +1596,16 @@ impl RenderState {
|
||||
.canvas(SurfaceId::Current)
|
||||
.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 {
|
||||
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
|
||||
// let's check if there are more pending nodes
|
||||
@@ -1711,13 +1749,19 @@ impl RenderState {
|
||||
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");
|
||||
|
||||
self.tiles.invalidate();
|
||||
|
||||
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() {
|
||||
if let Some(shape) = tree.get(&shape_id) {
|
||||
@@ -1737,7 +1781,6 @@ impl RenderState {
|
||||
for tile in all_tiles {
|
||||
self.remove_cached_tile(tile);
|
||||
}
|
||||
|
||||
performance::end_measure!("rebuild_tiles");
|
||||
}
|
||||
|
||||
|
||||
@@ -63,15 +63,27 @@ impl<'a> State<'a> {
|
||||
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> {
|
||||
self.render_state
|
||||
.start_render_loop(&self.shapes, timestamp)?;
|
||||
.start_render_loop(None, &self.shapes, timestamp, false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
|
||||
self.render_state
|
||||
.process_animation_frame(&self.shapes, timestamp)?;
|
||||
.process_animation_frame(None, &self.shapes, timestamp)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -162,7 +174,11 @@ impl<'a> State<'a> {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user