Write shapes directly to wasm memory

This commit is contained in:
Belén Albeza 2024-10-25 15:00:57 +02:00 committed by Andrey Antukh
parent 29e0964ebc
commit 4623f36042
9 changed files with 186 additions and 139 deletions

View file

@ -158,22 +158,22 @@
y (dm/get-prop rect :y) y (dm/get-prop rect :y)
w (dm/get-prop rect :width) w (dm/get-prop rect :width)
h (dm/get-prop rect :height)] h (dm/get-prop rect :height)]
(rc/assoc! rect (assoc rect
:x1 x :x1 x
:y1 y :y1 y
:x2 (+ x w) :x2 (+ x w)
:y2 (+ y h))) :y2 (+ y h)))
:corners :corners
(let [x1 (dm/get-prop rect :x1) (let [x1 (dm/get-prop rect :x1)
y1 (dm/get-prop rect :y1) y1 (dm/get-prop rect :y1)
x2 (dm/get-prop rect :x2) x2 (dm/get-prop rect :x2)
y2 (dm/get-prop rect :y2)] y2 (dm/get-prop rect :y2)]
(rc/assoc! rect (assoc rect
:x (mth/min x1 x2) :x (mth/min x1 x2)
:y (mth/min y1 y2) :y (mth/min y1 y2)
:width (mth/abs (- x2 x1)) :width (mth/abs (- x2 x1))
:height (mth/abs (- y2 y1)))))) :height (mth/abs (- y2 y1))))))
(defn close-rect? (defn close-rect?
[rect1 rect2] [rect1 rect2]

View file

@ -403,30 +403,30 @@
nil))) nil)))
~rsym))) ~rsym)))
;; (defmacro clone (defmacro clone
;; [ssym] [ssym]
;; (if (:ns &env) (if (:ns &env)
;; `(cljs.core/clone ~ssym) `(cljs.core/clone ~ssym)
;; ssym)) ssym))
;; (defmacro assoc! (defmacro assoc!
;; "A record specific update operation" "A record specific update operation"
;; [ssym & pairs] [ssym & pairs]
;; (if (:ns &env) (if (:ns &env)
;; (let [pairs (partition-all 2 pairs)] (let [pairs (partition-all 2 pairs)]
;; `(-> ~ssym `(-> ~ssym
;; ~@(map (fn [[ks vs]] ~@(map (fn [[ks vs]]
;; `(cljs.core/-assoc! ~ks ~vs)) `(cljs.core/-assoc! ~ks ~vs))
;; pairs))) pairs)))
;; `(assoc ~ssym ~@pairs))) `(assoc ~ssym ~@pairs)))
;; (defmacro update! (defmacro update!
;; "A record specific update operation" "A record specific update operation"
;; [ssym ksym f & params] [ssym ksym f & params]
;; (if (:ns &env) (if (:ns &env)
;; (let [ssym (with-meta ssym {:tag 'js})] (let [ssym (with-meta ssym {:tag 'js})]
;; `(cljs.core/assoc! ~ssym ~ksym (~f (. ~ssym ~(property-symbol ksym)) ~@params))) `(cljs.core/assoc! ~ssym ~ksym (~f (. ~ssym ~(property-symbol ksym)) ~@params)))
;; `(update ~ssym ~ksym ~f ~@params))) `(update ~ssym ~ksym ~f ~@params)))
(defmacro define-properties! (defmacro define-properties!
"Define properties in the prototype with `.defineProperty`" "Define properties in the prototype with `.defineProperty`"

View file

@ -19,12 +19,12 @@
[app.common.schema.generators :as sg] [app.common.schema.generators :as sg]
[app.common.transit :as t] [app.common.transit :as t]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.shape.impl :as impl]
[app.common.types.grid :as ctg] [app.common.types.grid :as ctg]
[app.common.types.plugins :as ctpg] [app.common.types.plugins :as ctpg]
[app.common.types.shape.attrs :refer [default-color]] [app.common.types.shape.attrs :refer [default-color]]
[app.common.types.shape.blur :as ctsb] [app.common.types.shape.blur :as ctsb]
[app.common.types.shape.export :as ctse] [app.common.types.shape.export :as ctse]
[app.common.types.shape.impl :as impl]
[app.common.types.shape.interactions :as ctsi] [app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctsl] [app.common.types.shape.layout :as ctsl]
[app.common.types.shape.path :as ctsp] [app.common.types.shape.path :as ctsp]

View file

@ -10,18 +10,20 @@
[app.common.colors :as clr] [app.common.colors :as clr]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.rect :as grc]
[app.common.record :as cr] [app.common.record :as cr]
[app.common.schema :as sm] [app.common.schema :as sm]
[app.common.schema.generators :as sg] [app.common.schema.generators :as sg]
[app.common.transit :as t] [app.common.transit :as t]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.common.geom.rect :as grc]
[cuerdas.core :as str]
[clojure.core :as c] [clojure.core :as c]
[clojure.set :as set])) [clojure.set :as set]
[cuerdas.core :as str]))
(def ArrayBuffer js/ArrayBuffer) #?(:cljs
(def Float32Array js/Float32Array) (do
(def ArrayBuffer js/ArrayBuffer)
(def Float32Array js/Float32Array)))
(cr/defrecord Shape [id name type x y width height rotation selrect points (cr/defrecord Shape [id name type x y width height rotation selrect points
transform transform-inverse parent-id frame-id flip-x flip-y]) transform transform-inverse parent-id frame-id flip-x flip-y])
@ -49,77 +51,77 @@
;; (let [bf32 (clone-float32-array buffer)] ;; (let [bf32 (clone-float32-array buffer)]
;; (ShapeWithBuffer. bf32 delegate))) ;; (ShapeWithBuffer. bf32 delegate)))
IWithMeta IWithMeta
(-with-meta [coll meta] (-with-meta [coll meta]
(ShapeWithBuffer. buffer (with-meta delegate meta))) (ShapeWithBuffer. buffer (with-meta delegate meta)))
IMeta IMeta
(-meta [coll] (meta delegate)) (-meta [coll] (meta delegate))
ICollection ICollection
(-conj [coll entry] (-conj [coll entry]
(impl-conj coll entry)) (impl-conj coll entry))
IEquiv IEquiv
(-equiv [coll other] (-equiv [coll other]
(c/equiv-map coll other)) (c/equiv-map coll other))
IHash IHash
(-hash [coll] (hash (into {} coll))) (-hash [coll] (hash (into {} coll)))
ISequential ISequential
ISeqable ISeqable
(-seq [coll] (-seq [coll]
(cons (find coll :selrect) (cons (find coll :selrect)
(seq delegate))) (seq delegate)))
ICounted ICounted
(-count [coll] (-count [coll]
(+ 1 (count delegate))) (+ 1 (count delegate)))
ILookup ILookup
(-lookup [coll k] (-lookup [coll k]
(-lookup coll k nil)) (-lookup coll k nil))
(-lookup [coll k not-found] (-lookup [coll k not-found]
(if (= k :selrect) (if (= k :selrect)
(read-selrect buffer) (read-selrect buffer)
(c/-lookup delegate k not-found))) (c/-lookup delegate k not-found)))
IFind IFind
(-find [coll k] (-find [coll k]
(if (= k :selrect) (if (= k :selrect)
(c/MapEntry. k (read-selrect buffer) nil) ; Replace with lazy MapEntry (c/MapEntry. k (read-selrect buffer) nil) ; Replace with lazy MapEntry
(c/-find delegate k))) (c/-find delegate k)))
IAssociative IAssociative
(-assoc [coll k v] (-assoc [coll k v]
(impl-assoc coll k v)) (impl-assoc coll k v))
(-contains-key? [coll k] (-contains-key? [coll k]
(or (= k :selrect) (or (= k :selrect)
(contains? delegate k))) (contains? delegate k)))
IMap IMap
(-dissoc [coll k] (-dissoc [coll k]
(impl-dissoc coll k)) (impl-dissoc coll k))
IFn IFn
(-invoke [coll k] (-invoke [coll k]
(-lookup coll k)) (-lookup coll k))
(-invoke [coll k not-found] (-invoke [coll k not-found]
(-lookup coll k not-found)) (-lookup coll k not-found))
IPrintWithWriter IPrintWithWriter
(-pr-writer [coll writer opts] (-pr-writer [coll writer opts]
(-write writer (str "#penpot/shape " (:id delegate)))))) (-write writer (str "#penpot/shape " (:id delegate))))))
(defn shape? (defn shape?
[o] [o]
(or (instance? Shape o) (or (instance? Shape o)
(instance? ShapeWithBuffer o))) #?(:cljs (instance? ShapeWithBuffer o))))
;; --- SHAPE IMPL ;; --- SHAPE IMPL
@ -211,12 +213,12 @@
:rfn #?(:cljs create-shape :rfn #?(:cljs create-shape
:clj map->Shape)}) :clj map->Shape)})
(t/add-handlers! #?(:cljs (t/add-handlers!
{:id "shape" {:id "shape"
:class ShapeWithBuffer :class ShapeWithBuffer
:wfn #(into {} %) :wfn #(into {} %)
:rfn #?(:cljs create-shape :rfn #?(:cljs create-shape
:clj map->Shape)}) :clj map->Shape)}))

