Compare commits

...

11 Commits

Author SHA1 Message Date
Aitor
1299074bd9 wip 2024-01-02 09:11:50 +01:00
Aitor
6b50c17781 wip 2023-12-20 13:29:29 +01:00
Aitor
e08e2bf64a wip 2023-12-19 14:35:57 +01:00
Alejandro Alonso
77f07f9751 Basic multiple fill support 2023-12-18 13:37:56 +01:00
Aitor
189e6b31d0 wip 2023-12-18 13:18:32 +01:00
Alejandro Alonso
4690945f59 WIP 2023-12-18 12:17:15 +01:00
Aitor
f0d0529f58 wip 2023-12-18 12:02:46 +01:00
Aitor
8525508c11 wip 2023-12-18 11:11:46 +01:00
Alejandro Alonso
eaa9aec8bb Read default shader from file 2023-12-18 11:01:50 +01:00
Aitor
23adf483ff wip 2023-12-18 10:38:26 +01:00
Aitor
bcf01abdbe wip 2023-12-18 10:08:42 +01:00
9 changed files with 556 additions and 41 deletions

View File

@@ -79,6 +79,7 @@
"date-fns": "^2.30.0",
"draft-js": "^0.11.7",
"eventsource-parser": "^1.1.1",
"gl-matrix": "^3.4.3",
"highlight.js": "^11.8.0",
"js-beautify": "^1.14.9",
"jszip": "^3.10.1",

View File

