1
0
Fork 0
2022-untitled-game/code/src/engine/renderer.c

584 lines
15 KiB
C

// 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
}