polyadvent

A game engine from scratch in C
git clone git://jb55.com/polyadvent
Log | Files | Refs | README

commit 9bdcd6a7afa5feab002d9d8e02682eb911026853
parent 03c31f0aff35f83f528646d4a36338a09595911b
Author: William Casarin <jb55@jb55.com>
Date:   Sun, 23 Sep 2018 12:33:11 -0700

shader hot reloading

Diffstat:
MMakefile | 6+++---
Metc/shaders/test.f.glsl | 2+-
Metc/shaders/test.v.glsl | 42+++++++++++++++++++++++-------------------
Msrc/file.c | 13+++++++++++--
Msrc/file.h | 5+++++
Msrc/game.c | 2+-
Msrc/game.h | 62++++++++++++++++++++++++++++++++------------------------------
Msrc/render.c | 136+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/shader.c | 141++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/shader.h | 30++++++++++++++++++++++++++++--
Msrc/update.c | 171++++++++++++++++++++++++++++++++++++++-----------------------------------------
11 files changed, 372 insertions(+), 238 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,14 +1,14 @@ NAME ?= polyadvent BIN ?= $(NAME) PREFIX ?= /usr/local -CFLAGS = -ggdb -Ofast -I src -Wall -Werror -Wextra -std=c99 \ +DEFS= -DGLFW_INCLUDE_NONE -DDEBUG +CFLAGS = $(DEFS) -ggdb -Ofast -I src -Wall -Werror -Wextra -std=c99 \ -Wno-unused-function \ -Wno-unused-parameter \ -Wno-unused-variable \ -Wno-cast-align \ -Wno-padded LDFLAGS = -lSDL2 -lGL -DEFS= -DGLFW_INCLUDE_NONE SRC=src OBJS = $(SRC)/window.o @@ -52,7 +52,7 @@ include $(OBJS:.o=.d) rm -f $@.$$$$ $(BIN): $(OBJS) - $(CC) $(CFLAGS) $(DEFS) $^ $(LDFLAGS) -o $@ + $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ install: $(BIN) install -d $(PREFIX)/bin diff --git a/etc/shaders/test.f.glsl b/etc/shaders/test.f.glsl @@ -14,7 +14,7 @@ uniform bool diffuse_on; uniform mat4 normal_matrix; vec3 apply_fog(in vec3 rgb, in float distance, in vec3 ray_orig, in vec3 ray_dir) { - const float b = 0.00046; + const float b = 0.00036; const float v = 1.0; const float zs = 1.0; diff --git a/etc/shaders/test.v.glsl b/etc/shaders/test.v.glsl @@ -20,23 +20,27 @@ out vec3 v_ray; void main() { - vec4 v4_normal = vec4(normal, 1); - vec4 trans_normal = normal_matrix * v4_normal; - vec4 v4_pos = vec4(position, 1.0); - gl_Position = mvp * v4_pos; - v_light = dot(trans_normal, vec4(normalize(light_dir), 0)); - - if (position.z <= 1.0) - v_color = vec3(0.0, 0.5, 0.79); - else if (position.z <= 2.0) - v_color = vec3(0.9176, 0.8156, 0.6588); - else if (position.z <= 5.0) - v_color = vec3(0.6274, 0.749, 0.156); - else if (position.z <= 100.0) - v_color = vec3(0.5, 0.5, 0.5); - else - v_color = vec3(1.0, 1.0, 1.0) * 0.9; - - v_normal = trans_normal.xyz; - v_ray = camera_position - (world * v4_pos).xyz; + vec4 v4_normal = vec4(normal, 1); + vec4 trans_normal = normal_matrix * v4_normal; + vec4 v4_pos = vec4(position, 1.0); + gl_Position = mvp * v4_pos; + v_light = dot(trans_normal, vec4(normalize(light_dir), 0)); + + // if (position.z <= 1.0) + // v_color = vec3(0.0, 0.5, 0.79); + // else if (position.z <= 2.0) + // v_color = vec3(0.9176, 0.8156, 0.6588); + // else if (position.z <= 20.0) + // v_color = vec3(0.6274, 0.749, 0.156); + // else if (position.z <= 60.0) + // v_color = vec3(0.8784, 0.8784, 0.7); + // else if (position.z <= 100.0) + // v_color = vec3(0.5, 0.5, 0.5)9 + // else + // v_color = vec3(1.0, 1.0, 1.0) * 0.9; + + v_color = vec3(position.z*0.05, position.z*0.005, position.z*0.0001) * 0.5; + + v_normal = trans_normal.xyz; + v_ray = camera_position - (world * v4_pos).xyz; } diff --git a/src/file.c b/src/file.c @@ -3,8 +3,17 @@ #include <stdlib.h> #include "file.h" -void * -file_contents(const char *filename, size_t *length) { +#include <sys/stat.h> + +time_t file_mtime(const char *filename) { + // TODO: windows file_mtime + struct stat stats; + stat(filename, &stats); + + return stats.st_mtime; +} + +void *file_contents(const char *filename, size_t *length) { FILE *f = fopen(filename, "r"); void *buffer; diff --git a/src/file.h b/src/file.h @@ -1,6 +1,11 @@ #ifndef POLYADVEMT_FILE_H #define POLYADVEMT_FILE_H +#include <time.h> + +time_t +file_mtime(const char *filename); + void * file_contents(const char *filename, size_t *length); diff --git a/src/game.c b/src/game.c @@ -27,7 +27,7 @@ void game_init(struct game *game) { struct terrain *terrain = &game->terrain; mat4 *light_dir = game->test_resources.light_dir; - const double size = 5000; + const double size = 20000; const double pdist = 1.7; terrain->settings = (struct perlin_settings){ diff --git a/src/game.h b/src/game.h @@ -6,6 +6,7 @@ #include "input.h" #include "node.h" #include "terrain.h" +#include "shader.h" #define PLAYER_HEIGHT 1.7 @@ -14,36 +15,37 @@ * NOTE: just for testing right now */ struct resources { - struct vbo vertex_buffer, element_buffer, normal_buffer; - GLuint vertex_shader, fragment_shader, program; - - struct uniforms { - GLint camera_position; - GLint light_dir; - GLint mvp; - GLint normal_matrix; - GLint view; - GLint fog_on; - GLint diffuse_on; - GLint model_view; - GLint world; - } uniforms; - - struct attributes { - gpu_addr position; - gpu_addr normal; - } attributes; - - struct node root; - struct node player; - struct node camera; - struct node terrain_node; - - bool fog_on, diffuse_on; - - float test_mvp[MAT4_ELEMS]; - float light_dir[3]; - float camera_persp[MAT4_ELEMS]; + struct vbo vertex_buffer, element_buffer, normal_buffer; + + struct gpu_program program; + + struct uniforms { + GLint camera_position; + GLint light_dir; + GLint mvp; + GLint normal_matrix; + GLint view; + GLint fog_on; + GLint diffuse_on; + GLint model_view; + GLint world; + } uniforms; + + struct attributes { + gpu_addr position; + gpu_addr normal; + } attributes; + + struct node root; + struct node player; + struct node camera; + struct node terrain_node; + + bool fog_on, diffuse_on; + + float test_mvp[MAT4_ELEMS]; + float light_dir[3]; + float camera_persp[MAT4_ELEMS]; }; struct game { diff --git a/src/render.c b/src/render.c @@ -56,89 +56,93 @@ static const GLushort cube_indices[] = { void init_gl(struct resources *resources, int width, int height) { - float tmp_matrix[16]; - glEnable(GL_DEPTH_TEST); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); - - // VBOs - make_vertex_buffer( - GL_ARRAY_BUFFER, - cube_vertices, - sizeof(cube_vertices), - &resources->vertex_buffer - ); + struct shader vertex, fragment; + float tmp_matrix[16]; + int ok = 0; - // cube normals - make_vertex_buffer( - GL_ARRAY_BUFFER, - cube_normals, - sizeof(cube_normals), - &resources->normal_buffer - ); + glEnable(GL_DEPTH_TEST); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); - // cube indices - make_index_buffer( - GL_ELEMENT_ARRAY_BUFFER, - cube_indices, - sizeof(cube_indices), - &resources->element_buffer - ); + // VBOs + make_vertex_buffer( + GL_ARRAY_BUFFER, + cube_vertices, + sizeof(cube_vertices), + &resources->vertex_buffer + ); - // Shaders - resources->vertex_shader = make_shader( - GL_VERTEX_SHADER, - SHADER("test.v.glsl") - ); + // cube normals + make_vertex_buffer( + GL_ARRAY_BUFFER, + cube_normals, + sizeof(cube_normals), + &resources->normal_buffer + ); - assert(resources->vertex_shader != 0); - resources->fragment_shader = make_shader( - GL_FRAGMENT_SHADER, - SHADER("test.f.glsl") - ); - assert(resources->fragment_shader != 0); + // cube indices + make_index_buffer( + GL_ELEMENT_ARRAY_BUFFER, + cube_indices, + sizeof(cube_indices), + &resources->element_buffer + ); + + // Shaders + ok = + make_shader(GL_VERTEX_SHADER, SHADER("test.v.glsl"), &vertex); + + assert(ok); + + ok = + make_shader(GL_FRAGMENT_SHADER, SHADER("test.f.glsl"), &fragment); + + assert(ok); - // camera - mat4_perspective(90 /* fov */, (float)width / height, 1, 5000, resources->camera_persp); + // camera + mat4_perspective(90 /* fov */, + (float)width / height, + 1, + 5000, + resources->camera_persp); - // Shader program - resources->program = - make_program(resources->vertex_shader, resources->fragment_shader); + // Shader program + ok = + make_program(&vertex, &fragment, &resources->program); - assert(resources->program != 0); + assert(ok); - // Program variables - resources->uniforms.camera_position - = glGetUniformLocation(resources->program, "camera_position"); + // Program variables + resources->uniforms.camera_position = + glGetUniformLocation(resources->program.handle, "camera_position"); - resources->uniforms.light_dir - = glGetUniformLocation(resources->program, "light_dir"); + resources->uniforms.light_dir = + glGetUniformLocation(resources->program.handle, "light_dir"); - resources->uniforms.world - = glGetUniformLocation(resources->program, "world"); + resources->uniforms.world = + glGetUniformLocation(resources->program.handle, "world"); - resources->uniforms.fog_on - = glGetUniformLocation(resources->program, "fog_on"); + resources->uniforms.fog_on = + glGetUniformLocation(resources->program.handle, "fog_on"); - resources->uniforms.diffuse_on - = glGetUniformLocation(resources->program, "diffuse_on"); + resources->uniforms.diffuse_on = + glGetUniformLocation(resources->program.handle, "diffuse_on"); - resources->uniforms.mvp - = glGetUniformLocation(resources->program, "mvp"); + resources->uniforms.mvp = + glGetUniformLocation(resources->program.handle, "mvp"); - resources->uniforms.model_view - = glGetUniformLocation(resources->program, "model_view"); + resources->uniforms.model_view = + glGetUniformLocation(resources->program.handle, "model_view"); - resources->uniforms.normal_matrix - = glGetUniformLocation(resources->program, "normal_matrix"); + resources->uniforms.normal_matrix = + glGetUniformLocation(resources->program.handle, "normal_matrix"); - resources->attributes.normal - = (gpu_addr)glGetAttribLocation(resources->program, "normal"); + resources->attributes.normal = + (gpu_addr)glGetAttribLocation(resources->program.handle, "normal"); - resources->attributes.position - = (gpu_addr)glGetAttribLocation(resources->program, "position"); + resources->attributes.position = + (gpu_addr)glGetAttribLocation(resources->program.handle, "position"); - assert(resources->program != 0); } @@ -214,7 +218,7 @@ void render (struct game *game, struct geometry *geom) { struct node *player = &res->player; struct node *camera = &res->camera; - glUseProgram(res->program); + glUseProgram(res->program.handle); /* static float v3[] = { 1, 1, 0 }; */ /* v3[1] = fade_factor * 1.4f; */ diff --git a/src/shader.c b/src/shader.c @@ -1,17 +1,18 @@ #include <stdlib.h> #include <stdio.h> +#include <assert.h> #include "file.h" #include "gl.h" #include "debug.h" #include "shader.h" -GLuint -make_shader(GLenum type, const char *filename) { + + +int make_shader(GLenum type, const char *filename, struct shader *shader) { size_t length; GLchar *source = (GLchar *)file_contents(filename, &length); - GLuint shader; GLint shader_ok; if (!source) @@ -19,42 +20,130 @@ make_shader(GLenum type, const char *filename) { source[length] = '\0'; - shader = glCreateShader(type); - glShaderSource(shader, 1, (const GLchar**)&source, (GLint*)&length); + shader->filename = filename; + shader->type = type; + shader->handle = glCreateShader(type); + + glShaderSource(shader->handle, 1, (const GLchar**)&source, (GLint*)&length); free(source); - glCompileShader(shader); + glCompileShader(shader->handle); - glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_ok); + glGetShaderiv(shader->handle, GL_COMPILE_STATUS, &shader_ok); if (!shader_ok) { fprintf(stderr, "Failed to compile %s:\n", filename); - show_info_log(shader); - glDeleteShader(shader); + show_info_log(shader->handle); + glDeleteShader(shader->handle); return 0; } - return shader; +#ifdef DEBUG + shader->load_mtime = + file_mtime(shader->filename); +#endif + + return 1; } +#ifdef DEBUG +int reload_program(struct gpu_program *program) { + struct shader vert, frag; + struct shader *pvert, *pfrag; + time_t frag_mtime, vert_mtime; + int ok; -GLuint -make_program(GLuint vertex_shader, GLuint fragment_shader) { - GLint program_ok; - GLuint program = glCreateProgram(); + vert_mtime = file_mtime(program->vertex.filename); + frag_mtime = file_mtime(program->fragment.filename); - if (vertex_shader) - glAttachShader(program, vertex_shader); + int vert_changed = + vert_mtime != program->vertex.load_mtime; - glAttachShader(program, fragment_shader); - glLinkProgram(program); + int frag_changed = + frag_mtime != program->fragment.load_mtime; - glGetProgramiv(program, GL_LINK_STATUS, &program_ok); - if (!program_ok) { - fprintf(stderr, "Failed to link shader program:\n"); - show_info_log(program); - glDeleteProgram(program); - return 0; - } - return program; + if (!vert_changed && !frag_changed) + return 2; + + // recompile vertex shader + if (vert_changed) { + glDeleteShader(program->vertex.handle); + + ok = + make_shader(GL_VERTEX_SHADER, + program->vertex.filename, + &vert); + + if (!ok) + return 0; + } + + // recompile fragment shader + if (frag_changed) { + glDeleteShader(program->fragment.handle); + + ok = + make_shader(GL_FRAGMENT_SHADER, + program->fragment.filename, + &frag); + + if (!ok) + return 0; + } + + if (vert_changed) { + glDeleteShader(program->vertex.handle); + pvert = &vert; + vert.load_mtime = vert_mtime; + } + else + pvert = &program->vertex; + + if (frag_changed) { + glDeleteShader(program->fragment.handle); + pfrag = &frag; + frag.load_mtime = frag_mtime; + } + else + pfrag = &program->fragment; + + glDeleteProgram(program->handle); + + make_program(pvert, pfrag, program); + + return 1; +} +#endif + +int +make_program(struct shader *vertex, struct shader *fragment, + struct gpu_program *program) +{ + GLint program_ok; + + // TODO: relax these constraints + assert(vertex); + assert(fragment); + + program->handle = + glCreateProgram(); + + program->fragment = *fragment; + program->vertex = *vertex; + + glAttachShader(program->handle, vertex->handle); + glAttachShader(program->handle, fragment->handle); + + glLinkProgram(program->handle); + + glGetProgramiv(program->handle, GL_LINK_STATUS, &program_ok); + + if (!program_ok) { + fprintf(stderr, "Failed to link shader program:\n"); + show_info_log(program->handle); + glDeleteProgram(program->handle); + return 0; + } + + return 1; } diff --git a/src/shader.h b/src/shader.h @@ -1,9 +1,35 @@ #ifndef POLYADVENT_SHADER_H #define POLYADVENT_SHADER_H +#include <time.h> +#include "gl.h" + #define SHADER(f) "etc/shaders/" f -GLuint make_shader(GLenum type, const char *filename); -GLuint make_program(GLuint vertex_shader, GLuint fragment_shader); +struct shader { + GLenum type; + GLuint handle; + const char *filename; +#ifdef DEBUG + time_t load_mtime; +#endif +}; + +struct gpu_program { + struct shader vertex; + struct shader fragment; + GLuint handle; +}; + + +#ifdef DEBUG +int reload_program(struct gpu_program *program); +#endif + +int make_shader(GLenum type, const char *filename, struct shader *shader); + +int make_program(struct shader *vertex, + struct shader *fragment, + struct gpu_program *program); #endif /* POLYADVENT_SHADER_H */ diff --git a/src/update.c b/src/update.c @@ -8,6 +8,8 @@ #include "camera.h" #include "poisson.h" #include "uniform.h" +#include "shader.h" +#include "file.h" static void movement(struct game *game, struct node *node, float speed_mult) { float amt = 0.03; @@ -151,98 +153,91 @@ static void player_movement(struct game *game) { } -void update (struct game *game, u32 dt) { - static int passed = 0; - static double last_ox, last_oy, last_oz; - static int last_gen_time = 50; - static int toggle_fog = 0, toggle_diffuse = 0; - static float n = 1; - static int first = 1; - struct resources *res = &game->test_resources; - static int stopped = 0; - struct perlin_settings *ts = &game->terrain.settings; - struct node *tnode = &game->test_resources.terrain_node; - struct node *root = &game->test_resources.root; - float *light = res->light_dir; - - if (first) { - update_terrain(game); - first = 0; - } +#ifdef DEBUG +static int try_reload_shaders(struct resources *res) { + int ret; + printf("reloading shaders... "); - if (game->input.modifiers & KMOD_LALT) { - movement(game, &res->camera, 1.0); - } - else if (game->input.modifiers & KMOD_RCTRL) { - movement(game, &res->terrain_node, 5.0); - } - else { - player_movement(game); - } + ret = reload_program(&res->program); - if (game->input.keystates[SDL_SCANCODE_C]) - printf("light_dir %f %f %f\n", light[0], light[1], light[2]); - - if (game->input.keystates[SDL_SCANCODE_F]) - toggle_fog = 1; - - if (game->input.keystates[SDL_SCANCODE_G]) - toggle_diffuse = 1; - - int space_down = game->input.keystates[SDL_SCANCODE_SPACE]; - - if (space_down) { - if (!stopped) { - printf("terrain amp %f exp %f freq %f (%d ms)\n", - ts->amplitude, - ts->exp, - ts->freq, - last_gen_time); - stopped = 1; - } - else { - stopped = 0; - } - } + if (ret == 2) + printf("nothing to reload\n"); + else if (ret == 1) + printf("success.\n"); + else + printf("failed.\n"); - if (space_down ) { - passed += dt; - } else { - if (toggle_fog) { - res->fog_on = !res->fog_on; - toggle_fog = 0; - } - if (toggle_diffuse) { - res->diffuse_on = !res->diffuse_on; - toggle_diffuse = 0; - } - passed = 0; - - double ox = tnode->pos[0]; - double oy = tnode->pos[1]; - - bool changed = last_ox != ox || last_oy != oy || last_oz != tnode->pos[2]; - - - if (!stopped && changed) { - int t1 = SDL_GetTicks(); - update_terrain(game); - last_ox = ts->ox = ox; - last_oy = ts->oy = oy; - last_oz = tnode->pos[2] = max(tnode->pos[2], 5.0); - int t2 = SDL_GetTicks(); - last_gen_time = t2 - t1; - - n += 0.01f; - } - } - /* res->light_dir[0] = fabs(cos(n)); */ - /* res->light_dir[1] = fabs(sin(n+50.0)); */ - /* res->light_dir[2] = 1; */ - /* res->light_dir[2] = fabs(sin(n+100.0)*cos(n)); */ - n += 0.001f; + return ret; +} +#endif - node_recalc(root); +void update (struct game *game, u32 dt) { + static double last_ox, last_oy, last_oz; + static int toggle_fog = 0, toggle_diffuse = 0; + static float n = 1; + static int first = 1; + struct resources *res = &game->test_resources; + struct perlin_settings *ts = &game->terrain.settings; + struct node *tnode = &game->test_resources.terrain_node; + struct node *root = &game->test_resources.root; + float *light = res->light_dir; + + if (first) { + update_terrain(game); + first = 0; + } + + if (game->input.modifiers & KMOD_LALT) { + movement(game, &res->camera, 1.0); + } + else if (game->input.modifiers & KMOD_RCTRL) { + movement(game, &res->terrain_node, 5.0); + } + else { + player_movement(game); + } + +#ifdef DEBUG + if (game->input.keystates[SDL_SCANCODE_R]) + try_reload_shaders(res); +#endif + + if (game->input.keystates[SDL_SCANCODE_C]) + printf("light_dir %f %f %f\n", light[0], light[1], light[2]); + + if (game->input.keystates[SDL_SCANCODE_F]) + toggle_fog = 1; + + if (game->input.keystates[SDL_SCANCODE_G]) + toggle_diffuse = 1; + + if (toggle_fog) { + res->fog_on = !res->fog_on; + toggle_fog = 0; + } + + if (toggle_diffuse) { + res->diffuse_on = !res->diffuse_on; + toggle_diffuse = 0; + } + + double ox = tnode->pos[0]; + double oy = tnode->pos[1]; + + bool changed = last_ox != ox || last_oy != oy || last_oz != tnode->pos[2]; + + if (changed) { + update_terrain(game); + last_ox = ts->ox = ox; + last_oy = ts->oy = oy; + last_oz = tnode->pos[2] = max(tnode->pos[2], 5.0); + + n += 0.01f; + } + + n += 0.001f; + + node_recalc(root); }