@@ -28,6 +28,7 @@
[app.main.ui.workspace.viewport.debug :as wvd]
[app.main.ui.workspace.viewport.drawarea :as drawarea]
[app.main.ui.workspace.viewport.frame-grid :as frame-grid]
[app.main.ui.workspace.viewport.gl :as gl]
[app.main.ui.workspace.viewport.gradients :as gradients]
[app.main.ui.workspace.viewport.grid-layout-editor :as grid-layout]
[app.main.ui.workspace.viewport.guides :as guides]
@@ -92,6 +93,7 @@
;; CONTEXT
page-id (mf/use-ctx ctx/current-page-id)
page (mf/deref refs/workspace-page)
;; DEREFS
drawing (mf/deref refs/workspace-drawing)
@@ -131,6 +133,8 @@
[viewport-ref
on-viewport-ref] (create-viewport-ref)
canvas-ref (mf/use-ref nil)
;; VARS
disable-paste (mf/use-var false)
in-viewport? (mf/use-var false)
@@ -258,7 +262,15 @@
(:y (first selected-shapes))
(:y selected-frame))
rule-area-size (/ rules/rule-area-size zoom)]
rule-area-size (/ rules/rule-area-size zoom)
;; Aquí podemos configurar como queremos que sea el renderizado:
;;
;; - "gl" Utilizando sólo WebGL2
;; - "svg" Utilizando sólo SVG
;; - "both" Utilizando ambos
;;
renderer "both"]
(hooks/setup-dom-events zoom disable-paste in-viewport? workspace-read-only?)
(hooks/setup-viewport-size vport viewport-ref)
@@ -302,49 +314,61 @@
[:& top-bar/top-bar]]
[:svg.render-shapes
{:id "render"
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns:penpot "https://penpot.app/xmlns"
:preserveAspectRatio "xMidYMid meet"
:key (str "render" page-id)
:width (:width vport 0)
:height (:height vport 0)
:view-box (utils/format-viewbox vbox)
:style {:background-color background
:pointer-events "none"}
:fill "none"}
(when (or (= renderer "svg") (= renderer "both"))
[:svg.render-shapes
{:id "render"
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns:penpot "https://penpot.app/xmlns"
:preserveAspectRatio "xMidYMid meet"
:key (str "render" page-id)
:width (:width vport 0)
:height (:height vport 0)
:view-box (utils/format-viewbox vbox)
:style {:background-color background
:pointer-events "none"}
:fill "none"}
[:defs
[:linearGradient {:id "frame-placeholder-gradient"}
[:animateTransform
{:attributeName "gradientTransform"
:type "translate"
:from "-1 0"
:to "1 0"
:dur "2s"
:repeatCount "indefinite"}]
[:stop {:offset "0%" :stop-color (str "color-mix(in srgb-linear, " background " 90%, #777)") :stop-opacity 1}]
[:stop {:offset "50%" :stop-color (str "color-mix(in srgb-linear, " background " 80%, #777)") :stop-opacity 1}]
[:stop {:offset "100%" :stop-color (str "color-mix(in srgb-linear, " background " 90%, #777)") :stop-opacity 1}]]]
[:defs
[:linearGradient {:id "frame-placeholder-gradient"}
[:animateTransform
{:attributeName "gradientTransform"
:type "translate"
:from "-1 0"
:to "1 0"
:dur "2s"
:repeatCount "indefinite"}]
[:stop {:offset "0%" :stop-color (str "color-mix(in srgb-linear, " background " 90%, #777)") :stop-opacity 1}]
[:stop {:offset "50%" :stop-color (str "color-mix(in srgb-linear, " background " 80%, #777)") :stop-opacity 1}]
[:stop {:offset "100%" :stop-color (str "color-mix(in srgb-linear, " background " 90%, #777)") :stop-opacity 1}]]]
(when (dbg/enabled? :show-export-metadata)
[:& use/export-page {:options options}])
(when (dbg/enabled? :show-export-metadata)
[:& use/export-page {:options options}])
;; We need a "real" background shape so layer transforms work properly in firefox
[:rect {:width (:width vbox 0)
:height (:height vbox 0)
:x (:x vbox 0)
:y (:y vbox 0)
:fill background}]
;; We need a "real" background shape so layer
;; transforms work properly in firefox
[:rect {:width (:width vbox 0)
:height (:height vbox 0)
:x (:x vbox 0)
:y (:y vbox 0)
:fill background}]
[:& (mf/provider ctx/current-vbox) {:value vbox'}
[:& (mf/provider use/include-metadata-ctx) {:value (dbg/enabled? :show-export-metadata)}
;; Render root shape
[:& shapes/root-shape {:key page-id
:objects base-objects
:active-frames @active-frames}]]]]
[:& (mf/provider ctx/current-vbox) {:value vbox'}
[:& (mf/provider use/include-metadata-ctx) {:value (dbg/enabled? :show-export-metadata)}
;; Render root shape
[:& shapes/root-shape {:key page-id
:objects base-objects
:active-frames @active-frames}]]]])
;;-----------------------------------
;;
;; IT's MAGIC!
;;
;;-----------------------------------
(when (or (= renderer "gl") (= renderer "both"))
[gl/canvas {:objects base-objects
:active-frames @active-frames
:vbox vbox}])
[:svg.viewport-controls
{:xmlns "http://www.w3.org/2000/svg"
@@ -629,4 +653,14 @@
:objects base-objects
:modifiers modifiers
:shape frame
:view-only true}]))]]]]))
:view-only true}]))]]]
(when (= (:name page) "DOOM")
[:iframe {:src "/wasm/doom/index.html"
:width 1280
:height 720
:style {:position "absolute"
:top 0
:left 0
:z-index 10000
:pointer-events "all"}}])]))

View File

