Skip to content

Latest commit

 

History

History
224 lines (183 loc) · 6.54 KB

core.org

File metadata and controls

224 lines (183 loc) · 6.54 KB

Contents

Namespace: thi.ng.glsl.core

GLSL dependency resolution

All shader functions in this library are defined via simple maps stored in vars, each grouping source code and dependencies to other GLSL functions/snippets. The example spec below defines a new function with two dependencies.

Note:

  1. Since GLSL snippets are stored in normal ClojureScript vars, namespaces of dependent GLSL snippets need to be required as usual.
  2. It’s recommended, though optional, to use the defglsl, glsl-spec or minified macros for these GLSL source snippets to reduce boilerplate & final JS file size (see macro section below for more details).
  3. This library is written using Emacs ORG-mode and all GLSL snippets here are defined using their own code blocks, which makes development easier.
(ns example
  (:require
   [thi.ng.glsl.core :include-macros true :refer-macros [defglsl minified]]
   [other.namespace :as other]
   [another.namespace :as another]))

(defglsl my-spec
  [other/spec another/spec]
  "vec3 myFunc(vec3 a, vec3 b) {
     return ...;
   }")

;; alternatively the same spec *without* using the defglsl macro...

(def my-spec
  (glsl/glsl-spec-plain
   [other/spec another/spec]
   "vec3 myFunc(vec3 a, vec3 b) {
      return ...;
   }"))

;; ...or...

(def my-spec
  {:deps [other/spec another/spec]
   :src  "vec3 myFunc(vec3 a, vec3 b) {
             return ...;
          }"})

In order to correctly concatenate the various shader source snippets, a dependency graph is computed during assembly using Stuart Sierra’s dependency library. The assemble function below receives a spec and returns an expanded GLSL source string with all dependencies in the correct order. Note that cyclic dependencies are not supported.

(defn- build-graph
  ([spec] (build-graph (dep/graph) spec))
  ([g curr]
     (reduce
      (fn [g d] (build-graph (dep/depend g curr d) d))
      g (:deps curr))))

(defn assemble
  [spec]
  (if (seq (:deps spec))
    (->> spec
         (build-graph)
         (dep/topo-sort)
         (mapv :src)
         (apply str))
    (:src spec)))

(defn glsl-spec-plain
  [deps src] {:deps deps :src src})

GLSL minifier

So far the minifier only performs the following operations:

  • comment removal (both single & multi line)
  • whitespace removal
(defn- minify-floats
  [src]
  (str/replace
   src #"(\d+)\.(\d+)"
   (fn [[o d f]]
     (cond
       (= "0" d)        (str \. f)
       (every? #{\0} f) (str d \.)
       :else            o))))

(defn- minify-line
  [src]
  (let [src (-> src
                (minify-floats)
                (str/replace #"\s{2,}|\t" "")
                (str/replace #"\s*(\{|\}|\=|\*|\,|\+|/|\>|\<|\&|\||\[|\]|\(|\)|\-|\!|\;)\s*" "$1"))]
    (if (= \# (first src))
      (str "\n" src "\n")
      src)))

(defn- clean-line-breaks
  [src] (str/replace src #"\n{2,}" "\n"))

(defn minify
  [src]
  (let [src (-> src
                (str/replace #"//.*" "")
                (str/replace #"/\*[\s\S]*?\*/" "")
                (str/replace #"^\n+" ""))]
    (->> (str/split src #"\n")
         (map minify-line)
         (apply str)
         (clean-line-breaks))))

Macro definitions

In addition to defining GLSL source code as Clojure strings, the library also allows to read source from files via the defglsl-file and glsl-file-spec macros. For example, the following spec pulls in the source from the file resources/glsl/foo.glsl in the current project dir.

(defglsl-file foo [dep1 dep2] "resources/glsl/foo.glsl")
#?(:clj
   (defmacro minified
     [src] `~(minify src)))

#?(:clj
   (defmacro glsl-spec
     [deps src] `{:deps ~deps :src (minified ~src)}))

#?(:clj
   (defmacro glsl-file-spec
     [deps path] `{:deps ~deps :src (minified ~(slurp path))}))

#?(:clj
   (defmacro defglsl
     [name deps src]
     `(do (def ~name (glsl-spec ~deps ~src))
          (alter-meta! (var ~name) merge ~(extract-glsl-meta src)))))

#?(:clj
   (defmacro defglsl-file
     [name deps path]
     `(do (def ~name (glsl-file-spec ~deps ~path))
          (alter-meta! (var ~name) merge ~(extract-glsl-meta (get name :src))))))

GLSL metadata extraction

To improve the user experience and ease the development of custom tooling (e.g. shader editors), all shader specs created via the defglsl or defglsl-file macros attach GLSL metadata to their generated Clojue vars. This metadata can then be retrieved via Clojure’s meta fn, like this:

(meta #'thi.ng.glsl.lighting/skylight-color)

;; {:line        165,
;;  :column      1,
;;  :file        "thi/ng/glsl/lighting.cljc",
;;  :name        skylight-color,
;;  :ns          #object[clojure.lang.Namespace 0x5ded04ed "thi.ng.glsl.lighting"],
;;  :glsl-return "vec3",
;;  :glsl-name   "skylightColor",
;;  :glsl-args   ["vec3 col1" "vec3 col2" "float height"]}
(def ^:private re-meta
  #"((((highp|mediump|lowp)\s+)?(void|bool|float|int|(b|i)?vec\d|mat\d))\s+([\w_]+)\s*\(([A-Za-z0-9_, ]*)\)\s*\{)")

(defn extract-glsl-meta
  "Attempts to extract function name, return type and args from given
  GLSL source string. If successful, returns map w/ these
  keys: :glsl-name :glsl-return :glsl-args"
  [src]
  (if-let [m (first (re-seq re-meta src))]
    (let [return (nth m 2)
          name   (nth m 7)
          args   (vec (filter seq (str/split (nth m 8) #",\s*")))]
      {:glsl-return return
       :glsl-name   name
       :glsl-args   args})))

Complete namespace definition

(ns thi.ng.glsl.core
  (:require
   [clojure.string :as str]
   [com.stuartsierra.dependency :as dep]))

<<minify>>

<<meta>>

<<macros>>

<<deps>>