<commit_before>#+SETUPFILE: ../../src/setup.org

* Contents                                                         :toc_3_gh:
 - [[#thinggeomwebglshaders][thi.ng.geom.webgl.shaders]]
     - [[#todo-shader-specs][TODO Shader specs]]
     - [[#shader-uniforms][Shader uniforms]]
         - [[#supported-types][Supported types]]
         - [[#todo-uniform-setup--handling][TODO Uniform setup & handling]]
     - [[#shader-attributes][Shader attributes]]
     - [[#shader-creation][Shader creation]]
         - [[#header-injection][Header injection]]
         - [[#creation-compilation--linking][Creation, compilation & linking]]
     - [[#shader-usage][Shader usage]]
     - [[#shading-state-preparation][Shading state preparation]]
     - [[#helpers][Helpers]]
     - [[#complete-namespace-definition][Complete namespace definition]]
     - [[#presets][Presets]]
         - [[#basic][Basic]]
         - [[#lambert][Lambert]]
         - [[#phong][Phong]]
         - [[#x-ray][X-Ray]]
         - [[#spotlight][Spotlight]]
         - [[#shadow][Shadow]]
         - [[#2d-overlay][2D Overlay]]

* thi.ng.geom.webgl.shaders

** TODO Shader specs
** Shader uniforms
*** Supported types

#+BEGIN_SRC clojure :noweb-ref uniforms
  (def uniform-types
    {:float       ["1fv" arrays/float32]
     :int         ["1iv" arrays/int32]
     :vec2        ["2fv" arrays/float32]
     :vec3        ["3fv" arrays/float32]
     :vec4        ["4fv" arrays/float32]
     :ivec2       ["2iv" arrays/int32]
     :ivec3       ["3iv" arrays/int32]
     :ivec4       ["4iv" arrays/int32]
     :bool        ["1iv" arrays/int32]
     :bvec2       ["2iv" arrays/int32]
     :bvec3       ["3iv" arrays/int32]
     :bvec4       ["4iv" arrays/int32]
     :mat2        ["Matrix2fv" arrays/float32]
     :mat3        ["Matrix3fv" arrays/float32]
     :mat4        ["Matrix4fv" arrays/float32]
     :sampler2D   ["1iv" arrays/int32]
     :samplerCube ["1iv" arrays/int32]})

#+END_SRC

*** TODO Uniform setup & handling

Shader specs define uniforms as a map under the =:uniforms= key. In
this map, each of the shader's uniform names must be stated as keys
and their types as values. Default values can be given by using a
vector of =[type default]=. Default values are only used when a user
doesn't specify a uniform in her model spec when drawing geometry. The
example below defines default values for the =:ambient=, =:model= and
=:texture= uniforms:

#+BEGIN_SRC clojure
  :uniforms {:alpha    :float
             :diffuse  :vec3
             :ambient  [:vec3 [0.1 0.1 0.1]]
             :texture  [:sampler2D 0]
             :modelMat [:mat4 M44]
             :viewMat  :mat4
             :projMat  :mat4}
#+END_SRC

Special cases:

- Setters for =:vec3= uniforms expect a 3-element vector or an
  integer. If the latter, it is interpreted as 24bit RGB value and
  converted into a normalized RGB vector (=[r g b]=)
- Values for Matrix uniforms can be specified as vector =[mat default transpose?]=
  to indicate given matrix should be transposed

#+BEGIN_SRC clojure :noweb-ref uniforms
  (defn init-shader-uniforms
    [^WebGLRenderingContext gl prog uniforms]
    (reduce
     (fn [umap [id type]]
       (let [loc (.getUniformLocation gl prog (name id))
             [type default transpose?] (if (sequential? type) type [type])
             transpose? (boolean transpose?)
             [u-type u-cast] (uniform-types type)
             setter (aget gl (str "uniform" u-type))]
         ;; TODO add check if valid uniform
         (assoc umap id
                {:type type
                 :default default
                 :setter (cond
                           (#{:mat2 :mat3 :mat4} type)
                           (fn [x]
                             (.call setter gl loc transpose?
                                    (if (arrays/typed-array? x)
                                      x (u-cast x))))
                           (= :vec3 type)
                           (fn [x]
                             (if (number? x)
                               (.call setter gl loc (-> x col/int24 col/as-rgba deref (subvec 0 3) u-cast))
                               (.call setter gl loc (if (arrays/typed-array? x) x (u-cast x)))))
                           :else
                           (fn [x]
                             (.call setter gl loc
                                    (if (arrays/typed-array? x)
                                      x (u-cast (if (not (sequential? x)) [x] x))))))
                 :loc loc})))
     {} uniforms))

  (defn set-uniform
    [shader id val]
    (if-let [u-spec (get-in shader [:uniforms id])]
      ((:setter u-spec) val)
      (warn "Unknown shader uniform: " id)))
#+END_SRC

** Shader attributes

#+BEGIN_SRC clojure :noweb-ref attribs
  (defn init-shader-attribs
    [^WebGLRenderingContext gl prog attribs]
    (reduce
     (fn [amap id]
       (assoc amap id (.getAttribLocation gl prog (name id))))
     {} attribs))

  (defn set-attribute
    [^WebGLRenderingContext gl shader id {:keys [buffer stride size type normalized? offset]}]
    (if-let [loc (get-in shader [:attribs id])]
      (doto gl
        (.bindBuffer gl/array-buffer buffer)
        (.enableVertexAttribArray loc)
        (.vertexAttribPointer
         loc
         size
         (or type gl/float)
         (boolean normalized?)
         (or stride 0)
         (or offset 0)))
      (warn (str "Unknown shader attribute: " id))))

  (defn disable-attribute
    [^WebGLRenderingContext gl shader id]
    (if-let [loc (get-in shader [:attribs id])]
      (do (.disableVertexAttribArray gl loc) gl)
      (warn (str "Unknown shader attribute: " id))))
#+END_SRC

** Shader creation

*** Header injection

These boilerplate =#define='s are prefixed by default to any given
shader source before compilation with =compile-shader= below.

#+BEGIN_SRC glsl :noweb-ref header
  #ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp int;
  precision highp float;
  #else
  precision mediump int;
  precision mediump float;
  #endif
  #ifndef PI
  #define PI      3.141592653589793
  #endif
  #ifndef TWO_PI
  #define TWO_PI  6.283185307179586
  #endif
  #ifndef HALF_PI
  #define HALF_PI 1.570796326794896
  #endif
  #ifndef RAD
  #define RAD     0.008726646259972
  #endif
#+END_SRC

#+BEGIN_SRC clojure :noweb-ref ctors :noweb yes
  (def header-prefix
    "
<<header>>
")
#+END_SRC

*** Creation, compilation & linking

#+BEGIN_SRC clojure :noweb-ref ctors
  (defn compile-glsl-vars
    [qualifier coll]
    (->> coll
         (map
          (fn [[id type]]
            (str qualifier " "
                 (name (if (sequential? type) (first type) type)) " "
                 (name id) ";\n")))
         (apply str)))

  (defn parse-and-throw-error
    [^WebGLRenderingContext gl shader src]
    (let [src-lines (vec (str/split-lines src))
          errors (->> shader
                      (.getShaderInfoLog gl)
                      (str/split-lines)
                      (map
                       (fn [line]
                         (let [[_ ln msg] (re-find #"ERROR: \d+:(\d+): (.*)" line)]
                           (when ln
                             (str "line " ln ": " msg "\n"
                                  (src-lines (dec (js/parseInt ln 10))))))))
                      (filter identity)
                      (str/join "\n"))]
      (.deleteShader gl shader)
      (err/throw! (str "Error compiling shader:\n" errors))))

  (defn compile-shader
    [^WebGLRenderingContext gl src type]
    (if-let [shader (.createShader gl type)]
      (do
        (.shaderSource gl shader src)
        (.compileShader gl shader)
        (if (.getShaderParameter gl shader gl/compile-status)
          shader
          (parse-and-throw-error gl shader src)))
      (err/throw! "Can't create shader")))

  (defn make-shader-from-spec
    [^WebGLRenderingContext gl {:keys [vs fs uniforms attribs varying prefix state]}]
    (let [u-src  (compile-glsl-vars "uniform" uniforms)
          a-src  (compile-glsl-vars "attribute" attribs)
          v-src  (compile-glsl-vars "varying" varying)
          prefix (str (or prefix header-prefix) u-src v-src)
          vs     (compile-shader gl (str prefix a-src vs) gl/vertex-shader)
          fs     (compile-shader gl (str prefix fs) gl/fragment-shader)
          prog   (.createProgram gl)]
      (doto gl
        (.attachShader prog vs)
        (.attachShader prog fs)
        (.linkProgram prog))
      (if (.getProgramParameter gl prog gl/link-status)
        (let [uniforms (init-shader-uniforms gl prog uniforms)
              attribs (init-shader-attribs gl prog (keys attribs))]
          (doto gl
            (.deleteShader vs)
            (.deleteShader fs))
          {:program  prog
           :uniforms uniforms
           :attribs  attribs
           :varying  varying
           :state    state})
        (err/throw! (str "Shader failed to link:" (.getProgramInfoLog gl prog))))))

  (defn make-shader-from-dom
    [^WebGLRenderingContext gl {:keys [vs fs] :as spec}]
    (make-shader-from-spec
     gl (assoc spec
          :vs (glu/get-script-text vs)
          :fs (glu/get-script-text fs))))
#+END_SRC

** Shader usage

#+BEGIN_SRC clojure :noweb-ref draw
  (defn apply-default-uniforms
    [shader uniforms]
    (->> (keys uniforms)
         (apply dissoc (:uniforms shader))
         (transduce
          (filter #(:default (val %)))
          (completing #(set-uniform shader (key %2) (:default (val %2))))
          nil)))

  (defn begin-shader
    [^WebGLRenderingContext gl shader uniforms attribs]
    (.useProgram gl (:program shader))
    (apply-default-uniforms shader uniforms)
    (reduce-kv #(set-uniform shader %2 %3) nil uniforms)
    (reduce-kv #(set-attribute gl shader %2 %3) nil attribs))

  (defn end-shader
    [^WebGLRenderingContext gl shader]
    (reduce #(disable-attribute gl shader (key %2)) nil (:attribs shader)))
#+END_SRC

** Shading state preparation

Shader specs can specify WebGL render state flags which can then be
automatically setup using the =prepare-render-state= function below.
Currently the following options are supported:

| *Key*         | *Values*                               | *Description*                  |
|---------------+----------------------------------------+--------------------------------|
| =:depth-test= | boolean                                | enabled if true, else disabled |
| =:blend=      | boolean                                | enabled if true, else disabled |
| =:blend-fn=   | 2-elem vector =[src-coeff dest-coeff]= | only used if =:blend true=     |
|               | [[https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBlendFunc.xml][reference]]                              |                                |

#+BEGIN_SRC clojure :noweb-ref state
  (defn prepare-render-state
    "Takes a Gl context and shader spec, sets GL render flags stored
    under :state key (only if :state is present)."
    [gl {:keys [state]}]
    (when state
      (if (:depth-test state)
        (gl/enable gl gl/depth-test)
        (gl/disable gl gl/depth-test))
      (if (:blend state)
        (let [[src dest] (or (:blend-fn state) [gl/src-alpha gl/one])]
          (doto gl
            (gl/enable gl/blend)
            (.blendFunc src dest)))
        (gl/disable gl gl/blend)))
    gl)
#+END_SRC

** Helpers

#+BEGIN_SRC clojure :noweb-ref helpers
  (defn compute-normal-matrix
    [m v] (-> v (m/* m) (m/invert) (m/transpose)))

  (defn inject-normal-matrix
    [spec model-mat view-mat normal-mat-id]
    (let [model-mat (if (keyword? model-mat)
                      (-> spec :uniforms model-mat)
                      model-mat)
          view-mat (if (keyword? view-mat)
                      (-> spec :uniforms view-mat)
                      view-mat)]
      (assoc-in
       spec [:uniforms normal-mat-id]
       (compute-normal-matrix model-mat view-mat))))
#+END_SRC

** Complete namespace definition

#+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/geom/webgl/shaders.cljs :noweb yes :mkdirp yes :padline no
(ns thi.ng.geom.webgl.shaders
  (:require-macros
    [cljs-log.core :refer [info warn]])
  (:require
    [thi.ng.math.core :as m]
    [thi.ng.geom.core :as g]
    [thi.ng.geom.core.matrix]
    [thi.ng.geom.webgl.core :as gl]
    [thi.ng.geom.webgl.utils :as glu]
    [thi.ng.typedarrays.core :as arrays]
    [thi.ng.color.core :as col]
    [thi.ng.xerror.core :as err]
    [clojure.string :as str]))

  <<uniforms>>

  <<attribs>>

  <<ctors>>

  <<draw>>

  <<state>>

  <<helpers>>
#+END_SRC

** Presets
*** Basic

#+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/geom/webgl/shaders/basic.cljs :noweb yes :mkdirp yes :padline no
  (ns thi.ng.geom.webgl.shaders.basic
    (:require
     [thi.ng.geom.core.matrix :refer [M44]]))

  (defn make-shader-spec
    [& [{attr? :color-attrib? is3d? :3d :or {is3d? true}}]]
    (let [spec {:vs       (str "void main(){"
                               (if attr? "vCol=color;")
                               "gl_Position=proj*"
                               (if is3d?
                                 "view*model*vec4(position,1.0)"
                                 "view*model*vec4(position,0.0,1.0)")
                               ";}")
                :fs       (str "void main(){gl_FragColor="
                               (if attr? "vCol" "color")
                               ";}")
                :uniforms {:proj  :mat4
                           :model [:mat4 M44]
                           :view  [:mat4 M44]}
                :attribs  {:position (if is3d? :vec3 :vec2)}}
          spec (if attr?
                 (-> spec
                     (assoc-in [:attribs :color] :vec4)
                     (assoc :varying {:vCol :vec4}))
                 (assoc-in spec [:uniforms :color] :vec4))]
      spec))

  (defn make-shader-spec-2d
    [color-attrib?] (make-shader-spec {:color-attrib? color-attrib? :3d false}))

  (defn make-shader-spec-3d
    [color-attrib?] (make-shader-spec {:color-attrib? color-attrib? :3d true}))
#+END_SRC

*** Lambert
**** Standard shading (uniform)

#+BEGIN_SRC glsl :noweb-ref lambert-vs-uni
  void main() {
    float lam = lambert(surfaceNormal(normal, normalMat), lightDir);
    vCol = vec4(ambientCol + diffuseCol * lightCol * lam, alpha);
    gl_Position = mvp(position, model, view, proj);
  }
#+END_SRC

**** Standard shading (attribute)

This version requires a =color= attribute buffer, used in place of the
=diffuseCol= shader uniform. The color buffer is assumed to contain
RGBA colors, however the alpha component is discarded.

#+BEGIN_SRC glsl :noweb-ref lambert-vs-attr
  void main() {
    float lam = lambert(surfaceNormal(normal, normalMat), lightDir);<commit_msg>[webgl] avoid varargs in make-shader-spec, update requires<commit_after>#+SETUPFILE: ../../src/setup.org

* Contents                                                         :toc_3_gh:
 - [[#thinggeomwebglshaders][thi.ng.geom.webgl.shaders]]
     - [[#todo-shader-specs][TODO Shader specs]]
     - [[#shader-uniforms][Shader uniforms]]
         - [[#supported-types][Supported types]]
         - [[#todo-uniform-setup--handling][TODO Uniform setup & handling]]
     - [[#shader-attributes][Shader attributes]]
     - [[#shader-creation][Shader creation]]
         - [[#header-injection][Header injection]]
         - [[#creation-compilation--linking][Creation, compilation & linking]]
     - [[#shader-usage][Shader usage]]
     - [[#shading-state-preparation][Shading state preparation]]
     - [[#helpers][Helpers]]
     - [[#complete-namespace-definition][Complete namespace definition]]
     - [[#presets][Presets]]
         - [[#basic][Basic]]
         - [[#lambert][Lambert]]
         - [[#phong][Phong]]
         - [[#x-ray][X-Ray]]
         - [[#spotlight][Spotlight]]
         - [[#shadow][Shadow]]
         - [[#2d-overlay][2D Overlay]]

* thi.ng.geom.webgl.shaders

** TODO Shader specs
** Shader uniforms
*** Supported types

#+BEGIN_SRC clojure :noweb-ref uniforms
  (def uniform-types
    {:float       ["1fv" arrays/float32]
     :int         ["1iv" arrays/int32]
     :vec2        ["2fv" arrays/float32]
     :vec3        ["3fv" arrays/float32]
     :vec4        ["4fv" arrays/float32]
     :ivec2       ["2iv" arrays/int32]
     :ivec3       ["3iv" arrays/int32]
     :ivec4       ["4iv" arrays/int32]
     :bool        ["1iv" arrays/int32]
     :bvec2       ["2iv" arrays/int32]
     :bvec3       ["3iv" arrays/int32]
     :bvec4       ["4iv" arrays/int32]
     :mat2        ["Matrix2fv" arrays/float32]
     :mat3        ["Matrix3fv" arrays/float32]
     :mat4        ["Matrix4fv" arrays/float32]
     :sampler2D   ["1iv" arrays/int32]
     :samplerCube ["1iv" arrays/int32]})

#+END_SRC

*** TODO Uniform setup & handling

Shader specs define uniforms as a map under the =:uniforms= key. In
this map, each of the shader's uniform names must be stated as keys
and their types as values. Default values can be given by using a
vector of =[type default]=. Default values are only used when a user
doesn't specify a uniform in her model spec when drawing geometry. The
example below defines default values for the =:ambient=, =:model= and
=:texture= uniforms:

#+BEGIN_SRC clojure
  :uniforms {:alpha    :float
             :diffuse  :vec3
             :ambient  [:vec3 [0.1 0.1 0.1]]
             :texture  [:sampler2D 0]
             :modelMat [:mat4 M44]
             :viewMat  :mat4
             :projMat  :mat4}
#+END_SRC

Special cases:

- Setters for =:vec3= uniforms expect a 3-element vector or an
  integer. If the latter, it is interpreted as 24bit RGB value and
  converted into a normalized RGB vector (=[r g b]=)
- Values for Matrix uniforms can be specified as vector =[mat default transpose?]=
  to indicate given matrix should be transposed

#+BEGIN_SRC clojure :noweb-ref uniforms
  (defn init-shader-uniforms
    [^WebGLRenderingContext gl prog uniforms]
    (reduce
     (fn [umap [id type]]
       (let [loc (.getUniformLocation gl prog (name id))
             [type default transpose?] (if (sequential? type) type [type])
             transpose? (boolean transpose?)
             [u-type u-cast] (uniform-types type)
             setter (aget gl (str "uniform" u-type))]
         ;; TODO add check if valid uniform
         (assoc umap id
                {:type type
                 :default default
                 :setter (cond
                           (#{:mat2 :mat3 :mat4} type)
                           (fn [x]
                             (.call setter gl loc transpose?
                                    (if (arrays/typed-array? x)
                                      x (u-cast x))))
                           (= :vec3 type)
                           (fn [x]
                             (if (number? x)
                               (.call setter gl loc (-> x col/int24 col/as-rgba deref (subvec 0 3) u-cast))
                               (.call setter gl loc (if (arrays/typed-array? x) x (u-cast x)))))
                           :else
                           (fn [x]
                             (.call setter gl loc
                                    (if (arrays/typed-array? x)
                                      x (u-cast (if (not (sequential? x)) [x] x))))))
                 :loc loc})))
     {} uniforms))

  (defn set-uniform
    [shader id val]
    (if-let [u-spec (get-in shader [:uniforms id])]
      ((:setter u-spec) val)
      (warn "Unknown shader uniform: " id)))
#+END_SRC

** Shader attributes

#+BEGIN_SRC clojure :noweb-ref attribs
  (defn init-shader-attribs
    [^WebGLRenderingContext gl prog attribs]
    (reduce
     (fn [amap id]
       (assoc amap id (.getAttribLocation gl prog (name id))))
     {} attribs))

  (defn set-attribute
    [^WebGLRenderingContext gl shader id {:keys [buffer stride size type normalized? offset]}]
    (if-let [loc (get-in shader [:attribs id])]
      (doto gl
        (.bindBuffer gl/array-buffer buffer)
        (.enableVertexAttribArray loc)
        (.vertexAttribPointer
         loc
         size
         (or type gl/float)
         (boolean normalized?)
         (or stride 0)
         (or offset 0)))
      (warn (str "Unknown shader attribute: " id))))

  (defn disable-attribute
    [^WebGLRenderingContext gl shader id]
    (if-let [loc (get-in shader [:attribs id])]
      (do (.disableVertexAttribArray gl loc) gl)
      (warn (str "Unknown shader attribute: " id))))
#+END_SRC

** Shader creation
*** Header injection

These boilerplate =#define='s are prefixed by default to any given
shader source before compilation with =compile-shader= below.

#+BEGIN_SRC glsl :noweb-ref header
  #ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp int;
  precision highp float;
  #else
  precision mediump int;
  precision mediump float;
  #endif
  #ifndef PI
  #define PI      3.141592653589793
  #endif
  #ifndef TWO_PI
  #define TWO_PI  6.283185307179586
  #endif
  #ifndef HALF_PI
  #define HALF_PI 1.570796326794896
  #endif
  #ifndef RAD
  #define RAD     0.008726646259972
  #endif
#+END_SRC

#+BEGIN_SRC clojure :noweb-ref ctors :noweb yes
  (def header-prefix
    "
<<header>>
")
#+END_SRC

*** Creation, compilation & linking

#+BEGIN_SRC clojure :noweb-ref ctors
  (defn compile-glsl-vars
    [qualifier coll]
    (->> coll
         (map
          (fn [[id type]]
            (str qualifier " "
                 (name (if (sequential? type) (first type) type)) " "
                 (name id) ";\n")))
         (apply str)))

  (defn parse-and-throw-error
    [^WebGLRenderingContext gl shader src]
    (let [src-lines (vec (str/split-lines src))
          errors (->> shader
                      (.getShaderInfoLog gl)
                      (str/split-lines)
                      (map
                       (fn [line]
                         (let [[_ ln msg] (re-find #"ERROR: \d+:(\d+): (.*)" line)]
                           (when ln
                             (str "line " ln ": " msg "\n"
                                  (src-lines (dec (js/parseInt ln 10))))))))
                      (filter identity)
                      (str/join "\n"))]
      (.deleteShader gl shader)
      (err/throw! (str "Error compiling shader:\n" errors))))

  (defn compile-shader
    [^WebGLRenderingContext gl src type]
    (if-let [shader (.createShader gl type)]
      (do
        (.shaderSource gl shader src)
        (.compileShader gl shader)
        (if (.getShaderParameter gl shader gl/compile-status)
          shader
          (parse-and-throw-error gl shader src)))
      (err/throw! "Can't create shader")))

  (defn make-shader-from-spec
    [^WebGLRenderingContext gl {:keys [vs fs uniforms attribs varying prefix state]}]
    (let [u-src  (compile-glsl-vars "uniform" uniforms)
          a-src  (compile-glsl-vars "attribute" attribs)
          v-src  (compile-glsl-vars "varying" varying)
          prefix (str (or prefix header-prefix) u-src v-src)
          vs     (compile-shader gl (str prefix a-src vs) gl/vertex-shader)
          fs     (compile-shader gl (str prefix fs) gl/fragment-shader)
          prog   (.createProgram gl)]
      (doto gl
        (.attachShader prog vs)
        (.attachShader prog fs)
        (.linkProgram prog))
      (if (.getProgramParameter gl prog gl/link-status)
        (let [uniforms (init-shader-uniforms gl prog uniforms)
              attribs (init-shader-attribs gl prog (keys attribs))]
          (doto gl
            (.deleteShader vs)
            (.deleteShader fs))
          {:program  prog
           :uniforms uniforms
           :attribs  attribs
           :varying  varying
           :state    state})
        (err/throw! (str "Shader failed to link:" (.getProgramInfoLog gl prog))))))

  (defn make-shader-from-dom
    [^WebGLRenderingContext gl {:keys [vs fs] :as spec}]
    (make-shader-from-spec
     gl (assoc spec
          :vs (glu/get-script-text vs)
          :fs (glu/get-script-text fs))))
#+END_SRC

** Shader usage

#+BEGIN_SRC clojure :noweb-ref draw
  (defn apply-default-uniforms
    [shader uniforms]
    (->> (keys uniforms)
         (apply dissoc (:uniforms shader))
         (transduce
          (filter #(:default (val %)))
          (completing #(set-uniform shader (key %2) (:default (val %2))))
          nil)))

  (defn begin-shader
    [^WebGLRenderingContext gl shader uniforms attribs]
    (.useProgram gl (:program shader))
    (apply-default-uniforms shader uniforms)
    (reduce-kv #(set-uniform shader %2 %3) nil uniforms)
    (reduce-kv #(set-attribute gl shader %2 %3) nil attribs))

  (defn end-shader
    [^WebGLRenderingContext gl shader]
    (reduce #(disable-attribute gl shader (key %2)) nil (:attribs shader)))
#+END_SRC

** Shading state preparation

Shader specs can specify WebGL render state flags which can then be
automatically setup using the =prepare-render-state= function below.
Currently the following options are supported:

| *Key*         | *Values*                               | *Description*                  |
|---------------+----------------------------------------+--------------------------------|
| =:depth-test= | boolean                                | enabled if true, else disabled |
| =:blend=      | boolean                                | enabled if true, else disabled |
| =:blend-fn=   | 2-elem vector =[src-coeff dest-coeff]= | only used if =:blend true=     |
|               | [[https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBlendFunc.xml][reference]]                              |                                |

#+BEGIN_SRC clojure :noweb-ref state
  (defn prepare-render-state
    "Takes a Gl context and shader spec, sets GL render flags stored
    under :state key (only if :state is present)."
    [gl {:keys [state]}]
    (when state
      (if (:depth-test state)
        (gl/enable gl gl/depth-test)
        (gl/disable gl gl/depth-test))
      (if (:blend state)
        (let [[src dest] (or (:blend-fn state) [gl/src-alpha gl/one])]
          (doto gl
            (gl/enable gl/blend)
            (.blendFunc src dest)))
        (gl/disable gl gl/blend)))
    gl)
#+END_SRC

** Helpers

#+BEGIN_SRC clojure :noweb-ref helpers
  (defn compute-normal-matrix
    [m v] (-> v (m/* m) (m/invert) (m/transpose)))

  (defn inject-normal-matrix
    [spec model-mat view-mat normal-mat-id]
    (let [model-mat (if (keyword? model-mat)
                      (-> spec :uniforms model-mat)
                      model-mat)
          view-mat (if (keyword? view-mat)
                      (-> spec :uniforms view-mat)
                      view-mat)]
      (assoc-in
       spec [:uniforms normal-mat-id]
       (compute-normal-matrix model-mat view-mat))))
#+END_SRC

** Complete namespace definition

#+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/geom/webgl/shaders.cljs :noweb yes :mkdirp yes :padline no
(ns thi.ng.geom.webgl.shaders
  (:require-macros
    [cljs-log.core :refer [info warn]])
  (:require
    [thi.ng.math.core :as m]
    [thi.ng.geom.core :as g]
    [thi.ng.geom.core.matrix]
    [thi.ng.geom.webgl.core :as gl]
    [thi.ng.geom.webgl.utils :as glu]
    [thi.ng.typedarrays.core :as arrays]
    [thi.ng.glsl.core :as glsl :include-macros true]
    [thi.ng.color.core :as col]
    [thi.ng.xerror.core :as err]
    [clojure.string :as str]))

  <<uniforms>>

  <<attribs>>

  <<ctors>>

  <<draw>>

  <<state>>

  <<helpers>>
#+END_SRC

** Presets
*** Basic

#+BEGIN_SRC clojure :tangle ../babel/src/thi/ng/geom/webgl/shaders/basic.cljs :noweb yes :mkdirp yes :padline no
  (ns thi.ng.geom.webgl.shaders.basic
    (:require
     [thi.ng.geom.core.matrix :refer [M44]]))

  (defn make-shader-spec
    ([]
     (make-shader-spec nil))
    ([{attr? :color-attrib? is3d? :3d :or {is3d? true}}]
     (let [spec {:vs       (str "void main(){"
                                (if attr? "vCol=color;")
                                "gl_Position=proj*"
                                (if is3d?
                                  "view*model*vec4(position,1.0)"
                                  "view*model*vec4(position,0.0,1.0)")
                                ";}")
                 :fs       (str "void main(){gl_FragColor="
                                (if attr? "vCol" "color")
                                ";}")
                 :uniforms {:proj  :mat4
                            :model [:mat4 M44]
                            :view  [:mat4 M44]}
                 :attribs  {:position (if is3d? :vec3 :vec2)}}
           spec (if attr?
                  (-> spec
                      (assoc-in [:attribs :color] :vec4)
                      (assoc :varying {:vCol :vec4}))
                  (assoc-in spec [:uniforms :color] :vec4))]
       spec)))

  (defn make-shader-spec-2d
    [color-attrib?] (make-shader-spec {:color-attrib? color-attrib? :3d false}))

  (defn make-shader-spec-3d
    [color-attrib?] (make-shader-spec {:color-attrib? color-attrib? :3d true}))
#+END_SRC

*** Lambert
**** Standard shading (uniform)

#+BEGIN_SRC glsl :noweb-ref lambert-vs-uni
  void main() {
    float lam = lambert(surfaceNormal(normal, normalMat), lightDir);
    vCol = vec4(ambientCol + diffuseCol * lightCol * lam, alpha);
    gl_Position = mvp(position, model, view, proj);
  }
#+END_SRC

**** Standard shading (attribute)

This version requires a =color= attribute buffer, used in place of the
=diffuseCol= shader uniform. The color buffer is assumed to contain
RGBA colors, however the alpha component is discarded.

#+BEGIN_SRC glsl :noweb-ref lambert-vs-attr
  void main() {
    float lam = lambert(surfaceNormal(normal, normalMat), lightDir);
{'fraction_non_alphanumeric': 0.13501453586640524, 'fraction_numerical': 0.013656953552836184, 'mean_word_length': 1.899157193257546, 'pattern_counts': {'":': 2, '<': 31, '<?xml version=': 0, '>': 53, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 36, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1044675', 'n_tokens_mistral': 9841, 'n_tokens_neox': 9243, 'n_words': 2560}