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
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.