c++visual-c++sdl-2opengl-es-3.0google-angle

Draw nothing for an OpenGL ES 3 + SDL2 + ANGLE + MSVC


I've got a problem following a tutorial aiming to teach how to use OpenGL ES 3.x on desktop environment primarily on Windows.

As stated in the title of my question I'm coding using MSVC, SDL2 and I link my program against ANGLE's libraries to emulate OpenGL ES 3.

The following code aims to draw a simple triangle but does not draw anything at all. In fact, the only thing I can do is set the clear color and clear the color buffer.

I do not have any error subsequent to any gl* function call I make. Beside, those functions are all loaded using the sdl get proc address capability.

Here is the initialisation code (sdl window and opengl context) that serves as entrypoint:

#include <SDL.h>
#include <cstdlib>

#include "../tuto_1/tuto_1.hpp"
#include "../tuto_2/tuto_2.hpp"

const unsigned int DISP_WIDTH = 640;
const unsigned int DISP_HEIGHT = 480;

auto SDL_main(int, char* []) -> int
{
    SDL_Window* window = nullptr;
    SDL_GLContext context = nullptr;

    if (SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        SDL_Log("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());

        return EXIT_FAILURE;
    }

    atexit(SDL_Quit);

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    window = SDL_CreateWindow("OpenGL ES 3 + SDL2 tutorial",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        DISP_WIDTH, DISP_HEIGHT,
        SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);

    if (window == nullptr)
    {
        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
            "Error",
            "Could not create the main window",
            nullptr);

        return EXIT_FAILURE;
    }

    context = SDL_GL_CreateContext(window);

    if (context == nullptr)
    {
        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
            "Error",
            "Could not create an OpenGL context",
            nullptr);

        return EXIT_FAILURE;
    }

    return tuto_1(window),
        tuto_2(window);
}

This code initiate context and somehow invoke tuto_1 and tuto_2 functions.

tuto_1 is fine, as it's only a matter of changing background, it works well.

Below is the code of the tuto_2.cpp that won't draw anything at all:

#include "tuto_2.hpp"
#include "shader.hpp"
#include "vbo.hpp"

#include <cstdlib>
#include <SDL.h>

struct Vertex
{
    float position[2];
};

auto tuto_2(SDL_Window* const window) -> int
{
    GLuint shaderProg = shaderProgLoad("Simple2D.vert", "Simple2D.frag");

    if (shaderProg == 0)
        return EXIT_FAILURE;

    auto glUseProgram = reinterpret_cast<PFNGLUSEPROGRAMPROC>(SDL_GL_GetProcAddress("glUseProgram"));

    if (glUseProgram == nullptr)
    {
        SDL_Log("Could not load the glUseProgram function");

        return EXIT_FAILURE;
    }

    glUseProgram(shaderProg);

    auto glGetIntegerv = reinterpret_cast<PFNGLGETINTEGERVPROC>(SDL_GL_GetProcAddress("glGetIntegerv"));

    if (glGetIntegerv == nullptr)
    {
        SDL_Log("Could not load the glGetIntegerv function");

        return EXIT_FAILURE;
    }

    GLint program;
    glGetIntegerv(GL_CURRENT_PROGRAM, &program);

    if (program == 0)
    {
        SDL_Log("Could not get the current program or no program in use");

        return EXIT_FAILURE;
    }

    const Vertex vertices[] =
    {
        { .0f , -.9f },
        { .9f , .9f },
        { -.9f, .9f }
    };

    GLsizei vertSize = sizeof(vertices[0]);
    GLsizei numVertices = sizeof(vertices) / vertSize;

    GLuint triangleVBO = vboCreate(vertices, numVertices);

    if (triangleVBO == 0)
    {
        shaderProgDestroy(shaderProg);

        return EXIT_FAILURE;
    }

    const GLuint positionIdx = 0;

    // auto glBindBuffer = reinterpret_cast<PFNGLBINDBUFFERPROC>(SDL_GL_GetProcAddress("glBindBuffer"));

    // if (glBindBuffer == nullptr)
    // {
    //  SDL_Log("Could not load the glBindBuffer function");

    //  return EXIT_FAILURE;
    // }

    // glBindBuffer(GL_ARRAY_BUFFER, triangleVBO);

    auto glVertexAttribPointer = reinterpret_cast<PFNGLVERTEXATTRIBPOINTERPROC>(SDL_GL_GetProcAddress("glVertexAttribPointer"));

    if (glVertexAttribPointer == nullptr)
    {
        SDL_Log("Could not load the glVertexAttribPointer function");

        return EXIT_FAILURE;
    }

    auto glGetError = reinterpret_cast<PFNGLGETERRORPROC>(SDL_GL_GetProcAddress("glGetError"));

    if (glGetError == nullptr)
    {
        SDL_Log("Could not load the glGetError function");

        return EXIT_FAILURE;
    }

    glVertexAttribPointer(positionIdx, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), static_cast<const void*>(0));
    const auto vertexAttribPointerError = glGetError();

    if (vertexAttribPointerError != GL_NO_ERROR)
    {
        SDL_Log("Error while setting up vertex attribute pointers");

        return EXIT_FAILURE;
    }

    auto glEnableVertexAttribArray = reinterpret_cast<PFNGLENABLEVERTEXATTRIBARRAYPROC>(SDL_GL_GetProcAddress("glEnableVertexAttribArray"));

    if (glEnableVertexAttribArray == nullptr)
    {
        SDL_Log("Could not load the glEnableVertexAttribArray function");

        return EXIT_FAILURE;
    }

    glEnableVertexAttribArray(positionIdx);
    const auto enableVertexAttribPointerError = glGetError();

    if (enableVertexAttribPointerError != GL_NO_ERROR)
    {
        SDL_Log("Error while enabling vertex attribute pointers");

        return EXIT_FAILURE;
    }

    auto glDrawArrays = reinterpret_cast<PFNGLDRAWARRAYSPROC>(SDL_GL_GetProcAddress("glDrawArrays"));

    if (glDrawArrays == nullptr)
    {
        SDL_Log("Could not load the glDrawArrays function");

        return EXIT_FAILURE;
    }
    auto glClear = reinterpret_cast<PFNGLCLEARPROC>(SDL_GL_GetProcAddress("glClear"));

    if (glClear == nullptr)
    {
        SDL_Log("Could not load the glClear function");

        return EXIT_FAILURE;
    }

    bool quit = false;

    while (!quit)
    {
        SDL_Event event;

        if (SDL_WaitEvent(&event) != 0)
        {
            if (event.type == SDL_QUIT)
            {
                vboFree(triangleVBO);
                shaderProgDestroy(shaderProg);
                quit = true;
            }
        }

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDrawArrays(GL_TRIANGLES, 0, numVertices);
        const auto drawArraysError = glGetError();

        if (drawArraysError != GL_NO_ERROR)
        {
            SDL_Log("Error while drawing arrays");

            return EXIT_FAILURE;
        }

        SDL_GL_SwapWindow(window);
    }

    return EXIT_SUCCESS;
}

static GLuint vboCreate(const Vertex* vertices, GLuint numVertices)
{
    GLuint vbo;
    const unsigned int nBuffer = 1;

    auto glGenBuffers = reinterpret_cast<PFNGLGENBUFFERSPROC>(SDL_GL_GetProcAddress("glGenBuffers"));

    if (glGenBuffers == nullptr)
    {
        SDL_Log("Could not load the glGenBuffer function");

        return 0;
    }

    glGenBuffers(nBuffer, &vbo);

    auto glBindBuffer = reinterpret_cast<PFNGLBINDBUFFERPROC>(SDL_GL_GetProcAddress("glBindBuffer"));

    if (glBindBuffer == nullptr)
    {
        SDL_Log("Could not load the glBindBuffer function");

        return 0;
    }

    glBindBuffer(GL_ARRAY_BUFFER, vbo);

    auto glBufferData = reinterpret_cast<PFNGLBUFFERDATAPROC>(SDL_GL_GetProcAddress("glBufferData"));

    if (glBufferData == nullptr)
    {
        SDL_Log("Could not load the glBufferData function");

        return 0;
    }

    auto glGetError = reinterpret_cast<PFNGLGETERRORPROC>(SDL_GL_GetProcAddress("glGetError"));

    if (glGetError == nullptr)
    {
        SDL_Log("Could not load the glGetError function");

        return 0;
    }

    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(Vertex), vertices, GL_STATIC_DRAW);
    GLenum err = glGetError();

    if (err != GL_NO_ERROR)
    {
        SDL_Log("Could not create an array buffer. Error: `%u`\n", err);

        vboFree(vbo);

        vbo = 0;
    }

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    return vbo;
}

static void vboFree(GLuint vbo)
{
    auto glDeleteBuffers = reinterpret_cast<PFNGLDELETEBUFFERSPROC>(SDL_GL_GetProcAddress("glDeleteBuffers"));

    if (glDeleteBuffers == nullptr)
    {
        SDL_Log("Could not load the glDeleteBuffers function");

        return;
    }

    glDeleteBuffers(1, &vbo);
}

This code relies also on shader.hpp too to create one vertex and one fragment shader:

#ifdef _MSC_VER
#pragma warning(disable:4996)
#endif

#include "shader.hpp"

#include <memory>
#include <cstdio>
#include <cstdlib>
#include <SDL.h>
#include <SDL_opengles2.h>
#include <GLES3/gl3.h>
#include <filesystem>

static size_t fileGetlength(FILE* file)
{
    size_t length;
    long curPos = ftell(file);

    fseek(file, curPos, SEEK_END);

    length = ftell(file);

    fseek(file, curPos, SEEK_SET);

    return length;
}

static void shaderDestroy(GLuint shader)
{
    auto glDeleteShader = reinterpret_cast<PFNGLDELETESHADERPROC>(SDL_GL_GetProcAddress("glDeleteShader"));

    if (glDeleteShader == nullptr)
    {
        SDL_Log("Can't load the glDeleteShader function");

        return;
    }

    glDeleteShader(shader);
}

static GLuint shaderLoad(const char* fileName, GLenum shaderType)
{
    const auto exe_base_path = SDL_GetBasePath();
    const auto filePath = std::filesystem::absolute(std::filesystem::path{ exe_base_path } / fileName);

    FILE* file = fopen(filePath.string().c_str(), "r");

    if (file == nullptr)
    {
        SDL_Log("Can't open file `%s`", fileName);

        return 0;
    }

    size_t length = fileGetlength(file);

    auto shaderSrc = std::make_unique < GLchar[] >(length + 1);

    fread(shaderSrc.get(), 1, length, file);

    fclose(file);

    auto glCreateShader = reinterpret_cast<PFNGLCREATESHADERPROC>(SDL_GL_GetProcAddress("glCreateShader"));

    if (glCreateShader == nullptr)
    {
        SDL_Log("Can't load the glCreateShader function");

        return 0;
    }

    GLuint shader = glCreateShader(shaderType);

    auto glShaderSource = reinterpret_cast<PFNGLSHADERSOURCEPROC>(SDL_GL_GetProcAddress("glShaderSource"));

    if (glCreateShader == nullptr)
    {
        SDL_Log("Can't load the glShaderSource function");

        return 0;
    }

    const auto* const v = shaderSrc.get();
    const auto* const pv = &v;
    glShaderSource(shader, 1, pv, nullptr);

    auto glCompileShader = reinterpret_cast<PFNGLCOMPILESHADERPROC>(SDL_GL_GetProcAddress("glCompileShader"));

    if (glCompileShader == nullptr)
    {
        SDL_Log("Can't load the glCompileShader function");

        return 0;
    }

    glCompileShader(shader);

    auto glGetShaderiv = reinterpret_cast<PFNGLGETSHADERIVPROC>(SDL_GL_GetProcAddress("glGetShaderiv"));

    if (glGetShaderiv == nullptr)
    {
        SDL_Log("Can't load the glGetShaderiv function");

        return 0;
    }

    GLint compileSucceeded = GL_FALSE;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compileSucceeded);

    if (!compileSucceeded)
    {
        SDL_Log("Error while compiling the `%s` shader file\n", fileName);

        GLint logLength = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);

        auto errLog = std::make_unique<GLchar[]>(logLength);

        auto glGetShaderInfoLog = reinterpret_cast<PFNGLGETSHADERINFOLOGPROC>(SDL_GL_GetProcAddress("glGetShaderInfoLog"));

        if (glGetShaderInfoLog == nullptr)
        {
            SDL_Log("Can't load the glGetShaderInfoLog function");

            return 0;
        }

        if (errLog)
        {
            glGetShaderInfoLog(shader, logLength, &logLength, errLog.get());

            SDL_Log("shader info log: `%s`", errLog.get());
        }

        shaderDestroy(shader);

        return 0;
    }

    return shader;
}

