Compare commits

4 Commits

Author SHA1 Message Date
Natsirt867
6c6f831353 feat: add model matrix transformation hierarchy and delta time tracking 2025-10-17 20:44:22 -05:00
Natsirt867
65e7d1b401 feat: establish stable OpenGL 3.3 core graphics conxtext
initializes the full graphics context pipeline required for modern OpenGL rendering
2025-10-17 12:40:25 -05:00
Natsirt867
c9c5fc428f updating renderer work done 2025-10-17 02:51:15 -05:00
Natsirt867
a428be256c starting work on renderer 2025-10-17 02:46:35 -05:00
15 changed files with 992 additions and 71 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
cmake-build-debug
SDL
.idea/
external/

View File

@@ -8,10 +8,26 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/$<CONFIGURATION>")
# This assumes the SDL source is available in SDL
add_subdirectory(SDL EXCLUDE_FROM_ALL)
add_subdirectory(external/SDL EXCLUDE_FROM_ALL)
add_subdirectory(external/cglm EXCLUDE_FROM_ALL)
#add_subdirectory(glad EXCLUDE_FROM_ALL)
# Create your game executable target as usual
add_executable(BeyondDepth WIN32 main.c)
add_executable(BeyondDepth src/main.c
src/engine/core/logger/log.c
src/engine/renderer/camera/camera.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/data/engine_data.h
src/engine/renderer/mesh/mesh.c
src/engine/renderer/mesh/mesh.h)
# Link to the actual SDL3 library.
target_link_libraries(BeyondDepth PRIVATE SDL3::SDL3)
set_target_properties(BeyondDepth PROPERTIES WIN32_EXECUTABLE FALSE)
target_include_directories(BeyondDepth PRIVATE external/glad/include)
target_link_libraries(BeyondDepth PRIVATE
SDL3::SDL3
cglm)

67
main.c
View File

