Requested improvements to OpenGL shader support in JUCE


#1

Hi all,

I’ve run into some problems lately which have highlighted a few issues with JUCE’s support for shaders that I think could be improved.

In short - there isn’t a workable solution currently for displaying shaders in JUCE with the ability to set uniforms in a way that integrates nicely with JUCE’s graphics elements. I know people might point to both OpenGLShaderProgram and OpenGLGraphicsContextCustomShader, but there are problems with each and I’m going to elaborate here.

OpenGLShaderProgram has a very nice interface and you can do lots with it, including setting uniforms. The problem is that (as far as I understand it) the JUCE graphics commands are not executed immediately but instead placed into some kind of stack and drawn later all together. This means that you can’t use OpenGLShaderProgram to draw on top of other JUCE components. E.g. If you do a background fill - this will appear on top of and obscure your shader. So OpenGLShaderProgram is fine for drawing gradient background fills, but it is hard to integrate it with JUCE’s graphics and components. E.g I would like to make a ShaderComponent that draws on top of other components and interacts nicely with the JUCE UI.

OpenGLGraphicsContextCustomShader does partially solve this problem - the fillRect() function that can be called on it is drawn at the same time as the JUCE graphics elements. However, there is no way to set a uniform on a shader like this that will be applied at the correct time.

OpenGLGraphicsContextCustomShader has a function getProgram() which returns the
internal OpenGLShaderProgram. The comments say this “can be called when you’re about to use fillRect, to set up any uniforms/textures that the program may require”. I tried calling use() on the shader program and setting uniforms, but this ended up giving lots of very weird behaviour where uniforms I was setting affected other graphics that were being drawn - there was flickering and seemingly unrelated components being filled with colours they were not drawn with, etc. Not nice.

I notice that the JUCE demo does not include an example showing that this can be done. Is there something I am missing? Or does this not work correctly? If I am incorrect, perhaps someone can provide an example of how this should be done.

However, it seems to me that the uniforms should really be set at the time shader.program.use(); is called in setShader() in the CurrentShader struct in the file juce_OpenGLGraphicsContext.cpp. I have put together a hacky solution that serves my needs (although I need to maintain a fork of JUCE), but a more comprehensive solution would be needed for JUCE users to properly set a range of different uniform types on shaders that integrate well with the JUCE graphics elements. I don’t recommend my changes as they are purely a demonstration of what makes everything work well for me - nevertheless, I’ve included them here in case they are helpful:

I have seen that the ability to set uniforms in this way has been requested many times (e.g. here & here). So this is clearly something people want.

Let me know your thoughts!

Thanks,

Adam


Pixel shader on 2D vector graphics
#2

Hi all,

I’d really like to get some feedback on this. I see that since my post, others have had the same problem (see here) in addition to previous requests by others (here & here).

To elaborate on where I’ve got to, I’ve found a workable solution now with very few changes to the JUCE codebase. In OpenGLGraphicsContextCustomShader I have added a public member:

std::function<void(OpenGLShaderProgram*)> onSetUniforms;

then in ShaderBase I have added one of these also.

Then in OpenGLGraphicsContextCustomShader::fillRect I have added a copy of the std::function - I actually hate this bit and would remove it but I have no idea of the lifetime of objects in this part of JUCE, so a better solution should be found for this…

if (OpenGLRendering::ShaderContext* sc = dynamic_cast<OpenGLRendering::ShaderContext*> (&gc))
        if (CustomProgram* c = CustomProgram::getOrCreate (gc, hashName, code, errorMessage))
        {
            c->onSetUniforms = onSetUniforms;
            sc->fillRectWithCustomShader (*c, area);
        }

Finally, in CurrentShader::setShader I have added the following after the call to shader.bindAttributes (context);:

if (shader.onSetUniforms)
    shader.onSetUniforms (&shader.program);

This allows me to use a lambda to set uniforms and do other things at this point, e.g.

shader->onSetUniforms = [&](OpenGLShaderProgram* program)
{
     program->setUniform ("myUniform", 0.5f);                    
};

I am doing this successfully with several uniforms and passing textures to shaders. They render in a way that integrates nicely with other JUCE components.

I can totally see that some other solution might be better - perhaps some virtual function in OpenGLGraphicsContextCustomShader such as…

virtual void setUniforms (OpenGLShaderProgram* program);

that we could override. But in general, it feels like this is something lots of people keep coming back to and I think a solution to it wouldn’t be too much work (unless I’m overlooking something).

Looking forward to your thoughts,

Adam


#3

Can you set uniforms other than floats? OpenGLGraphicsContextCustomShader supports floats, but no other data type for some reason.

edit: just re-read. you said you were passing textures. sounds rad.

@fabian check this out. you’re the openGL guy on the JUCE team, right?


#4

That class is mostly my responsibility - I hadn’t replied simply because I’ve not had time to think about your suggestion, and I haven’t really been near that class for a long time either, so would have to refresh my memory of how it works before I could say whether it’s a good request or not!


#5

Ok thanks! Let me know when you’ve had a chance to take a look. :slight_smile:


#6

TBH if you have a specific change in mind I’ll be able to do this quicker if you send me the changes - is the pull request above still what you think would make sense?


#7

Yes, I think the solution something like the std::function approach above would work. Although there is a horrid copy in there that I couldn’t get rid of without better understanding the lifetime of the objects in that part of the JUCE codebase.

I’ve made a pull request in case it is useful:

Thanks!

Adam


#8

std::function<> supports the move constructor, so it might not be as bad a copy as you think…


#9

Yeah, that’s true. Though I think I don’t like it because the callback should in theory never change, and so doesn’t need to be copied (or moved) each frame. I’m sure there is a more elegant solution. :slight_smile:


#10

it’s funny. a lot of the OpenGL folks say that any API that tries to wrap OpenGL calls into some elegant syntax ends up looking really ugly and adding complexity. I guess they’re of the mindset that we should all just learn OpenGL instead of learning to use some wrapper that tries to simplify OpenGL. Gotta give props to folks like Jules, etc for trying to make a system that makes using OpenGL easier than the usual OpenGL tutorials imply.


#11

FYI this should be on develop soon…


#12

Great! Thanks Jules! Really appreciate you taking a look. :slight_smile:

Adam


#13

@jules was this ever added to the develop branch?


#14

Looks like this was the change that was put in for this request: