From af99bd620c5f18d638c99ad6a48830eee1fdf632 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 9 Jul 2025 11:18:55 +0200 Subject: [PATCH] :sparkles: Use binary fills to write data to wasm memory --- common/src/app/common/types/fills.cljc | 50 +++++++++++- common/src/app/common/types/fills/impl.cljc | 63 ++++++++++----- frontend/src/app/render_wasm/api.cljs | 41 ++++------ frontend/src/app/render_wasm/api/texts.cljs | 18 ++--- .../app/render_wasm/serializers/fills.cljs | 81 ------------------- 5 files changed, 114 insertions(+), 139 deletions(-) delete mode 100644 frontend/src/app/render_wasm/serializers/fills.cljs diff --git a/common/src/app/common/types/fills.cljc b/common/src/app/common/types/fills.cljc index 5a37cd3d91..acc93080e4 100644 --- a/common/src/app/common/types/fills.cljc +++ b/common/src/app/common/types/fills.cljc @@ -6,6 +6,7 @@ (ns app.common.types.fills (:require + [app.common.exceptions :as ex] [app.common.schema :as sm] [app.common.types.color :as types.color] [app.common.types.fills.impl :as impl] @@ -50,10 +51,6 @@ (def check-fill (sm/check-fn schema:fill)) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; HELPERS -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; CONSTRUCTORS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -66,3 +63,48 @@ (defn fills? [o] (impl/fills? o)) + +(defn coerce + [o] + (cond + (nil? o) + (impl/from-plain []) + + (impl/fills? o) + o + + (vector? o) + (impl/from-plain o) + + :else + (ex/raise :type :internal + :code :invalid-type + :hint (str "cannot coerce " (pr-str o) "to fills")))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; HELPERS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn assoc-fill + [fills position fill] + (if (nil? fills) + (impl/from-plain [fill]) + (-> (coerce fills) + (assoc position fill)))) + +(defn get-image-ids + [fills] + (if (vector? fills) + (into #{} + (comp (keep :fill-image) + (map :id)) + fills) + (impl/-get-image-ids fills))) + +(defn get-byte-size + [fills] + (impl/-get-byte-size fills)) + +(defn write-to + [fills buffer offset] + (impl/-write-to fills buffer offset)) diff --git a/common/src/app/common/types/fills/impl.cljc b/common/src/app/common/types/fills/impl.cljc index 68c21c30dd..4d6368703d 100644 --- a/common/src/app/common/types/fills/impl.cljc +++ b/common/src/app/common/types/fills/impl.cljc @@ -11,6 +11,7 @@ [app.common.buffer :as buf] [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.exceptions :as ex] [app.common.math :as mth] [app.common.transit :as t])) @@ -35,6 +36,13 @@ (def ^:private xf:take-fills (take MAX-FILLS)) +(defprotocol IHeapWritable + (-write-to [_ buffer offset] "write the content to the specified buffer") + (-get-byte-size [_] "get byte size")) + +(defprotocol IBinaryFills + (-get-image-ids [_] "get referenced image ids")) + (defn- hex->rgb "Encode an hex string as rgb (int32)" [hex] @@ -64,16 +72,16 @@ n (unsigned-bit-shift-right n 24)] (mth/precision (/ (float n) 0xff) 2))) -(defn- write-solid-fill - [offset buffer color alpha] +(defn write-solid-fill + [offset buffer opacity color] (buf/write-byte buffer (+ offset 0) 0x00) (buf/write-int buffer (+ offset 4) (-> (hex->rgb color) - (rgb->rgba alpha))) + (rgb->rgba opacity))) (+ offset FILL-BYTE-SIZE)) -(defn- write-gradient-fill - [offset buffer gradient opacity] +(defn write-gradient-fill + [offset buffer opacity gradient] (let [start-x (:start-x gradient) start-y (:start-y gradient) end-x (:end-x gradient) @@ -108,10 +116,10 @@ (+ offset' GRADIENT-STOP-SIZE))) (+ offset FILL-BYTE-SIZE))))) -(defn- write-image-fill +(defn write-image-fill [offset buffer opacity image] - (let [image-id (get image :id) - image-width (get image :width) + (let [image-id (get image :id) + image-width (get image :width) image-height (get image :height) keep-ratio (get image :keep-aspect-ratio false)] (buf/write-byte buffer (+ offset 0) 0x03) @@ -282,7 +290,20 @@ :cljs #_:clj-kondo/ignore - (deftype Fills [size dbuffer mbuffer cache ^:mutable __hash] + (deftype Fills [size dbuffer mbuffer image-ids cache ^:mutable __hash] + + IHeapWritable + (-get-byte-size [_] + (- (.-byteLength dbuffer) 4)) + + (-write-to [_ heap offset] + (let [buffer' (.-buffer ^js/DataView dbuffer)] + (.set heap (js/Uint32Array. buffer' 4) offset))) + + IBinaryFills + (-get-image-ids [_] + image-ids) + cljs.core/ISequential cljs.core/IEquiv (-equiv [this other] @@ -368,8 +389,9 @@ (buf/write-byte dbuffer 0 total) - (loop [index 0] - (when (< index total) + (loop [index 0 + image-ids #{}] + (if (< index total) (let [fill (nth fills index) doffset (+ 4 (* index FILL-BYTE-SIZE)) moffset (* index METADATA-BYTE-SIZE) @@ -377,23 +399,26 @@ (if-let [color (get fill :fill-color)] (do - (write-solid-fill doffset dbuffer color opacity) + (write-solid-fill doffset dbuffer opacity color) (write-metadata moffset mbuffer fill) - (recur (inc index))) + (recur (inc index) image-ids)) (if-let [gradient (get fill :fill-color-gradient)] (do - (write-gradient-fill doffset dbuffer gradient opacity) + (write-gradient-fill doffset dbuffer opacity gradient) (write-metadata moffset mbuffer fill) - (recur (inc index))) + (recur (inc index) image-ids)) (if-let [image (get fill :fill-image)] (do (write-image-fill doffset dbuffer opacity image) (write-metadata moffset mbuffer fill) - (recur (inc index))) - (recur (inc index)))))))) + (recur (inc index) + (conj image-ids (get image :id)))) + (ex/raise :type :internal + :code :invalid-fill + :hint "found invalid fill on encoding fills to binary format"))))) - #?(:cljs (Fills. total dbuffer mbuffer (weak-map/create) nil) - :clj (Fills. total dbuffer mbuffer nil)))) + #?(:cljs (Fills. total dbuffer mbuffer image-ids (weak-map/create) nil) + :clj (Fills. total dbuffer mbuffer nil)))))) (defn fills? [o] diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 38f06a1a21..0bf4096577 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -13,6 +13,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.types.fills :as types.fills] + [app.common.types.fills.impl :as types.fills.impl] [app.common.types.path :as path] [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] @@ -28,7 +29,6 @@ [app.render-wasm.performance :as perf] [app.render-wasm.serializers :as sr] [app.render-wasm.serializers.color :as sr-clr] - [app.render-wasm.serializers.fills :as sr-fills] [app.render-wasm.wasm :as wasm] [app.util.debug :as dbg] [app.util.functions :as fns] @@ -201,6 +201,9 @@ (rx/map :body) (rx/mapcat wapi/read-file-as-array-buffer) (rx/map (fn [image] + ;; FIXME use bigger heap ptr size if it + ;; is possible (if image size modulo + ;; permits it) (let [size (.-byteLength image) offset (mem/alloc-bytes size) heap (mem/get-heap-u8) @@ -248,28 +251,19 @@ [shape-id fills] (if (empty? fills) (h/call wasm/internal-module "_clear_shape_fills") - (let [fills (take types.fills/MAX-FILLS fills) - image-fills (filter :fill-image fills) - offset (mem/alloc-bytes (* (count fills) sr-fills/FILL-BYTE-SIZE)) - heap (mem/get-heap-u8) - dview (js/DataView. (.-buffer heap))] + (let [fills (types.fills/coerce fills) + offset (mem/alloc-bytes-32 (types.fills/get-byte-size fills)) + heap (mem/get-heap-u32)] - ;; write fill data to heap - (loop [fills (seq fills) - current-offset offset] - (when-not (empty? fills) - (let [fill (first fills) - new-offset (sr-fills/write-fill! current-offset dview fill)] - (recur (rest fills) new-offset)))) + ;; write fills to the heap + (types.fills/write-to fills heap offset) ;; send fills to wasm (h/call wasm/internal-module "_set_shape_fills") ;; load images for image fills if not cached - (keep (fn [fill] - (let [image (:fill-image fill) - id (dm/get-prop image :id) - buffer (uuid/get-u32 id) + (keep (fn [id] + (let [buffer (uuid/get-u32 id) cached-image? (h/call wasm/internal-module "_is_image_cached" (aget buffer 0) (aget buffer 1) @@ -277,7 +271,8 @@ (aget buffer 3))] (when (zero? cached-image?) (fetch-image shape-id id)))) - image-fills)))) + + (types.fills/get-image-ids fills))))) (defn set-shape-strokes [shape-id strokes] @@ -292,7 +287,7 @@ style (-> stroke :stroke-style sr/translate-stroke-style) cap-start (-> stroke :stroke-cap-start sr/translate-stroke-cap) cap-end (-> stroke :stroke-cap-end sr/translate-stroke-cap) - offset (mem/alloc-bytes sr-fills/FILL-BYTE-SIZE) + offset (mem/alloc-bytes types.fills.impl/FILL-BYTE-SIZE) heap (mem/get-heap-u8) dview (js/DataView. (.-buffer heap))] (case align @@ -303,23 +298,21 @@ (cond (some? gradient) (do - (sr-fills/write-gradient-fill! offset dview gradient opacity) + (types.fills.impl/write-gradient-fill offset dview opacity gradient) (h/call wasm/internal-module "_add_shape_stroke_fill")) (some? image) (let [image-id (dm/get-prop image :id) buffer (uuid/get-u32 image-id) cached-image? (h/call wasm/internal-module "_is_image_cached" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3))] - (sr-fills/write-image-fill! offset dview image-id opacity - (dm/get-prop image :width) - (dm/get-prop image :height)) + (types.fills.impl/write-image-fill offset dview opacity image) (h/call wasm/internal-module "_add_shape_stroke_fill") (when (== cached-image? 0) (fetch-image shape-id image-id))) (some? color) (do - (sr-fills/write-solid-fill! offset dview (sr-clr/hex->u32argb color opacity)) + (types.fills.impl/write-solid-fill offset dview opacity color) (h/call wasm/internal-module "_add_shape_stroke_fill"))))) strokes)) diff --git a/frontend/src/app/render_wasm/api/texts.cljs b/frontend/src/app/render_wasm/api/texts.cljs index 70418f0099..dbb56d052e 100644 --- a/frontend/src/app/render_wasm/api/texts.cljs +++ b/frontend/src/app/render_wasm/api/texts.cljs @@ -7,12 +7,11 @@ (ns app.render-wasm.api.texts (:require [app.common.data.macros :as dm] + [app.common.types.fills.impl :as types.fills.impl] [app.render-wasm.api.fonts :as f] [app.render-wasm.helpers :as h] [app.render-wasm.mem :as mem] [app.render-wasm.serializers :as sr] - [app.render-wasm.serializers.color :as sr-clr] - [app.render-wasm.serializers.fills :as sr-fills] [app.render-wasm.wasm :as wasm])) (defn utf8->buffer [text] @@ -26,21 +25,18 @@ color (:fill-color fill) gradient (:fill-color-gradient fill) image (:fill-image fill)] + (cond (some? color) - (sr-fills/write-solid-fill! offset dview (sr-clr/hex->u32argb color opacity)) + (types.fills.impl/write-solid-fill offset dview opacity color) (some? gradient) - (sr-fills/write-gradient-fill! offset dview gradient opacity) + (types.fills.impl/write-gradient-fill offset dview opacity gradient) (some? image) - (sr-fills/write-image-fill! offset dview - (dm/get-prop image :id) - opacity - (dm/get-prop image :width) - (dm/get-prop image :height))) + (types.fills.impl/write-image-fill offset dview opacity image)) - (+ offset sr-fills/FILL-BYTE-SIZE))) + (+ offset types.fills.impl/FILL-BYTE-SIZE))) current-offset fills)) @@ -56,7 +52,7 @@ num-leaves (count leaves) paragraph-attr-size 48 total-fills (total-fills-count leaves) - total-fills-size (* sr-fills/FILL-BYTE-SIZE total-fills) + total-fills-size (* types.fills.impl/FILL-BYTE-SIZE total-fills) leaf-attr-size 56 metadata-size (+ paragraph-attr-size (* num-leaves leaf-attr-size) total-fills-size) text-buffer (utf8->buffer text) diff --git a/frontend/src/app/render_wasm/serializers/fills.cljs b/frontend/src/app/render_wasm/serializers/fills.cljs deleted file mode 100644 index 8e8efee578..0000000000 --- a/frontend/src/app/render_wasm/serializers/fills.cljs +++ /dev/null @@ -1,81 +0,0 @@ -(ns app.render-wasm.serializers.fills - (:require - [app.common.data.macros :as dm] - [app.common.types.fills :as types.fills] - [app.common.uuid :as uuid] - [app.render-wasm.serializers.color :as clr])) - -(def ^:private GRADIENT-STOP-SIZE 8) - -(def GRADIENT-BYTE-SIZE 156) -(def SOLID-BYTE-SIZE 4) -(def IMAGE-BYTE-SIZE 28) - -;; FIXME: get it from the wasm module -(def FILL-BYTE-SIZE (+ 4 (max GRADIENT-BYTE-SIZE IMAGE-BYTE-SIZE SOLID-BYTE-SIZE))) - - -(defn write-solid-fill! - [offset dview argb] - (.setUint8 dview offset 0x00 true) - (.setUint32 dview (+ offset 4) argb true) - (+ offset FILL-BYTE-SIZE)) - -(defn write-image-fill! - [offset dview id opacity width height] - (let [uuid-buffer (uuid/get-u32 id)] - (.setUint8 dview offset 0x03 true) - (.setUint32 dview (+ offset 4) (aget uuid-buffer 0) true) - (.setUint32 dview (+ offset 8) (aget uuid-buffer 1) true) - (.setUint32 dview (+ offset 12) (aget uuid-buffer 2) true) - (.setUint32 dview (+ offset 16) (aget uuid-buffer 3) true) - (.setFloat32 dview (+ offset 20) opacity true) - (.setInt32 dview (+ offset 24) width true) - (.setInt32 dview (+ offset 28) height true) - (+ offset FILL-BYTE-SIZE))) - -(defn write-gradient-fill! - [offset dview gradient opacity] - (let [start-x (:start-x gradient) - start-y (:start-y gradient) - end-x (:end-x gradient) - end-y (:end-y gradient) - width (or (:width gradient) 0) - stops (take types.fills/MAX-GRADIENT-STOPS (:stops gradient)) - type (if (= (:type gradient) :linear) 0x01 0x02)] - (.setUint8 dview offset type true) - (.setFloat32 dview (+ offset 4) start-x true) - (.setFloat32 dview (+ offset 8) start-y true) - (.setFloat32 dview (+ offset 12) end-x true) - (.setFloat32 dview (+ offset 16) end-y true) - (.setFloat32 dview (+ offset 20) opacity true) - (.setFloat32 dview (+ offset 24) width true) - (.setUint8 dview (+ offset 28) (count stops) true) - (loop [stops (seq stops) loop-offset (+ offset 32)] - (if (empty? stops) - (+ offset FILL-BYTE-SIZE) - (let [stop (first stops) - hex-color (:color stop) - stop-opacity (:opacity stop) - argb (clr/hex->u32argb hex-color stop-opacity) - stop-offset (:offset stop)] - (.setUint32 dview loop-offset argb true) - (.setFloat32 dview (+ loop-offset 4) stop-offset true) - (recur (rest stops) (+ loop-offset GRADIENT-STOP-SIZE))))))) - -(defn write-fill! - [offset dview fill] - (let [opacity (or (:fill-opacity fill) 1.0) - color (:fill-color fill) - gradient (:fill-color-gradient fill) - image (:fill-image fill)] - (cond - (some? color) - (write-solid-fill! offset dview (clr/hex->u32argb color opacity)) - - (some? gradient) - (write-gradient-fill! offset dview gradient opacity) - - (some? image) - (let [id (dm/get-prop image :id)] - (write-image-fill! offset dview id opacity (dm/get-prop image :width) (dm/get-prop image :height))))))