@@ -1,67 +0,0 @@
/*
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely.
*/
#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
/* This function runs once at startup. */
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
/* Create the window */
if (!SDL_CreateWindowAndRenderer("Hello World", 800, 600, SDL_WINDOW_FULLSCREEN, &window, &renderer)) {
SDL_Log("Couldn't create window and renderer: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
return SDL_APP_CONTINUE;
}
/* This function runs when a new event (mouse input, keypresses, etc) occurs. */
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
if (event->type == SDL_EVENT_KEY_DOWN ||
event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */
}
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;
/* 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;
/* 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);
return SDL_APP_CONTINUE;
}
/* This function runs once at shutdown. */
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright (c) 2020 rxi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "log.h"
#define MAX_CALLBACKS 32
typedef struct {
log_LogFn fn;
void *udata;
int level;
} Callback;
static struct {
void *udata;
log_LockFn lock;
int level;
bool quiet;
Callback callbacks[MAX_CALLBACKS];
} L;
static const char *level_strings[] = {
"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"
};
#ifdef LOG_USE_COLOR
static const char *level_colors[] = {
"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"
};
#endif
static void stdout_callback(log_Event *ev) {
char buf[16];
buf[strftime(buf, sizeof(buf), "%H:%M:%S", ev->time)] = '\0';
#ifdef LOG_USE_COLOR
fprintf(
ev->udata, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
buf, level_colors[ev->level], level_strings[ev->level],
ev->file, ev->line);
#else
fprintf(
ev->udata, "%s %-5s %s:%d: ",
buf, level_strings[ev->level], ev->file, ev->line);
#endif
vfprintf(ev->udata, ev->fmt, ev->ap);
fprintf(ev->udata, "\n");
fflush(ev->udata);
}
static void file_callback(log_Event *ev) {
char buf[64];
buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0';
fprintf(
ev->udata, "%s %-5s %s:%d: ",
buf, level_strings[ev->level], ev->file, ev->line);
vfprintf(ev->udata, ev->fmt, ev->ap);
fprintf(ev->udata, "\n");
fflush(ev->udata);
}
static void lock(void) {
if (L.lock) { L.lock(true, L.udata); }
}
static void unlock(void) {
if (L.lock) { L.lock(false, L.udata); }
}
const char* log_level_string(int level) {
return level_strings[level];
}
void log_set_lock(log_LockFn fn, void *udata) {
L.lock = fn;
L.udata = udata;
}
void log_set_level(int level) {
L.level = level;
}
void log_set_quiet(bool enable) {
L.quiet = enable;
}
int log_add_callback(log_LogFn fn, void *udata, int level) {
for (int i = 0; i < MAX_CALLBACKS; i++) {
if (!L.callbacks[i].fn) {
L.callbacks[i] = (Callback) { fn, udata, level };
return 0;
}
}
return -1;
}
int log_add_fp(FILE *fp, int level) {
return log_add_callback(file_callback, fp, level);
}
static void init_event(log_Event *ev, void *udata) {
if (!ev->time) {
time_t t = time(NULL);
ev->time = localtime(&t);
}
ev->udata = udata;
}
void log_log(int level, const char *file, int line, const char *fmt, ...) {
log_Event ev = {
.fmt = fmt,
.file = file,
.line = line,
.level = level,
};
lock();
if (!L.quiet && level >= L.level) {
init_event(&ev, stderr);
va_start(ev.ap, fmt);
stdout_callback(&ev);
va_end(ev.ap);
}
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++) {
Callback *cb = &L.callbacks[i];
if (level >= cb->level) {
init_event(&ev, cb->udata);
va_start(ev.ap, fmt);
cb->fn(&ev);
va_end(ev.ap);
}
}
unlock();
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2020 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See `log.c` for details.
*/
#ifndef LOG_H
#define LOG_H
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <time.h>
#define LOG_VERSION "0.1.0"
typedef struct {
va_list ap;
const char *fmt;
const char *file;
struct tm *time;
void *udata;
int line;
int level;
} log_Event;
typedef void (*log_LogFn)(log_Event *ev);
typedef void (*log_LockFn)(bool lock, void *udata);
enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL };
#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__)
#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)
const char* log_level_string(int level);
void log_set_lock(log_LockFn fn, void *udata);
void log_set_level(int level);
void log_set_quiet(bool enable);
int log_add_callback(log_LogFn fn, void *udata, int level);
int log_add_fp(FILE *fp, int level);
void log_log(int level, const char *file, int line, const char *fmt, ...);
#endif

View File

@@ -0,0 +1,127 @@
//
// Created by Tristan on 10/16/2025.
//
#include "camera.h"
#include "../..//core/logger/log.h"
#include <cglm/cglm.h>
#include "../shaders/shader.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.9f;
self->sensitivity = 3.5f;
self->FOV_deg = 45.0f;
self->yaw = -90.0f;
self->pitch = 0.0f;
self->width = width;
self->height = height;
// Init view matrix to identity (for first frame)
glm_mat4_identity(self->View);
}
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);
// create view matrix
glm_lookat(self->Position, target, self->Up, self->View);
}
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, Shader *shader, 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.001f, 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 = shader->camMatrixLocation;
if (uniform_location == -1) {
// log_error("Warning: Uniform '%s' not found in shader program %u.\n", uniform_name, shader);
return;
}
// upload combined matrix to the shader
// view_projection[0] array pointer decays to first value
glUniformMatrix4fv(uniform_location, 1, GL_FALSE, *view_projection);
}
void camera_process_keyboard(Camera *self, const Uint8 *keyState, float deltaTime) {
vec3 move;
float current_speed = self->speed * deltaTime;
// forward/back
if (keyState[SDL_SCANCODE_W]) {
glm_vec3_scale(self->Orientation, current_speed, move);
glm_vec3_add(self->Position, move, self->Position);
}
if (keyState[SDL_SCANCODE_S]) {
glm_vec3_scale(self->Orientation, current_speed, move);
glm_vec3_sub(self->Position, move, self->Position);
}
// strafe left/right
vec3 right;
glm_vec3_cross(self->Orientation, self->Up, right);
glm_vec3_normalize(right);
if (keyState[SDL_SCANCODE_A]) {
glm_vec3_scale(right, current_speed, move);
glm_vec3_sub(self->Position, move, self->Position);
}
if (keyState[SDL_SCANCODE_D]) {
glm_vec3_scale(right, current_speed, move);
glm_vec3_add(self->Position, move, self->Position);
}
}
// TODO: add deltaTime arg for smoother rotation
void camera_process_mouse(Camera *self, float xoffset, float yoffset, float deltaTime) {
float effective_sensitivity = self->sensitivity * deltaTime;
xoffset *= effective_sensitivity;
yoffset *= effective_sensitivity;
self->yaw += xoffset;
self->pitch += yoffset;
// clamps the pitch to prevent camera from flipping upside down
if (self->pitch > 89.0f)
self->pitch = 89.0f;
if (self->pitch < -89.0f)
self->pitch = -89.0f;
// calc new orientation vector from yaw and pitch
vec3 new_orientation;
new_orientation[0] = cos(glm_rad(self->yaw)) * cos(glm_rad(self->pitch));
new_orientation[1] = sin(glm_rad(self->pitch));
new_orientation[2] = sin(glm_rad(self->yaw)) * cos(glm_rad(self->pitch));
glm_vec3_normalize_to(new_orientation, self->Orientation);
}