View file

@ -72,7 +72,7 @@
(cr/update! :x2 + (:x delta)) (cr/update! :x2 + (:x delta))
(cr/update! :y2 + (:y delta))) (cr/update! :y2 + (:y delta)))
selrect (if ^boolean space? selrect (if ^boolean space?
(-> selrect (-> (cr/clone selrect)
(cr/update! :x1 + (:x delta)) (cr/update! :x1 + (:x delta))
(cr/update! :y1 + (:y delta))) (cr/update! :y1 + (:y delta)))
selrect)] selrect)]

View file

@ -134,13 +134,13 @@
hover-top-frame-id (mf/use-state nil) hover-top-frame-id (mf/use-state nil)
frame-hover (mf/use-state nil) frame-hover (mf/use-state nil)
active-frames (mf/use-state #{}) active-frames (mf/use-state #{})
canvas-init? (mf/use-state false)
;; REFS ;; REFS
[viewport-ref [viewport-ref
on-viewport-ref] (create-viewport-ref) on-viewport-ref] (create-viewport-ref)
canvas-ref (mf/use-ref nil) canvas-ref (mf/use-ref nil)
canvas-init (mf/use-ref false)
;; VARS ;; VARS
disable-paste (mf/use-var false) disable-paste (mf/use-var false)
@ -277,19 +277,22 @@
(when ^boolean render.wasm/enabled? (when ^boolean render.wasm/enabled?
(mf/with-effect [] (mf/with-effect []
(time (when-let [canvas (mf/ref-val canvas-ref)] (time (when-let [canvas (mf/ref-val canvas-ref)]
(->> render.wasm/module (->> render.wasm/module
(p/fmap (fn [ready?] (p/fmap (fn [ready?]
(when ready? (when ready?
(mf/set-ref-val! canvas-init true) (reset! canvas-init? true)
(render.wasm/assign-canvas canvas))))) (render.wasm/assign-canvas canvas)))))
(fn [] (fn []
(render.wasm/clear-canvas))))) (render.wasm/clear-canvas)))))
(mf/with-effect [vbox objects-modified] (mf/with-effect [objects-modified canvas-init?]
(let [sem (when (mf/ref-val canvas-init) (when @canvas-init?
(render.wasm/draw-objects objects-modified zoom vbox))] (render.wasm/set-objects objects-modified)
(partial render.wasm/cancel-draw sem))) (render.wasm/draw-objects zoom vbox)))
) (mf/with-effect [vbox canvas-init?]
(let [frame-id (when @canvas-init? (do
(render.wasm/draw-objects zoom vbox)))]
(partial render.wasm/cancel-draw frame-id))))
(hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool drawing-path?) (hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool drawing-path?)
(hooks/setup-viewport-size vport viewport-ref) (hooks/setup-viewport-size vport viewport-ref)

View file

@ -8,6 +8,7 @@
"A WASM based render API" "A WASM based render API"
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.config :as cf] [app.config :as cf]
[promesa.core :as p])) [promesa.core :as p]))
@ -17,41 +18,31 @@
(defonce ^:dynamic internal-module #js {}) (defonce ^:dynamic internal-module #js {})
(defonce ^:dynamic internal-gpu-state #js {}) (defonce ^:dynamic internal-gpu-state #js {})
(defn draw-objects (defn set-objects [objects]
[objects zoom vbox] (let [shapes-buffer (unchecked-get internal-module "_shapes_buffer")
(let [draw-rect (unchecked-get internal-module "_draw_rect") heap (unchecked-get internal-module "HEAPF32")
translate (unchecked-get internal-module "_translate") ;; size *in bytes* for each shapes::Shape
reset-canvas (unchecked-get internal-module "_reset_canvas") rect-size 16
scale (unchecked-get internal-module "_scale") ;; TODO: remove the `take` once we have the dynamic data structure in Rust
flush (unchecked-get internal-module "_flush") supported-shapes (take 2048 (filter #(not (cfh/root? %)) (vals objects)))
gpu-state internal-gpu-state] mem (js/Float32Array. (.-buffer heap) (shapes-buffer) (* rect-size (count supported-shapes)))]
(run! (fn [[shape index]]
(.set mem (.-buffer shape) (* index rect-size)))
(zipmap supported-shapes (range)))))
(defn draw-objects
[zoom vbox]
(let [draw-all-shapes (unchecked-get internal-module "_draw_all_shapes")]
(js/requestAnimationFrame (js/requestAnimationFrame
(fn [] (fn []
(reset-canvas gpu-state) (let [pan-x (- (dm/get-prop vbox :x))
(scale gpu-state zoom zoom) pan-y (- (dm/get-prop vbox :y))]
(draw-all-shapes internal-gpu-state zoom pan-x pan-y))))))
(let [x (dm/get-prop vbox :x)
y (dm/get-prop vbox :y)]
(translate gpu-state (- x) (- y)))
(run! (fn [shape]
;; (js/console.log "render-shape" (.-buffer shape))
(let [selrect (dm/get-prop shape :selrect)
x1 (dm/get-prop selrect :x1)
y1 (dm/get-prop selrect :y1)
x2 (dm/get-prop selrect :x2)
y2 (dm/get-prop selrect :y2)]
;; (prn (:id shape) selrect)
(draw-rect gpu-state x1 y1 x2 y2)))
(vals objects))
(flush gpu-state)))))
(defn cancel-draw (defn cancel-draw
[sem] [frame-id]
(when (some? sem) (when (some? frame-id)
(js/cancelAnimationFrame sem))) (js/cancelAnimationFrame frame-id)))
(def ^:private canvas-options (def ^:private canvas-options
#js {:antialias true #js {:antialias true

View file

@ -1,4 +1,5 @@
pub mod render; pub mod render;
pub mod shapes;
use skia_safe as skia; use skia_safe as skia;
@ -33,6 +34,19 @@ pub unsafe extern "C" fn draw_rect(state: *mut State, x1: f32, y1: f32, x2: f32,
render::render_rect(&mut state.surface, r, skia::Color::RED); render::render_rect(&mut state.surface, r, skia::Color::RED);
} }
#[no_mangle]
pub unsafe extern "C" fn draw_all_shapes(state: *mut State, zoom: f32, pan_x: f32, pan_y: f32) {
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
reset_canvas(state);
scale(state, zoom, zoom);
translate(state, pan_x, pan_y);
shapes::draw_all(state);
flush(state);
}
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn flush(state: *mut State) { pub unsafe extern "C" fn flush(state: *mut State) {
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer"); let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
@ -60,6 +74,12 @@ pub unsafe extern "C" fn reset_canvas(state: *mut State) {
flush(state); flush(state);
} }
#[no_mangle]
pub unsafe extern "C" fn shapes_buffer() -> *mut shapes::Shape {
let ptr = shapes::SHAPES_BUFFER.as_mut_ptr();
return ptr;
}
fn main() { fn main() {
render::init_gl(); render::init_gl();
} }

31
render-wasm/src/shapes.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::render::{render_rect, State};
use skia_safe as skia;
#[derive(Debug, Clone, Copy)]
pub struct Selrect {
pub x1: f32,
pub y1: f32,
pub x2: f32,
pub y2: f32,
}
pub type Shape = Selrect; // temp
pub static mut SHAPES_BUFFER: [Shape; 2048] = [Selrect {
x1: 0.0,
y1: 0.0,
x2: 0.0,
y2: 0.0,
}; 2048];
pub(crate) fn draw_all(state: &mut State) {
let shapes;
unsafe {
shapes = SHAPES_BUFFER.iter();
}
for shape in shapes {
let r = skia::Rect::new(shape.x1, shape.y1, shape.x2, shape.y2);
render_rect(&mut state.surface, r, skia::Color::RED);
}
}