// renderer.c
#include "renderer.h"
#include "shader.h"
#include "texture.h"
#include "light.h"
#include "model.h"
#include "cglm/cglm.h"
static vec3 cam_pos = { 0.0f, 2.0f, 5.0f };
static float cam_yaw = 0.0f;
static float cam_pitch = -15.0f;
int window_width = 800;
int window_height = 600;
static GLuint shader_program;
static Model* default_model = NULL;
static Texture* default_texture = NULL;
GLFWwindow* window = NULL;
#define MAX_DRAW_CALLS 1024
typedef struct {
const Model* model;
const Texture* texture;
vec4 color;
vec3 position;
vec3 rotation;
vec3 scale;
} DrawCall;
static DrawCall draw_queue[MAX_DRAW_CALLS];
static int draw_queue_count = 0;
static mat4 view_matrix;
static mat4 proj_matrix;
void window_init() {
glfwInit();
window = glfwCreateWindow(window_width, window_height, "My Game", NULL, NULL);
glfwMakeContextCurrent(window);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
glfwGetFramebufferSize(window, &window_width, &window_height);
glViewport(0, 0, window_width, window_height);
}
void renderer_init() {
glEnable(GL_DEPTH_TEST);
shader_program = create_shader_program(
"assets/shaders/default.vert",
"assets/shaders/default.frag"
);
if (!shader_program) {
printf("Failed to create shader program\n");
return;
}
default_texture = load_texture("assets/textures/default.png");
if (!default_texture) {
printf("Failed to load default texture\n");
}
float triangle[] = {
// pos normal uv
-0.5f,-0.5f, 0.0f, 0,0,1, 0.0f,0.0f,
0.5f,-0.5f, 0.0f, 0,0,1, 1.0f,0.0f,
0.0f, 0.5f, 0.0f, 0,0,1, 0.5f,1.0f,
};
int attrs[] = { 3, 3, 2 };
default_model = model_create(triangle, 3, attrs, 3);
if (!default_model) {
printf("Failed to create default model\n");
}
ASSERT_UNIFORM(shader_program, "uModel");
ASSERT_UNIFORM(shader_program, "uView");
ASSERT_UNIFORM(shader_program, "uProj");
ASSERT_UNIFORM(shader_program, "uTexture");
ASSERT_UNIFORM(shader_program, "uColor");
ASSERT_UNIFORM(shader_program, "light_positions");
ASSERT_UNIFORM(shader_program, "light_colors");
ASSERT_UNIFORM(shader_program, "light_intensities");
ASSERT_UNIFORM(shader_program, "num_lights");
}
void renderer_begin_frame() {
glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
float move_speed = 0.1f;
float turn_speed = 1.0f;
static double last_x = 0, last_y = 0;
static int first_mouse = 1;
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);
if (first_mouse) {
last_x = xpos;
last_y = ypos;
first_mouse = 0;
}
float xoffset = (float)(xpos - last_x);
float yoffset = (float)(last_y - ypos);
last_x = xpos;
last_y = ypos;
float sensitivity = 0.3f;
xoffset *= sensitivity;
yoffset *= sensitivity;
cam_yaw += xoffset;
cam_pitch += yoffset;
if (cam_pitch > 89.0f) cam_pitch = 89.0f;
if (cam_pitch < -89.0f) cam_pitch = -89.0f;
vec3 front;
front[0] = cosf(glm_rad(cam_yaw)) * cosf(glm_rad(cam_pitch));
front[1] = sinf(glm_rad(cam_pitch));
front[2] = sinf(glm_rad(cam_yaw)) * cosf(glm_rad(cam_pitch));
glm_normalize(front);
vec3 right;
glm_vec3_crossn(front, (vec3) { 0.0f, 1.0f, 0.0f }, right);
vec3 up;
glm_vec3_crossn(right, front, up);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
glm_vec3_add(cam_pos, (vec3) { front[0] * move_speed, front[1] * move_speed, front[2] * move_speed }, cam_pos);
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
glm_vec3_sub(cam_pos, (vec3) { front[0] * move_speed, front[1] * move_speed, front[2] * move_speed }, cam_pos);
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
glm_vec3_sub(cam_pos, (vec3) { right[0] * move_speed, right[1] * move_speed, right[2] * move_speed }, cam_pos);
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
glm_vec3_add(cam_pos, (vec3) { right[0] * move_speed, right[1] * move_speed, right[2] * move_speed }, cam_pos);
if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
cam_pos[1] -= move_speed;
if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
cam_pos[1] += move_speed;
vec3 center;
glm_vec3_add(cam_pos, front, center);
mat4 view, proj;
glm_lookat(cam_pos, center, up, view);
glfwGetFramebufferSize(window, &window_width, &window_height);
glViewport(0, 0, window_width, window_height);
float aspect = (float)window_width / (float)window_height;
glm_perspective(glm_rad(45.0f), aspect, 0.1f, 100.0f, proj);
renderer_set_view_proj(view, proj);
}
void renderer_end_frame() {
if (!shader_program || !default_model) {
printf("Cannot render: shader or model not initialized\n");
return;
}
glUseProgram(shader_program);
GLint loc_model = glGetUniformLocation(shader_program, "uModel");
GLint loc_view = glGetUniformLocation(shader_program, "uView");
GLint loc_proj = glGetUniformLocation(shader_program, "uProj");
GLint loc_tex = glGetUniformLocation(shader_program, "uTexture");
GLint loc_color = glGetUniformLocation(shader_program, "uColor");
if (loc_model < 0 || loc_view < 0 || loc_proj < 0 || loc_tex < 0) {
printf("Shader uniforms not found: model=%d view=%d proj=%d tex=%d\n", loc_model, loc_view, loc_proj, loc_tex);
return;
}
glUniformMatrix4fv(loc_view, 1, GL_FALSE, (float*)view_matrix);
glUniformMatrix4fv(loc_proj, 1, GL_FALSE, (float*)proj_matrix);
vec3 positions[MAX_LIGHTS];
vec3 colors[MAX_LIGHTS];
float intensities[MAX_LIGHTS];
int num_lights = 0;
for (int i = 0; i < g_light_count; ++i) {
if (!g_lights[i].enabled) continue;
glm_vec3_copy(g_lights[i].position, positions[num_lights]);
glm_vec3_copy(g_lights[i].color, colors[num_lights]);
intensities[num_lights] = g_lights[i].intensity;
++num_lights;
}
GLint loc_pos = glGetUniformLocation(shader_program, "light_positions");
GLint loc_col = glGetUniformLocation(shader_program, "light_colors");
GLint loc_int = glGetUniformLocation(shader_program, "light_intensities");
GLint loc_num = glGetUniformLocation(shader_program, "num_lights");
glUniform3fv(loc_pos, num_lights, (float*)positions);
glUniform3fv(loc_col, num_lights, (float*)colors);
glUniform1fv(loc_int, num_lights, intensities);
glUniform1i(loc_num, num_lights);
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
printf("OpenGL Error: %d\n", err);
}
//printf("Drawing %d models\n", draw_queue_count);
for (int i = 0; i < draw_queue_count; i++) {
DrawCall* call = &draw_queue[i];
const Model* m = call->model ? call->model : default_model;
const Texture* t = call->texture ? call->texture : default_texture;
//if (!call->model) {
// printf("using default model\n");
//}
mat4 model;
glm_mat4_identity(model);
glm_translate(model, call->position);
glm_rotate_z(model, call->rotation[2], model);
glm_rotate_y(model, call->rotation[1], model);
glm_rotate_x(model, call->rotation[0], model);
glm_scale(model, call->scale);
glUniformMatrix4fv(loc_model, 1, GL_FALSE, (float*)model);
glUniform4fv(loc_color, 1, call->color);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, t ? t->id : 0);
glUniform1i(loc_tex, 0);
glBindVertexArray(m->vao);
glDrawArrays(GL_TRIANGLES, 0, m->vertex_count);
GL_CHECK_ERROR("glDrawArrays");
}
draw_queue_count = 0;
glfwSwapBuffers(window);
}
void renderer_draw_model(const Model* model, const Texture* texture, const vec4 color, const vec3 pos, const vec3 rot, const vec3 scale) {
//printf("Drawing: pos.y=%.2f, scale.y=%.2f\n", pos[1], scale[1]);
if (draw_queue_count >= MAX_DRAW_CALLS) {
#if defined DEBUG || _DEBUG
fprintf(stderr, "[Renderer] Draw queue overflow.\n");
assert(0 && "Too many draw calls this frame");
#endif
return;
}
#if defined DEBUG || _DEBUG
if (model && model->vao == 0) {
fprintf(stderr, "[Renderer] Model VAO is 0 (likely uninitialized)\n");
assert(0 && "Model VAO invalid");
}
if (texture && texture->id == 0) {
fprintf(stderr, "[Renderer] Texture ID is 0 (likely uninitialized)\n");
assert(0 && "Texture ID invalid");
}
#endif
DrawCall* dc = &draw_queue[draw_queue_count++];
dc->model = model;
dc->texture = texture;
const float zero3[3] = { 0.0f, 0.0f, 0.0f };
const float one3[3] = { 1.0f, 1.0f, 1.0f };
const float default_color[4] = { 1, 1, 1, 1 };
glm_vec4_copy(color ? color : default_color, dc->color);
glm_vec3_copy(pos ? pos : zero3, dc->position);
glm_vec3_copy(rot ? rot : zero3, dc->rotation);
glm_vec3_copy(scale ? scale : one3, dc->scale);
}
void renderer_set_view_proj(mat4 view, mat4 proj) {
glm_mat4_copy(view, view_matrix);
glm_mat4_copy(proj, proj_matrix);
}