Merge pull request #5333 from penpot/niwinz-render-wasm-improvements

 Improvements to wasm api call usability
This commit is contained in:
Belén Albeza 2024-11-20 09:34:48 +01:00 committed by GitHub
commit 59fdf64c66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 414 additions and 401 deletions

View file

@ -76,7 +76,7 @@
[app.main.repo :as rp]
[app.main.streams :as ms]
[app.main.worker :as uw]
[app.render-wasm :as render.wasm]
[app.render-wasm :as wasm]
[app.util.dom :as dom]
[app.util.globals :as ug]
[app.util.http :as http]
@ -270,7 +270,7 @@
;; load. We need to wait the promise to be resolved
;; before continue with the next workspace loading
;; steps
(->> (rx/from render.wasm/module)
(->> (rx/from wasm/module)
(rx/ignore))
(->> (rp/cmd! :get-team {:id (:team-id project)})
(rx/mapcat (fn [team]

View file

@ -12,7 +12,7 @@
[app.common.logging :as log]
[app.config :as cf]
[app.main.store :as st]
[app.render-wasm :as render.wasm]
[app.render-wasm :as wasm]
[beicon.v2.core :as rx]
[clojure.set :as set]
[cuerdas.core :as str]
@ -126,8 +126,8 @@
(effect [_ state _]
(let [features (get-team-enabled-features state)]
(if (contains? features "render-wasm/v1")
(render.wasm/initialize true)
(render.wasm/initialize false))
(wasm/initialize true)
(wasm/initialize false))
(log/inf :hint "initialized"
:enabled (str/join "," features)

View file

@ -10,7 +10,7 @@
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.logic.shapes :as cls]
[app.common.types.shape.impl :as shape.impl]
[app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl]
[app.common.types.shape.radius :as ctsr]
[app.common.types.tokens-lib :as ctob]
@ -249,7 +249,7 @@
(fn [value attr]
(let [token-value (wtc/maybe-resolve-token-value value)
undo-id (js/Symbol)]
(binding [shape.impl/*wasm-sync* true]
(binding [cts/*wasm-sync* true]
(if-not design-tokens?
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(udw/update-dimensions ids attr (or token-value value)))
@ -285,7 +285,7 @@
(mf/deps ids)
(fn [value attr]
(st/emit! (udw/trigger-bounding-box-cloaking ids))
(binding [shape.impl/*wasm-sync* true]
(binding [cts/*wasm-sync* true]
(doall (map #(do-position-change %1 %2 value attr) shapes frames)))))
;; ROTATION
@ -294,7 +294,7 @@
(mf/use-fn
(mf/deps ids)
(fn [value]
(binding [shape.impl/*wasm-sync* true]
(binding [cts/*wasm-sync* true]
(st/emit! (udw/trigger-bounding-box-cloaking ids)
(udw/increase-rotation ids value)))))

View file

@ -12,8 +12,8 @@
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.shapes :as gsh]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.impl :as shape.impl]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.modifiers :as dwm]
[app.main.features :as features]
@ -49,7 +49,7 @@
[app.main.ui.workspace.viewport.utils :as utils]
[app.main.ui.workspace.viewport.viewport-ref :refer [create-viewport-ref]]
[app.main.ui.workspace.viewport.widgets :as widgets]
[app.render-wasm :as render.wasm]
[app.render-wasm.api :as wasm.api]
[app.util.debug :as dbg]
[beicon.v2.core :as rx]
[promesa.core :as p]
@ -113,7 +113,7 @@
text-modifiers (mf/deref refs/workspace-text-modifier)
objects-modified (mf/with-memo [base-objects text-modifiers modifiers]
(binding [shape.impl/*wasm-sync* true]
(binding [cts/*wasm-sync* true]
(apply-modifiers-to-selected selected base-objects text-modifiers modifiers)))
selected-shapes (keep (d/getf objects-modified) selected)
@ -275,26 +275,26 @@
(mf/with-effect []
(when-let [canvas (mf/ref-val canvas-ref)]
(->> render.wasm/module
(->> wasm.api/module
(p/fmap (fn [ready?]
(when ready?
(reset! canvas-init? true)
(render.wasm/assign-canvas canvas)))))
(wasm.api/assign-canvas canvas)))))
(fn []
(render.wasm/clear-canvas))))
(wasm.api/clear-canvas))))
(mf/with-effect [base-objects canvas-init?]
(when @canvas-init?
(render.wasm/set-objects base-objects)
(render.wasm/draw-objects zoom vbox)))
(wasm.api/set-objects base-objects)
(wasm.api/draw-objects zoom vbox)))
(mf/with-effect [modifiers canvas-init?]
(when (and @canvas-init? modifiers)
(render.wasm/draw-objects zoom vbox)))
(wasm.api/draw-objects zoom vbox)))
(mf/with-effect [vbox canvas-init?]
(let [frame-id (when @canvas-init? (render.wasm/draw-objects zoom vbox))]
(partial render.wasm/cancel-draw frame-id)))
(let [frame-id (when @canvas-init? (wasm.api/draw-objects zoom vbox))]
(partial wasm.api/cancel-draw frame-id)))
(hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool drawing-path?)
(hooks/setup-viewport-size vport viewport-ref)

View file

@ -7,189 +7,13 @@
(ns app.render-wasm
"A WASM based render API"
(:require
[app.common.colors :as cc]
[app.common.data.macros :as dm]
[app.common.types.shape.impl :as ctsi]
[app.common.uuid :as uuid]
[app.config :as cf]
[promesa.core :as p]))
[app.common.types.shape :as shape]
[app.render-wasm.api :as api]
[app.render-wasm.shape :as wasm.shape]))
(def module api/module)
(defn initialize
[enabled?]
(set! app.common.types.shape.impl/enabled-wasm-ready-shape enabled?))
(defonce internal-module #js {})
;; TODO: remove the `take` once we have the dynamic data structure in Rust
(def xform
(comp
(take 2048)))
(defn create-shape
[id]
(let [buffer (uuid/get-u32 id)
create-shape (unchecked-get internal-module "_create_shape")]
(^function create-shape (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
(defn use-shape
[id]
(let [buffer (uuid/get-u32 id)
use-shape (unchecked-get internal-module "_use_shape")]
(^function use-shape (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
(defn set-shape-selrect
[selrect]
(let [x1 (:x1 selrect)
y1 (:y1 selrect)
x2 (:x2 selrect)
y2 (:y2 selrect)
set-shape-selrect (unchecked-get internal-module "_set_shape_selrect")]
(^function set-shape-selrect x1 y1 x2 y2)))
(defn set-shape-transform
[transform]
(let [a (:a transform)
b (:b transform)
c (:c transform)
d (:d transform)
e (:e transform)
f (:f transform)
set-shape-transform (unchecked-get internal-module "_set_shape_transform")]
(^function set-shape-transform a b c d e f)))
(defn set-shape-rotation
[rotation]
(let [set-shape-rotation (unchecked-get internal-module "_set_shape_rotation")]
(^function set-shape-rotation rotation)))
(defn set-shape-children
[shape_ids]
(let [clear-shape-children (unchecked-get internal-module "_clear_shape_children")
add-shape-child (unchecked-get internal-module "_add_shape_child")]
(^function clear-shape-children)
(doseq [id shape_ids]
(let [buffer (uuid/get-u32 id)]
(^function add-shape-child (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))))
(defn set-shape-fills
[fills]
(let [clear-shape-fills (unchecked-get internal-module "_clear_shape_fills")
add-shape-fill (unchecked-get internal-module "_add_shape_solid_fill")]
(^function clear-shape-fills)
(doseq [fill (filter #(contains? % :fill-color) fills)]
(let [a (:fill-opacity fill)
[r g b] (cc/hex->rgb (:fill-color fill))]
(^function add-shape-fill r g b a)))))
(defn set-shape-blend-mode
[blend-mode]
;; These values correspond to skia::BlendMode representation
;; https://rust-skia.github.io/doc/skia_safe/enum.BlendMode.html
(let [encoded-blend (case blend-mode
:normal 3
:darken 16
:multiply 24
:color-burn 19
:lighten 17
:screen 14
:color-dodge 18
:overlay 15
:soft-light 21
:hard-light 20
:difference 22
:exclusion 23
:hue 25
:saturation 26
:color 27
:luminosity 28
3)
set-shape-blend-mode (unchecked-get internal-module "_set_shape_blend_mode")]
(^function set-shape-blend-mode encoded-blend)))
(defn set-objects
[objects]
(let [shapes (into [] xform (vals objects))
total-shapes (count shapes)]
(loop [index 0]
(when (< index total-shapes)
(let [shape (nth shapes index)
id (dm/get-prop shape :id)
selrect (dm/get-prop shape :selrect)
rotation (dm/get-prop shape :rotation)
transform (dm/get-prop shape :transform)
fills (dm/get-prop shape :fills)
children (dm/get-prop shape :shapes)
blend-mode (dm/get-prop shape :blend-mode)]
(use-shape id)
(set-shape-selrect selrect)
(set-shape-rotation rotation)
(set-shape-transform transform)
(set-shape-fills fills)
(set-shape-blend-mode blend-mode)
(set-shape-children children)
(recur (inc index)))))))
(defn draw-objects
[zoom vbox]
(js/requestAnimationFrame
(fn []
(let [pan-x (- (dm/get-prop vbox :x))
pan-y (- (dm/get-prop vbox :y))
draw-all-shapes (unchecked-get internal-module "_draw_all_shapes")]
(^function draw-all-shapes zoom pan-x pan-y)))))
(defn cancel-draw
[frame-id]
(when (some? frame-id)
(js/cancelAnimationFrame frame-id)))
(def ^:private canvas-options
#js {:antialias true
:depth true
:stencil true
:alpha true})
(defn clear-canvas
[]
;; TODO: perform corresponding cleaning
)
(defn assign-canvas
[canvas]
(let [gl (unchecked-get internal-module "GL")
init-fn (unchecked-get internal-module "_init")
context (.getContext ^js canvas "webgl2" canvas-options)
;; Register the context with emscripten
handle (.registerContext ^js gl context #js {"majorVersion" 2})]
(.makeContextCurrent ^js gl handle)
;; Initialize Skia
(^function init-fn (.-width ^js canvas)
(.-height ^js canvas))
(set! (.-width canvas) (.-clientWidth ^js canvas))
(set! (.-height canvas) (.-clientHeight ^js canvas))))
(defonce module
(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")]
(default))))
(p/fmap (fn [module]
(set! internal-module module)
true))
(p/merr (fn [cause]
(js/console.error cause)
(p/resolved false)))))
(p/resolved false)))
(set! app.common.types.shape.impl/wasm-create-shape create-shape)
(set! app.common.types.shape.impl/wasm-use-shape use-shape)
(set! app.common.types.shape.impl/wasm-set-shape-selrect set-shape-selrect)
(set! app.common.types.shape.impl/wasm-set-shape-transform set-shape-transform)
(set! app.common.types.shape.impl/wasm-set-shape-rotation set-shape-rotation)
(set! app.common.types.shape.impl/wasm-set-shape-fills set-shape-fills)
(set! app.common.types.shape.impl/wasm-set-shape-blend-mode set-shape-blend-mode)
(set! app.common.types.shape.impl/wasm-set-shape-children set-shape-children)
(set! app.common.types.shape/wasm-enabled? enabled?)
(set! app.common.types.shape/wasm-create-shape wasm.shape/create-shape))

View file

@ -0,0 +1,185 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.render-wasm.api
"A WASM based render API"
(:require
[app.common.data.macros :as dm]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.render-wasm.helpers :as h]
[promesa.core :as p]))
(defonce internal-module #js {})
(defn create-shape
[id]
(let [buffer (uuid/get-u32 id)]
(h/call internal-module "_create_shape"
(aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
(defn use-shape
[id]
(let [buffer (uuid/get-u32 id)]
(h/call internal-module "_use_shape"
(aget buffer 0)
(aget buffer 1)
(aget buffer 2)
(aget buffer 3))))
(defn set-shape-selrect
[selrect]
(h/call internal-module "_set_shape_selrect"
(dm/get-prop selrect :x1)
(dm/get-prop selrect :y1)
(dm/get-prop selrect :x2)
(dm/get-prop selrect :y2)))
(defn set-shape-transform
[transform]
(h/call internal-module "_set_shape_transform"
(dm/get-prop transform :a)
(dm/get-prop transform :b)
(dm/get-prop transform :c)
(dm/get-prop transform :d)
(dm/get-prop transform :e)
(dm/get-prop transform :f)))
(defn set-shape-rotation
[rotation]
(h/call internal-module "_set_shape_rotation" rotation))
(defn set-shape-children
[shape-ids]
(h/call internal-module "_clear_shape_children")
(run! (fn [id]
(let [buffer (uuid/get-u32 id)]
(h/call internal-module "_add_shape_child"
(aget buffer 0)
(aget buffer 1)
(aget buffer 2)
(aget buffer 3))))
shape-ids))
(defn set-shape-fills
[fills]
(h/call internal-module "_clear_shape_fills")
(run! (fn [fill]
(let [opacity (:fill-opacity fill)
color (:fill-color fill)]
(when ^boolean color
(let [rgb (js/parseInt (subs color 1) 16)
r (bit-shift-right rgb 16)
g (bit-and (bit-shift-right rgb 8) 255)
b (bit-and rgb 255)]
(h/call internal-module "_add_shape_solid_fill" r g b opacity)))))
fills))
(defn- translate-blend-mode
[blend-mode]
(case blend-mode
:normal 3
:darken 16
:multiply 24
:color-burn 19
:lighten 17
:screen 14
:color-dodge 18
:overlay 15
:soft-light 21
:hard-light 20
:difference 22
:exclusion 23
:hue 25
:saturation 26
:color 27
:luminosity 28
3))
(defn set-shape-blend-mode
[blend-mode]
;; These values correspond to skia::BlendMode representation
;; https://rust-skia.github.io/doc/skia_safe/enum.BlendMode.html
(h/call internal-module "_set_shape_blend_mode" (translate-blend-mode blend-mode)))
(defn set-objects
[objects]
(let [shapes (into [] (vals objects))
total-shapes (count shapes)]
(loop [index 0]
(when (< index total-shapes)
(let [shape (nth shapes index)
id (dm/get-prop shape :id)
selrect (dm/get-prop shape :selrect)
rotation (dm/get-prop shape :rotation)
transform (dm/get-prop shape :transform)
fills (dm/get-prop shape :fills)
children (dm/get-prop shape :shapes)
blend-mode (dm/get-prop shape :blend-mode)]
(use-shape id)
(set-shape-selrect selrect)
(set-shape-rotation rotation)
(set-shape-transform transform)
(set-shape-fills fills)
(set-shape-blend-mode blend-mode)
(set-shape-children children)
(recur (inc index)))))))
(defn draw-objects
[zoom vbox]
(js/requestAnimationFrame
(fn []
(let [pan-x (- (dm/get-prop vbox :x))
pan-y (- (dm/get-prop vbox :y))]
(h/call internal-module "_draw_all_shapes" zoom pan-x pan-y)))))
(defn cancel-draw
[frame-id]
(when (some? frame-id)
(js/cancelAnimationFrame frame-id)))
(def ^:private canvas-options
#js {:antialias true
:depth true
:stencil true
:alpha true})
(defn clear-canvas
[]
;; TODO: perform corresponding cleaning
)
(defn assign-canvas
[canvas]
(let [gl (unchecked-get internal-module "GL")
init-fn (unchecked-get internal-module "_init")
context (.getContext ^js canvas "webgl2" canvas-options)
;; Register the context with emscripten
handle (.registerContext ^js gl context #js {"majorVersion" 2})]
(.makeContextCurrent ^js gl handle)
;; Initialize Skia
(^function init-fn (.-width ^js canvas)
(.-height ^js canvas))
(set! (.-width canvas) (.-clientWidth ^js canvas))
(set! (.-height canvas) (.-clientHeight ^js canvas))))
(defonce module
(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")]
(default))))
(p/fmap (fn [module]
(set! internal-module module)
true))
(p/merr (fn [cause]
(js/console.error cause)
(p/resolved false)))))
(p/resolved false)))

View file

@ -0,0 +1,16 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.render-wasm.helpers
#?(:cljs (:require-macros [app.render-wasm.helpers])))
(defmacro call
"A helper for easy call wasm defined function in a module."
[module name & params]
(let [fn-sym (with-meta (gensym "fn-") {:tag 'function})]
`(let [~fn-sym (cljs.core/unchecked-get ~module ~name)]
(~fn-sym ~@params))))

View file

@ -0,0 +1,141 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.render-wasm.shape
(:require
[app.common.transit :as t]
[app.common.types.shape :as shape]
[app.render-wasm.api :as api]
[clojure.core :as c]
[cuerdas.core :as str]))
(declare ^:private impl-assoc)
(declare ^:private impl-conj)
(declare ^:private impl-dissoc)
(deftype ShapeProxy [delegate]
Object
(toString [coll]
(str "{" (str/join ", " (for [[k v] coll] (str k " " v))) "}"))
(equiv [this other]
(-equiv this other))
;; Marker protocol
shape/IShape
IWithMeta
(-with-meta [_ meta]
(ShapeProxy. (with-meta delegate meta)))
IMeta
(-meta [_] (meta delegate))
ICollection
(-conj [coll entry]
(impl-conj coll entry))
IEquiv
(-equiv [coll other]
(c/equiv-map coll other))
IHash
(-hash [coll] (hash (into {} coll)))
ISequential
ISeqable
(-seq [_]
(c/-seq delegate))
ICounted
(-count [_]
(+ 1 (count delegate)))
ILookup
(-lookup [coll k]
(-lookup coll k nil))
(-lookup [_ k not-found]
(c/-lookup delegate k not-found))
IFind
(-find [_ k]
(c/-find delegate k))
IAssociative
(-assoc [coll k v]
(impl-assoc coll k v))
(-contains-key? [_ k]
(contains? delegate k))
IMap
(-dissoc [coll k]
(impl-dissoc coll k))
IFn
(-invoke [coll k]
(-lookup coll k))
(-invoke [coll k not-found]
(-lookup coll k not-found))
IPrintWithWriter
(-pr-writer [_ writer _]
(-write writer (str "#penpot/shape " (:id delegate)))))
;; --- SHAPE IMPL
(defn- impl-assoc
[self k v]
(when ^boolean shape/*wasm-sync*
(api/use-shape (:id self))
(case k
:selrect (api/set-shape-selrect v)
:rotation (api/set-shape-rotation v)
:transform (api/set-shape-transform v)
:fills (api/set-shape-fills v)
:blend-mode (api/set-shape-blend-mode v)
:shapes (api/set-shape-children v)
nil))
(let [delegate (.-delegate ^ShapeProxy self)
delegate' (assoc delegate k v)]
(if (identical? delegate' delegate)
self
(ShapeProxy. delegate'))))
(defn- impl-dissoc
[self k]
(let [delegate (.-delegate ^ShapeProxy self)
delegate' (dissoc delegate k)]
(if (identical? delegate delegate')
self
(ShapeProxy. delegate'))))
(defn- impl-conj
[self entry]
(if (vector? entry)
(-assoc self (-nth entry 0) (-nth entry 1))
(loop [ret self es (seq entry)]
(if (nil? es)
ret
(let [e (first es)]
(if (vector? e)
(recur (-assoc ret (-nth e 0) (-nth e 1))
(next es))
(throw (js/Error. "conj on a map takes map entries or seqables of map entries"))))))))
(defn create-shape
"Instanciate a shape from a map"
[attrs]
(ShapeProxy. attrs))
(t/add-handlers!
;; We only add a write handler, read handler uses the dynamic dispatch
{:id "shape"
:class ShapeProxy
:wfn #(into {} %)})