Rendering the Scene#

This is the stage of the application where the triangle is actually rendered. The steps in this stage include clearing the buffer, calculating the transformation matrix for the vertex, describing what data is stored in the vertex buffer, and drawing the triangle.

Increment the current angle of the triangle.

_angle += 0.01f;

At the start of a frame, clear the image to tell OpenGL ES that whatever was drawn previously is finished with, and a new frame needs to be drawn. OpenGL ES will need to know what colour to set in the image’s place. glClearColor sets this value as four floating point values between 0.0 and 1.0 for the Red, Green, Blue and Alpha (r,g,b,a) channels respectively. Each value represents the intensity of the particular channel, with all 0.0 being transparent black, and all 1.0 being opaque white. Subsequent calls to glClear with the colour bit will clear the frame buffer to this value.

The functions glClearDepth and glClearStencil allow an application to do the same with depth and stencil values respectively.

glClearColor(0.00f, 0.70f, 0.67f, 1.0f);

glClear is used with the colour buffer to clear the colour. It can also be used to clear the depth or stencil buffer using GL_DEPTH_BUFFER_BIT or GL_STENCIL_BUFFER_BIT, respectively.

glClear(GL_COLOR_BUFFER_BIT);

This function gets the location of the shader variable which holds the transformation matrix using its name. The transformation matrix is a description of how the orientation of the vertices is altered by the vertex shader.

int matrixLocation = glGetUniformLocation(_shaderProgram, "transformationMatrix");

This is the aspect ratio of the screen. It is used to calculate the projection matrix.

float aspect = (_surfaceData.width / _surfaceData.height);

This calculates an orthographic projection matrix. A projection matrix converts the 3D co-ordinates of the object into 2D co-ordinates on the screen. An orthographic projection is a simple type of projection where no perspective is used.

This triangle is 2D anyway so the orthographic projection matrix is simply used to determine if any part of it is off-screen.

A glm function is used here to simplify this calculation.

glm::mat4 projMatrix = glm::ortho(-aspect, aspect, -1.0f, 1.0f);

The transformation matrix is the final matrix used to specify the orientation of the triangle on the screen. It combines a rotation matrix and the previously calculated projection matrix. The current angle variable of the triangle is updated at the start of each frame so a new rotation matrix has to be calculated.

Multiplying matrices concatenates their transformations, with a last-to-first order. Hence, in normal, more complicated cases, a final matrix will usually be a multiplication of (note the order) Projection x View x Translation x Rotation x Scale.

Again, the glm:rotate function simplifies this calculation.

glm::mat4 transformationMatrix = projMatrix * glm::rotate(_angle, glm::vec3(0.0f, 0.0f, 1.0f));

This passes the transformationMatrix to the shader using its location.

The vertex shader will use this matrix to calculate the final positions of the vertices.

glUniformMatrix4fv(matrixLocation, 1, GL_FALSE, glm::value_ptr(transformationMatrix));

Check for any GL Errors after a GL call.

if (!test-gl-error(_surfaceData), "glUniformMatrix4fv"))
{
    return false;
}

This enables the user-defined vertex attribute array. This was the attribute binding location mentioned in initializeShaders.

glEnableVertexAttribArray(_vertexArray);

This sets the vertex data to this attribute index. This attribute data describes the data format of the position information stored for each vertex. In this case there are three floats (x,y,z). The separation (or stride) between each vertex is also given. This is 5 * sizeof(GLfloat) rather than 3 * sizeof(GLfloat) because each vertex also includes two floats of texture positions.

glVertexAttribPointer(_vertexArray, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 0);

Check for any GL Errors after a GL call.

if (!test-gl-error(_surfaceData, "glVertexAttribPointer"))
{
    return false;
}

This enables the user-defined texture attribute array. Again, this binding location was mentioned in initializeShaders.

[glEnableVertexAttribArray](https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glEnableVertexAttribArray.xhtml)(_textureArray);

This function sets the texture data within the vertex buffer to this attribute index. The data format description of the texture data is similar to the one above except there are only two co-ordinates for each texture (u,v) and the data starts three elements into the buffer rather than at the beginning. This ensures Open GL ES will read the correct set of co-ordinates out of the buffer.

glVertexAttribPointer(_textureArray, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)(3 * sizeof(GLfloat)));
if (!test-gl-error(_surfaceData, "glVertexAttribPointer"))
{
    return false;
}

Finally, this function draws the triangle.

glDrawArrays is a draw call. It executes the shader program using the vertices and other states set by the developer. Draw calls are the functions which tell OpenGL ES when to actually draw something to the framebuffer given the current state.

glDrawArrays causes the vertices to be submitted sequentially from the position given by the first argument until it has processed “count” vertices. Other draw calls exist, notably glDrawElements which also accepts index data to allow the developer to specify that some vertices are accessed multiple times, without copying the vertex multiple times. Others include versions of the above that allow the developer to draw the same object multiple times with slightly different data. There is also a version of glDrawElements which allows a developer to restrict the actual indices accessed.

glDrawArrays(GL_TRIANGLES, 0, 3);
if (!test-gl-error(_surfaceData, "glDrawArrays"))
{
    return false;
}

The last thing that needs to be done is invalidate the contents of the specified buffers for the framebuffer to allow the implementation further optimisation opportunities.

The following is taken from https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_discard_framebuffer.txt.

Some OpenGL ES implementations cache framebuffer images in a small pool of fast memory. Before rendering, these implementations must load the existing contents of one or more of the logical buffers (colour, depth, stencil, etc.) into this memory. After rendering, some or all of these buffers are likewise stored back to external memory so their contents can be used again in the future. In many applications, some or all of the logical buffers are cleared at the start of rendering. If so, the effort to load or store those buffers is wasted.

Even without this extension, if a frame of rendering begins with a full-screen Clear, an OpenGL ES implementation may optimize away the loading of framebuffer contents prior to rendering the frame. With this extension, an application can use DiscardFramebufferEXT to signal that framebuffer contents will no longer be needed. In this case an OpenGL ES implementation may also optimise away the storing back of framebuffer contents after rendering the frame.

if (isGlExtensionSupported("GL_EXT_discard_framebuffer"))
{
    GLenum invalidateAttachments[2];
    invalidateAttachments[0] = GL_DEPTH_EXT;
    invalidateAttachments[1] = GL_STENCIL_EXT;

    glDiscardFramebufferEXT(GL_FRAMEBUFFER, 2, &invalidateAttachments[0]);
    if (!test-gl-error(_surfaceData, "glDiscardFramebufferEXT"))
    {
        return false;
    }
}

return true;