0
votes

I'm trying to do something utterly simple and I'm getting incredibly frustrated. I've set up a trivial vertex shader since apparently you need one to get OpenGL to do anything nowadays or the dreaded deprecation gods frown at you or something:

#version 110

uniform mat4 scene_matrix;
attribute vec4 a_position;

void main()
{
    gl_Position = scene_matrix * a_position;
}

Wonderful. After mucking around for 10 hours to create a suitable matrix I pass it into the shader with glUniformMatrix4 and it works gloriously. That is, until I want to render more than one THING.

So my goal is: For each object in the scene, I calculate the appropriate world matrix based on the coordinates of the object, call glUniformMatrix4 to tell the vertex shader about the matrix, call glBegin(), then draw the stupid object, and call glEnd(). Unfortunately it's pathetically drawing all the objects in the same stupid place (the place of the last object). Clearly it's lazily buffering things and not running the vertex shader until the end, when it has forgotten all about the previous matrices. But how do I tell it not to do that?

FloatBuffer matrixBuf = ByteBuffer.allocateDirect(16 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
for (int i = 0; i < 500; i++) {
    matrixBuf.rewind();
    matrices.world.translate(0.1, 0.1, 0.5);
    matrices.calc();
    matrices.combined.put(matrixBuf);
    matrixBuf.rewind();
    glUniformMatrix4(glGetUniformLocation(programId, "scene_matrix"), false, matrixBuf);

    glBegin(GL_TRIANGLES);
    {
        //final int T = 1;
        final float z = 0f;
        final float R = 0.5f;
        //glVertexAttrib2f(T, 0, 1);
        glVertex3f(-R, -R, z);

        //glVertexAttrib2f(T, 1, 0);
        glVertex3f(R, R, z);

        //glVertexAttrib2f(T, 0, 0);
        glVertex3f(-R, R, z);

        /*//glVertexAttrib2f(T, 0, 1);
        glVertex3f(-R, -R, z);

        //glVertexAttrib2f(T, 1, 1);
        glVertex3f(R, -R, z);

        //glVertexAttrib2f(T, 1, 0);
        glVertex3f(R, R, z);*/
    }
    glEnd();

    //glFlush(); // without this, all the triangles are in the same place
}

I discovered that calling glFlush() after every glEnd() solves the problem. However, this is causing a performance problem. I'm drawing only 500 objects (each a single pitiful triangle) and it is already maxing out the CPU and there is capacitor whine coming out of the computer. It feels wrong. I'm sure glFlush() must be overkill.

I found that relinking the program with glLinkProgram(programId); after every glUniformMatrix4 can also solve the problem, but it's an order of magnitude slower still.

I've looked everywhere. I just want to know what the name of the function that is used to tell it to get on and run the vertex shader now so then I can reconfigure the uniforms for the next object.

Or is this not what I'm supposed to be doing? Should I give up on the vertex pipeline, do all matrix transformation of vertices on the Java side and pass in already transformed vertices? Should I surrender to the deprecation devil and use the legacy OpenGL matrix stack to see if that's more cooperative? Any help is appreciated.

3
New uniform values will be used for the next draw call, nothing you need to do about it. This looks like a bug in the OpenGL implementation.Reto Koradi
@RetoKoradi: is it even required to synchronize the programmable API calls wrt to the fixed-function one?Yakov Galka
@RetoKoradi If that's true I'd accept it as an answer. That's what I'm asking, and I couldn't find it out from the documentation.Boann
@Boann: Yes, calling glUniform… immediately updates the uniform value for all subsequent drawing operations. There's not need for explicit synchronization if you update uniforms that way.datenwolf

3 Answers

3
votes

The first big mistake you do is that you use glBegin/glEnd. That has been deprecated together with the fixed function pipeline.

Alas for why you see the objects all being drawn in the same place: Because your transformation matrix is invariant under the loop variable, i.e. the iteration of the loop has no influence on the value of your transformation matrix: This block here:

matrixBuf.rewind();
matrices.world.translate(0.1, 0.1, 0.5);
matrices.calc();
matrices.combined.put(matrixBuf);
matrixBuf.rewind();

Would have to depend on i somehow, or matrices.combined would be actually combined with the translation, but the methiod name .put makes it clear, that it is simply replaced.

1
votes

Many state changes requires your to bind objects before they are visible to rendering pipeline.

For shaders binding would mean glUseProgram. I don't remember what spec says about uniforms and reusing the shader.

Using glDrawArrays is simple. You just need to setup vertex data into an array. The enable and set glVertexAttribPointer. You don't even have to use buffer objects in simple example cases. You can think glEnableVertexAttribArray as glBegin. Then putting data to vertex array as calls to glVertex*(). glVertexAttribPointer and glDraw*() can be tough as glEnd.

Optimizations over simple DrawArrays is to use indexes with DrawElements to avoid duplicated vertices in data. Then putting both vertices and indices to buffer objects makes everything fast.

Bonus: This case looks a bit like you could skip matrix to simplify your uniforms to translation vector only.

0
votes

So the answer according to the comments is that: there is no such function call; this is supposed to just naturally work.

Then why doesn't it? Lord knows. Could be a buggy driver, could be I'm a goof, could be both. In any case, the problem immediately and totally disappears if I use glDrawArrays or glDrawElements instead of fixed-function pipeline calls like glVertex3. (Yes that's definitely the only thing I'm changing to toggle it between working and not working.) Not a nice experience for someone who had been trying to start at the shallow end of things before drowning under the full weight of all the poorly-documented-unless-you-already-know-what-they-do APIs at once.

I found this nice Stack Overflow post that summarizes just what the hell these vertex specification functions do: https://stackoverflow.com/a/8705304

For future reference here is the drawing code that's working:

A vertex shader:

#version 110

uniform mat4 scene_matrix;
attribute vec4 a_position;
attribute vec2 a_texcoord;
varying vec2 v_texcoord;

void main()
{
    gl_Position = scene_matrix * a_position;

    // pass texture coordinates on to fragment shader
    v_texcoord = a_texcoord;
}

A fragment shader:

#version 110

uniform sampler2D texture;
varying vec2 v_texcoord;

void main()
{
    gl_FragColor = texture2D(texture, v_texcoord);
    //gl_FragColor = vec4(1, 0, 0, 1);
}

Code that draws a THING:

// Define a shape (a square)
// First 3 numbers on each line are vertex coords, next 2 are corresponding tex coords
final float R = 0.5f;
float[] vertices = {
    -R, -R, 0, 0, 1,
    +R, +R, 0, 1, 0,
    -R, +R, 0, 0, 0,
    -R, -R, 0, 0, 1,
    +R, -R, 0, 1, 1,
    +R, +R, 0, 1, 0,
};

// Create and bind a buffer object
int bufferId = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, bufferId);

// Just Java fluff: local buffer used for transferring data to the native method
FloatBuffer vertices1 = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vertices1.put(vertices);
vertices1.rewind();

// Load the vertices into the proper buffer
glBufferData(GL_ARRAY_BUFFER, vertices1, GL_STREAM_DRAW);

// Define layout of data in buffer:
// First, vertex coords (attrib "0"):
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 5 * 4, 0 * 4);
// The last 2 params to glVertexAttribPointer are stride and offset.
// Stride tells the total number of bytes between the start of one vertex
// and the start of the next, 5 * 4 because the data rows above have 5 elements
// and each element is a 4-byte float.
// Offset tells the byte offset of the start of the first element in the buffer,
// 0 * 4 because our coord data begins right at the beginning of the buffer.

// Now, tex coords (attrib "1"):
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 5 * 4, 3 * 4);
// Stride is the same as above, but need non-zero offset because the tex
// coords start after the 3 vertex coords in each row of data in the buffer.

// Call glDrawArrays as many times as you want to repeatedly,
// possibly with different uniform values, and with no need
// for glBegin(), glEnd(), or repeated glFlush().
glDrawArrays(GL_TRIANGLES, 0, vertices.length);

// Cleanup
glDeleteBuffers(bufferId);