feat: establish stable OpenGL 3.3 core graphics conxtext

initializes the full graphics context pipeline required for modern OpenGL rendering
This commit is contained in:
Natsirt867
2025-10-17 12:40:25 -05:00
parent c9c5fc428f
commit 65e7d1b401
8 changed files with 353 additions and 44 deletions

View File

@@ -19,7 +19,8 @@ add_executable(BeyondDepth src/main.c
src/engine/renderer/camera/camera.h
external/glad/src/glad.c
src/engine/renderer/shaders/shader.c
src/engine/renderer/shaders/shader.h)
src/engine/renderer/shaders/shader.h
src/engine/renderer/data/engine_data.h)
set_target_properties(BeyondDepth PROPERTIES WIN32_EXECUTABLE FALSE)

View File

@@ -3,34 +3,78 @@
//
#include "camera.h"
#include "../..//core/logger/log.h"
void camera_init(Camera *self, vec3 initial_position, int width, int height) {
// copy initial_pos vector
glm_vec3_copy(initial_position, self->Position);
// initialize orientation vector
glm_vec3_copy((vec3){0.0f, 0.0f, -1.0f}, self->Orientation);
// initialize up vector
glm_vec3_copy((vec3){0.0f, 1.0f, 0.0f}, self->Up);
// initialize controls and viewport
self->speed = 0.1f;
self->sensitivity = 100.0f;
self->FOV_deg = 45.0f;
self->width = width;
self->height = height;
// Init view matrix to identity (for first frame)
glm_mat4_identity(self->View);
}
/*
Camera l_camera = {
.Position = { 0.0f, 0.0f, 0.0f },
.Orientation = { 0.0f, 0.0f, -1.0f },
.Up = { 0.0f, 1.0f, 0.0f }
};
};*/
void matrix(float FOVdeg, float nearPlane, float farPlane, Shader* shader_instance, const char* uniform) {
mat4 view, projection;
glm_mat4_identity(view);
glm_mat4_identity(projection);
// store the result of the vector addition
void camera_update_view_matrix(Camera *self) {
// calc the center or target point (Pos + Orientation)
vec3 target;
glm_vec3_add(self->Position, self->Orientation, target);
// cglm function to perform vector addition
glm_vec3_add(l_camera.Position, l_camera.Orientation, target);
mat4 target_2;
glm_mat4_mul(projection[0], view[0], target_2);
// now we can use the addition of the two vec3's in the glm_lookat() function
glm_lookat(l_camera.Position, target, l_camera.Up, view);
glm_perspective(glm_rad(FOVdeg), (float)(l_camera.width / l_camera.height), nearPlane, farPlane, projection);
glUniformMatrix4fv(glGetUniformLocation(shader_instance->ID, uniform), 1, GL_FALSE, target_2);
// create view matrix
glm_lookat(self->Position, target, self->Up, self->View);
}
void inputs(SDL_Window* window);
void camera_update_projection(Camera *self, float nearPlane, float farPlane, mat4 dest) {
float aspect = (float)self->width / (float)self->height;
glm_perspective(glm_rad(self->FOV_deg), aspect, nearPlane, farPlane, dest);
}
// TODO: Update matrix function signature to accept camera entity?
// change name to camera_upload_matrix for clarity?
void camera_matrix_to_shader(Camera *self, GLuint shader_program_ID, const char *uniform_name) {
// update the view matrix first, camera position might have moved
camera_update_view_matrix(self);
// calculate projection matrix (P) with temporary matrix 'projection' for the result
mat4 projection;
camera_update_projection(self, 0.1f, 100.0f, projection);
// combine the matrices -- P * V -> PV
mat4 view_projection;
glm_mat4_mul(projection, self->View, view_projection);
// get uniform location
GLint uniform_location = glGetUniformLocation(shader_program_ID, uniform_name);
if (uniform_location == -1) {
log_error("Warning: Uniform '%s' not found in shader program %u.\n", uniform_name, shader_program_ID);
return;
}
// upload combined matrix to the shader
// view_projection[0] array pointer decays to first value
glUniformMatrix4fv(uniform_location, 1, GL_FALSE, view_projection[0]);
}
void camera_inputs(Camera *self, SDL_Event *event) {
// TODO: implementation for handling keyboard/mouse inputs for camera
}

View File

@@ -15,11 +15,26 @@ typedef struct {
vec3 Up;
float speed;
float sensitivity;
float FOV_deg;
int width;
int height;
mat4 View; // cache for the calculated View Matrix
} Camera;
void matrix(float FOVdeg, float nearPlane, float farPlane, Shader* shader_instance, const char* uniform);
void inputs(SDL_Window* window);
// initializer
void camera_init(Camera *self, vec3 initial_position, int width, int height);
/* --- Matrix calculation functions --- */
// Updates self->View matrix based on pos, orientation and up
void camera_update_view_matrix(Camera *self);
// calculates the projection matrix for a given set of planes
void camera_update_projection(Camera *self, float nearPlane, float forPlane, mat4 dest);
// generates (projection * view) and uploads to the shader program
void camera_matrix_to_shader(Camera *self, GLuint shader_program_ID, const char *uniform_name);
// TODO: Input handling (needs full SDL context later)
void camera_inputs(Camera *self, SDL_Event *event);
#endif //CAMERA_H

View File

@@ -0,0 +1,25 @@
//
// Created by Tristan on 10/17/2025.
//
#ifndef ENGINE_DATA_H
#define ENGINE_DATA_H
#include <SDL3/SDL.h>
#include "../camera/camera.h"
typedef struct {
// Camera instance
Camera camera;
// Shader program instance
Shader shader;
// Keep track of window size
int window_width;
int window_height;
// TODO: Add other components, SDL_Window* or Meshes, etc...
} Engine_Data;
#endif //ENGINE_DATA_H

View File

@@ -0,0 +1,18 @@
#version 330 core
// Outputs colors in RGBA
out vec4 FragColor;
// Inputs the color from the vertex shader
in vec3 color;
// Inputs the texture coordinates from the vertex shader
in vec2 texCoord;
// get the texture unit from the main function
uniform sampler2D tex0;
void main()
{
FragColor = texture(tex0, texCoord);
}

View File

@@ -0,0 +1,22 @@
#version 330 core
layout (location = 0) in vec3 aPos; // Vertex Position
layout (location = 1) in vec3 aColor; // Vertex Color
layout (location = 2) in vec2 aTex; // Texture coordinates
// Outputs the color for the Fragment shader
out vec3 color;
// Outputs the texture coordinates for the Fragment shader
out vec2 texCoord;
// Imports the camera matrix from the main function
uniform mat4 camMatrix;
void main()
{
// Apply the combined P*V matrix to transform the vertex position.
gl_Position = camMatrix * vec4(aPos, 1.0);
// Assigns the colors from the Vertex Data to "color"
color = aColor;
// Assigns the texture coordinates from the Vertex Data to "texCoord"
texCoord = aTex;
}

View File

@@ -3,3 +3,115 @@
//
#include "shader.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "../../core/logger/log.h"
// reads entire file contents into a string
char *get_file_contents(const char *filename) {
FILE *fp;
long length;
char *buffer = NULL;
fp = fopen(filename, "rb");
if (!fp) {
log_error("Could not open file '%s'. Reason: %s\n", filename, strerror(errno));
return NULL;
}
fseek(fp, 0, SEEK_END);
length = ftell(fp);
fseek(fp, 0, SEEK_SET);
buffer = (char *)malloc(length + 1);
if (!buffer) {
log_error("Memory allocation failed for file '%s'.\n", filename);
fclose(fp);
return NULL;
}
fread(buffer, 1, length, fp);
buffer[length] = '\0';
fclose(fp);
return buffer;
}
// check if the different shaders have compiled properly
void compile_errors(unsigned int shader, const char *type) {
GLint hasCompiled;
char infoLog[1024];
if (strcmp(type, "PROGRAM") != 0) {
glGetShaderiv(shader, GL_COMPILE_STATUS, &hasCompiled);
if (hasCompiled == GL_FALSE) {
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
log_error("SHADER_COMPILATION_ERROR for: %s\n%s", type, infoLog);
}
} else {
glGetProgramiv(shader, GL_LINK_STATUS, &hasCompiled);
if (hasCompiled == GL_FALSE) {
glGetShaderInfoLog(shader, 1024, NULL, infoLog);
log_error("SHADER_LINKING_ERROR for %s\n%s", type, infoLog);
}
}
}
// build the shader program from 2 different shaders
Shader shader_create(const char *vertexFile, const char *fragmentFile) {
// read shader source files
char *vertexSource = get_file_contents(vertexFile);
char *fragmentSource = get_file_contents(fragmentFile);
if (!vertexSource || !fragmentSource) {
log_error("Failed to load shader files. Returning empty shader\n");
Shader emptyShader = { .ID = 0};
if (vertexSource)
free(vertexSource);
if (fragmentSource)
free(fragmentSource);
return emptyShader;
}
// compile vertex shader
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, (const char *const *)&vertexSource, NULL);
glCompileShader(vertexShader);
compile_errors(vertexShader, "VERTEX");
// compile fragment shader
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, (const char *const *)&fragmentSource, NULL);
glCompileShader(fragmentShader);
compile_errors(fragmentShader, "FRAGMENT");
// cleanup strings after compilation
free(vertexSource);
free(fragmentSource);
// link shaders into a program
GLuint programID = glCreateProgram();
glAttachShader(programID, vertexShader);
glAttachShader(programID, fragmentShader);
glLinkProgram(programID);
compile_errors(programID, "PROGRAM");
// cleanup temporary shaders now that we've linked into the program
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
Shader newShader = { .ID = programID };
return newShader;
}
// activates the shader program
void shader_activate(Shader *self) {
glUseProgram(self->ID);
}
// deletes the shader program
void shader_delete(Shader *self) {
glDeleteProgram(self->ID);
self->ID = 0; // just in-case
}