GLuint shaderProgLoad(const char* vertFileName, const char* fragFileName)
{
    GLuint vertShader = shaderLoad(vertFileName, GL_VERTEX_SHADER);

    if (vertShader == 0)
    {
        SDL_Log("Cannot load the `%s` vertex shader file", vertFileName);

        return 0;
    }

    GLuint fragShader = shaderLoad(fragFileName, GL_FRAGMENT_SHADER);

    if (fragShader == 0)
    {
        SDL_Log("Cannot load the `%s` fragment shader file", vertFileName);

        shaderDestroy(vertShader);

        return 0;
    }

    auto glCreateProgram = reinterpret_cast<PFNGLCREATEPROGRAMPROC>(SDL_GL_GetProcAddress("glCreateProgram"));

    if (glCreateProgram == nullptr)
    {
        SDL_Log("Could not load the glCreateProgram function");

        return 0;
    }

    auto shaderProg = glCreateProgram();

    if (!shaderProg)
    {
        SDL_Log("Could not create a shader program");

        return 0;
    }

    auto glAttachShader = reinterpret_cast<PFNGLATTACHSHADERPROC>(SDL_GL_GetProcAddress("glAttachShader"));

    if (glAttachShader == nullptr)
    {
        SDL_Log("Could not load the glAttachShader function");

        return 0;
    }

    glAttachShader(shaderProg, vertShader);
    glAttachShader(shaderProg, fragShader);

    auto glLinkProgram = reinterpret_cast<PFNGLLINKPROGRAMPROC>(SDL_GL_GetProcAddress("glLinkProgram"));

    if (glLinkProgram == nullptr)
    {
        SDL_Log("Could not load the glLinkProgram function");

        return 0;
    }

    glLinkProgram(shaderProg);

    GLint linkingSucceeded = GL_FALSE;

    auto glGetProgramiv = reinterpret_cast<PFNGLGETPROGRAMIVPROC>(SDL_GL_GetProcAddress("glGetProgramiv"));

    if (glGetProgramiv == nullptr)
    {
        SDL_Log("Could not load the glGetProgramiv function");

        return 0;
    }

    glGetProgramiv(shaderProg, GL_LINK_STATUS, &linkingSucceeded);

    if (!linkingSucceeded)
    {
        SDL_Log("Shader program linking failed. (vertex shader: %s, fragment shader: %s\n)", vertFileName, fragFileName);

        GLint logLength = 0;
        glGetProgramiv(shaderProg, GL_INFO_LOG_LENGTH, &logLength);

        auto glGetProgramInfoLog = reinterpret_cast<PFNGLGETPROGRAMINFOLOGPROC>(SDL_GL_GetProcAddress("glGetProgramInfoLog"));

        if (glGetProgramInfoLog == nullptr)
        {
            SDL_Log("Could not load the glGetProgramInfoLog function");

            return 0;
        }

        auto errLog = std::make_unique<GLchar[]>(logLength + 1);
        glGetProgramInfoLog(shaderProg, logLength, &logLength, errLog.get());

        SDL_Log("Shader linking error: `%s`", errLog.get());
    }

    shaderDestroy(vertShader);
    shaderDestroy(fragShader);

    return shaderProg;
}

void shaderProgDestroy(GLuint shaderProg)
{
    auto glDeleteProgram = reinterpret_cast<PFNGLDELETEPROGRAMPROC>(SDL_GL_GetProcAddress("glDeleteProgram"));

    if (glDeleteProgram == nullptr)
    {
        SDL_Log("Could not load the glDeleteProgram function");

        return;
    }

    glDeleteProgram(shaderProg);
}

Below is the fragment shader :

#version 300 es

#ifdef GL_ES
precision highp float;
#endif

in vec4 colour;
out vec4 fragColour;

void main()
{
  fragColour = colour;
}

And here's the vertex shader:

#version 300 es

in vec2 vertPos;
out vec4 colour;

const vec4 white = vec4(1.0);

void main()
{
  colour = white;

  gl_Position = vec4(vertPos, 0.0, 1.0);
}

I do not know why nothing is drawing on the screen, though the background color is correctly setup.

I initially thought the issue could be the ANGLE library so I linked using my graphic card vendor libraries instead, but no change occurred.

Initially I also had the issue even with the background color in tuto_1. The issue was related with using directly glClear as exposed in the header. I'd had to dynamically load this function using SDL_GL_GetProcAddress and the problem had vanished.

I didn't find any more direct function call exposed from headers for tuto_2 thud I think the issue is not based on this.


edit 1

If it matters, I can create a public github repository with the complete code as well as a readme helping the setup of the thing. Let me know in comments if such a thing could be of interest for you.

I also forgotten to specify the source of this tutorial: GLES3-and-SDL2-Turorials.pdf

However, the code is old, totally broken and required me to fix a lot of things in order to make it functional. I provide this link as-is, to give more context.


edit 2

I updated the code in the question, added a missing glEnableVertexAttrib call and a bunch of glGetError calls.

No errors are captured and the vertex attribute enabling does not change anything.

I also ensured the shader program is in use after having called glUseProgram


Solution

  • Uncommenting the code in the second code block resolve my issue. I must call glBindBuffer to ensure OpenGL's subsequent calls to glVertexAttribPointer() should use triangleVBO. I initially thought that binding a buffer was necessary only to copy data from application to the GPU.