View File

@@ -0,0 +1,45 @@
//
// Created by Tristan on 10/16/2025.
//
#ifndef CAMERA_H
#define CAMERA_H
#include <SDL3/SDL.h>
#include <cglm/cglm.h>
#include "../shaders/shader.h"
typedef struct {
vec3 Position;
vec3 Orientation;
vec3 Up;
float yaw;
float pitch;
float speed;
float sensitivity;
float FOV_deg;
int width;
int height;
mat4 View; // cache for the calculated View Matrix
} Camera;
// 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, Shader *shader, const char *uniform_name);
// TODO: Input handling (needs full SDL context later)
void camera_inputs(Camera *self, SDL_Event *event);
void camera_process_keyboard(Camera *self, const Uint8 *keystate, float deltaTime);
void camera_process_mouse(Camera *self, float xoffset, float yoffset, float deltaTime);
#endif //CAMERA_H

View File

@@ -0,0 +1,33 @@
//
// Created by Tristan on 10/17/2025.
//
#ifndef ENGINE_DATA_H
#define ENGINE_DATA_H
#include <SDL3/SDL.h>
#include "../camera/camera.h"
#include "../mesh/mesh.h"
typedef struct {
// Camera instance
Camera camera;
// Shader program instance
Shader shader;
// Mesh instance
Mesh triangle_mesh;
mat4 pyramidModel;
// Keep track of window size
int window_width;
int window_height;
float lastFrameTime; // time of the previous frame
float deltaTime; // time elapsed since the last frame
// TODO: Add other components, SDL_Window* or Meshes, etc...
} Engine_Data;
#endif //ENGINE_DATA_H

View File

@@ -0,0 +1,93 @@
//
// Created by Tristan on 10/17/2025.
//
#include "mesh.h"
#include "../../core/logger/log.h"
#include <stdlib.h>
GLfloat vertices[] = {
// Position // Color
// X Y Z R G B // Index
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 0: Apex
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 1: Base Corner 1 (Red)
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // 2: Base Corner 2 (Green)
0.0f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f // 3: Base Corner 3 (Blue)
};
const size_t vertices_size = sizeof(vertices);
GLuint indices[] = {
// side 1: (Apex, C1, C2)
0, 1, 2, // vertices that form the triangle
// side2: (Apex, C2, C3)
0, 2, 3,
// side 3: (Apex, C3, C1)
0, 3, 1,
// base: (C1, C3, C2) - order is crucial for culling
1, 3, 2
};
void mesh_init(Mesh *self, GLfloat *vertices, GLsizei vertices_size, GLuint *indices, GLsizei num_indices) {
self->num_indices = num_indices;
// generate buffers, vao, vbo, ebo
glGenVertexArrays(1, &self->VAO);
glGenBuffers(1, &self->VBO);
glGenBuffers(1, &self->EBO);
// bind the VAO
glBindVertexArray(self->VAO);
// bind and upload VBO data
glBindBuffer(GL_ARRAY_BUFFER, self->VBO);
glBufferData(GL_ARRAY_BUFFER, vertices_size, vertices, GL_STATIC_DRAW);
// bind and upload ebo data
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self->EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * num_indices, indices, GL_STATIC_DRAW);
// configure vertex attributes
// a. Position attribute (layout = 0)
// 3 floats per pos, stride = 6 floats total (3 pos + 3 colour), offset is 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);
// b. Color attribute (layout = 1)
// 3 floats per color, stride = 6 floats total, offset is 3 floats (after pos)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (void*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
// TODO: hader also expects layout = 2 but lazy, pray no crash
// unbind the VAO (so it can't be modified)
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
log_info("Mesh initialized with VAO: %u", self->VAO);
}
void mesh_draw(Mesh *self) {
// bind the vao
glBindVertexArray(self->VAO);
// draw the triangle
glDrawElements(GL_TRIANGLES, self->num_indices, GL_UNSIGNED_INT, 0);
// unbind
glBindVertexArray(0);
}
void mesh_delete(Mesh *self) {
glDeleteVertexArrays(1, &self->VAO);
glDeleteBuffers(1, &self->VBO);
glDeleteBuffers(1, &self->EBO);
self->VAO = 0;
self->VBO = 0;
self->EBO = 0;
}

