mirror of
https://github.com/penpot/penpot.git
synced 2026-11-06 18:10:39 +00:00
* ⚡ Capture canvas snapshot only when the renderer is idle * ⚡ Skip descendant silhouettes for frames with nested drop shadows * 🔧 Add a profiling build mode for render-wasm
183 lines
8.4 KiB
Clojure
183 lines
8.4 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 app.render-wasm.api.webgl
|
|
"WebGL utilities for pixel capture and rendering"
|
|
(:require
|
|
[app.common.logging :as log]
|
|
[app.common.render-wasm.wasm :as wasm]
|
|
[promesa.core :as p]))
|
|
|
|
(defn get-webgl-context
|
|
"Gets the WebGL context from the WASM module"
|
|
[]
|
|
(when (wasm/live?)
|
|
(let [gl-obj (unchecked-get wasm/internal-module "GL")]
|
|
(when gl-obj
|
|
;; Get the current WebGL context from Emscripten
|
|
;; The GL object has a currentContext property that contains the context handle
|
|
(let [current-ctx (.-currentContext ^js gl-obj)]
|
|
(when current-ctx
|
|
(.-GLctx ^js current-ctx)))))))
|
|
|
|
(defn create-webgl-texture-from-image
|
|
"Creates a WebGL texture from an HTMLImageElement or ImageBitmap and returns the texture object"
|
|
[gl image-element]
|
|
(let [texture (.createTexture ^js gl)]
|
|
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
|
|
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_S ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
|
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_T ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
|
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MIN_FILTER ^js gl) (.-LINEAR ^js gl))
|
|
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MAG_FILTER ^js gl) (.-LINEAR ^js gl))
|
|
(.texImage2D ^js gl (.-TEXTURE_2D ^js gl) 0 (.-RGBA ^js gl) (.-RGBA ^js gl) (.-UNSIGNED_BYTE ^js gl) image-element)
|
|
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil)
|
|
texture))
|
|
|
|
;; FIXME: temporary function until we are able to keep the same <canvas> across pages.
|
|
(defn- draw-imagedata-to-webgl
|
|
"Draws ImageData to a WebGL2 context by creating a texture"
|
|
[gl image-data]
|
|
(let [width (.-width ^js image-data)
|
|
height (.-height ^js image-data)
|
|
texture (.createTexture ^js gl)]
|
|
;; Bind texture and set parameters
|
|
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
|
|
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_S ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
|
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_T ^js gl) (.-CLAMP_TO_EDGE ^js gl))
|
|
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MIN_FILTER ^js gl) (.-LINEAR ^js gl))
|
|
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MAG_FILTER ^js gl) (.-LINEAR ^js gl))
|
|
(.texImage2D ^js gl (.-TEXTURE_2D ^js gl) 0 (.-RGBA ^js gl) (.-RGBA ^js gl) (.-UNSIGNED_BYTE ^js gl) image-data)
|
|
|
|
;; Set up viewport
|
|
(.viewport ^js gl 0 0 width height)
|
|
|
|
;; Vertex & Fragment shaders
|
|
;; Since we are only calling this function once (on page switch), we don't need
|
|
;; to cache the compiled shaders somewhere else (cannot be reused in a differen context).
|
|
(let [vertex-shader-source "#version 300 es
|
|
in vec2 a_position;
|
|
in vec2 a_texCoord;
|
|
out vec2 v_texCoord;
|
|
void main() {
|
|
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
v_texCoord = a_texCoord;
|
|
}"
|
|
fragment-shader-source "#version 300 es
|
|
precision highp float;
|
|
in vec2 v_texCoord;
|
|
uniform sampler2D u_texture;
|
|
out vec4 fragColor;
|
|
void main() {
|
|
fragColor = texture(u_texture, v_texCoord);
|
|
}"
|
|
vertex-shader (.createShader ^js gl (.-VERTEX_SHADER ^js gl))
|
|
fragment-shader (.createShader ^js gl (.-FRAGMENT_SHADER ^js gl))
|
|
program (.createProgram ^js gl)]
|
|
(.shaderSource ^js gl vertex-shader vertex-shader-source)
|
|
(.compileShader ^js gl vertex-shader)
|
|
(when-not (.getShaderParameter ^js gl vertex-shader (.-COMPILE_STATUS ^js gl))
|
|
(log/error :hint "Vertex shader compilation failed"
|
|
:log (.getShaderInfoLog ^js gl vertex-shader)))
|
|
|
|
(.shaderSource ^js gl fragment-shader fragment-shader-source)
|
|
(.compileShader ^js gl fragment-shader)
|
|
(when-not (.getShaderParameter ^js gl fragment-shader (.-COMPILE_STATUS ^js gl))
|
|
(log/error :hint "Fragment shader compilation failed"
|
|
:log (.getShaderInfoLog ^js gl fragment-shader)))
|
|
|
|
(.attachShader ^js gl program vertex-shader)
|
|
(.attachShader ^js gl program fragment-shader)
|
|
(.linkProgram ^js gl program)
|
|
|
|
(if (.getProgramParameter ^js gl program (.-LINK_STATUS ^js gl))
|
|
(do
|
|
(.useProgram ^js gl program)
|
|
|
|
;; Create full-screen quad vertices (normalized device coordinates)
|
|
(let [position-location (.getAttribLocation ^js gl program "a_position")
|
|
texcoord-location (.getAttribLocation ^js gl program "a_texCoord")
|
|
position-buffer (.createBuffer ^js gl)
|
|
texcoord-buffer (.createBuffer ^js gl)
|
|
positions #js [-1.0 -1.0 1.0 -1.0 -1.0 1.0 -1.0 1.0 1.0 -1.0 1.0 1.0]
|
|
texcoords #js [0.0 0.0 1.0 0.0 0.0 1.0 0.0 1.0 1.0 0.0 1.0 1.0]]
|
|
;; Set up position buffer
|
|
(.bindBuffer ^js gl (.-ARRAY_BUFFER ^js gl) position-buffer)
|
|
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. positions) (.-STATIC_DRAW ^js gl))
|
|
(.enableVertexAttribArray ^js gl position-location)
|
|
(.vertexAttribPointer ^js gl position-location 2 (.-FLOAT ^js gl) false 0 0)
|
|
;; Set up texcoord buffer
|
|
(.bindBuffer ^js gl (.-ARRAY_BUFFER ^js gl) texcoord-buffer)
|
|
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. texcoords) (.-STATIC_DRAW ^js gl))
|
|
(.enableVertexAttribArray ^js gl texcoord-location)
|
|
(.vertexAttribPointer ^js gl texcoord-location 2 (.-FLOAT ^js gl) false 0 0)
|
|
;; Set texture uniform
|
|
(.activeTexture ^js gl (.-TEXTURE0 ^js gl))
|
|
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
|
|
(let [texture-location (.getUniformLocation ^js gl program "u_texture")]
|
|
(.uniform1i ^js gl texture-location 0))
|
|
|
|
;; draw
|
|
(.drawArrays ^js gl (.-TRIANGLES ^js gl) 0 6)
|
|
|
|
;; cleanup
|
|
(.deleteBuffer ^js gl position-buffer)
|
|
(.deleteBuffer ^js gl texcoord-buffer)
|
|
(.deleteShader ^js gl vertex-shader)
|
|
(.deleteShader ^js gl fragment-shader)
|
|
(.deleteProgram ^js gl program)))
|
|
(log/error :hint "Program linking failed"
|
|
:log (.getProgramInfoLog ^js gl program)))
|
|
|
|
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil)
|
|
(.deleteTexture ^js gl texture))))
|
|
|
|
(defn capture-canvas-snapshot
|
|
"Captures the current viewport canvas as an `ImageBitmap` and stores it in
|
|
`wasm/canvas-snapshot`. It avoids `canvas.toBlob`'s PNG encoding, but the
|
|
readback itself is not free: on Firefox, `createImageBitmap` on a WebGL canvas
|
|
takes a synchronous back-buffer snapshot over IPC before the promise is even
|
|
created, which blocks the main thread until the queued GL commands drain.
|
|
Callers must only invoke it when the renderer is idle.
|
|
|
|
Returns a promise resolving to the ImageBitmap (or nil)."
|
|
[]
|
|
(if-let [^js canvas wasm/canvas]
|
|
(-> (js/createImageBitmap canvas)
|
|
(p/then (fn [^js bitmap]
|
|
(set! wasm/canvas-snapshot bitmap)
|
|
bitmap))
|
|
(p/catch (fn [_] nil)))
|
|
(p/resolved nil)))
|
|
|
|
(defn draw-thumbnail-to-canvas
|
|
"Loads an image from `uri` and draws it stretched to fill the WebGL canvas.
|
|
Returns a promise that resolves to true if drawn, false otherwise."
|
|
[uri]
|
|
(if (and uri wasm/canvas wasm/gl-context)
|
|
(p/create
|
|
(fn [resolve _reject]
|
|
(let [img (js/Image.)]
|
|
(set! (.-crossOrigin img) "anonymous")
|
|
(set! (.-onload img)
|
|
(fn []
|
|
(if wasm/gl-context
|
|
(let [gl wasm/gl-context
|
|
width (.-width wasm/canvas)
|
|
height (.-height wasm/canvas)
|
|
;; Draw image to an offscreen 2D canvas, scaled to fill
|
|
canvas2d (js/OffscreenCanvas. width height)
|
|
ctx2d (.getContext canvas2d "2d")]
|
|
(.drawImage ctx2d img 0 0 width height)
|
|
(let [image-data (.getImageData ctx2d 0 0 width height)]
|
|
(draw-imagedata-to-webgl gl image-data))
|
|
(resolve true))
|
|
(resolve false))))
|
|
(set! (.-onerror img)
|
|
(fn [_]
|
|
(resolve false)))
|
|
(set! (.-src img) uri))))
|
|
(p/resolved false)))
|