OpenGLShaderProgram - adding shaders

hi all

I'm days into my epic quest to simply get a spinning cube on my iPhone using JUCE and openGL ES. Which means I've had to roll my own shaders. Now comes the step of actually compiling them. Overall I think I understand how JUCE's OpenGLShaderProgram class is meant to work, except for the step of using addShader().

So the docs say I need to have a const char *const shaderSourceCode as a parameter for this. How does that work? I've got my vertex and fragment shaders added to the project, but it's not clear to me how to tell JUCE what to do with them.

cheers, nick

 

bool Program::create(OpenGLContext& ctx, const char* vsm, const char* fsm)
{
    _shaderProgram = new OpenGLShaderProgram(ctx);
    bool v = _shaderProgram->addShader(fsm, GL_FRAGMENT_SHADER);
    if (v)
    {
        v = _shaderProgram->addShader(vsm, GL_VERTEX_SHADER);
        if (v)
        {
            pid = _shaderProgram->programID;

          
            v = _shaderProgram->link();
        }

....

}

and

 OpenGLShaderProgram*            _shaderProgram;

thanks Hugh! that's helpful, and this all makes sense.

The core thing i'm having trouble with here is that i don't understand how to give the addShader() method my shader code which is sitting in two other files. I'm assuming JUCE wants to read those files like an array, but I'm too green to know how to do that. Sorry I realize this is all probably C++ 101 stuff, I just haven't had to do this yet and can't find a good explanation. 

I have an example project on github that shows how to use shaders and OpenGL ES 2 in Juce:

https://github.com/gkellum/OpenGLES2InJuce

It needs a bit of cleaning and that's why I never mentioned it on the Juce forums before.  But if you're stuck, it might help.

1 Like

There's not really anything to understand... Instead of putting your shaders in separate files and adding them to the project, you can just paste them in as string literals, and pass them to the addShader method.

If you grep the juce codebase for addShader, you'll see that being done in a bunch of places.

wow, thanks! I just started looking through this, and have spotted a couple things that I didn't understand that you've made more obvious. I guess it should have been obvious that Juce had some features to turn a file into a string :)

I'm curious, why did you end up making separate shaders for desktop vs mobile? I see you're changing precision in the mobile versions, was that for performance reasons? Just interested to know what you ran into there.

There's not really anything to understand... 

says the master programmer to the 6-month-in newbie :) You have a lot of knowledge that you take for granted. I've learned a lot from the docs and example code but occasionally I still can't connect the dots, so thanks for the suggestion. 

Every other openGL example I've seen keeps shaders in separate files. It seems like that might be easier to deal with in the long run, even though they're not that much code in most cases. But lo and behold, there's a JUCE function to loadFileAsString ... so anyway that looks like the route to go down, no? 

there's a JUCE function to loadFileAsString ... so anyway that looks like the route to go down, no?

Well, no! Using a file means distributing those files with your app, making your installers copy them to somewhere on the user's machine, then finding the files on the target machine, loading them... all of which is a complete waste of effort and bound to lead to mistakes.

If you really don't want to write them as literals, you can use the introjucer to embed them to your app as resources, and grab the strings from the BinaryData class.

But personally, I'd say just keep things simple and use string literals - it keeps all the code together in one file, you can see exactly what's going on, there's no extra build or install step, no files to lose or mix-up or get the wrong version, etc.

aha, makes perfect sense. I wasn't thinking that since this stuff actually gets compiled when the program's running, those files are always going to have to be hanging out somewhere. And using them as resources would make them annoying to make changes to. Thanks Jules!

There are separate shaders for desktop vs mobile because of those precision specifiers.  The shaders won't work on OpenGL ES 2 without them, and they won't work on desktop OpenGL 2 with them.  In most of the example shader code I looked through, people used macros in the shader file itself to turn the precision specifiers on or off based on what environment they were running under.  There doesn't seem to be a way to define macros for the shader compiler though in Juce which is why I broke the shaders up into different files.

But what Jules mentioned about including the shaders in your cpp source files as string literals also provides a better solution to this problem.  If you have the shaders in your source cpp file, you can use C++ macros to turn the precision specifiers on and off.  For example:


    String fragmentShader =     
#if OS_MACIOS || OS_ANDROID
                                "precision mediump float; \n"
#endif
                                "varying vec2 v_texCoord; \n"
                                "uniform sampler2D s_texture; \n"
                                "varying float v_alpha_multiplier; \n"
                                "void main()                       \n"
                                "{                                 \n"
                                "  gl_FragColor = texture2D( s_texture, v_texCoord ); \n"
                                "  gl_FragColor.a *= v_alpha_multiplier; \n"
                                "}\n";
 

 

ah yes, figured that out when I got my shader in there, I had to take out the precision stuff to get it to load on osX. That is annoying! but nice solution there, will likely end up using that. thanks!