-
Notifications
You must be signed in to change notification settings - Fork 20
API Guide
The GLSL AST is represented as Clojure maps with certain keys.
Instead of entering the maps directly, use the constructor functions provided in gamma.api:
(require '[gamma.api :as g])
(g/sin 1)
=> {:tag :term, :head :sin, :id {:tag :id, :id 1}, :type :float,
:body ({:tag :term, :head :literal, :value 1, :type :float, :id {:tag :id, :id 2}})}
Each GLSL operator, function, or type constructor has an equivalent function in gamma.api.
The different species of GLSL input/output variables also have constructors:
;; attribute
(g/attribute "a_Attr" :float)
=> {:tag :variable, :name "a_Attr", :type :float, :storage :attribute}
;; uniform
(g/uniform "u_Uniform" :mat4)
=> {:tag :variable, :name "u_Uniform", :type :mat4, :storage :uniform}
;; varying
(g/varying "v_Varying" :float :highp)
=> {:tag :variable, :name "v_Varying", :type :float, :storage :varying, :precision :highp}
;; bult-in variables
(g/gl-position)
=> {:tag :variable, :name "gl_Position", :type :vec4}
(g/gl-frag-color)
=> {:tag :variable, :name "gl_FragColor", :type :vec4}
Building the AST is just a matter of composing constructor functions, resulting in nested maps:
(g/clamp (g/sin 1.0) 0.25 0.5)
=> {:tag :term,
:head :clamp,
:body ({:tag :term,
:head :sin,
:body ({:tag :term,
:head :literal,
:value 1,
:type :float,
:id {:tag :id, :id 3}}),
:id {:tag :id, :id 2},
:type :float}
{:tag :term,
:head :literal,
:value 0.25,
:type :float,
:id {:tag :id, :id 5}}
{:tag :term,
:head :literal,
:value 0.5,
:type :float,
:id {:tag :id, :id 6}}),
:id {:tag :id, :id 4},
:type :float}
To refer to a input variable with the AST, simply create it and pass it to an AST constuctor:
(g/sin (g/attribute "a_Attr" :float))
If's are expressions, so we can nest if's inside of other expressions:
(g/sin (g/if (g/attribute "b_Bool" :bool) 1 2))
To reuse an expression in multiple places, use let, or any other binding form:
(let [x (g/sin (g/attribute "a_Attr" :float))]
(g/vec3 x x x))
;; equivalent to
(g/vec3
(g/sin (g/attribute "a_Attr" :float))
(g/sin (g/attribute "a_Attr" :float))
(g/sin (g/attribute "a_Attr" :float)))
Gamma's compiler will ensure that the (g/sin (g/attribute "a_Attr" :float)) expression will only be evaluated once. This frees you from having to think about intermediary variables within the AST and their impact on performance.
In general, Gamma disallows use of GLSL AST's binding forms. You never directly create assignments in GLSL yourself; use Clojure's binding to feed values where needed, and the compiler will insert an assignment to eliminate duplication. This restriction buys us an important property: referential transparency. This property is what allows easy metaprogramming and full use of Clojurescript's facilities.
Constructor functions typecheck their arguments and infer their own types:
(:type (g/sin 1.0))
=> :float
(:type (g/sin (g/vec3 0.0 0.0 1.0)))
=> :vec3
Passing the wrong type results in an exception:
(g/sin true)
=> Error: Wrong argument types for term sin: :bool
This is useful for debugging. Your code can also dispatch based on the GLSL type of the AST.
It doesn't really matter how the AST comes together, just flow data to where it is needed.
We can create AST, put in in some datastructure, and write logic to flow it to a destination:
;; create some AST fragments and hang on to them
(def x {:partA (g/sin 1) :partB (g/cos 1)})
;; get AST fragments and put them where we want
(g/clamp (:partA x) 0 (:partB x))
Functions are an even more powerful abstraction. Use functions to factor out or parameterize subtrees:
;; start with
(g/+ 1 (g/+ 2 3))
;; create helper
(defn my-helper [x] (g/+ x 3))
;; refactor tree using helper:
(g/+ 1 (my-helper 2))
Metaprogramming GLSL with higher-order functions:
(reduce g/+ 0 [1 2 3 4])
(apply g/vec4 (map #(g/clamp % 0 1) [0 0.5 1 2]))
Feel free to use whatever abstractions you want for building up the tree. Just remember that GLSL is a typed language, and its functions and operations have type signatures that need to be respected.
For example, Gamma does not provide a for loop, since WebGL only supports unrollable for-loops.
Using Clojurescript, it is trivial to unroll loops ourselves:
(defn map-over-vec4 [f v]
(apply g/vec4 (for [i (range 4)] (f (g/part v i)))))
A third party is free to develop a general-purpose library to cover common iteration pattens.