0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-12 10:09:03 -05:00

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)
w (dm/get-prop rect :width)
h (dm/get-prop rect :height)]
(rc/assoc! rect
:x1 x
:y1 y
:x2 (+ x w)
:y2 (+ y h)))
(assoc rect
:x1 x
:y1 y
:x2 (+ x w)
:y2 (+ y h)))
:corners
(let [x1 (dm/get-prop rect :x1)
y1 (dm/get-prop rect :y1)
x2 (dm/get-prop rect :x2)
y2 (dm/get-prop rect :y2)]
(rc/assoc! rect
:x (mth/min x1 x2)
:y (mth/min y1 y2)
:width (mth/abs (- x2 x1))
:height (mth/abs (- y2 y1))))))
(assoc rect
:x (mth/min x1 x2)
:y (mth/min y1 y2)
:width (mth/abs (- x2 x1))
:height (mth/abs (- y2 y1))))))
(defn close-rect?
[rect1 rect2]

View file

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

View file

@ -19,12 +19,12 @@
[app.common.schema.generators :as sg]
[app.common.transit :as t]
[app.common.types.color :as ctc]
[app.common.types.shape.impl :as impl]
[app.common.types.grid :as ctg]
[app.common.types.plugins :as ctpg]
[app.common.types.shape.attrs :refer [default-color]]
[app.common.types.shape.blur :as ctsb]
[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.layout :as ctsl]
[app.common.types.shape.path :as ctsp]

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
pub mod render;
pub mod shapes;
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);
}
#[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]
pub unsafe extern "C" fn flush(state: *mut State) {
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);
}
#[no_mangle]
pub unsafe extern "C" fn shapes_buffer() -> *mut shapes::Shape {
let ptr = shapes::SHAPES_BUFFER.as_mut_ptr();
return ptr;
}
fn main() {
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);
}
}