@@ -0,0 +1,207 @@
(ns app.main.ui.workspace.viewport.gl
(:require-macros [app.main.style :as stl])
(:require-macros [app.util.gl.macros :refer [slurp]])
(:require
["gl-matrix" :as glm]
[app.common.data.macros :as dm]
[app.common.math :as math]
[app.util.gl :as gl]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(def CANVAS_CONTEXT_ID "webgl2")
(def default-vertex-shader (slurp "src/app/util/gl/shaders/default.v.glsl"))
(def default-fragment-shader (slurp "src/app/util/gl/shaders/default.f.glsl"))
#_(def shaders (js/Map.))
(def programs (js/Map.))
(def program-actives (js/Map.))
#_(def textures (js/Map.))
#_(def framebuffers (js/Map.))
(defn parse-color-hex
"Parses a color string and returns a vector with the RGBA values."
[color opacity]
(let [r (str/slice color 1 3)
g (str/slice color 3 5)
b (str/slice color 5 7)]
#js [(/ (js/parseInt r 16) 255.0)
(/ (js/parseInt g 16) 255.0)
(/ (js/parseInt b 16) 255.0)
opacity]))
(defn parse-color
"Parses a color string and returns a vector with the RGBA values."
[color opacity]
(cond
(str/starts-with? color "#")
(parse-color-hex color opacity)
:else
#js [0.0 0.0 0.0 1.0]))
(defn get-object-type-as-int
"Returns the object type as an integer."
[object]
(case (:type object)
:rect 0
:circle 1
:group 2
:path 3
:text 4
:image 5
:svg-raw 6
:bool 7
:frame 8))
(defn get-object-border-radius-as-vec4
"Returns a vec4 from the object border radius."
[object]
(let [rx (:rx object)
ry (:ry object)
r1 (:r1 object)
r2 (:r2 object)
r3 (:r3 object)
r4 (:r4 object)]
(cond
(or rx ry)
#js [(:rx object) (:ry object) (:rx object) (:ry object)]
(or r1 r2 r3 r4)
#js [(:r1 object) (:r2 object) (:r3 object) (:r4 object)]
:else
#js [0.0 0.0 0.0 0.0])))
(defn resize-canvas-to
"Resize canvas to specific coordinates."
[canvas width height]
(let [resized-width (not= (.-width canvas) width)
resized-height (not= (.-height canvas) height)
resized (or resized-width resized-height)]
(when resized-width
(set! (.-width canvas) width))
(when resized-height
(set! (.-height canvas) height))
resized))
(defn resize-canvas
"Resizes the canvas intrinsic size to the element size."
[canvas]
(let [width (math/floor (.-clientWidth canvas))
height (math/floor (.-clientHeight canvas))]
(resize-canvas-to canvas width height)))
(defn prepare-gl
"Prepares the WebGL context for rendering."
[gl]
(let [default-program (gl/create-program-from-sources gl default-vertex-shader default-fragment-shader)
default-program-actives (gl/get-program-actives gl default-program)]
(js/console.log default-program-actives)
(.set programs "default" default-program)
(.set program-actives "default" default-program-actives)))
(defn render-gl
"Renders the whole document to the canvas."
[gl objects vbox]
(let [projection (.create glm/mat3)
projection (.projection glm/mat3 projection (:width vbox) (:height vbox))
program (.get programs "default")
actives (.get program-actives "default")]
(.clearColor gl 1.0 0.0 1.0 0.5)
(.clear gl (.-COLOR_BUFFER_BIT gl))
(.viewport gl 0 0 (.-width (.-canvas gl)) (.-height (.-canvas gl)))
;; Enable alpha blending
(.enable gl (.-BLEND gl))
(.blendFunc gl (.-SRC_ALPHA gl) (.-ONE_MINUS_SRC_ALPHA gl))
(.useProgram gl program)
(println "---------------> vbox" (:x vbox) (:width vbox) (:y vbox) (:height vbox))
(.uniformMatrix3fv gl (gl/get-program-uniform-location actives "u_projection") false projection)
(.uniform4f gl (gl/get-program-uniform-location actives "u_vbox") (:x vbox) (:y vbox) (:width vbox) (:height vbox))
(doseq [[_ object] objects]
(let [selrect (:selrect object)
x (:x selrect)
y (:y selrect)
width (:width selrect)
height (:height selrect)
rotation (:rotation object)
border (get-object-border-radius-as-vec4 object)
type (get-object-type-as-int object)]
(js/console.log border)
(.uniform4fv gl (gl/get-program-uniform-location actives "u_border") border)
(.uniform1i gl (gl/get-program-uniform-location actives "u_type") type)
(.uniform2f gl (gl/get-program-uniform-location actives "u_size") width height)
(.uniform2f gl (gl/get-program-uniform-location actives "u_position") x y)
(.uniform1f gl (gl/get-program-uniform-location actives "u_rotation") (/ (* rotation js/Math.PI) 180.0))
#_(.uniformMatrix3fv gl (gl/get-program-uniform-location actives "u_transform") false matrix)
;; NOTA: Esto es sólo aplicable en objetos que poseen fills (los textos no
;; poseen fills).
(doseq [fill (reverse (:fills object))]
(do
(.uniform4fv gl (gl/get-program-uniform-location actives "u_color") (parse-color (:fill-color fill) (:fill-opacity fill)))
(.drawArrays gl (.-TRIANGLE_STRIP gl) 0 4)))
(doseq [stroke (reverse (:strokes object))]
(do
(.uniform4fv gl (gl/get-program-uniform-location actives "u_color") (parse-color (:stroke-color stroke) (:stroke-opacity stroke)))
(.drawArrays gl (.-TRIANGLE_STRIP gl) 0 4)))))))
(mf/defc canvas
"A canvas element with a WebGL context."
{::mf/wrap-props false}
[props]
(let [objects (unchecked-get props "objects")
vbox (unchecked-get props "vbox")
canvas-ref (mf/use-ref nil)
gl-ref (mf/use-ref nil)
on-context-lost
(mf/use-fn (fn []
(mf/set-ref-val! gl-ref nil)))
on-context-restore
(mf/use-fn (fn []
(let [canvas (mf/ref-val canvas-ref)]
(when (some? canvas)
(let [gl (.getContext canvas CANVAS_CONTEXT_ID)]
(mf/set-ref-val! gl-ref gl)
(resize-canvas canvas)
(prepare-gl gl)
(render-gl gl objects vbox))))))]
(mf/with-effect [objects vbox]
(let [gl (mf/ref-val gl-ref)]
(when (some? gl)
(render-gl gl objects vbox))))
(mf/with-effect [canvas-ref]
(let [canvas (mf/ref-val canvas-ref)]
(when (some? canvas)
(.addEventListener canvas "webglcontextlost" on-context-lost)
(.addEventListener canvas "webglcontextrestore" on-context-restore)
(let [gl (.getContext canvas CANVAS_CONTEXT_ID)]
(mf/set-ref-val! gl-ref gl)
(resize-canvas canvas)
(prepare-gl gl)
(render-gl gl objects vbox))))
;; unmount
(fn []
(let [canvas (mf/ref-val canvas-ref)]
(when (some? canvas)
(.removeEventListener canvas "webglcontextlost" on-context-lost)
(.removeEventListener canvas "webglcontextrestore" on-context-restore)))))
[:canvas {:class (stl/css :canvas)
:ref canvas-ref}]))
;; TODO
;; - blend modes
;; - strokes

View File

@@ -0,0 +1,13 @@
// 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
@import "refactor/common-refactor.scss";
.canvas {
position: absolute;
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,127 @@
(ns app.util.gl
(:require [app.common.data.macros :as dm]))
;;
;; Shaders
;;
(defn get-shader-type
[gl type]
(cond
(= type (.-VERTEX_SHADER gl)) "vertex shader"
(= type (.-FRAGMENT_SHADER gl)) "fragment shader"
:else "unknown shader type"))
(defn create-shader
"Creates a shader of the given type with the given source"
[gl type source]
(let [shader (.createShader gl type)]
(.shaderSource gl shader source)
(.compileShader gl shader)
(when-not (.getShaderParameter gl shader (.-COMPILE_STATUS gl))
(throw (js/Error. (dm/str (get-shader-type gl type) " " (.getShaderInfoLog gl shader)))))
shader))
(defn create-vertex-shader
"Creates a vertex shader with the given source"
[gl source]
(create-shader gl (.-VERTEX_SHADER gl) source))
(defn create-fragment-shader
"Creates a fragment shader with the given source"
[gl source]
(create-shader gl (.-FRAGMENT_SHADER gl) source))
;;
;; Programs
;;
(defn create-program
"Creates a program with the given vertex and fragment shaders"
[gl vertex-shader fragment-shader]
(let [program (.createProgram gl)]
(.attachShader gl program vertex-shader)
(.attachShader gl program fragment-shader)
(.linkProgram gl program)
(when-not (.getProgramParameter gl program (.-LINK_STATUS gl))
(throw (js/Error. (.getProgramInfoLog gl program))))
program))
(defn create-program-from-sources
"Creates a program with the given vertex and fragment shader sources"
[gl vertex-source fragment-source]
(let [vertex-shader (create-vertex-shader gl vertex-source)
fragment-shader (create-fragment-shader gl fragment-source)]
(create-program gl vertex-shader fragment-shader)))
(defn get-program-active-factory
"Returns a map of active uniform and attribute names to their locations"
[parameter get-active-name get-location-name]
(fn [gl program]
(let [count (.getProgramParameter gl program parameter)
get-active (unchecked-get gl get-active-name)
get-location (unchecked-get gl get-location-name)
actives #js {}]
(doseq [index (range 0 count)]
(let [info (.apply get-active gl #js [program index])
name (.-name info)
location (.apply get-location gl #js [program name])]
(.defineProperty js/Object actives name #js {:value #js {:name name :info info :location location} :enumerable true :writable false :configurable false})))
actives)))
(def get-program-uniforms (get-program-active-factory (.-ACTIVE_UNIFORMS js/WebGLRenderingContext) "getActiveUniform" "getUniformLocation"))
(def get-program-attributes (get-program-active-factory (.-ACTIVE_ATTRIBUTES js/WebGLRenderingContext) "getActiveAttrib" "getAttribLocation"))
(defn get-program-actives
"Returns a map of uniform names to uniform locations for the given program"
[gl program]
(let [uniforms (get-program-uniforms gl program)
attributes (get-program-attributes gl program)]
#js { :uniforms uniforms :attributes attributes }))
(defn get-program-uniform-location
"Returns the location of the given uniform in the given program"
[actives name]
(let [uniforms (unchecked-get actives "uniforms")
uniform (unchecked-get uniforms name)]
(unchecked-get uniform "location")))
;;
;; Buffers
;;
(defn create-buffer-from-data
[gl data target usage]
(let [buffer (.createBuffer gl)]
(.bindBuffer gl target buffer)
(.bufferData gl target data usage)
(.bindBuffer gl target nil)
buffer))
;;
;; Framebuffers
;;
(defn create-framebuffer
[gl attachments]
(let [framebuffer (.createFramebuffer gl)]
(.bindFramebuffer gl framebuffer)
(doseq [[attachment attachment-info] attachments]
(let [attachment-type (unchecked-get attachment-info "type")
attachment-data (unchecked-get attachment-info "data")]
(cond
(= attachment-type "texture")
(.framebufferTexture2D gl (.-FRAMEBUFFER gl) attachment (.-TEXTURE_2D gl) attachment-data 0)
(= attachment-type "renderbuffer")
(.framebufferRenderbuffer gl (.-FRAMEBUFFER gl) attachment (.-RENDERBUFFER gl) attachment-data)
:else
(throw (js/Error. (dm/str "Unknown attachment type: " attachment-type))))))
(.bindFramebuffer gl nil)
framebuffer))
;;
;; Renderbuffers
;;
(defn create-renderbuffer
[gl]
(let [renderbuffer (.createRenderbuffer gl)]
(.bindRenderbuffer gl renderbuffer)
(.bindRenderbuffer gl nil)
renderbuffer))

View File

@@ -0,0 +1,6 @@
(ns app.util.gl.macros
(:refer-clojure :exclude [slurp])
(:require [clojure.core :as core]))
(defmacro slurp [file]
(core/slurp file))

View File

@@ -0,0 +1,69 @@
#version 300 es
const int type_rect = 0;
const int type_circle = 1;
const int type_group = 2;
const int type_path = 3;
const int type_text = 4;
const int type_image = 5;
const int type_svg_raw = 6;
const int type_bool = 7;
const int type_frame = 8;
precision highp float;
out vec4 fragColor;
in vec2 v_texCoord;
uniform int u_type;
uniform vec2 u_size;
uniform vec4 u_color;
uniform vec4 u_border;
bool isRoundedRect(in vec4 border) {
return border.x > 0.0 || border.y > 0.0 || border.z > 0.0 || border.w > 0.0;
}
// Thanks to Iñigo Quilez for this awesome functions.
// @see https://iquilezles.org/articles/distfunctions2d/
float sdRoundBox(in vec2 p, in vec2 b, in vec4 r) {
r.xy = (p.x > 0.0f) ? r.xy : r.zw;
r.x = (p.y > 0.0f) ? r.x : r.y;
vec2 q = abs(p) - b + r.x;
return min(max(q.x, q.y), 0.0f) + length(max(q, 0.0f)) - r.x;
}
float sdCircle(in vec2 p, in float r) {
return length(p) - r;
}
void main() {
// Si es un rect o un frame, simplemente asignamos el color al fragColor.
if (u_type == type_rect || u_type == type_frame) {
if (isRoundedRect(u_border)) {
if (sdRoundBox(v_texCoord - 0.5, vec2(0.5), u_border / u_size.x) > 0.0) {
discard;
}
fragColor = u_color;
} else {
fragColor = u_color;
}
// Si es un circulo, comprobamos que el pixel este dentro del circulo, en caso
// contrario descartamos el pixel.
} else if (u_type == type_circle) {
if (sdCircle(v_texCoord - 0.5, 0.5) > 0.0) {
discard;
}
fragColor = u_color;
// Para cualquier otro elemento no soportado pintamos una especie de rejilla
// raruna.
} else {
fragColor = vec4(
round(mod(v_texCoord.x, 0.1) * 10.0),
round(mod(v_texCoord.y, .1) * 10.0),
0.0,
1.0
);
}
}

View File

@@ -0,0 +1,50 @@
#version 300 es
precision highp float;
uniform vec4 u_vbox;
uniform vec2 u_position;
uniform vec2 u_size;
uniform float u_rotation;
uniform mat3 u_projection;
out vec2 v_texCoord;
vec2 get_vertex_position(int id) {
if(id == 0) {
return vec2(-1.0, -1.0);
} else if(id == 1) {
return vec2(1.0, -1.0);
} else if(id == 2) {
return vec2(-1.0, 1.0);
} else if(id == 3) {
return vec2(1.0, 1.0);
} else {
return vec2(0.0f, 0.0f);
}
}
void main() {
vec2 center = u_size * 0.5;
vec2 position = u_position - vec2(u_vbox.xy);
float c = cos(u_rotation);
float s = sin(u_rotation);
mat2 rotation = mat2(c, s, -s, c);
mat2 scale = mat2(
u_size.x * 0.5, 0.0f,
0.0f, u_size.y * 0.5
);
mat2 rotation_scale = rotation * scale;
vec2 vertex = get_vertex_position(gl_VertexID);
vec2 vertex_rotated_scaled = rotation_scale * vertex;
vec2 vertex_positioned = center + vertex_rotated_scaled + position;
vec3 projected = u_projection * vec3(vertex_positioned, 1.0);
gl_Position = vec4(projected, 1.0f);
v_texCoord = (vertex + 1.0) * 0.5;
}

View File

@@ -7587,6 +7587,7 @@ __metadata:
draft-js: "npm:^0.11.7"
eventsource-parser: "npm:^1.1.1"
gettext-parser: "npm:^7.0.1"
gl-matrix: "npm:^3.4.3"
gulp: "npm:4.0.2"
gulp-cached: "npm:^1.1.1"
gulp-concat: "npm:^2.6.1"
@@ -7891,6 +7892,13 @@ __metadata:
languageName: node
linkType: hard
"gl-matrix@npm:^3.4.3":
version: 3.4.3
resolution: "gl-matrix@npm:3.4.3"
checksum: c8ee6e2ce2d089b4ba4ae13ec9d4cb99bf2abe5f68f0cb08d94bbd8bafbec13aacc7230b86539ce5ca01b79226ea8c3194f971f5ca0c81838bc5e4e619dc398e
languageName: node
linkType: hard
"glob-parent@npm:^3.1.0":
version: 3.1.0
resolution: "glob-parent@npm:3.1.0"