mirror of
https://github.com/penpot/penpot.git
synced 2025-05-29 07:26:12 +02:00
🎉 Save shape data in rust memory
This commit is contained in:
parent
48909dc3c4
commit
65ee2f9081
13 changed files with 479 additions and 297 deletions
|
@ -7,34 +7,30 @@
|
||||||
(ns app.common.types.shape.impl
|
(ns app.common.types.shape.impl
|
||||||
(:require
|
(:require
|
||||||
#?(:clj [app.common.fressian :as fres])
|
#?(:clj [app.common.fressian :as fres])
|
||||||
#?(:cljs [app.common.data.macros :as dm])
|
|
||||||
#?(:cljs [app.common.geom.rect :as grc])
|
|
||||||
#?(:cljs [cuerdas.core :as str])
|
#?(:cljs [cuerdas.core :as str])
|
||||||
[app.common.record :as cr]
|
[app.common.record :as cr]
|
||||||
[app.common.transit :as t]
|
[app.common.transit :as t]
|
||||||
[clojure.core :as c]))
|
[clojure.core :as c]))
|
||||||
|
|
||||||
(def enabled-wasm-ready-shape false)
|
(defonce ^:dynamic *wasm-sync* false)
|
||||||
|
(defonce enabled-wasm-ready-shape false)
|
||||||
#?(:cljs
|
(defonce wasm-create-shape (constantly nil))
|
||||||
(do
|
(defonce wasm-use-shape (constantly nil))
|
||||||
(def ArrayBuffer js/ArrayBuffer)
|
(defonce wasm-set-shape-selrect (constantly nil))
|
||||||
(def Float32Array js/Float32Array)))
|
(defonce wasm-set-shape-transform (constantly nil))
|
||||||
|
(defonce wasm-set-shape-rotation (constantly nil))
|
||||||
|
|
||||||
(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])
|
||||||
|
|
||||||
(declare ^:private clone-f32-array)
|
|
||||||
(declare ^:private impl-assoc)
|
(declare ^:private impl-assoc)
|
||||||
(declare ^:private impl-conj)
|
(declare ^:private impl-conj)
|
||||||
(declare ^:private impl-dissoc)
|
(declare ^:private impl-dissoc)
|
||||||
(declare ^:private read-selrect)
|
|
||||||
(declare ^:private write-selrect)
|
|
||||||
|
|
||||||
;; TODO: implement lazy MapEntry
|
;; TODO: implement lazy MapEntry
|
||||||
|
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(deftype ShapeWithBuffer [buffer delegate]
|
(deftype ShapeProxy [delegate]
|
||||||
Object
|
Object
|
||||||
(toString [coll]
|
(toString [coll]
|
||||||
(str "{" (str/join ", " (for [[k v] coll] (str k " " v))) "}"))
|
(str "{" (str/join ", " (for [[k v] coll] (str k " " v))) "}"))
|
||||||
|
@ -42,14 +38,9 @@
|
||||||
(equiv [this other]
|
(equiv [this other]
|
||||||
(-equiv this other))
|
(-equiv this other))
|
||||||
|
|
||||||
;; ICloneable
|
|
||||||
;; (-clone [_]
|
|
||||||
;; (let [bf32 (clone-float32-array buffer)]
|
|
||||||
;; (ShapeWithBuffer. bf32 delegate)))
|
|
||||||
|
|
||||||
IWithMeta
|
IWithMeta
|
||||||
(-with-meta [_ meta]
|
(-with-meta [_ meta]
|
||||||
(ShapeWithBuffer. buffer (with-meta delegate meta)))
|
(ShapeProxy. (with-meta delegate meta)))
|
||||||
|
|
||||||
IMeta
|
IMeta
|
||||||
(-meta [_] (meta delegate))
|
(-meta [_] (meta delegate))
|
||||||
|
@ -68,9 +59,8 @@
|
||||||
ISequential
|
ISequential
|
||||||
|
|
||||||
ISeqable
|
ISeqable
|
||||||
(-seq [coll]
|
(-seq [_]
|
||||||
(cons (find coll :selrect)
|
(c/-seq delegate))
|
||||||
(seq delegate)))
|
|
||||||
|
|
||||||
ICounted
|
ICounted
|
||||||
(-count [_]
|
(-count [_]
|
||||||
|
@ -81,23 +71,18 @@
|
||||||
(-lookup coll k nil))
|
(-lookup coll k nil))
|
||||||
|
|
||||||
(-lookup [_ k not-found]
|
(-lookup [_ k not-found]
|
||||||
(if (= k :selrect)
|
(c/-lookup delegate k not-found))
|
||||||
(read-selrect buffer)
|
|
||||||
(c/-lookup delegate k not-found)))
|
|
||||||
|
|
||||||
IFind
|
IFind
|
||||||
(-find [_ k]
|
(-find [_ k]
|
||||||
(if (= k :selrect)
|
(c/-find delegate k))
|
||||||
(c/MapEntry. k (read-selrect buffer) nil) ; Replace with lazy MapEntry
|
|
||||||
(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? [_ k]
|
(-contains-key? [_ k]
|
||||||
(or (= k :selrect)
|
(contains? delegate k))
|
||||||
(contains? delegate k)))
|
|
||||||
|
|
||||||
IMap
|
IMap
|
||||||
(-dissoc [coll k]
|
(-dissoc [coll k]
|
||||||
|
@ -118,64 +103,34 @@
|
||||||
[o]
|
[o]
|
||||||
#?(:clj (instance? Shape o)
|
#?(:clj (instance? Shape o)
|
||||||
:cljs (or (instance? Shape o)
|
:cljs (or (instance? Shape o)
|
||||||
(instance? ShapeWithBuffer o))))
|
(instance? ShapeProxy o))))
|
||||||
|
|
||||||
;; --- SHAPE IMPL
|
;; --- SHAPE IMPL
|
||||||
|
|
||||||
#?(:cljs
|
|
||||||
(defn- clone-f32-array
|
|
||||||
[^Float32Array src]
|
|
||||||
(let [copy (new Float32Array (.-length src))]
|
|
||||||
(.set copy src)
|
|
||||||
copy)))
|
|
||||||
|
|
||||||
#?(:cljs
|
|
||||||
(defn- write-selrect
|
|
||||||
"Write the selrect into the buffer"
|
|
||||||
[data selrect]
|
|
||||||
(assert (instance? Float32Array data) "expected instance of float32array")
|
|
||||||
|
|
||||||
(aset data 0 (dm/get-prop selrect :x1))
|
|
||||||
(aset data 1 (dm/get-prop selrect :y1))
|
|
||||||
(aset data 2 (dm/get-prop selrect :x2))
|
|
||||||
(aset data 3 (dm/get-prop selrect :y2))))
|
|
||||||
|
|
||||||
#?(:cljs
|
|
||||||
(defn- read-selrect
|
|
||||||
"Read selrect from internal buffer"
|
|
||||||
[^Float32Array buffer]
|
|
||||||
(let [x1 (aget buffer 0)
|
|
||||||
y1 (aget buffer 1)
|
|
||||||
x2 (aget buffer 2)
|
|
||||||
y2 (aget buffer 3)]
|
|
||||||
(grc/make-rect x1 y1
|
|
||||||
(- x2 x1)
|
|
||||||
(- y2 y1)))))
|
|
||||||
|
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(defn- impl-assoc
|
(defn- impl-assoc
|
||||||
[coll k v]
|
[coll k v]
|
||||||
(if (= k :selrect)
|
(when *wasm-sync*
|
||||||
(let [buffer (clone-f32-array (.-buffer coll))]
|
(wasm-use-shape (:id coll))
|
||||||
(write-selrect buffer v)
|
(case k
|
||||||
(ShapeWithBuffer. buffer (.-delegate ^ShapeWithBuffer coll)))
|
:selrect (wasm-set-shape-selrect v)
|
||||||
|
:rotation (wasm-set-shape-rotation v)
|
||||||
(let [delegate (.-delegate ^ShapeWithBuffer coll)
|
:transform (wasm-set-shape-transform v)
|
||||||
delegate' (assoc delegate k v)]
|
nil))
|
||||||
(if (identical? delegate' delegate)
|
(let [delegate (.-delegate ^ShapeProxy coll)
|
||||||
coll
|
delegate' (assoc delegate k v)]
|
||||||
(let [buffer (clone-f32-array (.-buffer coll))]
|
(if (identical? delegate' delegate)
|
||||||
(ShapeWithBuffer. buffer delegate')))))))
|
coll
|
||||||
|
(ShapeProxy. delegate')))))
|
||||||
|
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(defn- impl-dissoc
|
(defn- impl-dissoc
|
||||||
[coll k]
|
[coll k]
|
||||||
(let [delegate (.-delegate ^ShapeWithBuffer coll)
|
(let [delegate (.-delegate ^ShapeProxy coll)
|
||||||
delegate' (dissoc delegate k)]
|
delegate' (dissoc delegate k)]
|
||||||
(if (identical? delegate delegate')
|
(if (identical? delegate delegate')
|
||||||
coll
|
coll
|
||||||
(let [buffer (clone-f32-array (.-buffer coll))]
|
(ShapeProxy. delegate')))))
|
||||||
(ShapeWithBuffer. buffer delegate'))))))
|
|
||||||
|
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(defn- impl-conj
|
(defn- impl-conj
|
||||||
|
@ -196,10 +151,7 @@
|
||||||
[attrs]
|
[attrs]
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(if enabled-wasm-ready-shape
|
(if enabled-wasm-ready-shape
|
||||||
(let [selrect (:selrect attrs)
|
(ShapeProxy. attrs)
|
||||||
buffer (new Float32Array 4)]
|
|
||||||
(write-selrect buffer selrect)
|
|
||||||
(ShapeWithBuffer. buffer (dissoc attrs :selrect)))
|
|
||||||
(map->Shape attrs))
|
(map->Shape attrs))
|
||||||
|
|
||||||
:clj (map->Shape attrs)))
|
:clj (map->Shape attrs)))
|
||||||
|
@ -215,7 +167,7 @@
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(t/add-handlers!
|
(t/add-handlers!
|
||||||
{:id "shape"
|
{:id "shape"
|
||||||
:class ShapeWithBuffer
|
:class ShapeProxy
|
||||||
:wfn #(into {} %)
|
:wfn #(into {} %)
|
||||||
:rfn create-shape}))
|
:rfn create-shape}))
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,11 @@
|
||||||
[id]
|
[id]
|
||||||
(impl/short-v8 (dm/str id))))
|
(impl/short-v8 (dm/str id))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn uuid->u32
|
||||||
|
[id]
|
||||||
|
(impl/get-u32 (dm/str id))))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(defn hash-int
|
(defn hash-int
|
||||||
[id]
|
[id]
|
||||||
|
|
|
@ -122,7 +122,6 @@ goog.scope(function() {
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
self.v4 = (function () {
|
self.v4 = (function () {
|
||||||
const arr = new Uint8Array(16);
|
const arr = new Uint8Array(16);
|
||||||
|
|
||||||
|
@ -211,7 +210,6 @@ goog.scope(function() {
|
||||||
return factory;
|
return factory;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
self.short_v8 = function(uuid) {
|
self.short_v8 = function(uuid) {
|
||||||
const buff = encoding.hexToBuffer(uuid);
|
const buff = encoding.hexToBuffer(uuid);
|
||||||
const short = new Uint8Array(buff, 4);
|
const short = new Uint8Array(buff, 4);
|
||||||
|
@ -222,5 +220,41 @@ goog.scope(function() {
|
||||||
const most = mostSigBits.toString("16").padStart(16, "0");
|
const most = mostSigBits.toString("16").padStart(16, "0");
|
||||||
const least = leastSigBits.toString("16").padStart(16, "0");
|
const least = leastSigBits.toString("16").padStart(16, "0");
|
||||||
return `${most.substring(0, 8)}-${most.substring(8, 12)}-${most.substring(12)}-${least.substring(0, 4)}-${least.substring(4)}`;
|
return `${most.substring(0, 8)}-${most.substring(8, 12)}-${most.substring(12)}-${least.substring(0, 4)}-${least.substring(4)}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
self.get_u32 = (function() {
|
||||||
|
const UUID_BYTE_SIZE = 16;
|
||||||
|
const ab = new ArrayBuffer(UUID_BYTE_SIZE);
|
||||||
|
const u32buffer = new Uint32Array(ab);
|
||||||
|
const HYPHEN = '-'.charCodeAt(0);
|
||||||
|
const A = 'a'.charCodeAt(0);
|
||||||
|
const A_SUB = A - 10;
|
||||||
|
const ZERO = '0'.charCodeAt(0);
|
||||||
|
const MAX_DIGIT = 8;
|
||||||
|
const HALF_BITS = 4;
|
||||||
|
return function(uuid) {
|
||||||
|
let digit = 0;
|
||||||
|
let numDigit = 0;
|
||||||
|
let u32index = 0;
|
||||||
|
let u32 = 0;
|
||||||
|
for (let i = 0; i < uuid.length; i++) {
|
||||||
|
const charCode = uuid.charCodeAt(i);
|
||||||
|
if (charCode === HYPHEN) continue;
|
||||||
|
if (charCode >= A) {
|
||||||
|
digit = charCode - A_SUB;
|
||||||
|
} else {
|
||||||
|
digit = charCode - ZERO;
|
||||||
|
}
|
||||||
|
numDigit++;
|
||||||
|
const bitPos = (MAX_DIGIT - numDigit) * HALF_BITS;
|
||||||
|
u32 |= (digit << bitPos);
|
||||||
|
if (numDigit === MAX_DIGIT) {
|
||||||
|
u32buffer[u32index++] = u32;
|
||||||
|
u32 = 0;
|
||||||
|
numDigit = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return u32buffer;
|
||||||
|
}
|
||||||
|
})();
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.logic.shapes :as cls]
|
[app.common.logic.shapes :as cls]
|
||||||
|
[app.common.types.shape.impl :as shape.impl]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
[app.common.types.shape.radius :as ctsr]
|
[app.common.types.shape.radius :as ctsr]
|
||||||
[app.common.types.tokens-lib :as ctob]
|
[app.common.types.tokens-lib :as ctob]
|
||||||
|
@ -248,19 +249,20 @@
|
||||||
(fn [value attr]
|
(fn [value attr]
|
||||||
(let [token-value (wtc/maybe-resolve-token-value value)
|
(let [token-value (wtc/maybe-resolve-token-value value)
|
||||||
undo-id (js/Symbol)]
|
undo-id (js/Symbol)]
|
||||||
(if-not design-tokens?
|
(binding [shape.impl/*wasm-sync* true]
|
||||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
(if-not design-tokens?
|
||||||
(udw/update-dimensions ids attr (or token-value value)))
|
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
(udw/update-dimensions ids attr (or token-value value)))
|
||||||
(dwu/start-undo-transaction undo-id)
|
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||||
(dwsh/update-shapes ids
|
(dwu/start-undo-transaction undo-id)
|
||||||
(if token-value
|
(dwsh/update-shapes ids
|
||||||
#(assoc-in % [:applied-tokens attr] (:id value))
|
(if token-value
|
||||||
#(d/dissoc-in % [:applied-tokens attr]))
|
#(assoc-in % [:applied-tokens attr] (:id value))
|
||||||
{:reg-objects? true
|
#(d/dissoc-in % [:applied-tokens attr]))
|
||||||
:attrs [:applied-tokens]})
|
{:reg-objects? true
|
||||||
(udw/update-dimensions ids attr (or token-value value))
|
:attrs [:applied-tokens]})
|
||||||
(dwu/commit-undo-transaction undo-id))))))
|
(udw/update-dimensions ids attr (or token-value value))
|
||||||
|
(dwu/commit-undo-transaction undo-id)))))))
|
||||||
|
|
||||||
on-proportion-lock-change
|
on-proportion-lock-change
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
@ -283,7 +285,8 @@
|
||||||
(mf/deps ids)
|
(mf/deps ids)
|
||||||
(fn [value attr]
|
(fn [value attr]
|
||||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||||
(doall (map #(do-position-change %1 %2 value attr) shapes frames))))
|
(binding [shape.impl/*wasm-sync* true]
|
||||||
|
(doall (map #(do-position-change %1 %2 value attr) shapes frames)))))
|
||||||
|
|
||||||
;; ROTATION
|
;; ROTATION
|
||||||
|
|
||||||
|
@ -291,8 +294,9 @@
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps ids)
|
(mf/deps ids)
|
||||||
(fn [value]
|
(fn [value]
|
||||||
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
(binding [shape.impl/*wasm-sync* true]
|
||||||
(udw/increase-rotation ids value))))
|
(st/emit! (udw/trigger-bounding-box-cloaking ids)
|
||||||
|
(udw/increase-rotation ids value)))))
|
||||||
|
|
||||||
;; RADIUS
|
;; RADIUS
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
[app.common.files.helpers :as cfh]
|
[app.common.files.helpers :as cfh]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.types.shape-tree :as ctt]
|
[app.common.types.shape-tree :as ctt]
|
||||||
|
[app.common.types.shape.impl :as shape.impl]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
[app.main.data.workspace.modifiers :as dwm]
|
[app.main.data.workspace.modifiers :as dwm]
|
||||||
[app.main.features :as features]
|
[app.main.features :as features]
|
||||||
|
@ -111,10 +112,9 @@
|
||||||
modifiers (mf/deref refs/workspace-modifiers)
|
modifiers (mf/deref refs/workspace-modifiers)
|
||||||
text-modifiers (mf/deref refs/workspace-text-modifier)
|
text-modifiers (mf/deref refs/workspace-text-modifier)
|
||||||
|
|
||||||
render-context-lost? (mf/deref refs/render-context-lost?)
|
|
||||||
|
|
||||||
objects-modified (mf/with-memo [base-objects text-modifiers modifiers]
|
objects-modified (mf/with-memo [base-objects text-modifiers modifiers]
|
||||||
(apply-modifiers-to-selected selected base-objects text-modifiers modifiers))
|
(binding [shape.impl/*wasm-sync* true]
|
||||||
|
(apply-modifiers-to-selected selected base-objects text-modifiers modifiers)))
|
||||||
|
|
||||||
selected-shapes (keep (d/getf objects-modified) selected)
|
selected-shapes (keep (d/getf objects-modified) selected)
|
||||||
|
|
||||||
|
@ -177,8 +177,6 @@
|
||||||
|
|
||||||
mode-inspect? (= options-mode :inspect)
|
mode-inspect? (= options-mode :inspect)
|
||||||
|
|
||||||
on-render-restore-context #(.reload js/location)
|
|
||||||
|
|
||||||
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?)
|
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?)
|
||||||
on-context-menu (actions/on-context-menu hover hover-ids read-only?)
|
on-context-menu (actions/on-context-menu hover hover-ids read-only?)
|
||||||
on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id drawing-path? base-objects edition drawing-tool z? read-only?)
|
on-double-click (actions/on-double-click hover hover-ids hover-top-frame-id drawing-path? base-objects edition drawing-tool z? read-only?)
|
||||||
|
@ -281,18 +279,18 @@
|
||||||
(p/fmap (fn [ready?]
|
(p/fmap (fn [ready?]
|
||||||
(when ready?
|
(when ready?
|
||||||
(reset! canvas-init? true)
|
(reset! canvas-init? true)
|
||||||
(render.wasm/setup-canvas canvas)))))
|
(render.wasm/assign-canvas canvas)))))
|
||||||
(fn []
|
(fn []
|
||||||
(render.wasm/dispose-canvas canvas))))
|
(render.wasm/clear-canvas))))
|
||||||
|
|
||||||
(mf/with-effect [objects-modified canvas-init?]
|
(mf/with-effect [base-objects modifiers canvas-init?]
|
||||||
(when @canvas-init?
|
(when @canvas-init?
|
||||||
(render.wasm/set-objects objects-modified)
|
;; FIXME: review this to not call it but still do the first draw
|
||||||
|
;; (render.wasm/set-objects base-objects modifiers)
|
||||||
(render.wasm/draw-objects zoom vbox)))
|
(render.wasm/draw-objects zoom vbox)))
|
||||||
|
|
||||||
(mf/with-effect [vbox canvas-init?]
|
(mf/with-effect [vbox canvas-init?]
|
||||||
(let [frame-id (when @canvas-init? (do
|
(let [frame-id (when @canvas-init? (render.wasm/draw-objects zoom vbox))]
|
||||||
(render.wasm/draw-objects zoom vbox)))]
|
|
||||||
(partial render.wasm/cancel-draw frame-id)))
|
(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?)
|
||||||
|
@ -639,11 +637,4 @@
|
||||||
{:objects base-objects
|
{:objects base-objects
|
||||||
:zoom zoom
|
:zoom zoom
|
||||||
:vbox vbox
|
:vbox vbox
|
||||||
:bottom-padding (when palete-size (+ palete-size 8))}]]]]
|
:bottom-padding (when palete-size (+ palete-size 8))}]]]]]))
|
||||||
|
|
||||||
(when render-context-lost?
|
|
||||||
[:div {:id "context-lost" :class (stl/css :context-lost)}
|
|
||||||
[:h1 "GL Error Screen"]
|
|
||||||
[:button
|
|
||||||
{:on-click on-render-restore-context}
|
|
||||||
"Restore context"]])]))
|
|
||||||
|
|
|
@ -9,12 +9,10 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.files.helpers :as cfh]
|
[app.common.files.helpers :as cfh]
|
||||||
[app.common.types.shape.impl]
|
[app.common.types.shape.impl :as ctsi]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.main.data.render-wasm :as drw]
|
[app.util.object :as obj]
|
||||||
[app.main.store :as st]
|
|
||||||
[app.util.debug :as dbg]
|
|
||||||
[app.util.dom :as dom]
|
|
||||||
[promesa.core :as p]))
|
[promesa.core :as p]))
|
||||||
|
|
||||||
(def enabled?
|
(def enabled?
|
||||||
|
@ -23,7 +21,6 @@
|
||||||
(set! app.common.types.shape.impl/enabled-wasm-ready-shape enabled?)
|
(set! app.common.types.shape.impl/enabled-wasm-ready-shape enabled?)
|
||||||
|
|
||||||
(defonce internal-module #js {})
|
(defonce internal-module #js {})
|
||||||
(defonce internal-gpu-state #js {})
|
|
||||||
|
|
||||||
;; TODO: remove the `take` once we have the dynamic data structure in Rust
|
;; TODO: remove the `take` once we have the dynamic data structure in Rust
|
||||||
(def xform
|
(def xform
|
||||||
|
@ -31,49 +28,70 @@
|
||||||
(remove cfh/root?)
|
(remove cfh/root?)
|
||||||
(take 2048)))
|
(take 2048)))
|
||||||
|
|
||||||
;; Size in number of f32 values that represents the shape selrect (
|
(defn create-shape
|
||||||
(def rect-size 4)
|
[id]
|
||||||
|
(let [buffer (uuid/uuid->u32 id)]
|
||||||
|
(._create_shape ^js internal-module (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))))
|
||||||
|
|
||||||
|
(defn use-shape
|
||||||
|
[id]
|
||||||
|
(let [buffer (uuid/uuid->u32 id)]
|
||||||
|
(._use_shape ^js internal-module (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 ^js internal-module 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 ^js internal-module a b c d e f)))
|
||||||
|
|
||||||
|
(defn set-shape-rotation
|
||||||
|
[rotation]
|
||||||
|
(._set_shape_rotation ^js internal-module rotation))
|
||||||
|
|
||||||
|
(defn set-shape-x
|
||||||
|
[x]
|
||||||
|
(._set_shape_x ^js internal-module x))
|
||||||
|
|
||||||
|
(defn set-shape-y
|
||||||
|
[y]
|
||||||
|
(._set_shape_y ^js internal-module y))
|
||||||
|
|
||||||
(defn set-objects
|
(defn set-objects
|
||||||
[objects]
|
[objects]
|
||||||
;; FIXME: maybe change the name of `_shapes_buffer` (?)
|
(let [shapes (into [] xform (vals objects))
|
||||||
(let [get-shapes-buffer-ptr
|
total-shapes (count shapes)]
|
||||||
(unchecked-get internal-module "_shapes_buffer")
|
|
||||||
|
|
||||||
heap
|
|
||||||
(unchecked-get internal-module "HEAPF32")
|
|
||||||
|
|
||||||
shapes
|
|
||||||
(into [] xform (vals objects))
|
|
||||||
|
|
||||||
total-shapes
|
|
||||||
(count shapes)
|
|
||||||
|
|
||||||
heap-offset
|
|
||||||
(get-shapes-buffer-ptr)
|
|
||||||
|
|
||||||
heap-size
|
|
||||||
(* rect-size total-shapes)
|
|
||||||
|
|
||||||
mem
|
|
||||||
(js/Float32Array. (.-buffer heap)
|
|
||||||
heap-offset
|
|
||||||
heap-size)]
|
|
||||||
|
|
||||||
(loop [index 0]
|
(loop [index 0]
|
||||||
(when (< index total-shapes)
|
(when (< index total-shapes)
|
||||||
(let [shape (nth shapes index)]
|
(let [shape (nth shapes index)
|
||||||
(.set ^js mem (.-buffer shape) (* index rect-size))
|
id (dm/get-prop shape :id)
|
||||||
|
selrect (dm/get-prop shape :selrect)
|
||||||
|
rotation (dm/get-prop shape :rotation)
|
||||||
|
transform (dm/get-prop shape :transform)]
|
||||||
|
(use-shape id)
|
||||||
|
(set-shape-selrect selrect)
|
||||||
|
(set-shape-rotation rotation)
|
||||||
|
(set-shape-transform transform)
|
||||||
(recur (inc index)))))))
|
(recur (inc index)))))))
|
||||||
|
|
||||||
(defn draw-objects
|
(defn draw-objects
|
||||||
[zoom vbox]
|
[zoom vbox]
|
||||||
(let [draw-all-shapes (unchecked-get internal-module "_draw_all_shapes")]
|
(js/requestAnimationFrame
|
||||||
(js/requestAnimationFrame
|
(fn []
|
||||||
(fn []
|
(let [pan-x (- (dm/get-prop vbox :x))
|
||||||
(let [pan-x (- (dm/get-prop vbox :x))
|
pan-y (- (dm/get-prop vbox :y))]
|
||||||
pan-y (- (dm/get-prop vbox :y))]
|
(._draw_all_shapes ^js internal-module zoom pan-x pan-y)))))
|
||||||
(draw-all-shapes internal-gpu-state zoom pan-x pan-y))))))
|
|
||||||
|
|
||||||
(defn cancel-draw
|
(defn cancel-draw
|
||||||
[frame-id]
|
[frame-id]
|
||||||
|
@ -86,67 +104,28 @@
|
||||||
:stencil true
|
:stencil true
|
||||||
:alpha true})
|
:alpha true})
|
||||||
|
|
||||||
(defn init-skia
|
(defn clear-canvas
|
||||||
[canvas]
|
[]
|
||||||
(let [init-fn (unchecked-get internal-module "_init")
|
|
||||||
state (init-fn (.-width ^js canvas)
|
|
||||||
(.-height ^js canvas))]
|
|
||||||
(set! internal-gpu-state state)))
|
|
||||||
|
|
||||||
;; NOTE: This function can be called externally
|
|
||||||
;; by the button in the context lost component (shown
|
|
||||||
;; in viewport-wasm) or called internally by
|
|
||||||
;; on-webgl-context
|
|
||||||
(defn restore-canvas
|
|
||||||
[canvas]
|
|
||||||
(st/emit! (drw/context-restored))
|
|
||||||
;; We need to reinitialize skia when the
|
|
||||||
;; context is restored.
|
|
||||||
(init-skia canvas))
|
|
||||||
|
|
||||||
;; Handles both events: webglcontextlost and
|
|
||||||
;; webglcontextrestored
|
|
||||||
(defn on-webgl-context
|
|
||||||
[event]
|
|
||||||
(dom/prevent-default event)
|
|
||||||
(if (= (.-type event) "webglcontextlost")
|
|
||||||
(st/emit! (drw/context-lost))
|
|
||||||
(restore-canvas (dom/get-target event))))
|
|
||||||
|
|
||||||
(defn dispose-canvas
|
|
||||||
[canvas]
|
|
||||||
;; TODO: perform corresponding cleaning
|
;; TODO: perform corresponding cleaning
|
||||||
(.removeEventListener canvas "webglcontextlost" on-webgl-context)
|
)
|
||||||
(.removeEventListener canvas "webglcontextrestored" on-webgl-context))
|
|
||||||
|
|
||||||
(defn init-debug-webgl-context-state
|
(defn assign-canvas
|
||||||
[context]
|
|
||||||
(let [context-extension (.getExtension ^js context "WEBGL_lose_context")
|
|
||||||
info-extension (.getExtension ^js context "WEBGL_debug_renderer_info")]
|
|
||||||
(set! (.-penpotGL js/window) #js {:context context-extension
|
|
||||||
:renderer info-extension})
|
|
||||||
(js/console.log "WEBGL_lose_context" context-extension)
|
|
||||||
(js/console.log "WEBGL_debug_renderer_info" info-extension)))
|
|
||||||
|
|
||||||
(defn setup-canvas
|
|
||||||
[canvas]
|
[canvas]
|
||||||
(let [gl (unchecked-get internal-module "GL")
|
(let [gl (unchecked-get internal-module "GL")
|
||||||
|
init-fn (unchecked-get internal-module "_init")
|
||||||
|
|
||||||
context (.getContext ^js canvas "webgl2" canvas-options)
|
context (.getContext ^js canvas "webgl2" canvas-options)
|
||||||
|
|
||||||
;; Register the context with emscripten
|
;; Register the context with emscripten
|
||||||
handle (.registerContext ^js gl context #js {"majorVersion" 2})
|
handle (.registerContext ^js gl context #js {"majorVersion" 2})]
|
||||||
_ (.makeContextCurrent ^js gl handle)]
|
(.makeContextCurrent ^js gl handle)
|
||||||
|
;; Initialize Skia
|
||||||
(when (dbg/enabled? :gl-context)
|
(init-fn (.-width ^js canvas)
|
||||||
(init-debug-webgl-context-state context))
|
(.-height ^js canvas))
|
||||||
|
|
||||||
(.addEventListener canvas "webglcontextlost" on-webgl-context)
|
|
||||||
(.addEventListener canvas "webglcontextrestored" on-webgl-context)
|
|
||||||
|
|
||||||
(set! (.-width canvas) (.-clientWidth ^js canvas))
|
(set! (.-width canvas) (.-clientWidth ^js canvas))
|
||||||
(set! (.-height canvas) (.-clientHeight ^js canvas))
|
(set! (.-height canvas) (.-clientHeight ^js canvas))
|
||||||
|
|
||||||
(init-skia canvas)))
|
(obj/set! js/window "shape_list" (fn [] ((unchecked-get internal-module "_shape_list"))))))
|
||||||
|
|
||||||
(defonce module
|
(defonce module
|
||||||
(->> (js/dynamicImport "/js/render_wasm.js")
|
(->> (js/dynamicImport "/js/render_wasm.js")
|
||||||
|
@ -159,3 +138,9 @@
|
||||||
(p/merr (fn [cause]
|
(p/merr (fn [cause]
|
||||||
(js/console.error 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)
|
||||||
|
|
45
render-wasm/Cargo.lock
generated
45
render-wasm/Cargo.lock
generated
|
@ -96,22 +96,6 @@ version = "1.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "emscripten-functions"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "62c026cc030b24957ca45d9555f9fa241d6b3a01d725cd98a25924de249b840a"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"emscripten-functions-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "emscripten-functions-sys"
|
|
||||||
version = "4.1.67"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "65715a5f07b03636d7cd5508a45d1b62486840cb7d91a66564a73f1d7aa70b79"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -150,6 +134,17 @@ dependencies = [
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gl"
|
name = "gl"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
@ -386,10 +381,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
name = "render"
|
name = "render"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"emscripten-functions",
|
|
||||||
"emscripten-functions-sys",
|
|
||||||
"gl",
|
"gl",
|
||||||
"skia-safe",
|
"skia-safe",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -554,6 +548,21 @@ version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "which"
|
name = "which"
|
||||||
version = "4.4.2"
|
version = "4.4.2"
|
||||||
|
|
|
@ -11,10 +11,9 @@ name = "render_wasm"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
emscripten-functions = "0.2.3"
|
|
||||||
emscripten-functions-sys = "4.1.67"
|
|
||||||
gl = "0.14.0"
|
gl = "0.14.0"
|
||||||
skia-safe = { version = "0.78.2", features = ["gl"] }
|
skia-safe = { version = "0.78.2", features = ["gl"] }
|
||||||
|
uuid = { version = "1.11.0", features = ["v4"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
|
|
|
@ -1,83 +1,161 @@
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod shapes;
|
pub mod shapes;
|
||||||
|
pub mod state;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use render::State;
|
use crate::shapes::Shape;
|
||||||
|
use crate::state::State;
|
||||||
|
use crate::utils::uuid_from_u32_quartet;
|
||||||
|
|
||||||
|
static mut STATE: Option<Box<State>> = None;
|
||||||
|
|
||||||
/// This is called from JS after the WebGL context has been created.
|
/// This is called from JS after the WebGL context has been created.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn init(width: i32, height: i32) -> Box<render::State> {
|
pub extern "C" fn init(width: i32, height: i32) {
|
||||||
let mut gpu_state = render::create_gpu_state();
|
let state_box = Box::new(State::with_capacity(width, height, 2048));
|
||||||
let surface = render::create_surface(&mut gpu_state, width, height);
|
unsafe {
|
||||||
|
STATE = Some(state_box);
|
||||||
let state = State::new(gpu_state, surface);
|
}
|
||||||
|
|
||||||
Box::new(state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is called from JS when the window is resized.
|
/// This is called from JS when the window is resized.
|
||||||
/// # Safety
|
/// # Safety
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn resize_surface(state: *mut State, width: i32, height: i32) {
|
pub unsafe extern "C" fn resize_surface(width: i32, height: i32) {
|
||||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
let surface = render::create_surface(&mut state.gpu_state, width, height);
|
let surface = render::create_surface(&mut state.render_state.gpu_state, width, height);
|
||||||
state.set_surface(surface);
|
state.set_surface(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a rect at the specified coordinates with the give ncolor
|
/// Draws a rect at the specified coordinates with the give ncolor
|
||||||
/// # Safety
|
/// # Safety
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn draw_rect(state: *mut State, x1: f32, y1: f32, x2: f32, y2: f32) {
|
pub unsafe extern "C" fn draw_rect(x1: f32, y1: f32, x2: f32, y2: f32) {
|
||||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
let r = skia::Rect::new(x1, y1, x2, y2);
|
let r = skia::Rect::new(x1, y1, x2, y2);
|
||||||
render::render_rect(&mut state.surface, r, skia::Color::RED);
|
render::render_rect(&mut state.render_state.surface, r, skia::Color::RED);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn draw_all_shapes(state: *mut State, zoom: f32, pan_x: f32, pan_y: f32) {
|
pub unsafe extern "C" fn draw_all_shapes(zoom: f32, pan_x: f32, pan_y: f32) {
|
||||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
|
||||||
reset_canvas(state);
|
reset_canvas();
|
||||||
scale(state, zoom, zoom);
|
scale(zoom, zoom);
|
||||||
translate(state, pan_x, pan_y);
|
translate(pan_x, pan_y);
|
||||||
|
|
||||||
shapes::draw_all(state);
|
render::render_all(state);
|
||||||
|
|
||||||
flush(state);
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn flush(state: *mut State) {
|
pub unsafe extern "C" fn flush() {
|
||||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
state
|
state
|
||||||
|
.render_state
|
||||||
.gpu_state
|
.gpu_state
|
||||||
.context
|
.context
|
||||||
.flush_and_submit_surface(&mut state.surface, None);
|
.flush_and_submit_surface(&mut state.render_state.surface, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn translate(state: *mut State, dx: f32, dy: f32) {
|
pub unsafe extern "C" fn translate(dx: f32, dy: f32) {
|
||||||
(*state).surface.canvas().translate((dx, dy));
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
state.render_state.surface.canvas().translate((dx, dy));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn scale(state: *mut State, sx: f32, sy: f32) {
|
pub unsafe extern "C" fn scale(sx: f32, sy: f32) {
|
||||||
(*state).surface.canvas().scale((sx, sy));
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
state.render_state.surface.canvas().scale((sx, sy));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn reset_canvas(state: *mut State) {
|
pub extern "C" fn reset_canvas() {
|
||||||
let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
state.surface.canvas().clear(skia_safe::Color::TRANSPARENT);
|
state
|
||||||
state.surface.canvas().reset_matrix();
|
.render_state
|
||||||
flush(state);
|
.surface
|
||||||
|
.canvas()
|
||||||
|
.clear(skia_safe::Color::TRANSPARENT);
|
||||||
|
state.render_state.surface.canvas().reset_matrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_or_create_shape<'a>(shapes: &'a mut HashMap<Uuid, Shape>, id: Uuid) -> &'a mut Shape {
|
||||||
|
if !shapes.contains_key(&id) {
|
||||||
|
let new_shape = Shape::new(id);
|
||||||
|
shapes.insert(id, new_shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
shapes.get_mut(&id).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn shapes_buffer() -> *mut shapes::Shape {
|
pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) {
|
||||||
let ptr = shapes::SHAPES_BUFFER.as_mut_ptr();
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
return ptr;
|
let id = uuid_from_u32_quartet(a, b, c, d);
|
||||||
|
state.current_id = Some(id);
|
||||||
|
let shapes = &mut state.shapes;
|
||||||
|
state.current_shape = Some(get_or_create_shape(shapes, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn set_shape_selrect(x1: f32, y1: f32, x2: f32, y2: f32) {
|
||||||
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
|
||||||
|
if let Some(shape) = state.current_shape.as_deref_mut() {
|
||||||
|
shape.selrect.x1 = x1;
|
||||||
|
shape.selrect.y1 = y1;
|
||||||
|
shape.selrect.x2 = x2;
|
||||||
|
shape.selrect.y2 = y2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn set_shape_rotation(rotation: f32) {
|
||||||
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
if let Some(shape) = state.current_shape.as_deref_mut() {
|
||||||
|
shape.rotation = rotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn set_shape_x(x: f32) {
|
||||||
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
if let Some(shape) = state.current_shape.as_deref_mut() {
|
||||||
|
let width = shape.selrect.x2 - shape.selrect.x1;
|
||||||
|
shape.selrect.x1 = x;
|
||||||
|
shape.selrect.x2 = x + width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn set_shape_y(y: f32) {
|
||||||
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
if let Some(shape) = state.current_shape.as_deref_mut() {
|
||||||
|
let height = shape.selrect.y2 - shape.selrect.y1;
|
||||||
|
shape.selrect.y1 = y;
|
||||||
|
shape.selrect.y2 = y + height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn set_shape_transform(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) {
|
||||||
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
if let Some(shape) = state.current_shape.as_deref_mut() {
|
||||||
|
shape.transform.a = a;
|
||||||
|
shape.transform.b = b;
|
||||||
|
shape.transform.c = c;
|
||||||
|
shape.transform.d = d;
|
||||||
|
shape.transform.e = e;
|
||||||
|
shape.transform.f = f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -1,33 +1,29 @@
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
use skia_safe::gpu::{self, gl::FramebufferInfo, DirectContext};
|
use skia_safe::gpu::{self, gl::FramebufferInfo, DirectContext};
|
||||||
|
|
||||||
|
use crate::state::State;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn emscripten_GetProcAddress(
|
pub fn emscripten_GetProcAddress(
|
||||||
name: *const ::std::os::raw::c_char,
|
name: *const ::std::os::raw::c_char,
|
||||||
) -> *const ::std::os::raw::c_void;
|
) -> *const ::std::os::raw::c_void;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GpuState {
|
pub(crate) struct GpuState {
|
||||||
pub context: DirectContext,
|
pub context: DirectContext,
|
||||||
framebuffer_info: FramebufferInfo,
|
framebuffer_info: FramebufferInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This struct holds the state of the Rust application between JS calls.
|
pub(crate) struct RenderState {
|
||||||
///
|
|
||||||
/// It is created by [init] and passed to the other exported functions. Note that rust-skia data
|
|
||||||
/// structures are not thread safe, so a state must not be shared between different Web Workers.
|
|
||||||
pub struct State {
|
|
||||||
pub gpu_state: GpuState,
|
pub gpu_state: GpuState,
|
||||||
pub surface: skia::Surface,
|
pub surface: skia::Surface,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl RenderState {
|
||||||
pub fn new(gpu_state: GpuState, surface: skia::Surface) -> Self {
|
pub fn new(width: i32, height: i32) -> RenderState {
|
||||||
State { gpu_state, surface }
|
let mut gpu_state = create_gpu_state();
|
||||||
}
|
let surface = create_surface(&mut gpu_state, width, height);
|
||||||
|
RenderState { gpu_state, surface }
|
||||||
pub fn set_surface(&mut self, surface: skia::Surface) {
|
|
||||||
self.surface = surface;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,3 +80,35 @@ pub(crate) fn render_rect(surface: &mut skia::Surface, rect: skia::Rect, color:
|
||||||
paint.set_anti_alias(true);
|
paint.set_anti_alias(true);
|
||||||
surface.canvas().draw_rect(rect, &paint);
|
surface.canvas().draw_rect(rect, &paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render_all(state: &mut State) {
|
||||||
|
for shape in state.shapes.values() {
|
||||||
|
let r = skia::Rect::new(
|
||||||
|
shape.selrect.x1,
|
||||||
|
shape.selrect.y1,
|
||||||
|
shape.selrect.x2,
|
||||||
|
shape.selrect.y2,
|
||||||
|
);
|
||||||
|
|
||||||
|
state.render_state.surface.canvas().save();
|
||||||
|
|
||||||
|
// Check transform-matrix code from common/src/app/common/geom/shapes/transforms.cljc
|
||||||
|
let mut matrix = skia::Matrix::new_identity();
|
||||||
|
let (translate_x, translate_y) = shape.translation();
|
||||||
|
let (scale_x, scale_y) = shape.scale();
|
||||||
|
let (skew_x, skew_y) = shape.skew();
|
||||||
|
|
||||||
|
matrix.set_all(scale_x, skew_x, translate_x, skew_y, scale_y, translate_y, 0., 0., 1.);
|
||||||
|
|
||||||
|
let mut center = r.center();
|
||||||
|
matrix.post_translate(center);
|
||||||
|
center.negate();
|
||||||
|
matrix.pre_translate(center);
|
||||||
|
|
||||||
|
state.render_state.surface.canvas().concat(&matrix);
|
||||||
|
|
||||||
|
render_rect(&mut state.render_state.surface, r, skia::Color::RED);
|
||||||
|
|
||||||
|
state.render_state.surface.canvas().restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,31 +1,84 @@
|
||||||
use crate::render::{render_rect, State};
|
use uuid::Uuid;
|
||||||
use skia_safe as skia;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Selrect {
|
pub enum Kind {
|
||||||
|
None,
|
||||||
|
Text,
|
||||||
|
Path,
|
||||||
|
SVGRaw,
|
||||||
|
Image,
|
||||||
|
Circle,
|
||||||
|
Rect,
|
||||||
|
Bool,
|
||||||
|
Group,
|
||||||
|
Frame,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Point {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct Rect {
|
||||||
pub x1: f32,
|
pub x1: f32,
|
||||||
pub y1: f32,
|
pub y1: f32,
|
||||||
pub x2: f32,
|
pub x2: f32,
|
||||||
pub y2: f32,
|
pub y2: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Shape = Selrect; // temp
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Matrix {
|
||||||
|
pub a: f32,
|
||||||
|
pub b: f32,
|
||||||
|
pub c: f32,
|
||||||
|
pub d: f32,
|
||||||
|
pub e: f32,
|
||||||
|
pub f: f32,
|
||||||
|
}
|
||||||
|
|
||||||
pub static mut SHAPES_BUFFER: [Shape; 2048] = [Selrect {
|
impl Matrix {
|
||||||
x1: 0.0,
|
pub fn identity() -> Self {
|
||||||
y1: 0.0,
|
Self {
|
||||||
x2: 0.0,
|
a: 1.,
|
||||||
y2: 0.0,
|
b: 0.,
|
||||||
}; 2048];
|
c: 0.,
|
||||||
|
d: 1.,
|
||||||
pub(crate) fn draw_all(state: &mut State) {
|
e: 0.,
|
||||||
let shapes;
|
f: 0.,
|
||||||
unsafe {
|
}
|
||||||
shapes = SHAPES_BUFFER.iter();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for shape in shapes {
|
#[derive(Debug, Clone, Copy)]
|
||||||
let r = skia::Rect::new(shape.x1, shape.y1, shape.x2, shape.y2);
|
pub struct Shape {
|
||||||
render_rect(&mut state.surface, r, skia::Color::RED);
|
pub id: Uuid,
|
||||||
|
pub kind: Kind,
|
||||||
|
pub selrect: Rect,
|
||||||
|
pub transform: Matrix,
|
||||||
|
pub rotation: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shape {
|
||||||
|
pub fn new(id: Uuid) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
kind: Kind::Rect,
|
||||||
|
selrect: Rect::default(),
|
||||||
|
transform: Matrix::identity(),
|
||||||
|
rotation: 0.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translation(&self) -> (f32, f32) {
|
||||||
|
(self.transform.e, self.transform.f)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scale(&self) -> (f32, f32) {
|
||||||
|
(self.transform.a, self.transform.d)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skew(&self) -> (f32, f32) {
|
||||||
|
(self.transform.c, self.transform.b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
36
render-wasm/src/state.rs
Normal file
36
render-wasm/src/state.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use skia_safe as skia;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::vec::Vec;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::render::RenderState;
|
||||||
|
use crate::shapes::Shape;
|
||||||
|
|
||||||
|
/// This struct holds the state of the Rust application between JS calls.
|
||||||
|
///
|
||||||
|
/// It is created by [init] and passed to the other exported functions.
|
||||||
|
/// Note that rust-skia data structures are not thread safe, so a state
|
||||||
|
/// must not be shared between different Web Workers.
|
||||||
|
pub(crate) struct State<'a> {
|
||||||
|
pub render_state: RenderState,
|
||||||
|
pub current_id: Option<Uuid>,
|
||||||
|
pub current_shape: Option<&'a mut Shape>,
|
||||||
|
pub shapes: HashMap<Uuid, Shape>,
|
||||||
|
pub display_list: Vec<Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> State<'a> {
|
||||||
|
pub fn with_capacity(width: i32, height: i32, capacity: usize) -> Self {
|
||||||
|
State {
|
||||||
|
render_state: RenderState::new(width, height),
|
||||||
|
current_id: None,
|
||||||
|
current_shape: None,
|
||||||
|
shapes: HashMap::with_capacity(capacity),
|
||||||
|
display_list: Vec::with_capacity(capacity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_surface(&mut self, surface: skia::Surface) {
|
||||||
|
self.render_state.surface = surface;
|
||||||
|
}
|
||||||
|
}
|
8
render-wasm/src/utils.rs
Normal file
8
render-wasm/src/utils.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub fn uuid_from_u32_quartet(a: u32, b: u32, c: u32, d: u32) -> Uuid
|
||||||
|
{
|
||||||
|
let hi: u64 = ((a as u64) << 32) | b as u64;
|
||||||
|
let lo: u64 = ((c as u64) << 32) | d as u64;
|
||||||
|
Uuid::from_u64_pair(hi, lo)
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue