As part of the process of studying OpenGL shaders, I’ve added just enough extra shader support to praxis to run a few of the examples in Shadertoy. For all the following examples, use the version of praxis from git commit ffeca91 and set the working directory to “cauldron”.
The first thing I wanted to be able to do was to “draw” to a bitmap with the mouse. As a prelude to this, I added support to praxis for running this simple mouse input example from Shadertoy. Among other things, this support included basic stuff like glUniform for example. Press F4 to run this shader example.
A drawing shader also requires a persistent buffer to draw to. I added functions for creating and working with FBOs, in particular, a function that would render to an FBO given a shader and other FBOs as inputs (or “channels”). To see a simple persistent “drawing” shader in action, modify the prerender() function in gameoflife.lua so it uses the draw shader instead of the game of life shader. In particular, change the line
render_to_fbo_with_input(g.fbo_curr, g.mainshader, g.fbo_prev)
render_to_fbo_with_input(g.fbo_curr, g.drawshader, g.fbo_prev)
Save it, then press F5 in praxis.
After I got that working, I thought an obvious mouse interaction thing to do with shaders would be cellular automata, such as Conway’s Game of Life. This plays to the main strength of shaders which is that you get to customize the per-pixel calculation that gets performed by the GPU. So I added enough support to praxis so it would run a Game of Life implementation I found on shadertoy with absolutely minimal modification. To run it, undo the change you just made to gameoflife.lua so it uses mainshader instead of drawshader, then press F5 in praxis. You can click and drag with the mouse to interact with it.
Zooming in to see detail:
So far so good! Given these successes, I tried running other shadertoy examples that caught my eye. I very quickly found one that didn’t behave the same way, and became curious as to why. The shader in question was this very interesting looking cellular automata. This is what it looked like when I tried to run it in praxis the first time:
After tearing away at the innards of this shader trying to see where it behaved differently in praxis than it did in shadertoy, I finally discovered that the textures needed to be standard 32 bit floats, PER COMPONENT. Red is a 32 bit float, green is a 32 bit float, blue is a 32 bit float, and alpha is a 32 bit float. This never occurred to me initially because I was so used to components being a single byte (0-255), resulting in a 4 byte pixel. This is after all the only information that gets actually rendered to the screen. The calculations in this thing used the full range of the floats in the pixel. What got rendered to the screen was a modulo of this number, resulting in automata that seemed to have a lot of internal state. Which of course, they do have! So after merrily changing the glTexImage2D call in luaCBGLPrepareFBOTexture so it gets passed a GL_RGBA32F_ARB parameter and a couple of calls to glClampColorARB to make sure there is no clamping, this example started to work correctly too. To run this shader, press F6 in praxis. You can click and drag with the mouse as before.
Almost forgot: it was at this point that I also added the ability to specify the texture filtering parameter. For this example, even though my tastes are usually for the hard pixelly goodness of GL_NEAREST, this one needs GL_LINEAR for the effect to work, or it just ends up degenerating into a very different, somewhat less interesting effect:
Lastly, I’ve had the pipe dream of being able to render text in a shader for quite a while. Rendering the text editor in a shader has always been something I’ve wanted to try doing since the text editor has always been heavier on the rendering load than I’d like it to be, and shaders seem like a natural way to lighten the rendering load of the editor significantly. I call it a pipe dream, because there is very very little about doing this on the internet generally. Fortunately, some excellent programmer on shadertoy provided a beautiful example of a text rendering shader that matches what I wanted almost perfectly.
This thing makes 3 passes with 3 different shaders, and it will require praxis to run a shader with more than one texture channel as input. The first pass creates a font texture from a font encoded in a shader. To get this to run on the 3 machines I tested it on, I changed asciiToSprite to use the mix function instead of the series of if statements mechanism, because one of the computers I tested it on complained about nested if statements. They aren’t nested, but the code the glsl compiler generates on some machines is so I did this workaround. Weird thing is, the webgl shadertoy version which uses the if statements still runs on the computer that complained, so go figure. Maybe Shadertoy does a pre-compilation step. The second pass makes a texture that encodes a string. This required no modification. The third pass takes the font and the string and renders the text. This also required no modification.
Once I made that one change to the font shader, this shader example worked well in praxis. I was pleased at this result and invigorated toward my next goal, which was to have the string texture come from an actual string instead of hard coded in a shader. The font shader can remain for now, especially since it only has to run once to generate the font FBO anyway.
As I mentioned, the second pass makes a texture that encodes a string. When I made a texture from a string, taking care to allocate the texture correctly and follow the conventions and assumptions about the texture the code seems to make (first byte in the floats is zero, next three are the chars), it wouldn’t display what I expected it to. I investigated by hardcoding inputs to functions within the shader to verify the operation of these functions. When I then tried passing the same data but from the string texture, the results didn’t match. Was there something OpenGL was doing to the texture before passing it to the shader? I checked the memory – no, the data is all there, and it was correct.
It took me a while to realize that even though its written in the string shader as raw ascii bytes, those raw ascii bytes get interpreted as a single number that gets converted to a float as that number! For example, 0x616263 which corresponds to the string “abc” and which would look like 0x616263 in memory if it were a string, doesn’t appear in texture memory as 0x616263! Those 3 bytes need to appear as the 4 byte float 6382179.0, which is 0x616263 in decimal then cast to a float.
Expressed in one line, its much easier to see what will really happen – the 0x616263 won’t be interpreted as raw memory, but the actual number to set the float to.
float val = float(0x616263);
When I realized this I went hunting for how to get the raw bytes. I also realized this would add the happy benefit of letting me use all 4 bytes instead of being restricted to 3 (because you can’t represent the magnitude of a 4 byte integer in a float). Once again, the internet has very little to say, and what it does seem to say is mostly misleading.
Through some persistence, I stumbled across this:
Exactly what I wanted.
With this realization, I was finally able to implement the magical glStringToTexture function.To see it running, press F7 in praxis.
To set your own string, use the following incantation to rebuild the texture at any time:
stringtex = glStringToTexture("your string")
So that is the saga of adding some shadertoy shader support to praxis. As always, have fun, and happy programming!