♻️ Refactor heap usage (#6204)

This commit is contained in:
Aitor Moreno 2025-04-03 16:04:51 +02:00 committed by GitHub
parent 9bc49e3381
commit cd731c3ad2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 249 additions and 150 deletions

View file

@ -10,7 +10,6 @@
["react-dom/server" :as rds]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
[app.common.math :as mth]
[app.common.types.shape.layout :as ctl]
[app.common.types.shape.path :as path]
@ -20,7 +19,9 @@
[app.main.refs :as refs]
[app.main.render :as render]
[app.render-wasm.api.fonts :as f]
[app.render-wasm.deserializers :as dr]
[app.render-wasm.helpers :as h]
[app.render-wasm.mem :as mem]
[app.render-wasm.serializers :as sr]
[app.render-wasm.wasm :as wasm]
[app.util.debug :as dbg]
@ -28,18 +29,51 @@
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[goog.object :as gobj]
[promesa.core :as p]
[rumext.v2 :as mf]))
;; (defonce internal-frame-id nil)
;; (defonce internal-module #js {})
;; (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)
(def GRADIENT-STOP-SIZE 5)
(defn gradient-stop-get-entries-size
[stops]
(mem/get-list-size stops GRADIENT-STOP-SIZE))
(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}
@ -154,28 +188,22 @@
(defn set-shape-children
[shape-ids]
(let [ENTRY-SIZE 16
ptr
(h/call wasm/internal-module "_alloc_bytes" (* ENTRY-SIZE (count shape-ids)))
(let [num-shapes (count shape-ids)]
(when (> num-shapes 0)
(let [offset (mem/alloc-bytes (* CHILD-ENTRY-SIZE num-shapes))
heap (mem/get-heap-u32)]
heap
(js/Uint8Array.
(.-buffer (gobj/get ^js wasm/internal-module "HEAPU8"))
ptr
(* ENTRY-SIZE (count shape-ids)))]
(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)))))
(loop [entries (seq shape-ids)
offset 0]
(when-not (empty? entries)
(let [id (first entries)]
(.set heap (sr/uuid->u8 id) offset)
(recur (rest entries) (+ offset ENTRY-SIZE)))))
(h/call wasm/internal-module "_set_children")))
(h/call wasm/internal-module "_set_children")))))
(defn- get-string-length [string] (+ (count string) 1))
(defn- store-image
[id]
@ -187,11 +215,11 @@
(rx/map :body)
(rx/mapcat wapi/read-file-as-array-buffer)
(rx/map (fn [image]
(let [image-size (.-byteLength image)
image-ptr (h/call wasm/internal-module "_alloc_bytes" image-size)
heap (gobj/get ^js wasm/internal-module "HEAPU8")
mem (js/Uint8Array. (.-buffer heap) image-ptr image-size)]
(.set mem (js/Uint8Array. image))
(let [size (.-byteLength image)
offset (mem/alloc-bytes size)
heap (mem/get-heap-u8)
mem (js/Uint8Array. (.-buffer heap) offset size)]
(.set heap mem offset)
(h/call wasm/internal-module "_store_image"
(aget buffer 0)
(aget buffer 1)
@ -214,11 +242,10 @@
(some? gradient)
(let [stops (:stops gradient)
n-stops (count stops)
mem-size (* 5 n-stops)
stops-ptr (h/call wasm/internal-module "_alloc_bytes" mem-size)
heap (gobj/get ^js wasm/internal-module "HEAPU8")
mem (js/Uint8Array. (.-buffer heap) stops-ptr mem-size)]
size (gradient-stop-get-entries-size stops)
offset (mem/alloc-bytes size)
heap (mem/get-heap-u8)
mem (js/Uint8Array. (.-buffer heap) offset size)]
(if (= (:type gradient) :linear)
(h/call wasm/internal-module "_add_shape_linear_fill"
(:start-x gradient)
@ -277,11 +304,10 @@
(cond
(some? gradient)
(let [stops (:stops gradient)
n-stops (count stops)
mem-size (* 5 n-stops)
stops-ptr (h/call wasm/internal-module "_alloc_bytes" mem-size)
heap (gobj/get ^js wasm/internal-module "HEAPU8")
mem (js/Uint8Array. (.-buffer heap) stops-ptr mem-size)]
size (gradient-stop-get-entries-size stops)
offset (mem/alloc-bytes size)
heap (mem/get-heap-u8)
mem (js/Uint8Array. (.-buffer heap) offset size)]
(if (= (:type gradient) :linear)
(h/call wasm/internal-module "_add_shape_stroke_linear_fill"
(:start-x gradient)
@ -333,24 +359,24 @@
(merge style))
str (sr/serialize-path-attrs attrs)
size (count str)
ptr (h/call wasm/internal-module "_alloc_bytes" size)]
(h/call wasm/internal-module "stringToUTF8" str ptr size)
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))))
(defn set-shape-path-content
[content]
(let [pdata (path/path-data content)
size (* (count pdata) path/SEGMENT-BYTE-SIZE)
offset (h/call wasm/internal-module "_alloc_bytes" size)
heap (gobj/get ^js wasm/internal-module "HEAPU8")]
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)
ptr (h/call wasm/internal-module "_alloc_bytes" size)]
(h/call wasm/internal-module "stringToUTF8" content ptr size)
offset (mem/alloc-bytes size)]
(h/call wasm/internal-module "stringToUTF8" content offset size)
(h/call wasm/internal-module "_set_shape_svg_raw_content")))
@ -365,8 +391,6 @@
[opacity]
(h/call wasm/internal-module "_set_shape_opacity" (or opacity 1)))
(defn set-constraints-h
[constraint]
(when constraint
@ -473,93 +497,93 @@
padding-left))
;; Send Rows
(let [entry-size 5
entries (:layout-grid-rows shape)
ptr (h/call wasm/internal-module "_alloc_bytes" (* entry-size (count entries)))
(let [entries (:layout-grid-rows shape)
size (grid-layout-get-row-entries-size entries)
offset (mem/alloc-bytes size)
heap
(js/Uint8Array.
(.-buffer (gobj/get ^js wasm/internal-module "HEAPU8"))
ptr
(* entry-size (count entries)))]
(.-buffer (mem/get-heap-u8))
offset
size)]
(loop [entries (seq entries)
offset 0]
current-offset 0]
(when-not (empty? entries)
(let [{:keys [type value]} (first entries)]
(.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ offset 0))
(.set heap (sr/f32->u8 value) (+ offset 1))
(recur (rest entries) (+ offset entry-size)))))
(.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"))
;; Send Columns
(let [entry-size 5
entries (:layout-grid-columns shape)
ptr (h/call wasm/internal-module "_alloc_bytes" (* entry-size (count entries)))
(let [entries (:layout-grid-columns shape)
size (grid-layout-get-column-entries-size entries)
offset (mem/alloc-bytes size)
heap
(js/Uint8Array.
(.-buffer (gobj/get ^js wasm/internal-module "HEAPU8"))
ptr
(* entry-size (count entries)))]
(.-buffer (mem/get-heap-u8))
offset
size)]
(loop [entries (seq entries)
offset 0]
current-offset 0]
(when-not (empty? entries)
(let [{:keys [type value]} (first entries)]
(.set heap (sr/u8 (sr/translate-grid-track-type type)) (+ offset 0))
(.set heap (sr/f32->u8 value) (+ offset 1))
(recur (rest entries) (+ offset entry-size)))))
(.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"))
;; Send cells
(let [entry-size 37
entries (-> shape :layout-grid-cells vals)
ptr (h/call wasm/internal-module "_alloc_bytes" (* entry-size (count entries)))
(let [entries (-> shape :layout-grid-cells vals)
size (grid-layout-get-cell-entries-size entries)
offset (mem/alloc-bytes size)
heap
(js/Uint8Array.
(.-buffer (gobj/get ^js wasm/internal-module "HEAPU8"))
ptr
(* entry-size (count entries)))]
(.-buffer (mem/get-heap-u8))
offset
size)]
(loop [entries (seq entries)
offset 0]
current-offset 0]
(when-not (empty? entries)
(let [cell (first entries)]
;; row: [u8; 4],
(.set heap (sr/i32->u8 (:row cell)) (+ offset 0))
(.set heap (sr/i32->u8 (:row cell)) (+ current-offset 0))
;; row_span: [u8; 4],
(.set heap (sr/i32->u8 (:row-span cell)) (+ offset 4))
(.set heap (sr/i32->u8 (:row-span cell)) (+ current-offset 4))
;; column: [u8; 4],
(.set heap (sr/i32->u8 (:column cell)) (+ offset 8))
(.set heap (sr/i32->u8 (:column cell)) (+ current-offset 8))
;; column_span: [u8; 4],
(.set heap (sr/i32->u8 (:column-span cell)) (+ offset 12))
(.set heap (sr/i32->u8 (:column-span cell)) (+ current-offset 12))
;; has_align_self: u8,
(.set heap (sr/bool->u8 (some? (:align-self cell))) (+ offset 16))
(.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))) (+ offset 17))
(.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))) (+ offset 18))
(.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))) (+ offset 19))
(.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))) (+ offset 20))
(.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)) (+ offset 21))
(.set heap (sr/uuid->u8 (or (-> cell :shapes first) uuid/zero)) (+ current-offset 21))
(recur (rest entries) (+ offset entry-size)))))
(recur (rest entries) (+ current-offset GRID-LAYOUT-CELL-ENTRY-SIZE)))))
(h/call wasm/internal-module "_set_grid_cells")))
@ -637,9 +661,9 @@
font-size (js/Number (dm/get-prop leaf :font-size))
buffer (utf8->buffer text)
size (.-byteLength buffer)
ptr (h/call wasm/internal-module "_alloc_bytes" size)
heap (gobj/get ^js wasm/internal-module "HEAPU8")
mem (js/Uint8Array. (.-buffer heap) ptr size)]
offset (mem/alloc-bytes size)
heap (mem/get-heap-u8)
mem (js/Uint8Array. (.-buffer heap) offset size)]
(.set mem buffer)
(h/call wasm/internal-module "_add_text_leaf"
(aget font-id 0)
@ -788,51 +812,27 @@
(clear-drawing-cache)
(request-render "set-objects")))))))
(defn data->entry
[data offset]
(let [id1 (.getUint32 data (+ offset 0) true)
id2 (.getUint32 data (+ offset 4) true)
id3 (.getUint32 data (+ offset 8) true)
id4 (.getUint32 data (+ offset 12) true)
a (.getFloat32 data (+ offset 16) true)
b (.getFloat32 data (+ offset 20) true)
c (.getFloat32 data (+ offset 24) true)
d (.getFloat32 data (+ offset 28) true)
e (.getFloat32 data (+ offset 32) true)
f (.getFloat32 data (+ offset 36) true)
id (uuid/from-unsigned-parts id1 id2 id3 id4)]
{:id id
:transform (gmt/matrix a b c d e f)}))
(defn propagate-modifiers
[entries]
(let [entry-size 40
ptr (h/call wasm/internal-module "_alloc_bytes" (* entry-size (count entries)))
heap
(js/Uint8Array.
(.-buffer (gobj/get ^js wasm/internal-module "HEAPU8"))
ptr
(* entry-size (count 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)
offset 0]
current-offset offset]
(when-not (empty? entries)
(let [{:keys [id transform]} (first entries)]
(.set heap (sr/uuid->u8 id) offset)
(.set heap (sr/matrix->u8 transform) (+ offset 16))
(recur (rest entries) (+ offset entry-size)))))
(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-ptr (h/call wasm/internal-module "_propagate_modifiers")
heap (js/DataView. (.-buffer (gobj/get ^js wasm/internal-module "HEAPU8")))
len (.getUint32 heap result-ptr true)
(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 #(data->entry heap (+ result-ptr 4 (* % entry-size)))))]
(mapv #(dr/heap32->entry heapu32 heapf32 (mem/ptr8->ptr32 (+ result-offset 4 (* % MODIFIER-ENTRY-SIZE))))))]
(h/call wasm/internal-module "_free_bytes")
result)))
@ -848,24 +848,17 @@
(if (empty? modifiers)
(h/call wasm/internal-module "_clean_modifiers")
(let [ENTRY-SIZE 40
ptr
(h/call wasm/internal-module "_alloc_bytes" (* ENTRY-SIZE (count modifiers)))
heap
(js/Uint8Array.
(.-buffer (gobj/get ^js wasm/internal-module "HEAPU8"))
ptr
(* ENTRY-SIZE (count 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)
offset 0]
current-offset offset]
(when-not (empty? entries)
(let [{:keys [id transform]} (first entries)]
(.set heap (sr/uuid->u8 id) offset)
(.set heap (sr/matrix->u8 transform) (+ offset 16))
(recur (rest entries) (+ offset ENTRY-SIZE)))))
(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")

View file

@ -0,0 +1,29 @@
;; 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.deserializers
(:require
[app.common.geom.matrix :as gmt]
[app.common.uuid :as uuid]))
(defn heap32->entry
[heapu32 heapf32 offset]
(let [id1 (aget heapu32 (+ offset 0))
id2 (aget heapu32 (+ offset 1))
id3 (aget heapu32 (+ offset 2))
id4 (aget heapu32 (+ offset 3))
a (aget heapf32 (+ offset 4))
b (aget heapf32 (+ offset 5))
c (aget heapf32 (+ offset 6))
d (aget heapf32 (+ offset 7))
e (aget heapf32 (+ offset 8))
f (aget heapf32 (+ offset 9))
id (uuid/from-unsigned-parts id1 id2 id3 id4)]
{:id id
:transform (gmt/matrix a b c d e f)}))

View file

@ -0,0 +1,56 @@
;; 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.mem
(:require
[app.render-wasm.helpers :as h]
[app.render-wasm.wasm :as wasm]))
(defn ptr8->ptr32
"Returns a 32-bit (4-byte aligned) pointer of an 8-bit pointer"
[value]
;; Divides the value by 4
(bit-shift-right value 2))
(defn ptr32->ptr8
"Returns a 8-bit pointer of a 32-bit (4-byte aligned) pointer"
[value]
;; Multiplies by 4
(bit-shift-left value 2))
(defn get-list-size
"Returns the size of a list in bytes"
[list list-item-size]
(* list-item-size (count list)))
(defn alloc-bytes
"Allocates an arbitrary amount of bytes"
[size]
(when (= size 0)
(js/console.trace "Tried to allocate 0 bytes"))
(h/call wasm/internal-module "_alloc_bytes" size))
(defn alloc-bytes-32
"Allocates a 4-byte aligned amount of bytes"
[size]
(when (= size 0)
(js/console.trace "Tried to allocate 0 bytes"))
(h/call wasm/internal-module "_alloc_bytes" size))
(defn get-heap-u8
"Returns a Uint8Array view of the heap"
[]
(unchecked-get ^js wasm/internal-module "HEAPU8"))
(defn get-heap-u32
"Returns a Uint32Array view of the heap"
[]
(unchecked-get ^js wasm/internal-module "HEAPU32"))
(defn get-heap-f32
"Returns a Float32Array view of the heap"
[]
(unchecked-get ^js wasm/internal-module "HEAPF32"))

View file

@ -6,6 +6,7 @@
(ns app.render-wasm.serializers
(:require
[app.common.data.macros :as dm]
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
@ -43,16 +44,26 @@
(aset u32-arr 3 (aget buffer 3))
(js/Uint8Array. (.-buffer u32-arr))))
(defn matrix->u8
[{:keys [a b c d e f]}]
(let [f32-arr (js/Float32Array. 6)]
(aset f32-arr 0 a)
(aset f32-arr 1 b)
(aset f32-arr 2 c)
(aset f32-arr 3 d)
(aset f32-arr 4 e)
(aset f32-arr 5 f)
(js/Uint8Array. (.-buffer f32-arr))))
(defn heapu32-set-uuid
[id heap offset]
(let [buffer (uuid/get-u32 id)]
(.set heap buffer offset)
buffer))
(defn heapf32-set-matrix
[matrix heap offset]
(let [a (dm/get-prop matrix :a)
b (dm/get-prop matrix :b)
c (dm/get-prop matrix :c)
d (dm/get-prop matrix :d)
e (dm/get-prop matrix :e)
f (dm/get-prop matrix :f)]
(aset heap (+ offset 0) a)
(aset heap (+ offset 1) b)
(aset heap (+ offset 2) c)
(aset heap (+ offset 3) d)
(aset heap (+ offset 4) e)
(aset heap (+ offset 5) f)))
(defn translate-shape-type
[type]

View file

@ -1,5 +1,9 @@
use std::alloc::{alloc, Layout};
use std::ptr;
use std::sync::Mutex;
const LAYOUT_ALIGN: usize = 4;
static BUFFERU8: Mutex<Option<Box<Vec<u8>>>> = Mutex::new(None);
#[no_mangle]
@ -10,11 +14,17 @@ pub extern "C" fn alloc_bytes(len: usize) -> *mut u8 {
panic!("Bytes already allocated");
}
let mut new_buffer = Box::new(vec![0u8; len]);
let ptr = new_buffer.as_mut_ptr();
*guard = Some(new_buffer);
ptr
unsafe {
let layout = Layout::from_size_align_unchecked(len, LAYOUT_ALIGN);
let ptr = alloc(layout) as *mut u8;
if ptr.is_null() {
panic!("Allocation failed");
}
// TODO: Esto quizá se podría eliminar.
ptr::write_bytes(ptr, 0, len);
*guard = Some(Box::new(Vec::from_raw_parts(ptr, len, len)));
ptr
}
}
pub fn write_bytes(bytes: Vec<u8>) -> *mut u8 {