From d635f5a8dce9fefdd3ccc506a7bf05d264cb446e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 29 Dec 2025 14:16:09 +0100 Subject: [PATCH] :bug: Detecting situations where WebGL context is lost or no WebGL support --- .../app/main/ui/workspace/viewport_wasm.cljs | 11 ++- frontend/src/app/render_wasm/api.cljs | 78 ++++++++++++------- frontend/src/app/render_wasm/wasm.cljs | 3 + 3 files changed, 60 insertions(+), 32 deletions(-) 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 50a33b7d14..a6916d873e 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] @@ -95,20 +96,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) @@ -122,7 +123,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] @@ -1248,40 +1249,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/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