;; This Source Code Form is subject to the terms of the Mozilla Public ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; ;; Copyright (c) KALEIDOS INC (ns app.render-wasm.api "A WASM based render API" (:require ["react-dom/server" :as rds] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.types.path :as path] [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.config :as cf] [app.main.fonts :as fonts] [app.main.refs :as refs] [app.main.render :as render] [app.render-wasm.api.fonts :as f] [app.render-wasm.api.texts :as t] [app.render-wasm.deserializers :as dr] [app.render-wasm.helpers :as h] [app.render-wasm.mem :as mem] [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.http :as http] [app.util.perf :as uperf] [app.util.webapi :as wapi] [beicon.v2.core :as rx] [promesa.core :as p] [rumext.v2 :as mf])) ;; (defonce internal-frame-id nil) ;; (defonce wasm/internal-module #js {}) (defonce use-dpr? (contains? cf/flags :render-wasm-dpr)) ;; ;; List of common entry sizes. ;; ;; All of these entries are in bytes so we need to adjust ;; these values to work with TypedArrays of 32 bits. ;; (def CHILD-ENTRY-SIZE 16) (def MODIFIER-ENTRY-SIZE 40) (def MODIFIER-ENTRY-TRANSFORM-OFFSET 16) (def GRID-LAYOUT-ROW-ENTRY-SIZE 5) (def GRID-LAYOUT-COLUMN-ENTRY-SIZE 5) (def GRID-LAYOUT-CELL-ENTRY-SIZE 37) (defn modifier-get-entries-size "Returns the list of a modifier list in bytes" [modifiers] (mem/get-list-size modifiers MODIFIER-ENTRY-SIZE)) (defn grid-layout-get-row-entries-size [rows] (mem/get-list-size rows GRID-LAYOUT-ROW-ENTRY-SIZE)) (defn grid-layout-get-column-entries-size [columns] (mem/get-list-size columns GRID-LAYOUT-COLUMN-ENTRY-SIZE)) (defn grid-layout-get-cell-entries-size [cells] (mem/get-list-size cells GRID-LAYOUT-CELL-ENTRY-SIZE)) (def dpr (if use-dpr? js/window.devicePixelRatio 1.0)) ;; Based on app.main.render/object-svg (mf/defc object-svg {::mf/props :obj} [{:keys [shape] :as props}] (let [objects (mf/deref refs/workspace-page-objects) shape-wrapper (mf/with-memo [shape] (render/shape-wrapper-factory objects))] [:svg {:version "1.1" :xmlns "http://www.w3.org/2000/svg" :xmlnsXlink "http://www.w3.org/1999/xlink" :fill "none"} [:& shape-wrapper {:shape shape}]])) (defn get-static-markup [shape] (-> (mf/element object-svg #js {:shape shape}) (rds/renderToStaticMarkup))) ;; This should never be called from the outside. (defn- render [timestamp] (h/call wasm/internal-module "_render" timestamp) (set! wasm/internal-frame-id nil)) (defn cancel-render [_] (when wasm/internal-frame-id (js/cancelAnimationFrame wasm/internal-frame-id) (set! wasm/internal-frame-id nil))) (defn request-render [requester] (when wasm/internal-frame-id (cancel-render requester)) (let [frame-id (js/requestAnimationFrame render)] (set! wasm/internal-frame-id frame-id))) (defn use-shape [id] (let [buffer (uuid/get-u32 id)] (h/call wasm/internal-module "_use_shape" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3)))) (defn set-parent-id [id] (let [buffer (uuid/get-u32 id)] (h/call wasm/internal-module "_set_parent" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3)))) (defn set-shape-clip-content [clip-content] (h/call wasm/internal-module "_set_shape_clip_content" clip-content)) (defn set-shape-type [type] (h/call wasm/internal-module "_set_shape_type" (sr/translate-shape-type type))) (defn set-masked [masked] (h/call wasm/internal-module "_set_shape_masked_group" masked)) (defn set-shape-selrect [selrect] (h/call wasm/internal-module "_set_shape_selrect" (dm/get-prop selrect :x1) (dm/get-prop selrect :y1) (dm/get-prop selrect :x2) (dm/get-prop selrect :y2))) (defn set-shape-transform [transform] (h/call wasm/internal-module "_set_shape_transform" (dm/get-prop transform :a) (dm/get-prop transform :b) (dm/get-prop transform :c) (dm/get-prop transform :d) (dm/get-prop transform :e) (dm/get-prop transform :f))) (defn set-shape-rotation [rotation] (h/call wasm/internal-module "_set_shape_rotation" rotation)) (defn set-shape-children [shape-ids] (let [num-shapes (count shape-ids)] (perf/begin-measure "set-shape-children") (when (> num-shapes 0) (let [offset (mem/alloc-bytes (* CHILD-ENTRY-SIZE num-shapes)) heap (mem/get-heap-u32)] (loop [entries (seq shape-ids) current-offset offset] (when-not (empty? entries) (let [id (first entries)] (sr/heapu32-set-uuid id heap (mem/ptr8->ptr32 current-offset)) (recur (rest entries) (+ current-offset CHILD-ENTRY-SIZE))))))) (let [result (h/call wasm/internal-module "_set_children")] (perf/end-measure "set-shape-children") result))) (defn- get-string-length [string] (+ (count string) 1)) (defn- store-image [id] (let [buffer (uuid/get-u32 id) url (cf/resolve-file-media {:id id})] (->> (http/send! {:method :get :uri url :response-type :blob}) (rx/map :body) (rx/mapcat wapi/read-file-as-array-buffer) (rx/map (fn [image] (let [size (.-byteLength image) offset (mem/alloc-bytes size) heap (mem/get-heap-u8) data (js/Uint8Array. image)] (.set heap data offset) (h/call wasm/internal-module "_store_image" (aget buffer 0) (aget buffer 1) (aget buffer 2) (aget buffer 3)) true)))))) (defn set-shape-fills [fills] (h/call wasm/internal-module "_clear_shape_fills") (keep (fn [fill] (let [opacity (or (:fill-opacity fill) 1.0) color (:fill-color fill) gradient (:fill-color-gradient fill) image (:fill-image fill) offset (mem/alloc-bytes sr-fills/FILL-BYTE-SIZE) heap (mem/get-heap-u32)] (cond (some? color) (let [argb (sr-clr/hex->u32argb color opacity)] (sr-fills/write-solid-fill! offset heap argb) (h/call wasm/internal-module "_add_shape_fill")) (some? gradient) (do (sr-fills/write-gradient-fill! offset heap gradient opacity) (h/call wasm/internal-module "_add_shape_fill")) (some? image) (let [id (dm/get-prop image :id) buffer (uuid/get-u32 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 heap id opacity (dm/get-prop image :width) (dm/get-prop image :height)) (h/call wasm/internal-module "_add_shape_fill") (when (== cached-image? 0) (store-image id)))))) fills)) (defn set-shape-strokes [strokes] (h/call wasm/internal-module "_clear_shape_strokes") (keep (fn [stroke] (let [opacity (or (:stroke-opacity stroke) 1.0) color (:stroke-color stroke) gradient (:stroke-color-gradient stroke) image (:stroke-image stroke) width (:stroke-width stroke) align (:stroke-alignment stroke) 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) heap (mem/get-heap-u32)] (case align :inner (h/call wasm/internal-module "_add_shape_inner_stroke" width style cap-start cap-end) :outer (h/call wasm/internal-module "_add_shape_outer_stroke" width style cap-start cap-end) (h/call wasm/internal-module "_add_shape_center_stroke" width style cap-start cap-end)) (cond (some? gradient) (let [_ nil] (sr-fills/write-gradient-fill! offset heap gradient opacity) (h/call wasm/internal-module "_add_shape_stroke_fill")) (some? image) (let [id (dm/get-prop image :id) buffer (uuid/get-u32 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 heap id opacity (dm/get-prop image :width) (dm/get-prop image :height)) (h/call wasm/internal-module "_add_shape_stroke_fill") (when (== cached-image? 0) (store-image id))) (some? color) (let [argb (sr-clr/hex->u32argb color opacity)] (sr-fills/write-solid-fill! offset heap argb) (h/call wasm/internal-module "_add_shape_stroke_fill"))))) strokes)) (defn set-shape-path-attrs [attrs] (let [style (:style attrs) attrs (-> attrs (dissoc :style) (merge style)) str (sr/serialize-path-attrs attrs) size (count str) offset (mem/alloc-bytes size)] (h/call wasm/internal-module "stringToUTF8" str offset size) (h/call wasm/internal-module "_set_shape_path_attrs" (count attrs)))) ;; FIXME: revisit on heap refactor is merged to use u32 instead u8 (defn set-shape-path-content [content] (let [pdata (path/content content) size (path/get-byte-size content) offset (mem/alloc-bytes size) heap (mem/get-heap-u8)] (path/write-to pdata (.-buffer heap) offset) (h/call wasm/internal-module "_set_shape_path_content"))) (defn set-shape-svg-raw-content [content] (let [size (get-string-length content) offset (mem/alloc-bytes size)] (h/call wasm/internal-module "stringToUTF8" content offset size) (h/call wasm/internal-module "_set_shape_svg_raw_content"))) (defn set-shape-blend-mode [blend-mode] ;; These values correspond to skia::BlendMode representation ;; https://rust-skia.github.io/doc/skia_safe/enum.BlendMode.html (h/call wasm/internal-module "_set_shape_blend_mode" (sr/translate-blend-mode blend-mode))) (defn set-shape-opacity [opacity] (h/call wasm/internal-module "_set_shape_opacity" (or opacity 1))) (defn set-constraints-h [constraint] (when constraint (h/call wasm/internal-module "_set_shape_constraint_h" (sr/translate-constraint-h constraint)))) (defn set-constraints-v [constraint] (when constraint (h/call wasm/internal-module "_set_shape_constraint_v" (sr/translate-constraint-v constraint)))) (defn set-shape-hidden [hidden] (h/call wasm/internal-module "_set_shape_hidden" hidden)) (defn set-shape-bool-type [bool-type] (h/call wasm/internal-module "_set_shape_bool_type" (sr/translate-bool-type bool-type))) (defn- translate-blur-type [blur-type] (case blur-type :layer-blur 1 0)) (defn set-shape-blur [blur] (let [type (-> blur :type sr/translate-blur-type) hidden (:hidden blur) value (:value blur)] (h/call wasm/internal-module "_set_shape_blur" type hidden value))) (defn set-shape-corners [corners] (let [r1 (or (get corners 0) 0) r2 (or (get corners 1) 0) r3 (or (get corners 2) 0) r4 (or (get corners 3) 0)] (h/call wasm/internal-module "_set_shape_corners" r1 r2 r3 r4))) (defn set-flex-layout [shape] (let [dir (-> (or (dm/get-prop shape :layout-flex-dir) :row) sr/translate-layout-flex-dir) gap (dm/get-prop shape :layout-gap) row-gap (or (dm/get-prop gap :row-gap) 0) column-gap (or (dm/get-prop gap :column-gap) 0) align-items (-> (or (dm/get-prop shape :layout-align-items) :start) sr/translate-layout-align-items) align-content (-> (or (dm/get-prop shape :layout-align-content) :stretch) sr/translate-layout-align-content) justify-items (-> (or (dm/get-prop shape :layout-justify-items) :start) sr/translate-layout-justify-items) justify-content (-> (or (dm/get-prop shape :layout-justify-content) :stretch) sr/translate-layout-justify-content) wrap-type (-> (or (dm/get-prop shape :layout-wrap-type) :nowrap) sr/translate-layout-wrap-type) padding (dm/get-prop shape :layout-padding) padding-top (or (dm/get-prop padding :p1) 0) padding-right (or (dm/get-prop padding :p2) 0) padding-bottom (or (dm/get-prop padding :p3) 0) padding-left (or (dm/get-prop padding :p4) 0)] (h/call wasm/internal-module "_set_flex_layout_data" dir row-gap column-gap align-items align-content justify-items justify-content wrap-type padding-top padding-right padding-bottom padding-left))) (defn set-grid-layout-data [shape] (let [dir (-> (or (dm/get-prop shape :layout-grid-dir) :row) sr/translate-layout-grid-dir) gap (dm/get-prop shape :layout-gap) row-gap (or (dm/get-prop gap :row-gap) 0) column-gap (or (dm/get-prop gap :column-gap) 0) align-items (-> (or (dm/get-prop shape :layout-align-items) :start) sr/translate-layout-align-items) align-content (-> (or (dm/get-prop shape :layout-align-content) :stretch) sr/translate-layout-align-content) justify-items (-> (or (dm/get-prop shape :layout-justify-items) :start) sr/translate-layout-justify-items) justify-content (-> (or (dm/get-prop shape :layout-justify-content) :stretch) sr/translate-layout-justify-content) padding (dm/get-prop shape :layout-padding) padding-top (or (dm/get-prop padding :p1) 0) padding-right (or (dm/get-prop padding :p2) 0) padding-bottom (or (dm/get-prop padding :p3) 0) padding-left (or (dm/get-prop padding :p4) 0)] (h/call wasm/internal-module "_set_grid_layout_data" dir row-gap column-gap align-items align-content justify-items justify-content padding-top padding-right padding-bottom padding-left))) (defn set-grid-layout-rows [entries] (let [size (grid-layout-get-row-entries-size entries) offset (mem/alloc-bytes size) heap (js/Uint8Array. (.-buffer (mem/get-heap-u8)) offset size)] (loop [entries (seq entries) current-offset 0] (when-not (empty? entries) (let [{:keys [type value]} (first entries)] (.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ current-offset 0)) (.set heap (sr/f32->u8 value) (+ current-offset 1)) (recur (rest entries) (+ current-offset GRID-LAYOUT-ROW-ENTRY-SIZE))))) (h/call wasm/internal-module "_set_grid_rows"))) (defn set-grid-layout-columns [entries] (let [size (grid-layout-get-column-entries-size entries) offset (mem/alloc-bytes size) heap (js/Uint8Array. (.-buffer (mem/get-heap-u8)) offset size)] (loop [entries (seq entries) current-offset 0] (when-not (empty? entries) (let [{:keys [type value]} (first entries)] (.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ current-offset 0)) (.set heap (sr/f32->u8 value) (+ current-offset 1)) (recur (rest entries) (+ current-offset GRID-LAYOUT-COLUMN-ENTRY-SIZE))))) (h/call wasm/internal-module "_set_grid_columns"))) (defn set-grid-layout-cells [cells] (let [entries (vals cells) size (grid-layout-get-cell-entries-size entries) offset (mem/alloc-bytes size) heap (js/Uint8Array. (.-buffer (mem/get-heap-u8)) offset size)] (loop [entries (seq entries) current-offset 0] (when-not (empty? entries) (let [cell (first entries)] ;; row: [u8; 4], (.set heap (sr/i32->u8 (:row cell)) (+ current-offset 0)) ;; row_span: [u8; 4], (.set heap (sr/i32->u8 (:row-span cell)) (+ current-offset 4)) ;; column: [u8; 4], (.set heap (sr/i32->u8 (:column cell)) (+ current-offset 8)) ;; column_span: [u8; 4], (.set heap (sr/i32->u8 (:column-span cell)) (+ current-offset 12)) ;; has_align_self: u8, (.set heap (sr/bool->u8 (some? (:align-self cell))) (+ current-offset 16)) ;; align_self: u8, (.set heap (sr/u8 (sr/translate-align-self (:align-self cell))) (+ current-offset 17)) ;; has_justify_self: u8, (.set heap (sr/bool->u8 (some? (:justify-self cell))) (+ current-offset 18)) ;; justify_self: u8, (.set heap (sr/u8 (sr/translate-justify-self (:justify-self cell))) (+ current-offset 19)) ;; has_shape_id: u8, (.set heap (sr/bool->u8 (d/not-empty? (:shapes cell))) (+ current-offset 20)) ;; shape_id_a: [u8; 4], ;; shape_id_b: [u8; 4], ;; shape_id_c: [u8; 4], ;; shape_id_d: [u8; 4], (.set heap (sr/uuid->u8 (or (-> cell :shapes first) uuid/zero)) (+ current-offset 21)) (recur (rest entries) (+ current-offset GRID-LAYOUT-CELL-ENTRY-SIZE))))) (h/call wasm/internal-module "_set_grid_cells"))) (defn set-grid-layout [shape] (set-grid-layout-data shape) (set-grid-layout-rows (:layout-grid-rows shape)) (set-grid-layout-columns (:layout-grid-columns shape)) (set-grid-layout-cells (:layout-grid-cells shape))) (defn set-layout-child [shape] (let [margins (dm/get-prop shape :layout-item-margin) margin-top (or (dm/get-prop margins :m1) 0) margin-right (or (dm/get-prop margins :m2) 0) margin-bottom (or (dm/get-prop margins :m3) 0) margin-left (or (dm/get-prop margins :m4) 0) h-sizing (-> (dm/get-prop shape :layout-item-h-sizing) (or :fix) sr/translate-layout-sizing) v-sizing (-> (dm/get-prop shape :layout-item-v-sizing) (or :fix) sr/translate-layout-sizing) align-self (-> (dm/get-prop shape :layout-item-align-self) sr/translate-align-self) max-h (dm/get-prop shape :layout-item-max-h) has-max-h (some? max-h) min-h (dm/get-prop shape :layout-item-min-h) has-min-h (some? min-h) max-w (dm/get-prop shape :layout-item-max-w) has-max-w (some? max-w) min-w (dm/get-prop shape :layout-item-min-w) has-min-w (some? min-w) is-absolute (boolean (dm/get-prop shape :layout-item-absolute)) z-index (-> (dm/get-prop shape :layout-item-z-index) (or 0))] (h/call wasm/internal-module "_set_layout_child_data" margin-top margin-right margin-bottom margin-left h-sizing v-sizing has-max-h (or max-h 0) has-min-h (or min-h 0) has-max-w (or max-w 0) has-min-w (or min-w 0) (some? align-self) (or align-self 0) is-absolute z-index))) (defn set-shape-shadows [shadows] (h/call wasm/internal-module "_clear_shape_shadows") (let [total-shadows (count shadows)] (loop [index 0] (when (< index total-shadows) (let [shadow (nth shadows index) color (dm/get-prop shadow :color) blur (dm/get-prop shadow :blur) rgba (sr-clr/hex->u32argb (dm/get-prop color :color) (dm/get-prop color :opacity)) hidden (dm/get-prop shadow :hidden) x (dm/get-prop shadow :offset-x) y (dm/get-prop shadow :offset-y) spread (dm/get-prop shadow :spread) style (dm/get-prop shadow :style)] (h/call wasm/internal-module "_add_shape_shadow" rgba blur spread x y (sr/translate-shadow-style style) hidden) (recur (inc index))))))) (declare propagate-apply) (defn set-shape-text-content [content] (h/call wasm/internal-module "_clear_shape_text") (let [paragraph-set (first (dm/get-prop content :children)) paragraphs (dm/get-prop paragraph-set :children) fonts (fonts/get-content-fonts content) emoji? (atom false)] (loop [index 0] (when (< index (count paragraphs)) (let [paragraph (nth paragraphs index) leaves (dm/get-prop paragraph :children)] (when (seq leaves) (let [text (apply str (map :text leaves))] (when (and (not @emoji?) (t/contains-emoji? text)) (reset! emoji? true)) (t/write-shape-text leaves paragraph text)) (recur (inc index)))))) (let [fonts (if @emoji? (f/add-emoji-font fonts) fonts)] (f/store-fonts fonts)))) (defn set-shape-grow-type [grow-type] (h/call wasm/internal-module "_set_shape_grow_type" (sr/translate-grow-type grow-type))) (defn text-dimensions ([id] (use-shape id) (text-dimensions)) ([] (let [offset (h/call wasm/internal-module "_get_text_dimensions") heapf32 (mem/get-heap-f32) width (aget heapf32 (mem/ptr8->ptr32 offset)) height (aget heapf32 (mem/ptr8->ptr32 (+ offset 4)))] (h/call wasm/internal-module "_free_bytes") {:width width :height height}))) (defn set-view-box [zoom vbox] (h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) (render (uperf/now))) (defn clear-drawing-cache [] (h/call wasm/internal-module "_clear_drawing_cache")) (defn update-shape-tiles [] (h/call wasm/internal-module "_update_shape_tiles")) (defn set-object [objects shape] (perf/begin-measure "set-object") (let [id (dm/get-prop shape :id) parent-id (dm/get-prop shape :parent-id) type (dm/get-prop shape :type) masked (dm/get-prop shape :masked-group) selrect (dm/get-prop shape :selrect) constraint-h (dm/get-prop shape :constraints-h) constraint-v (dm/get-prop shape :constraints-v) clip-content (if (= type :frame) (not (dm/get-prop shape :show-content)) false) rotation (dm/get-prop shape :rotation) transform (dm/get-prop shape :transform) fills (if (= type :group) [] (dm/get-prop shape :fills)) strokes (if (= type :group) [] (dm/get-prop shape :strokes)) children (dm/get-prop shape :shapes) blend-mode (dm/get-prop shape :blend-mode) opacity (dm/get-prop shape :opacity) hidden (dm/get-prop shape :hidden) content (dm/get-prop shape :content) grow-type (dm/get-prop shape :grow-type) blur (dm/get-prop shape :blur) corners (when (some? (dm/get-prop shape :r1)) [(dm/get-prop shape :r1) (dm/get-prop shape :r2) (dm/get-prop shape :r3) (dm/get-prop shape :r4)]) svg-attrs (dm/get-prop shape :svg-attrs) shadows (dm/get-prop shape :shadow)] (use-shape id) (set-parent-id parent-id) (set-shape-type type) (set-shape-clip-content clip-content) (set-shape-selrect selrect) (set-constraints-h constraint-h) (set-constraints-v constraint-v) (set-shape-rotation rotation) (set-shape-transform transform) (set-shape-blend-mode blend-mode) (set-shape-opacity opacity) (set-shape-hidden hidden) (set-shape-children children) (when (and (= type :group) masked) (set-masked masked)) (when (some? blur) (set-shape-blur blur)) (when (and (some? content) (or (= type :path) (= type :bool))) (when (some? svg-attrs) (set-shape-path-attrs svg-attrs)) (set-shape-path-content content)) (when (and (some? content) (= type :svg-raw)) (set-shape-svg-raw-content (get-static-markup shape))) (when (some? corners) (set-shape-corners corners)) (when (some? shadows) (set-shape-shadows shadows)) (when (and (= type :text) (some? content)) (set-shape-text-content content)) (when (= type :text) (set-shape-grow-type grow-type)) (when (or (ctl/any-layout? shape) (ctl/any-layout-immediate-child? objects shape)) (set-layout-child shape)) (when (ctl/flex-layout? shape) (set-flex-layout shape)) (when (ctl/grid-layout? shape) (set-grid-layout shape)) (let [pending (into [] (concat (if (and (= type :text) (some? content)) (set-shape-text-content content) []) (set-shape-fills fills) (set-shape-strokes strokes)))] (perf/end-measure "set-object") pending))) (defn process-object [shape] (let [pending (set-object [] shape)] (when-let [pending (seq pending)] (->> (rx/from pending) (rx/mapcat identity) (rx/reduce conj []) (rx/subs! (fn [_] (clear-drawing-cache) (request-render "set-objects"))))))) (defn set-objects [objects] (perf/begin-measure "set-objects") (let [shapes (into [] (vals objects)) total-shapes (count shapes) pending (loop [index 0 pending []] (if (< index total-shapes) (let [shape (nth shapes index) pending' (set-object objects shape)] (recur (inc index) (into pending pending'))) pending))] (perf/end-measure "set-objects") (clear-drawing-cache) (request-render "set-objects") (when-let [pending (seq pending)] (->> (rx/from pending) (rx/mapcat identity) (rx/reduce conj []) (rx/subs! (fn [_] (clear-drawing-cache) (request-render "set-objects"))))))) (defn set-structure-modifiers [entries] (when-not (empty? entries) (let [offset (mem/alloc-bytes-32 (mem/get-list-size entries 40)) heapu32 (mem/get-heap-u32)] (loop [entries (seq entries) current-offset offset] (when-not (empty? entries) (let [{:keys [type parent id index] :as entry} (first entries)] (sr/heapu32-set-u32 (sr/translate-structure-modifier-type type) heapu32 (+ current-offset 0)) (sr/heapu32-set-u32 (or index 0) heapu32 (+ current-offset 1)) (sr/heapu32-set-uuid parent heapu32 (+ current-offset 2)) (sr/heapu32-set-uuid id heapu32 (+ current-offset 6)) (recur (rest entries) (+ current-offset 10))))) (h/call wasm/internal-module "_set_structure_modifiers")))) (defn propagate-modifiers [entries] (when (d/not-empty? entries) (let [offset (mem/alloc-bytes-32 (modifier-get-entries-size entries)) heapf32 (mem/get-heap-f32) heapu32 (mem/get-heap-u32)] (loop [entries (seq entries) current-offset offset] (when-not (empty? entries) (let [{:keys [id transform]} (first entries)] (sr/heapu32-set-uuid id heapu32 current-offset) (sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-TRANSFORM-OFFSET))) (recur (rest entries) (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-SIZE)))))) (let [result-offset (h/call wasm/internal-module "_propagate_modifiers") heapf32 (mem/get-heap-f32) heapu32 (mem/get-heap-u32) len (aget heapu32 (mem/ptr8->ptr32 result-offset)) result (->> (range 0 len) (mapv #(dr/heap32->entry heapu32 heapf32 (mem/ptr8->ptr32 (+ result-offset 4 (* % MODIFIER-ENTRY-SIZE))))))] (h/call wasm/internal-module "_free_bytes") result)))) (defn propagate-apply [entries] (when (d/not-empty? entries) (let [offset (mem/alloc-bytes-32 (modifier-get-entries-size entries)) heapf32 (mem/get-heap-f32) heapu32 (mem/get-heap-u32)] (loop [entries (seq entries) current-offset offset] (when-not (empty? entries) (let [{:keys [id transform]} (first entries)] (sr/heapu32-set-uuid id heapu32 current-offset) (sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-TRANSFORM-OFFSET))) (recur (rest entries) (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-SIZE)))))) (let [offset (h/call wasm/internal-module "_propagate_apply") heapf32 (mem/get-heap-f32) width (aget heapf32 (mem/ptr8->ptr32 (+ offset 0))) height (aget heapf32 (mem/ptr8->ptr32 (+ offset 4))) cx (aget heapf32 (mem/ptr8->ptr32 (+ offset 8))) cy (aget heapf32 (mem/ptr8->ptr32 (+ offset 12))) a (aget heapf32 (mem/ptr8->ptr32 (+ offset 16))) b (aget heapf32 (mem/ptr8->ptr32 (+ offset 20))) c (aget heapf32 (mem/ptr8->ptr32 (+ offset 24))) d (aget heapf32 (mem/ptr8->ptr32 (+ offset 28))) e (aget heapf32 (mem/ptr8->ptr32 (+ offset 32))) f (aget heapf32 (mem/ptr8->ptr32 (+ offset 36))) transform (gmt/matrix a b c d e f)] (h/call wasm/internal-module "_free_bytes") (request-render "set-modifiers") {:width width :height height :center (gpt/point cx cy) :transform transform})))) (defn set-canvas-background [background] (let [rgba (sr-clr/hex->u32argb background 1)] (h/call wasm/internal-module "_set_canvas_background" rgba) (request-render "set-canvas-background"))) (defn clean-modifiers [] (h/call wasm/internal-module "_clean_modifiers")) (defn set-modifiers [modifiers] (when-not (empty? modifiers) (let [offset (mem/alloc-bytes-32 (* MODIFIER-ENTRY-SIZE (count modifiers))) heapu32 (mem/get-heap-u32) heapf32 (mem/get-heap-f32)] (loop [entries (seq modifiers) current-offset offset] (when-not (empty? entries) (let [{:keys [id transform]} (first entries)] (sr/heapu32-set-uuid id heapu32 current-offset) (sr/heapf32-set-matrix transform heapf32 (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-TRANSFORM-OFFSET))) (recur (rest entries) (+ current-offset (mem/ptr8->ptr32 MODIFIER-ENTRY-SIZE)))))) (h/call wasm/internal-module "_set_modifiers") (request-render "set-modifiers")))) (defn initialize [base-objects zoom vbox background] (let [rgba (sr-clr/hex->u32argb background 1) shapes (into [] (vals base-objects)) total-shapes (count shapes)] (h/call wasm/internal-module "_set_canvas_background" rgba) (h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox))) (h/call wasm/internal-module "_init_shapes_pool" total-shapes) (set-objects base-objects))) (def ^:private canvas-options #js {:antialias false :depth true :stencil true :alpha true "preserveDrawingBuffer" true}) (defn resize-viewbox [width height] (h/call wasm/internal-module "_resize_viewbox" width height)) (defn- debug-flags [] (cond-> 0 (dbg/enabled? :wasm-viewbox) (bit-or 2r00000000000000000000000000000001))) (defn assign-canvas [canvas] (let [gl (unchecked-get wasm/internal-module "GL") flags (debug-flags) context (.getContext ^js canvas "webgl2" canvas-options) ;; Register the context with emscripten handle (.registerContext ^js gl context #js {"majorVersion" 2})] (.makeContextCurrent ^js gl handle) ;; Force the WEBGL_debug_renderer_info extension as emscripten does not enable it (.getExtension context "WEBGL_debug_renderer_info") ;; Initialize Wasm Render Engine (h/call wasm/internal-module "_init" (/ (.-width ^js canvas) dpr) (/ (.-height ^js canvas) dpr)) (h/call wasm/internal-module "_set_render_options" flags dpr)) (set! (.-width canvas) (* dpr (.-clientWidth ^js canvas))) (set! (.-height canvas) (* dpr (.-clientHeight ^js canvas)))) (defn clear-canvas [] ;; TODO: perform corresponding cleaning (h/call wasm/internal-module "_clean_up")) (defonce module (delay (if (exists? js/dynamicImport) (let [uri (cf/resolve-static-asset "js/render_wasm.js")] (->> (js/dynamicImport (str uri)) (p/mcat (fn [module] (let [default (unchecked-get module "default")] (default)))) (p/fmap (fn [module] (set! wasm/internal-module module) true)) (p/merr (fn [cause] (js/console.error cause) (p/resolved false))))) (p/resolved false))))