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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
25
src/engine/renderer/data/engine_data.h
Normal file
25
src/engine/renderer/data/engine_data.h
Normal 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
|
||||
18
src/engine/renderer/shaders/default.frag
Normal file
18
src/engine/renderer/shaders/default.frag
Normal 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);
|
||||
}
|
||||
22
src/engine/renderer/shaders/default.vert
Normal file
22
src/engine/renderer/shaders/default.vert
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
112
src/main.c
112
src/main.c
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user