View File

@@ -15,9 +15,11 @@
#include <cglm/cglm.h>
#include "engine/core/logger/log.h"
#include "engine/renderer/camera/camera.h"
#include "engine/renderer/data/engine_data.h"
#include "engine/renderer/shaders/shader.h"
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_GLContext gl_context = NULL;
/* This function runs once at startup. */
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
@@ -26,12 +28,69 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
log_info("Starting program and initializing SDL...");
/* Create the window */
// SDL_WINDOW_FULLSCREEN overrides with window_flags to make window fullscreen
if (!SDL_CreateWindowAndRenderer("Hello World", 800, 600, 0, &window, &renderer)) {
SDL_Log("Couldn't create window and renderer: %s", SDL_GetError());
log_error("Couldn't create window and renderer %s", SDL_GetError());
// TODO: make use of arena allocators potentially? see: digital grove repo
Engine_Data *state = (Engine_Data*)malloc(sizeof(Engine_Data));
if (state == NULL) {
log_error("Failed to allocate engine_data state, %s", SDL_GetError());
return SDL_APP_FAILURE;
}
// pass allocated state back to the SDL loop
*appstate = state;
int win_width = 1280;
int win_height = 720;
// use of SDL_WINDOW_OPENGL flag to create a context-compatible window with shaders
window = SDL_CreateWindow("Beyond Depth", win_width, win_height, SDL_WINDOW_OPENGL);
if (!window) {
log_error("Couldn't create SDL window: %s", SDL_GetError());
free(state);
return SDL_APP_FAILURE;
}
// create openGL context with openGL 3.3 core profile
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
gl_context = SDL_GL_CreateContext(window);
if (!gl_context) {
log_error("Failed to create OpenGL context: %s", SDL_GetError());
SDL_DestroyWindow(window);
free(state);
return SDL_APP_FAILURE;
}
// initialize GLAD
// loads all the OpenGL function pointers
if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
log_error("Failed to initialize GLAD");
SDL_GL_DestroyContext(gl_context);
SDL_DestroyWindow(window);
free(state);
return SDL_APP_FAILURE;
}
// TODO: if still crashing on initial startup, ensure all of OpenGL, GLAD is properly initialized
state->window_width = win_width;
state->window_height = win_height;
vec3 initial_pos = {0.0f, 0.0f, 3.0f};
camera_init(&state->camera, initial_pos, state->window_width, state->window_height);
state->shader = shader_create("default.vert", "default.frag");
if (state->shader.ID == 0) {
log_error("Failed to initialize shader program");
// TODO: cleanup memory
return SDL_APP_FAILURE;
}
log_info("Camera, Shaders and App State initialized");
return SDL_APP_CONTINUE;
}
@@ -46,28 +105,24 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
return SDL_APP_CONTINUE;
}
/* This function runs once per frame, and is the heart of the program. */
SDL_AppResult SDL_AppIterate(void *appstate)
{
const char *message = "Hello World!";
int w = 0, h = 0;
float x, y;
const float scale = 4.0f;
Engine_Data *state = (Engine_Data *)appstate; // good practice
/* Center the message and scale it up */
SDL_GetRenderOutputSize(renderer, &w, &h);
SDL_SetRenderScale(renderer, scale, scale);
x = ((w / scale) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE * SDL_strlen(message)) / 2;
y = ((h / scale) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2;
// set viewport
glViewport(0, 0, 1280, 720);
/* Draw the message */
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderDebugText(renderer, x, y, message);
SDL_RenderPresent(renderer);
// clear the screen before drawing
// TODO: macros for colours so its easier to read? 0.07f does nothing for me context wise lol
glClearColor(0.07f, 0.13f, 0.17f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// TODO: add drawing logic
// swap front and back buffers to display rendered frame
SDL_GL_SwapWindow(window);
return SDL_APP_CONTINUE;
}
@@ -75,4 +130,21 @@ SDL_AppResult SDL_AppIterate(void *appstate)
/* This function runs once at shutdown. */
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
Engine_Data *state = (Engine_Data *)appstate;
// cleanup all resources
if (state) {
shader_delete(&state->shader);
free(state);
}
if (gl_context) {
SDL_GL_DestroyContext(gl_context);
}
if (window) {
SDL_DestroyWindow(window);
}
log_info("Application quit and resources cleaned up");
}