diff --git a/CHANGES.md b/CHANGES.md index a697530ab4..73fb7b17f2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,7 +23,14 @@ - Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339) -## 2.12.0 (Unreleased) +## 2.12.1 + +### :bug: Bugs fixed + +- Fix setting a portion of text as bold or underline messes things up [Github #7980](https://github.com/penpot/penpot/issues/7980) + + +## 2.12.0 ### :boom: Breaking changes & Deprecations diff --git a/docker/devenv/files/start-tmux.sh b/docker/devenv/files/start-tmux.sh index 69e0423f22..6f1716bed9 100755 --- a/docker/devenv/files/start-tmux.sh +++ b/docker/devenv/files/start-tmux.sh @@ -23,30 +23,25 @@ tmux -2 new-session -d -s penpot tmux rename-window -t penpot:0 'frontend watch' tmux select-window -t penpot:0 tmux send-keys -t penpot 'cd penpot/frontend' enter C-l -tmux send-keys -t penpot 'yarn run watch' enter +tmux send-keys -t penpot './scripts/watch app' enter -tmux new-window -t penpot:1 -n 'frontend shadow' +tmux new-window -t penpot:1 -n 'frontend storybook' tmux select-window -t penpot:1 tmux send-keys -t penpot 'cd penpot/frontend' enter C-l -tmux send-keys -t penpot 'yarn run watch:app' enter +tmux send-keys -t penpot './scripts/watch storybook' enter -tmux new-window -t penpot:2 -n 'frontend storybook' +tmux new-window -t penpot:2 -n 'exporter' tmux select-window -t penpot:2 -tmux send-keys -t penpot 'cd penpot/frontend' enter C-l -tmux send-keys -t penpot 'yarn run watch:storybook' enter - -tmux new-window -t penpot:3 -n 'exporter' -tmux select-window -t penpot:3 tmux send-keys -t penpot 'cd penpot/exporter' enter C-l tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l -tmux send-keys -t penpot 'yarn run watch' enter +tmux send-keys -t penpot './scripts/watch' enter tmux split-window -v tmux send-keys -t penpot 'cd penpot/exporter' enter C-l tmux send-keys -t penpot './scripts/wait-and-start.sh' enter -tmux new-window -t penpot:4 -n 'backend' -tmux select-window -t penpot:4 +tmux new-window -t penpot:3 -n 'backend' +tmux select-window -t penpot:3 tmux send-keys -t penpot 'cd penpot/backend' enter C-l tmux send-keys -t penpot './scripts/start-dev' enter diff --git a/exporter/package.json b/exporter/package.json index f9ad501711..df314c4008 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -30,8 +30,8 @@ }, "scripts": { "clear:shadow-cache": "rm -rf .shadow-cljs && rm -rf target", - "watch:app": "clojure -M:dev:shadow-cljs watch main", - "watch": "yarn run clear:shadow-cache && yarn run watch:app", + "watch:app": "yarn run clear:shadow-cache && clojure -M:dev:shadow-cljs watch main", + "watch": "yarn run watch:app", "build:app": "clojure -M:dev:shadow-cljs release main", "build": "yarn run clear:shadow-cache && yarn run build:app", "fmt:clj:check": "cljfmt check --parallel=false src/", diff --git a/exporter/scripts/watch b/exporter/scripts/watch new file mode 100755 index 0000000000..212f756bb6 --- /dev/null +++ b/exporter/scripts/watch @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +TARGET=${1:-app}; + +set -ex + +exec yarn run watch:$TARGET diff --git a/frontend/package.json b/frontend/package.json index be4438adcb..8279b3fd3b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -47,10 +47,9 @@ "watch:app:libs": "node ./scripts/build-libs.js --watch", "watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook", "clear:shadow-cache": "rm -rf .shadow-cljs", - "watch:app": "yarn run clear:shadow-cache && concurrently \"yarn run watch:app:main\" \"yarn run watch:app:libs\"", - "watch": "yarn run watch:app:assets", - "watch:storybook": "yarn run build:storybook:assets && concurrently \"storybook dev -p 6006 --no-open\" \"yarn run watch:storybook:assets\"", - "watch:storybook:assets": "node ./scripts/watch-storybook.js" + "watch": "exit 0", + "watch:app": "yarn run clear:shadow-cache && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"", + "watch:storybook": "yarn run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\"" }, "devDependencies": { "@penpot/draft-js": "portal:./packages/draft-js", diff --git a/frontend/scripts/_helpers.js b/frontend/scripts/_helpers.js index 126f3d9f44..1065327d83 100644 --- a/frontend/scripts/_helpers.js +++ b/frontend/scripts/_helpers.js @@ -74,7 +74,7 @@ export function isJsFile(path) { export async function compileSass(worker, path, options) { path = ph.resolve(path); - log.info("compile:", path); + // log.info("compile:", path); return worker.exec("compileSass", [path, options]); } diff --git a/frontend/scripts/watch b/frontend/scripts/watch new file mode 100755 index 0000000000..212f756bb6 --- /dev/null +++ b/frontend/scripts/watch @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +TARGET=${1:-app}; + +set -ex + +exec yarn run watch:$TARGET diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 6e739ada49..33c77b3fdc 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -106,9 +106,11 @@ :overflowWrap "initial" :lineBreak "auto" :whiteSpace "break-spaces" - :textRendering "geometricPrecision" - :display "inline-block" - :verticalAlign "top"} + :textRendering "geometricPrecision"} + base (cond-> base + (= (:line-height data) "0") + (-> (obj/set! "display" "inline-block") + (obj/set! "verticalAlign" "top"))) fills (cond ;; DEPRECATED: still here for backward compatibility with diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index a667d3abc5..645cba9b15 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -16,6 +16,7 @@ [app.common.types.path :as path] [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] + [app.main.data.common :as dcm] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.variants :as dwv] [app.main.features :as features] @@ -305,9 +306,15 @@ (->> wasm.api/module (p/fmap (fn [ready?] (when ready? - (let [init? (wasm.api/init-canvas-context canvas)] + (let [init? (try + (wasm.api/init-canvas-context canvas) + (catch :default e + (js/console.error "Error initializing canvas context:" e) + false))] (reset! canvas-init? init?) - (when-not init? (js/alert "WebGL not supported"))))))) + (when-not init? + (js/alert "WebGL not supported") + (st/emit! (dcm/go-to-dashboard-recent)))))))) (fn [] (wasm.api/clear-canvas)))) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index f0c8a7a12b..f6bc99f71f 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -21,6 +21,7 @@ [app.common.types.text :as txt] [app.common.uuid :as uuid] [app.config :as cf] + [app.main.data.render-wasm :as drw] [app.main.refs :as refs] [app.main.render :as render] [app.main.store :as st] @@ -96,20 +97,20 @@ ;; This should never be called from the outside. (defn- render [timestamp] - (when wasm/context-initialized? + (when (and wasm/context-initialized? (not @wasm/context-lost?)) (h/call wasm/internal-module "_render" timestamp) (set! wasm/internal-frame-id nil) (ug/dispatch! (ug/event "penpot:wasm:render")))) (defn render-sync [] - (when wasm/context-initialized? + (when (and wasm/context-initialized? (not @wasm/context-lost?)) (h/call wasm/internal-module "_render_sync") (set! wasm/internal-frame-id nil))) (defn render-sync-shape [id] - (when wasm/context-initialized? + (when (and wasm/context-initialized? (not @wasm/context-lost?)) (let [buffer (uuid/get-u32 id)] (h/call wasm/internal-module "_render_sync_shape" (aget buffer 0) @@ -123,7 +124,7 @@ (defn request-render [_requester] - (when (not @pending-render) + (when (and wasm/context-initialized? (not @pending-render) (not @wasm/context-lost?)) (reset! pending-render true) (js/requestAnimationFrame (fn [ts] @@ -759,13 +760,8 @@ (h/call wasm/internal-module "_clear_shape_layout")) (defn- set-shape-layout - [shape objects] + [shape] (clear-layout) - (when (or (ctl/any-layout? shape) - (ctl/any-layout-immediate-child? objects shape) - (has-any-layout-prop? shape)) - (set-layout-data shape)) - (when (ctl/flex-layout? shape) (set-flex-layout shape)) @@ -916,7 +912,7 @@ (perf/end-measure "set-view-box::zoom"))))) (defn set-object - [objects shape] + [shape] (perf/begin-measure "set-object") (let [shape (svg-filters/apply-svg-derived shape) id (dm/get-prop shape :id) @@ -981,7 +977,7 @@ (when (= type :text) (set-shape-grow-type grow-type)) - (set-shape-layout shape objects) + (set-shape-layout shape) (set-shape-selrect selrect) (let [pending_thumbnails (into [] (concat @@ -1035,7 +1031,7 @@ (defn process-object [shape] - (let [{:keys [thumbnails full]} (set-object [] shape)] + (let [{:keys [thumbnails full]} (set-object shape)] (process-pending [shape] thumbnails full noop-fn))) (defn set-objects @@ -1050,7 +1046,7 @@ (loop [index 0 thumbnails-acc [] full-acc []] (if (< index total-shapes) (let [shape (nth shapes index) - {:keys [thumbnails full]} (set-object objects shape)] + {:keys [thumbnails full]} (set-object shape)] (recur (inc index) (into thumbnails-acc thumbnails) (into full-acc full))) @@ -1254,40 +1250,57 @@ ;; 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! wasm/context-initialized? true)) + (h/call wasm/internal-module "_set_render_options" flags dpr) - (h/call wasm/internal-module "_set_browser" browser) + ;; Set browser and canvas size only after initialization + (h/call wasm/internal-module "_set_browser" browser) + (set-canvas-size canvas) + + ;; Add event listeners for WebGL context lost + (let [handler (fn [event] + (.preventDefault event) + (reset! wasm/context-lost? true) + (log/warn :hint "WebGL context lost") + (st/emit! (drw/context-lost)))] + (set! wasm/context-lost-handler handler) + (set! wasm/context-lost-canvas canvas) + (.addEventListener canvas "webglcontextlost" handler)) + (set! wasm/context-initialized? true))) - (h/call wasm/internal-module "_set_render_options" flags dpr) - (set-canvas-size canvas) context-init?)) (defn clear-canvas [] - (try - ;; TODO: perform corresponding cleaning - (set! wasm/context-initialized? false) - (h/call wasm/internal-module "_clean_up") + (when wasm/context-initialized? + (try + ;; TODO: perform corresponding cleaning + (set! wasm/context-initialized? false) + (h/call wasm/internal-module "_clean_up") - ;; Ensure the WebGL context is properly disposed so browsers do not keep - ;; accumulating active contexts between page switches. - (when-let [gl (unchecked-get wasm/internal-module "GL")] - (when-let [handle wasm/gl-context-handle] - (try - ;; Ask the browser to release resources explicitly if available. - (when-let [ctx wasm/gl-context] - (when-let [lose-ext (.getExtension ^js ctx "WEBGL_lose_context")] - (.loseContext ^js lose-ext))) - (.deleteContext ^js gl handle) - (finally - (set! wasm/gl-context-handle nil) - (set! wasm/gl-context nil))))) + ;; Remove event listener for WebGL context lost + (when (and wasm/context-lost-handler wasm/context-lost-canvas) + (.removeEventListener wasm/context-lost-canvas "webglcontextlost" wasm/context-lost-handler) + (set! wasm/context-lost-canvas nil) + (set! wasm/context-lost-handler nil)) - ;; If this calls panics we don't want to crash. This happens sometimes - ;; with hot-reload in develop - (catch :default error - (.error js/console error)))) + ;; Ensure the WebGL context is properly disposed so browsers do not keep + ;; accumulating active contexts between page switches. + (when-let [gl (unchecked-get wasm/internal-module "GL")] + (when-let [handle wasm/gl-context-handle] + (try + ;; Ask the browser to release resources explicitly if available. + (when-let [ctx wasm/gl-context] + (when-let [lose-ext (.getExtension ^js ctx "WEBGL_lose_context")] + (.loseContext ^js lose-ext))) + (.deleteContext ^js gl handle) + (finally + (set! wasm/gl-context-handle nil) + (set! wasm/gl-context nil))))) + + ;; If this calls panics we don't want to crash. This happens sometimes + ;; with hot-reload in develop + (catch :default error + (.error js/console error))))) (defn show-grid [id] diff --git a/frontend/src/app/render_wasm/api/fonts.cljs b/frontend/src/app/render_wasm/api/fonts.cljs index 0e095d1cc8..0e939634f9 100644 --- a/frontend/src/app/render_wasm/api/fonts.cljs +++ b/frontend/src/app/render_wasm/api/fonts.cljs @@ -354,32 +354,32 @@ :is-fallback true})) (def noto-fonts - {:japanese {:font-id "gfont-noto-sans-jp" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :chinese {:font-id "gfont-noto-sans-sc" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :korean {:font-id "gfont-noto-sans-kr" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :arabic {:font-id "gfont-noto-sans-arabic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :cyrillic {:font-id "gfont-noto-sans-cyrillic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :greek {:font-id "gfont-noto-sans-greek" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :hebrew {:font-id "gfont-noto-sans-hebrew" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :thai {:font-id "gfont-noto-sans-thai" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :devanagari {:font-id "gfont-noto-sans-devanagari" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :tamil {:font-id "gfont-noto-sans-tamil" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :latin-ext {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :vietnamese {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :armenian {:font-id "gfont-noto-sans-armenian" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :bengali {:font-id "gfont-noto-sans-bengali" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :cherokee {:font-id "gfont-noto-sans-cherokee" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :ethiopic {:font-id "gfont-noto-sans-ethiopic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :georgian {:font-id "gfont-noto-sans-georgian" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :gujarati {:font-id "gfont-noto-sans-gujarati" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :gurmukhi {:font-id "gfont-noto-sans-gurmukhi" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :khmer {:font-id "gfont-noto-sans-khmer" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :lao {:font-id "gfont-noto-sans-lao" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :malayalam {:font-id "gfont-noto-sans-malayalam" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :myanmar {:font-id "gfont-noto-sans-myanmar" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :sinhala {:font-id "gfont-noto-sans-sinhala" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :telugu {:font-id "gfont-noto-sans-telugu" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :tibetan {:font-id "gfont-noto-sans-tibetan" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + {:japanese {:font-id "gfont-noto-sans-jp" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :chinese {:font-id "gfont-noto-sans-sc" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :korean {:font-id "gfont-noto-sans-kr" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :arabic {:font-id "gfont-noto-sans-arabic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :cyrillic {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :greek {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :hebrew {:font-id "gfont-noto-sans-hebrew" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :thai {:font-id "gfont-noto-sans-thai" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :devanagari {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :tamil {:font-id "gfont-noto-sans-tamil" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :latin-ext {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :vietnamese {:font-id "gfont-noto-sans" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :armenian {:font-id "gfont-noto-sans-armenian" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :bengali {:font-id "gfont-noto-sans-bengali" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :cherokee {:font-id "gfont-noto-sans-cherokee" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :ethiopic {:font-id "gfont-noto-sans-ethiopic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :georgian {:font-id "gfont-noto-sans-georgian" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :gujarati {:font-id "gfont-noto-sans-gujarati" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :gurmukhi {:font-id "gfont-noto-sans-gurmukhi" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :khmer {:font-id "gfont-noto-sans-khmer" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :lao {:font-id "gfont-noto-sans-lao" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :malayalam {:font-id "gfont-noto-sans-malayalam" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :myanmar {:font-id "gfont-noto-sans-myanmar" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :sinhala {:font-id "gfont-noto-sans-sinhala" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :telugu {:font-id "gfont-noto-sans-telugu" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :tibetan {:font-id "gfont-noto-serif-tibetan" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :javanese {:font-id "gfont-noto-sans-javanese" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :kannada {:font-id "gfont-noto-sans-kannada" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :oriya {:font-id "gfont-noto-sans-oriya" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} @@ -399,8 +399,8 @@ :bamum {:font-id "gfont-noto-sans-bamum" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :meroitic {:font-id "gfont-noto-sans-meroitic" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} :symbols {:font-id "gfont-noto-sans-symbols" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :symbols-2 {:font-id "gfont-noto-sans-symbols-2" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} - :music {:font-id "gfont-noto-music" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}}) + :symbols-2 {:font-id "gfont-noto-sans-symbols-2" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true} + :music {:font-id "gfont-noto-music" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}}) (defn add-noto-fonts [fonts languages] (reduce (fn [acc lang] diff --git a/frontend/src/app/render_wasm/shape.cljs b/frontend/src/app/render_wasm/shape.cljs index 0ae88e07c1..6475fb9e2f 100644 --- a/frontend/src/app/render_wasm/shape.cljs +++ b/frontend/src/app/render_wasm/shape.cljs @@ -280,8 +280,18 @@ :layout-grid-cells (api/set-grid-layout-cells v) - (:layout - :layout-flex-dir + :layout + (do + (api/clear-layout) + (cond + (ctl/grid-layout? shape) + (api/set-grid-layout shape) + + (ctl/flex-layout? shape) + (api/set-flex-layout shape)) + (api/set-layout-data shape)) + + (:layout-flex-dir :layout-gap-type :layout-gap :layout-align-items @@ -291,15 +301,12 @@ :layout-wrap-type :layout-padding-type :layout-padding) - (do - (api/clear-layout) - (cond - (ctl/grid-layout? shape) - (api/set-grid-layout-data shape) + (cond + (ctl/grid-layout? shape) + (api/set-grid-layout-data shape) - (ctl/flex-layout? shape) - (api/set-flex-layout shape)) - (api/set-layout-data shape)) + (ctl/flex-layout? shape) + (api/set-flex-layout shape)) ;; Property not in WASM nil)))) diff --git a/frontend/src/app/render_wasm/wasm.cljs b/frontend/src/app/render_wasm/wasm.cljs index 5fec8156e3..ef80362956 100644 --- a/frontend/src/app/render_wasm/wasm.cljs +++ b/frontend/src/app/render_wasm/wasm.cljs @@ -46,3 +46,6 @@ :fill-rule shared/RawFillRule}) (defonce context-initialized? false) +(defonce context-lost? (atom false)) +(defonce context-lost-handler nil) +(defonce context-lost-canvas nil) \ No newline at end of file diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index c519e27b6a..9d4ff41617 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -275,6 +275,14 @@ pub extern "C" fn set_view_end() { } performance::end_measure!("set_view_end::rebuild_tiles"); performance::end_timed_log!("rebuild_tiles", _rebuild_start); + } else { + // During pan, we only clear the tile index without + // invalidating cached textures, which is more efficient. + let _clear_start = performance::begin_timed_log!("clear_tile_index"); + performance::begin_measure!("set_view_end::clear_tile_index"); + state.clear_tile_index(); + performance::end_measure!("set_view_end::clear_tile_index"); + performance::end_timed_log!("clear_tile_index", _clear_start); } performance::end_measure!("set_view_end"); performance::end_timed_log!("set_view_end", _end_start); diff --git a/render-wasm/src/render.rs b/render-wasm/src/render.rs index 2af83dc286..a0763e6d40 100644 --- a/render-wasm/src/render.rs +++ b/render-wasm/src/render.rs @@ -999,12 +999,13 @@ impl RenderState { let viewbox_cache_size = get_cache_size(self.viewbox, scale); let cached_viewbox_cache_size = get_cache_size(self.cached_viewbox, scale); - if viewbox_cache_size != cached_viewbox_cache_size { - self.surfaces.resize_cache( - &mut self.gpu_state, - viewbox_cache_size, - VIEWPORT_INTEREST_AREA_THRESHOLD, - ); + // Only resize cache if the new size is larger than the cached size + // This avoids unnecessary surface recreations when the cache size decreases + if viewbox_cache_size.width > cached_viewbox_cache_size.width + || viewbox_cache_size.height > cached_viewbox_cache_size.height + { + self.surfaces + .resize_cache(viewbox_cache_size, VIEWPORT_INTEREST_AREA_THRESHOLD); } // FIXME - review debug @@ -1961,6 +1962,17 @@ impl RenderState { performance::end_measure!("rebuild_tiles_shallow"); } + /// Clears the tile index without invalidating cached tile textures. + /// This is useful when tile positions don't change (e.g., during pan operations) + /// but the tile index needs to be synchronized. The cached tile textures remain + /// valid since they don't depend on the current view position, only on zoom level. + /// This is much more efficient than clearing the entire cache surface. + pub fn clear_tile_index(&mut self) { + performance::begin_measure!("clear_tile_index"); + self.surfaces.clear_tiles(); + performance::end_measure!("clear_tile_index"); + } + pub fn rebuild_tiles_from(&mut self, tree: ShapesPoolRef, base_id: Option<&Uuid>) { performance::begin_measure!("rebuild_tiles"); diff --git a/render-wasm/src/render/surfaces.rs b/render-wasm/src/render/surfaces.rs index 94c19915fb..fe0edbb455 100644 --- a/render-wasm/src/render/surfaces.rs +++ b/render-wasm/src/render/surfaces.rs @@ -108,6 +108,10 @@ impl Surfaces { } } + pub fn clear_tiles(&mut self) { + self.tiles.clear(); + } + pub fn resize(&mut self, gpu_state: &mut GpuState, new_width: i32, new_height: i32) { self.reset_from_target(gpu_state.create_target_surface(new_width, new_height)); } @@ -248,13 +252,8 @@ impl Surfaces { // The rest are tile size surfaces } - pub fn resize_cache( - &mut self, - gpu_state: &mut GpuState, - cache_dims: skia::ISize, - interest_area_threshold: i32, - ) { - self.cache = gpu_state.create_surface_with_isize("cache".to_string(), cache_dims); + pub fn resize_cache(&mut self, cache_dims: skia::ISize, interest_area_threshold: i32) { + self.cache = self.target.new_surface_with_dimensions(cache_dims).unwrap(); self.cache.canvas().reset_matrix(); self.cache.canvas().translate(( (interest_area_threshold as f32 * TILE_SIZE), diff --git a/render-wasm/src/state.rs b/render-wasm/src/state.rs index 5e48cea05e..40b272007c 100644 --- a/render-wasm/src/state.rs +++ b/render-wasm/src/state.rs @@ -197,6 +197,10 @@ impl<'a> State<'a> { self.render_state.rebuild_tiles_shallow(&self.shapes); } + pub fn clear_tile_index(&mut self) { + self.render_state.clear_tile_index(); + } + pub fn rebuild_tiles(&mut self) { self.render_state.rebuild_tiles_from(&self.shapes, None); }