View File

@@ -0,0 +1,43 @@
//
// Created by Tristan on 10/17/2025.
//
#ifndef MESH_H
#define MESH_H
#include <glad/glad.h>
#include <cglm/cglm.h>
// Vertices for a simple 2D triangle (x, y, z)
// Add (r, g, b) color data after each pos
// X, Y, Z, R, G, B
extern GLfloat vertices[];
extern const size_t vertices_size;
// Indices define the order vertices are drawn
extern GLuint indices[];
#define INDICES_COUNT 12
typedef struct {
// Vertex Array Object
GLuint VAO;
// Vertex Buffer Object
GLuint VBO;
// Element Buffer Object
GLuint EBO;
// Number of indices to draw
GLsizei num_indices;
} Mesh;
// initializes and uploads mesh data to gpu
void mesh_init(Mesh *self, GLfloat *vertices, GLsizei vertices_size, GLuint *indices, GLsizei num_indices);
// Draws the mesh
void mesh_draw(Mesh *self);
void mesh_delete(Mesh *self);
#endif //MESH_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

@@ -0,0 +1,129 @@
//
// Created by Tristan on 10/16/2025.
//
#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 == NULL || fragmentSource == NULL) {
log_error("Failed to load shader files. Returning empty shader\n");
Shader emptyShader = { .ID = 0, .camMatrixLocation = -1};
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);
GLint camMatrixLocation = glGetUniformLocation(programID, "camMatrix");
if (camMatrixLocation == -1) {
log_error("Uniform 'camMatrix' not found after linking shader program %u. Is it used in default.vert?", programID);
}
GLint modelMatrixLocation = glGetUniformLocation(programID, "modelMatrix");
if (modelMatrixLocation == -1) {
log_warn("Uniform 'modelMatrix' not found in shader program %u. Is it used in default.vert?", programID);
}
Shader newShader = { .ID = programID,
.camMatrixLocation = camMatrixLocation,
.modelMatrixLocation = modelMatrixLocation };
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

@@ -0,0 +1,31 @@
//
// Created by Tristan on 10/16/2025.
//
#ifndef SHADER_H
#define SHADER_H
#include <glad/glad.h>
#include <string.h>
typedef struct {
// Reference ID of the shader program
GLuint ID;
// Location of the camera matrix uniform, set at creation
GLint camMatrixLocation;
GLint modelMatrixLocation;
} Shader;
char *get_file_contents(const char *filename);
Shader shader_create(const char *vertexFile, const char *fragmentFile);
// Takes a pointer to the instance
void shader_activate(Shader *self);
void shader_delete(Shader *self);
// Private helper on shaderClass.h C++ header -- no private in C :)
void compile_errors(unsigned int shader, const char *type);
#endif //SHADER_H

213
src/main.c Normal file
View File

