mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 22:10:34 +00:00
* ♻️ Factor GL context initialization out of canvas init - fixes setting of gl-context-handle to be after _init so an exception does not leave invalid state behind. - api.init-canvas-context uses a new webgl.init-context! - this decouples config from init, so we can reuse it in benchmarks - some helpers move to the webgl ns AI-assisted-by: muse-spark-1.3, deepseek-flash, glm-5.3 * 🐛 Release acquired GL context when renderer init fails - Properly release resources if init fails - Rethrown exceptions with the original cause - Fix vacuous check in clear-canvas AI-assisted-by: muse-spark-1.3, deepseek-flash, glm-5.3 * ✨ Test webgl context init and release AI-assisted-by: muse-spark, glm-5.3 * 💄 Address review comments * 💄 Drop `!` from some function names
106 lines
4.5 KiB
Clojure
106 lines
4.5 KiB
Clojure
;; 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 SUBSIDIARY SL
|
|
|
|
(ns frontend-tests.render-wasm.webgl-test
|
|
"Unit tests for `app.render-wasm.api.webgl` context init and release."
|
|
(:require
|
|
[app.render-wasm.api.webgl :as webgl]
|
|
[cljs.test :as t :include-macros true]))
|
|
|
|
(defn- fake-env
|
|
"Builds `{canvas context module calls}` fakes.
|
|
- `calls` records `:register`, `:delete`, `:clean` and `:lose` events.
|
|
- Options:
|
|
- `:throw-in` makes that module fn throw (`:_init`, `:_set_browser`, ...),
|
|
- `:register-throws?` fails Emscripten registration (no handle exists),
|
|
- `:nil-context?` makes `getContext` return nil (nothing is acquired)."
|
|
[{:keys [throw-in register-throws? nil-context?]}]
|
|
(let [calls (atom [])
|
|
lose (fn [] (swap! calls conj [:lose]) nil)
|
|
context #js {:getExtension (fn [name]
|
|
(when (= name "WEBGL_lose_context")
|
|
#js {:loseContext lose}))}
|
|
gl #js {:registerContext (fn [_ _]
|
|
(swap! calls conj [:register])
|
|
(when register-throws?
|
|
(throw (js/Error. "register failed")))
|
|
7)
|
|
:makeContextCurrent (fn [_] nil)
|
|
:deleteContext (fn [handle]
|
|
(swap! calls conj [:delete handle])
|
|
nil)}
|
|
thrower (fn [name]
|
|
(fn [& _]
|
|
(when (= name throw-in)
|
|
(throw (js/Error. (str "fake " (pr-str name) " failure"))))
|
|
nil))
|
|
module #js {:GL gl
|
|
:_init (thrower :_init)
|
|
:_set_render_options (thrower :_set_render_options)
|
|
:_set_browser (thrower :_set_browser)
|
|
:_clean_up (fn [& _] (swap! calls conj [:clean]) nil)}
|
|
canvas #js {:getContext (fn [_ _] (when-not nil-context? context))}]
|
|
{:calls calls :canvas canvas :context context :module module}))
|
|
|
|
(defn- init-opts
|
|
[module]
|
|
{:module module
|
|
:context-id "webgl2"
|
|
:css-width 100
|
|
:css-height 100
|
|
:dpr 1
|
|
:flags 0
|
|
:browser 0
|
|
:params {}})
|
|
|
|
(defn- init-failure
|
|
"Runs `init-context` against `env`, returning the thrown cause."
|
|
[{:keys [canvas module]}]
|
|
(try
|
|
(webgl/init-context canvas (init-opts module))
|
|
nil
|
|
(catch :default cause
|
|
cause)))
|
|
|
|
(defn- effect-count
|
|
[calls effect]
|
|
(count (filter #(= effect (first %)) calls)))
|
|
|
|
(t/deftest init-failure-releases-the-acquired-context
|
|
(let [{:keys [canvas module calls]} (fake-env {:throw-in :_set_browser})
|
|
failure (init-failure {:canvas canvas :module module})]
|
|
(t/is (some? failure) "the original failure propagates")
|
|
(t/is (= "_set_browser" (:fn (ex-data failure)))
|
|
"the cause keeps the failing fn")
|
|
(t/is (= 1 (effect-count @calls :clean)) "renderer state is released")
|
|
(t/is (some #{[:delete 7]} @calls) "the registered handle is deleted")
|
|
(t/is (some #{[:lose]} @calls) "the browser context is released")))
|
|
|
|
(t/deftest init-success-returns-context-and-handle
|
|
(let [{:keys [canvas context module calls]} (fake-env {})
|
|
result (webgl/init-context canvas (init-opts module))]
|
|
(t/is (= 7 (:handle result)))
|
|
(t/is (identical? context (:context result)))
|
|
(t/is (zero? (effect-count @calls :clean)) "no release on success")
|
|
(t/is (zero? (effect-count @calls :delete)) "no release on success")
|
|
(t/is (zero? (effect-count @calls :lose)) "no release on success")))
|
|
|
|
(t/deftest nil-context-returns-nil-without-releasing
|
|
(let [{:keys [canvas module calls]} (fake-env {:nil-context? true})]
|
|
(t/is (nil? (webgl/init-context canvas (init-opts module))))
|
|
(t/is (empty? @calls) "nothing acquired, nothing released")))
|
|
|
|
(t/deftest registration-failure-releases-browser-context-only
|
|
(let [{:keys [canvas module calls]} (fake-env {:register-throws? true})
|
|
failure (init-failure {:canvas canvas :module module})]
|
|
(t/is (some? failure) "the original failure propagates")
|
|
(t/is (zero? (effect-count @calls :clean))
|
|
"no renderer state exists without registration")
|
|
(t/is (zero? (effect-count @calls :delete))
|
|
"no handle exists to delete")
|
|
(t/is (= 1 (effect-count @calls :lose))
|
|
"the browser context is still released")))
|