0
votes

I'm writing an app on iOS allows drawing free style (using finger) and drawing image on screen. I use OpenGL ES to implement. I have 2 functions, one is drawing free style, one is drawing texture

--- Code drawing free style

- (void)drawFreeStyle:(NSMutableArray *)pointArray {

        //Prepare vertex data
        .....

        // Load data to the Vertex Buffer Object
        glBindBuffer(GL_ARRAY_BUFFER, vboId);
        glBufferData(GL_ARRAY_BUFFER, vertexCount*2*sizeof(GLfloat), vertexBuffer, GL_DYNAMIC_DRAW);

        glEnableVertexAttribArray(ATTRIB_VERTEX);
        glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 0, 0);

        **GLuint a_ver_flag_drawing_type = glGetAttribLocation(program[PROGRAM_POINT].id, "a_drawingType");
        glVertexAttrib1f(a_ver_flag_drawing_type, 0.0f);

        GLuint u_fra_flag_drawing_type = glGetUniformLocation(program[PROGRAM_POINT].id, "v_drawing_type");
        glUniform1f(u_fra_flag_drawing_type, 0.0);**

        glUseProgram(program[PROGRAM_POINT].id);
        glDrawArrays(GL_POINTS, 0, (int)vertexCount);

        // Display the buffer
        glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);
        [context presentRenderbuffer:GL_RENDERBUFFER];
}

--- Code drawing texture

- (void)drawTexture:(UIImage *)image atRect:(CGRect)rect {

    GLuint a_ver_flag_drawing_type = glGetAttribLocation(program[PROGRAM_POINT].id, "a_drawingType");

    GLuint u_fra_flag_drawing_type = glGetUniformLocation(program[PROGRAM_POINT].id, "v_drawing_type");

    GLuint a_position_location = glGetAttribLocation(program[PROGRAM_POINT].id, "a_Position");
    GLuint a_texture_coordinates_location = glGetAttribLocation(program[PROGRAM_POINT].id, "a_TextureCoordinates");
    GLuint u_texture_unit_location = glGetUniformLocation(program[PROGRAM_POINT].id, "u_TextureUnit");

    glUseProgram(PROGRAM_POINT);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texName);
    glUniform1i(u_texture_unit_location, 0);
    glUniform1f(u_fra_flag_drawing_type, 1.0);

    const float textrect[] = {-1.0f, -1.0f, 0.0f, 0.0f,
        -1.0f,  1.0f, 0.0f, 1.0f,
        1.0f, -1.0f, 1.0f, 0.0f,
        1.0f,  1.0f, 1.0f, 1.0f};

    glBindBuffer(GL_ARRAY_BUFFER, vboId);
    glBufferData(GL_ARRAY_BUFFER, sizeof(textrect), textrect, GL_STATIC_DRAW);

    glVertexAttrib1f(a_ver_flag_drawing_type, 1.0f);

    glVertexAttribPointer(a_position_location, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(0));
    glVertexAttribPointer(a_texture_coordinates_location, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));

    glEnableVertexAttribArray(a_ver_flag_drawing_type);
    glEnableVertexAttribArray(a_position_location);
    glEnableVertexAttribArray(a_texture_coordinates_location);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];
}

Notice 2 variables a_ver_flag_drawing_type (attribute) and u_fra_flag_drawing_type (uniform). They're use for setting flags on vertex shader and fragment shader to determine drawing free style or texture on both files

--- Vertex shader

//Flag
attribute lowp float a_drawingType;

//For drawing
attribute vec4 inVertex;

uniform mat4 MVP;
uniform float pointSize;
uniform lowp vec4 vertexColor;

varying lowp vec4 color;

//For texture
attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;

varying vec2 v_TextureCoordinates;

void main()
{
    if (abs(a_drawingType - 1.0) < 0.0001) {
        //Draw texture        
        v_TextureCoordinates = a_TextureCoordinates;
        gl_Position = a_Position;
    } else {
        //Draw free style
        gl_Position = MVP * inVertex;
        gl_PointSize = pointSize;
        color = vertexColor;
    }

}

--- Fragment shader

precision mediump float;

uniform sampler2D texture;
varying lowp vec4 color;

uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;

uniform lowp float v_drawing_type;

void main()
{
    if (abs(v_drawing_type - 1.0) < 0.0001) {
        //Draw texture
        gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
    } else {
        //Drawing free style
        gl_FragColor = color * texture2D(texture, gl_PointCoord);
    }

}

My idea is setting these flags from drawing code at drawing time. Attribute a_drawingType is used for vertex shader and Uniform v_drawing_type is used for fragment shader. Depending these flags to know draw free style or texture.

