// Release target will include shaders into the binary #ifdef BUILD_RELEASE #include "glsl.inc" #endif enum { MAX_TEXT_LENGTH = 128 }; typedef struct render_mgr { struct { GLuint shader; GLuint u_proj; GLuint u_view; GLuint u_model; GLuint u_texture; GLuint u_texture_alpha; GLuint u_light_count; GLuint u_lights; GLuint u_is_emitter; GLuint u_uv_scale; GLuint u_uv_translation; GLuint u_powerup_timer; GLuint u_ambient_adjust; mat4 proj_mat; } geo; struct { GLuint shader; GLuint vao; GLuint vbo; GLuint texture; GLuint u_proj; GLuint u_model; GLuint u_texture; GLuint u_color; mat4 proj_mat; } text; struct { GLuint shader; GLuint texture; GLuint vao; GLuint vbo; GLuint fbo; GLuint rbo; GLuint u_texture; GLuint u_powerup_timer; } screen; #ifdef BUILD_DEBUG struct { time_t geo; time_t screen; time_t text; } debug; #endif } render_mgr_t; GLuint compile_shader(char* contents, size_t size, unsigned int type) { const char* sources[3]; sources[0] = "#version 400 core\n"; sources[1] = (type == GL_VERTEX_SHADER) ? "#define VERTEX\n" : "#define FRAGMENT\n"; sources[2] = contents; int lengths[3]; lengths[0] = strlen(sources[0]); lengths[1] = strlen(sources[1]); lengths[2] = size; GLuint id = glCreateShader(type); glShaderSource(id, 3, sources, lengths); glCompileShader(id); char log[512]; int success; glGetShaderiv(id, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(id, sizeof(log), NULL, log); LOG_ERROR("Failed to compile shader:"); LOG_ERROR(" %s", log); #ifdef BUILD_RELEASE exit(EXIT_FAILURE); #endif } return id; } #ifdef BUILD_DEBUG char* load_shader(const char* path, size_t* size) { SDL_RWops* ops = SDL_RWFromFile(path, "r"); if (ops == NULL) { LOG_ERROR("Error opening file %s: %s", path, SDL_GetError()); exit(EXIT_FAILURE); } Sint64 file_size = SDL_RWsize(ops); if (size < 0) { LOG_ERROR("Error getting size of file %s: %s", path, SDL_GetError()); } char* buf = scratch_mem_alloc(1, file_size); size_t items_read = SDL_RWread(ops, buf, file_size, 1); if (items_read != 1) { LOG_ERROR("Error reading file %s: %s", path, SDL_GetError()); } int ret = SDL_RWclose(ops); if (ret != 0) { LOG_ERROR("Error closing file %s: %s", path, SDL_GetError()); } *size = (size_t)file_size; return buf; } #endif GLuint create_shader(char* buf, size_t size) { size_t vertex_size; { char* substring = strstr(buf, "#ifdef FRAGMENT"); LOG_ASSERT(substring != NULL, "Error parsing vertex shader"); vertex_size = substring - buf; } size_t fragment_size; { fragment_size = size - vertex_size; } GLuint vertex = compile_shader(buf, vertex_size, GL_VERTEX_SHADER); GLuint fragment = compile_shader(buf+vertex_size, fragment_size, GL_FRAGMENT_SHADER); GLuint shader = glCreateProgram(); { char log[512]; int success; glAttachShader(shader, vertex); glAttachShader(shader, fragment); glLinkProgram(shader); glGetProgramiv(shader, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shader, sizeof(log), NULL, log); LOG_ERROR("Failed to link shader program:"); LOG_ERROR(" %s", log); #ifdef BUILD_RELEASE exit(EXIT_FAILURE); #endif } glDeleteShader(vertex); glDeleteShader(fragment); } return shader; } void render_mgr_startup(void) { // Geometry rendering { #ifdef BUILD_RELEASE size_t size = strlen(geo_glsl); char* buf = (char*)geo_glsl; #else size_t size; char* buf = load_shader("shaders/geo.glsl", &size); struct stat attrib; stat("shaders/geo.glsl", &attrib); g_render_mgr->debug.geo = attrib.st_mtime; #endif GLuint shader = create_shader(buf, size); GLuint u_model = glGetUniformLocation(shader, "u_model"); GLuint u_view = glGetUniformLocation(shader, "u_view"); GLuint u_proj = glGetUniformLocation(shader, "u_proj"); GLuint u_texture = glGetUniformLocation(shader, "u_texture"); GLuint u_texture_alpha = glGetUniformLocation(shader, "u_texture_alpha"); GLuint u_light_count = glGetUniformLocation(shader, "u_light_count"); GLuint u_lights = glGetUniformLocation(shader, "u_lights[0].position"); GLuint u_is_emitter = glGetUniformLocation(shader, "u_is_emitter"); GLuint u_uv_scale = glGetUniformLocation(shader, "u_uv_scale"); GLuint u_uv_translation = glGetUniformLocation(shader, "u_uv_translation"); GLuint u_powerup_timer = glGetUniformLocation(shader, "u_powerup_timer"); GLuint u_ambient_adjust = glGetUniformLocation(shader, "u_ambient_adjust"); const float n = 0.1f; const float f = 100.0f; const float fov = 75.0f; const float d = 1.0f / tanf(DEG_TO_RAD(fov*0.5f)); const float a = (float)RENDER_WIDTH / (float)RENDER_HEIGHT; mat4 proj_mat = { d/a, 0.0f, 0.0f, 0.0f, 0.0f, d, 0.0f, 0.0f, 0.0f, 0.0f, f/(f-n), (-f*n)/(f-n), 0.0f, 0.0f, 1.0f, 0.0f }; g_render_mgr->geo.shader = shader; g_render_mgr->geo.u_model = u_model; g_render_mgr->geo.u_proj = u_proj; g_render_mgr->geo.u_view= u_view; g_render_mgr->geo.u_texture = u_texture; g_render_mgr->geo.u_texture_alpha = u_texture_alpha; g_render_mgr->geo.u_light_count = u_light_count; g_render_mgr->geo.u_lights = u_lights; g_render_mgr->geo.u_is_emitter = u_is_emitter; g_render_mgr->geo.u_uv_scale = u_uv_scale; g_render_mgr->geo.u_uv_translation = u_uv_translation; g_render_mgr->geo.u_powerup_timer = u_powerup_timer; g_render_mgr->geo.u_ambient_adjust = u_ambient_adjust; g_render_mgr->geo.proj_mat = proj_mat; } // Text rendering { #ifdef BUILD_RELEASE size_t size = strlen(text_glsl); char* buf = (char*)text_glsl; #else size_t size; char* buf = load_shader("shaders/text.glsl", &size); struct stat attrib; stat("shaders/text.glsl", &attrib); g_render_mgr->debug.text = attrib.st_mtime; #endif GLuint shader = create_shader(buf, size); GLuint vao; glGenVertexArrays(1, &vao); glBindVertexArray(vao); GLuint vbo; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, g_font.width, g_font.height, 0, GL_RED, GL_UNSIGNED_BYTE, g_font.bitmap); GLuint u_proj = glGetUniformLocation(shader, "u_proj"); GLuint u_model = glGetUniformLocation(shader, "u_model"); GLuint u_texture = glGetUniformLocation(shader, "u_texture"); GLuint u_color = glGetUniformLocation(shader, "u_color"); mat4 proj_mat = { 2.0f/WINDOW_WIDTH, 0.0f, 0.0f, -1.0f, 0.0f, 2.0f/WINDOW_HEIGHT, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; g_render_mgr->text.shader = shader; g_render_mgr->text.vao = vao; g_render_mgr->text.vbo = vbo; g_render_mgr->text.texture = texture; g_render_mgr->text.u_proj = u_proj; g_render_mgr->text.u_model = u_model; g_render_mgr->text.u_texture = u_texture; g_render_mgr->text.u_color = u_color; g_render_mgr->text.proj_mat = proj_mat; } // Screen rendering { #ifdef BUILD_RELEASE size_t size = strlen(screen_glsl); char* buf = (char*)screen_glsl; #else size_t size; char* buf = load_shader("shaders/screen.glsl", &size); struct stat attrib; stat("shaders/screen.glsl", &attrib); g_render_mgr->debug.screen= attrib.st_mtime; #endif GLuint shader = create_shader(buf, size); // Low-resolution texture that will be rendered into GLuint texture; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, RENDER_WIDTH, RENDER_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); // Render buffer object required for the framebuffer GLuint rbo; glGenRenderbuffers(1, &rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, RENDER_WIDTH, RENDER_HEIGHT); glBindRenderbuffer(GL_RENDERBUFFER, 0); // Framebuffer attached to texture and RBO GLuint fbo; glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { LOG_ERROR("Unable to create render-to-texture framebuffer."); exit(EXIT_FAILURE); } glBindFramebuffer(GL_FRAMEBUFFER, 0); float vertices[] = { // Position Texcoord -1.0f, 1.0f, 0.0f, 1.0f, // upper left -1.0f, -1.0f, 0.0f, 0.0f, // lower left 1.0f, -1.0f, 1.0f, 0.0f, // lower right 1.0f, -1.0f, 1.0f, 0.0f, // lower right 1.0f, 1.0f, 1.0f, 1.0f, // upper right -1.0f, 1.0f, 0.0f, 1.0f, // upper left }; GLuint vao; glGenVertexArrays(1, &vao); glBindVertexArray(vao); GLuint vbo; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(vertices[0]), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(vertices[0]), (void*)(2 * sizeof(vertices[0]))); glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); GLuint u_texture = glGetUniformLocation(shader, "u_texture"); GLuint u_powerup_timer = glGetUniformLocation(shader, "u_powerup_timer"); g_render_mgr->screen.shader = shader; g_render_mgr->screen.vao = vao; g_render_mgr->screen.vbo = vbo; g_render_mgr->screen.fbo = fbo; g_render_mgr->screen.rbo = rbo; g_render_mgr->screen.texture = texture; g_render_mgr->screen.u_texture = u_texture; g_render_mgr->screen.u_powerup_timer = u_powerup_timer; } } const char* format_text(const char* format, ...) { static char buf[MAX_TEXT_LENGTH] = {}; va_list args; va_start(args, format); vsprintf(buf, format, args); va_end(args); return buf; } void render_geo_begin(camera_t* camera) { glBindFramebuffer(GL_FRAMEBUFFER, g_render_mgr->screen.fbo); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); glUseProgram(g_render_mgr->geo.shader); glViewport(0, 0, RENDER_WIDTH, RENDER_HEIGHT); glUniformMatrix4fv(g_render_mgr->geo.u_proj, 1, GL_TRUE, (float*)&g_render_mgr->geo.proj_mat); glUniformMatrix4fv(g_render_mgr->geo.u_view, 1, GL_TRUE, (float*)&camera->view_mat); } void render_geo_end(void) { glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(VIEWPORT_X, VIEWPORT_Y, VIEWPORT_WIDTH, VIEWPORT_HEIGHT); } void render_screen_begin(void) { glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_BLEND); glUseProgram(g_render_mgr->screen.shader); glBindVertexArray(g_render_mgr->screen.vao); } void render_screen_end(void) { glDisable(GL_DEPTH_TEST); glBindTexture(GL_TEXTURE_2D, g_render_mgr->screen.texture); glDrawArrays(GL_TRIANGLES, 0, 6); glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); } void render_text(const char* text, vec2 position, vec3 color, float size) { typedef struct vertex { vec2 position; vec2 texcoord; } vertex_t; vertex_t vertices[6 * MAX_TEXT_LENGTH]; int text_length = strlen(text); LOG_ASSERT(text_length < MAX_TEXT_LENGTH, "Text length (%d) exceeds allowed maximum (%d)", text_length, MAX_TEXT_LENGTH); float atlas_width_recip = 1.0f / g_font.width; float atlas_height_recip = 1.0f / g_font.height; int cursor = 0; for (int i = 0; i < text_length; i++) { // Indices start with space character (ASCII 0x20 / 32) int idx = text[i] - ' '; glyph_t glyph = g_font.glyphs[idx]; // Set up the four vertices required for the glyph's quad (texcoord.y is flipped to account for flipped texture) vertex_t upper_left = { .position.x = cursor - glyph.origin_x, .position.y = glyph.origin_y, .texcoord.x = glyph.x * atlas_width_recip, .texcoord.y = 1.0f - (glyph.y * atlas_height_recip)}; vertex_t upper_right = { .position.x = upper_left.position.x + glyph.width, .position.y = upper_left.position.y, .texcoord.x = (glyph.x + glyph.width) * atlas_width_recip, .texcoord.y = upper_left.texcoord.y}; vertex_t bottom_right = { .position.x = upper_right.position.x, .position.y = upper_left.position.y - glyph.height, .texcoord.x = upper_right.texcoord.x, .texcoord.y = 1.0f - ((glyph.y + glyph.height) * atlas_height_recip)}; vertex_t bottom_left = { .position.x = upper_left.position.x, .position.y = bottom_right.position.y, .texcoord.x = upper_left.texcoord.x, .texcoord.y = bottom_right.texcoord.y}; // o-----o // | \ | // | \ | // | \ | // o-----o vertices[6*i + 0] = upper_left; vertices[6*i + 1] = upper_right; vertices[6*i + 2] = bottom_right; vertices[6*i + 3] = bottom_right; vertices[6*i + 4] = bottom_left; vertices[6*i + 5] = upper_left; cursor += g_font.advance; } mat4 model_mat = mat4_identity(); model_mat.e00 = size; model_mat.e11 = size; model_mat.e03 = position.x; model_mat.e13 = position.y; glUseProgram(g_render_mgr->text.shader); glUniformMatrix4fv(g_render_mgr->text.u_proj, 1, GL_TRUE, (float*)&g_render_mgr->text.proj_mat); glUniformMatrix4fv(g_render_mgr->text.u_model, 1, GL_TRUE, (float*)&model_mat); glUniform1i(g_render_mgr->text.u_texture, 0); glUniform3fv(g_render_mgr->text.u_color, 1, (float*)&color); glBindVertexArray(g_render_mgr->text.vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, g_render_mgr->text.texture); glBindBuffer(GL_ARRAY_BUFFER, g_render_mgr->text.vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * 6 * text_length, vertices, GL_DYNAMIC_DRAW); glDrawArrays(GL_TRIANGLES, 0, 6 * text_length); } void reload_shaders(void) { #ifdef BUILD_DEBUG glUseProgram(0); { struct stat attrib; stat("shaders/geo.glsl", &attrib); time_t mod_time = attrib.st_mtime; if (mod_time != g_render_mgr->debug.geo) { glDeleteProgram(g_render_mgr->geo.shader); size_t size; char* buf = load_shader("shaders/geo.glsl", &size); g_render_mgr->geo.shader = create_shader(buf, size); g_render_mgr->debug.geo = mod_time; } } { struct stat attrib; stat("shaders/screen.glsl", &attrib); time_t mod_time = attrib.st_mtime; if (mod_time != g_render_mgr->debug.screen) { glDeleteProgram(g_render_mgr->screen.shader); size_t size; char* buf = load_shader("shaders/screen.glsl", &size); g_render_mgr->screen.shader = create_shader(buf, size); g_render_mgr->debug.screen = mod_time; } } { struct stat attrib; stat("shaders/text.glsl", &attrib); time_t mod_time = attrib.st_mtime; if (mod_time != g_render_mgr->debug.text) { glDeleteProgram(g_render_mgr->text.shader); size_t size; char* buf = load_shader("shaders/text.glsl", &size); g_render_mgr->text.shader = create_shader(buf, size); g_render_mgr->debug.text = mod_time; } } #endif }