@@ -0,0 +1,213 @@
/*
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely.
*/
#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#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"
#include "engine/renderer/mesh/mesh.h"
static SDL_Window *window = NULL;
static SDL_GLContext gl_context = NULL;
/* This function runs once at startup. */
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
//log_set_level(2);
log_info("Starting program and initializing SDL...");
/* Create the window */
int win_width = 1280;
int win_height = 720;
// 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;
}
state->window_width = win_width;
state->window_height = win_height;
// 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);
// 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 | SDL_WINDOW_RESIZABLE);
if (!window) {
log_error("Couldn't create SDL window: %s", SDL_GetError());
free(state);
return SDL_APP_FAILURE;
}
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;
}
// z-buffer
glEnable(GL_DEPTH_TEST);
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;
}
vec3 initial_pos = {0.0f, 0.0f, 2.0f};
camera_init(&state->camera, initial_pos, state->window_width, state->window_height);
mesh_init(&state->triangle_mesh, vertices, (GLsizei)vertices_size, indices, INDICES_COUNT);
glm_mat4_identity(state->pyramidModel);
SDL_SetWindowRelativeMouseMode(window, true);
// pass allocated state back to the SDL loop
*appstate = state;
log_info("Camera, Shaders and App State initialized");
return SDL_APP_CONTINUE;
}
/* This function runs when a new event (mouse input, keypresses, etc) occurs. */
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
Engine_Data *state = (Engine_Data *)appstate;
// handles quitting
if (event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS;
}
// handles esc key to quit
if (event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_ESCAPE) {
return SDL_APP_SUCCESS;
}
// handle mouse motion for camera look
if (event->type == SDL_EVENT_MOUSE_MOTION) {
// y movement inverted?
camera_process_mouse(&state->camera, event->motion.xrel, -event->motion.yrel, state->deltaTime);
}
return SDL_APP_CONTINUE;
}
/* This function runs once per frame, and is the heart of the program. */
SDL_AppResult SDL_AppIterate(void *appstate)
{
Engine_Data *state = (Engine_Data *)appstate; // good practice
float currentTime = (float)SDL_GetTicks() / 1000.0f;
state->deltaTime = currentTime - state->lastFrameTime;
state->lastFrameTime = currentTime;
const Uint8 *keyState = SDL_GetKeyboardState(NULL);
camera_process_keyboard(&state->camera, keyState, state->deltaTime);
// set viewport
glViewport(0, 0, 1280, 720);
// 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);
// calculate "orbit" angle for total rotation overtime
float rotation_speed_deg = 45.0f;
float rotation_amount = glm_rad(rotation_speed_deg * state->deltaTime);
// accumulate total rotation
static float total_rotation = 0.0f;
total_rotation += rotation_amount;
shader_activate(&state->shader);
// upload camera matrix (P*V) to the shader
camera_matrix_to_shader(&state->camera, &state->shader, "camMatrix");
float rotation = (float)SDL_GetTicks() / 1000.0f;
glm_mat4_identity(state->pyramidModel);
// rotate around the y-axis
glm_rotate(state->pyramidModel, total_rotation * 0.5f, (vec3){0.0f, 1.0f, 0.0f});
mat4 orbitModel; // temporary object
glm_mat4_identity(orbitModel);
glm_rotate(orbitModel, total_rotation, (vec3){0.0f, 1.0f, 0.0f});
glm_translate(orbitModel, (vec3){3.0f, 0.0f, 0.0f});
glm_rotate(orbitModel, total_rotation * 2.0f, (vec3){0.5f, 1.0f, 0.0f});
// upload model matrix to shader
glUniformMatrix4fv(state->shader.modelMatrixLocation, 1, GL_FALSE, *state->pyramidModel);
mesh_draw(&state->triangle_mesh);
glUniformMatrix4fv(state->shader.modelMatrixLocation, 1, GL_FALSE, *orbitModel);
mesh_draw(&state->triangle_mesh);
// swap front and back buffers to display rendered frame
SDL_GL_SwapWindow(window);
return SDL_APP_CONTINUE;
}
/* 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) {
mesh_delete(&state->triangle_mesh);
if (state->shader.ID != 0) {
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");
}