diff --git a/CMakeLists.txt b/CMakeLists.txt index 9168563..86c1b65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/src/engine/renderer/camera/camera.c b/src/engine/renderer/camera/camera.c index 4cb08a5..4687ed5 100644 --- a/src/engine/renderer/camera/camera.c +++ b/src/engine/renderer/camera/camera.c @@ -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); \ No newline at end of file +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 +} \ No newline at end of file diff --git a/src/engine/renderer/camera/camera.h b/src/engine/renderer/camera/camera.h index 6d5e8ef..433477d 100644 --- a/src/engine/renderer/camera/camera.h +++ b/src/engine/renderer/camera/camera.h @@ -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 diff --git a/src/engine/renderer/data/engine_data.h b/src/engine/renderer/data/engine_data.h new file mode 100644 index 0000000..99fa7f6 --- /dev/null +++ b/src/engine/renderer/data/engine_data.h @@ -0,0 +1,25 @@ +// +// Created by Tristan on 10/17/2025. +// + +#ifndef ENGINE_DATA_H +#define ENGINE_DATA_H + +#include +#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 diff --git a/src/engine/renderer/shaders/default.frag b/src/engine/renderer/shaders/default.frag new file mode 100644 index 0000000..1f95fd5 --- /dev/null +++ b/src/engine/renderer/shaders/default.frag @@ -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); +} \ No newline at end of file diff --git a/src/engine/renderer/shaders/default.vert b/src/engine/renderer/shaders/default.vert new file mode 100644 index 0000000..b230988 --- /dev/null +++ b/src/engine/renderer/shaders/default.vert @@ -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; +} \ No newline at end of file diff --git a/src/engine/renderer/shaders/shader.c b/src/engine/renderer/shaders/shader.c index fe5fb21..d2ef3df 100644 --- a/src/engine/renderer/shaders/shader.c +++ b/src/engine/renderer/shaders/shader.c @@ -3,3 +3,115 @@ // #include "shader.h" +#include +#include +#include +#include +#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 +} \ No newline at end of file diff --git a/src/main.c b/src/main.c index b3fd877..19a58a9 100644 --- a/src/main.c +++ b/src/main.c @@ -15,9 +15,11 @@ #include #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"); }