├── .gitignore ├── .gitattributes ├── .vscode ├── settings.json ├── c_cpp_properties.json └── launch.json ├── image.png ├── shaders ├── shadow.frag ├── raycast.frag ├── fullscreen.vert ├── random.frag ├── sky.frag ├── raycast.vert ├── shadow.vert ├── sky.vert ├── opaque.frag ├── opaque.vert ├── transparent.frag ├── transparent.vert ├── composite.frag ├── ui.frag ├── ssao.frag └── helpers.glsl ├── lib └── stb │ ├── stb.c │ └── stb_perlin.h ├── textures └── atlas.png ├── .gitmodules ├── src ├── noise.h ├── raycast.h ├── voxel.h ├── pipeline.h ├── world.h ├── database.h ├── block.h ├── helpers.h ├── helpers.c ├── raycast.c ├── camera.h ├── chunk.h ├── config.h ├── noise.c ├── block.c ├── chunk.c ├── database.c ├── camera.c ├── world.c ├── voxel.c ├── pipeline.c └── main.c ├── LICENSE.txt ├── README.md └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | lib/** linguist-vendored -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.parallelJobs": 8 3 | } -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsoulier/blocks/HEAD/image.png -------------------------------------------------------------------------------- /shaders/shadow.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | void main() 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /lib/stb/stb.c: -------------------------------------------------------------------------------- 1 | #define STB_PERLIN_IMPLEMENTATION 2 | #include -------------------------------------------------------------------------------- /textures/atlas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsoulier/blocks/HEAD/textures/atlas.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/SDL"] 2 | path = lib/SDL 3 | url = https://github.com/libsdl-org/SDL 4 | -------------------------------------------------------------------------------- /src/noise.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "chunk.h" 4 | 5 | void noise_generate( 6 | chunk_t* chunk, 7 | const int x, 8 | const int z); -------------------------------------------------------------------------------- /shaders/raycast.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec3 i_position; 4 | layout(location = 0) out vec4 o_color; 5 | 6 | void main() 7 | { 8 | o_color = vec4(normalize(i_position), 0.3); 9 | } -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "cmake", 5 | "configurationProvider": "ms-vscode.cmake-tools" 6 | } 7 | ], 8 | "version": 4 9 | } -------------------------------------------------------------------------------- /src/raycast.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | bool raycast( 6 | float* x, 7 | float* y, 8 | float* z, 9 | const float dx, 10 | const float dy, 11 | const float dz, 12 | const bool previous); -------------------------------------------------------------------------------- /shaders/fullscreen.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) out vec2 o_uv; 4 | 5 | void main() 6 | { 7 | o_uv = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); 8 | gl_Position = vec4(o_uv * 2.0f - 1.0f, 0.0f, 1.0f); 9 | o_uv.y = 1.0 - o_uv.y; 10 | } -------------------------------------------------------------------------------- /shaders/random.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec2 i_uv; 4 | layout(location = 0) out vec2 o_random; 5 | 6 | void main() 7 | { 8 | o_random.x = fract(sin(dot(i_uv * 1.0, vec2(12.9898, 78.233))) * 43758.5453); 9 | o_random.y = fract(sin(dot(i_uv * 2.0, vec2(12.9898, 78.233))) * 43758.5453); 10 | o_random = o_random * 2.0 - 1.0; 11 | } -------------------------------------------------------------------------------- /shaders/sky.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "helpers.glsl" 4 | 5 | layout(location = 0) in vec3 i_position; 6 | layout(location = 0) out vec4 o_color; 7 | 8 | void main() 9 | { 10 | const float dy = i_position.y; 11 | const float dx = length(i_position.xz); 12 | const float pitch = atan(dy, dx); 13 | o_color = vec4(get_sky(pitch), 1.0); 14 | } -------------------------------------------------------------------------------- /shaders/raycast.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec3 i_position; 4 | layout(location = 0) out vec3 o_position; 5 | layout(set = 1, binding = 0) uniform t_matrix 6 | { 7 | mat4 u_matrix; 8 | }; 9 | layout(set = 1, binding = 1) uniform t_position 10 | { 11 | ivec3 u_position; 12 | }; 13 | 14 | void main() 15 | { 16 | o_position = i_position * 1.05 / 2.0 + vec3(0.5, 0.5, 0.5); 17 | gl_Position = u_matrix * vec4(u_position + o_position, 1.0); 18 | } -------------------------------------------------------------------------------- /src/voxel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "chunk.h" 7 | #include "helpers.h" 8 | 9 | bool voxel_vbo( 10 | chunk_t* chunk, 11 | const chunk_t* neighbors[DIRECTION_2], 12 | SDL_GPUDevice* device, 13 | SDL_GPUTransferBuffer* tbos[CHUNK_TYPE_COUNT], 14 | uint32_t capacities[CHUNK_TYPE_COUNT]); 15 | bool voxel_ibo( 16 | SDL_GPUDevice* device, 17 | SDL_GPUBuffer** ibo, 18 | const uint32_t size); -------------------------------------------------------------------------------- /shaders/shadow.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "helpers.glsl" 4 | 5 | layout(location = 0) in uint i_voxel; 6 | layout(set = 1, binding = 0) uniform t_position 7 | { 8 | ivec3 u_position; 9 | }; 10 | layout(set = 1, binding = 1) uniform t_matrix 11 | { 12 | mat4 u_matrix; 13 | }; 14 | 15 | void main() 16 | { 17 | if (get_shadow(i_voxel)) 18 | { 19 | gl_Position = u_matrix * vec4(u_position + get_position(i_voxel), 1.0); 20 | } 21 | else 22 | { 23 | gl_Position = vec4(0.0, 0.0, 2.0, 1.0); 24 | } 25 | } -------------------------------------------------------------------------------- /shaders/sky.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec3 i_position; 4 | layout(location = 0) out vec3 o_position; 5 | layout(set = 1, binding = 0) uniform t_view 6 | { 7 | mat4 u_view; 8 | }; 9 | layout(set = 1, binding = 1) uniform t_proj 10 | { 11 | mat4 u_proj; 12 | }; 13 | 14 | void main() 15 | { 16 | o_position = i_position; 17 | mat4 rotation = u_view; 18 | rotation[3][0] = 0.0; 19 | rotation[3][1] = 0.0; 20 | rotation[3][2] = 0.0; 21 | gl_Position = u_proj * rotation * vec4(i_position, 1.0); 22 | } -------------------------------------------------------------------------------- /shaders/opaque.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in flat uint i_voxel; 4 | layout(location = 1) in vec4 i_position; 5 | layout(location = 2) in vec3 i_uv; 6 | layout(location = 0) out vec4 o_position; 7 | layout(location = 1) out vec3 o_uv; 8 | layout(location = 2) out uint o_voxel; 9 | layout(set = 2, binding = 0) uniform sampler2DArray s_atlas; 10 | 11 | void main() 12 | { 13 | if (texture(s_atlas, i_uv).a < 0.001) 14 | { 15 | discard; 16 | } 17 | o_position = i_position; 18 | o_uv = i_uv; 19 | o_voxel = i_voxel; 20 | } -------------------------------------------------------------------------------- /src/pipeline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef enum 7 | { 8 | PIPELINE_SKY, 9 | PIPELINE_SHADOW, 10 | PIPELINE_OPAQUE, 11 | PIPELINE_SSAO, 12 | PIPELINE_COMPOSITE, 13 | PIPELINE_TRANSPARENT, 14 | PIPELINE_RAYCAST, 15 | PIPELINE_UI, 16 | PIPELINE_RANDOM, 17 | PIPELINE_COUNT 18 | } 19 | pipeline_t; 20 | 21 | bool pipeline_init( 22 | SDL_GPUDevice* device, 23 | const SDL_GPUTextureFormat format); 24 | void pipeline_free(); 25 | void pipeline_bind( 26 | void* pass, 27 | const pipeline_t pipeline); -------------------------------------------------------------------------------- /src/world.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "block.h" 6 | #include "camera.h" 7 | #include "chunk.h" 8 | 9 | bool world_init( 10 | SDL_GPUDevice* device); 11 | void world_free(); 12 | void world_update( 13 | const int x, 14 | const int y, 15 | const int z); 16 | void world_render( 17 | const camera_t* camera, 18 | SDL_GPUCommandBuffer* commands, 19 | SDL_GPURenderPass* pass, 20 | const chunk_type_t type); 21 | void world_set_block( 22 | int x, 23 | int y, 24 | int z, 25 | const block_t block); 26 | block_t world_get_block( 27 | int x, 28 | int y, 29 | int z); -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "win32", 6 | "type": "cppvsdbg", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/build/bin/blocks.exe", 9 | "cwd": "${workspaceFolder}/build/bin", 10 | "preLaunchTask": "CMake: build", 11 | }, 12 | { 13 | "name": "linux", 14 | "type": "cppdbg", 15 | "request": "launch", 16 | "program": "${workspaceFolder}/build/bin/blocks", 17 | "cwd": "${workspaceFolder}/build/bin", 18 | "preLaunchTask": "CMake: build", 19 | }, 20 | ] 21 | } -------------------------------------------------------------------------------- /shaders/opaque.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "helpers.glsl" 4 | 5 | layout(location = 0) in uint i_voxel; 6 | layout(location = 0) out flat uint o_voxel; 7 | layout(location = 1) out vec4 o_position; 8 | layout(location = 2) out vec3 o_uv; 9 | layout(set = 1, binding = 0) uniform t_position 10 | { 11 | ivec3 u_position; 12 | }; 13 | layout(set = 1, binding = 1) uniform t_view 14 | { 15 | mat4 u_view; 16 | }; 17 | layout(set = 1, binding = 2) uniform t_proj 18 | { 19 | mat4 u_proj; 20 | }; 21 | 22 | void main() 23 | { 24 | o_voxel = i_voxel; 25 | o_position.xyz = u_position + get_position(i_voxel); 26 | o_uv = get_uv(i_voxel); 27 | const vec4 position = u_view * vec4(o_position.xyz, 1.0); 28 | o_position.w = position.z; 29 | gl_Position = u_proj * position; 30 | } -------------------------------------------------------------------------------- /src/database.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "block.h" 5 | #include "chunk.h" 6 | 7 | bool database_init( 8 | const char* file); 9 | void database_free(); 10 | void database_commit(); 11 | void database_set_player( 12 | const int id, 13 | const float x, 14 | const float y, 15 | const float z, 16 | const float pitch, 17 | const float yaw); 18 | bool database_get_player( 19 | const int id, 20 | float* x, 21 | float* y, 22 | float* z, 23 | float* pitch, 24 | float* yaw); 25 | void database_set_block( 26 | const int a, 27 | const int c, 28 | const int x, 29 | const int y, 30 | const int z, 31 | const block_t block); 32 | void database_get_blocks( 33 | chunk_t* chunk, 34 | const int a, 35 | const int c); -------------------------------------------------------------------------------- /src/block.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "helpers.h" 6 | 7 | typedef uint8_t block_t; 8 | enum 9 | { 10 | BLOCK_EMPTY, 11 | BLOCK_GRASS, 12 | BLOCK_DIRT, 13 | BLOCK_SAND, 14 | BLOCK_SNOW, 15 | BLOCK_STONE, 16 | BLOCK_LOG, 17 | BLOCK_LEAVES, 18 | BLOCK_CLOUD, 19 | BLOCK_BUSH, 20 | BLOCK_BLUEBELL, 21 | BLOCK_DANDELION, 22 | BLOCK_ROSE, 23 | BLOCK_LAVENDER, 24 | BLOCK_WATER, 25 | BLOCK_COUNT, 26 | }; 27 | 28 | bool block_opaque( 29 | const block_t block); 30 | bool block_shadow( 31 | const block_t block); 32 | bool block_shadowed( 33 | const block_t block); 34 | bool block_occluded( 35 | const block_t block); 36 | bool block_solid( 37 | const block_t block); 38 | bool block_sprite( 39 | const block_t block); 40 | 41 | extern const int blocks[][DIRECTION_3]; -------------------------------------------------------------------------------- /src/helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #undef assert 7 | #undef min 8 | #undef max 9 | 10 | #define EPSILON 0.000001 11 | #define PI 3.14159265359 12 | #define max(a, b) ((a) > (b) ? (a) : (b)) 13 | #define min(a, b) ((a) < (b) ? (a) : (b)) 14 | #define clamp(x, a, b) min(b, max(a, x)) 15 | #define deg(rad) ((rad) * 180.0 / PI) 16 | #define rad(deg) ((deg) * PI / 180.0) 17 | #define abs(x) ((x) > 0 ? (x) : -(x)) 18 | 19 | #ifndef NDEBUG 20 | #define assert(e) SDL_assert_always(e) 21 | #else 22 | #define assert(e) 23 | #endif 24 | 25 | typedef enum 26 | { 27 | DIRECTION_N, 28 | DIRECTION_S, 29 | DIRECTION_E, 30 | DIRECTION_W, 31 | DIRECTION_U, 32 | DIRECTION_D, 33 | DIRECTION_2 = 4, 34 | DIRECTION_3 = 6, 35 | } 36 | direction_t; 37 | 38 | extern const int directions[][3]; 39 | 40 | void sort_2d( 41 | const int x, 42 | const int z, 43 | void* data, 44 | const int size); -------------------------------------------------------------------------------- /src/helpers.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "helpers.h" 3 | 4 | const int directions[][3] = 5 | { 6 | [DIRECTION_N] = { 0, 0, 1 }, 7 | [DIRECTION_S] = { 0, 0,-1 }, 8 | [DIRECTION_E] = { 1, 0, 0 }, 9 | [DIRECTION_W] = {-1, 0, 0 }, 10 | [DIRECTION_U] = { 0, 1, 0 }, 11 | [DIRECTION_D] = { 0,-1, 0 }, 12 | }; 13 | 14 | static int cx; 15 | static int cz; 16 | 17 | static int squared( 18 | const int x, 19 | const int z) 20 | { 21 | const int dx = x - cx; 22 | const int dz = z - cz; 23 | return dx * dx + dz * dz; 24 | } 25 | 26 | static int compare( 27 | const void* a, 28 | const void* b) 29 | { 30 | const int* l = a; 31 | const int* r = b; 32 | const int c = squared(l[0], l[1]); 33 | const int d = squared(r[0], r[1]); 34 | if (c < d) 35 | { 36 | return -1; 37 | } 38 | else if (c > d) 39 | { 40 | return 1; 41 | } 42 | else 43 | { 44 | return 0; 45 | } 46 | } 47 | 48 | void sort_2d( 49 | const int x, 50 | const int z, 51 | void* data, 52 | const int size) 53 | { 54 | assert(data); 55 | assert(size); 56 | cx = x; 57 | cz = z; 58 | qsort(data, size, 8, compare); 59 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /shaders/transparent.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "helpers.glsl" 4 | 5 | layout(location = 0) in vec3 i_position; 6 | layout(location = 1) in vec3 i_uv; 7 | layout(location = 2) in flat vec3 i_normal; 8 | layout(location = 3) in vec4 i_shadow_position; 9 | layout(location = 4) in flat uint i_shadowed; 10 | layout(location = 5) in flat uint i_occluded; 11 | layout(location = 6) in float i_fog; 12 | layout(location = 7) in vec2 i_fragment; 13 | layout(location = 0) out vec4 o_color; 14 | layout(set = 2, binding = 0) uniform sampler2DArray s_atlas; 15 | layout(set = 2, binding = 1) uniform sampler2D s_shadowmap; 16 | layout(set = 2, binding = 2) uniform sampler2D s_position; 17 | layout(set = 3, binding = 0) uniform t_shadow_vector 18 | { 19 | vec3 u_shadow_vector; 20 | }; 21 | layout(set = 3, binding = 1) uniform t_player_position 22 | { 23 | vec3 u_player_position; 24 | }; 25 | 26 | void main() 27 | { 28 | o_color = get_color( 29 | s_atlas, 30 | s_shadowmap, 31 | i_position, 32 | i_uv, 33 | i_normal, 34 | u_player_position, 35 | i_shadow_position.xyz / i_shadow_position.w, 36 | u_shadow_vector, 37 | bool(i_shadowed), 38 | bool(i_occluded), 39 | i_fog, 40 | 1.0, 41 | (i_position.y - texture(s_position, i_fragment).y) / 20.0); 42 | } -------------------------------------------------------------------------------- /src/raycast.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "block.h" 3 | #include "config.h" 4 | #include "helpers.h" 5 | #include "raycast.h" 6 | #include "world.h" 7 | 8 | bool raycast( 9 | float* x, 10 | float* y, 11 | float* z, 12 | const float dx, 13 | const float dy, 14 | const float dz, 15 | const bool previous) 16 | { 17 | assert(x); 18 | assert(y); 19 | assert(z); 20 | const float step = 0.02f; 21 | for (float i = 0.0f; i < PLAYER_REACH; i += step) 22 | { 23 | float a = *x + dx * i; 24 | float b = *y + dy * i; 25 | float c = *z + dz * i; 26 | if (a <= 0.0f) 27 | { 28 | a -= 1.0f; 29 | } 30 | if (c <= 0.0f) 31 | { 32 | c -= 1.0f; 33 | } 34 | if (block_solid(world_get_block(a, b, c))) 35 | { 36 | if (previous) 37 | { 38 | a -= dx * step; 39 | b -= dy * step; 40 | c -= dz * step; 41 | } 42 | if (a < 0.0f && a > -1.0f && dx > 0.0f) 43 | { 44 | a = -1.0f; 45 | } 46 | if (c < 0.0f && c > -1.0f && dz > 0.0f) 47 | { 48 | c = -1.0f; 49 | } 50 | *x = a; 51 | *y = b; 52 | *z = c; 53 | return true; 54 | } 55 | } 56 | return false; 57 | } -------------------------------------------------------------------------------- /shaders/transparent.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "helpers.glsl" 4 | 5 | layout(location = 0) in uint i_voxel; 6 | layout(location = 0) out vec3 o_position; 7 | layout(location = 1) out vec3 o_uv; 8 | layout(location = 2) out flat vec3 o_normal; 9 | layout(location = 3) out vec4 o_shadow_position; 10 | layout(location = 4) out flat uint o_shadowed; 11 | layout(location = 5) out flat uint o_occluded; 12 | layout(location = 6) out float o_fog; 13 | layout(location = 7) out vec2 o_fragment; 14 | layout(set = 1, binding = 0) uniform t_position 15 | { 16 | ivec3 u_position; 17 | }; 18 | layout(set = 1, binding = 1) uniform t_matrix 19 | { 20 | mat4 u_matrix; 21 | }; 22 | layout(set = 1, binding = 2) uniform t_player_position 23 | { 24 | vec3 u_player_position; 25 | }; 26 | layout(set = 1, binding = 3) uniform t_shadow_matrix 27 | { 28 | mat4 u_shadow_matrix; 29 | }; 30 | 31 | void main() 32 | { 33 | o_position = u_position + get_position(i_voxel); 34 | o_uv = get_uv(i_voxel); 35 | o_shadowed = uint(get_shadowed(i_voxel)); 36 | o_occluded = uint(get_occluded(i_voxel)); 37 | o_fog = get_fog(distance(o_position.xz, u_player_position.xz)); 38 | gl_Position = u_matrix * vec4(o_position, 1.0); 39 | o_fragment = gl_Position.xy / gl_Position.w; 40 | o_fragment = o_fragment * 0.5 + 0.5; 41 | o_fragment.y = 1.0 - o_fragment.y; 42 | if (!bool(o_shadowed)) 43 | { 44 | return; 45 | } 46 | o_shadow_position = u_shadow_matrix * vec4(o_position, 1.0); 47 | o_normal = get_normal(i_voxel); 48 | } -------------------------------------------------------------------------------- /shaders/composite.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "helpers.glsl" 4 | 5 | layout(location = 0) in vec2 i_uv; 6 | layout(location = 0) out vec4 o_color; 7 | layout(set = 2, binding = 0) uniform sampler2DArray s_atlas; 8 | layout(set = 2, binding = 1) uniform sampler2D s_position; 9 | layout(set = 2, binding = 2) uniform sampler2D s_uv; 10 | layout(set = 2, binding = 3) uniform usampler2D s_voxel; 11 | layout(set = 2, binding = 4) uniform sampler2D s_shadowmap; 12 | layout(set = 2, binding = 5) uniform sampler2D s_ssao; 13 | layout(set = 3, binding = 0) uniform t_player_position 14 | { 15 | vec3 u_player_position; 16 | }; 17 | layout(set = 3, binding = 1) uniform t_shadow_vector 18 | { 19 | vec3 u_shadow_vector; 20 | }; 21 | layout(set = 3, binding = 2) uniform t_shadow_matrix 22 | { 23 | mat4 u_shadow_matrix; 24 | }; 25 | 26 | void main() 27 | { 28 | const vec3 position = texture(s_position, i_uv).xyz; 29 | const vec3 uv = texture(s_uv, i_uv).xyz; 30 | const uint voxel = texture(s_voxel, i_uv).x; 31 | if (length(uv) == 0) 32 | { 33 | discard; 34 | } 35 | const vec4 shadow_position = u_shadow_matrix * vec4(position, 1.0); 36 | o_color = get_color( 37 | s_atlas, 38 | s_shadowmap, 39 | position, 40 | uv, 41 | get_normal(voxel), 42 | u_player_position, 43 | shadow_position.xyz / shadow_position.w, 44 | u_shadow_vector, 45 | get_shadowed(voxel), 46 | get_occluded(voxel), 47 | get_fog(distance(position.xz, u_player_position.xz)), 48 | texture(s_ssao, i_uv).r, 49 | 0.0); 50 | } -------------------------------------------------------------------------------- /src/camera.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef enum 6 | { 7 | CAMERA_TYPE_ORTHO, 8 | CAMERA_TYPE_PERSPECTIVE, 9 | CAMERA_TYPE_COUNT, 10 | } 11 | camera_type_t; 12 | 13 | typedef struct 14 | { 15 | camera_type_t type; 16 | float matrix[4][4]; 17 | float view[4][4]; 18 | float proj[4][4]; 19 | float planes[6][4]; 20 | float x; 21 | float y; 22 | float z; 23 | float pitch; 24 | float yaw; 25 | float width; 26 | float height; 27 | float fov; 28 | float near; 29 | float far; 30 | float ortho; 31 | bool dirty; 32 | } 33 | camera_t; 34 | 35 | void camera_init( 36 | camera_t* camera, 37 | const camera_type_t type); 38 | void camera_update( 39 | camera_t* camera); 40 | void camera_set_viewport( 41 | camera_t* camera, 42 | const int width, 43 | const int height); 44 | void camera_move( 45 | camera_t* camera, 46 | const float x, 47 | const float y, 48 | const float z); 49 | void camera_rotate( 50 | camera_t* camera, 51 | const float pitch, 52 | const float yaw); 53 | void camera_set_position( 54 | camera_t* camera, 55 | const float x, 56 | const float y, 57 | const float z); 58 | void camera_get_position( 59 | const camera_t* camera, 60 | float* x, 61 | float* y, 62 | float* z); 63 | void camera_set_rotation( 64 | camera_t* camera, 65 | const float pitch, 66 | const float yaw); 67 | void camera_get_rotation( 68 | const camera_t* camera, 69 | float* pitch, 70 | float* yaw); 71 | void camera_get_vector( 72 | const camera_t* camera, 73 | float* x, 74 | float* y, 75 | float* z); 76 | bool camera_test( 77 | const camera_t* camera, 78 | const float x, 79 | const float y, 80 | const float z, 81 | const float a, 82 | const float b, 83 | const float c); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blocks 2 | 3 | ![](image.png) 4 | 5 | Tiny Minecraft clone in C and GLSL using the new SDL3 GPU API 6 | 7 | ### Features 8 | 9 | - Procedural world generation 10 | - Parallel chunk loading 11 | - Blocks and plants 12 | - Transparency (limited) 13 | - Deferred rendering 14 | - Directional shadows 15 | - SSAO (ish) 16 | - Water depth shading 17 | - Persistent worlds 18 | 19 | ### Building 20 | 21 | #### Windows 22 | 23 | Install the [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) for glslc 24 | 25 | ```bash 26 | git clone https://github.com/jsoulier/blocks --recurse-submodules 27 | cd blocks 28 | mkdir build 29 | cd build 30 | cmake .. 31 | cmake --build . --parallel 8 --config Release 32 | cd bin 33 | ./blocks.exe 34 | ``` 35 | 36 | #### Linux 37 | 38 | ```bash 39 | sudo apt install glslc 40 | ``` 41 | 42 | ```bash 43 | git clone https://github.com/jsoulier/blocks --recurse-submodules 44 | cd blocks 45 | mkdir build 46 | cd build 47 | cmake .. -DCMAKE_BUILD_TYPE=Release 48 | cmake --build . --parallel 8 49 | cd bin 50 | ./blocks 51 | ``` 52 | 53 | ### Controls 54 | 55 | - `WASDEQ` to move 56 | - `Escape` to unfocus 57 | - `LClick` to break a block 58 | - `RClick` to place a block 59 | - `B` to toggle blocks 60 | - `F11` to toggle fullscreen 61 | - `LControl` to move quickly 62 | - `LShift` to move slowly 63 | 64 | ### Rendering 65 | 66 | 1. Draw the sky to the g-buffer 67 | 2. Draw the world from the sun's perspective to a depth texture (shadows) 68 | 3. Draw the world (opaque only) to the g-buffer 69 | 4. Calculate SSAO using the g-buffer 70 | 5. Combine the g-buffer, SSAO, and shadows together to create a composite texture 71 | 6. Draw the world (transparent only) with blending to the composite texture 72 | 7. Draw the raycast block to the composite texture 73 | 8. Upscale the composite texture to the swapchain texture 74 | 9. Draw the UI over the swapchain texture 75 | 10. Submit -------------------------------------------------------------------------------- /shaders/ui.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "helpers.glsl" 4 | 5 | layout(location = 0) out vec4 o_color; 6 | layout(set = 2, binding = 0) uniform sampler2DArray s_atlas; 7 | layout(set = 3, binding = 0) uniform t_viewport 8 | { 9 | ivec2 u_viewport; 10 | }; 11 | layout(set = 3, binding = 1) uniform t_face 12 | { 13 | uint u_face; 14 | }; 15 | 16 | void main() 17 | { 18 | const vec2 position = vec2(gl_FragCoord.x, u_viewport.y - gl_FragCoord.y); 19 | const vec2 center = u_viewport / 2.0; 20 | const vec2 ratio = vec2(u_viewport) / vec2(WINDOW_WIDTH, WINDOW_HEIGHT); 21 | const float scale = min(ratio.x, ratio.y); 22 | const float block_width = 50 * scale; 23 | const vec2 block_start = vec2(10 * scale); 24 | const vec2 block_end = block_start + block_width; 25 | if (position.x > block_start.x && position.x < block_end.x && 26 | position.y > block_start.y && position.y < block_end.y) 27 | { 28 | const float x = (position.x - block_start.x) / block_width; 29 | const float y = (position.y - block_start.y) / block_width; 30 | o_color = texture(s_atlas, vec3(x, 1.0 - y, u_face)); 31 | o_color.xyz *= 1.5; 32 | return; 33 | } 34 | const float cross_width = 8 * scale; 35 | const float cross_thickness = 2 * scale; 36 | const vec2 cross_start1 = center - vec2(cross_width, cross_thickness); 37 | const vec2 cross_end1 = center + vec2(cross_width, cross_thickness); 38 | const vec2 cross_start2 = center - vec2(cross_thickness, cross_width); 39 | const vec2 cross_end2 = center + vec2(cross_thickness, cross_width); 40 | if ((position.x > cross_start1.x && position.y > cross_start1.y && 41 | position.x < cross_end1.x && position.y < cross_end1.y) || 42 | (position.x > cross_start2.x && position.y > cross_start2.y && 43 | position.x < cross_end2.x && position.y < cross_end2.y)) 44 | { 45 | o_color = vec4(1.0); 46 | return; 47 | } 48 | discard; 49 | } -------------------------------------------------------------------------------- /shaders/ssao.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "helpers.glsl" 4 | 5 | layout(location = 0) in vec2 i_uv; 6 | layout(location = 0) out float o_ssao; 7 | layout(set = 2, binding = 0) uniform sampler2D s_position; 8 | layout(set = 2, binding = 1) uniform sampler2D s_uv; 9 | layout(set = 2, binding = 2) uniform usampler2D s_voxel; 10 | layout(set = 2, binding = 3) uniform sampler2D s_random; 11 | 12 | bool test( 13 | const uint direction, 14 | const vec3 position, 15 | const vec2 uv) 16 | { 17 | const vec3 neighbor_position = texture(s_position, uv).xyz; 18 | const vec2 neighbor_uv = texture(s_uv, uv).xy; 19 | if (length(neighbor_uv) == 0.0) 20 | { 21 | return false; 22 | } 23 | const float bias = 0.01; 24 | float values[6]; 25 | values[0] = neighbor_position.z - position.z; 26 | values[1] = position.z - neighbor_position.z; 27 | values[2] = neighbor_position.x - position.x; 28 | values[3] = position.x - neighbor_position.x; 29 | values[4] = neighbor_position.y - position.y; 30 | values[5] = position.y - neighbor_position.y; 31 | return values[direction] > bias; 32 | } 33 | 34 | void main() 35 | { 36 | const vec2 uv = texture(s_uv, i_uv).xy; 37 | const uint voxel = texture(s_voxel, i_uv).x; 38 | if (!get_occluded(voxel) || length(uv) == 0) 39 | { 40 | discard; 41 | } 42 | const vec4 position = texture(s_position, i_uv); 43 | const uint direction = get_direction(voxel); 44 | const vec2 scale = 75.0 / (textureSize(s_voxel, 0) * position.w); 45 | float ssao = 0.0; 46 | int kernel = 2; 47 | for (int x = -kernel; x <= kernel; x++) 48 | { 49 | for (int y = -kernel; y <= kernel; y++) 50 | { 51 | const vec2 origin = i_uv + vec2(x, y) * scale; 52 | const vec2 offset = texture(s_random, origin).xy * scale; 53 | ssao += float(test(direction, position.xyz, origin + offset)); 54 | } 55 | } 56 | kernel = kernel * 2 + 1; 57 | kernel = kernel * kernel; 58 | kernel -= 1; 59 | o_ssao = 1.0 - (ssao / float(kernel)); 60 | } -------------------------------------------------------------------------------- /src/chunk.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "block.h" 7 | #include "config.h" 8 | #include "helpers.h" 9 | 10 | typedef enum 11 | { 12 | CHUNK_TYPE_OPAQUE, 13 | CHUNK_TYPE_TRANSPARENT, 14 | CHUNK_TYPE_COUNT, 15 | } 16 | chunk_type_t; 17 | 18 | typedef struct 19 | { 20 | block_t blocks[CHUNK_X][CHUNK_Y][CHUNK_Z]; 21 | SDL_GPUBuffer* vbos[CHUNK_TYPE_COUNT]; 22 | uint32_t sizes[CHUNK_TYPE_COUNT]; 23 | uint32_t capacities[CHUNK_TYPE_COUNT]; 24 | bool load; 25 | bool mesh; 26 | } 27 | chunk_t; 28 | 29 | void chunk_wrap( 30 | int* x, 31 | int* y, 32 | int* z); 33 | bool chunk_in( 34 | const int x, 35 | const int y, 36 | const int z); 37 | void chunk_set_block( 38 | chunk_t* chunk, 39 | const int x, 40 | const int y, 41 | const int z, 42 | const block_t block); 43 | 44 | typedef struct 45 | { 46 | chunk_t* chunks[WORLD_X][WORLD_Z]; 47 | int x; 48 | int z; 49 | } 50 | terrain_t; 51 | 52 | void terrain_init( 53 | terrain_t* terrain); 54 | void terrain_free( 55 | terrain_t* terrain); 56 | chunk_t* terrain_get( 57 | const terrain_t* terrain, 58 | const int x, 59 | const int z); 60 | bool terrain_in( 61 | const terrain_t* terrain, 62 | const int x, 63 | const int z); 64 | bool terrain_border( 65 | const terrain_t* terrain, 66 | const int x, 67 | const int z); 68 | void terrain_neighbors( 69 | terrain_t* terrain, 70 | const int x, 71 | const int z, 72 | chunk_t* neighbors[DIRECTION_2]); 73 | chunk_t* terrain_get2( 74 | const terrain_t* terrain, 75 | int x, 76 | int z); 77 | bool terrain_in2( 78 | const terrain_t* terrain, 79 | int x, 80 | int z); 81 | bool terrain_border2( 82 | const terrain_t* terrain, 83 | int x, 84 | int z); 85 | void terrain_neighbors2( 86 | terrain_t* terrain, 87 | int x, 88 | int z, 89 | chunk_t* neighbors[DIRECTION_2]); 90 | int* terrain_move( 91 | terrain_t* terrain, 92 | const int x, 93 | const int z, 94 | int* size); -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | project(blocks) 3 | 4 | set(BINARY_DIR ${CMAKE_BINARY_DIR}/bin) 5 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR}) 6 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${BINARY_DIR}) 7 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${BINARY_DIR}) 8 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${BINARY_DIR}) 9 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${BINARY_DIR}) 10 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${BINARY_DIR}) 11 | make_directory(${BINARY_DIR}) 12 | 13 | add_subdirectory(lib/SDL) 14 | add_executable(blocks WIN32 15 | lib/sqlite3/sqlite3.c 16 | lib/stb/stb.c 17 | src/block.c 18 | src/camera.c 19 | src/chunk.c 20 | src/database.c 21 | src/helpers.c 22 | src/main.c 23 | src/noise.c 24 | src/pipeline.c 25 | src/raycast.c 26 | src/voxel.c 27 | src/world.c 28 | ) 29 | target_link_libraries(blocks PUBLIC SDL3::SDL3) 30 | if(UNIX) 31 | target_link_libraries(blocks PUBLIC m) 32 | endif() 33 | target_include_directories(blocks PUBLIC lib/sqlite3) 34 | target_include_directories(blocks PUBLIC lib/stb) 35 | set_target_properties(blocks PROPERTIES C_STANDARD 11) 36 | 37 | function(shader FILE) 38 | set(SOURCE shaders/${FILE}) 39 | set(OUTPUT ${BINARY_DIR}/${FILE}) 40 | add_custom_command( 41 | OUTPUT ${OUTPUT} 42 | COMMAND glslc ${SOURCE} -o ${OUTPUT} -I src 43 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 44 | DEPENDS ${SOURCE} shaders/helpers.glsl src/config.h 45 | COMMENT ${SOURCE} 46 | ) 47 | string(REPLACE . _ NAME ${FILE}) 48 | add_custom_target(${NAME} DEPENDS ${OUTPUT}) 49 | add_dependencies(blocks ${NAME}) 50 | endfunction() 51 | shader(composite.frag) 52 | shader(fullscreen.vert) 53 | shader(opaque.frag) 54 | shader(opaque.vert) 55 | shader(random.frag) 56 | shader(raycast.frag) 57 | shader(raycast.vert) 58 | shader(shadow.frag) 59 | shader(shadow.vert) 60 | shader(sky.frag) 61 | shader(sky.vert) 62 | shader(ssao.frag) 63 | shader(transparent.frag) 64 | shader(transparent.vert) 65 | shader(ui.frag) 66 | 67 | configure_file(LICENSE.txt ${BINARY_DIR} COPYONLY) 68 | configure_file(README.md ${BINARY_DIR} COPYONLY) 69 | configure_file(textures/atlas.png ${BINARY_DIR} COPYONLY) -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #define WINDOW_NAME "blocks" 5 | #define WINDOW_WIDTH 1280 6 | #define WINDOW_HEIGHT 720 7 | #define RENDERER_SIZE 1280 8 | #define DEVICE_VALIDATION false 9 | #define WINDOW_ICON BLOCK_ROSE 10 | #define INPUT_FORWARD SDL_SCANCODE_W 11 | #define INPUT_BACKWARD SDL_SCANCODE_S 12 | #define INPUT_LEFT SDL_SCANCODE_A 13 | #define INPUT_RIGHT SDL_SCANCODE_D 14 | #define INPUT_UP SDL_SCANCODE_E 15 | #define INPUT_DOWN SDL_SCANCODE_Q 16 | #define INPUT_FAST SDL_SCANCODE_LCTRL 17 | #define INPUT_SLOW SDL_SCANCODE_LSHIFT 18 | #define INPUT_BLOCK SDL_SCANCODE_B 19 | #define INPUT_PAUSE SDL_SCANCODE_ESCAPE 20 | #define INPUT_FULLSCREEN SDL_SCANCODE_F11 21 | #define INPUT_PLACE SDL_BUTTON_RMASK 22 | #define INPUT_BREAK SDL_BUTTON_LMASK 23 | #define BLOCK_WIDTH 16.0f 24 | #define ATLAS_WIDTH 512.0f 25 | #define ATLAS_LEVELS 4 26 | #define PLAYER_SPEED 0.015f 27 | #define PLAYER_SENSITIVITY 0.1f 28 | #define PLAYER_Y 60.0f 29 | #define PLAYER_REACH 10.0f 30 | #define SHADOW_SIZE 4096 31 | #define SHADOW_Y 30.0f 32 | #define SHADOW_PITCH (-PI / 4.0f) 33 | #define SHADOW_YAW (PI / 4.0f) 34 | #define CHUNK_X 30 35 | #define CHUNK_Y 210 36 | #define CHUNK_Z 30 37 | #define WORLD_X 20 38 | #define WORLD_Z 20 39 | #define WORLD_CHUNKS (WORLD_X * WORLD_Z) 40 | #define WORLD_WORKERS 4 41 | #define DATABASE_PATH "blocks.sqlite3" 42 | #define DATABASE_COOLDOWN 10000.0f 43 | #define DATABASE_PLAYER 0 44 | #define VOXEL_X_BITS 5 45 | #define VOXEL_Y_BITS 8 46 | #define VOXEL_Z_BITS 5 47 | #define VOXEL_U_BITS 1 48 | #define VOXEL_V_BITS 1 49 | #define VOXEL_FACE_BITS 6 50 | #define VOXEL_DIRECTION_BITS 3 51 | #define VOXEL_SHADOW_BITS 1 52 | #define VOXEL_SHADOWED_BITS 1 53 | #define VOXEL_OCCLUDED_BITS 1 54 | #define VOXEL_X_OFFSET (0) 55 | #define VOXEL_Y_OFFSET (VOXEL_X_OFFSET + VOXEL_X_BITS) 56 | #define VOXEL_Z_OFFSET (VOXEL_Y_OFFSET + VOXEL_Y_BITS) 57 | #define VOXEL_U_OFFSET (VOXEL_Z_OFFSET + VOXEL_Z_BITS) 58 | #define VOXEL_V_OFFSET (VOXEL_U_OFFSET + VOXEL_U_BITS) 59 | #define VOXEL_FACE_OFFSET (VOXEL_V_OFFSET + VOXEL_V_BITS) 60 | #define VOXEL_DIRECTION_OFFSET (VOXEL_FACE_OFFSET + VOXEL_FACE_BITS) 61 | #define VOXEL_SHADOW_OFFSET (VOXEL_DIRECTION_OFFSET + VOXEL_DIRECTION_BITS) 62 | #define VOXEL_SHADOWED_OFFSET (VOXEL_SHADOW_OFFSET + VOXEL_SHADOW_BITS) 63 | #define VOXEL_OCCLUDED_OFFSET (VOXEL_SHADOWED_OFFSET + VOXEL_SHADOWED_BITS) 64 | #define VOXEL_X_MASK ((1 << VOXEL_X_BITS) - 1) 65 | #define VOXEL_Y_MASK ((1 << VOXEL_Y_BITS) - 1) 66 | #define VOXEL_Z_MASK ((1 << VOXEL_Z_BITS) - 1) 67 | #define VOXEL_U_MASK ((1 << VOXEL_U_BITS) - 1) 68 | #define VOXEL_V_MASK ((1 << VOXEL_V_BITS) - 1) 69 | #define VOXEL_FACE_MASK ((1 << VOXEL_FACE_BITS) - 1) 70 | #define VOXEL_DIRECTION_MASK ((1 << VOXEL_DIRECTION_BITS) - 1) 71 | #define VOXEL_SHADOW_MASK ((1 << VOXEL_SHADOW_BITS) - 1) 72 | #define VOXEL_SHADOWED_MASK ((1 << VOXEL_SHADOWED_BITS) - 1) 73 | #define VOXEL_OCCLUDED_MASK ((1 << VOXEL_OCCLUDED_BITS) - 1) 74 | 75 | #endif -------------------------------------------------------------------------------- /shaders/helpers.glsl: -------------------------------------------------------------------------------- 1 | #ifndef HELPERS_GLSL 2 | #define HELPERS_GLSL 3 | 4 | #include "config.h" 5 | 6 | const vec3 normals[6] = vec3[6] 7 | ( 8 | vec3( 0, 0, 1 ), 9 | vec3( 0, 0,-1 ), 10 | vec3( 1, 0, 0 ), 11 | vec3(-1, 0, 0 ), 12 | vec3( 0, 1, 0 ), 13 | vec3( 0,-1, 0 ) 14 | ); 15 | 16 | vec3 get_position( 17 | const uint voxel) 18 | { 19 | return vec3( 20 | voxel >> VOXEL_X_OFFSET & VOXEL_X_MASK, 21 | voxel >> VOXEL_Y_OFFSET & VOXEL_Y_MASK, 22 | voxel >> VOXEL_Z_OFFSET & VOXEL_Z_MASK); 23 | } 24 | 25 | vec3 get_uv( 26 | const uint voxel) 27 | { 28 | return vec3( 29 | voxel >> VOXEL_U_OFFSET & VOXEL_U_MASK, 30 | voxel >> VOXEL_V_OFFSET & VOXEL_V_MASK, 31 | voxel >> VOXEL_FACE_OFFSET & VOXEL_FACE_MASK); 32 | } 33 | 34 | uint get_direction( 35 | const uint voxel) 36 | { 37 | return voxel >> VOXEL_DIRECTION_OFFSET & VOXEL_DIRECTION_MASK; 38 | } 39 | 40 | vec3 get_normal( 41 | const uint voxel) 42 | { 43 | return normals[get_direction(voxel)]; 44 | } 45 | 46 | bool get_shadow( 47 | const uint voxel) 48 | { 49 | return bool(voxel >> VOXEL_SHADOW_OFFSET & VOXEL_SHADOW_MASK); 50 | } 51 | 52 | bool get_shadowed( 53 | const uint voxel) 54 | { 55 | return bool(voxel >> VOXEL_SHADOWED_OFFSET & VOXEL_SHADOWED_MASK); 56 | } 57 | 58 | bool get_occluded( 59 | const uint voxel) 60 | { 61 | return bool(voxel >> VOXEL_OCCLUDED_OFFSET & VOXEL_OCCLUDED_MASK); 62 | } 63 | 64 | vec3 get_sky( 65 | const float y) 66 | { 67 | return mix(vec3(0.7, 0.9, 1.0), vec3(0.3, 0.6, 0.9), clamp(y, 0.0, 0.8)); 68 | } 69 | 70 | float get_fog( 71 | const float x) 72 | { 73 | return min(pow(x / 250.0, 2.5), 1.0); 74 | } 75 | 76 | vec4 get_color( 77 | const sampler2DArray atlas, 78 | const sampler2D shadowmap, 79 | const vec3 position, 80 | const vec3 uv, 81 | const vec3 normal, 82 | const vec3 player_position, 83 | const vec3 shadow_position, 84 | const vec3 shadow_vector, 85 | const bool shadowed, 86 | const bool occluded, 87 | const float fog, 88 | const float ssao, 89 | const float alpha) 90 | { 91 | vec3 shadow_uv; 92 | shadow_uv.x = shadow_position.x * 0.5 + 0.5; 93 | shadow_uv.y = 1.0 - (shadow_position.y * 0.5 + 0.5); 94 | shadow_uv.z = shadow_position.z; 95 | float ao = ssao * 0.4; 96 | float ambient = 0.2; 97 | float directional = 0.0; 98 | const float angle = dot(normal, -shadow_vector); 99 | const float depth = shadow_uv.z - 0.001; 100 | if (!shadowed || (angle > 0.0 && ( 101 | all(lessThanEqual(shadow_uv, vec3(0.0))) || 102 | all(greaterThanEqual(shadow_uv, vec3(1.0))) || 103 | (depth < texture(shadowmap, shadow_uv.xy).x)))) 104 | { 105 | directional = max(angle, 0.0) * 1.2; 106 | } 107 | if (!occluded) 108 | { 109 | ao = 0.7; 110 | } 111 | vec4 color = texture(atlas, uv); 112 | color.a = clamp(color.a + alpha, 0.0, 1.0); 113 | const float light = ao + ambient + directional; 114 | const float dy = position.y - player_position.y; 115 | const float dx = distance(position.xz, player_position.xz); 116 | const float pitch = atan(dy, dx); 117 | const vec4 sky = vec4(get_sky(pitch), 1.0); 118 | const vec4 composite = vec4(color.xyz * light, color.a); 119 | return mix(composite, sky, fog); 120 | } 121 | 122 | #endif -------------------------------------------------------------------------------- /src/noise.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "block.h" 4 | #include "chunk.h" 5 | #include "helpers.h" 6 | #include "noise.h" 7 | 8 | void noise_generate( 9 | chunk_t* chunk, 10 | const int x, 11 | const int z) 12 | { 13 | for (int a = 0; a < CHUNK_X; a++) 14 | for (int b = 0; b < CHUNK_Z; b++) 15 | { 16 | const int s = x * CHUNK_X + a; 17 | const int t = z * CHUNK_Z + b; 18 | bool low = false; 19 | bool grass = false; 20 | float height = stb_perlin_fbm_noise3(s * 0.005f, 0.0f, t * 0.005f, 2.0f, 0.5f, 6); 21 | height *= 50.0f; 22 | height = powf(max(height, 0.0f), 1.3f); 23 | height += 30; 24 | height = clamp(height, 0, CHUNK_Y - 1); 25 | if (height < 40) 26 | { 27 | height += stb_perlin_fbm_noise3(-s * 0.01f, 0.0f, t * 0.01f, 2.0f, 0.5f, 6) * 12.0f; 28 | low = true; 29 | } 30 | float biome = stb_perlin_fbm_noise3(s * 0.2f, 0.0f, t * 0.2f, 2.0f, 0.5f, 6); 31 | block_t top; 32 | block_t bottom; 33 | if (height + biome < 31) 34 | { 35 | top = BLOCK_SAND; 36 | bottom = BLOCK_SAND; 37 | } 38 | else 39 | { 40 | biome *= 8.0f; 41 | biome = clamp(biome, -5.0f, 5.0f); 42 | if (height + biome < 61) 43 | { 44 | top = BLOCK_GRASS; 45 | bottom = BLOCK_DIRT; 46 | grass = true; 47 | } 48 | else if (height + biome < 132) 49 | { 50 | top = BLOCK_STONE; 51 | bottom = BLOCK_STONE; 52 | } 53 | else 54 | { 55 | top = BLOCK_SNOW; 56 | bottom = BLOCK_STONE; 57 | } 58 | } 59 | int y = 0; 60 | for (; y < height; y++) 61 | { 62 | chunk_set_block(chunk, a, y, b, bottom); 63 | } 64 | chunk_set_block(chunk, a, y, b, top); 65 | for (; y < 30; y++) 66 | { 67 | chunk_set_block(chunk, a, y, b, BLOCK_WATER); 68 | } 69 | if (low && grass) 70 | { 71 | const float plant = stb_perlin_fbm_noise3(s * 0.2f, 0.0f, t * 0.2f, 2.0f, 0.5f, 3) * 0.5f + 0.5f; 72 | if (plant > 0.8f && a > 2 && a < CHUNK_X - 2 && b > 2 && b < CHUNK_Z - 2) 73 | { 74 | const int log = 3 + plant * 2.0f; 75 | for (int dy = 0; dy < log; dy++) 76 | { 77 | chunk_set_block(chunk, a, y + dy + 1, b, BLOCK_LOG); 78 | } 79 | for (int dx = -1; dx <= 1; dx++) 80 | for (int dz = -1; dz <= 1; dz++) 81 | for (int dy = 0; dy < 2; dy++) 82 | { 83 | if (dx || dz || dy) 84 | { 85 | chunk_set_block(chunk, a + dx, y + log + dy, b + dz, BLOCK_LEAVES); 86 | } 87 | } 88 | } 89 | else if (plant > 0.55f) 90 | { 91 | chunk_set_block(chunk, a, y + 1, b, BLOCK_BUSH); 92 | } 93 | else if (plant > 0.52f) 94 | { 95 | const int value = max(((int) (plant * 1000.0f)) % 4, 0); 96 | const block_t flowers[] = {BLOCK_BLUEBELL, BLOCK_DANDELION, BLOCK_LAVENDER, BLOCK_ROSE}; 97 | chunk_set_block(chunk, a, y + 1, b, flowers[value]); 98 | } 99 | } 100 | if (height > 130) 101 | { 102 | continue; 103 | } 104 | const float cloud = stb_perlin_turbulence_noise3(s * 0.015f, 0.0f, t * 0.015f, 2.0f, 0.5f, 6); 105 | int scale = -1; 106 | if (cloud > 0.9f) 107 | { 108 | scale = 2; 109 | } 110 | else if (cloud > 0.7f) 111 | { 112 | scale = 1; 113 | } 114 | else if (cloud > 0.6) 115 | { 116 | scale = 0; 117 | } 118 | for (int y = -scale; y <= scale; y++) 119 | { 120 | chunk_set_block(chunk, a, 155 - y, b, BLOCK_CLOUD); 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /src/block.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "block.h" 3 | #include "helpers.h" 4 | 5 | bool block_opaque( 6 | const block_t block) 7 | { 8 | assert(block < BLOCK_COUNT); 9 | switch (block) 10 | { 11 | case BLOCK_WATER: 12 | return false; 13 | } 14 | return true; 15 | } 16 | 17 | bool block_shadow( 18 | const block_t block) 19 | { 20 | assert(block < BLOCK_COUNT); 21 | if (block_sprite(block)) 22 | { 23 | return false; 24 | } 25 | switch (block) 26 | { 27 | case BLOCK_CLOUD: 28 | return false; 29 | } 30 | return true; 31 | } 32 | 33 | bool block_shadowed( 34 | const block_t block) 35 | { 36 | assert(block < BLOCK_COUNT); 37 | switch (block) 38 | { 39 | case BLOCK_CLOUD: 40 | return false; 41 | } 42 | return true; 43 | } 44 | 45 | bool block_occluded( 46 | const block_t block) 47 | { 48 | assert(block < BLOCK_COUNT); 49 | switch (block) 50 | { 51 | case BLOCK_CLOUD: 52 | return false; 53 | } 54 | return true; 55 | } 56 | 57 | bool block_solid( 58 | const block_t block) 59 | { 60 | assert(block < BLOCK_COUNT); 61 | if (block_sprite(block)) 62 | { 63 | return false; 64 | } 65 | switch (block) 66 | { 67 | case BLOCK_EMPTY: 68 | case BLOCK_WATER: 69 | return false; 70 | } 71 | return true; 72 | } 73 | 74 | bool block_sprite( 75 | const block_t block) 76 | { 77 | assert(block < BLOCK_COUNT); 78 | switch (block) 79 | { 80 | case BLOCK_BUSH: 81 | case BLOCK_BLUEBELL: 82 | case BLOCK_DANDELION: 83 | case BLOCK_LAVENDER: 84 | case BLOCK_ROSE: 85 | return true; 86 | } 87 | return false; 88 | } 89 | 90 | const int blocks[][DIRECTION_3] = 91 | { 92 | [BLOCK_BLUEBELL] = 93 | { 94 | [DIRECTION_E] = 13, 95 | [DIRECTION_W] = 13, 96 | [DIRECTION_N] = 13, 97 | [DIRECTION_S] = 13, 98 | [DIRECTION_U] = 13, 99 | [DIRECTION_D] = 13, 100 | }, 101 | [BLOCK_LAVENDER] = 102 | { 103 | [DIRECTION_E] = 14, 104 | [DIRECTION_W] = 14, 105 | [DIRECTION_N] = 14, 106 | [DIRECTION_S] = 14, 107 | [DIRECTION_U] = 14, 108 | [DIRECTION_D] = 14, 109 | }, 110 | [BLOCK_CLOUD] = 111 | { 112 | [DIRECTION_E] = 9, 113 | [DIRECTION_W] = 9, 114 | [DIRECTION_N] = 9, 115 | [DIRECTION_S] = 9, 116 | [DIRECTION_U] = 9, 117 | [DIRECTION_D] = 9, 118 | }, 119 | [BLOCK_DANDELION] = 120 | { 121 | [DIRECTION_E] = 12, 122 | [DIRECTION_W] = 12, 123 | [DIRECTION_N] = 12, 124 | [DIRECTION_S] = 12, 125 | [DIRECTION_U] = 12, 126 | [DIRECTION_D] = 12, 127 | }, 128 | [BLOCK_BUSH] = 129 | { 130 | [DIRECTION_E] = 15, 131 | [DIRECTION_W] = 15, 132 | [DIRECTION_N] = 15, 133 | [DIRECTION_S] = 15, 134 | [DIRECTION_U] = 15, 135 | [DIRECTION_D] = 15, 136 | }, 137 | [BLOCK_DIRT] = 138 | { 139 | [DIRECTION_E] = 3, 140 | [DIRECTION_W] = 3, 141 | [DIRECTION_N] = 3, 142 | [DIRECTION_S] = 3, 143 | [DIRECTION_U] = 3, 144 | [DIRECTION_D] = 3, 145 | }, 146 | [BLOCK_GRASS] = 147 | { 148 | [DIRECTION_E] = 2, 149 | [DIRECTION_W] = 2, 150 | [DIRECTION_N] = 2, 151 | [DIRECTION_S] = 2, 152 | [DIRECTION_U] = 1, 153 | [DIRECTION_D] = 3, 154 | }, 155 | [BLOCK_LEAVES] = 156 | { 157 | [DIRECTION_E] = 10, 158 | [DIRECTION_W] = 10, 159 | [DIRECTION_N] = 10, 160 | [DIRECTION_S] = 10, 161 | [DIRECTION_U] = 10, 162 | [DIRECTION_D] = 10, 163 | }, 164 | [BLOCK_LOG] = 165 | { 166 | [DIRECTION_E] = 8, 167 | [DIRECTION_W] = 8, 168 | [DIRECTION_N] = 8, 169 | [DIRECTION_S] = 8, 170 | [DIRECTION_U] = 7, 171 | [DIRECTION_D] = 7, 172 | }, 173 | [BLOCK_ROSE] = 174 | { 175 | [DIRECTION_E] = 11, 176 | [DIRECTION_W] = 11, 177 | [DIRECTION_N] = 11, 178 | [DIRECTION_S] = 11, 179 | [DIRECTION_U] = 11, 180 | [DIRECTION_D] = 11, 181 | }, 182 | [BLOCK_SAND] = 183 | { 184 | [DIRECTION_E] = 5, 185 | [DIRECTION_W] = 5, 186 | [DIRECTION_N] = 5, 187 | [DIRECTION_S] = 5, 188 | [DIRECTION_U] = 5, 189 | [DIRECTION_D] = 5, 190 | }, 191 | [BLOCK_SNOW] = 192 | { 193 | [DIRECTION_E] = 6, 194 | [DIRECTION_W] = 6, 195 | [DIRECTION_N] = 6, 196 | [DIRECTION_S] = 6, 197 | [DIRECTION_U] = 6, 198 | [DIRECTION_D] = 6, 199 | }, 200 | [BLOCK_STONE] = 201 | { 202 | [DIRECTION_E] = 4, 203 | [DIRECTION_W] = 4, 204 | [DIRECTION_N] = 4, 205 | [DIRECTION_S] = 4, 206 | [DIRECTION_U] = 4, 207 | [DIRECTION_D] = 4, 208 | }, 209 | [BLOCK_WATER] = 210 | { 211 | [DIRECTION_E] = 16, 212 | [DIRECTION_W] = 16, 213 | [DIRECTION_N] = 16, 214 | [DIRECTION_S] = 16, 215 | [DIRECTION_U] = 16, 216 | [DIRECTION_D] = 16, 217 | }, 218 | }; -------------------------------------------------------------------------------- /src/chunk.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "chunk.h" 7 | #include "config.h" 8 | #include "helpers.h" 9 | 10 | void chunk_wrap( 11 | int* x, 12 | int* y, 13 | int* z) 14 | { 15 | assert(x); 16 | assert(y); 17 | assert(z); 18 | *x = (*x % CHUNK_X + CHUNK_X) % CHUNK_X; 19 | *y = (*y % CHUNK_Y + CHUNK_Y) % CHUNK_Y; 20 | *z = (*z % CHUNK_Z + CHUNK_Z) % CHUNK_Z; 21 | } 22 | 23 | bool chunk_in( 24 | const int x, 25 | const int y, 26 | const int z) 27 | { 28 | return 29 | x >= 0 && 30 | y >= 0 && 31 | z >= 0 && 32 | x < CHUNK_X && 33 | y < CHUNK_Y && 34 | z < CHUNK_Z; 35 | } 36 | 37 | void chunk_set_block( 38 | chunk_t* chunk, 39 | const int x, 40 | const int y, 41 | const int z, 42 | const block_t block) 43 | { 44 | assert(chunk); 45 | assert(chunk_in(x, y, z)); 46 | chunk->blocks[x][y][z] = block; 47 | } 48 | 49 | void terrain_init( 50 | terrain_t* terrain) 51 | { 52 | assert(terrain); 53 | terrain->x = INT_MAX; 54 | terrain->z = INT_MAX; 55 | for (int x = 0; x < WORLD_X; x++) 56 | for (int z = 0; z < WORLD_Z; z++) 57 | { 58 | terrain->chunks[x][z] = calloc(1, sizeof(chunk_t)); 59 | assert(terrain->chunks[x][z]); 60 | } 61 | } 62 | 63 | void terrain_free( 64 | terrain_t* terrain) 65 | { 66 | assert(terrain); 67 | for (int x = 0; x < WORLD_X; x++) 68 | for (int z = 0; z < WORLD_Z; z++) 69 | { 70 | free(terrain->chunks[x][z]); 71 | terrain->chunks[x][z] = NULL; 72 | } 73 | } 74 | 75 | chunk_t* terrain_get( 76 | const terrain_t* terrain, 77 | const int x, 78 | const int z) 79 | { 80 | assert(terrain); 81 | assert(terrain_in(terrain, x, z)); 82 | return terrain->chunks[x][z]; 83 | } 84 | 85 | bool terrain_in( 86 | const terrain_t* terrain, 87 | const int x, 88 | const int z) 89 | { 90 | assert(terrain); 91 | return 92 | x >= 0 && 93 | z >= 0 && 94 | x < WORLD_X && 95 | z < WORLD_Z; 96 | } 97 | 98 | bool terrain_border( 99 | const terrain_t* terrain, 100 | const int x, 101 | const int z) 102 | { 103 | assert(terrain); 104 | return 105 | x == 0 || 106 | z == 0 || 107 | x == WORLD_X - 1 || 108 | z == WORLD_Z - 1; 109 | } 110 | 111 | void terrain_neighbors( 112 | terrain_t* terrain, 113 | const int x, 114 | const int z, 115 | chunk_t* neighbors[DIRECTION_2]) 116 | { 117 | assert(terrain); 118 | assert(terrain_in(terrain, x, z)); 119 | for (direction_t d = 0; d < DIRECTION_2; d++) 120 | { 121 | const int a = x + directions[d][0]; 122 | const int b = z + directions[d][2]; 123 | if (terrain_in(terrain, a, b)) 124 | { 125 | neighbors[d] = terrain_get(terrain, a, b); 126 | } 127 | else 128 | { 129 | neighbors[d] = NULL; 130 | } 131 | } 132 | } 133 | 134 | chunk_t* terrain_get2( 135 | const terrain_t* terrain, 136 | int x, 137 | int z) 138 | { 139 | assert(terrain); 140 | x -= terrain->x; 141 | z -= terrain->z; 142 | return terrain_get(terrain, x, z); 143 | } 144 | 145 | bool terrain_in2( 146 | const terrain_t* terrain, 147 | int x, 148 | int z) 149 | { 150 | assert(terrain); 151 | x -= terrain->x; 152 | z -= terrain->z; 153 | return terrain_in(terrain, x, z); 154 | } 155 | 156 | bool terrain_border2( 157 | const terrain_t* terrain, 158 | int x, 159 | int z) 160 | { 161 | assert(terrain); 162 | x -= terrain->x; 163 | z -= terrain->z; 164 | return terrain_border(terrain, x, z); 165 | } 166 | 167 | void terrain_neighbors2( 168 | terrain_t* terrain, 169 | int x, 170 | int z, 171 | chunk_t* neighbors[DIRECTION_2]) 172 | { 173 | assert(terrain); 174 | x -= terrain->x; 175 | z -= terrain->z; 176 | terrain_neighbors(terrain, x, z, neighbors); 177 | } 178 | 179 | int* terrain_move( 180 | terrain_t* terrain, 181 | const int x, 182 | const int z, 183 | int* size) 184 | { 185 | assert(terrain); 186 | assert(size); 187 | *size = 0; 188 | const int a = x - terrain->x; 189 | const int b = z - terrain->z; 190 | if (!a && !b) 191 | { 192 | return NULL; 193 | } 194 | terrain->x = x; 195 | terrain->z = z; 196 | chunk_t* in[WORLD_X][WORLD_Z] = {0}; 197 | chunk_t* out[WORLD_CHUNKS]; 198 | int* indices = malloc(WORLD_CHUNKS * 2 * sizeof(int)); 199 | assert(indices); 200 | for (int i = 0; i < WORLD_X; i++) 201 | for (int j = 0; j < WORLD_Z; j++) 202 | { 203 | const int c = i - a; 204 | const int d = j - b; 205 | if (terrain_in(terrain, c, d)) 206 | { 207 | in[c][d] = terrain_get(terrain, i, j); 208 | } 209 | else 210 | { 211 | out[(*size)++] = terrain_get(terrain, i, j); 212 | } 213 | terrain->chunks[i][j] = NULL; 214 | } 215 | memcpy(terrain->chunks, in, sizeof(in)); 216 | int n = *size; 217 | for (int i = 0; i < WORLD_X; i++) 218 | for (int j = 0; j < WORLD_Z; j++) 219 | { 220 | if (terrain->chunks[i][j]) 221 | { 222 | continue; 223 | } 224 | --n; 225 | terrain->chunks[i][j] = out[n]; 226 | indices[n * 2 + 0] = i; 227 | indices[n * 2 + 1] = j; 228 | } 229 | assert(!n); 230 | return indices; 231 | } -------------------------------------------------------------------------------- /src/database.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "block.h" 6 | #include "database.h" 7 | #include "chunk.h" 8 | #include "helpers.h" 9 | 10 | static sqlite3* handle; 11 | static sqlite3_stmt* set_player_stmt; 12 | static sqlite3_stmt* get_player_stmt; 13 | static sqlite3_stmt* set_block_stmt; 14 | static sqlite3_stmt* get_blocks_stmt; 15 | static SDL_Mutex* mtx; 16 | 17 | bool database_init( 18 | const char* file) 19 | { 20 | assert(file); 21 | if (sqlite3_open(file, &handle)) 22 | { 23 | SDL_Log("Failed to open %s database: %s", file, sqlite3_errmsg(handle)); 24 | return false; 25 | } 26 | const char* players_table = 27 | "CREATE TABLE IF NOT EXISTS players (" 28 | " id INT PRIMARY KEY NOT NULL," 29 | " x REAL NOT NULL," 30 | " y REAL NOT NULL," 31 | " z REAL NOT NULL," 32 | " pitch REAL NOT NULL," 33 | " yaw REAL NOT NULL" 34 | ");"; 35 | const char* blocks_table = 36 | "CREATE TABLE IF NOT EXISTS blocks (" 37 | " a INTEGER NOT NULL," 38 | " c INTEGER NOT NULL," 39 | " x INTEGER NOT NULL," 40 | " y INTEGER NOT NULL," 41 | " z INTEGER NOT NULL," 42 | " data INTEGER NOT NULL," 43 | " PRIMARY KEY (a, c, x, y, z)" 44 | ");"; 45 | if (sqlite3_exec(handle, players_table, NULL, NULL, NULL)) 46 | { 47 | SDL_Log("Failed to create players table: %s", sqlite3_errmsg(handle)); 48 | return false; 49 | } 50 | if (sqlite3_exec(handle, blocks_table, NULL, NULL, NULL)) 51 | { 52 | SDL_Log("Failed to create blocks table: %s", sqlite3_errmsg(handle)); 53 | return false; 54 | } 55 | const char* set_player = 56 | "INSERT OR REPLACE INTO players (id, x, y, z, pitch, yaw) " 57 | "VALUES (?, ?, ?, ?, ?, ?);"; 58 | const char* get_player = 59 | "SELECT x, y, z, pitch, yaw FROM players " 60 | "WHERE id = ?;"; 61 | const char* set_block = 62 | "INSERT OR REPLACE INTO blocks (a, c, x, y, z, data) " 63 | "VALUES (?, ?, ?, ?, ?, ?);"; 64 | const char* get_blocks = 65 | "SELECT x, y, z, data FROM blocks " 66 | "WHERE a = ? AND c = ?;"; 67 | if (sqlite3_prepare_v2(handle, set_player, -1, &set_player_stmt, NULL)) 68 | { 69 | SDL_Log("Failed to prepare set player: %s", sqlite3_errmsg(handle)); 70 | return false; 71 | } 72 | if (sqlite3_prepare_v2(handle, get_player, -1, &get_player_stmt, NULL)) 73 | { 74 | SDL_Log("Failed to prepare get player: %s", sqlite3_errmsg(handle)); 75 | return false; 76 | } 77 | if (sqlite3_prepare_v2(handle, set_block, -1, &set_block_stmt, NULL)) 78 | { 79 | SDL_Log("Failed to prepare set block: %s", sqlite3_errmsg(handle)); 80 | return false; 81 | } 82 | if (sqlite3_prepare_v2(handle, get_blocks, -1, &get_blocks_stmt, NULL)) 83 | { 84 | SDL_Log("Failed to prepare get blocks: %s", sqlite3_errmsg(handle)); 85 | return false; 86 | } 87 | const char* blocks_index = 88 | "CREATE INDEX IF NOT EXISTS blocks_index " 89 | "ON blocks (a, c);"; 90 | if (sqlite3_exec(handle, blocks_index, NULL, NULL, NULL)) 91 | { 92 | SDL_Log("Failed to create blocks index: %s", sqlite3_errmsg(handle)); 93 | return false; 94 | } 95 | mtx = SDL_CreateMutex(); 96 | if (!mtx) 97 | { 98 | SDL_Log("Failed to create mutex: %s", SDL_GetError()); 99 | return false; 100 | } 101 | sqlite3_exec(handle, "BEGIN;", NULL, NULL, NULL); 102 | return true; 103 | } 104 | 105 | void database_free() 106 | { 107 | SDL_DestroyMutex(mtx); 108 | sqlite3_exec(handle, "COMMIT;", NULL, NULL, NULL); 109 | sqlite3_finalize(set_player_stmt); 110 | sqlite3_finalize(get_player_stmt); 111 | sqlite3_finalize(set_block_stmt); 112 | sqlite3_finalize(get_blocks_stmt); 113 | sqlite3_close(handle); 114 | } 115 | 116 | void database_commit() 117 | { 118 | SDL_LockMutex(mtx); 119 | sqlite3_exec(handle, "COMMIT; BEGIN;", NULL, NULL, NULL); 120 | SDL_UnlockMutex(mtx); 121 | } 122 | 123 | void database_set_player( 124 | const int id, 125 | const float x, 126 | const float y, 127 | const float z, 128 | const float pitch, 129 | const float yaw) 130 | { 131 | SDL_LockMutex(mtx); 132 | sqlite3_bind_int(set_player_stmt, 1, id); 133 | sqlite3_bind_double(set_player_stmt, 2, x); 134 | sqlite3_bind_double(set_player_stmt, 3, y); 135 | sqlite3_bind_double(set_player_stmt, 4, z); 136 | sqlite3_bind_double(set_player_stmt, 5, pitch); 137 | sqlite3_bind_double(set_player_stmt, 6, yaw); 138 | if (sqlite3_step(set_player_stmt) != SQLITE_DONE) 139 | { 140 | SDL_Log("Failed to set player: %s", sqlite3_errmsg(handle)); 141 | } 142 | sqlite3_reset(set_player_stmt); 143 | SDL_UnlockMutex(mtx); 144 | } 145 | 146 | bool database_get_player( 147 | const int id, 148 | float* x, 149 | float* y, 150 | float* z, 151 | float* pitch, 152 | float* yaw) 153 | { 154 | assert(x); 155 | assert(y); 156 | assert(z); 157 | assert(pitch); 158 | assert(yaw); 159 | SDL_LockMutex(mtx); 160 | sqlite3_bind_int(get_player_stmt, 1, id); 161 | const bool player = sqlite3_step(get_player_stmt) == SQLITE_ROW; 162 | if (player) 163 | { 164 | *x = sqlite3_column_double(get_player_stmt, 0); 165 | *y = sqlite3_column_double(get_player_stmt, 1); 166 | *z = sqlite3_column_double(get_player_stmt, 2); 167 | *pitch = sqlite3_column_double(get_player_stmt, 3); 168 | *yaw = sqlite3_column_double(get_player_stmt, 4); 169 | } 170 | sqlite3_reset(get_player_stmt); 171 | SDL_UnlockMutex(mtx); 172 | return player; 173 | } 174 | 175 | void database_set_block( 176 | const int a, 177 | const int c, 178 | const int x, 179 | const int y, 180 | const int z, 181 | const block_t block) 182 | { 183 | SDL_LockMutex(mtx); 184 | sqlite3_bind_int(set_block_stmt, 1, a); 185 | sqlite3_bind_int(set_block_stmt, 2, c); 186 | sqlite3_bind_int(set_block_stmt, 3, x); 187 | sqlite3_bind_int(set_block_stmt, 4, y); 188 | sqlite3_bind_int(set_block_stmt, 5, z); 189 | sqlite3_bind_int(set_block_stmt, 6, block); 190 | if (sqlite3_step(set_block_stmt) != SQLITE_DONE) 191 | { 192 | SDL_Log("Failed to set block: %s", sqlite3_errmsg(handle)); 193 | } 194 | sqlite3_reset(set_block_stmt); 195 | SDL_UnlockMutex(mtx); 196 | } 197 | 198 | void database_get_blocks( 199 | chunk_t* chunk, 200 | const int a, 201 | const int c) 202 | { 203 | assert(chunk); 204 | SDL_LockMutex(mtx); 205 | sqlite3_bind_int(get_blocks_stmt, 1, a); 206 | sqlite3_bind_int(get_blocks_stmt, 2, c); 207 | while (sqlite3_step(get_blocks_stmt) == SQLITE_ROW) 208 | { 209 | const int x = sqlite3_column_int(get_blocks_stmt, 0); 210 | const int y = sqlite3_column_int(get_blocks_stmt, 1); 211 | const int z = sqlite3_column_int(get_blocks_stmt, 2); 212 | const block_t block = sqlite3_column_int(get_blocks_stmt, 3); 213 | chunk_set_block(chunk, x, y, z, block); 214 | } 215 | sqlite3_reset(get_blocks_stmt); 216 | SDL_UnlockMutex(mtx); 217 | } -------------------------------------------------------------------------------- /src/camera.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "camera.h" 4 | #include "helpers.h" 5 | 6 | static void multiply( 7 | float matrix[4][4], 8 | const float a[4][4], 9 | const float b[4][4]) 10 | { 11 | float c[4][4]; 12 | for (int i = 0; i < 4; i++) 13 | { 14 | for (int j = 0; j < 4; j++) 15 | { 16 | c[i][j] = 0.0f; 17 | c[i][j] += a[0][j] * b[i][0]; 18 | c[i][j] += a[1][j] * b[i][1]; 19 | c[i][j] += a[2][j] * b[i][2]; 20 | c[i][j] += a[3][j] * b[i][3]; 21 | } 22 | } 23 | for (int i = 0; i < 4; i++) 24 | { 25 | for (int j = 0; j < 4; j++) 26 | { 27 | matrix[i][j] = c[i][j]; 28 | } 29 | } 30 | } 31 | 32 | static void translate( 33 | float matrix[4][4], 34 | const float x, 35 | const float y, 36 | const float z) 37 | { 38 | matrix[0][0] = 1.0f; 39 | matrix[0][1] = 0.0f; 40 | matrix[0][2] = 0.0f; 41 | matrix[0][3] = 0.0f; 42 | matrix[1][0] = 0.0f; 43 | matrix[1][1] = 1.0f; 44 | matrix[1][2] = 0.0f; 45 | matrix[1][3] = 0.0f; 46 | matrix[2][0] = 0.0f; 47 | matrix[2][1] = 0.0f; 48 | matrix[2][2] = 1.0f; 49 | matrix[2][3] = 0.0f; 50 | matrix[3][0] = x; 51 | matrix[3][1] = y; 52 | matrix[3][2] = z; 53 | matrix[3][3] = 1.0f; 54 | } 55 | 56 | static void rotate( 57 | float matrix[4][4], 58 | const float x, 59 | const float y, 60 | const float z, 61 | const float angle) 62 | { 63 | const float s = sinf(angle); 64 | const float c = cosf(angle); 65 | const float i = 1.0f - c; 66 | matrix[0][0] = i * x * x + c; 67 | matrix[0][1] = i * x * y - z * s; 68 | matrix[0][2] = i * z * x + y * s; 69 | matrix[0][3] = 0.0f; 70 | matrix[1][0] = i * x * y + z * s; 71 | matrix[1][1] = i * y * y + c; 72 | matrix[1][2] = i * y * z - x * s; 73 | matrix[1][3] = 0.0f; 74 | matrix[2][0] = i * z * x - y * s; 75 | matrix[2][1] = i * y * z + x * s; 76 | matrix[2][2] = i * z * z + c; 77 | matrix[2][3] = 0.0f; 78 | matrix[3][0] = 0.0f; 79 | matrix[3][1] = 0.0f; 80 | matrix[3][2] = 0.0f; 81 | matrix[3][3] = 1.0f; 82 | } 83 | 84 | static void perspective( 85 | float matrix[4][4], 86 | const float aspect, 87 | const float fov, 88 | const float near, 89 | const float far) 90 | { 91 | const float f = 1.0f / tanf(fov / 2.0f); 92 | matrix[0][0] = f / aspect; 93 | matrix[0][1] = 0.0f; 94 | matrix[0][2] = 0.0f; 95 | matrix[0][3] = 0.0f; 96 | matrix[1][0] = 0.0f; 97 | matrix[1][1] = f; 98 | matrix[1][2] = 0.0f; 99 | matrix[1][3] = 0.0f; 100 | matrix[2][0] = 0.0f; 101 | matrix[2][1] = 0.0f; 102 | matrix[2][2] = -(far + near) / (far - near); 103 | matrix[2][3] = -1.0f; 104 | matrix[3][0] = 0.0f; 105 | matrix[3][1] = 0.0f; 106 | matrix[3][2] = -(2.0f * far * near) / (far - near); 107 | matrix[3][3] = 0.0f; 108 | } 109 | 110 | static void ortho( 111 | float matrix[4][4], 112 | const float left, 113 | const float right, 114 | const float bottom, 115 | const float top, 116 | const float near, 117 | const float far) 118 | { 119 | matrix[0][0] = 2.0f / (right - left); 120 | matrix[0][1] = 0.0f; 121 | matrix[0][2] = 0.0f; 122 | matrix[0][3] = 0.0f; 123 | matrix[1][0] = 0.0f; 124 | matrix[1][1] = 2.0f / (top - bottom); 125 | matrix[1][2] = 0.0f; 126 | matrix[1][3] = 0.0f; 127 | matrix[2][0] = 0.0f; 128 | matrix[2][1] = 0.0f; 129 | matrix[2][2] = -1.0f / (far - near); 130 | matrix[2][3] = 0.0f; 131 | matrix[3][0] = -(right + left) / (right - left); 132 | matrix[3][1] = -(top + bottom) / (top - bottom); 133 | matrix[3][2] = -near / (far - near); 134 | matrix[3][3] = 1.0f; 135 | } 136 | 137 | static void frustum( 138 | float planes[6][4], 139 | const float a[4][4]) 140 | { 141 | planes[0][0] = a[0][3] + a[0][0]; 142 | planes[0][1] = a[1][3] + a[1][0]; 143 | planes[0][2] = a[2][3] + a[2][0]; 144 | planes[0][3] = a[3][3] + a[3][0]; 145 | planes[1][0] = a[0][3] - a[0][0]; 146 | planes[1][1] = a[1][3] - a[1][0]; 147 | planes[1][2] = a[2][3] - a[2][0]; 148 | planes[1][3] = a[3][3] - a[3][0]; 149 | planes[2][0] = a[0][3] + a[0][1]; 150 | planes[2][1] = a[1][3] + a[1][1]; 151 | planes[2][2] = a[2][3] + a[2][1]; 152 | planes[2][3] = a[3][3] + a[3][1]; 153 | planes[3][0] = a[0][3] - a[0][1]; 154 | planes[3][1] = a[1][3] - a[1][1]; 155 | planes[3][2] = a[2][3] - a[2][1]; 156 | planes[3][3] = a[3][3] - a[3][1]; 157 | planes[4][0] = a[0][3] + a[0][2]; 158 | planes[4][1] = a[1][3] + a[1][2]; 159 | planes[4][2] = a[2][3] + a[2][2]; 160 | planes[4][3] = a[3][3] + a[3][2]; 161 | planes[5][0] = a[0][3] - a[0][2]; 162 | planes[5][1] = a[1][3] - a[1][2]; 163 | planes[5][2] = a[2][3] - a[2][2]; 164 | planes[5][3] = a[3][3] - a[3][2]; 165 | for (int i = 0; i < 6; ++i) 166 | { 167 | float length = 0.0f; 168 | length += planes[i][0] * planes[i][0]; 169 | length += planes[i][1] * planes[i][1]; 170 | length += planes[i][2] * planes[i][2]; 171 | length = sqrtf(length); 172 | if (length < EPSILON) 173 | { 174 | continue; 175 | } 176 | planes[i][0] /= length; 177 | planes[i][1] /= length; 178 | planes[i][2] /= length; 179 | planes[i][3] /= length; 180 | } 181 | } 182 | 183 | void camera_init( 184 | camera_t* camera, 185 | const camera_type_t type) 186 | { 187 | assert(camera); 188 | camera->type = type; 189 | camera->x = 0.0f; 190 | camera->y = 0.0f; 191 | camera->z = 0.0f; 192 | camera->pitch = rad(0.0f); 193 | camera->yaw = rad(0.0f); 194 | camera->width = 640.0f; 195 | camera->height = 480.0f; 196 | camera->fov = rad(90.0f); 197 | camera->near = 1.0f; 198 | camera->far = 300.0f; 199 | camera->ortho = 300.0f; 200 | camera->dirty = true; 201 | } 202 | 203 | void camera_update( 204 | camera_t* camera) 205 | { 206 | assert(camera); 207 | if (!camera->dirty) 208 | { 209 | return; 210 | } 211 | const float s = sinf(camera->yaw); 212 | const float c = cosf(camera->yaw); 213 | translate(camera->view, -camera->x, -camera->y, -camera->z); 214 | rotate(camera->proj, c, 0.0f, s, camera->pitch); 215 | multiply(camera->view, camera->proj, camera->view); 216 | rotate(camera->proj, 0.0f, 1.0f, 0.0f, -camera->yaw); 217 | multiply(camera->view, camera->proj, camera->view); 218 | if (camera->type == CAMERA_TYPE_ORTHO) 219 | { 220 | const float o = camera->ortho; 221 | ortho(camera->proj, -o, o, -o, o, -camera->far, camera->far); 222 | } 223 | else 224 | { 225 | const float a = camera->width / camera->height; 226 | perspective(camera->proj, a, camera->fov, camera->near, camera->far); 227 | } 228 | multiply(camera->matrix, camera->proj, camera->view); 229 | frustum(camera->planes, camera->matrix); 230 | camera->dirty = false; 231 | } 232 | 233 | void camera_set_viewport( 234 | camera_t* camera, 235 | const int width, 236 | const int height) 237 | { 238 | assert(camera); 239 | assert(width > 0.0f); 240 | assert(height > 0.0f); 241 | if (camera->width == width && camera->height == height) 242 | { 243 | return; 244 | } 245 | camera->width = width; 246 | camera->height = height; 247 | camera->dirty = true; 248 | } 249 | 250 | void camera_move( 251 | camera_t* camera, 252 | const float x, 253 | const float y, 254 | const float z) 255 | { 256 | assert(camera); 257 | if (!x && !y && !z) 258 | { 259 | return; 260 | } 261 | const float s = sinf(camera->yaw); 262 | const float c = cosf(camera->yaw); 263 | const float a = sinf(camera->pitch); 264 | const float b = cosf(camera->pitch); 265 | camera->x += b * (s * z) + c * x; 266 | camera->y += y + z * a; 267 | camera->z -= b * (c * z) - s * x; 268 | camera->dirty = true; 269 | } 270 | 271 | void camera_rotate( 272 | camera_t* camera, 273 | const float pitch, 274 | const float yaw) 275 | { 276 | assert(camera); 277 | if (!pitch && !yaw) 278 | { 279 | return; 280 | } 281 | const float a = camera->pitch + rad(pitch); 282 | const float b = camera->yaw + rad(yaw); 283 | camera_set_rotation(camera, a, b); 284 | } 285 | 286 | void camera_set_position( 287 | camera_t* camera, 288 | const float x, 289 | const float y, 290 | const float z) 291 | { 292 | assert(camera); 293 | if (camera->x == x && camera->y == y && camera->z == z) 294 | { 295 | return; 296 | } 297 | camera->x = x; 298 | camera->y = y; 299 | camera->z = z; 300 | camera->dirty = true; 301 | } 302 | 303 | void camera_get_position( 304 | const camera_t* camera, 305 | float* x, 306 | float* y, 307 | float* z) 308 | { 309 | assert(camera); 310 | assert(x); 311 | assert(y); 312 | assert(z); 313 | *x = camera->x; 314 | *y = camera->y; 315 | *z = camera->z; 316 | } 317 | 318 | void camera_set_rotation( 319 | camera_t* camera, 320 | const float pitch, 321 | const float yaw) 322 | { 323 | assert(camera); 324 | if (camera->pitch == pitch && camera->yaw == yaw) 325 | { 326 | return; 327 | } 328 | const float e = PI / 2.0f - EPSILON; 329 | camera->pitch = clamp(pitch, -e, e); 330 | camera->yaw = yaw; 331 | camera->dirty = true; 332 | } 333 | 334 | void camera_get_rotation( 335 | const camera_t* camera, 336 | float* pitch, 337 | float* yaw) 338 | { 339 | assert(camera); 340 | assert(pitch); 341 | assert(yaw); 342 | *pitch = camera->pitch; 343 | *yaw = camera->yaw; 344 | } 345 | 346 | void camera_get_vector( 347 | const camera_t* camera, 348 | float* x, 349 | float* y, 350 | float* z) 351 | { 352 | assert(camera); 353 | assert(x); 354 | assert(y); 355 | assert(z); 356 | const float c = cosf(camera->pitch); 357 | *x = cosf(camera->yaw - rad(90)) * c; 358 | *y = sinf(camera->pitch); 359 | *z = sinf(camera->yaw - rad(90)) * c; 360 | } 361 | 362 | bool camera_test( 363 | const camera_t* camera, 364 | const float x, 365 | const float y, 366 | const float z, 367 | const float a, 368 | const float b, 369 | const float c) 370 | { 371 | assert(camera); 372 | const float s = x + a; 373 | const float t = y + b; 374 | const float p = z + c; 375 | for (int i = 0; i < 6; ++i) 376 | { 377 | const float *plane = camera->planes[i]; 378 | const float q = plane[0] >= 0.0f ? s : x; 379 | const float u = plane[1] >= 0.0f ? t : y; 380 | const float v = plane[2] >= 0.0f ? p : z; 381 | if (plane[0] * q + plane[1] * u + plane[2] * v + plane[3] < 0.0f) 382 | { 383 | return false; 384 | } 385 | } 386 | return true; 387 | } -------------------------------------------------------------------------------- /src/world.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "block.h" 9 | #include "camera.h" 10 | #include "chunk.h" 11 | #include "database.h" 12 | #include "helpers.h" 13 | #include "noise.h" 14 | #include "voxel.h" 15 | #include "world.h" 16 | 17 | typedef enum 18 | { 19 | JOB_TYPE_QUIT, 20 | JOB_TYPE_LOAD, 21 | JOB_TYPE_MESH, 22 | } 23 | job_type_t; 24 | 25 | typedef struct 26 | { 27 | job_type_t type; 28 | int x; 29 | int z; 30 | } 31 | job_t; 32 | 33 | typedef struct 34 | { 35 | SDL_Thread* thrd; 36 | SDL_Mutex* mtx; 37 | SDL_Condition* cnd; 38 | const job_t* job; 39 | SDL_GPUTransferBuffer* tbos[CHUNK_TYPE_COUNT]; 40 | uint32_t sizes[CHUNK_TYPE_COUNT]; 41 | } 42 | worker_t; 43 | 44 | static terrain_t terrain; 45 | static SDL_GPUDevice* device; 46 | static SDL_GPUBuffer* ibo; 47 | static uint32_t ibo_size; 48 | static worker_t workers[WORLD_WORKERS]; 49 | static int sorted[WORLD_CHUNKS][2]; 50 | 51 | static int loop( 52 | void* args) 53 | { 54 | assert(args); 55 | worker_t* worker = args; 56 | while (true) 57 | { 58 | SDL_LockMutex(worker->mtx); 59 | while (!worker->job) 60 | { 61 | SDL_WaitCondition(worker->cnd, worker->mtx); 62 | } 63 | if (worker->job->type == JOB_TYPE_QUIT) 64 | { 65 | worker->job = NULL; 66 | SDL_SignalCondition(worker->cnd); 67 | SDL_UnlockMutex(worker->mtx); 68 | return 0; 69 | } 70 | const int x = terrain.x + worker->job->x; 71 | const int z = terrain.z + worker->job->z; 72 | chunk_t* chunk = terrain_get2(&terrain, x, z); 73 | switch (worker->job->type) 74 | { 75 | case JOB_TYPE_LOAD: 76 | assert(chunk->load); 77 | assert(chunk->mesh); 78 | noise_generate(chunk, x, z); 79 | database_get_blocks(chunk, x, z); 80 | chunk->load = false; 81 | break; 82 | case JOB_TYPE_MESH: 83 | assert(!chunk->load); 84 | assert(chunk->mesh); 85 | chunk_t* neighbors[DIRECTION_2]; 86 | terrain_neighbors2(&terrain, x, z, neighbors); 87 | chunk->mesh = !voxel_vbo( 88 | chunk, 89 | (const chunk_t**) neighbors, 90 | device, 91 | worker->tbos, 92 | worker->sizes); 93 | break; 94 | default: 95 | assert(0); 96 | } 97 | worker->job = NULL; 98 | SDL_SignalCondition(worker->cnd); 99 | SDL_UnlockMutex(worker->mtx); 100 | } 101 | return 0; 102 | } 103 | 104 | static void dispatch( 105 | worker_t* worker, 106 | const job_t* job) 107 | { 108 | assert(worker); 109 | assert(job); 110 | SDL_LockMutex(worker->mtx); 111 | assert(!worker->job); 112 | worker->job = job; 113 | SDL_SignalCondition(worker->cnd); 114 | SDL_UnlockMutex(worker->mtx); 115 | } 116 | 117 | bool world_init( 118 | SDL_GPUDevice* handle) 119 | { 120 | assert(handle); 121 | device = handle; 122 | terrain_init(&terrain); 123 | for (int i = 0; i < WORLD_WORKERS; i++) 124 | { 125 | worker_t* worker = &workers[i]; 126 | worker->mtx = SDL_CreateMutex(); 127 | if (!worker->mtx) 128 | { 129 | SDL_Log("Failed to create mutex: %s", SDL_GetError()); 130 | return false; 131 | } 132 | worker->cnd = SDL_CreateCondition(); 133 | if (!worker->cnd) 134 | { 135 | SDL_Log("Failed to create condition variable: %s", SDL_GetError()); 136 | return false; 137 | } 138 | worker->thrd = SDL_CreateThread(loop, "worker", worker); 139 | if (!worker->thrd) 140 | { 141 | SDL_Log("Failed to create thread: %s", SDL_GetError()); 142 | return false; 143 | } 144 | } 145 | int i = 0; 146 | for (int x = 0; x < WORLD_X; x++) 147 | for (int z = 0; z < WORLD_Z; z++) 148 | { 149 | sorted[i][0] = x; 150 | sorted[i][1] = z; 151 | i++; 152 | } 153 | const int w = WORLD_X / 2; 154 | const int h = WORLD_Z / 2; 155 | sort_2d(w, h, sorted, WORLD_CHUNKS); 156 | return true; 157 | } 158 | 159 | void world_free() 160 | { 161 | for (int i = 0; i < WORLD_WORKERS; i++) 162 | { 163 | job_t job; 164 | job.type = JOB_TYPE_QUIT; 165 | dispatch(&workers[i], &job); 166 | } 167 | for (int i = 0; i < WORLD_WORKERS; i++) 168 | { 169 | worker_t* worker = &workers[i]; 170 | SDL_WaitThread(worker->thrd, NULL); 171 | SDL_DestroyMutex(worker->mtx); 172 | SDL_DestroyCondition(worker->cnd); 173 | worker->thrd = NULL; 174 | worker->mtx = NULL; 175 | worker->cnd = NULL; 176 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 177 | { 178 | if (worker->tbos[type]) 179 | { 180 | SDL_ReleaseGPUTransferBuffer(device, worker->tbos[type]); 181 | worker->tbos[type] = NULL; 182 | } 183 | } 184 | } 185 | for (int x = 0; x < WORLD_X; x++) 186 | for (int z = 0; z < WORLD_Z; z++) 187 | { 188 | chunk_t* chunk = terrain_get(&terrain, x, z); 189 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 190 | { 191 | if (chunk->vbos[type]) 192 | { 193 | SDL_ReleaseGPUBuffer(device, chunk->vbos[type]); 194 | chunk->vbos[type] = NULL; 195 | } 196 | } 197 | } 198 | terrain_free(&terrain); 199 | if (ibo) 200 | { 201 | SDL_ReleaseGPUBuffer(device, ibo); 202 | ibo = NULL; 203 | } 204 | device = NULL; 205 | } 206 | 207 | static void move( 208 | const int x, 209 | const int y, 210 | const int z) 211 | { 212 | const int a = x / CHUNK_X - WORLD_X / 2; 213 | const int c = z / CHUNK_Z - WORLD_Z / 2; 214 | int size; 215 | int* data = terrain_move(&terrain, a, c, &size); 216 | if (!data) 217 | { 218 | return; 219 | } 220 | for (int i = 0; i < size; i++) 221 | { 222 | const int j = data[i * 2 + 0]; 223 | const int k = data[i * 2 + 1]; 224 | chunk_t* chunk = terrain_get(&terrain, j, k); 225 | memset(chunk->blocks, 0, sizeof(chunk->blocks)); 226 | chunk->load = true; 227 | chunk->mesh = true; 228 | } 229 | free(data); 230 | } 231 | 232 | void world_update( 233 | const int x, 234 | const int y, 235 | const int z) 236 | { 237 | move(x, y, z); 238 | int n = 0; 239 | job_t jobs[WORLD_WORKERS]; 240 | for (int i = 0; i < WORLD_CHUNKS && n < WORLD_WORKERS; i++) 241 | { 242 | const int j = sorted[i][0]; 243 | const int k = sorted[i][1]; 244 | chunk_t* chunk = terrain_get(&terrain, j, k); 245 | if (chunk->load) 246 | { 247 | job_t* job = &jobs[n++]; 248 | job->type = JOB_TYPE_LOAD; 249 | job->x = j; 250 | job->z = k; 251 | continue; 252 | } 253 | if (!chunk->mesh || terrain_border(&terrain, j, k)) 254 | { 255 | continue; 256 | } 257 | bool status = true; 258 | chunk_t* neighbors[DIRECTION_2]; 259 | terrain_neighbors(&terrain, j, k, neighbors); 260 | for (direction_t direction = 0; direction < DIRECTION_2; direction++) 261 | { 262 | const chunk_t* neighbor = neighbors[direction]; 263 | if (!neighbor || neighbor->load) 264 | { 265 | status = false; 266 | break; 267 | } 268 | } 269 | if (status) 270 | { 271 | job_t* job = &jobs[n++]; 272 | job->type = JOB_TYPE_MESH; 273 | job->x = j; 274 | job->z = k; 275 | continue; 276 | } 277 | } 278 | uint32_t size = 0; 279 | for (int i = 0; i < n; i++) 280 | { 281 | dispatch(&workers[i], &jobs[i]); 282 | } 283 | for (int i = 0; i < n; i++) 284 | { 285 | worker_t* worker = &workers[i]; 286 | SDL_LockMutex(worker->mtx); 287 | while (worker->job) 288 | { 289 | SDL_WaitCondition(worker->cnd, worker->mtx); 290 | } 291 | SDL_UnlockMutex(worker->mtx); 292 | } 293 | for (int i = 0; i < n; i++) 294 | { 295 | const job_t* job = &jobs[i]; 296 | if (job->type != JOB_TYPE_MESH) 297 | { 298 | continue; 299 | } 300 | chunk_t* chunk = terrain_get(&terrain, job->x, job->z); 301 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 302 | { 303 | size = max(size, chunk->sizes[type]); 304 | } 305 | } 306 | if (size > ibo_size) 307 | { 308 | if (ibo) 309 | { 310 | SDL_ReleaseGPUBuffer(device, ibo); 311 | ibo = NULL; 312 | ibo_size = 0; 313 | } 314 | if (voxel_ibo(device, &ibo, size)) 315 | { 316 | ibo_size = size; 317 | } 318 | } 319 | } 320 | 321 | void world_render( 322 | const camera_t* camera, 323 | SDL_GPUCommandBuffer* commands, 324 | SDL_GPURenderPass* pass, 325 | const chunk_type_t type) 326 | { 327 | assert(commands); 328 | assert(pass); 329 | if (!ibo) 330 | { 331 | return; 332 | } 333 | SDL_GPUBufferBinding ibb = {0}; 334 | ibb.buffer = ibo; 335 | SDL_BindGPUIndexBuffer(pass, &ibb, SDL_GPU_INDEXELEMENTSIZE_32BIT); 336 | for (int i = 0; i < WORLD_CHUNKS; i++) 337 | { 338 | int x; 339 | int z; 340 | if (type == CHUNK_TYPE_TRANSPARENT) 341 | { 342 | x = sorted[WORLD_CHUNKS - i - 1][0] + terrain.x; 343 | z = sorted[WORLD_CHUNKS - i - 1][1] + terrain.z; 344 | } 345 | else 346 | { 347 | x = sorted[i][0] + terrain.x; 348 | z = sorted[i][1] + terrain.z; 349 | } 350 | const chunk_t* chunk = terrain_get2(&terrain, x, z); 351 | if (terrain_border2(&terrain, x, z) || chunk->mesh || !chunk->sizes[type]) 352 | { 353 | continue; 354 | } 355 | assert(chunk->sizes[type] <= ibo_size); 356 | x *= CHUNK_X; 357 | z *= CHUNK_Z; 358 | if (camera && !camera_test(camera, x, 0, z, CHUNK_X, CHUNK_Y, CHUNK_Z)) 359 | { 360 | continue; 361 | } 362 | int32_t position[3] = { x, 0, z }; 363 | SDL_GPUBufferBinding vbb = {0}; 364 | vbb.buffer = chunk->vbos[type]; 365 | SDL_PushGPUVertexUniformData(commands, 0, position, sizeof(position)); 366 | SDL_BindGPUVertexBuffers(pass, 0, &vbb, 1); 367 | SDL_DrawGPUIndexedPrimitives(pass, chunk->sizes[type] * 6, 1, 0, 0, 0); 368 | } 369 | } 370 | 371 | void world_set_block( 372 | int x, 373 | int y, 374 | int z, 375 | const block_t block) 376 | { 377 | const int a = floor((float) x / CHUNK_X); 378 | const int c = floor((float) z / CHUNK_Z); 379 | if (!terrain_in2(&terrain, a, c) || y < 0 || y >= CHUNK_Y) 380 | { 381 | return; 382 | } 383 | chunk_wrap(&x, &y, &z); 384 | chunk_t* chunk = terrain_get2(&terrain, a, c); 385 | database_set_block(a, c, x, y, z, block); 386 | chunk_set_block(chunk, x, y, z, block); 387 | chunk->mesh = true; 388 | chunk_t* neighbors[DIRECTION_2]; 389 | terrain_neighbors2(&terrain, a, c, neighbors); 390 | if (x == 0 && neighbors[DIRECTION_W]) 391 | { 392 | neighbors[DIRECTION_W]->mesh = true; 393 | } 394 | else if (x == CHUNK_X - 1 && neighbors[DIRECTION_E]) 395 | { 396 | neighbors[DIRECTION_E]->mesh = true; 397 | } 398 | if (z == 0 && neighbors[DIRECTION_S]) 399 | { 400 | neighbors[DIRECTION_S]->mesh = true; 401 | } 402 | else if (z == CHUNK_Z - 1 && neighbors[DIRECTION_N]) 403 | { 404 | neighbors[DIRECTION_N]->mesh = true; 405 | } 406 | } 407 | 408 | block_t world_get_block( 409 | int x, 410 | int y, 411 | int z) 412 | { 413 | const int a = floor((float) x / CHUNK_X); 414 | const int c = floor((float) z / CHUNK_Z); 415 | if (!terrain_in2(&terrain, a, c) || y < 0 || y >= CHUNK_Y) 416 | { 417 | return BLOCK_EMPTY; 418 | } 419 | chunk_wrap(&x, &y, &z); 420 | const chunk_t* chunk = terrain_get2(&terrain, a, c); 421 | if (!chunk->load) 422 | { 423 | return chunk->blocks[x][y][z]; 424 | } 425 | else 426 | { 427 | return BLOCK_EMPTY; 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /src/voxel.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "block.h" 5 | #include "chunk.h" 6 | #include "config.h" 7 | #include "helpers.h" 8 | #include "voxel.h" 9 | 10 | static uint32_t pack( 11 | const block_t block, 12 | const int x, 13 | const int y, 14 | const int z, 15 | const int u, 16 | const int v, 17 | const direction_t direction) 18 | { 19 | static_assert(VOXEL_X_OFFSET + VOXEL_X_BITS <= 32, ""); 20 | static_assert(VOXEL_Y_OFFSET + VOXEL_Y_BITS <= 32, ""); 21 | static_assert(VOXEL_Z_OFFSET + VOXEL_Z_BITS <= 32, ""); 22 | static_assert(VOXEL_U_OFFSET + VOXEL_U_BITS <= 32, ""); 23 | static_assert(VOXEL_V_OFFSET + VOXEL_V_BITS <= 32, ""); 24 | static_assert(VOXEL_FACE_OFFSET + VOXEL_FACE_BITS <= 32, ""); 25 | static_assert(VOXEL_DIRECTION_OFFSET + VOXEL_DIRECTION_BITS <= 32, ""); 26 | static_assert(VOXEL_SHADOW_OFFSET + VOXEL_SHADOW_BITS <= 32, ""); 27 | static_assert(VOXEL_SHADOWED_OFFSET + VOXEL_SHADOWED_BITS <= 32, ""); 28 | static_assert(VOXEL_OCCLUDED_OFFSET + VOXEL_OCCLUDED_BITS <= 32, ""); 29 | const int face = blocks[block][direction]; 30 | assert(x <= VOXEL_X_MASK); 31 | assert(y <= VOXEL_Y_MASK); 32 | assert(z <= VOXEL_Z_MASK); 33 | assert(u <= VOXEL_U_MASK); 34 | assert(v <= VOXEL_V_MASK); 35 | assert(face <= VOXEL_FACE_MASK); 36 | assert(direction <= VOXEL_DIRECTION_MASK); 37 | uint32_t voxel = 0; 38 | voxel |= x << VOXEL_X_OFFSET; 39 | voxel |= y << VOXEL_Y_OFFSET; 40 | voxel |= z << VOXEL_Z_OFFSET; 41 | voxel |= u << VOXEL_U_OFFSET; 42 | voxel |= v << VOXEL_V_OFFSET; 43 | voxel |= face << VOXEL_FACE_OFFSET; 44 | voxel |= direction << VOXEL_DIRECTION_OFFSET; 45 | voxel |= block_shadow(block) << VOXEL_SHADOW_OFFSET; 46 | voxel |= block_shadowed(block) << VOXEL_SHADOWED_OFFSET; 47 | voxel |= block_occluded(block) << VOXEL_OCCLUDED_OFFSET; 48 | return voxel; 49 | } 50 | 51 | static uint32_t pack_non_sprite( 52 | const block_t block, 53 | const int x, 54 | const int y, 55 | const int z, 56 | const direction_t direction, 57 | const int i) 58 | { 59 | assert(block > BLOCK_EMPTY); 60 | assert(block < BLOCK_COUNT); 61 | assert(direction < DIRECTION_3); 62 | assert(i < 4); 63 | static const int positions[][4][3] = 64 | { 65 | {{0, 0, 1}, {0, 1, 1}, {1, 0, 1}, {1, 1, 1}}, 66 | {{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0}}, 67 | {{1, 0, 0}, {1, 0, 1}, {1, 1, 0}, {1, 1, 1}}, 68 | {{0, 0, 0}, {0, 1, 0}, {0, 0, 1}, {0, 1, 1}}, 69 | {{0, 1, 0}, {1, 1, 0}, {0, 1, 1}, {1, 1, 1}}, 70 | {{0, 0, 0}, {0, 0, 1}, {1, 0, 0}, {1, 0, 1}}, 71 | }; 72 | static const int uvs[][4][2] = 73 | { 74 | {{1, 1}, {1, 0}, {0, 1}, {0, 0}}, 75 | {{1, 1}, {0, 1}, {1, 0}, {0, 0}}, 76 | {{1, 1}, {0, 1}, {1, 0}, {0, 0}}, 77 | {{1, 1}, {1, 0}, {0, 1}, {0, 0}}, 78 | {{0, 0}, {1, 0}, {0, 1}, {1, 1}}, 79 | {{0, 0}, {0, 1}, {1, 0}, {1, 1}}, 80 | }; 81 | const int a = positions[direction][i][0] + x; 82 | const int b = positions[direction][i][1] + y; 83 | const int c = positions[direction][i][2] + z; 84 | const int d = uvs[direction][i][0]; 85 | const int e = uvs[direction][i][1]; 86 | return pack(block, a, b, c, d, e, direction); 87 | } 88 | 89 | static uint32_t pack_sprite( 90 | const block_t block, 91 | const int x, 92 | const int y, 93 | const int z, 94 | const int direction, 95 | const int i) 96 | { 97 | assert(block > BLOCK_EMPTY); 98 | assert(block < BLOCK_COUNT); 99 | assert(direction < 4); 100 | assert(i < 4); 101 | static const int positions[][4][3] = 102 | { 103 | {{0, 0, 0}, {0, 1, 0}, {1, 0, 1}, {1, 1, 1}}, 104 | {{0, 0, 0}, {1, 0, 1}, {0, 1, 0}, {1, 1, 1}}, 105 | {{0, 0, 1}, {1, 0, 0}, {0, 1, 1}, {1, 1, 0}}, 106 | {{0, 0, 1}, {0, 1, 1}, {1, 0, 0}, {1, 1, 0}}, 107 | }; 108 | static const int uvs[][4][2] = 109 | { 110 | {{1, 1}, {1, 0}, {0, 1}, {0, 0}}, 111 | {{1, 1}, {0, 1}, {1, 0}, {0, 0}}, 112 | {{1, 1}, {0, 1}, {1, 0}, {0, 0}}, 113 | {{1, 1}, {1, 0}, {0, 1}, {0, 0}}, 114 | }; 115 | const int a = positions[direction][i][0] + x; 116 | const int b = positions[direction][i][1] + y; 117 | const int c = positions[direction][i][2] + z; 118 | const int d = uvs[direction][i][0]; 119 | const int e = uvs[direction][i][1]; 120 | return pack(block, a, b, c, d, e, DIRECTION_U); 121 | } 122 | 123 | static void fill( 124 | const chunk_t* chunk, 125 | const chunk_t* neighbors[DIRECTION_2], 126 | uint32_t* datas[CHUNK_TYPE_COUNT], 127 | uint32_t sizes[CHUNK_TYPE_COUNT], 128 | const uint32_t capacities[CHUNK_TYPE_COUNT]) 129 | { 130 | assert(chunk); 131 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 132 | { 133 | sizes[type] = 0; 134 | } 135 | for (int x = 0; x < CHUNK_X; x++) 136 | for (int y = 0; y < CHUNK_Y; y++) 137 | for (int z = 0; z < CHUNK_Z; z++) 138 | { 139 | const block_t a = chunk->blocks[x][y][z]; 140 | if (a == BLOCK_EMPTY) 141 | { 142 | continue; 143 | } 144 | chunk_type_t type; 145 | if (block_opaque(a)) 146 | { 147 | type = CHUNK_TYPE_OPAQUE; 148 | } 149 | else 150 | { 151 | type = CHUNK_TYPE_TRANSPARENT; 152 | } 153 | if (block_sprite(a)) 154 | { 155 | sizes[type] += 4; 156 | if (sizes[type] > capacities[type]) 157 | { 158 | continue; 159 | } 160 | for (int direction = 0; direction < 4; direction++) 161 | { 162 | for (int i = 0; i < 4; i++) 163 | { 164 | const int j = sizes[type] * 4 - 4 * (direction + 1) + i; 165 | datas[type][j] = pack_sprite(a, x, y, z, direction, i); 166 | } 167 | } 168 | continue; 169 | } 170 | for (direction_t d = 0; d < DIRECTION_3; d++) 171 | { 172 | if (y == 0 && d != DIRECTION_U) 173 | { 174 | continue; 175 | } 176 | block_t b; 177 | int s = x + directions[d][0]; 178 | int t = y + directions[d][1]; 179 | int p = z + directions[d][2]; 180 | if (chunk_in(s, t, p)) 181 | { 182 | b = chunk->blocks[s][t][p]; 183 | } 184 | else if (d < DIRECTION_2 && neighbors[d]) 185 | { 186 | chunk_wrap(&s, &t, &p); 187 | b = neighbors[d]->blocks[s][t][p]; 188 | } 189 | else 190 | { 191 | b = BLOCK_EMPTY; 192 | } 193 | if ((b != BLOCK_EMPTY && !block_sprite(b) && 194 | !(block_opaque(a) && !block_opaque(b))) || 195 | ++sizes[type] > capacities[type]) 196 | { 197 | continue; 198 | } 199 | for (int i = 0; i < 4; i++) 200 | { 201 | const int j = sizes[type] * 4 - 4 + i; 202 | datas[type][j] = pack_non_sprite(a, x, y, z, d, i); 203 | } 204 | } 205 | } 206 | } 207 | 208 | bool voxel_vbo( 209 | chunk_t* chunk, 210 | const chunk_t* neighbors[DIRECTION_2], 211 | SDL_GPUDevice* device, 212 | SDL_GPUTransferBuffer* tbos[CHUNK_TYPE_COUNT], 213 | uint32_t capacities[CHUNK_TYPE_COUNT]) 214 | { 215 | assert(chunk); 216 | assert(device); 217 | uint32_t* datas[CHUNK_TYPE_COUNT] = {0}; 218 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 219 | { 220 | if (!tbos[type]) 221 | { 222 | continue; 223 | } 224 | datas[type] = SDL_MapGPUTransferBuffer(device, tbos[type], true); 225 | if (!datas[type]) 226 | { 227 | SDL_Log("Failed to map tbo buffer: %s", SDL_GetError()); 228 | return false; 229 | } 230 | } 231 | fill( 232 | chunk, 233 | neighbors, 234 | datas, 235 | chunk->sizes, 236 | capacities); 237 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 238 | { 239 | if (datas[type]) 240 | { 241 | SDL_UnmapGPUTransferBuffer(device, tbos[type]); 242 | datas[type] = NULL; 243 | } 244 | } 245 | bool status = false; 246 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 247 | { 248 | if (chunk->sizes[type]) 249 | { 250 | status = true; 251 | break; 252 | } 253 | } 254 | if (!status) 255 | { 256 | return true; 257 | } 258 | status = false; 259 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 260 | { 261 | if (chunk->sizes[type] > capacities[type]) 262 | { 263 | status = true; 264 | break; 265 | } 266 | } 267 | if (status) 268 | { 269 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 270 | { 271 | if (chunk->sizes[type] <= capacities[type]) 272 | { 273 | continue; 274 | } 275 | if (tbos[type]) 276 | { 277 | SDL_ReleaseGPUTransferBuffer(device, tbos[type]); 278 | tbos[type] = NULL; 279 | capacities[type] = 0; 280 | } 281 | SDL_GPUTransferBufferCreateInfo tbci = {0}; 282 | tbci.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; 283 | tbci.size = chunk->sizes[type] * 16; 284 | tbos[type] = SDL_CreateGPUTransferBuffer(device, &tbci); 285 | if (!tbos[type]) 286 | { 287 | SDL_Log("Failed to create tbo buffer: %s", SDL_GetError()); 288 | return false; 289 | } 290 | capacities[type] = chunk->sizes[type]; 291 | } 292 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 293 | { 294 | if (!chunk->sizes[type]) 295 | { 296 | continue; 297 | } 298 | datas[type] = SDL_MapGPUTransferBuffer(device, tbos[type], true); 299 | if (!datas[type]) 300 | { 301 | SDL_Log("Failed to map tbo buffer: %s", SDL_GetError()); 302 | return false; 303 | } 304 | } 305 | fill( 306 | chunk, 307 | neighbors, 308 | datas, 309 | chunk->sizes, 310 | capacities); 311 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 312 | { 313 | if (datas[type]) 314 | { 315 | SDL_UnmapGPUTransferBuffer(device, tbos[type]); 316 | datas[type] = NULL; 317 | } 318 | } 319 | } 320 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 321 | { 322 | if (chunk->sizes[type] <= chunk->capacities[type]) 323 | { 324 | continue; 325 | } 326 | if (chunk->vbos[type]) 327 | { 328 | SDL_ReleaseGPUBuffer(device, chunk->vbos[type]); 329 | chunk->vbos[type] = NULL; 330 | chunk->capacities[type] = 0; 331 | } 332 | SDL_GPUBufferCreateInfo bci = {0}; 333 | bci.usage = SDL_GPU_BUFFERUSAGE_VERTEX; 334 | bci.size = chunk->sizes[type] * 16; 335 | chunk->vbos[type]= SDL_CreateGPUBuffer(device, &bci); 336 | if (!chunk->vbos[type]) 337 | { 338 | SDL_Log("Failed to create vertex buffer: %s", SDL_GetError()); 339 | return false; 340 | } 341 | chunk->capacities[type] = chunk->sizes[type]; 342 | } 343 | SDL_GPUCommandBuffer* commands = SDL_AcquireGPUCommandBuffer(device); 344 | if (!commands) 345 | { 346 | SDL_Log("Failed to acquire command buffer: %s", SDL_GetError()); 347 | return false; 348 | } 349 | SDL_GPUCopyPass* pass = SDL_BeginGPUCopyPass(commands); 350 | if (!pass) 351 | { 352 | SDL_Log("Failed to begin copy pass: %s", SDL_GetError()); 353 | return false; 354 | } 355 | SDL_GPUTransferBufferLocation location = {0}; 356 | SDL_GPUBufferRegion region = {0}; 357 | for (chunk_type_t type = 0; type < CHUNK_TYPE_COUNT; type++) 358 | { 359 | if (!chunk->sizes[type]) 360 | { 361 | continue; 362 | } 363 | location.transfer_buffer = tbos[type]; 364 | region.size = chunk->sizes[type] * 16; 365 | region.buffer = chunk->vbos[type]; 366 | SDL_UploadToGPUBuffer(pass, &location, ®ion, 1); 367 | } 368 | SDL_EndGPUCopyPass(pass); 369 | SDL_SubmitGPUCommandBuffer(commands); 370 | return true; 371 | } 372 | 373 | bool voxel_ibo( 374 | SDL_GPUDevice* device, 375 | SDL_GPUBuffer** ibo, 376 | const uint32_t size) 377 | { 378 | assert(device); 379 | assert(ibo); 380 | assert(size); 381 | SDL_GPUTransferBufferCreateInfo tbci = {0}; 382 | tbci.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; 383 | tbci.size = size * 24; 384 | SDL_GPUTransferBuffer* tbo = SDL_CreateGPUTransferBuffer(device, &tbci); 385 | if (!tbo) 386 | { 387 | SDL_Log("Failed to create tbo buffer: %s", SDL_GetError()); 388 | return false; 389 | } 390 | SDL_GPUBufferCreateInfo bci = {0}; 391 | bci.usage = SDL_GPU_BUFFERUSAGE_INDEX; 392 | bci.size = size * 24; 393 | *ibo = SDL_CreateGPUBuffer(device, &bci); 394 | if (!(*ibo)) 395 | { 396 | SDL_Log("Failed to create index buffer: %s", SDL_GetError()); 397 | return false; 398 | } 399 | uint32_t* data = SDL_MapGPUTransferBuffer(device, tbo, false); 400 | if (!data) 401 | { 402 | SDL_Log("Failed to map tbo buffer: %s", SDL_GetError()); 403 | return false; 404 | } 405 | for (uint32_t i = 0; i < size; i++) 406 | { 407 | data[i * 6 + 0] = i * 4 + 0; 408 | data[i * 6 + 1] = i * 4 + 1; 409 | data[i * 6 + 2] = i * 4 + 2; 410 | data[i * 6 + 3] = i * 4 + 3; 411 | data[i * 6 + 4] = i * 4 + 2; 412 | data[i * 6 + 5] = i * 4 + 1; 413 | } 414 | SDL_UnmapGPUTransferBuffer(device, tbo); 415 | SDL_GPUCommandBuffer* commands = SDL_AcquireGPUCommandBuffer(device); 416 | if (!commands) 417 | { 418 | SDL_Log("Failed to acquire command buffer: %s", SDL_GetError()); 419 | return false; 420 | } 421 | SDL_GPUCopyPass* pass = SDL_BeginGPUCopyPass(commands); 422 | if (!pass) 423 | { 424 | SDL_Log("Failed to begin copy pass: %s", SDL_GetError()); 425 | return false; 426 | } 427 | SDL_GPUTransferBufferLocation location = {0}; 428 | location.transfer_buffer = tbo; 429 | SDL_GPUBufferRegion region = {0}; 430 | region.size = size * 24; 431 | region.buffer = *ibo; 432 | SDL_UploadToGPUBuffer(pass, &location, ®ion, 1); 433 | SDL_EndGPUCopyPass(pass); 434 | SDL_SubmitGPUCommandBuffer(commands); 435 | SDL_ReleaseGPUTransferBuffer(device, tbo); 436 | return true; 437 | } -------------------------------------------------------------------------------- /src/pipeline.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "helpers.h" 6 | #include "pipeline.h" 7 | 8 | static SDL_GPUDevice* device; 9 | static SDL_GPUGraphicsPipeline* pipelines[PIPELINE_COUNT]; 10 | 11 | static SDL_GPUShader* load( 12 | const char* file, 13 | const int uniforms, 14 | const int samplers) 15 | { 16 | assert(file); 17 | SDL_GPUShaderCreateInfo info = {0}; 18 | void* code = SDL_LoadFile(file, &info.code_size); 19 | if (!code) 20 | { 21 | SDL_Log("Failed to load %s shader: %s", file, SDL_GetError()); 22 | return NULL; 23 | } 24 | info.code = code; 25 | if (strstr(file, ".vert")) 26 | { 27 | info.stage = SDL_GPU_SHADERSTAGE_VERTEX; 28 | } 29 | else 30 | { 31 | info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT; 32 | } 33 | info.format = SDL_GPU_SHADERFORMAT_SPIRV; 34 | info.entrypoint = "main"; 35 | info.num_uniform_buffers = uniforms; 36 | info.num_samplers = samplers; 37 | SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info); 38 | SDL_free(code); 39 | if (!shader) 40 | { 41 | SDL_Log("Failed to create %s shader: %s", file, SDL_GetError()); 42 | return NULL; 43 | } 44 | return shader; 45 | } 46 | 47 | static SDL_GPUGraphicsPipeline* load_sky( 48 | const SDL_GPUTextureFormat format) 49 | { 50 | SDL_GPUGraphicsPipelineCreateInfo info = 51 | { 52 | .vertex_shader = load("sky.vert", 2, 0), 53 | .fragment_shader = load("sky.frag", 0, 0), 54 | .target_info = 55 | { 56 | .num_color_targets = 1, 57 | .color_target_descriptions = (SDL_GPUColorTargetDescription[]) 58 | {{ 59 | .format = SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, 60 | }}, 61 | }, 62 | .vertex_input_state = 63 | { 64 | .num_vertex_attributes = 1, 65 | .vertex_attributes = (SDL_GPUVertexAttribute[]) 66 | {{ 67 | .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 68 | }}, 69 | .num_vertex_buffers = 1, 70 | .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]) 71 | {{ 72 | .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, 73 | .pitch = 12, 74 | }}, 75 | }, 76 | }; 77 | SDL_GPUGraphicsPipeline* pipeline = NULL; 78 | if (info.vertex_shader && info.fragment_shader) 79 | { 80 | pipeline = SDL_CreateGPUGraphicsPipeline(device, &info); 81 | } 82 | if (!pipeline) 83 | { 84 | SDL_Log("Failed to create sky pipeline: %s", SDL_GetError()); 85 | } 86 | SDL_ReleaseGPUShader(device, info.vertex_shader); 87 | SDL_ReleaseGPUShader(device, info.fragment_shader); 88 | return pipeline; 89 | } 90 | 91 | static SDL_GPUGraphicsPipeline* load_shadow( 92 | const SDL_GPUTextureFormat format) 93 | { 94 | SDL_GPUGraphicsPipelineCreateInfo info = 95 | { 96 | .vertex_shader = load("shadow.vert", 2, 0), 97 | .fragment_shader = load("shadow.frag", 0, 0), 98 | .target_info = 99 | { 100 | .has_depth_stencil_target = true, 101 | .depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT, 102 | }, 103 | .vertex_input_state = 104 | { 105 | .num_vertex_attributes = 1, 106 | .vertex_attributes = (SDL_GPUVertexAttribute[]) 107 | {{ 108 | .format = SDL_GPU_VERTEXELEMENTFORMAT_UINT, 109 | }}, 110 | .num_vertex_buffers = 1, 111 | .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]) 112 | {{ 113 | .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, 114 | .pitch = 4, 115 | }}, 116 | }, 117 | .depth_stencil_state = 118 | { 119 | .enable_depth_test = true, 120 | .enable_depth_write = true, 121 | .compare_op = SDL_GPU_COMPAREOP_LESS, 122 | }, 123 | }; 124 | SDL_GPUGraphicsPipeline* pipeline = NULL; 125 | if (info.vertex_shader && info.fragment_shader) 126 | { 127 | pipeline = SDL_CreateGPUGraphicsPipeline(device, &info); 128 | } 129 | if (!pipeline) 130 | { 131 | SDL_Log("Failed to create shadow pipeline: %s", SDL_GetError()); 132 | } 133 | SDL_ReleaseGPUShader(device, info.vertex_shader); 134 | SDL_ReleaseGPUShader(device, info.fragment_shader); 135 | return pipeline; 136 | } 137 | 138 | static SDL_GPUGraphicsPipeline* load_opaque( 139 | const SDL_GPUTextureFormat format) 140 | { 141 | SDL_GPUGraphicsPipelineCreateInfo info = 142 | { 143 | .vertex_shader = load("opaque.vert", 3, 0), 144 | .fragment_shader = load("opaque.frag", 0, 1), 145 | .target_info = 146 | { 147 | .num_color_targets = 3, 148 | .color_target_descriptions = (SDL_GPUColorTargetDescription[]) 149 | {{ 150 | .format = SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, 151 | }, 152 | { 153 | .format = SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, 154 | }, 155 | { 156 | .format = SDL_GPU_TEXTUREFORMAT_R32_UINT, 157 | }}, 158 | .has_depth_stencil_target = true, 159 | .depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT, 160 | }, 161 | .vertex_input_state = 162 | { 163 | .num_vertex_attributes = 1, 164 | .vertex_attributes = (SDL_GPUVertexAttribute[]) 165 | {{ 166 | .format = SDL_GPU_VERTEXELEMENTFORMAT_UINT, 167 | }}, 168 | .num_vertex_buffers = 1, 169 | .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]) 170 | {{ 171 | .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, 172 | .pitch = 4, 173 | }}, 174 | }, 175 | .depth_stencil_state = 176 | { 177 | .enable_depth_test = true, 178 | .enable_depth_write = true, 179 | .compare_op = SDL_GPU_COMPAREOP_LESS, 180 | }, 181 | .rasterizer_state = 182 | { 183 | .cull_mode = SDL_GPU_CULLMODE_BACK, 184 | .front_face = SDL_GPU_FRONTFACE_CLOCKWISE, 185 | }, 186 | }; 187 | SDL_GPUGraphicsPipeline* pipeline = NULL; 188 | if (info.vertex_shader && info.fragment_shader) 189 | { 190 | pipeline = SDL_CreateGPUGraphicsPipeline(device, &info); 191 | } 192 | if (!pipeline) 193 | { 194 | SDL_Log("Failed to create opaque pipeline: %s", SDL_GetError()); 195 | } 196 | SDL_ReleaseGPUShader(device, info.vertex_shader); 197 | SDL_ReleaseGPUShader(device, info.fragment_shader); 198 | return pipeline; 199 | } 200 | 201 | static SDL_GPUGraphicsPipeline* load_ssao( 202 | const SDL_GPUTextureFormat format) 203 | { 204 | SDL_GPUGraphicsPipelineCreateInfo info = 205 | { 206 | .vertex_shader = load("fullscreen.vert", 0, 0), 207 | .fragment_shader = load("ssao.frag", 0, 4), 208 | .target_info = 209 | { 210 | .num_color_targets = 1, 211 | .color_target_descriptions = (SDL_GPUColorTargetDescription[]) 212 | {{ 213 | .format = SDL_GPU_TEXTUREFORMAT_R32_FLOAT 214 | }} 215 | }, 216 | }; 217 | SDL_GPUGraphicsPipeline* pipeline = NULL; 218 | if (info.vertex_shader && info.fragment_shader) 219 | { 220 | pipeline = SDL_CreateGPUGraphicsPipeline(device, &info); 221 | } 222 | if (!pipeline) 223 | { 224 | SDL_Log("Failed to create ssao pipeline: %s", SDL_GetError()); 225 | } 226 | SDL_ReleaseGPUShader(device, info.vertex_shader); 227 | SDL_ReleaseGPUShader(device, info.fragment_shader); 228 | return pipeline; 229 | } 230 | 231 | static SDL_GPUGraphicsPipeline* load_composite( 232 | const SDL_GPUTextureFormat format) 233 | { 234 | SDL_GPUGraphicsPipelineCreateInfo info = 235 | { 236 | .vertex_shader = load("fullscreen.vert", 0, 0), 237 | .fragment_shader = load("composite.frag", 3, 6), 238 | .target_info = 239 | { 240 | .num_color_targets = 1, 241 | .color_target_descriptions = (SDL_GPUColorTargetDescription[]) 242 | {{ 243 | .format = SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, 244 | }}, 245 | }, 246 | }; 247 | SDL_GPUGraphicsPipeline* pipeline = NULL; 248 | if (info.vertex_shader && info.fragment_shader) 249 | { 250 | pipeline = SDL_CreateGPUGraphicsPipeline(device, &info); 251 | } 252 | if (!pipeline) 253 | { 254 | SDL_Log("Failed to create composite pipeline: %s", SDL_GetError()); 255 | } 256 | SDL_ReleaseGPUShader(device, info.vertex_shader); 257 | SDL_ReleaseGPUShader(device, info.fragment_shader); 258 | return pipeline; 259 | } 260 | 261 | static SDL_GPUGraphicsPipeline* load_transparent( 262 | const SDL_GPUTextureFormat format) 263 | { 264 | SDL_GPUGraphicsPipelineCreateInfo info = 265 | { 266 | .vertex_shader = load("transparent.vert", 4, 0), 267 | .fragment_shader = load("transparent.frag", 4, 3), 268 | .target_info = 269 | { 270 | .num_color_targets = 1, 271 | .color_target_descriptions = (SDL_GPUColorTargetDescription[]) 272 | {{ 273 | .format = SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, 274 | .blend_state = 275 | { 276 | .enable_blend = true, 277 | .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA, 278 | .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 279 | .src_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE, 280 | .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 281 | .color_blend_op = SDL_GPU_BLENDOP_ADD, 282 | .alpha_blend_op = SDL_GPU_BLENDOP_ADD, 283 | }, 284 | }}, 285 | .has_depth_stencil_target = true, 286 | .depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT, 287 | }, 288 | .vertex_input_state = 289 | { 290 | .num_vertex_attributes = 1, 291 | .vertex_attributes = (SDL_GPUVertexAttribute[]) 292 | {{ 293 | .format = SDL_GPU_VERTEXELEMENTFORMAT_UINT, 294 | }}, 295 | .num_vertex_buffers = 1, 296 | .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]) 297 | {{ 298 | .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, 299 | .pitch = 4, 300 | }}, 301 | }, 302 | .depth_stencil_state = 303 | { 304 | .enable_depth_test = true, 305 | .enable_depth_write = false, 306 | .compare_op = SDL_GPU_COMPAREOP_LESS, 307 | }, 308 | }; 309 | SDL_GPUGraphicsPipeline* pipeline = NULL; 310 | if (info.vertex_shader && info.fragment_shader) 311 | { 312 | pipeline = SDL_CreateGPUGraphicsPipeline(device, &info); 313 | } 314 | if (!pipeline) 315 | { 316 | SDL_Log("Failed to create transparent pipeline: %s", SDL_GetError()); 317 | } 318 | SDL_ReleaseGPUShader(device, info.vertex_shader); 319 | SDL_ReleaseGPUShader(device, info.fragment_shader); 320 | return pipeline; 321 | } 322 | 323 | static SDL_GPUGraphicsPipeline* load_raycast( 324 | const SDL_GPUTextureFormat format) 325 | { 326 | SDL_GPUGraphicsPipelineCreateInfo info = 327 | { 328 | .vertex_shader = load("raycast.vert", 2, 0), 329 | .fragment_shader = load("raycast.frag", 0, 0), 330 | .target_info = 331 | { 332 | .num_color_targets = 1, 333 | .color_target_descriptions = (SDL_GPUColorTargetDescription[]) 334 | {{ 335 | .format = SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, 336 | .blend_state = 337 | { 338 | .enable_blend = true, 339 | .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA, 340 | .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 341 | .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA, 342 | .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 343 | .color_blend_op = SDL_GPU_BLENDOP_ADD, 344 | .alpha_blend_op = SDL_GPU_BLENDOP_ADD, 345 | }, 346 | }}, 347 | .has_depth_stencil_target = true, 348 | .depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT, 349 | }, 350 | .vertex_input_state = 351 | { 352 | .num_vertex_attributes = 1, 353 | .vertex_attributes = (SDL_GPUVertexAttribute[]) 354 | {{ 355 | .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 356 | }}, 357 | .num_vertex_buffers = 1, 358 | .vertex_buffer_descriptions = (SDL_GPUVertexBufferDescription[]) 359 | {{ 360 | .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX, 361 | .pitch = 12, 362 | }}, 363 | }, 364 | .depth_stencil_state = 365 | { 366 | .enable_depth_test = true, 367 | .enable_depth_write = true, 368 | .compare_op = SDL_GPU_COMPAREOP_LESS, 369 | }, 370 | }; 371 | SDL_GPUGraphicsPipeline* pipeline = NULL; 372 | if (info.vertex_shader && info.fragment_shader) 373 | { 374 | pipeline = SDL_CreateGPUGraphicsPipeline(device, &info); 375 | } 376 | if (!pipeline) 377 | { 378 | SDL_Log("Failed to create raycast pipeline: %s", SDL_GetError()); 379 | } 380 | SDL_ReleaseGPUShader(device, info.vertex_shader); 381 | SDL_ReleaseGPUShader(device, info.fragment_shader); 382 | return pipeline; 383 | } 384 | 385 | static SDL_GPUGraphicsPipeline* load_ui( 386 | const SDL_GPUTextureFormat format) 387 | { 388 | SDL_GPUGraphicsPipelineCreateInfo info = 389 | { 390 | .vertex_shader = load("fullscreen.vert", 0, 0), 391 | .fragment_shader = load("ui.frag", 2, 1), 392 | .target_info = 393 | { 394 | .num_color_targets = 1, 395 | .color_target_descriptions = (SDL_GPUColorTargetDescription[]) 396 | {{ 397 | .format = format, 398 | .blend_state = 399 | { 400 | .enable_blend = true, 401 | .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA, 402 | .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 403 | .src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA, 404 | .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, 405 | .color_blend_op = SDL_GPU_BLENDOP_ADD, 406 | .alpha_blend_op = SDL_GPU_BLENDOP_ADD, 407 | }, 408 | }}, 409 | }, 410 | }; 411 | SDL_GPUGraphicsPipeline* pipeline = NULL; 412 | if (info.vertex_shader && info.fragment_shader) 413 | { 414 | pipeline = SDL_CreateGPUGraphicsPipeline(device, &info); 415 | } 416 | if (!pipeline) 417 | { 418 | SDL_Log("Failed to create ui pipeline: %s", SDL_GetError()); 419 | } 420 | SDL_ReleaseGPUShader(device, info.vertex_shader); 421 | SDL_ReleaseGPUShader(device, info.fragment_shader); 422 | return pipeline; 423 | } 424 | 425 | static SDL_GPUGraphicsPipeline* load_random( 426 | const SDL_GPUTextureFormat format) 427 | { 428 | SDL_GPUGraphicsPipelineCreateInfo info = 429 | { 430 | .vertex_shader = load("fullscreen.vert", 0, 0), 431 | .fragment_shader = load("random.frag", 0, 0), 432 | .target_info = 433 | { 434 | .num_color_targets = 1, 435 | .color_target_descriptions = (SDL_GPUColorTargetDescription[]) 436 | {{ 437 | .format = SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT, 438 | }}, 439 | }, 440 | }; 441 | SDL_GPUGraphicsPipeline* pipeline = NULL; 442 | if (info.vertex_shader && info.fragment_shader) 443 | { 444 | pipeline = SDL_CreateGPUGraphicsPipeline(device, &info); 445 | } 446 | if (!pipeline) 447 | { 448 | SDL_Log("Failed to create ui pipeline: %s", SDL_GetError()); 449 | } 450 | SDL_ReleaseGPUShader(device, info.vertex_shader); 451 | SDL_ReleaseGPUShader(device, info.fragment_shader); 452 | return pipeline; 453 | } 454 | 455 | bool pipeline_init( 456 | SDL_GPUDevice* handle, 457 | const SDL_GPUTextureFormat format) 458 | { 459 | assert(handle); 460 | assert(format); 461 | device = handle; 462 | pipelines[PIPELINE_SKY] = load_sky(format); 463 | pipelines[PIPELINE_SHADOW] = load_shadow(format); 464 | pipelines[PIPELINE_OPAQUE] = load_opaque(format); 465 | pipelines[PIPELINE_SSAO] = load_ssao(format); 466 | pipelines[PIPELINE_COMPOSITE] = load_composite(format); 467 | pipelines[PIPELINE_TRANSPARENT] = load_transparent(format); 468 | pipelines[PIPELINE_RAYCAST] = load_raycast(format); 469 | pipelines[PIPELINE_UI] = load_ui(format); 470 | pipelines[PIPELINE_RANDOM] = load_random(format); 471 | for (pipeline_t pipeline = 0; pipeline < PIPELINE_COUNT; pipeline++) 472 | { 473 | if (!pipelines[pipeline]) 474 | { 475 | SDL_Log("Failed to load pipeline: %d", pipeline); 476 | return false; 477 | } 478 | } 479 | return true; 480 | } 481 | 482 | void pipeline_free() 483 | { 484 | for (pipeline_t pipeline = 0; pipeline < PIPELINE_COUNT; pipeline++) 485 | { 486 | if (pipelines[pipeline]) 487 | { 488 | SDL_ReleaseGPUGraphicsPipeline(device, pipelines[pipeline]); 489 | pipelines[pipeline] = NULL; 490 | } 491 | } 492 | device = NULL; 493 | } 494 | 495 | void pipeline_bind( 496 | void* pass, 497 | const pipeline_t pipeline) 498 | { 499 | assert(pass); 500 | assert(pipeline < PIPELINE_COUNT); 501 | switch (pipeline) 502 | { 503 | case PIPELINE_SKY: 504 | case PIPELINE_SHADOW: 505 | case PIPELINE_OPAQUE: 506 | case PIPELINE_SSAO: 507 | case PIPELINE_COMPOSITE: 508 | case PIPELINE_TRANSPARENT: 509 | case PIPELINE_RAYCAST: 510 | case PIPELINE_UI: 511 | case PIPELINE_RANDOM: 512 | SDL_BindGPUGraphicsPipeline(pass, pipelines[pipeline]); 513 | break; 514 | default: 515 | assert(0); 516 | } 517 | } -------------------------------------------------------------------------------- /lib/stb/stb_perlin.h: -------------------------------------------------------------------------------- 1 | // stb_perlin.h - v0.5 - perlin noise 2 | // public domain single-file C implementation by Sean Barrett 3 | // 4 | // LICENSE 5 | // 6 | // See end of file. 7 | // 8 | // 9 | // to create the implementation, 10 | // #define STB_PERLIN_IMPLEMENTATION 11 | // in *one* C/CPP file that includes this file. 12 | // 13 | // 14 | // Documentation: 15 | // 16 | // float stb_perlin_noise3( float x, 17 | // float y, 18 | // float z, 19 | // int x_wrap=0, 20 | // int y_wrap=0, 21 | // int z_wrap=0) 22 | // 23 | // This function computes a random value at the coordinate (x,y,z). 24 | // Adjacent random values are continuous but the noise fluctuates 25 | // its randomness with period 1, i.e. takes on wholly unrelated values 26 | // at integer points. Specifically, this implements Ken Perlin's 27 | // revised noise function from 2002. 28 | // 29 | // The "wrap" parameters can be used to create wraparound noise that 30 | // wraps at powers of two. The numbers MUST be powers of two. Specify 31 | // 0 to mean "don't care". (The noise always wraps every 256 due 32 | // details of the implementation, even if you ask for larger or no 33 | // wrapping.) 34 | // 35 | // float stb_perlin_noise3_seed( float x, 36 | // float y, 37 | // float z, 38 | // int x_wrap=0, 39 | // int y_wrap=0, 40 | // int z_wrap=0, 41 | // int seed) 42 | // 43 | // As above, but 'seed' selects from multiple different variations of the 44 | // noise function. The current implementation only uses the bottom 8 bits 45 | // of 'seed', but possibly in the future more bits will be used. 46 | // 47 | // 48 | // Fractal Noise: 49 | // 50 | // Three common fractal noise functions are included, which produce 51 | // a wide variety of nice effects depending on the parameters 52 | // provided. Note that each function will call stb_perlin_noise3 53 | // 'octaves' times, so this parameter will affect runtime. 54 | // 55 | // float stb_perlin_ridge_noise3(float x, float y, float z, 56 | // float lacunarity, float gain, float offset, int octaves) 57 | // 58 | // float stb_perlin_fbm_noise3(float x, float y, float z, 59 | // float lacunarity, float gain, int octaves) 60 | // 61 | // float stb_perlin_turbulence_noise3(float x, float y, float z, 62 | // float lacunarity, float gain, int octaves) 63 | // 64 | // Typical values to start playing with: 65 | // octaves = 6 -- number of "octaves" of noise3() to sum 66 | // lacunarity = ~ 2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) 67 | // gain = 0.5 -- relative weighting applied to each successive octave 68 | // offset = 1.0? -- used to invert the ridges, may need to be larger, not sure 69 | // 70 | // 71 | // Contributors: 72 | // Jack Mott - additional noise functions 73 | // Jordan Peck - seeded noise 74 | // 75 | 76 | 77 | #ifdef __cplusplus 78 | extern "C" { 79 | #endif 80 | extern float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap); 81 | extern float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed); 82 | extern float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves); 83 | extern float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves); 84 | extern float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves); 85 | extern float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed); 86 | #ifdef __cplusplus 87 | } 88 | #endif 89 | 90 | #ifdef STB_PERLIN_IMPLEMENTATION 91 | 92 | #include // fabs() 93 | 94 | // not same permutation table as Perlin's reference to avoid copyright issues; 95 | // Perlin's table can be found at http://mrl.nyu.edu/~perlin/noise/ 96 | static unsigned char stb__perlin_randtab[512] = 97 | { 98 | 23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123, 99 | 152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72, 100 | 175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240, 101 | 8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57, 102 | 225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233, 103 | 94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172, 104 | 165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243, 105 | 65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122, 106 | 26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76, 107 | 250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246, 108 | 132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3, 109 | 91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231, 110 | 38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221, 111 | 131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62, 112 | 27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135, 113 | 61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5, 114 | 115 | // and a second copy so we don't need an extra mask or static initializer 116 | 23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123, 117 | 152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72, 118 | 175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240, 119 | 8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57, 120 | 225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233, 121 | 94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172, 122 | 165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243, 123 | 65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122, 124 | 26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76, 125 | 250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246, 126 | 132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3, 127 | 91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231, 128 | 38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221, 129 | 131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62, 130 | 27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135, 131 | 61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5, 132 | }; 133 | 134 | 135 | // perlin's gradient has 12 cases so some get used 1/16th of the time 136 | // and some 2/16ths. We reduce bias by changing those fractions 137 | // to 5/64ths and 6/64ths 138 | 139 | // this array is designed to match the previous implementation 140 | // of gradient hash: indices[stb__perlin_randtab[i]&63] 141 | static unsigned char stb__perlin_randtab_grad_idx[512] = 142 | { 143 | 7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7, 144 | 8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8, 145 | 7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8, 146 | 8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5, 147 | 5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1, 148 | 2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4, 149 | 9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11, 150 | 1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6, 151 | 10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0, 152 | 6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2, 153 | 4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3, 154 | 11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11, 155 | 10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1, 156 | 3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10, 157 | 11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7, 158 | 9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5, 159 | 160 | // and a second copy so we don't need an extra mask or static initializer 161 | 7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7, 162 | 8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8, 163 | 7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8, 164 | 8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5, 165 | 5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1, 166 | 2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4, 167 | 9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11, 168 | 1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6, 169 | 10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0, 170 | 6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2, 171 | 4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3, 172 | 11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11, 173 | 10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1, 174 | 3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10, 175 | 11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7, 176 | 9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5, 177 | }; 178 | 179 | static float stb__perlin_lerp(float a, float b, float t) 180 | { 181 | return a + (b-a) * t; 182 | } 183 | 184 | static int stb__perlin_fastfloor(float a) 185 | { 186 | int ai = (int) a; 187 | return (a < ai) ? ai-1 : ai; 188 | } 189 | 190 | // different grad function from Perlin's, but easy to modify to match reference 191 | static float stb__perlin_grad(int grad_idx, float x, float y, float z) 192 | { 193 | static float basis[12][4] = 194 | { 195 | { 1, 1, 0 }, 196 | { -1, 1, 0 }, 197 | { 1,-1, 0 }, 198 | { -1,-1, 0 }, 199 | { 1, 0, 1 }, 200 | { -1, 0, 1 }, 201 | { 1, 0,-1 }, 202 | { -1, 0,-1 }, 203 | { 0, 1, 1 }, 204 | { 0,-1, 1 }, 205 | { 0, 1,-1 }, 206 | { 0,-1,-1 }, 207 | }; 208 | 209 | float *grad = basis[grad_idx]; 210 | return grad[0]*x + grad[1]*y + grad[2]*z; 211 | } 212 | 213 | float stb_perlin_noise3_internal(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed) 214 | { 215 | float u,v,w; 216 | float n000,n001,n010,n011,n100,n101,n110,n111; 217 | float n00,n01,n10,n11; 218 | float n0,n1; 219 | 220 | unsigned int x_mask = (x_wrap-1) & 255; 221 | unsigned int y_mask = (y_wrap-1) & 255; 222 | unsigned int z_mask = (z_wrap-1) & 255; 223 | int px = stb__perlin_fastfloor(x); 224 | int py = stb__perlin_fastfloor(y); 225 | int pz = stb__perlin_fastfloor(z); 226 | int x0 = px & x_mask, x1 = (px+1) & x_mask; 227 | int y0 = py & y_mask, y1 = (py+1) & y_mask; 228 | int z0 = pz & z_mask, z1 = (pz+1) & z_mask; 229 | int r0,r1, r00,r01,r10,r11; 230 | 231 | #define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a) 232 | 233 | x -= px; u = stb__perlin_ease(x); 234 | y -= py; v = stb__perlin_ease(y); 235 | z -= pz; w = stb__perlin_ease(z); 236 | 237 | r0 = stb__perlin_randtab[x0+seed]; 238 | r1 = stb__perlin_randtab[x1+seed]; 239 | 240 | r00 = stb__perlin_randtab[r0+y0]; 241 | r01 = stb__perlin_randtab[r0+y1]; 242 | r10 = stb__perlin_randtab[r1+y0]; 243 | r11 = stb__perlin_randtab[r1+y1]; 244 | 245 | n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z ); 246 | n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 ); 247 | n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z ); 248 | n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 ); 249 | n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z ); 250 | n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 ); 251 | n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z ); 252 | n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 ); 253 | 254 | n00 = stb__perlin_lerp(n000,n001,w); 255 | n01 = stb__perlin_lerp(n010,n011,w); 256 | n10 = stb__perlin_lerp(n100,n101,w); 257 | n11 = stb__perlin_lerp(n110,n111,w); 258 | 259 | n0 = stb__perlin_lerp(n00,n01,v); 260 | n1 = stb__perlin_lerp(n10,n11,v); 261 | 262 | return stb__perlin_lerp(n0,n1,u); 263 | } 264 | 265 | float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap) 266 | { 267 | return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap,0); 268 | } 269 | 270 | float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed) 271 | { 272 | return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap, (unsigned char) seed); 273 | } 274 | 275 | float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves) 276 | { 277 | int i; 278 | float frequency = 1.0f; 279 | float prev = 1.0f; 280 | float amplitude = 0.5f; 281 | float sum = 0.0f; 282 | 283 | for (i = 0; i < octaves; i++) { 284 | float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i); 285 | r = offset - (float) fabs(r); 286 | r = r*r; 287 | sum += r*amplitude*prev; 288 | prev = r; 289 | frequency *= lacunarity; 290 | amplitude *= gain; 291 | } 292 | return sum; 293 | } 294 | 295 | float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves) 296 | { 297 | int i; 298 | float frequency = 1.0f; 299 | float amplitude = 1.0f; 300 | float sum = 0.0f; 301 | 302 | for (i = 0; i < octaves; i++) { 303 | sum += stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude; 304 | frequency *= lacunarity; 305 | amplitude *= gain; 306 | } 307 | return sum; 308 | } 309 | 310 | float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves) 311 | { 312 | int i; 313 | float frequency = 1.0f; 314 | float amplitude = 1.0f; 315 | float sum = 0.0f; 316 | 317 | for (i = 0; i < octaves; i++) { 318 | float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude; 319 | sum += (float) fabs(r); 320 | frequency *= lacunarity; 321 | amplitude *= gain; 322 | } 323 | return sum; 324 | } 325 | 326 | float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed) 327 | { 328 | float u,v,w; 329 | float n000,n001,n010,n011,n100,n101,n110,n111; 330 | float n00,n01,n10,n11; 331 | float n0,n1; 332 | 333 | int px = stb__perlin_fastfloor(x); 334 | int py = stb__perlin_fastfloor(y); 335 | int pz = stb__perlin_fastfloor(z); 336 | int x_wrap2 = (x_wrap ? x_wrap : 256); 337 | int y_wrap2 = (y_wrap ? y_wrap : 256); 338 | int z_wrap2 = (z_wrap ? z_wrap : 256); 339 | int x0 = px % x_wrap2, x1; 340 | int y0 = py % y_wrap2, y1; 341 | int z0 = pz % z_wrap2, z1; 342 | int r0,r1, r00,r01,r10,r11; 343 | 344 | if (x0 < 0) x0 += x_wrap2; 345 | if (y0 < 0) y0 += y_wrap2; 346 | if (z0 < 0) z0 += z_wrap2; 347 | x1 = (x0+1) % x_wrap2; 348 | y1 = (y0+1) % y_wrap2; 349 | z1 = (z0+1) % z_wrap2; 350 | 351 | #define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a) 352 | 353 | x -= px; u = stb__perlin_ease(x); 354 | y -= py; v = stb__perlin_ease(y); 355 | z -= pz; w = stb__perlin_ease(z); 356 | 357 | r0 = stb__perlin_randtab[x0]; 358 | r0 = stb__perlin_randtab[r0+seed]; 359 | r1 = stb__perlin_randtab[x1]; 360 | r1 = stb__perlin_randtab[r1+seed]; 361 | 362 | r00 = stb__perlin_randtab[r0+y0]; 363 | r01 = stb__perlin_randtab[r0+y1]; 364 | r10 = stb__perlin_randtab[r1+y0]; 365 | r11 = stb__perlin_randtab[r1+y1]; 366 | 367 | n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z ); 368 | n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 ); 369 | n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z ); 370 | n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 ); 371 | n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z ); 372 | n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 ); 373 | n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z ); 374 | n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 ); 375 | 376 | n00 = stb__perlin_lerp(n000,n001,w); 377 | n01 = stb__perlin_lerp(n010,n011,w); 378 | n10 = stb__perlin_lerp(n100,n101,w); 379 | n11 = stb__perlin_lerp(n110,n111,w); 380 | 381 | n0 = stb__perlin_lerp(n00,n01,v); 382 | n1 = stb__perlin_lerp(n10,n11,v); 383 | 384 | return stb__perlin_lerp(n0,n1,u); 385 | } 386 | #endif // STB_PERLIN_IMPLEMENTATION 387 | 388 | /* 389 | ------------------------------------------------------------------------------ 390 | This software is available under 2 licenses -- choose whichever you prefer. 391 | ------------------------------------------------------------------------------ 392 | ALTERNATIVE A - MIT License 393 | Copyright (c) 2017 Sean Barrett 394 | Permission is hereby granted, free of charge, to any person obtaining a copy of 395 | this software and associated documentation files (the "Software"), to deal in 396 | the Software without restriction, including without limitation the rights to 397 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 398 | of the Software, and to permit persons to whom the Software is furnished to do 399 | so, subject to the following conditions: 400 | The above copyright notice and this permission notice shall be included in all 401 | copies or substantial portions of the Software. 402 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 403 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 404 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 405 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 406 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 407 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 408 | SOFTWARE. 409 | ------------------------------------------------------------------------------ 410 | ALTERNATIVE B - Public Domain (www.unlicense.org) 411 | This is free and unencumbered software released into the public domain. 412 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 413 | software, either in source code form or as a compiled binary, for any purpose, 414 | commercial or non-commercial, and by any means. 415 | In jurisdictions that recognize copyright laws, the author or authors of this 416 | software dedicate any and all copyright interest in the software to the public 417 | domain. We make this dedication for the benefit of the public at large and to 418 | the detriment of our heirs and successors. We intend this dedication to be an 419 | overt act of relinquishment in perpetuity of all present and future rights to 420 | this software under copyright law. 421 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 422 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 423 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 424 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 425 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 426 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 427 | ------------------------------------------------------------------------------ 428 | */ 429 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "block.h" 9 | #include "camera.h" 10 | #include "database.h" 11 | #include "pipeline.h" 12 | #include "raycast.h" 13 | #include "world.h" 14 | 15 | static SDL_Window* window; 16 | static SDL_GPUDevice* device; 17 | static SDL_GPUCommandBuffer* commands; 18 | static uint32_t width; 19 | static uint32_t height; 20 | static uint32_t render_width; 21 | static uint32_t render_height; 22 | static SDL_GPUBuffer* cube_vbo; 23 | static SDL_GPUTexture* color_texture; 24 | static SDL_GPUTexture* depth_texture; 25 | static SDL_GPUTexture* shadow_texture; 26 | static SDL_GPUTexture* position_texture; 27 | static SDL_GPUTexture* uv_texture; 28 | static SDL_GPUTexture* voxel_texture; 29 | static SDL_GPUTexture* ssao_texture; 30 | static SDL_GPUTexture* random_texture; 31 | static SDL_GPUTexture* atlas_texture; 32 | static SDL_GPUTexture* composite_texture; 33 | static SDL_GPUSampler* nearest_sampler; 34 | static SDL_GPUSampler* linear_sampler; 35 | static SDL_Surface* atlas_surface; 36 | static camera_t player_camera; 37 | static camera_t shadow_camera; 38 | static uint64_t time1; 39 | static uint64_t time2; 40 | static float cooldown; 41 | static block_t selected = BLOCK_GRASS; 42 | 43 | static bool create_atlas() 44 | { 45 | atlas_surface = SDL_LoadPNG("atlas.png"); 46 | if (!atlas_surface) 47 | { 48 | SDL_Log("Failed to create atlas surface: %s", SDL_GetError()); 49 | return false; 50 | } 51 | SDL_GPUTextureCreateInfo tci = {0}; 52 | tci.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; 53 | tci.type = SDL_GPU_TEXTURETYPE_2D_ARRAY; 54 | tci.layer_count_or_depth = ATLAS_WIDTH / BLOCK_WIDTH; 55 | tci.num_levels = ATLAS_LEVELS; 56 | tci.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET; 57 | tci.width = BLOCK_WIDTH; 58 | tci.height = BLOCK_WIDTH; 59 | atlas_texture = SDL_CreateGPUTexture(device, &tci); 60 | if (!atlas_texture) 61 | { 62 | SDL_Log("Failed to create atlas texture: %s", SDL_GetError()); 63 | return false; 64 | } 65 | tci.type = SDL_GPU_TEXTURETYPE_2D; 66 | tci.layer_count_or_depth = 1; 67 | tci.num_levels = 1; 68 | tci.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET; 69 | tci.width = atlas_surface->w; 70 | tci.height = atlas_surface->h; 71 | SDL_GPUTexture* texture = SDL_CreateGPUTexture(device, &tci); 72 | if (!texture) 73 | { 74 | SDL_Log("Failed to create texture: %s", SDL_GetError()); 75 | return false; 76 | } 77 | SDL_GPUTransferBufferCreateInfo tbci = {0}; 78 | tbci.size = atlas_surface->w * atlas_surface->h * 4; 79 | tbci.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; 80 | SDL_GPUTransferBuffer* buffer = SDL_CreateGPUTransferBuffer(device, &tbci); 81 | if (!buffer) 82 | { 83 | SDL_Log("Failed to create transfer buffer: %s", SDL_GetError()); 84 | return false; 85 | } 86 | void* data = SDL_MapGPUTransferBuffer(device, buffer, 0); 87 | if (!data) 88 | { 89 | SDL_Log("Failed to map transfer buffer: %s", SDL_GetError()); 90 | return false; 91 | } 92 | memcpy(data, atlas_surface->pixels, atlas_surface->w * atlas_surface->h * 4); 93 | SDL_UnmapGPUTransferBuffer(device, buffer); 94 | SDL_GPUCommandBuffer* commands = SDL_AcquireGPUCommandBuffer(device); 95 | if (!commands) 96 | { 97 | SDL_Log("Failed to acquire command buffer: %s", SDL_GetError()); 98 | return false; 99 | } 100 | SDL_GPUCopyPass* pass = SDL_BeginGPUCopyPass(commands); 101 | if (!pass) 102 | { 103 | SDL_Log("Failed to begin copy pass: %s", SDL_GetError()); 104 | return false; 105 | } 106 | SDL_GPUTextureTransferInfo tti = {0}; 107 | SDL_GPUTextureRegion region = {0}; 108 | tti.transfer_buffer = buffer; 109 | region.texture = texture; 110 | region.w = atlas_surface->w; 111 | region.h = atlas_surface->h; 112 | region.d = 1; 113 | SDL_UploadToGPUTexture(pass, &tti, ®ion, 0); 114 | SDL_EndGPUCopyPass(pass); 115 | for (int i = 0; i < ATLAS_WIDTH / BLOCK_WIDTH; i++) 116 | { 117 | SDL_GPUBlitInfo info = {0}; 118 | info.source.texture = texture; 119 | info.source.x = i * BLOCK_WIDTH; 120 | info.source.y = 0; 121 | info.source.w = BLOCK_WIDTH; 122 | info.source.h = BLOCK_WIDTH; 123 | info.destination.texture = atlas_texture; 124 | info.destination.x = 0; 125 | info.destination.y = 0; 126 | info.destination.w = BLOCK_WIDTH; 127 | info.destination.h = BLOCK_WIDTH; 128 | info.destination.layer_or_depth_plane = i; 129 | SDL_BlitGPUTexture(commands, &info); 130 | } 131 | SDL_GenerateMipmapsForGPUTexture(commands, atlas_texture); 132 | SDL_SubmitGPUCommandBuffer(commands); 133 | SDL_ReleaseGPUTexture(device, texture); 134 | SDL_ReleaseGPUTransferBuffer(device, buffer); 135 | return true; 136 | } 137 | 138 | static bool create_samplers() 139 | { 140 | SDL_GPUSamplerCreateInfo sci = {0}; 141 | sci.min_filter = SDL_GPU_FILTER_LINEAR; 142 | sci.mag_filter = SDL_GPU_FILTER_LINEAR; 143 | sci.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST; 144 | sci.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; 145 | sci.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; 146 | sci.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE; 147 | linear_sampler = SDL_CreateGPUSampler(device, &sci); 148 | if (!linear_sampler) 149 | { 150 | SDL_Log("Failed to create linear sampler: %s", SDL_GetError()); 151 | return false; 152 | } 153 | sci.min_filter = SDL_GPU_FILTER_NEAREST; 154 | sci.mag_filter = SDL_GPU_FILTER_NEAREST; 155 | nearest_sampler = SDL_CreateGPUSampler(device, &sci); 156 | if (!nearest_sampler) 157 | { 158 | SDL_Log("Failed to create nearest sampler: %s", SDL_GetError()); 159 | return false; 160 | } 161 | return true; 162 | } 163 | 164 | static bool create_textures() 165 | { 166 | SDL_GPUTextureCreateInfo tci = {0}; 167 | tci.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; 168 | tci.type = SDL_GPU_TEXTURETYPE_2D; 169 | tci.format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT; 170 | tci.width = SHADOW_SIZE; 171 | tci.height = SHADOW_SIZE; 172 | tci.layer_count_or_depth = 1; 173 | tci.num_levels = 1; 174 | shadow_texture = SDL_CreateGPUTexture(device, &tci); 175 | if (!shadow_texture) 176 | { 177 | SDL_Log("Failed to create shadow texture: %s", SDL_GetError()); 178 | return false; 179 | } 180 | return true; 181 | } 182 | 183 | static bool resize_textures() 184 | { 185 | if (depth_texture) 186 | { 187 | SDL_ReleaseGPUTexture(device, depth_texture); 188 | depth_texture = NULL; 189 | } 190 | if (position_texture) 191 | { 192 | SDL_ReleaseGPUTexture(device, position_texture); 193 | position_texture = NULL; 194 | } 195 | if (uv_texture) 196 | { 197 | SDL_ReleaseGPUTexture(device, uv_texture); 198 | uv_texture = NULL; 199 | } 200 | if (voxel_texture) 201 | { 202 | SDL_ReleaseGPUTexture(device, voxel_texture); 203 | voxel_texture = NULL; 204 | } 205 | if (ssao_texture) 206 | { 207 | SDL_ReleaseGPUTexture(device, ssao_texture); 208 | ssao_texture = NULL; 209 | } 210 | if (random_texture) 211 | { 212 | SDL_ReleaseGPUTexture(device, random_texture); 213 | random_texture = NULL; 214 | } 215 | if (composite_texture) 216 | { 217 | SDL_ReleaseGPUTexture(device, composite_texture); 218 | composite_texture = NULL; 219 | } 220 | SDL_GPUTextureCreateInfo tci = {0}; 221 | tci.type = SDL_GPU_TEXTURETYPE_2D; 222 | tci.layer_count_or_depth = 1; 223 | tci.num_levels = 1; 224 | tci.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET; 225 | tci.format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT; 226 | tci.width = render_width; 227 | tci.height = render_height; 228 | depth_texture = SDL_CreateGPUTexture(device, &tci); 229 | if (!depth_texture) 230 | { 231 | SDL_Log("Failed to create depth texture: %s", SDL_GetError()); 232 | return false; 233 | } 234 | tci.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; 235 | tci.format = SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT; 236 | position_texture = SDL_CreateGPUTexture(device, &tci); 237 | if (!position_texture) 238 | { 239 | SDL_Log("Failed to create position texture: %s", SDL_GetError()); 240 | return false; 241 | } 242 | tci.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; 243 | tci.format = SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT; 244 | uv_texture = SDL_CreateGPUTexture(device, &tci); 245 | if (!uv_texture) 246 | { 247 | SDL_Log("Failed to create uv texture: %s", SDL_GetError()); 248 | return false; 249 | } 250 | tci.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; 251 | tci.format = SDL_GPU_TEXTUREFORMAT_R32_UINT; 252 | voxel_texture = SDL_CreateGPUTexture(device, &tci); 253 | if (!voxel_texture) 254 | { 255 | SDL_Log("Failed to create voxel texture: %s", SDL_GetError()); 256 | return false; 257 | } 258 | tci.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; 259 | tci.format = SDL_GPU_TEXTUREFORMAT_R32_FLOAT; 260 | ssao_texture = SDL_CreateGPUTexture(device, &tci); 261 | if (!ssao_texture) 262 | { 263 | SDL_Log("Failed to create ssao texture: %s", SDL_GetError()); 264 | return false; 265 | } 266 | tci.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; 267 | tci.format = SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT; 268 | random_texture = SDL_CreateGPUTexture(device, &tci); 269 | if (!random_texture) 270 | { 271 | SDL_Log("Failed to create random texture: %s", SDL_GetError()); 272 | return false; 273 | } 274 | tci.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER; 275 | tci.format = SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT; 276 | composite_texture = SDL_CreateGPUTexture(device, &tci); 277 | if (!composite_texture) 278 | { 279 | SDL_Log("Failed to create composite texture: %s", SDL_GetError()); 280 | return false; 281 | } 282 | return true; 283 | } 284 | 285 | SDL_Surface* create_icon( 286 | const block_t block) 287 | { 288 | assert(block < BLOCK_COUNT); 289 | if (!atlas_surface) 290 | { 291 | return NULL; 292 | } 293 | SDL_Surface* icon = SDL_CreateSurface(BLOCK_WIDTH, BLOCK_WIDTH, SDL_PIXELFORMAT_RGBA32); 294 | if (!icon) 295 | { 296 | SDL_Log("Failed to create icon surface: %s", SDL_GetError()); 297 | return NULL; 298 | } 299 | SDL_Rect src; 300 | src.x = blocks[block][0] * BLOCK_WIDTH; 301 | src.y = 0; 302 | src.w = BLOCK_WIDTH; 303 | src.h = BLOCK_WIDTH; 304 | SDL_Rect dst; 305 | dst.x = 0; 306 | dst.y = 0; 307 | dst.w = BLOCK_WIDTH; 308 | dst.h = BLOCK_WIDTH; 309 | if (!SDL_BlitSurface(atlas_surface, &src, icon, &dst)) 310 | { 311 | SDL_Log("Failed to blit icon surface: %s", SDL_GetError()); 312 | SDL_DestroySurface(icon); 313 | return NULL; 314 | } 315 | return icon; 316 | } 317 | 318 | static bool create_vbos() 319 | { 320 | const float cube[][3] = 321 | { 322 | {-1,-1,-1 }, { 1,-1,-1 }, { 1, 1,-1 }, 323 | {-1,-1,-1 }, { 1, 1,-1 }, {-1, 1,-1 }, 324 | { 1,-1, 1 }, { 1, 1, 1 }, {-1, 1, 1 }, 325 | { 1,-1, 1 }, {-1, 1, 1 }, {-1,-1, 1 }, 326 | {-1,-1,-1 }, {-1, 1,-1 }, {-1, 1, 1 }, 327 | {-1,-1,-1 }, {-1, 1, 1 }, {-1,-1, 1 }, 328 | { 1,-1,-1 }, { 1,-1, 1 }, { 1, 1, 1 }, 329 | { 1,-1,-1 }, { 1, 1, 1 }, { 1, 1,-1 }, 330 | {-1, 1,-1 }, { 1, 1,-1 }, { 1, 1, 1 }, 331 | {-1, 1,-1 }, { 1, 1, 1 }, {-1, 1, 1 }, 332 | {-1,-1,-1 }, {-1,-1, 1 }, { 1,-1, 1 }, 333 | {-1,-1,-1 }, { 1,-1, 1 }, { 1,-1,-1 }, 334 | }; 335 | SDL_GPUBufferCreateInfo bci = {0}; 336 | bci.usage = SDL_GPU_BUFFERUSAGE_VERTEX; 337 | bci.size = sizeof(cube); 338 | cube_vbo = SDL_CreateGPUBuffer(device, &bci); 339 | if (!cube_vbo) 340 | { 341 | SDL_Log("Failed to create vertex buffer: %s", SDL_GetError()); 342 | return false; 343 | } 344 | SDL_GPUTransferBufferCreateInfo tbci = {0}; 345 | tbci.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; 346 | tbci.size = sizeof(cube); 347 | SDL_GPUTransferBuffer* tbo = SDL_CreateGPUTransferBuffer(device, &tbci); 348 | if (!tbo) 349 | { 350 | SDL_Log("Failed to create transfer buffer: %s", SDL_GetError()); 351 | return false; 352 | } 353 | void* data = SDL_MapGPUTransferBuffer(device, tbo, false); 354 | if (!data) 355 | { 356 | SDL_Log("Failed to map transfer buffer: %s", SDL_GetError()); 357 | return false; 358 | } 359 | memcpy(data, cube, sizeof(cube)); 360 | SDL_UnmapGPUTransferBuffer(device, tbo); 361 | SDL_GPUCommandBuffer* commands = SDL_AcquireGPUCommandBuffer(device); 362 | if (!commands) 363 | { 364 | SDL_Log("Failed to acquire command buffer: %s", SDL_GetError()); 365 | return false; 366 | } 367 | SDL_GPUCopyPass* pass = SDL_BeginGPUCopyPass(commands); 368 | if (!pass) 369 | { 370 | SDL_Log("Failed to begin copy pass: %s", SDL_GetError()); 371 | return false; 372 | } 373 | SDL_GPUTransferBufferLocation location = {0}; 374 | SDL_GPUBufferRegion region = {0}; 375 | location.transfer_buffer = tbo; 376 | region.size = sizeof(cube); 377 | region.buffer = cube_vbo; 378 | SDL_UploadToGPUBuffer(pass, &location, ®ion, 1); 379 | SDL_EndGPUCopyPass(pass); 380 | SDL_SubmitGPUCommandBuffer(commands); 381 | SDL_ReleaseGPUTransferBuffer(device, tbo); 382 | return true; 383 | } 384 | 385 | static void draw_random() 386 | { 387 | SDL_GPUCommandBuffer* commands = SDL_AcquireGPUCommandBuffer(device); 388 | if (!commands) 389 | { 390 | SDL_Log("Failed to acquire command buffer: %s", SDL_GetError()); 391 | return; 392 | } 393 | SDL_GPUColorTargetInfo cti = {0}; 394 | cti.load_op = SDL_GPU_LOADOP_CLEAR; 395 | cti.store_op = SDL_GPU_STOREOP_STORE; 396 | cti.texture = random_texture; 397 | SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(commands, &cti, 1, NULL); 398 | if (!pass) 399 | { 400 | SDL_Log("Failed to begin render pass: %s", SDL_GetError()); 401 | SDL_CancelGPUCommandBuffer(commands); 402 | return; 403 | } 404 | pipeline_bind(pass, PIPELINE_RANDOM); 405 | SDL_DrawGPUPrimitives(pass, 4, 1, 0, 0); 406 | SDL_EndGPURenderPass(pass); 407 | SDL_SubmitGPUCommandBuffer(commands); 408 | } 409 | 410 | static void draw_sky() 411 | { 412 | SDL_GPUColorTargetInfo cti = {0}; 413 | cti.load_op = SDL_GPU_LOADOP_DONT_CARE; 414 | cti.store_op = SDL_GPU_STOREOP_STORE; 415 | cti.texture = composite_texture; 416 | cti.cycle = true; 417 | SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(commands, &cti, 1, NULL); 418 | if (!pass) 419 | { 420 | SDL_Log("Failed to begin render pass: %s", SDL_GetError()); 421 | return; 422 | } 423 | SDL_GPUBufferBinding bb = {0}; 424 | bb.buffer = cube_vbo; 425 | pipeline_bind(pass, PIPELINE_SKY); 426 | SDL_PushGPUVertexUniformData(commands, 0, player_camera.view, 64); 427 | SDL_PushGPUVertexUniformData(commands, 1, player_camera.proj, 64); 428 | SDL_BindGPUVertexBuffers(pass, 0, &bb, 1); 429 | SDL_DrawGPUPrimitives(pass, 36, 1, 0, 0); 430 | SDL_EndGPURenderPass(pass); 431 | } 432 | 433 | static void draw_shadow() 434 | { 435 | SDL_GPUDepthStencilTargetInfo dsti = {0}; 436 | dsti.clear_depth = 1.0f; 437 | dsti.load_op = SDL_GPU_LOADOP_CLEAR; 438 | dsti.store_op = SDL_GPU_STOREOP_STORE; 439 | dsti.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE; 440 | dsti.texture = shadow_texture; 441 | dsti.cycle = true; 442 | SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(commands, NULL, 0, &dsti); 443 | if (!pass) 444 | { 445 | SDL_Log("Failed to begin render pass: %s", SDL_GetError()); 446 | return; 447 | } 448 | pipeline_bind(pass, PIPELINE_SHADOW); 449 | SDL_PushGPUVertexUniformData(commands, 1, shadow_camera.matrix, 64); 450 | world_render(NULL, commands, pass, CHUNK_TYPE_OPAQUE); 451 | SDL_EndGPURenderPass(pass); 452 | } 453 | 454 | static void draw_opaque() 455 | { 456 | SDL_GPUColorTargetInfo cti[3] = {0}; 457 | cti[0].load_op = SDL_GPU_LOADOP_CLEAR; 458 | cti[0].store_op = SDL_GPU_STOREOP_STORE; 459 | cti[0].texture = position_texture; 460 | cti[0].cycle = true; 461 | cti[1].load_op = SDL_GPU_LOADOP_CLEAR; 462 | cti[1].store_op = SDL_GPU_STOREOP_STORE; 463 | cti[1].texture = uv_texture; 464 | cti[2].cycle = true; 465 | cti[2].load_op = SDL_GPU_LOADOP_DONT_CARE; 466 | cti[2].store_op = SDL_GPU_STOREOP_STORE; 467 | cti[2].texture = voxel_texture; 468 | cti[2].cycle = true; 469 | SDL_GPUDepthStencilTargetInfo dsti = {0}; 470 | dsti.clear_depth = 1.0f; 471 | dsti.load_op = SDL_GPU_LOADOP_CLEAR; 472 | dsti.store_op = SDL_GPU_STOREOP_STORE; 473 | dsti.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE; 474 | dsti.texture = depth_texture; 475 | dsti.cycle = true; 476 | SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(commands, cti, 3, &dsti); 477 | if (!pass) 478 | { 479 | SDL_Log("Failed to begin render pass: %s", SDL_GetError()); 480 | return; 481 | } 482 | SDL_GPUTextureSamplerBinding tsb = {0}; 483 | tsb.sampler = nearest_sampler; 484 | tsb.texture = atlas_texture; 485 | pipeline_bind(pass, PIPELINE_OPAQUE); 486 | SDL_BindGPUFragmentSamplers(pass, 0, &tsb, 1); 487 | SDL_PushGPUVertexUniformData(commands, 1, player_camera.view, 64); 488 | SDL_PushGPUVertexUniformData(commands, 2, player_camera.proj, 64); 489 | world_render(&player_camera, commands, pass, CHUNK_TYPE_OPAQUE); 490 | SDL_EndGPURenderPass(pass); 491 | } 492 | 493 | static void draw_ssao() 494 | { 495 | SDL_GPUColorTargetInfo cti = {0}; 496 | cti.load_op = SDL_GPU_LOADOP_CLEAR; 497 | cti.store_op = SDL_GPU_STOREOP_STORE; 498 | cti.texture = ssao_texture; 499 | cti.cycle = true; 500 | SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(commands, &cti, 1, NULL); 501 | if (!pass) 502 | { 503 | SDL_Log("Failed to begin render pass: %s", SDL_GetError()); 504 | return; 505 | } 506 | SDL_GPUTextureSamplerBinding tsb[4] = {0}; 507 | tsb[0].sampler = nearest_sampler; 508 | tsb[0].texture = position_texture; 509 | tsb[1].sampler = nearest_sampler; 510 | tsb[1].texture = uv_texture; 511 | tsb[2].sampler = nearest_sampler; 512 | tsb[2].texture = voxel_texture; 513 | tsb[3].sampler = nearest_sampler; 514 | tsb[3].texture = random_texture; 515 | pipeline_bind(pass, PIPELINE_SSAO); 516 | SDL_BindGPUFragmentSamplers(pass, 0, tsb, 4); 517 | SDL_DrawGPUPrimitives(pass, 4, 1, 0, 0); 518 | SDL_EndGPURenderPass(pass); 519 | } 520 | 521 | static void composite() 522 | { 523 | SDL_GPUColorTargetInfo cti = {0}; 524 | cti.load_op = SDL_GPU_LOADOP_LOAD; 525 | cti.store_op = SDL_GPU_STOREOP_STORE; 526 | cti.texture = composite_texture; 527 | SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(commands, &cti, 1, NULL); 528 | if (!pass) 529 | { 530 | SDL_Log("Failed to begin render pass: %s", SDL_GetError()); 531 | return; 532 | } 533 | float position[3]; 534 | float vector[3]; 535 | SDL_GPUTextureSamplerBinding tsb[6] = {0}; 536 | tsb[0].sampler = nearest_sampler; 537 | tsb[0].texture = atlas_texture; 538 | tsb[1].sampler = nearest_sampler; 539 | tsb[1].texture = position_texture; 540 | tsb[2].sampler = nearest_sampler; 541 | tsb[2].texture = uv_texture; 542 | tsb[3].sampler = nearest_sampler; 543 | tsb[3].texture = voxel_texture; 544 | tsb[4].sampler = linear_sampler; 545 | tsb[4].texture = shadow_texture; 546 | tsb[5].sampler = nearest_sampler; 547 | tsb[5].texture = ssao_texture; 548 | camera_get_position(&player_camera, &position[0], &position[1], &position[2]); 549 | camera_get_vector(&shadow_camera, &vector[0], &vector[1], &vector[2]); 550 | pipeline_bind(pass, PIPELINE_COMPOSITE); 551 | SDL_BindGPUFragmentSamplers(pass, 0, tsb, 6); 552 | SDL_PushGPUFragmentUniformData(commands, 0, position, 12); 553 | SDL_PushGPUFragmentUniformData(commands, 1, vector, 12); 554 | SDL_PushGPUFragmentUniformData(commands, 2, shadow_camera.matrix, 64); 555 | SDL_DrawGPUPrimitives(pass, 4, 1, 0, 0); 556 | SDL_EndGPURenderPass(pass); 557 | } 558 | 559 | static void draw_transparent() 560 | { 561 | SDL_GPUColorTargetInfo cti = {0}; 562 | cti.load_op = SDL_GPU_LOADOP_LOAD; 563 | cti.store_op = SDL_GPU_STOREOP_STORE; 564 | cti.texture = composite_texture; 565 | SDL_GPUDepthStencilTargetInfo dsti = {0}; 566 | dsti.load_op = SDL_GPU_LOADOP_LOAD; 567 | dsti.store_op = SDL_GPU_STOREOP_STORE; 568 | dsti.texture = depth_texture; 569 | SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(commands, &cti, 1, &dsti); 570 | if (!pass) 571 | { 572 | SDL_Log("Failed to begin render pass: %s", SDL_GetError()); 573 | return; 574 | } 575 | float position[3]; 576 | float vector[3]; 577 | SDL_GPUTextureSamplerBinding tsb[3] = {0}; 578 | tsb[0].sampler = nearest_sampler; 579 | tsb[0].texture = atlas_texture; 580 | tsb[1].sampler = linear_sampler; 581 | tsb[1].texture = shadow_texture; 582 | tsb[2].sampler = nearest_sampler; 583 | tsb[2].texture = position_texture; 584 | camera_get_position(&player_camera, &position[0], &position[1], &position[2]); 585 | camera_get_vector(&shadow_camera, &vector[0], &vector[1], &vector[2]); 586 | pipeline_bind(pass, PIPELINE_TRANSPARENT); 587 | SDL_PushGPUVertexUniformData(commands, 1, player_camera.matrix, 64); 588 | SDL_PushGPUVertexUniformData(commands, 2, position, 12); 589 | SDL_PushGPUVertexUniformData(commands, 3, shadow_camera.matrix, 64); 590 | SDL_PushGPUFragmentUniformData(commands, 0, vector, 12); 591 | SDL_PushGPUFragmentUniformData(commands, 1, position, 12); 592 | SDL_BindGPUFragmentSamplers(pass, 0, tsb, 3); 593 | world_render(&player_camera, commands, pass, CHUNK_TYPE_TRANSPARENT); 594 | SDL_EndGPURenderPass(pass); 595 | } 596 | 597 | static void draw_raycast() 598 | { 599 | float x, y, z; 600 | float a, b, c; 601 | camera_get_position(&player_camera, &x, &y, &z); 602 | camera_get_vector(&player_camera, &a, &b, &c); 603 | if (!raycast(&x, &y, &z, a, b, c, false)) 604 | { 605 | return; 606 | } 607 | SDL_GPUColorTargetInfo cti = {0}; 608 | cti.load_op = SDL_GPU_LOADOP_LOAD; 609 | cti.store_op = SDL_GPU_STOREOP_STORE; 610 | cti.texture = composite_texture; 611 | SDL_GPUDepthStencilTargetInfo dsti = {0}; 612 | dsti.load_op = SDL_GPU_LOADOP_LOAD; 613 | dsti.store_op = SDL_GPU_STOREOP_STORE; 614 | dsti.texture = depth_texture; 615 | SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(commands, &cti, 1, &dsti); 616 | if (!pass) 617 | { 618 | SDL_Log("Failed to begin render pass: %s", SDL_GetError()); 619 | return; 620 | } 621 | int32_t position[3] = { x, y, z }; 622 | SDL_GPUBufferBinding bb = {0}; 623 | bb.buffer = cube_vbo; 624 | pipeline_bind(pass, PIPELINE_RAYCAST); 625 | SDL_PushGPUVertexUniformData(commands, 0, player_camera.matrix, 64); 626 | SDL_PushGPUVertexUniformData(commands, 1, position, 12); 627 | SDL_BindGPUVertexBuffers(pass, 0, &bb, 1); 628 | SDL_DrawGPUPrimitives(pass, 36, 1, 0, 0); 629 | SDL_EndGPURenderPass(pass); 630 | } 631 | 632 | static void blit() 633 | { 634 | SDL_GPUBlitInfo blit = {0}; 635 | blit.source.x = 0; 636 | blit.source.y = 0; 637 | blit.source.w = render_width; 638 | blit.source.h = render_height; 639 | blit.source.texture = composite_texture; 640 | blit.destination.x = 0; 641 | blit.destination.y = 0; 642 | blit.destination.w = width; 643 | blit.destination.h = height; 644 | blit.destination.texture = color_texture; 645 | blit.load_op = SDL_GPU_LOADOP_CLEAR; 646 | blit.filter = SDL_GPU_FILTER_NEAREST; 647 | SDL_BlitGPUTexture(commands, &blit); 648 | } 649 | 650 | static void draw_ui() 651 | { 652 | SDL_GPUColorTargetInfo cti = {0}; 653 | cti.load_op = SDL_GPU_LOADOP_LOAD; 654 | cti.store_op = SDL_GPU_STOREOP_STORE; 655 | cti.texture = color_texture; 656 | SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(commands, &cti, 1, NULL); 657 | if (!pass) 658 | { 659 | SDL_Log("Failed to begin render pass: %s", SDL_GetError()); 660 | return; 661 | } 662 | int32_t viewport[2] = { width, height }; 663 | SDL_GPUTextureSamplerBinding tsb = {0}; 664 | tsb.sampler = nearest_sampler; 665 | tsb.texture = atlas_texture; 666 | pipeline_bind(pass, PIPELINE_UI); 667 | SDL_BindGPUFragmentSamplers(pass, 0, &tsb, 1); 668 | SDL_PushGPUFragmentUniformData(commands, 0, viewport, 8); 669 | SDL_PushGPUFragmentUniformData(commands, 1, blocks[selected], 4); 670 | SDL_DrawGPUPrimitives(pass, 4, 1, 0, 0); 671 | SDL_EndGPURenderPass(pass); 672 | } 673 | 674 | static void draw() 675 | { 676 | SDL_WaitForGPUSwapchain(device, window); 677 | commands = SDL_AcquireGPUCommandBuffer(device); 678 | if (!commands) 679 | { 680 | SDL_Log("Failed to acquire command buffer: %s", SDL_GetError()); 681 | return; 682 | } 683 | uint32_t w; 684 | uint32_t h; 685 | if (!SDL_AcquireGPUSwapchainTexture(commands, window, &color_texture, &w, &h)) 686 | { 687 | SDL_Log("Failed to aqcuire swapchain image: %s", SDL_GetError()); 688 | SDL_CancelGPUCommandBuffer(commands); 689 | return; 690 | } 691 | if (!color_texture || !w || !h) 692 | { 693 | SDL_SubmitGPUCommandBuffer(commands); 694 | return; 695 | } 696 | if (width != w || height != h) 697 | { 698 | const float ratio = (float) w / (float) h; 699 | render_width = RENDERER_SIZE; 700 | render_height = (float) RENDERER_SIZE / ratio; 701 | if (!resize_textures(render_width, render_height)) 702 | { 703 | SDL_Log("Failed to resize textures"); 704 | SDL_SubmitGPUCommandBuffer(commands); 705 | return; 706 | } 707 | camera_set_viewport(&player_camera, render_width, render_height); 708 | width = w; 709 | height = h; 710 | draw_random(); 711 | } 712 | camera_update(&player_camera); 713 | camera_update(&shadow_camera); 714 | { 715 | SDL_PushGPUDebugGroup(commands, "sky"); 716 | draw_sky(); 717 | SDL_PopGPUDebugGroup(commands); 718 | } 719 | { 720 | SDL_PushGPUDebugGroup(commands, "shadow"); 721 | draw_shadow(); 722 | SDL_PopGPUDebugGroup(commands); 723 | } 724 | { 725 | SDL_PushGPUDebugGroup(commands, "opaque"); 726 | draw_opaque(); 727 | SDL_PopGPUDebugGroup(commands); 728 | } 729 | { 730 | SDL_PushGPUDebugGroup(commands, "ssao"); 731 | draw_ssao(); 732 | SDL_PopGPUDebugGroup(commands); 733 | } 734 | { 735 | SDL_PushGPUDebugGroup(commands, "composite"); 736 | composite(); 737 | SDL_PopGPUDebugGroup(commands); 738 | } 739 | { 740 | SDL_PushGPUDebugGroup(commands, "transparent"); 741 | draw_transparent(); 742 | SDL_PopGPUDebugGroup(commands); 743 | } 744 | { 745 | SDL_PushGPUDebugGroup(commands, "raycast"); 746 | draw_raycast(); 747 | SDL_PopGPUDebugGroup(commands); 748 | } 749 | { 750 | SDL_PushGPUDebugGroup(commands, "blit"); 751 | blit(); 752 | SDL_PopGPUDebugGroup(commands); 753 | } 754 | { 755 | SDL_PushGPUDebugGroup(commands, "ui"); 756 | draw_ui(); 757 | SDL_PopGPUDebugGroup(commands); 758 | } 759 | SDL_SubmitGPUCommandBuffer(commands); 760 | } 761 | 762 | static bool poll() 763 | { 764 | SDL_Event event; 765 | while (SDL_PollEvent(&event)) 766 | { 767 | switch (event.type) 768 | { 769 | case SDL_EVENT_MOUSE_MOTION: 770 | if (SDL_GetWindowRelativeMouseMode(window)) 771 | { 772 | const float yaw = event.motion.xrel * PLAYER_SENSITIVITY; 773 | const float pitch = -event.motion.yrel * PLAYER_SENSITIVITY; 774 | camera_rotate(&player_camera, pitch, yaw); 775 | } 776 | break; 777 | case SDL_EVENT_MOUSE_BUTTON_DOWN: 778 | if (!SDL_GetWindowRelativeMouseMode(window)) 779 | { 780 | SDL_SetWindowRelativeMouseMode(window, true); 781 | break; 782 | } 783 | if (event.button.button & (INPUT_PLACE | INPUT_BREAK)) 784 | { 785 | bool previous = true; 786 | block_t block = selected; 787 | if (event.button.button == INPUT_BREAK) 788 | { 789 | previous = false; 790 | block = BLOCK_EMPTY; 791 | } 792 | float x, y, z; 793 | float a, b, c; 794 | camera_get_position(&player_camera, &x, &y, &z); 795 | camera_get_vector(&player_camera, &a, &b, &c); 796 | if (raycast(&x, &y, &z, a, b, c, previous) && y >= 1.0f) 797 | { 798 | world_set_block(x, y, z, block); 799 | } 800 | } 801 | break; 802 | case SDL_EVENT_KEY_DOWN: 803 | if (event.key.scancode == INPUT_PAUSE) 804 | { 805 | SDL_SetWindowRelativeMouseMode(window, false); 806 | SDL_SetWindowFullscreen(window, false); 807 | } 808 | else if (event.key.scancode == INPUT_BLOCK) 809 | { 810 | selected = (selected + 1) % BLOCK_COUNT; 811 | selected = clamp(selected, BLOCK_EMPTY + 1, BLOCK_COUNT - 1); 812 | } 813 | else if (event.key.scancode == INPUT_FULLSCREEN) 814 | { 815 | if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) 816 | { 817 | SDL_SetWindowFullscreen(window, false); 818 | SDL_SetWindowRelativeMouseMode(window, false); 819 | } 820 | else 821 | { 822 | SDL_SetWindowFullscreen(window, true); 823 | SDL_SetWindowRelativeMouseMode(window, true); 824 | } 825 | } 826 | break; 827 | case SDL_EVENT_QUIT: 828 | return false; 829 | } 830 | } 831 | return true; 832 | } 833 | 834 | static void move( 835 | const float dt) 836 | { 837 | float x = 0.0f; 838 | float y = 0.0f; 839 | float z = 0.0f; 840 | if (SDL_GetWindowRelativeMouseMode(window)) 841 | { 842 | float speed = PLAYER_SPEED; 843 | const bool* state = SDL_GetKeyboardState(NULL); 844 | x += state[INPUT_RIGHT]; 845 | x -= state[INPUT_LEFT]; 846 | y += state[INPUT_UP]; 847 | y -= state[INPUT_DOWN]; 848 | z += state[INPUT_FORWARD]; 849 | z -= state[INPUT_BACKWARD]; 850 | if (state[INPUT_FAST]) 851 | { 852 | speed *= 5.0f; 853 | } 854 | else if (state[INPUT_SLOW]) 855 | { 856 | speed /= 5.0f; 857 | } 858 | x *= speed * dt; 859 | y *= speed * dt; 860 | z *= speed * dt; 861 | camera_move(&player_camera, x, y, z); 862 | } 863 | camera_get_position(&player_camera, &x, &y, &z); 864 | int a = x; 865 | int c = z; 866 | a /= CHUNK_X; 867 | c /= CHUNK_Z; 868 | a *= CHUNK_X; 869 | c *= CHUNK_Z; 870 | camera_set_position(&shadow_camera, a, SHADOW_Y, c); 871 | } 872 | 873 | static void commit( 874 | const float dt) 875 | { 876 | cooldown += dt; 877 | if (cooldown < DATABASE_COOLDOWN) 878 | { 879 | return; 880 | } 881 | float x; 882 | float y; 883 | float z; 884 | float pitch; 885 | float yaw; 886 | camera_get_position(&player_camera, &x, &y, &z); 887 | camera_get_rotation(&player_camera, &pitch, &yaw); 888 | database_set_player(DATABASE_PLAYER, x, y, z, pitch, yaw); 889 | database_commit(); 890 | cooldown = 0.0f; 891 | } 892 | 893 | int main( 894 | int argc, 895 | char** argv) 896 | { 897 | SDL_SetAppMetadata(WINDOW_NAME, NULL, NULL); 898 | if (!SDL_Init(SDL_INIT_VIDEO)) 899 | { 900 | SDL_Log("Failed to initialize SDL: %s", SDL_GetError()); 901 | return EXIT_FAILURE; 902 | } 903 | window = SDL_CreateWindow(WINDOW_NAME, WINDOW_WIDTH, WINDOW_HEIGHT, 0); 904 | if (!window) 905 | { 906 | SDL_Log("Failed to create window: %s", SDL_GetError()); 907 | return EXIT_FAILURE; 908 | } 909 | device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, DEVICE_VALIDATION, NULL); 910 | if (!device) 911 | { 912 | SDL_Log("Failed to create device: %s", SDL_GetError()); 913 | return EXIT_FAILURE; 914 | } 915 | if (!SDL_ClaimWindowForGPUDevice(device, window)) 916 | { 917 | SDL_Log("Failed to create swapchain: %s", SDL_GetError()); 918 | return EXIT_FAILURE; 919 | } 920 | if (!pipeline_init(device, SDL_GetGPUSwapchainTextureFormat(device, window))) 921 | { 922 | SDL_Log("Failed to create pipelines"); 923 | return EXIT_FAILURE; 924 | } 925 | if (!create_atlas()) 926 | { 927 | SDL_Log("Failed to create atlas"); 928 | return EXIT_FAILURE; 929 | } 930 | if (!create_samplers()) 931 | { 932 | SDL_Log("Failed to create samplers"); 933 | return EXIT_FAILURE; 934 | } 935 | if (!create_textures()) 936 | { 937 | SDL_Log("Failed to create textures"); 938 | return EXIT_FAILURE; 939 | } 940 | if (!create_vbos()) 941 | { 942 | SDL_Log("Failed to create vbos"); 943 | return EXIT_FAILURE; 944 | } 945 | if (!database_init(DATABASE_PATH)) 946 | { 947 | SDL_Log("Failed to create database"); 948 | return EXIT_FAILURE; 949 | } 950 | if (!world_init(device)) 951 | { 952 | SDL_Log("Failed to create world"); 953 | return EXIT_FAILURE; 954 | } 955 | SDL_SetWindowResizable(window, true); 956 | SDL_FlashWindow(window, SDL_FLASH_BRIEFLY); 957 | SDL_Surface* icon = create_icon(WINDOW_ICON); 958 | SDL_SetWindowIcon(window, icon); 959 | SDL_DestroySurface(icon); 960 | SDL_GPUSwapchainComposition composition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR; 961 | SDL_GPUPresentMode mode = SDL_GPU_PRESENTMODE_VSYNC; 962 | if (SDL_WindowSupportsGPUPresentMode(device, window, SDL_GPU_PRESENTMODE_MAILBOX)) 963 | { 964 | mode = SDL_GPU_PRESENTMODE_MAILBOX; 965 | } 966 | SDL_SetGPUSwapchainParameters(device, window, composition, mode); 967 | float x; 968 | float y; 969 | float z; 970 | float pitch; 971 | float yaw; 972 | camera_init(&player_camera, CAMERA_TYPE_PERSPECTIVE); 973 | if (database_get_player(DATABASE_PLAYER, &x, &y, &z, &pitch, &yaw)) 974 | { 975 | camera_set_position(&player_camera, x, y, z); 976 | camera_set_rotation(&player_camera, pitch, yaw); 977 | } 978 | else 979 | { 980 | srand(time(NULL)); 981 | x = rand() % INT16_MAX; 982 | z = rand() % INT16_MAX; 983 | camera_set_position(&player_camera, x, PLAYER_Y, z); 984 | } 985 | camera_init(&shadow_camera, CAMERA_TYPE_ORTHO); 986 | camera_set_rotation(&shadow_camera, SHADOW_PITCH, SHADOW_YAW); 987 | move(0.0f); 988 | time1 = SDL_GetPerformanceCounter(); 989 | time2 = 0; 990 | while (true) 991 | { 992 | time2 = time1; 993 | time1 = SDL_GetPerformanceCounter(); 994 | const float frequency = SDL_GetPerformanceFrequency(); 995 | const float dt = (time1 - time2) * 1000.0f / frequency; 996 | if (!poll()) 997 | { 998 | break; 999 | } 1000 | move(dt); 1001 | camera_get_position(&player_camera, &x, &y, &z); 1002 | world_update(x, y, z); 1003 | draw(); 1004 | commit(dt); 1005 | } 1006 | world_free(); 1007 | commit(DATABASE_COOLDOWN); 1008 | database_free(); 1009 | pipeline_free(); 1010 | SDL_ReleaseGPUBuffer(device, cube_vbo); 1011 | SDL_ReleaseGPUTexture(device, depth_texture); 1012 | SDL_ReleaseGPUTexture(device, position_texture); 1013 | SDL_ReleaseGPUTexture(device, uv_texture); 1014 | SDL_ReleaseGPUTexture(device, voxel_texture); 1015 | SDL_ReleaseGPUTexture(device, ssao_texture); 1016 | SDL_ReleaseGPUTexture(device, random_texture); 1017 | SDL_ReleaseGPUTexture(device, composite_texture); 1018 | SDL_ReleaseGPUTexture(device, shadow_texture); 1019 | SDL_ReleaseGPUTexture(device, atlas_texture); 1020 | SDL_ReleaseGPUSampler(device, nearest_sampler); 1021 | SDL_ReleaseGPUSampler(device, linear_sampler); 1022 | SDL_DestroySurface(atlas_surface); 1023 | SDL_ReleaseWindowFromGPUDevice(device, window); 1024 | SDL_DestroyGPUDevice(device); 1025 | SDL_DestroyWindow(window); 1026 | SDL_Quit(); 1027 | return EXIT_SUCCESS; 1028 | } --------------------------------------------------------------------------------