static GLuint compile_shader(char* contents, size_t size, unsigned int type); static GLuint create_shader(char* buf, size_t size); void render_mgr_startup(void) { // Geometry rendering { #ifdef BUILD_DEBUG size_t size; char* buf = DEBUG_load_shader("shaders/geo.glsl", &size); #else size_t size = strlen(geo_glsl); char* buf = (char*)geo_glsl; #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_light = glGetUniformLocation(shader, "u_light"); 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_engine->render_mgr.geo.shader = shader; g_engine->render_mgr.geo.u_model = u_model; g_engine->render_mgr.geo.u_proj = u_proj; g_engine->render_mgr.geo.u_view= u_view; g_engine->render_mgr.geo.u_texture = u_texture; g_engine->render_mgr.geo.u_light = u_light; g_engine->render_mgr.geo.proj_mat = proj_mat; } // Text rendering { #ifdef BUILD_DEBUG size_t size; char* buf = DEBUG_load_shader("shaders/text.glsl", &size); #else size_t size = strlen(text_glsl); char* buf = (char*)text_glsl; #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_engine->render_mgr.text.shader = shader; g_engine->render_mgr.text.vao = vao; g_engine->render_mgr.text.vbo = vbo; g_engine->render_mgr.text.texture = texture; g_engine->render_mgr.text.u_proj = u_proj; g_engine->render_mgr.text.u_model = u_model; g_engine->render_mgr.text.u_texture = u_texture; g_engine->render_mgr.text.u_color = u_color; g_engine->render_mgr.text.proj_mat = proj_mat; } // Screen rendering { #ifdef BUILD_DEBUG size_t size; char* buf = DEBUG_load_shader("shaders/screen.glsl", &size); #else size_t size = strlen(screen_glsl); char* buf = (char*)screen_glsl; #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"); g_engine->render_mgr.screen.shader = shader; g_engine->render_mgr.screen.vao = vao; g_engine->render_mgr.screen.vbo = vbo; g_engine->render_mgr.screen.fbo = fbo; g_engine->render_mgr.screen.rbo = rbo; g_engine->render_mgr.screen.texture = texture; g_engine->render_mgr.screen.u_texture = u_texture; } } 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(void) { glBindFramebuffer(GL_FRAMEBUFFER, g_engine->render_mgr.screen.fbo); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); glUseProgram(g_engine->render_mgr.geo.shader); glViewport(0, 0, RENDER_WIDTH, RENDER_HEIGHT); glUniformMatrix4fv(g_engine->render_mgr.geo.u_proj, 1, GL_TRUE, (float*)&g_engine->render_mgr.geo.proj_mat); #ifdef BUILD_DEBUG if (g_debug->camera.enabled) { glUniformMatrix4fv(g_engine->render_mgr.geo.u_view, 1, GL_TRUE, (float*)&g_debug->camera.fly.view_mat); } else { glUniformMatrix4fv(g_engine->render_mgr.geo.u_view, 1, GL_TRUE, (float*)&g_engine->render_mgr.active_camera->view_mat); } #else glUniformMatrix4fv(g_engine->render_mgr.geo.u_view, 1, GL_TRUE, (float*)&g_engine->render_mgr.active_camera->view_mat); #endif } 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_engine->render_mgr.screen.shader); glBindVertexArray(g_engine->render_mgr.screen.vao); glUniform1i(g_engine->render_mgr.screen.u_texture, 0); } void render_screen_end(void) { glDisable(GL_DEPTH_TEST); glBindTexture(GL_TEXTURE_2D, g_engine->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) { struct vertex { vec2 position; vec2 texcoord; }; struct vertex 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) struct vertex 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)}; struct vertex 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}; struct vertex 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)}; struct vertex 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_engine->render_mgr.text.shader); glUniformMatrix4fv(g_engine->render_mgr.text.u_proj, 1, GL_TRUE, (float*)&g_engine->render_mgr.text.proj_mat); glUniformMatrix4fv(g_engine->render_mgr.text.u_model, 1, GL_TRUE, (float*)&model_mat); glUniform1i(g_engine->render_mgr.text.u_texture, 0); glUniform3fv(g_engine->render_mgr.text.u_color, 1, (float*)&color); glBindVertexArray(g_engine->render_mgr.text.vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, g_engine->render_mgr.text.texture); glBindBuffer(GL_ARRAY_BUFFER, g_engine->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 set_active_camera(camera_t* camera) { g_engine->render_mgr.active_camera = camera; } camera_t* get_active_camera(void) { return g_engine->render_mgr.active_camera; } static 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); #ifndef BUILD_DEBUG exit(EXIT_FAILURE); #endif } return id; } static 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); #ifndef BUILD_DEBUG exit(EXIT_FAILURE); #endif } glDeleteShader(vertex); glDeleteShader(fragment); } return shader; }