Use binary fills to write data to wasm memory

This commit is contained in:
Andrey Antukh 2025-07-09 11:18:55 +02:00
parent 8a58b9d459
commit af99bd620c
5 changed files with 114 additions and 139 deletions

View File

@ -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))

View File

@ -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]

View File

@ -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))

View File

@ -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)

View File

@ -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))))))