mike-shah-opengl-series/src/main.cc

326 lines
9.7 KiB
C++

#include "glad/glad.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_error.h>
#include <SDL2/SDL_video.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_keycode.h>
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
#define WINDOW_WIDTH 1280
#define WINDOW_HEIGHT 720
enum exit_codes : int {
EXIT_CODE_SUCCESS,
EXIT_CODE_SDL_INIT_FAILED,
EXIT_CODE_WINDOW_CREATION_FAILED,
EXIT_CODE_OPENGL_CONTEXT_FAILED,
EXIT_CODE_GLAD_LOADER_FAILED,
};
struct App {
SDL_Window *window;
SDL_GLContext context;
SDL_Event event;
// Defines the different attributes in the vertex data and how to access them.
// Think of it as a specification or a C struct that defines the types of data stored for each
// vertex.
GLuint vao;
// The buffer that contains all the vertex data
// e.g. assume 3 vertices with position, colour and uv, the buffer would look like this
// -----------------------------------------------------------------------------------
// | position1 | colour1 | uv1 | position2 | colour2 | uv2 | position3 | colour3 | uv3 |
// -----------------------------------------------------------------------------------
GLuint vbo;
// Holds the indices of the vertices that draw each triangle. Each triangle constitutes what
// OpenGL refers to as element, hence the name Element Buffer Object
GLuint ebo;
float offset_modifier;
// offset value passed to shaders as uniform
float offset;
// offset uniform index
GLint u_offset_idx;
GLuint shader_program;
bool running;
};
int init (App &app);
void create_vertex_spec (App &app);
void create_graphics_pipeline(App &app);
void run_main_loop (App &app);
void cleanup (App &app);
GLuint create_shader_program (const std::string &vertex_shader_source, const std::string &fragment_shader_source);
GLuint compile_shader (GLuint type, const std::string &source);
std::string load_shader (const std::string &filepath);
int main() {
App app = {};
int result = init(app);
if (result != EXIT_CODE_SUCCESS) {
return result;
}
// Setup the geometry
create_vertex_spec(app);
// Setup graphics pipeline. At the very minimum creating vertex and fragment shaders
create_graphics_pipeline(app);
run_main_loop(app);
cleanup(app);
return EXIT_CODE_SUCCESS;
}
int init(App &app) {
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
fprintf(stderr, "Failed to initialise SDL: %s", SDL_GetError());
return EXIT_CODE_SDL_INIT_FAILED;
}
uint32_t flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI;
app.window = SDL_CreateWindow("Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
WINDOW_WIDTH, WINDOW_HEIGHT, flags);
if (!app.window) {
fprintf(stderr, "Failed to create window: %s", SDL_GetError());
return EXIT_CODE_WINDOW_CREATION_FAILED;
}
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
app.context = SDL_GL_CreateContext(app.window);
if (!app.context) {
fprintf(stderr, "Failed to create OpenGL context: %s", SDL_GetError());
return EXIT_CODE_OPENGL_CONTEXT_FAILED;
}
// Init GLAD
if (!gladLoadGLLoader(SDL_GL_GetProcAddress)) {
fprintf(stderr, "Failed to initialise glad");
return EXIT_CODE_GLAD_LOADER_FAILED;
}
printf("%s\n%s\n%s\n%s\n",
glGetString(GL_VENDOR),
glGetString(GL_RENDERER),
glGetString(GL_VERSION),
glGetString(GL_SHADING_LANGUAGE_VERSION)
);
app.offset_modifier = 0.02f;
return EXIT_CODE_SUCCESS;
}
void create_vertex_spec(App &app) {
const std::vector<GLfloat> vertices = {
// vert0
-0.5f, -0.5f, 0.0f, // position
1.0f, 0.0f, 0.0f, // colour
// vert1
0.5f, -0.5f, 0.0f, // position
0.0f, 1.0f, 0.0f, // colour
// vert2
-0.5f, 0.5f, 0.0f, // position
0.0f, 0.0f, 1.0f, // colour
// vert3
0.5f, 0.5f, 0.0f, // position
1.0f, 1.0f, 0.0f, // colour
};
const std::vector<GLuint> indices = {
// first triangle
0, 1, 2,
// second triangle
2, 1, 3,
};
// Create and activate the VAO
glGenVertexArrays(1, &app.vao);
glBindVertexArray(app.vao);
// Create and set up the VBO
glGenBuffers(1, &app.vbo);
glBindBuffer(GL_ARRAY_BUFFER, app.vbo);
glBufferData(GL_ARRAY_BUFFER,
vertices.size() * sizeof(GLfloat),
(void *)vertices.data(),
GL_STATIC_DRAW
);
// Create and set up the EBO
glGenBuffers(1, &app.ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, app.ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
indices.size() * sizeof(GLuint),
(void *)indices.data(),
GL_STATIC_DRAW
);
GLsizei vertex_stride = 6 * sizeof(GLfloat);
GLvoid *position_start_offset = (void *)0;
GLvoid *colour_start_offset = (void *)(3 * sizeof(GLfloat));
// Defines the vertex attributes and their indices. This defines the attribute for the currently
// bound VAO
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
// Defines the number of components for the attribute, its type, the stride between the component
// for each vertex and the offset of the first instance of the component in the buffer
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertex_stride, position_start_offset);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, vertex_stride, colour_start_offset);
// Cleanup.
// The order matters. Unbind VAO first then disable the attributes. If you do it the
// other way around, the VAO will store that the attributes are disabled and nothing will be
// drawn during the rendering stage
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
}
void create_graphics_pipeline(App &app) {
const std::string vs_source = load_shader("shaders/vert.glsl");
const std::string fs_source = load_shader("shaders/frag.glsl");
app.shader_program = create_shader_program(vs_source, fs_source);
const char *u_offset = "u_offset";
app.u_offset_idx = glGetUniformLocation(app.shader_program, u_offset);
if (app.u_offset_idx < 0) {
printf("Can't find uniform %s", u_offset);
}
}
void run_main_loop(App &app) {
app.running = true;
while (app.running) {
while (SDL_PollEvent(&app.event)) {
switch (app.event.type) {
case SDL_QUIT:
app.running = false;
break;
case SDL_KEYDOWN:
if (app.event.key.keysym.sym == SDLK_UP) {
app.offset += app.offset_modifier;
} else if (app.event.key.keysym.sym == SDLK_DOWN) {
app.offset -= app.offset_modifier;
}
}
}
// Pre draw setup
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glViewport (0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
glClearColor(0.36f, 0.34f, 0.42f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glUseProgram(app.shader_program);
if (app.u_offset_idx >= 0) {
glUniform1f(app.u_offset_idx, app.offset);
}
// End pre draw setup
// Draw call
glBindVertexArray(app.vao);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void *)0);
// End draw call
// Not necessary if we only have one shader program
glUseProgram(0);
SDL_GL_SwapWindow(app.window);
}
}
void cleanup(App &app) {
SDL_GL_DeleteContext(app.context);
SDL_DestroyWindow(app.window);
SDL_Quit();
}
GLuint create_shader_program(const std::string &vertex_shader_source, const std::string &fragment_shader_source) {
GLuint shader_program = glCreateProgram();
GLuint vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex_shader_source);
GLuint fragment_shader = compile_shader(GL_FRAGMENT_SHADER, fragment_shader_source);
glAttachShader(shader_program, vertex_shader);
glAttachShader(shader_program, fragment_shader);
glLinkProgram (shader_program);
GLint link_status;
glGetProgramiv(shader_program, GL_LINK_STATUS, &link_status);
if (link_status != GL_TRUE) {
GLsizei log_length = 0;
GLchar message[1024];
glGetProgramInfoLog(shader_program, 1024, &log_length, message);
fprintf(stderr, "Failed to link program: %s\n", message);
}
// Validate the shader_program
glValidateProgram(shader_program);
GLint validation_status;
glGetProgramiv(shader_program, GL_VALIDATE_STATUS, &validation_status);
if (validation_status != GL_TRUE) {
GLsizei log_length = 0;
GLchar message[1024];
glGetProgramInfoLog(shader_program, 1024, &log_length, message);
fprintf(stderr, "Failed to validate program: %s\n", message);
}
return shader_program;
}
GLuint compile_shader(GLuint type, const std::string &source) {
GLuint shader = glCreateShader(type);
const char *shader_source = source.c_str();
glShaderSource (shader, 1, &shader_source, NULL);
glCompileShader(shader);
GLint compile_status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
if (compile_status != GL_TRUE) {
GLsizei log_length = 0;
GLchar message[1024];
glGetShaderInfoLog(shader, 1024, &log_length, message);
fprintf(stderr, "Failed to compile shader: %s\n", message);
}
return shader;
}
std::string load_shader(const std::string &filepath) {
FILE *fp = fopen(filepath.c_str(), "r");
if (!fp) {
return "";
}
std::string output = {};
char buf[1024] = {0};
while (fgets(buf, sizeof(buf), fp)) {
output += buf;
}
return output;
}