So, I’m still writing boat-loads of documentation, and unfortunately, I haven’t yet worked my way around to writing documentation for how to build materials in Jax. That’s kind of sad because Jax’s material management is really impressive. So much so, in fact, that I’m really looking forward to writing that part of the docs. I’m not sure how I’ll organize it yet, as there’s a lot to say, but it definitely promises to be an interesting section of the Wiki.
That said, I probably have to give you a little bit of background so you’ll understand where I’m going with all this. Here’s a sample of an average Jax Material:
# ambient component multiplied with the light source's ambient component ambient: red: 1.0 green: 1.0 blue: 1.0 alpha: 1.0 # diffuse component multiplied with the light source's diffuse component diffuse: red: 1.0 green: 1.0 blue: 1.0 alpha: 1.0 # specular component multiplied with the light source's specular component specular: red: 1.0 green: 1.0 blue: 1.0 alpha: 1.0 shininess: 30 layers: - type: Texture path: "/images/rock.png" flip_y: false scale: 1 - type: Lighting - type: NormalMap path: "/images/rockNormal.png" flip_y: false scale: 1 - type: ShadowMap
The “layers” section is the key to this post. They’ve always been impressive; Jax uses some preprocessing magic to turn shaders into pluggable modules, and each type of shader has a corresponding subclass of Material which is responsible for setting its uniforms, attributes and whatnot. (You can even add your own, though I haven’t written a spiffy generator for automating that yet!)
I don’t know if anybody else uses this technique to manage material layers, and I don’t know what anybody else would call the paradigm. I call this a “shader chain”, with each layer acting as a single element in the chain.
I think this is pretty cool, to be honest, because it means that Jax materials can mix and match layers in any order, and each element in a chain could technically be used as its own standalone material, if you so wished. So it’s quite versatile.
And today, it got even more powerful.
I just committed some code to make shader chains smart enough to incrementally back off on individual elements if they appear to be stretching the limits of the client’s graphics processor. So, in the above example, if it is determined that color (which all Materials implicitly support), texture, lighting support, normal maps and shadow maps are just too much for the client to handle, Jax will now automatically back off on layers until it comes up with a result that the GPU can handle. In the above case, it would start with removing the shadow maps. If that’s still not enough, the normal map would be dropped. Failing that, lighting support would be disabled. Finally, if, God forbid, the GPU is just too weak, texturing support would be removed. The only thing left in this worst-case scenario is the core shader, which provides basic per-vertex color support. The core shader is virtually guaranteed to work with all graphics hardware that complies with the WebGL standard, but if for some reason the driver doesn’t follow the standard, and the core shader can’t be used, a fatal error will be raised because there’s no shader left to draw with.
Every time Jax drops an element from the shader chain, a warning is logged to the console. If no console exists, (which is the case in Firefox if you don’t have the Web Console activated), then it is reported in the form of a non-program-halting error (via #setTimeout).
Once an element is dropped from the shader chain, it is dropped permanently unless you programmatically re-add it. Since the determination is made based on the number of uniforms, attributes and varying variables used by the shader vs. how many of each is supported by the GPU, this adaptation phase only has to happen once — because most graphics cards don’t dynamically alter their internal hardware, as far as I know.
Here’s another thing you might want to note: you can easily control which elements will be dropped! As I hopefully implied above, Jax will always drop the last element in the chain. So if you have a scene where you have determined shadow mapping to be more important than normal mapping, simply swap their positions so that normal mapping appears after shadow mapping. That way, if Jax dynamically backs off, it’ll drop normal mapping and hopefully the client will still handle shadow mapping without issue. Job done!
But wait! There’s more! Before popping the last element from the chain, Jax first checks all of the other shaders to see if any of them are simply not going to work. If, for instance, the Lighting shader is using so many uniforms and attributes that the graphics card will never be able to handle it, then Jax simply removes Lighting and tries again. It’ll only pop elements from the end of the array if there are no more obvious candidates for removal.
Realistically, we all hope this is a feature that won’t ever be required (because we’d all prefer our customers to have the best possible experience, no?) — but it’s always there for you, just in case.