Uniform setting float triggers GL_INVALID_OPERATION

I try to enhance OpenGLContext copyTexture allowing an alpha multiplier (and later an AffineTransform).

When I simply add an additional Uniform to the Params struct, I get an GL_INVALID_OPERATION on set (float).

struct Params
{
    Params (juce::OpenGLShaderProgram& prog)
        : positionAttribute (prog, "position"),
          screenSize (prog, "screenSize"),
          imageTexture (prog, "imageTexture"),
          textureBounds (prog, "textureBounds"),
          vOffsetAndScale (prog, "vOffsetAndScale"),
          alphaMultiplier (prog, "alphaMultiplier")    // < new addition
    {}

    void set (const float targetWidth, const float targetHeight, const juce::Rectangle<float>& bounds, bool flipVertically, float alpha) const
    {
        const GLfloat m[] = { bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() };
        textureBounds.set (m, 4);
        JUCE_CHECK_OPENGL_ERROR
        imageTexture.set (0);
        JUCE_CHECK_OPENGL_ERROR
        screenSize.set (targetWidth, targetHeight);
        JUCE_CHECK_OPENGL_ERROR
        vOffsetAndScale.set (flipVertically ? 0.0f : 1.0f,
                                         flipVertically ? 1.0f : -1.0f);
        JUCE_CHECK_OPENGL_ERROR
        alphaMultiplier.set (alpha);
        JUCE_CHECK_OPENGL_ERROR   // <== here GL_INVALID_OPERATION is triggered
    }

    juce::OpenGLShaderProgram::Attribute positionAttribute;
    juce::OpenGLShaderProgram::Uniform screenSize, imageTexture, textureBounds, vOffsetAndScale, alphaMultiplier;
    //                                                                                            ^-----------
};

I tried with or without adding the uniform in the shader:

prog.addFragmentShader (juce::OpenGLHelpers::translateFragmentShaderToV3 (
    "uniform sampler2D imageTexture;"
    "varying " JUCE_HIGHP " vec2 texturePos;"
    "uniform " JUCE_HIGHP " float alphaMultiplier;"  // < tried with or without this line
    "void main()"
    "{"
      "gl_FragColor = texture2D (imageTexture, texturePos);"
    "}"));

Is there anything else I need to do to add an additional parameter to the shader?

Any hint is much appreciated!

Some more debugging revealed that the uniformID of my added variable is invalid (some high negative number).
The jassert didn’t catch it, probably disabled:

OpenGLShaderProgram::Uniform::Uniform (const OpenGLShaderProgram& program, const char* const name)
    : uniformID (program.context.extensions.glGetUniformLocation (program.getProgramID(), name)), context (program.context)
{
   #if JUCE_DEBUG && ! JUCE_DONT_ASSERT_ON_GLSL_COMPILE_ERROR
    jassert (uniformID >= 0);
   #endif
}

I think I understood now that glGetUniformLocation only finds keywords referenced in the shaders. But I tried adding that there too with no difference.

You’re not using the uniform in your shader, so the GLSL compiler optimizes it away.
This is why you get an invalid location.

Ah great! So declaring the variable in the into is not sufficient, I need to actually use it in the formula. That makes sense. Thank you!

I tried different ways to multiply the alpha, but never succeeded. I simply see no result.

I understand I need to premultiply myself, so I tried this:

                    prog.addFragmentShader (juce::OpenGLHelpers::translateFragmentShaderToV3 (
                        "uniform sampler2D imageTexture;"
                        "varying " JUCE_HIGHP " vec2 texturePos;"
                        "uniform " JUCE_HIGHP " float alphaMultiplier;"
                        "void main()"
                        "{"
                          "vec4 colour = texture2D (imageTexture, texturePos);"
                          "vec4 mask = colour.aaaa;"
                          "gl_FragColor = colour * (mask * alphaMultiplier);"
                        "}"));

Does that make sense?
Well, at least it doesn’t do anything. I also tried to replace alphaMultiplier with 0.1 to see an effect, no dice…

How is that usually done?

Here is one thing I naively suggest as I am new to this (literally first week of knowledge and experience with JUCE OpenGL so far)

You can create a vec4 mask which will have 4 components: vec4(R,G,B,A)
Initialize each of its R,G,B components to a factor of 1.0 so as to not modify the colour vectors’s R,G,B components, and set the A component to the alphaMultiplier.

