├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── build_msvc.bat ├── images ├── ayaya.png ├── flushed.png ├── gl.png ├── jebaited.png ├── monkaS.png ├── pog.png ├── poggers.png ├── rms.png ├── sleep.png └── yep.png ├── scene.conf ├── setup_glfw.bat ├── shaders ├── main.frag └── main.vert └── src ├── geo.c ├── geo.h ├── main.c ├── region.c ├── region.h ├── rgba.h ├── stb_image.h ├── sv.c └── sv.h /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | Dependencies 3 | *.exe 4 | kidito 5 | cube -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Alexey Kutepov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GL_PKGS=glfw3 glew 2 | CFLAGS=-Wall -Wextra 3 | SRC=src/main.c src/geo.c src/sv.c src/region.c 4 | 5 | all: kidito 6 | 7 | kidito: $(SRC) 8 | $(CC) $(CFLAGS) `pkg-config --cflags $(GL_PKGS)` -o kidito $(SRC) `pkg-config --libs $(GL_PKGS)` -lm 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kidito 2 | 3 | ## Quick Start 4 | 5 | ```console 6 | $ make 7 | $ ./kidito 8 | ``` 9 | 10 | ## [scene.conf](./scene.conf) 11 | 12 | | Key | Description | 13 | |---------------|-----------------------------------| 14 | | `frag_shader` | path to the fragment shader | 15 | | `vert_shader` | path to the vertex shader | 16 | | `texture` | path to the image for the texture | 17 | 18 | ## Controls 19 | 20 | | Shortcut | Description | 21 | |-----------------------------------|--------------------------------------------------------------------------------------| 22 | | F5 | Hot-reload [./scene.conf](./scene.conf) and all of the associated with it resources. | 23 | | SPACE | Pause/unpause the time uniform variable in shaders | 24 | | / | Manually step in time back and forth in the paused mode. | 25 | 26 | ## Objectives 27 | 28 | - [x] Generate cube mesh 29 | - [x] Render the cube mesh with perspective projection 30 | - [x] Texture the cube 31 | - [x] Fog 32 | - [x] Simple lighting (the classical one with normals and stuff) 33 | -------------------------------------------------------------------------------- /build_msvc.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem launch this from msvc-enabled console 3 | 4 | set CFLAGS=/std:c11 /O2 /FC /W4 /WX /Zl /D_USE_MATH_DEFINES /wd4996 /nologo 5 | set INCLUDES=/I Dependencies\GLFW\include /I Dependencies\GLEW\include 6 | set LIBS=Dependencies\GLFW\lib\glfw3.lib Dependencies\GLEW\lib\glew32s.lib opengl32.lib User32.lib Gdi32.lib Shell32.lib 7 | 8 | cl.exe %CFLAGS% %INCLUDES% /Fe"main.exe" ./main.c %LIBS% /link /NODEFAULTLIB:libcmt.lib 9 | -------------------------------------------------------------------------------- /images/ayaya.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/kidito/7ce2cd3059788fbaec323bae9d962a27dc710cb5/images/ayaya.png -------------------------------------------------------------------------------- /images/flushed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/kidito/7ce2cd3059788fbaec323bae9d962a27dc710cb5/images/flushed.png -------------------------------------------------------------------------------- /images/gl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/kidito/7ce2cd3059788fbaec323bae9d962a27dc710cb5/images/gl.png -------------------------------------------------------------------------------- /images/jebaited.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/kidito/7ce2cd3059788fbaec323bae9d962a27dc710cb5/images/jebaited.png -------------------------------------------------------------------------------- /images/monkaS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/kidito/7ce2cd3059788fbaec323bae9d962a27dc710cb5/images/monkaS.png -------------------------------------------------------------------------------- /images/pog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/kidito/7ce2cd3059788fbaec323bae9d962a27dc710cb5/images/pog.png -------------------------------------------------------------------------------- /images/poggers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/kidito/7ce2cd3059788fbaec323bae9d962a27dc710cb5/images/poggers.png -------------------------------------------------------------------------------- /images/rms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/kidito/7ce2cd3059788fbaec323bae9d962a27dc710cb5/images/rms.png -------------------------------------------------------------------------------- /images/sleep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/kidito/7ce2cd3059788fbaec323bae9d962a27dc710cb5/images/sleep.png -------------------------------------------------------------------------------- /images/yep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsoding/kidito/7ce2cd3059788fbaec323bae9d962a27dc710cb5/images/yep.png -------------------------------------------------------------------------------- /scene.conf: -------------------------------------------------------------------------------- 1 | frag_shader = ./shaders/main.frag 2 | vert_shader = ./shaders/main.vert 3 | # texture = ./images/flushed.png 4 | # texture = ./images/gl.png 5 | # texture = ./images/yep.png 6 | # texture = ./images/monkaS.png 7 | # texture = ./images/poggers.png 8 | # texture = ./images/sleep.png 9 | # texture = ./images/jebaited.png 10 | # texture = ./images/rms.png 11 | texture = ./images/ayaya.png -------------------------------------------------------------------------------- /setup_glfw.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | curl -fsSL -o glfw-3.3.2.bin.WIN64.zip https://github.com/glfw/glfw/releases/download/3.3.2/glfw-3.3.2.bin.WIN64.zip 4 | tar -xf glfw-3.3.2.bin.WIN64.zip 5 | mkdir Dependencies\GLFW\lib\ 6 | move glfw-3.3.2.bin.WIN64\lib-vc2019\glfw3.lib Dependencies\GLFW\lib\glfw3.lib 7 | mkdir Dependencies\GLFW\include\GLFW 8 | move glfw-3.3.2.bin.WIN64\include\GLFW\glfw3.h Dependencies\GLFW\include\GLFW\glfw3.h 9 | del glfw-3.3.2.bin.WIN64.zip 10 | rmdir /s /q glfw-3.3.2.bin.WIN64 11 | 12 | curl -fsSL -o glew-2.1.0-win32.zip https://sourceforge.net/projects/glew/files/glew/2.1.0/glew-2.1.0-win32.zip/download 13 | tar -xf glew-2.1.0-win32.zip 14 | mkdir Dependencies\GLEW\lib\ 15 | move glew-2.1.0\lib\Release\x64\glew32s.lib Dependencies\GLEW\lib\glew32s.lib 16 | mkdir Dependencies\GLEW\include\GL\ 17 | move glew-2.1.0\include\GL\glew.h Dependencies\GLEW\include\GL\glew.h 18 | del glew-2.1.0-win32.zip 19 | rmdir /s /q glew-2.1.0 20 | -------------------------------------------------------------------------------- /shaders/main.frag: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision mediump float; 4 | 5 | uniform sampler2D pog; 6 | uniform float time; 7 | 8 | in vec2 uv; 9 | in vec4 vertex; 10 | in vec4 normal; 11 | out vec4 frag_color; 12 | 13 | #define FOG_MIN 1.0 14 | #define FOG_MAX 50.0 15 | 16 | float fog_factor(float d) 17 | { 18 | if (d <= FOG_MIN) return 0.0; 19 | if (d >= FOG_MAX) return 1.0; 20 | return 1.0 - (FOG_MAX - d) / (FOG_MAX - FOG_MIN); 21 | } 22 | 23 | void main(void) { 24 | vec3 light_source = vec3(0.0, 0.0, 00.0); 25 | float a = abs(dot(normalize(light_source - vertex.xyz), normal.xyz)); 26 | vec4 t = texture(pog, uv); 27 | 28 | frag_color = mix( 29 | vec4(t.xyz * vec3(1.0, 1.0, 1.0) * a, 1.0), 30 | vec4(0.0, 0.0, 0.0, 1.0), 31 | fog_factor(length(vertex))); 32 | } 33 | -------------------------------------------------------------------------------- /shaders/main.vert: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision mediump float; 4 | 5 | uniform float time; 6 | uniform vec2 resolution; 7 | 8 | layout(location = 0) in vec4 vertex_position; 9 | layout(location = 1) in vec2 vertex_uv; 10 | layout(location = 2) in vec4 vertex_normal; 11 | 12 | out vec2 uv; 13 | out vec4 vertex; 14 | out vec4 normal; 15 | 16 | mat4 mat4_translate(vec3 dir) 17 | { 18 | mat4 result = mat4(1.0); 19 | result[3] = vec4(dir, 1.0); 20 | return result; 21 | } 22 | 23 | mat4 mat4_scale(vec3 s) 24 | { 25 | mat4 result = mat4(1.0); 26 | result[0][0] = s.x; 27 | result[1][1] = s.y; 28 | result[2][2] = s.z; 29 | return result; 30 | } 31 | 32 | mat4 mat4_rotate_y(float angle) 33 | { 34 | mat4 result = mat4(1.0); 35 | result[0][0] = cos(angle); 36 | result[2][0] = sin(angle); 37 | result[0][2] = -sin(angle); 38 | result[2][2] = cos(angle); 39 | return result; 40 | } 41 | 42 | mat4 mat4_rotate_z(float angle) 43 | { 44 | mat4 result = mat4(1.0); 45 | result[0][0] = cos(angle); 46 | result[0][1] = sin(angle); 47 | result[1][0] = -sin(angle); 48 | result[1][1] = cos(angle); 49 | return result; 50 | } 51 | 52 | mat4 mat4_perspective(float fovy, float aspect, float near, float far) 53 | { 54 | float tan_half_fovy = tan(fovy * 0.5); 55 | mat4 result = mat4(0.0); 56 | result[0][0] = 1.0 / (aspect * tan_half_fovy); 57 | result[1][1] = 1.0 / tan_half_fovy; 58 | result[2][2] = far / (near - far); 59 | result[3][2] = -1.0; 60 | result[2][3] = -(far * near) / (far - near); 61 | return result; 62 | } 63 | 64 | void main(void) 65 | { 66 | float aspect = resolution.x / resolution.y; 67 | float fovy = radians(90.0);// + sin(time); 68 | 69 | mat4 camera = ( 70 | mat4_translate(vec3(0.0, 0.0, -30.0 + 30.0 * sin(time))) * 71 | mat4_rotate_z(time) * 72 | mat4_rotate_y(time) * 73 | mat4_translate(vertex_normal.xyz * 20.0 * ((sin(time) + 1.0) / 2.0)) * 74 | mat4_scale(vec3(25.0, 25.0, 25.0)) * 75 | mat4_translate(vec3(-0.5, -0.5, -0.5)) * 76 | mat4(1.0) 77 | ); 78 | 79 | vec4 camera_pos = ( 80 | camera * 81 | vertex_position 82 | ); 83 | 84 | gl_Position = ( 85 | mat4_perspective(fovy, aspect, 1.0, 500.0) * 86 | camera_pos 87 | ); 88 | 89 | uv = vertex_uv; 90 | vertex = camera_pos; 91 | normal = ( 92 | mat4_rotate_z(time) * 93 | mat4_rotate_y(time) * 94 | vertex_normal 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /src/geo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "./geo.h" 6 | 7 | V4 v4_add(V4 a, V4 b) 8 | { 9 | for (size_t i = 0; i < TRI_VERTICES; ++i) { 10 | a.cs[i] += b.cs[i]; 11 | } 12 | return a; 13 | } 14 | 15 | V4 v4_scale(V4 a, float s) 16 | { 17 | for (size_t i = 0; i < TRI_VERTICES; ++i) { 18 | a.cs[i] *= s; 19 | } 20 | return a; 21 | } 22 | 23 | void generate_cube_mesh(V4 mesh[TRIS_PER_CUBE][TRI_VERTICES], 24 | RGBA colors[TRIS_PER_CUBE][TRI_VERTICES], 25 | V2 uvs[TRIS_PER_CUBE][TRI_VERTICES], 26 | V4 normals[TRIS_PER_CUBE][TRI_VERTICES]) 27 | { 28 | size_t count = 0; 29 | 30 | static const RGBA face_colors[CUBE_FACE_PAIRS][PAIR_COMPS] = { 31 | {RED, GREEN}, 32 | {BLUE, YELLOW}, 33 | {PURPLE, CYAN}, 34 | }; 35 | 36 | static const size_t face_pairs[CUBE_FACE_PAIRS][V3_COMPS] = { 37 | {X, Y, Z}, 38 | {Z, Y, X}, 39 | {X, Z, Y} 40 | }; 41 | 42 | for (size_t face_pair_index = 0; face_pair_index < CUBE_FACE_PAIRS; ++face_pair_index) { 43 | for (size_t pair_comp_index = 0; pair_comp_index < PAIR_COMPS; ++pair_comp_index) { 44 | for (size_t tri = 0; tri < TRIS_PER_FACE; ++tri) { 45 | for (size_t vert = 0; vert < TRI_VERTICES; ++vert) { 46 | const size_t strip_index = tri + vert; 47 | const size_t A = face_pairs[face_pair_index][0]; 48 | const size_t B = face_pairs[face_pair_index][1]; 49 | const size_t C = face_pairs[face_pair_index][2]; 50 | 51 | // Mesh 52 | { 53 | 54 | mesh[count][vert].cs[A] = (float) (strip_index & 1); 55 | mesh[count][vert].cs[B] = (float) (strip_index >> 1); 56 | mesh[count][vert].cs[C] = (float) pair_comp_index;; 57 | mesh[count][vert].cs[W] = 1.0f; 58 | } 59 | 60 | // Color 61 | { 62 | colors[count][vert] = 63 | face_colors[face_pair_index][pair_comp_index]; 64 | } 65 | 66 | // UVs 67 | { 68 | uvs[count][vert].cs[X] = (float) (strip_index & 1); 69 | uvs[count][vert].cs[Y] = (float) (strip_index >> 1); 70 | } 71 | 72 | // Normals 73 | { 74 | normals[count][vert].cs[A] = 0.0f; 75 | normals[count][vert].cs[B] = 0.0f; 76 | normals[count][vert].cs[C] = (float) (2 * (int) pair_comp_index - 1); 77 | normals[count][vert].cs[W] = 1.0f; 78 | } 79 | } 80 | count += 1; 81 | } 82 | } 83 | } 84 | 85 | assert(count == TRIS_PER_CUBE); 86 | } 87 | 88 | Mat4 mat4_id(void) 89 | { 90 | return (Mat4) { 91 | .vs = { 92 | {1.0f, 0.0f, 0.0f, 0.0f}, 93 | {0.0f, 1.0f, 0.0f, 0.0f}, 94 | {0.0f, 0.0f, 1.0f, 0.0f}, 95 | {0.0f, 0.0f, 0.0f, 1.0f}, 96 | }, 97 | }; 98 | } 99 | 100 | V4 mat4_mult_v4(Mat4 mat, V4 vec) 101 | { 102 | V4 result = {0}; 103 | for (int row = 0; row < V4_COMPS; ++row) { 104 | for (int col = 0; col < V4_COMPS; ++col) { 105 | result.cs[row] += mat.vs[row][col] * vec.cs[col]; 106 | } 107 | } 108 | return result; 109 | } 110 | 111 | Mat4 mat4_mult_mat4(Mat4 m1, Mat4 m2) 112 | { 113 | Mat4 result = {0}; 114 | 115 | for (int row = 0; row < V4_COMPS; ++row) { 116 | for (int col = 0; col < V4_COMPS; ++col) { 117 | for (int t = 0; t < V4_COMPS; ++t) { 118 | result.vs[row][col] += m1.vs[row][t] * m2.vs[t][col]; 119 | } 120 | } 121 | } 122 | 123 | return result; 124 | } 125 | 126 | Mat4 mat4_translate(float x, float y, float z) 127 | { 128 | return (Mat4) { 129 | .vs = { 130 | {1.0f, 0.0f, 0.0f, x}, 131 | {0.0f, 1.0f, 0.0f, y}, 132 | {0.0f, 0.0f, 1.0f, z}, 133 | {0.0f, 0.0f, 0.0f, 1.0f}, 134 | } 135 | }; 136 | } 137 | 138 | Mat4 mat4_scale(float x, float y, float z) 139 | { 140 | return (Mat4) { 141 | .vs = { 142 | { x, 0.0f, 0.0f, 0.0f}, 143 | {0.0f, y, 0.0f, 0.0f}, 144 | {0.0f, 0.0f, z, 0.0f}, 145 | {0.0f, 0.0f, 0.0f, 1.0f}, 146 | } 147 | }; 148 | } 149 | 150 | // https://en.wikipedia.org/wiki/Rotation_matrix 151 | Mat4 mat4_rotate_y(float angle) 152 | { 153 | return (Mat4) { 154 | .vs = { 155 | { cosf(angle), 0.0f, sinf(angle), 0.0f}, 156 | { 0.0f, 1.0f, 0.0f, 0.0f}, 157 | {-sinf(angle), 0.0f, cosf(angle), 0.0f}, 158 | { 0.0f, 0.0f, 0.0f, 1.0f}, 159 | } 160 | }; 161 | } 162 | 163 | Mat4 mat4_rotate_z(float angle) 164 | { 165 | return (Mat4) { 166 | .vs = { 167 | {cosf(angle), -sinf(angle), 0.0f, 0.0f}, 168 | {sinf(angle), cosf(angle), 0.0f, 0.0f}, 169 | { 0.0f, 0.0f, 1.0f, 0.0f}, 170 | { 0.0f, 0.0f, 0.0f, 1.0f}, 171 | } 172 | }; 173 | } 174 | 175 | // NOTE: stolen from glm::perspectiveRH_NO() 176 | Mat4 mat4_perspective(float fovy, float aspect, float near, float far) 177 | { 178 | const float tan_half_fovy = tanf(fovy * 0.5f); 179 | 180 | Mat4 result = {0}; 181 | result.vs[0][0] = 1.0f / (aspect * tan_half_fovy); 182 | result.vs[1][1] = 1.0f / tan_half_fovy; 183 | result.vs[2][2] = far / (near - far); 184 | result.vs[2][3] = -1.0f; 185 | result.vs[3][2] = -(far * near) / (far - near); 186 | return result; 187 | } 188 | -------------------------------------------------------------------------------- /src/geo.h: -------------------------------------------------------------------------------- 1 | #ifndef GEO_H_ 2 | #define GEO_H_ 3 | 4 | #include "./rgba.h" 5 | 6 | #define MY_PI 3.14159265359f 7 | 8 | #define X 0 9 | #define Y 1 10 | #define Z 2 11 | #define W 3 12 | 13 | #define V2_COMPS 2 14 | 15 | typedef struct { 16 | float cs[V2_COMPS]; 17 | } V2; 18 | 19 | #define V2_Fmt "V2(%f, %f)" 20 | #define V2_Arg(v2) v2.cs[X], v2.cs[Y] 21 | 22 | #define V3_COMPS 3 23 | #define V4_COMPS 4 24 | #define PAIR_COMPS 2 25 | 26 | typedef struct { 27 | float cs[V4_COMPS]; 28 | } V4; 29 | 30 | #define V4_Fmt "V4(%f, %f, %f, %f)" 31 | #define V4_Arg(v4) v4.cs[X], v4.cs[Y], v4.cs[Z], v4.cs[W] 32 | 33 | V4 v4_add(V4 a, V4 b); 34 | V4 v4_scale(V4 a, float s); 35 | 36 | #define CUBE_FACE_PAIRS 3 37 | #define CUBE_FACES (CUBE_FACE_PAIRS * PAIR_COMPS) 38 | 39 | #define TRI_VERTICES 3 40 | #define TRIS_PER_FACE 2 41 | #define TRIS_PER_CUBE (CUBE_FACES * TRIS_PER_FACE) 42 | 43 | void generate_cube_mesh(V4 mesh[TRIS_PER_CUBE][TRI_VERTICES], 44 | RGBA colors[TRIS_PER_CUBE][TRI_VERTICES], 45 | V2 uvs[TRIS_PER_CUBE][TRI_VERTICES], 46 | V4 normals[TRIS_PER_CUBE][TRI_VERTICES]); 47 | 48 | typedef struct { 49 | float vs[V4_COMPS][V4_COMPS]; 50 | } Mat4; 51 | 52 | V4 mat4_mult_v4(Mat4 mat, V4 vec); 53 | Mat4 mat4_mult_mat4(Mat4 m1, Mat4 m2); 54 | 55 | Mat4 mat4_id(void); 56 | Mat4 mat4_translate(float x, float y, float z); 57 | Mat4 mat4_scale(float x, float y, float z); 58 | Mat4 mat4_rotate_y(float angle); 59 | Mat4 mat4_rotate_z(float angle); 60 | Mat4 mat4_perspective(float fovy, float aspect, float near, float far); 61 | 62 | #endif // GEO_H_ 63 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define GLEW_STATIC 9 | #include 10 | 11 | #define GL_GLEXT_PROTOTYPES 12 | #include 13 | 14 | #include "./geo.h" 15 | #include "./sv.h" 16 | #include "./region.h" 17 | 18 | Region hot_reload_memory; 19 | 20 | #define STBI_MALLOC(size) region_malloc(&hot_reload_memory, size) 21 | #define STBI_FREE(ignored) do {(void)ignored;} while(0) 22 | #define STBI_REALLOC_SIZED(ptr, oldsz, newsz) \ 23 | region_realloc(&hot_reload_memory, ptr, oldsz, newsz) 24 | 25 | #define STB_IMAGE_IMPLEMENTATION 26 | #include "./stb_image.h" 27 | 28 | #define VERTEX_CAPACITY 1000 29 | 30 | #define MANUAL_TIME_STEP 0.05f 31 | #define HOT_RELOAD_ERROR_COLOR 0.5f, 0.0f, 0.0f, 1.0f 32 | #define BACKGROUND_COLOR 0.0f, 0.0f, 0.0f, 0.0f 33 | 34 | bool compile_shader_source(const GLchar *source, GLenum shader_type, GLuint *shader) 35 | { 36 | *shader = glCreateShader(shader_type); 37 | glShaderSource(*shader, 1, &source, NULL); 38 | glCompileShader(*shader); 39 | 40 | GLint compiled = 0; 41 | glGetShaderiv(*shader, GL_COMPILE_STATUS, &compiled); 42 | 43 | if (!compiled) { 44 | GLchar message[1024]; 45 | GLsizei message_size = 0; 46 | glGetShaderInfoLog(*shader, sizeof(message), &message_size, message); 47 | fprintf(stderr, "%.*s\n", message_size, message); 48 | return false; 49 | } 50 | 51 | return true; 52 | } 53 | 54 | bool link_program(GLuint vert_shader, GLuint frag_shader, GLuint *program) 55 | { 56 | *program = glCreateProgram(); 57 | 58 | glAttachShader(*program, vert_shader); 59 | glAttachShader(*program, frag_shader); 60 | glLinkProgram(*program); 61 | 62 | GLint linked = 0; 63 | glGetProgramiv(*program, GL_LINK_STATUS, &linked); 64 | if (!linked) { 65 | GLsizei message_size = 0; 66 | GLchar message[1024]; 67 | 68 | glGetProgramInfoLog(*program, sizeof(message), &message_size, message); 69 | fprintf(stderr, "Program Linking: %.*s\n", message_size, message); 70 | } 71 | 72 | glDeleteShader(vert_shader); 73 | glDeleteShader(frag_shader); 74 | 75 | return program; 76 | } 77 | 78 | // Global variables (fragile people with CS degree look away) 79 | bool program_failed = false; 80 | GLuint program = 0; 81 | 82 | double time = 0.0; 83 | GLint time_location = 0; 84 | bool pause = false; 85 | GLint resolution_location = 0; 86 | 87 | GLuint texture_id = 0; 88 | 89 | void reload_scene(void) 90 | { 91 | const char *const scene_conf_file_path = "./scene.conf"; 92 | const char *vertex_shader_file_path = NULL; 93 | size_t vertex_shader_def_line = 0; 94 | const char *fragment_shader_file_path = NULL; 95 | size_t fragment_shader_def_line = 0; 96 | const char *texture_file_path = NULL; 97 | size_t texture_def_line = 0; 98 | 99 | glClearColor(HOT_RELOAD_ERROR_COLOR); 100 | program_failed = true; 101 | 102 | // reload scene.conf begin 103 | { 104 | String_View scene_conf_content = sv_from_cstr(region_slurp_file(&hot_reload_memory, scene_conf_file_path)); 105 | if (scene_conf_content.data == NULL) { 106 | return; 107 | } 108 | 109 | for (size_t line_number = 0; scene_conf_content.count > 0; line_number++) { 110 | String_View line = sv_chop_by_delim(&scene_conf_content, '\n'); 111 | line = sv_trim(sv_chop_by_delim(&line, '#')); 112 | 113 | if (line.count > 0) { 114 | String_View key = sv_trim(sv_chop_by_delim(&line, '=')); 115 | String_View value = sv_trim(line); 116 | if (sv_eq(key, SV("frag_shader"))) { 117 | fragment_shader_file_path = region_cstr_from_sv(&hot_reload_memory, value); 118 | fragment_shader_def_line = line_number; 119 | } else if (sv_eq(key, SV("vert_shader"))) { 120 | vertex_shader_file_path = region_cstr_from_sv(&hot_reload_memory, value); 121 | vertex_shader_def_line = line_number; 122 | } else if (sv_eq(key, SV("texture"))) { 123 | texture_file_path = region_cstr_from_sv(&hot_reload_memory, value); 124 | texture_def_line = line_number; 125 | } else { 126 | printf("%s:%zu: WARNING: unknown key `"SV_Fmt"`\n", 127 | scene_conf_file_path, line_number, 128 | SV_Arg(key)); 129 | } 130 | } 131 | } 132 | 133 | if (vertex_shader_file_path == NULL) { 134 | fprintf(stderr, "ERROR: `vert_shader` is not specified in %s\n", 135 | scene_conf_file_path); 136 | return; 137 | } 138 | 139 | if (fragment_shader_file_path == NULL) { 140 | fprintf(stderr, "ERROR: `frag_shader` is not specified in %s\n", 141 | scene_conf_file_path); 142 | return; 143 | } 144 | 145 | if (texture_file_path == NULL) { 146 | fprintf(stderr, "ERROR: `texture` is not specified in %s\n", 147 | scene_conf_file_path); 148 | return; 149 | } 150 | } 151 | // reload scene.conf end 152 | 153 | // reload shader program begin 154 | { 155 | glDeleteProgram(program); 156 | 157 | char *vert_source = region_slurp_file(&hot_reload_memory, vertex_shader_file_path); 158 | if (vert_source == NULL) { 159 | fprintf(stderr, "%s:%zu: ERROR: Could not read file `%s`: %s\n", 160 | scene_conf_file_path, vertex_shader_def_line, vertex_shader_file_path, strerror(errno)); 161 | return; 162 | } 163 | 164 | GLuint vert = 0; 165 | if (!compile_shader_source(vert_source, GL_VERTEX_SHADER, &vert)) { 166 | fprintf(stderr, "%s:%zu: ERROR: Failed to compile vertex shader `%s`\n", 167 | scene_conf_file_path, vertex_shader_def_line, vertex_shader_file_path); 168 | return; 169 | } 170 | 171 | char *frag_source = region_slurp_file(&hot_reload_memory, fragment_shader_file_path); 172 | if (frag_source == NULL) { 173 | fprintf(stderr, "%s:%zu: ERROR: Could not read file `%s`: %s\n", 174 | scene_conf_file_path, fragment_shader_def_line, fragment_shader_file_path, strerror(errno)); 175 | return; 176 | } 177 | 178 | GLuint frag = 0; 179 | if (!compile_shader_source(frag_source, GL_FRAGMENT_SHADER, &frag)) { 180 | fprintf(stderr, "%s:%zu: ERROR: Failed to compile fragment shader `%s`\n", 181 | scene_conf_file_path, fragment_shader_def_line, fragment_shader_file_path); 182 | return; 183 | } 184 | 185 | if (!link_program(vert, frag, &program)) { 186 | fprintf(stderr, "ERROR: failed to link shader program\n"); 187 | return; 188 | } 189 | 190 | glUseProgram(program); 191 | time_location = glGetUniformLocation(program, "time"); 192 | resolution_location = glGetUniformLocation(program, "resolution"); 193 | } 194 | // reload shader program end 195 | 196 | // reload texture begin 197 | { 198 | glDeleteTextures(1, &texture_id); 199 | 200 | int w, h; 201 | uint32_t *pixels = (uint32_t*) stbi_load(texture_file_path, &w, &h, NULL, 4); 202 | if (pixels == NULL) { 203 | fprintf(stderr, "%s:%zu: ERROR: could not load file %s: %s\n", 204 | scene_conf_file_path, texture_def_line, texture_file_path, strerror(errno)); 205 | return; 206 | } 207 | 208 | glGenTextures(1, &texture_id); 209 | glBindTexture(GL_TEXTURE_2D, texture_id); 210 | 211 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 212 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 213 | 214 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 215 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 216 | 217 | glTexImage2D(GL_TEXTURE_2D, 218 | 0, 219 | GL_RGBA, 220 | w, 221 | h, 222 | 0, 223 | GL_RGBA, 224 | GL_UNSIGNED_BYTE, 225 | pixels); 226 | glGenerateMipmap(GL_TEXTURE_2D); 227 | } 228 | // reload texture end 229 | 230 | glClearColor(BACKGROUND_COLOR); 231 | program_failed = false; 232 | 233 | printf("Successfully reloaded scene\n"); 234 | printf("Memory %zu/%zu bytes\n", hot_reload_memory.size, (size_t) REGION_CAPACITY); 235 | region_clean(&hot_reload_memory); 236 | } 237 | 238 | void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) 239 | { 240 | (void) window; 241 | (void) scancode; 242 | (void) action; 243 | (void) mods; 244 | 245 | if (action == GLFW_PRESS) { 246 | if (key == GLFW_KEY_F5) { 247 | reload_scene(); 248 | } else if (key == GLFW_KEY_SPACE) { 249 | pause = !pause; 250 | } 251 | 252 | if (pause) { 253 | if (key == GLFW_KEY_LEFT) { 254 | time -= MANUAL_TIME_STEP; 255 | } else if (key == GLFW_KEY_RIGHT) { 256 | time += MANUAL_TIME_STEP; 257 | } 258 | } 259 | } 260 | } 261 | 262 | void window_size_callback(GLFWwindow* window, int width, int height) 263 | { 264 | (void) window; 265 | glViewport(0, 0, width, height); 266 | } 267 | 268 | void MessageCallback(GLenum source, 269 | GLenum type, 270 | GLuint id, 271 | GLenum severity, 272 | GLsizei length, 273 | const GLchar* message, 274 | const void* userParam) 275 | { 276 | (void) source; 277 | (void) id; 278 | (void) length; 279 | (void) userParam; 280 | fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", 281 | (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), 282 | type, severity, message); 283 | } 284 | 285 | int main() 286 | { 287 | if (!glfwInit()) { 288 | fprintf(stderr, "ERROR: could not initialize GLFW\n"); 289 | exit(1); 290 | } 291 | 292 | GLFWwindow * const window = 293 | glfwCreateWindow( 294 | 800, 295 | 600, 296 | "kidito", 297 | NULL, 298 | NULL); 299 | if (window == NULL) { 300 | fprintf(stderr, "ERROR: could not create a window.\n"); 301 | glfwTerminate(); 302 | exit(1); 303 | } 304 | 305 | glfwMakeContextCurrent(window); 306 | 307 | if (GLEW_OK != glewInit()) { 308 | fprintf(stderr, "Could not initialize GLEW!\n"); 309 | exit(1); 310 | } 311 | 312 | 313 | glEnable(GL_DEBUG_OUTPUT); 314 | glDebugMessageCallback(MessageCallback, 0); 315 | 316 | glEnable(GL_DEPTH_TEST); 317 | 318 | glEnable(GL_BLEND); 319 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 320 | 321 | reload_scene(); 322 | 323 | 324 | V4 mesh[TRIS_PER_CUBE][TRI_VERTICES] = {0}; 325 | RGBA colors[TRIS_PER_CUBE][TRI_VERTICES] = {0}; 326 | V2 uvs[TRIS_PER_CUBE][TRI_VERTICES] = {0}; 327 | V4 normals[TRIS_PER_CUBE][TRI_VERTICES] = {0}; 328 | 329 | generate_cube_mesh(mesh, colors, uvs, normals); 330 | 331 | { 332 | GLuint position_buffer_id; 333 | glGenBuffers(1, &position_buffer_id); 334 | glBindBuffer(GL_ARRAY_BUFFER, position_buffer_id); 335 | glBufferData(GL_ARRAY_BUFFER, 336 | sizeof(mesh), 337 | mesh, 338 | GL_STATIC_DRAW); 339 | GLuint position_index = 0; 340 | glEnableVertexAttribArray(position_index); 341 | glVertexAttribPointer(position_index, 342 | V4_COMPS, 343 | GL_FLOAT, 344 | GL_FALSE, 345 | 0, 346 | NULL); 347 | } 348 | 349 | { 350 | GLuint uv_buffer_id; 351 | glGenBuffers(1, &uv_buffer_id); 352 | glBindBuffer(GL_ARRAY_BUFFER, uv_buffer_id); 353 | glBufferData(GL_ARRAY_BUFFER, 354 | sizeof(uvs), 355 | uvs, 356 | GL_STATIC_DRAW); 357 | GLuint uv_index = 1; 358 | glEnableVertexAttribArray(uv_index); 359 | glVertexAttribPointer(uv_index, 360 | V2_COMPS, 361 | GL_FLOAT, 362 | GL_FALSE, 363 | 0, 364 | NULL); 365 | } 366 | 367 | { 368 | GLuint normal_buffer_id; 369 | glGenBuffers(1, &normal_buffer_id); 370 | glBindBuffer(GL_ARRAY_BUFFER, normal_buffer_id); 371 | glBufferData(GL_ARRAY_BUFFER, 372 | sizeof(normals), 373 | normals, 374 | GL_STATIC_DRAW); 375 | GLuint normal_index = 2; 376 | glEnableVertexAttribArray(normal_index); 377 | glVertexAttribPointer(normal_index, 378 | V4_COMPS, 379 | GL_FLOAT, 380 | GL_FALSE, 381 | 0, 382 | NULL); 383 | } 384 | 385 | glfwSetKeyCallback(window, key_callback); 386 | glfwSetFramebufferSizeCallback(window, window_size_callback); 387 | double prev_time = 0.0; 388 | while (!glfwWindowShouldClose(window)) { 389 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 390 | 391 | if (!program_failed) { 392 | int width, height; 393 | glfwGetFramebufferSize(window, &width, &height); 394 | glUniform2f(resolution_location, width, height); 395 | glUniform1f(time_location, time); 396 | glDrawArrays(GL_TRIANGLES, 0, TRIS_PER_CUBE * TRI_VERTICES); 397 | } 398 | 399 | glfwSwapBuffers(window); 400 | glfwPollEvents(); 401 | double cur_time = glfwGetTime(); 402 | if (!pause) { 403 | time += cur_time - prev_time; 404 | } 405 | prev_time = cur_time; 406 | } 407 | 408 | return 0; 409 | } 410 | -------------------------------------------------------------------------------- /src/region.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "./region.h" 5 | 6 | void *region_malloc(Region *region, size_t size) 7 | { 8 | if (region->size + size >= REGION_CAPACITY) { 9 | errno = ENOMEM; 10 | return NULL; 11 | } 12 | 13 | void *result = region->memory + region->size; 14 | region->size += size; 15 | return result; 16 | } 17 | 18 | void *region_realloc(Region *region, void *old_memory, size_t old_size, size_t new_size) 19 | { 20 | void *new_memory = region_malloc(region, new_size); 21 | 22 | if (old_size > new_size) { 23 | old_size = new_size; 24 | } 25 | 26 | memcpy(new_memory, old_memory, old_size); 27 | return new_memory; 28 | } 29 | 30 | void region_clean(Region *region) 31 | { 32 | region->size = 0; 33 | } 34 | 35 | char *region_cstr_from_sv(Region *region, String_View sv) 36 | { 37 | char *result = region_malloc(region, sv.count + 1); 38 | memcpy(result, sv.data, sv.count); 39 | result[sv.count] = '\0'; 40 | return result; 41 | } 42 | 43 | char *region_slurp_file(Region *region, const char *file_path) 44 | { 45 | FILE *f = NULL; 46 | char *buffer = NULL; 47 | 48 | f = fopen(file_path, "r"); 49 | if (f == NULL) goto end; 50 | if (fseek(f, 0, SEEK_END) < 0) goto end; 51 | 52 | long size = ftell(f); 53 | if (size < 0) goto end; 54 | 55 | buffer = region_malloc(region, size + 1); 56 | if (buffer == NULL) goto end; 57 | 58 | if (fseek(f, 0, SEEK_SET) < 0) goto end; 59 | 60 | fread(buffer, 1, size, f); 61 | if (ferror(f) < 0) goto end; 62 | 63 | buffer[size] = '\0'; 64 | 65 | end: 66 | if (f) fclose(f); 67 | return buffer; 68 | } 69 | -------------------------------------------------------------------------------- /src/region.h: -------------------------------------------------------------------------------- 1 | #ifndef REGION_H_ 2 | #define REGION_H_ 3 | 4 | #include 5 | #include "sv.h" 6 | 7 | #define REGION_CAPACITY (1 * 1000 * 1000) 8 | 9 | typedef struct { 10 | size_t size; 11 | char memory[REGION_CAPACITY]; 12 | } Region; 13 | 14 | void *region_malloc(Region *region, size_t size); 15 | void *region_realloc(Region *region, void *old_memory, size_t old_size, size_t new_size); 16 | void region_clean(Region *region); 17 | char *region_cstr_from_sv(Region *region, String_View sv); 18 | char *region_slurp_file(Region *region, const char *file_path); 19 | 20 | #endif // REGION_H_ 21 | -------------------------------------------------------------------------------- /src/rgba.h: -------------------------------------------------------------------------------- 1 | #ifndef RGBA_H_ 2 | #define RGBA_H_ 3 | 4 | #define RGBA_COMPS 4 5 | 6 | typedef struct { 7 | float cs[RGBA_COMPS]; 8 | } RGBA; 9 | 10 | #define RGBA_Fmt "RGBA(%f,%f,%f,%f)" 11 | #define RGBA_Arg(rgba) rgba.cs[0], rgba.cs[1], rgba.cs[2], rgba.cs[3] 12 | 13 | #define RED (RGBA) {.cs = {1.0f, 0.0f, 0.0f, 1.0f}} 14 | #define GREEN (RGBA) {.cs = {0.0f, 1.0f, 0.0f, 1.0f}} 15 | #define BLUE (RGBA) {.cs = {0.0f, 0.0f, 1.0f, 1.0f}} 16 | #define YELLOW (RGBA) {.cs = {1.0f, 1.0f, 0.0f, 1.0f}} 17 | #define PURPLE (RGBA) {.cs = {1.0f, 0.0f, 1.0f, 1.0f}} 18 | #define CYAN (RGBA) {.cs = {0.0f, 1.0f, 1.0f, 1.0f}} 19 | 20 | #endif // RGBA_H_ 21 | -------------------------------------------------------------------------------- /src/sv.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "./sv.h" 5 | 6 | String_View sv_from_cstr(const char *cstr) 7 | { 8 | if (cstr) { 9 | return (String_View) { 10 | .count = strlen(cstr), 11 | .data = cstr, 12 | }; 13 | } else { 14 | return SV_NULL; 15 | } 16 | } 17 | 18 | String_View sv_trim_left(String_View sv) 19 | { 20 | size_t i = 0; 21 | while (i < sv.count && isspace(sv.data[i])) { 22 | i += 1; 23 | } 24 | 25 | return (String_View) { 26 | .count = sv.count - i, 27 | .data = sv.data + i, 28 | }; 29 | } 30 | 31 | String_View sv_trim_right(String_View sv) 32 | { 33 | size_t i = 0; 34 | while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) { 35 | i += 1; 36 | } 37 | 38 | return (String_View) { 39 | .count = sv.count - i, 40 | .data = sv.data 41 | }; 42 | } 43 | 44 | String_View sv_trim(String_View sv) 45 | { 46 | return sv_trim_right(sv_trim_left(sv)); 47 | } 48 | 49 | String_View sv_chop_left(String_View *sv, size_t n) 50 | { 51 | if (n > sv->count) { 52 | n = sv->count; 53 | } 54 | 55 | String_View result = { 56 | .data = sv->data, 57 | .count = n, 58 | }; 59 | 60 | sv->data += n; 61 | sv->count -= n; 62 | 63 | return result; 64 | } 65 | 66 | String_View sv_chop_right(String_View *sv, size_t n) 67 | { 68 | if (n > sv->count) { 69 | n = sv->count; 70 | } 71 | 72 | String_View result = { 73 | .data = sv->data + sv->count - n, 74 | .count = n 75 | }; 76 | 77 | sv->count -= n; 78 | 79 | return result; 80 | } 81 | 82 | bool sv_index_of(String_View sv, char c, size_t *index) 83 | { 84 | size_t i = 0; 85 | while (i < sv.count && sv.data[i] != c) { 86 | i += 1; 87 | } 88 | 89 | if (i < sv.count) { 90 | *index = i; 91 | return true; 92 | } else { 93 | return false; 94 | } 95 | } 96 | 97 | String_View sv_chop_by_delim(String_View *sv, char delim) 98 | { 99 | size_t i = 0; 100 | while (i < sv->count && sv->data[i] != delim) { 101 | i += 1; 102 | } 103 | 104 | String_View result = { 105 | .count = i, 106 | .data = sv->data, 107 | }; 108 | 109 | if (i < sv->count) { 110 | sv->count -= i + 1; 111 | sv->data += i + 1; 112 | } else { 113 | sv->count -= i; 114 | sv->data += i; 115 | } 116 | 117 | return result; 118 | } 119 | 120 | bool sv_starts_with(String_View sv, String_View expected_prefix) 121 | { 122 | if (expected_prefix.count <= sv.count) { 123 | String_View actual_prefix = { 124 | .data = sv.data, 125 | .count = expected_prefix.count, 126 | }; 127 | 128 | return sv_eq(expected_prefix, actual_prefix); 129 | } 130 | 131 | return false; 132 | } 133 | 134 | bool sv_ends_with(String_View sv, String_View expected_suffix) 135 | { 136 | if (expected_suffix.count <= sv.count) { 137 | String_View actual_suffix = { 138 | .data = sv.data + sv.count - expected_suffix.count, 139 | .count = expected_suffix.count 140 | }; 141 | 142 | return sv_eq(expected_suffix, actual_suffix); 143 | } 144 | 145 | return false; 146 | } 147 | 148 | bool sv_eq(String_View a, String_View b) 149 | { 150 | if (a.count != b.count) { 151 | return false; 152 | } else { 153 | return memcmp(a.data, b.data, a.count) == 0; 154 | } 155 | } 156 | 157 | uint64_t sv_to_u64(String_View sv) 158 | { 159 | uint64_t result = 0; 160 | 161 | for (size_t i = 0; i < sv.count && isdigit(sv.data[i]); ++i) { 162 | result = result * 10 + (uint64_t) sv.data[i] - '0'; 163 | } 164 | 165 | return result; 166 | } 167 | 168 | String_View sv_chop_left_while(String_View *sv, bool (*predicate)(char x)) 169 | { 170 | size_t i = 0; 171 | while (i < sv->count && predicate(sv->data[i])) { 172 | i += 1; 173 | } 174 | return sv_chop_left(sv, i); 175 | } 176 | -------------------------------------------------------------------------------- /src/sv.h: -------------------------------------------------------------------------------- 1 | #ifndef SV_H_ 2 | #define SV_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct { 9 | size_t count; 10 | const char *data; 11 | } String_View; 12 | 13 | #define SV(cstr_lit) \ 14 | ((String_View) { \ 15 | .count = sizeof(cstr_lit) - 1, \ 16 | .data = (cstr_lit) \ 17 | }) 18 | 19 | #define SV_NULL (String_View) {0} 20 | 21 | // printf macros for String_View 22 | #define SV_Fmt "%.*s" 23 | #define SV_Arg(sv) (int) sv.count, sv.data 24 | // USAGE: 25 | // String_View name = ...; 26 | // printf("Name: "SV_Fmt"\n", SV_Arg(name)); 27 | 28 | String_View sv_from_cstr(const char *cstr); 29 | String_View sv_trim_left(String_View sv); 30 | String_View sv_trim_right(String_View sv); 31 | String_View sv_trim(String_View sv); 32 | String_View sv_chop_by_delim(String_View *sv, char delim); 33 | String_View sv_chop_left(String_View *sv, size_t n); 34 | String_View sv_chop_right(String_View *sv, size_t n); 35 | String_View sv_chop_left_while(String_View *sv, bool (*predicate)(char x)); 36 | bool sv_index_of(String_View sv, char c, size_t *index); 37 | bool sv_eq(String_View a, String_View b); 38 | bool sv_starts_with(String_View sv, String_View prefix); 39 | bool sv_ends_with(String_View sv, String_View suffix); 40 | uint64_t sv_to_u64(String_View sv); 41 | 42 | #endif // SV_H_ 43 | --------------------------------------------------------------------------------