But if I run independently, each time just one type (if run drawing free style, comment code config drawing texture on vertex shader and fragment shader file and vice versa) it can draw as I want. If I combine them, it not only can't draw but also makes app crash as

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

I'm new for OpenGL ES and GLSL language, so I'm not sure my thinking about setting flags like that is right or wrong. Can anyone help me

4

4 Answers

2
votes

So why don't you just build 2 seperate shader programs and useProgram() on one of them, instead of sending flag values to GL and making expensive conditional branch in vertex and especially fragment shader?

1
votes

Attributes are per-vertex, uniforms are per-shader program.

You might see a crash if you supplied only one value for an attribute then asked OpenGL to draw, say, 100 points. In that case OpenGL is going to do an out-of-bounds array access when it attempts to fetch the attributes for vertices 2–100.

It'd be more normal to use two separate programs. Conditionals are very expensive on GPUs because GPUs try to maintain one program counter while processing multiple fragments. They're SIMD units. Any time the evaluation of an if differs between two neighbouring fragments you're probably reducing parallelism. The idiom is therefore not to use if statements where possible.

If you switch to a uniform there's a good chance you won't lose any performance through absent parallelism because the paths will never diverge. Your GLSL compiler may even be smart enough to recompile the shader every time you reset the constant, effectively performing constant folding. But you'll be paying for the recompilation every time.

If you just had two programs and switched between them you wouldn't pay the recompilation fee.

It's not completely relevant in this case but e.g. you'd also often see code like:

if (abs(v_drawing_type - 1.0) < 0.0001) {
    //Draw texture
    gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
} else {
    //Drawing free style
    gl_FragColor = color * texture2D(texture, gl_PointCoord);
}

Written more like:

gl_FragColor = mix(texture2D(u_TextureUnit, v_TextureCoordinates),
                   color * texture2D(texture, gl_PointCoord),
                   v_drawing_type);

... because that avoids the conditional entirely. In this case you'd want to adjust the texture2D so that both calls were identical, and probably factor them out of the call, to ensure you don't end up always doing two samples instead of one.

1
votes

The previously posted answers explain certain pieces, but an important part is missing in all of them. It is perfectly legal to specify a single attribute value that is applied to all vertices in a draw call. What you did here was basically valid:

glVertexAttrib1f(a_ver_flag_drawing_type, 1.0f);

The direct problem was this call that followed shortly after:

glEnableVertexAttribArray(a_ver_flag_drawing_type);

There are two main ways to specify the value of a vertex attribute:

  1. Use the current value, as specified in your case by glVertexAttrib1f().
  2. Use values from an array, as specified with glVertexAttribPointer().

You select which of the two options is used for any given attribute by enabling/disabling the array, which is done by calling glEnableVertexAttribArray()/glDisableVertexAttribArray().

In the posted code, the vertex attribute was specified as only a current value, but the attribute was then enabled to fetch from an array with glEnableVertexAttribArray(). This conflict caused the crash, because the attribute values would have been fetched from an array that was never specified. To use the specified current value, the call simply has to be changed to:

glDisableVertexAttribArray(a_ver_flag_drawing_type);

Or, if the array was never enabled, the call could be left out completely. But just in case another part of the code might have enabled it, it's safer to disable it explicitly.

As a side note, the following statement sequence from the first draw function also looks suspicious. glUniform*() sets value on the active program, so this will set a value on the previously active program, not the one specified in the second statement. If you want to set the value on the new program, the order of the statements has to be reversed.

glUniform1f(u_fra_flag_drawing_type, 0.0);
glUseProgram(program[PROGRAM_POINT].id);

On the whole thing, I think there are at least two approaches that are better than the one chosen:

  1. Use separate shader programs for the two different types of rendering. While using a single program with switchable behavior is a valid option, it looks artificial, and using separate programs seems much cleaner.
  2. If you want to stick with a single program, use a single uniform to do the switching, instead of using an attribute and a uniform. You could use the one you already have, but you might just as well make it a boolean while you're at it. So in both the vertex and fragment shader, use the same uniform declaration:

    uniform bool v_use_texture;
    

    Then the tests become:

    if (v_use_texture) {
    

    Getting the uniform location is the same as before, and you can set the value, which will the be available in both the vertex and fragment shader, with one of:

    glUniform1i(loc, 0);
    glUniform1i(loc, 1);
    
0
votes

I found the problem, just change variable a_drawingType from Attribute to Uniform then use glGetUniformLocation and glUniform1f to get index and pass value. I think Attribute will pass for any vertex so use uniform to pass once.