kovasb / gamma Goto Github PK
View Code? Open in Web Editor NEWglsl shaders made simple
License: Eclipse Public License 1.0
glsl shaders made simple
License: Eclipse Public License 1.0
It looks like it'll be a long road to get human-readable output from gamma. For example, at a glance, can you tell if these two shaders are equivalent?
precision mediump float;
varying vec2 vTextureCoord;
varying vec3 vTransformedNormal;
varying vec4 vPosition;
uniform bool uUseColorMap;
uniform bool uUseSpecularMap;
uniform bool uUseLighting;
uniform vec3 uAmbientColor;
uniform vec3 uPointLightingLocation;
uniform vec3 uPointLightingSpecularColor;
uniform vec3 uPointLightingDiffuseColor;
uniform sampler2D uColorMapSampler;
uniform sampler2D uSpecularMapSampler;
void main(void) {
vec3 lightWeighting;
if (!uUseLighting) {
lightWeighting = vec3(1.0, 1.0, 1.0);
} else {
vec3 lightDirection = normalize(uPointLightingLocation - vPosition.xyz);
vec3 normal = normalize(vTransformedNormal);
float specularLightWeighting = 0.0;
float shininess = 32.0;
if (uUseSpecularMap) {
shininess = texture2D(uSpecularMapSampler, vec2(vTextureCoord.s, vTextureCoord.t)).r * 255.0;
}
if (shininess < 255.0) {
vec3 eyeDirection = normalize(-vPosition.xyz);
vec3 reflectionDirection = reflect(-lightDirection, normal);
specularLightWeighting = pow(max(dot(reflectionDirection, eyeDirection), 0.0), shininess);
}
float diffuseLightWeighting = max(dot(normal, lightDirection), 0.0);
lightWeighting = uAmbientColor
+ uPointLightingSpecularColor * specularLightWeighting
+ uPointLightingDiffuseColor * diffuseLightWeighting;
}
vec4 fragmentColor;
if (uUseColorMap) {
fragmentColor = texture2D(uColorMapSampler, vec2(vTextureCoord.s, vTextureCoord.t));
} else {
fragmentColor = vec4(1.0, 1.0, 1.0, 1.0);
}
gl_FragColor = vec4(fragmentColor.rgb * lightWeighting, fragmentColor.a);
}
precision mediump float;
uniform vec3 uPointLightingSpecularColor;
uniform vec3 uPointLightingLocation;
varying mediump vec3 vTransformedNormal;
varying highp vec4 vPosition;
uniform sampler2D uColorMapSampler;
uniform vec3 uAmbientColor;
varying mediump vec2 vTextureCoord;
uniform sampler2D uSpecularMapSampler;
void main(void){
vec3 v1848;
vec3 v1850;
vec3 v1849;
vec2 v1839;
(v1839 = vec2(vTextureCoord.st));
(v1848 = vPosition.xyz);
(v1849 = normalize(vTransformedNormal));
(v1850 = normalize((uPointLightingLocation - v1848)));
(gl_FragColor = vec4(
(texture2D(uColorMapSampler, v1839).rgb * (uAmbientColor + ((uPointLightingSpecularColor * pow(
max(dot(reflect((-1.0 * v1850), v1849), normalize((-1.0 * v1848))), 0.0),
(texture2D(uSpecularMapSampler, v1839).r * 255.0))) + (uPointLightingSpecularColor * max(
dot(v1849, v1850),
0.0))))),
1.0));
}
Ultimately the gamma-source shader isn't so bad, but as people are learning (and learning to trust) gamma, it's likely they'll want to compare some hand-written glsl with the output, to get a sense of things. Could certainly be a big issue with onboarding new developers.
There will probably be lots of ways to "pretty-print" the shaders, but here are a two I've thought of:
(let [st (g/local :st (g/vec2 (g/swizzle v-texture-coord :st)))
diffuse-light-weighting (g/local :diffuseLightWeighting (.....))]
....)
This would probably be enough for me to quickly piece together whether the above shaders were equivalent, which would probably tell me why my current example is not working.
In the spirit of every gl-lib, I'm porting http://learningwebgl.com/blog/?p=28 over to gamma. Ran into an issue with the first example vertex shader:
attribute vec3 aVertexPosition;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
}
Which I believe the equivalent gamma is:
(def vertex-position
(g/attribute "aVertexPosition" :vec3))
(def p-matrix
(g/uniform "uPMatrix" :mat4))
(def mv-matrix
(g/uniform "uMVMatrix" :mat4))
(def vertex-shader
{(g/gl-position) (-> (g/* u-p-matrix u-mv-matrix)
(g/* (g/vec4 vertex-position 1)))})
But this results in:
Uncaught Error: Arguments to * must have type :int or :float, given :mat4 and :vec4
Right now you just say that a shader failed to compile, change the error to state which one failed.
I'll send PR for this tonight or tomorrow.
(def vertex-position (g/attribute "aVertexPosition" :vec3))
(def u-p-matrix (g/uniform "uPMatrix" :mat4))
(def u-mv-matrix (g/uniform "uMVMatrix" :mat4))
(def vtx-time (g/uniform "u_Time" :vec2))
(def vertex-shader
{(g/gl-position) (-> (g/* u-p-matrix u-mv-matrix)
(g/* (g/vec4 vertex-position 1))
(g/+ (g/vec4 (g/sin (g/swizzle vtx-time :x)) 0 0 0)))})
Errors somewhere in gamma.ast
:
Uncaught Error: Invalid literal: :fail
Trying to produce the output:
uniform mat4 uPMatrix;
uniform mat4 uMVMatrix;
attribute vec3 aVertexPosition;
uniform vec2 u_Time;
void main(void){
(gl_Position = (((uPMatrix * uMVMatrix) * vec4(aVertexPosition, 1)) + vec4(
(sin u_Time.x),
0,
0,
0)));
}
Want a constructor fn in gamma.api to indicate negation.
Currently we'd have to do something like (g/* -1.0 x)
Whereas we'd want something like (g/neg x)
(require '[gamma.api :as g])
Implementation in gamma.emit.operator
implies this should work:
(g/selector (g/vec2 1) "x")
but instead throws:
gamma-tools.core=> System.Exception: Invalid literal: "x"
at gamma/ast$literal__186.invoke (System.Object ) [0x0014e] in ast.clj:33
at gamma/ast$term$fn__193__197.invoke (System.Object ) [0x0003e] in ast.clj:44
at clojure/core$map$fn__57190__57194.invoke () [0x00199] in core.clj:2614
at clojure.lang.LazySeq.sval () [0x00000] in <filename unknown>:0
at (wrapper synchronized) clojure.lang.LazySeq:sval ()
at clojure.lang.LazySeq.seq () [0x00000] in <filename unknown>:0
at (wrapper synchronized) clojure.lang.LazySeq:seq ()
at clojure.lang.Cons.next () [0x00000] in <filename unknown>:0
at clojure.lang.RT.next (System.Object x) [0x00000] in <filename unknown>:0
at clojure/core$next__55177.invoke (System.Object ) [0x00000] in core.clj:64
at clojure/core$nthnext__57657.invoke (System.Object , System.Object ) [0x00085] in core.clj:3033
at clojure/core$print_sequential__61068.invoke (System.Object , System.Object , System.Object , System.Object , System.Object , System.Object ) [0x00257] in core_print.clj:48
at clojure/core$fn__61368__61372.invoke (System.Object , System.Object ) [0x00017] in core_print.clj:191
at clojure.lang.MultiFn.invoke (System.Object arg1, System.Object arg2) [0x00000] in <filename unknown>:0
at clojure/core$pr_on__58136.invoke (System.Object , System.Object ) [0x00049] in core.clj:3543
at clojure/core$print_map$fn__61436__61440.invoke (System.Object , System.Object ) [0x0004e] in core_print.clj:248
at clojure/core$print_sequential__61068.invoke (System.Object , System.Object , System.Object , System.Object , System.Object , System.Object ) [0x002fd] in core_print.clj:53
at clojure/core$print_map__61443.invoke (System.Object , System.Object , System.Object ) [0x00024] in core_print.clj:251
at clojure/core$fn__61446__61450.invoke (System.Object , System.Object ) [0x00017] in core_print.clj:255
at clojure.lang.MultiFn.invoke (System.Object arg1, System.Object arg2) [0x00000] in <filename unknown>:0
at clojure/core$pr_on__58136.invoke (System.Object , System.Object ) [0x00049] in core.clj:3543
at clojure/core$pr__58144.invoke (System.Object ) [0x00000] in core.clj:3555
at clojure.lang.AFn.ApplyToHelper (IFn fn, ISeq argList) [0x00000] in <filename unknown>:0
at clojure.lang.RestFn.applyTo (ISeq arglist) [0x00000] in <filename unknown>:0
at clojure/core$apply__55543.invoke (System.Object , System.Object ) [0x00006] in core.clj:626
at clojure/core$prn__58162.doInvoke (System.Object ) [0x00000] in core.clj:3588
at clojure.lang.RestFn.invoke (System.Object arg1) [0x00000] in <filename unknown>:0
at arcadia/repl$repl_eval_print$fn__28359__28363.invoke () [0x0020d] in repl.clj:63
at arcadia/repl$repl_eval_print__28366.invoke (System.Object , System.Object ) [0x0041e] in repl.clj:57
at arcadia/repl$repl_eval_string__28382.invoke (System.Object , System.Object ) [0x0002f] in repl.clj:75
at arcadia/repl$repl_eval_string__28382.invoke (System.Object ) [0x00000] in repl.clj:72
at arcadia/repl$eval_queue$fn__28390__28394.invoke () [0x00000] in repl.clj:84
When failing, currently there's a message like:
Uncaught Error: Wrong argument types for term texture2D: :sampler2d ,:vec2
Would be nice (since you know the right types) to include "Should be: ..."
Want a trivial emitter so we can port this to clojureclr
Seems like it's not allowed http://stackoverflow.com/questions/27874983/webgl-how-to-use-integer-attributes-in-glsl - if not, it's better to fail early with an explanatory error message.
This one is tough, and may not be easily solvable, but I just lost a lot of time to copy/pasting some code without updating the id...
(def a-position
(g/attribute "aVertexPosition" :vec3))
(def a-offset
(g/attribute "aVertexPosition" :vec3))
I thought my shader (which used both a-position
and a-offset
) took two inputs, but obviously only took one - didn't realize it until I looked at the generated glsl and then the program inputs.
Not sure how to fix this one, but seems likely to be a source of confusion and pain down the road.
Hi, I found this failure case. If a term appears multiple times within one branch of an if
, the shared term gets emitted without an assignment to a variable.
(require '[gamma.api :as g])
(require '[gamma.program :as p])
(def ans2
(let [g-elem (g/* 5 5)
sh {(g/gl-position) (g/vec4 (g/if (g/== 0 0)
(g/+ g-elem g-elem)
0.0))}]
(:glsl (p/shader sh {:float :highp}))))
(println ans2)
precision highp float;
void main(void){
float v387;
float v394;
if((0.0 == 0.0)){
(5.0 * 5.0); <----- note missing assignment
(v387 = (v394 + v394));}
else {
(v387 = 0.0);}
(gl_Position = vec4(v387));
}
I tried to track it down in the code but got lost in the compiler.
Several glsl builtins are missing (I'm looking at http://www.shaderific.com/glsl-functions/)
(require '[gamma.api :as g])
(g/+ 1 (g/aget (g/vec3 2) 0))
throws:
gamma-tools.core=> System.Exception: Arguments to + must have type :int or :float
at gamma/api$_PLUS___172.invoke (System.Object , System.Object ) [0x000a1] in api.clj:210
at gamma_tools/core$eval__3333__3338.invoke () [0x00000] in <filename unknown>:0
at clojure.lang.Compiler.eval (System.Object form) [0x00000] in <filename unknown>:0
at clojure.lang.Compiler.eval (System.Object form) [0x00000] in <filename unknown>:0
at clojure/core$eval__57691.invoke (System.Object ) [0x00000] in core.clj:3076
at arcadia/repl$repl_eval_print$fn__28359__28363.invoke () [0x001e3] in repl.clj:59
at arcadia/repl$repl_eval_print__28366.invoke (System.Object , System.Object ) [0x0041e] in repl.clj:57
at arcadia/repl$repl_eval_string__28382.invoke (System.Object , System.Object ) [0x0002f] in repl.clj:75
at arcadia/repl$repl_eval_string__28382.invoke (System.Object ) [0x00000] in repl.clj:72
at arcadia/repl$eval_queue$fn__28390__28394.invoke () [0x00000] in repl.clj:84
and
(g/float (g/aget (g/vec3 1) 0))
throws:
gamma-tools.core=> System.Exception: Wrong argument types for term float:
at gamma/api$build_standard_function_term__284.invoke (System.Object , System.Object , System.Object ) [0x00110] in api.clj:329
at gamma/api$float__338.doInvoke (System.Object ) [0x00000] in api.clj:331
at clojure.lang.RestFn.invoke (System.Object arg1) [0x00000] in <filename unknown>:0
at gamma_tools/core$eval__3347__3352.invoke () [0x00000] in <filename unknown>:0
at clojure.lang.Compiler.eval (System.Object form) [0x00000] in <filename unknown>:0
at clojure.lang.Compiler.eval (System.Object form) [0x00000] in <filename unknown>:0
at clojure/core$eval__57691.invoke (System.Object ) [0x00000] in core.clj:3076
at arcadia/repl$repl_eval_print$fn__28359__28363.invoke () [0x001e3] in repl.clj:59
at arcadia/repl$repl_eval_print__28366.invoke (System.Object , System.Object ) [0x0041e] in repl.clj:57
at arcadia/repl$repl_eval_string__28382.invoke (System.Object , System.Object ) [0x0002f] in repl.clj:75
at arcadia/repl$repl_eval_string__28382.invoke (System.Object ) [0x00000] in repl.clj:72
at arcadia/repl$eval_queue$fn__28390__28394.invoke () [0x00000] in repl.clj:84
This needs another pass, unhandled or buggy cases likely lurking.
I'm getting the error:
Uncaught Error: failed to compile fragment-shader:ERROR: 0:5: '' : No precision specified for (float)
from glsl:
uniform sampler2D uSampler;
varying mediump vec2 vTextureCoord;
varying mediump vec3 vLightWeighting;
void main(void){
vec4 v118;
(v118 = texture2D(uSampler, vec2(vTextureCoord.st)));
(gl_FragColor = vec4((v118.rgb * vLightWeighting), v118.a));
}
from gamma:
(def v-texture-coord
(g/varying "vTextureCoord" :vec2 :mediump))
(def v-light-weighting
(g/varying "vLightWeighting" :vec3 :mediump))
(def u-sampler
(g/uniform "uSampler" :sampler2D))
(def program source
(p/program
{:fragment-shader (let [texture-color (g/texture2D u-sampler (g/vec2 (g/swizzle v-texture-coord :st)))
rgb (g/* (g/swizzle texture-color :rgb) v-light-weighting)
a (g/swizzle texture-color :a)]
{(g/gl-frag-color) (g/vec4 rgb a)})})
While trying to produce the equivalent of:
precision mediump float;
varying vec2 vTextureCoord;
varying vec3 vLightWeighting;
uniform sampler2D uSampler;
void main(void) {
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a);
}
vec2 other = 1.0 - vec2(1.0)
is valid glsl, but gamma doesn't allow it
Since Gamma prunes it all before it gets to the GPU, it's unclear what clutter I have in my VS. At the very least, should be configurable.
I spent a while chasing a bug which turned out to be me thinking a vec4 was a vec3, and it would have tipped me off a lot sooner if (g/vec4 (g/vec4 1.0) 1.0)
threw an error (currently it just silently ignores the extra argument)
(def vertex-position (g/attribute "a_VertexPosition" :vec2))
(def vertex-color (g/attribute "a_VertexColor" :vec4))
(def vtx-time (g/uniform "u_Time" :vec2))
(def v-color (g/varying "vColor" :vec4 :mediump))
(def vertex-shader {v-color vertex-color
(g/gl-position) (g/+
(g/vec4 (g/+ (g/* (g/swizzle vertex-position :x) (g/swizzle vtx-time :y))
(g/* (g/swizzle vertex-position :y) (g/swizzle vtx-time :x)))
(g/- (g/* (g/swizzle vertex-position :y) (g/swizzle vtx-time :y))
(g/* (g/swizzle vertex-position :x) (g/swizzle vtx-time :x)))
(g/swizzle vertex-position :z) 0)
(g/vec4 vertex-position 0 1))})
Outputs:
attribute vec4 a_VertexColor;
attribute vec2 a_VertexPosition;
uniform vec2 u_Time;
varying mediump vec4 vColor;
void main(void){
[:nil {:source-id {:tag :id, :id 4572}, :id {:tag :id, :id 4608}}][:nil {:source-id {:tag :id, :id 4573}, :id {:tag :id, :id 4609}}][:nil {:source-id {:tag :id, :id 4574}, :id {:tag :id, :id 4610}}]
vColor = a_VertexColor;
gl_Position = vec4(
a_VertexPosition.x * u_Time.y + a_VertexPosition.y * u_Time.x,
a_VertexPosition.y * u_Time.y - a_VertexPosition.x * u_Time.x,
a_VertexPosition.z,
0) + vec4(, , );
}
(notice the clojure literals)
Either removing the v-color entry produces valid output:
(def vertex-shader {(g/gl-position) (g/+
(g/vec4 (g/+ (g/* (g/swizzle vertex-position :x) (g/swizzle vtx-time :y))
(g/* (g/swizzle vertex-position :y) (g/swizzle vtx-time :x)))
(g/- (g/* (g/swizzle vertex-position :y) (g/swizzle vtx-time :y))
(g/* (g/swizzle vertex-position :x) (g/swizzle vtx-time :x)))
(g/swizzle vertex-position :z) 0)
(g/vec4 vertex-position 0 1))})
=>
attribute vec2 a_VertexPosition;
uniform vec2 u_Time;
void main(void){
gl_Position = vec4(
a_VertexPosition.x * u_Time.y + a_VertexPosition.y * u_Time.x,
a_VertexPosition.y * u_Time.y - a_VertexPosition.x * u_Time.x,
a_VertexPosition.z,
0) + vec4(a_VertexPosition, 0, 1);
}
Or changing one of the x or y (but not z or w) ops:
(def vertex-shader {v-color vertex-color
(g/gl-position) (g/+
(g/vec4 1
;; Could also change the g/- call below
(g/- (g/* (g/swizzle vertex-position :y) (g/swizzle vtx-time :y))
(g/* (g/swizzle vertex-position :x) (g/swizzle vtx-time :x)))
(g/swizzle vertex-position :z) 0)
(g/vec4 vertex-position 0 1))})
=>
attribute vec4 a_VertexColor;
attribute vec2 a_VertexPosition;
uniform vec2 u_Time;
varying mediump vec4 vColor;
void main(void){
vColor = a_VertexColor;
gl_Position = vec4(
1,
a_VertexPosition.y * u_Time.y - a_VertexPosition.x * u_Time.x,
a_VertexPosition.z,
0) + vec4(a_VertexPosition, 0, 1);
}
I don't get any warnings, so it's a bit hard to track down what I'm doing wrong.
As it looks gamma only supports assignments at the top level, since the "ast" function in program.cljs maps to g/set k v over the top level dictionary. The discard statement is needed for proper transparency sorting, as described in https://www.khronos.org/opengl/wiki/Transparency_Sorting .
I resorted to putting a magic vector vec4(0.0, 1.0, 2.0, 3.0) and then doing a search/replace on the glsl string to insert a discard later, but that's not very robust to changes in the gamma compiler. Is there a better temporary hack? And is support for imperative statements expected in the future or completely out of scope?
This shader:
(def program-source
(p/program
{:vertex-shader {(g/gl-position) (-> u-p-matrix
(g/* u-mv-matrix)
(g/* (g/vec4 a-position 1)))
v-texture-coord a-texture-coord
v-light-weighting (g/if u-use-lighting
(let [transformed-normal (g/* u-n-matrix a-vertex-normal)
directional-light-weighting (g/max (g/dot transformed-normal u-lighting-direction) 0.0)]
(g/* directional-light-weighting
(g/+ u-ambient-color u-directional-color)))
(g/vec3 1 1 1))}})}))
is output as:
uniform mat3 uNMatrix;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform vec3 uAmbientColor;
uniform vec3 uDirectionalColor;
uniform vec3 uLightingDirection;
attribute vec3 aVertexNormal;
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;
uniform bool uUseLighting;
varying mediump vec2 vTextureCoord;
varying mediump vec3 vLightWeighting;
void main(void){
vec3 v90;
if(uUseLighting){
(v90 = (max(dot((uNMatrix * aVertexNormal), uLightingDirection), 0) * (uAmbientColor + uDirectionalColor)));}
else {
(v90 = vec3(1, 1, 1));}
(gl_Position = ((uPMatrix * uMVMatrix) * vec4(aVertexPosition, 1)));
(vTextureCoord = aTextureCoord);
(vLightWeighting = v90);
}
And will fail because the 0.0
is output as 0
, and according to this max
requires a float. There also doesn't seem to be any casting functions to manually work around this for the time being.
Right now I have to filter :inputs
to figure out how many attributes to enable/disable, also useful for general tooling. Probably useful to have :uniform-count
and :varying-count
as well for parity.
Maybe there are some others, but these should definitely be the starting point. Would remove some of the threading I need to do right now, and make the code look more clojure-ish.
It would be nice to know what program is causing an error in the console, perhaps we can encourage/require (p/program {:name ... :fragment-shader ... :vertex-shader ...})
to give saner error messages?
Just lost more time than I care to admit to: (g/swizzle :xyz quaternion)
where the message was Uncaught Error: Doesn't support name: {:tag :variable, :name "aRotation", :type :vec4, :storage :attribute}
.
Gamma asserting types for all of the base operations should make it much more enjoyable to use.
The compiler is too complicated for people to contribute / fix their bug.
A - Document how stuff works
B - Add compiler tests for key transformations
C - Refactor
D - Profit
How Stuff Works
User generates gamma ast. This is a tree.
Compiler gets tree.
First: flatten-ast . This turns tree into a graph; we call this the 'db'.
Right now all transforms operate on this graphlike 'db'. This causes complexity. ToFix.
Transformation 1: bubble-terms.
This generates information needed for detecting common subexpressions.
For each subexpression in the db, we traverse its parents up the tree. For each parent, we associate the start node to its meta info (:shared )
Specifically, we add it to a hashmap of { start-node times-seen }
Every time a node equivalent to start-node passes though a parent on the way up, we increment this count.
This principle is extended to deal with conditionals. If a node is scoped within an 'if' it will not necessarily evaluate. However we wish to propagate this information. Therefor node counts
[todo more description]
Not sure how you feel about this one, since it's not in the spec, but perhaps extremely common, base functions like this should be put in place to encourage higher-level library interop/reuse, rather than everyone implementing their own floor(a + 0.5)
function?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.