I believe you would only want to multiply the ‘A’ alpha component of the colour vector.
I think multiplying the RGB components changes the colour. Have to test it.

                    "void main()"
                    "{"
                      "vec4 colour = texture2D (imageTexture, texturePos);"
                      "vec4 mask = vec4(1.0, 1.0, 1.0, alphaMultiplier);"
                      "gl_FragColor = colour * mask;"
                    "}"));

Ignoring the mask and alphaMultiplier for a moment, does your code render the imageTexture ?

Yes it does. Just returning the result of texture2D as well as the variant I posted above both render the texture.

I tried your version before but will try again. However on some forum posts I read that the result of the fragment shader should be premultiplied. (however now I am hesitant if that can actually be correct, since that would mean you cannot render semi transparency?)

I changed the shader to this as suggested by @jskarulis. However even though I am using the uniform now in the fragment shader, I still get an invalid uniform location.

prog.addFragmentShader (juce::OpenGLHelpers::translateFragmentShaderToV3 (
    "uniform sampler2D imageTexture;"
    "varying " JUCE_HIGHP " vec2 texturePos;"
    "uniform " JUCE_HIGHP " float alphaMultiplier;"
    "void main()"
    "{"
      "vec4 colour = texture2D (imageTexture, texturePos);"
      "vec4 mask = vec4 (1.0, 1.0, 1.0, alphaMultiplier);"
      "gl_FragColor = colour * mask;"
    "}"));

For the rendering:
I tried not setting alphaMultiplier and use 0.2 hardcoded instead… no transparency.

EDIT: I tried now to assign a static colour instead and I still see the original texture. That verifies that the fragment shader is not used at all. It also explains why the uniform location was not available.

I need to figure out now, how the fragment shader is actually activated. It is added to the ProgramBuilder. Like I wrote above, the code is 100% from

prog.addFragmentShader (juce::OpenGLHelpers::translateFragmentShaderToV3 (
    "uniform sampler2D imageTexture;"
    "varying " JUCE_HIGHP " vec2 texturePos;"
    "uniform " JUCE_HIGHP " float alphaMultiplier;"
    "void main()"
    "{"
      JUCE_HIGHP "vec4 textureColour = texture2D (imageTexture, texturePos);"
      JUCE_HIGHP "vec4 mask = (1.0, 1.0, 1.0, alphaMultiplier);"
      JUCE_HIGHP "vec4 colour = textureColour * mask;"
      "gl_FragColor = colour;"
    "}"));

Finally solved the riddle:

The ProgramBuilder uses a static const char programValueID[] to reference it. Because I copied the whole code, it was still using the old shader programs.

That explains, why @lorcan correctly suggested the uniform was not used, even though I added it to the shader. The whole shader was in fact never used.

It works now correctly after renaming the ProgramBuilder programValueID.

This is the FragmentShader I use:

prog.addFragmentShader (juce::OpenGLHelpers::translateFragmentShaderToV3 (
    "uniform sampler2D imageTexture;"
    "varying " JUCE_HIGHP " vec2 texturePos;"
    "uniform " JUCE_HIGHP " float alphaMultiplier;"
    "void main()"
    "{"
      "gl_FragColor = texture2D (imageTexture, texturePos) * vec4 (alphaMultiplier, alphaMultiplier, alphaMultiplier, alphaMultiplier);"
    "}"));

Thank you all for helping me out!

I believe you can initialize all 4 components of a vec4 like this:

vec4 mask = vec4(alphaMultiplier);

Have not checked yet but I think the JUCE_HIGHP is to ensure the floating points are high precision.

prog.addFragmentShader (juce::OpenGLHelpers::translateFragmentShaderToV3 (
    "uniform sampler2D imageTexture;"
    "varying " JUCE_HIGHP " vec2 texturePos;"
    "uniform " JUCE_HIGHP " float alphaMultiplier;"
    "void main()"
    "{"
      JUCE_HIGHP "vec4 mask = vec4(alphaMultiplier);"
      "gl_FragColor = texture2D (imageTexture, texturePos) * mask;"
    "}"));

Interesting, thank you. Looks a bit cleaner.

The JUCE_HIGHP is only used in OPEN_GLES. On desktop it is a define which is empty.

Try this out I think the alpha multiply works with just a scalar multiply:

https://learnopengl.com/Getting-started/Transformations

"void main()"
    "{"
      "gl_FragColor = texture2D (imageTexture, texturePos) * alphaMultiplier;"
    "}"));
2 Likes