├── vcpkg.json ├── lib ├── stb_image.cpp └── vk_mem_alloc.cpp ├── .github ├── screenshot1.png ├── screenshot2.png └── screenshot3.png ├── .gitmodules ├── .clang-format ├── polymer ├── version.h ├── render │ ├── vulkan.h │ ├── render_config.h │ ├── render_util.h │ ├── render_pass.h │ ├── chunk_renderer.h │ ├── texture.h │ ├── swapchain.h │ ├── font_renderer.h │ ├── render_pass.cpp │ ├── block_mesher.h │ ├── render.h │ └── render_util.cpp ├── input.h ├── unicode.h ├── asset │ ├── block_model_rotate.h │ ├── asset_system.h │ ├── unihex_font.h │ ├── asset_store.h │ ├── parsed_block_model.h │ ├── block_assets.h │ ├── asset_system.cpp │ └── unihex_font.cpp ├── zip_archive.h ├── polymer.h ├── packet_interpreter.h ├── ui │ ├── debug.h │ ├── chat_window.h │ └── chat_window.cpp ├── camera.h ├── CMakeLists.txt ├── util.h ├── world │ ├── dimension.h │ ├── world.h │ ├── chunk.h │ ├── dimension.cpp │ ├── block.h │ └── world.cpp ├── buffer.h ├── platform │ ├── platform.h │ ├── args.h │ └── unix │ │ └── unix_main.cpp ├── nbt.h ├── unicode.cpp ├── network_queue.h ├── bitset.h ├── zip_archive.cpp ├── gamestate.h ├── connection.h ├── types.h ├── memory.h ├── memory.cpp ├── hashmap.h ├── connection.cpp ├── protocol.cpp ├── protocol.h ├── polymer.cpp ├── nbt.cpp ├── network_queue.cpp └── util.cpp ├── compile_shaders.sh ├── compile_shaders.bat ├── CMakePresets.json ├── .gitignore ├── shaders ├── font_shader.frag ├── font_shader.vert ├── chunk_shader.frag └── chunk_shader.vert ├── CMakeLists.txt ├── LICENSE └── README.md /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "curl", 4 | "volk" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /lib/stb_image.cpp: -------------------------------------------------------------------------------- 1 | #define STB_IMAGE_IMPLEMENTATION 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /.github/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atxi/Polymer/HEAD/.github/screenshot1.png -------------------------------------------------------------------------------- /.github/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atxi/Polymer/HEAD/.github/screenshot2.png -------------------------------------------------------------------------------- /.github/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atxi/Polymer/HEAD/.github/screenshot3.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vcpkg"] 2 | path = vcpkg 3 | url = https://github.com/Microsoft/vcpkg.git 4 | -------------------------------------------------------------------------------- /lib/vk_mem_alloc.cpp: -------------------------------------------------------------------------------- 1 | #define VMA_IMPLEMENTATION 2 | 3 | #define VMA_STATIC_VULKAN_FUNCTIONS 0 4 | #include 5 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | Language: Cpp 3 | ColumnLimit: 120 4 | AllowShortFunctionsOnASingleLine: Empty 5 | AlwaysBreakTemplateDeclarations: true 6 | PointerAlignment: Left 7 | AllowShortIfStatementsOnASingleLine: true 8 | -------------------------------------------------------------------------------- /polymer/version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define POLYMER_VERSION "0.0.5" 4 | #define MINECRAFT_VERSION "1.21.4" 5 | #define MINECRAFT_VERSION_HASH "a3bcba436caa849622fd7e1e5b89489ed6c9ac63" 6 | 7 | #define BLOCKS_FILENAME "blocks-" MINECRAFT_VERSION ".json" 8 | -------------------------------------------------------------------------------- /polymer/render/vulkan.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_RENDER_VULKAN_H_ 2 | #define POLYMER_RENDER_VULKAN_H_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | #ifdef near 9 | #undef near 10 | #endif 11 | 12 | #ifdef far 13 | #undef far 14 | #endif 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /compile_shaders.sh: -------------------------------------------------------------------------------- 1 | glslangValidator -V shaders/chunk_shader.vert -o shaders/chunk_vert.spv 2 | glslangValidator -V shaders/chunk_shader.frag -o shaders/chunk_frag.spv 3 | glslangValidator -V shaders/font_shader.vert -o shaders/font_vert.spv 4 | glslangValidator -V shaders/font_shader.frag -o shaders/font_frag.spv 5 | -------------------------------------------------------------------------------- /polymer/input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace polymer { 4 | 5 | // TODO: Make this more advanced 6 | struct InputState { 7 | bool forward; 8 | bool backward; 9 | bool left; 10 | bool right; 11 | bool climb; 12 | bool fall; 13 | bool sprint; 14 | bool display_players; 15 | }; 16 | 17 | } // namespace polymer 18 | -------------------------------------------------------------------------------- /compile_shaders.bat: -------------------------------------------------------------------------------- 1 | %VULKAN_SDK%/Bin/glslc.exe shaders/chunk_shader.vert -o shaders/chunk_vert.spv 2 | %VULKAN_SDK%/Bin/glslc.exe shaders/chunk_shader.frag -o shaders/chunk_frag.spv 3 | %VULKAN_SDK%/Bin/glslc.exe shaders/font_shader.vert -o shaders/font_vert.spv 4 | %VULKAN_SDK%/Bin/glslc.exe shaders/font_shader.frag -o shaders/font_frag.spv 5 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 6, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 25, 6 | "patch": 1 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "clang", 11 | "generator": "Unix Makefiles" 12 | }, 13 | { 14 | "name": "msvc", 15 | "generator": "Visual Studio 17 2022" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /polymer/unicode.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_UNICODE_H_ 2 | #define POLYMER_UNICODE_H_ 3 | 4 | #include 5 | 6 | namespace polymer { 7 | 8 | struct MemoryArena; 9 | 10 | struct Unicode { 11 | static WString FromUTF8(MemoryArena& arena, const String& str); 12 | static String ToUTF8(MemoryArena& arena, const WString& wstr); 13 | }; 14 | 15 | } // namespace polymer 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /polymer/asset/block_model_rotate.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_ASSET_BLOCK_MODEL_ROTATE_H_ 2 | #define POLYMER_ASSET_BLOCK_MODEL_ROTATE_H_ 3 | 4 | #include 5 | 6 | namespace polymer { 7 | namespace asset { 8 | 9 | void RotateVariant(MemoryArena& perm_arena, world::BlockModel& model, const ParsedBlockModel& parsed_model, 10 | size_t element_start, size_t element_count, const Vector3i& rotation, bool uvlock); 11 | 12 | } // namespace asset 13 | } // namespace polymer 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.kdev4 2 | /.kdev_include_paths 3 | /coverage 4 | bench_results 5 | /extras/bazel_root/bazel-* 6 | .idea 7 | *.pyc 8 | /build* 9 | /test_package/build 10 | /cmake-build-debug 11 | /cmake-build-release 12 | /.vs 13 | /Debug 14 | /Release 15 | /*.sln 16 | /*.vcxproj 17 | /*.vcxproj.filters 18 | /CMakeSettings.json 19 | /*.vcxproj.user 20 | /out/build/x64-Debug 21 | /out/build/x64-Debug cxx-17 22 | /extras/bazel_usage_example/bazel-* 23 | /clang 24 | blocks*.json 25 | /x64 26 | *.spv 27 | *.jar 28 | *.hex 29 | vcpkg_installed 30 | out 31 | .vscode 32 | x64 33 | -------------------------------------------------------------------------------- /shaders/font_shader.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 1) uniform sampler2DArray texSampler; 5 | 6 | layout(location = 0) in vec2 fragTexCoord; 7 | layout(location = 1) flat in uint fragSheetIndex; 8 | layout(location = 2) in vec4 fragColorMod; 9 | 10 | layout(location = 0) out vec4 outColor; 11 | 12 | void main() { 13 | float value = texture(texSampler, vec3(fragTexCoord, fragSheetIndex)).r; 14 | 15 | outColor = fragColorMod; 16 | outColor.a *= value; 17 | 18 | if (outColor.a <= 0.1) { 19 | discard; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /polymer/zip_archive.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_ZIP_ARCHIVE_H_ 2 | #define POLYMER_ZIP_ARCHIVE_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace polymer { 8 | 9 | struct MemoryArena; 10 | 11 | struct ZipArchiveElement { 12 | char name[512]; 13 | }; 14 | 15 | struct ZipArchive { 16 | mz_zip_archive archive; 17 | 18 | bool Open(const char* path); 19 | bool OpenFromMemory(String contents); 20 | 21 | void Close(); 22 | 23 | char* ReadFile(MemoryArena* arena, const char* filename, size_t* size); 24 | ZipArchiveElement* ListFiles(MemoryArena* arena, const char* search, size_t* count); 25 | }; 26 | 27 | } // namespace polymer 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /polymer/polymer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace polymer { 11 | 12 | struct GameState; 13 | 14 | struct Polymer { 15 | MemoryArena& perm_arena; 16 | MemoryArena& trans_arena; 17 | Platform platform = {}; 18 | PolymerWindow window = nullptr; 19 | 20 | render::VulkanRenderer renderer; 21 | GameState* game = nullptr; 22 | 23 | polymer::LaunchArgs args; 24 | 25 | Polymer(MemoryArena& perm_arena, MemoryArena& trans_arena, int argc, char** argv); 26 | 27 | int Run(InputState* input); 28 | }; 29 | 30 | } // namespace polymer 31 | -------------------------------------------------------------------------------- /polymer/render/render_config.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_RENDER_RENDER_CONFIG_H_ 2 | #define POLYMER_RENDER_RENDER_CONFIG_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace polymer { 8 | namespace render { 9 | 10 | struct RenderConfig { 11 | VkPresentModeKHR desired_present_mode = VK_PRESENT_MODE_MAILBOX_KHR; 12 | VkSampleCountFlagBits desired_msaa_samples = VK_SAMPLE_COUNT_4_BIT; 13 | 14 | // This makes the multisampling much better, but has a high performance cost that scales with resolution. 15 | bool sample_shading = true; 16 | 17 | // Range: [1, 32]. This increases rendering and processing time by a lot. 18 | u8 view_distance = 16; 19 | }; 20 | 21 | } // namespace render 22 | } // namespace polymer 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /polymer/packet_interpreter.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_PACKET_INTERPRETER_H_ 2 | #define POLYMER_PACKET_INTERPRETER_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace polymer { 8 | 9 | struct GameState; 10 | 11 | struct PacketInterpreter { 12 | GameState* game; 13 | bool compression; 14 | 15 | RingBuffer inflate_buffer; 16 | 17 | PacketInterpreter(GameState* game); 18 | 19 | // Returns the packet interpreted count. 20 | size_t Interpret(); 21 | 22 | private: 23 | void InterpretStatus(RingBuffer* rb, u64 pkt_id, size_t pkt_size); 24 | void InterpretLogin(RingBuffer* rb, u64 pkt_id, size_t pkt_size); 25 | void InterpretConfiguration(RingBuffer* rb, u64 pkt_id, size_t pkt_size); 26 | void InterpretPlay(RingBuffer* rb, u64 pkt_id, size_t pkt_size); 27 | }; 28 | 29 | } // namespace polymer 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /polymer/render/render_util.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_RENDER_UTIL_H_ 2 | #define POLYMER_RENDER_UTIL_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace polymer { 10 | 11 | struct MemoryArena; 12 | 13 | namespace render { 14 | 15 | struct Mipmap { 16 | unsigned char* data; 17 | size_t dimension; 18 | 19 | Mipmap(unsigned char* data, size_t dimension) : data(data), dimension(dimension) {} 20 | 21 | int Sample(size_t x, size_t y, size_t color_offset) { 22 | return data[(y * dimension + x) * 4 + color_offset]; 23 | } 24 | 25 | u32 SampleFull(size_t x, size_t y) { 26 | return *(u32*)&data[(y * dimension + x) * 4]; 27 | } 28 | }; 29 | 30 | // Performs basic pixel averaging filter for generating mipmap. 31 | void BoxFilterMipmap(u8* previous, u8* data, size_t data_size, size_t dim, bool brighten_mipping); 32 | 33 | VkShaderModule CreateShaderModule(VkDevice device, String code); 34 | 35 | } // namespace render 36 | } // namespace polymer 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | if (NOT CMAKE_BUILD_TYPE) 4 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE) 5 | endif() 6 | 7 | if (WIN32) 8 | set(VCPKG_TARGET_TRIPLET x64-windows-static) 9 | elseif (UNIX) 10 | set(VCPKG_TARGET_TRIPLET x64-linux) 11 | endif() 12 | 13 | set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" 14 | CACHE STRING "Vcpkg toolchain file") 15 | 16 | project(polymer VERSION 0.0.5 LANGUAGES CXX) 17 | 18 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 19 | 20 | include(GNUInstallDirs) 21 | 22 | add_subdirectory(polymer) 23 | 24 | set(CPACK_PACKAGE_NAME "Polymer") 25 | set(CPACK_PACKAGE_VENDOR "atxi") 26 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Polymer - Clean-room Minecraft client implementation") 27 | set(CPACK_PACKAGE_INSTALL_DIRECTORY "Polymer") 28 | 29 | set(CMAKE_INSTALL_PREFIX ".") 30 | 31 | install(TARGETS polymer 32 | CONFIGURATIONS Debug 33 | RUNTIME DESTINATION Debug) 34 | 35 | install(TARGETS polymer 36 | CONFIGURATIONS Release 37 | RUNTIME DESTINATION Release) 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 atxi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /polymer/asset/asset_system.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_ASSET_SYSTEM_H_ 2 | #define POLYMER_ASSET_SYSTEM_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace polymer { 13 | namespace render { 14 | 15 | struct VulkanRenderer; 16 | struct VulkanTexture; 17 | 18 | } // namespace render 19 | 20 | namespace asset { 21 | 22 | struct AssetSystem { 23 | MemoryArena perm_arena; 24 | BlockAssets* block_assets = nullptr; 25 | render::VulkanTexture* glyph_page_texture = nullptr; 26 | u8* glyph_size_table = nullptr; 27 | AssetStore* asset_store = nullptr; 28 | 29 | AssetSystem(); 30 | 31 | bool Load(render::VulkanRenderer& renderer, const char* jar_path, const char* blocks_path, 32 | world::BlockRegistry* registry); 33 | 34 | bool LoadFont(render::VulkanRenderer& renderer, MemoryArena& perm_arena, MemoryArena& trans_arena); 35 | 36 | BlockTextureDescriptor GetTextureRange(const String& texture_path); 37 | }; 38 | 39 | } // namespace asset 40 | } // namespace polymer 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /polymer/ui/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_DEBUG_H_ 2 | #define POLYMER_DEBUG_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace polymer { 11 | namespace ui { 12 | 13 | enum class DebugTextAlignment { Left, Right, Center }; 14 | 15 | struct DebugTextSystem { 16 | render::FontRenderer& font_renderer; 17 | Vector2f position; 18 | Vector4f color; 19 | DebugTextAlignment alignment; 20 | 21 | DebugTextSystem(render::FontRenderer& renderer) : font_renderer(renderer) {} 22 | 23 | void Write(const char* fmt, ...) { 24 | char buffer[2048]; 25 | 26 | va_list args; 27 | 28 | va_start(args, fmt); 29 | #ifdef _WIN32 30 | size_t size = vsprintf_s(buffer, fmt, args); 31 | #else 32 | size_t size = vsprintf(buffer, fmt, args); 33 | #endif 34 | va_end(args); 35 | 36 | render::FontStyleFlags style = render::FontStyle_Background | render::FontStyle_DropShadow; 37 | 38 | font_renderer.RenderText(Vector3f(position.x, position.y, 0), String(buffer, size), style, color); 39 | position.y += 16; 40 | } 41 | }; 42 | 43 | } // namespace ui 44 | } // namespace polymer 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /shaders/font_shader.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform UniformBufferObject { 5 | mat4 mvp; 6 | } ubo; 7 | 8 | layout(location = 0) in vec3 inPosition; 9 | layout(location = 1) in uint inRGBA; 10 | layout(location = 2) in uint inGlyphId; 11 | layout(location = 3) in uint inUV; 12 | 13 | layout(location = 0) out vec2 fragTexCoord; 14 | layout(location = 1) flat out uint fragSheetIndex; 15 | layout(location = 2) out vec4 fragColorMod; 16 | 17 | void main() { 18 | gl_Position = ubo.mvp * vec4(inPosition, 1.0); 19 | 20 | fragSheetIndex = inGlyphId / 256; 21 | 22 | uint index_in_sheet = inGlyphId - (fragSheetIndex * 256); 23 | float glyph_x = (index_in_sheet % 16) / 16.0; 24 | float glyph_y = (index_in_sheet / 16) / 16.0; 25 | 26 | uint uv_x = inUV >> 1; 27 | uint uv_y = (inUV & 1) * 16; 28 | vec2 uv = vec2(uv_x / 256.0, uv_y / 256.0); 29 | 30 | fragTexCoord = vec2(glyph_x, glyph_y) + uv; 31 | 32 | uint alpha = (inRGBA >> 24) & 0xFF; 33 | uint blue = (inRGBA >> 16) & 0xFF; 34 | uint green = (inRGBA >> 8) & 0xFF; 35 | uint red = (inRGBA) & 0xFF; 36 | 37 | fragColorMod = vec4(red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0); 38 | } 39 | -------------------------------------------------------------------------------- /polymer/asset/unihex_font.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_ASSET_UNIHEX_FONT_H_ 2 | #define POLYMER_ASSET_UNIHEX_FONT_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace polymer { 8 | namespace asset { 9 | 10 | struct UnihexFont { 11 | enum class ReadState { Codepoint, Data }; 12 | 13 | char* unifont_data = nullptr; 14 | char* unifont_end = nullptr; 15 | u8* images = nullptr; 16 | 17 | u8* glyph_size_table; 18 | size_t glyph_page_width; 19 | size_t glyph_page_height; 20 | size_t glyph_page_count; 21 | 22 | String codepoint_str; 23 | String data_str; 24 | u32 codepoint = 0; 25 | ReadState state = ReadState::Codepoint; 26 | 27 | UnihexFont(u8* glyph_size_table, size_t glyph_page_width, size_t glyph_page_height, size_t glyph_page_count) 28 | : glyph_size_table(glyph_size_table), glyph_page_width(glyph_page_width), glyph_page_height(glyph_page_height), 29 | glyph_page_count(glyph_page_count) {} 30 | 31 | bool Load(const char* filename, MemoryArena& perm_arena, MemoryArena& trans_arena); 32 | bool Load(MemoryArena& perm_arena, MemoryArena& trans_arena, String file_data); 33 | 34 | private: 35 | bool ProcessCodepoint(char c); 36 | 37 | bool ProcessData(char c); 38 | }; 39 | 40 | } // namespace asset 41 | } // namespace polymer 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /polymer/camera.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_CAMERA_H_ 2 | #define POLYMER_CAMERA_H_ 3 | 4 | #include 5 | 6 | namespace polymer { 7 | 8 | struct Camera { 9 | Vector3f position; 10 | float yaw; 11 | float pitch; 12 | 13 | float fov; 14 | float aspect_ratio; 15 | float near; 16 | float far; 17 | 18 | inline Vector3f GetForward() const { 19 | return Vector3f(cosf(yaw) * cosf(pitch), sinf(pitch), sinf(yaw) * cosf(pitch)); 20 | } 21 | 22 | inline mat4 GetViewMatrix() const { 23 | static const Vector3f kWorldUp(0, 1, 0); 24 | 25 | Vector3f front(cosf(yaw) * cosf(pitch), sinf(pitch), sinf(yaw) * cosf(pitch)); 26 | 27 | return LookAt(Vector3f(0, 0, 0), front, kWorldUp); 28 | } 29 | 30 | inline mat4 GetProjectionMatrix() const { 31 | return Perspective(fov, aspect_ratio, near, far); 32 | } 33 | 34 | inline Frustum GetViewFrustum() const { 35 | static const Vector3f kWorldUp(0, 1, 0); 36 | 37 | Vector3f front(cosf(yaw) * cosf(pitch), sinf(pitch), sinf(yaw) * cosf(pitch)); 38 | 39 | Vector3f forward = Normalize(front); 40 | Vector3f side = Normalize(forward.Cross(kWorldUp)); 41 | Vector3f up = Normalize(side.Cross(forward)); 42 | 43 | return Frustum(position, forward, near, far, fov, aspect_ratio, up, side); 44 | } 45 | }; 46 | 47 | } // namespace polymer 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /polymer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | file(GLOB_RECURSE SOURCES ${PROJECT_SOURCE_DIR}/lib/*.cpp *.cpp) 4 | 5 | find_package(volk REQUIRED) 6 | find_package(CURL REQUIRED) 7 | 8 | list(FILTER SOURCES EXCLUDE REGEX "/platform/") 9 | 10 | if (UNIX) 11 | find_package(glfw3 CONFIG REQUIRED) 12 | 13 | file(GLOB_RECURSE UNIX_SOURCES ${PROJECT_SOURCE_DIR}/polymer/platform/unix/*.cpp) 14 | list(APPEND SOURCES ${UNIX_SOURCES}) 15 | 16 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 17 | elseif(WIN32) 18 | file(GLOB_RECURSE WIN32_SOURCES ${PROJECT_SOURCE_DIR}/polymer/platform/win32/*.cpp) 19 | list(APPEND SOURCES ${WIN32_SOURCES}) 20 | 21 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 22 | endif() 23 | 24 | add_definitions(-DUNICODE) 25 | add_executable(polymer ${SOURCES}) 26 | include_directories(polymer PRIVATE ..) 27 | 28 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 29 | # Ignore VMA nullability warnings 30 | set(CMAKE_CXX_FLAGS "${CMAKE_CPP_FLAGS} -Wno-nullability-completeness") 31 | endif() 32 | 33 | if (UNIX) 34 | target_link_libraries(polymer PRIVATE glfw) 35 | elseif (WIN32) 36 | add_compile_definitions(WIN32_LEAN_AND_MEAN VK_USE_PLATFORM_WIN32_KHR NOMINMAX) 37 | endif() 38 | 39 | target_include_directories(polymer PRIVATE ${CURL_INCLUDE_DIRS}) 40 | target_link_libraries(polymer PRIVATE volk::volk_headers CURL::libcurl) 41 | -------------------------------------------------------------------------------- /polymer/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace polymer { 9 | 10 | struct MemoryArena; 11 | 12 | String ReadEntireFile(const char* filename, MemoryArena& arena); 13 | 14 | // Creates all the necessary folders and opens a FILE handle. 15 | FILE* CreateAndOpenFile(String filename, const char* mode); 16 | // Creates all the necessary folders and opens a FILE handle. 17 | FILE* CreateAndOpenFile(const char* filename, const char* mode); 18 | 19 | struct HashSha1 { 20 | u8 hash[20] = {}; 21 | 22 | HashSha1() {} 23 | 24 | HashSha1(const char* hex, size_t len) { 25 | char temp[3] = {}; 26 | 27 | for (size_t i = 0; i < len; i += 2) { 28 | temp[0] = hex[i]; 29 | temp[1] = hex[i + 1]; 30 | u8 value = (u8)strtol(temp, nullptr, 16); 31 | 32 | hash[i / 2] = value; 33 | } 34 | } 35 | 36 | HashSha1(const char* hex) : HashSha1(hex, strlen(hex)) {} 37 | 38 | bool operator==(const HashSha1& other) const { 39 | return memcmp(hash, other.hash, sizeof(hash)) == 0; 40 | } 41 | 42 | bool operator!=(const HashSha1& other) const { 43 | return !(*this == other); 44 | } 45 | 46 | void ToString(char* out) { 47 | for (size_t i = 0; i < sizeof(hash); ++i) { 48 | sprintf(out + i * 2, "%02x", (int)hash[i]); 49 | } 50 | 51 | out[40] = 0; 52 | } 53 | }; 54 | 55 | HashSha1 Sha1(const String& contents); 56 | 57 | } // namespace polymer 58 | -------------------------------------------------------------------------------- /polymer/world/dimension.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_DIMENSION_H_ 2 | #define POLYMER_DIMENSION_H_ 3 | 4 | #include 5 | 6 | namespace polymer { 7 | 8 | struct MemoryArena; 9 | 10 | namespace nbt { 11 | 12 | struct TagCompound; 13 | 14 | } // namespace nbt 15 | 16 | namespace world { 17 | 18 | enum DimensionFlags { 19 | DimensionFlag_PiglinSafe = (1 << 0), 20 | DimensionFlag_Natural = (1 << 1), 21 | DimensionFlag_RespawnAnchor = (1 << 2), 22 | DimensionFlag_HasSkylight = (1 << 3), 23 | DimensionFlag_BedWorks = (1 << 4), 24 | DimensionFlag_HasRaids = (1 << 5), 25 | DimensionFlag_Ultrawarm = (1 << 6), 26 | DimensionFlag_HasCeiling = (1 << 7), 27 | }; 28 | 29 | struct DimensionType { 30 | String name; 31 | String infiniburn; 32 | String effects; 33 | 34 | s32 id; 35 | u32 flags; 36 | 37 | s32 min_y; 38 | s32 height; 39 | 40 | s32 logical_height; 41 | float ambient_light; 42 | 43 | double coordinate_scale; 44 | 45 | u64 fixed_time; 46 | }; 47 | 48 | struct DimensionCodec { 49 | DimensionType* types; 50 | size_t type_count; 51 | 52 | void Initialize(MemoryArena& arena, size_t size); 53 | 54 | void ParseType(MemoryArena& arena, nbt::TagCompound& nbt, DimensionType& type); 55 | void ParseDefaultType(MemoryArena& arena, size_t index); 56 | 57 | DimensionType* GetDimensionTypeById(s32 identifier); 58 | DimensionType* GetDimensionTypeByName(const String& identifier); 59 | }; 60 | 61 | } // namespace world 62 | } // namespace polymer 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /polymer/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_BUFFER_H_ 2 | #define POLYMER_BUFFER_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace polymer { 8 | 9 | // Simple circular buffer where the read and write methods assume there's space to operate 10 | // The only method that checks for read/write cursor wrapping is ReadVarInt. 11 | // This could be simplified greatly by using virtual memory wrapping. 12 | struct RingBuffer { 13 | size_t read_offset; 14 | size_t write_offset; 15 | 16 | size_t size; 17 | u8* data; 18 | 19 | RingBuffer(MemoryArena& arena, size_t size); 20 | 21 | void WriteU8(u8 value); 22 | void WriteU16(u16 value); 23 | void WriteU32(u32 value); 24 | void WriteU64(u64 value); 25 | void WriteVarInt(u64 value); 26 | void WriteFloat(float value); 27 | void WriteDouble(double value); 28 | void WriteString(const char* str, size_t size); 29 | void WriteString(const String& str); 30 | void WriteRawString(const String& str); 31 | void WriteRawString(const char* str, size_t size); 32 | 33 | u8 ReadU8(); 34 | u16 ReadU16(); 35 | u32 ReadU32(); 36 | u64 ReadU64(); 37 | bool ReadVarInt(u64* value); 38 | float ReadFloat(); 39 | double ReadDouble(); 40 | // Allocates a string from the arena according to the length received from the buffer. 41 | String ReadAllocString(MemoryArena& arena); 42 | String ReadAllocRawString(MemoryArena& arena, size_t size); 43 | size_t ReadString(String* str); 44 | void ReadRawString(String* str, size_t size); 45 | 46 | size_t GetFreeSize() const; 47 | size_t GetReadAmount() const; 48 | }; 49 | 50 | size_t GetVarIntSize(u64 value); 51 | 52 | } // namespace polymer 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /polymer/render/render_pass.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_RENDER_RENDER_PASS_H_ 2 | #define POLYMER_RENDER_RENDER_PASS_H_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace polymer { 11 | namespace render { 12 | 13 | struct RenderPass { 14 | VkRenderPass render_pass; 15 | FramebufferSet framebuffers; 16 | 17 | bool valid = false; 18 | 19 | void Create(Swapchain& swapchain, VkRenderPassCreateInfo* create_info); 20 | void CreateSimple(Swapchain& swapchain, VkAttachmentDescription color, VkAttachmentDescription depth, 21 | VkAttachmentDescription color_resolve); 22 | 23 | void Destroy(Swapchain& swapchain); 24 | 25 | inline void BeginPass(VkCommandBuffer buffer, VkExtent2D extent, size_t index, VkClearValue* clears, 26 | size_t clear_count) { 27 | assert(index < framebuffers.count); 28 | assert(valid); 29 | 30 | VkRenderPassBeginInfo render_pass_info = {}; 31 | 32 | render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; 33 | render_pass_info.framebuffer = framebuffers.framebuffers[index]; 34 | render_pass_info.renderArea.offset = {0, 0}; 35 | render_pass_info.renderArea.extent = extent; 36 | render_pass_info.renderPass = render_pass; 37 | render_pass_info.clearValueCount = (u32)clear_count; 38 | render_pass_info.pClearValues = clears; 39 | 40 | vkCmdBeginRenderPass(buffer, &render_pass_info, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS); 41 | } 42 | 43 | inline void EndPass(VkCommandBuffer buffer) { 44 | vkCmdEndRenderPass(buffer); 45 | } 46 | }; 47 | 48 | } // namespace render 49 | } // namespace polymer 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /polymer/platform/platform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace polymer { 6 | 7 | using PolymerWindow = void*; 8 | 9 | struct MemoryArena; 10 | 11 | struct ExtensionRequest { 12 | const char** extensions; 13 | u32 extension_count = 0; 14 | 15 | const char** device_extensions; 16 | u32 device_extension_count = 0; 17 | 18 | const char** validation_layers; 19 | u32 validation_layer_count = 0; 20 | }; 21 | 22 | using PlatformGetPlatformName = const char* (*)(); 23 | 24 | using PlatformWindowCreate = PolymerWindow (*)(int width, int height); 25 | using PlatformWindowCreateSurface = bool (*)(PolymerWindow window, void* surface); 26 | using PlatformWindowGetRect = IntRect (*)(PolymerWindow window); 27 | using PlatformWindowPump = void (*)(PolymerWindow window); 28 | 29 | using PlatformGetExtensionRequest = ExtensionRequest (*)(); 30 | 31 | using PlatformGetAssetStorePath = String (*)(MemoryArena& arena); 32 | 33 | using PlatformFolderExists = bool (*)(const char* path); 34 | using PlatformCreateFolder = bool (*)(const char* path); 35 | 36 | using PlatformAllocate = u8* (*)(size_t size); 37 | using PlatformFree = void (*)(u8* ptr); 38 | 39 | struct Platform { 40 | PlatformGetPlatformName GetPlatformName; 41 | 42 | PlatformWindowCreate WindowCreate; 43 | PlatformWindowCreateSurface WindowCreateSurface; 44 | PlatformWindowGetRect WindowGetRect; 45 | PlatformWindowPump WindowPump; 46 | 47 | PlatformGetExtensionRequest GetExtensionRequest; 48 | 49 | PlatformGetAssetStorePath GetAssetStorePath; 50 | PlatformFolderExists FolderExists; 51 | PlatformCreateFolder CreateFolder; 52 | 53 | PlatformAllocate Allocate; 54 | PlatformFree Free; 55 | }; 56 | extern Platform g_Platform; 57 | 58 | } // namespace polymer 59 | -------------------------------------------------------------------------------- /polymer/nbt.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_NBT_H_ 2 | #define POLYMER_NBT_H_ 3 | 4 | #include 5 | 6 | namespace polymer { 7 | 8 | struct MemoryArena; 9 | struct RingBuffer; 10 | 11 | namespace nbt { 12 | 13 | enum class TagType : u8 { 14 | End = 0, 15 | Byte, 16 | Short, 17 | Int, 18 | Long, 19 | Float, 20 | Double, 21 | ByteArray, 22 | String, 23 | List, 24 | Compound, 25 | IntArray, 26 | LongArray, 27 | 28 | Unknown = 0xFF 29 | }; 30 | 31 | struct Tag { 32 | void* tag; 33 | char* name; 34 | size_t name_length; 35 | TagType type; 36 | }; 37 | 38 | constexpr size_t kMaxTags = 1024; 39 | 40 | struct TagCompound { 41 | Tag tags[kMaxTags]; 42 | size_t ntags; 43 | 44 | char* name; 45 | size_t name_length; 46 | 47 | Tag* GetNamedTag(const String& str); 48 | }; 49 | 50 | struct TagByte { 51 | u8 data; 52 | }; 53 | 54 | struct TagShort { 55 | u16 data; 56 | }; 57 | 58 | struct TagInt { 59 | u32 data; 60 | }; 61 | 62 | struct TagLong { 63 | u64 data; 64 | }; 65 | 66 | struct TagFloat { 67 | float data; 68 | }; 69 | 70 | struct TagDouble { 71 | double data; 72 | }; 73 | 74 | struct TagByteArray { 75 | s8* data; 76 | size_t length; 77 | }; 78 | 79 | struct TagString { 80 | char* data; 81 | size_t length; 82 | }; 83 | 84 | struct TagList { 85 | TagType type; 86 | size_t length; 87 | Tag* tags; 88 | }; 89 | 90 | struct TagIntArray { 91 | s32* data; 92 | size_t length; 93 | }; 94 | 95 | struct TagLongArray { 96 | s64* data; 97 | size_t length; 98 | }; 99 | 100 | bool Parse(bool network_nbt, RingBuffer& rb, MemoryArena& arena, TagCompound* result); 101 | 102 | } // namespace nbt 103 | } // namespace polymer 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /polymer/unicode.cpp: -------------------------------------------------------------------------------- 1 | #define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | std::wstring_convert> converter; 13 | 14 | namespace polymer { 15 | 16 | WString Unicode::FromUTF8(MemoryArena& arena, const String& str) { 17 | WString result = {}; 18 | 19 | char* utf8 = memory_arena_push_type_count(&arena, char, str.size + 1); 20 | memcpy(utf8, str.data, str.size); 21 | utf8[str.size] = 0; 22 | 23 | std::wstring utf16 = converter.from_bytes(utf8); 24 | 25 | result.length = utf16.length(); 26 | result.data = memory_arena_push_type_count(&arena, wchar, result.length); 27 | 28 | #ifdef _WIN32 29 | for (size_t i = 0; i < result.length; ++i) { 30 | wchar c = (wchar)utf16[i]; 31 | result.data[i] = c; 32 | } 33 | #else 34 | memcpy(result.data, utf16.data(), result.length * sizeof(wchar_t)); 35 | #endif 36 | 37 | return result; 38 | } 39 | 40 | String Unicode::ToUTF8(MemoryArena& arena, const WString& wstr) { 41 | String result = {}; 42 | 43 | wchar_t* wide_data = (wchar_t*)memory_arena_push_type_count(&arena, wchar_t, wstr.length + 1); 44 | size_t wide_data_size = wstr.length * sizeof(wchar_t); 45 | 46 | #ifdef _WIN32 47 | for (size_t i = 0; i < wstr.length; ++i) { 48 | wchar_t c = (wchar_t)wstr.data[i]; 49 | wide_data[i] = c; 50 | } 51 | #else 52 | memcpy((void*)wide_data, (void*)wstr.data, wide_data_size); 53 | #endif 54 | wide_data[wstr.length] = 0; 55 | 56 | std::string utf8 = converter.to_bytes(wide_data); 57 | 58 | result.size = utf8.size(); 59 | result.data = memory_arena_push_type_count(&arena, char, result.size); 60 | 61 | memcpy(result.data, utf8.data(), utf8.size()); 62 | 63 | return result; 64 | } 65 | 66 | } // namespace polymer 67 | -------------------------------------------------------------------------------- /polymer/network_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace polymer { 6 | 7 | struct NetworkChunk { 8 | struct NetworkChunk* next; 9 | u8 data[2048]; 10 | size_t size; 11 | }; 12 | 13 | struct NetworkResponse { 14 | int http_code; 15 | int transfer_code; 16 | 17 | size_t size; 18 | NetworkChunk* chunks = nullptr; 19 | 20 | void SaveToFile(const char* filename); 21 | void SaveToFile(String filename); 22 | }; 23 | 24 | using NetworkCompleteCallback = void (*)(struct NetworkRequest* request, NetworkResponse* response); 25 | 26 | struct NetworkRequest { 27 | struct NetworkRequest* next; 28 | 29 | char url[2048]; 30 | void* userp; 31 | NetworkCompleteCallback callback; 32 | }; 33 | 34 | struct NetworkActiveRequest { 35 | struct NetworkQueue* queue = nullptr; 36 | NetworkRequest* request = nullptr; 37 | bool active = false; 38 | size_t size = 0; 39 | 40 | NetworkChunk* chunks = nullptr; 41 | NetworkChunk* last_chunk = nullptr; 42 | }; 43 | 44 | struct NetworkQueue { 45 | constexpr static size_t kParallelRequests = 10; 46 | 47 | bool Initialize(); 48 | 49 | NetworkRequest* PushRequest(const char* url, void* userp, NetworkCompleteCallback callback); 50 | NetworkRequest* PushRequest(String url, void* userp, NetworkCompleteCallback callback); 51 | 52 | void Run(); 53 | void Clear(); 54 | bool IsEmpty() const; 55 | 56 | NetworkChunk* AllocateChunk(); 57 | void FreeChunk(NetworkChunk* chunk); 58 | 59 | private: 60 | void ProcessWaitingQueue(); 61 | 62 | NetworkRequest* AllocateRequest(); 63 | 64 | void* curl_multi; 65 | NetworkActiveRequest active_requests[kParallelRequests]; 66 | 67 | int active_request_count = 0; 68 | NetworkRequest* waiting_queue = nullptr; 69 | NetworkRequest* waiting_queue_end = nullptr; 70 | 71 | NetworkRequest* free = nullptr; 72 | NetworkChunk* free_chunks = nullptr; 73 | }; 74 | 75 | } // namespace polymer 76 | -------------------------------------------------------------------------------- /polymer/asset/asset_store.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | struct json_object_s; 14 | 15 | namespace polymer { 16 | namespace asset { 17 | 18 | enum class AssetType : u8 { VersionDescriptor, Index, Object, Client }; 19 | 20 | struct AssetInfo { 21 | String name; 22 | HashSha1 hash; 23 | AssetType type; 24 | }; 25 | 26 | // Keeps a local asset store synchronized with the remote store by using the index. 27 | // The store begins by checking if the local index exists, if it doesn't, then it kicks it off to the network queue. 28 | // When the netqueue finishes downloading, it will callback to the store to continue processing the index and kicking 29 | // off any missing assets in the index to the netqueue. 30 | // 31 | // The netqueue will need to be completely empty before assets are considered fully downloaded. 32 | struct AssetStore { 33 | Platform& platform; 34 | MemoryArena& perm_arena; 35 | MemoryArena& trans_arena; 36 | NetworkQueue& net_queue; 37 | HashMap asset_hash_map; 38 | String path; 39 | 40 | AssetStore(Platform& platform, MemoryArena& perm_arena, MemoryArena& trans_arena, NetworkQueue& net_queue) 41 | : platform(platform), perm_arena(perm_arena), trans_arena(trans_arena), asset_hash_map(perm_arena), 42 | net_queue(net_queue) { 43 | path = platform.GetAssetStorePath(trans_arena); 44 | } 45 | 46 | void Initialize(); 47 | 48 | bool HasAsset(AssetInfo& info); 49 | 50 | char* GetClientPath(MemoryArena& arena); 51 | 52 | String LoadObject(MemoryArena& arena, String name); 53 | 54 | private: 55 | void ProcessVersionDescriptor(const char* path); 56 | void ProcessIndex(const char* path); 57 | }; 58 | 59 | } // namespace asset 60 | } // namespace polymer 61 | -------------------------------------------------------------------------------- /shaders/chunk_shader.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform UniformBufferObject { 5 | mat4 mvp; 6 | vec4 camera; 7 | uint frame; 8 | float sunlight; 9 | uint alpha_discard; 10 | } ubo; 11 | 12 | layout(binding = 1) uniform sampler2DArray texSampler; 13 | 14 | layout(location = 0) in vec2 fragTexCoord; 15 | layout(location = 1) flat in uint fragTexId; 16 | layout(location = 2) in vec4 fragColorMod; 17 | layout(location = 3) flat in uint fragTexIdInterpolate; 18 | layout(location = 4) flat in float interpolate_t; 19 | 20 | layout(location = 0) out vec4 outColor; 21 | 22 | vec3 rgb2hsv(vec3 c) { 23 | vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); 24 | vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); 25 | vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); 26 | 27 | float d = q.x - min(q.w, q.y); 28 | float e = 1.0e-10; 29 | return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); 30 | } 31 | 32 | vec3 hsv2rgb(vec3 c) { 33 | vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); 34 | vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); 35 | return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); 36 | } 37 | 38 | // TODO: Move to post-processing and figure out the actual post effects used. 39 | const float saturation = 1.0; 40 | const float brightness = 1.0; 41 | 42 | void main() { 43 | vec4 diffuse = texture(texSampler, vec3(fragTexCoord, fragTexId)); 44 | if (fragTexIdInterpolate != 0xFFFFFFFF) { 45 | vec4 next_diffuse = texture(texSampler, vec3(fragTexCoord, fragTexIdInterpolate)); 46 | diffuse = mix(diffuse, next_diffuse, interpolate_t); 47 | } 48 | 49 | outColor = diffuse * fragColorMod; 50 | 51 | if (ubo.alpha_discard > 0 && outColor.a <= 0.6) { 52 | discard; 53 | } 54 | 55 | vec3 hsv = rgb2hsv(outColor.xyz); 56 | 57 | hsv.y *= saturation; 58 | hsv.z *= brightness; 59 | 60 | outColor.xyz = hsv2rgb(hsv); 61 | } 62 | -------------------------------------------------------------------------------- /polymer/asset/parsed_block_model.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_ASSET_PARSED_BLOCK_MODEL_H_ 2 | #define POLYMER_ASSET_PARSED_BLOCK_MODEL_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct json_object_s; 10 | 11 | namespace polymer { 12 | 13 | struct MemoryArena; 14 | 15 | namespace asset { 16 | 17 | struct ParsedTextureName { 18 | char name[64]; 19 | char value[64]; 20 | 21 | ParsedTextureName* next; 22 | }; 23 | 24 | struct ParsedRenderableFace { 25 | Vector2f uv_from; 26 | Vector2f uv_to; 27 | 28 | int rotation; 29 | 30 | char texture_name[64]; 31 | size_t texture_name_size; 32 | 33 | u32 texture_id; 34 | u32 frame_count; 35 | 36 | struct { 37 | u32 custom_uv : 1; 38 | u32 render : 1; 39 | u32 transparency : 1; 40 | u32 cullface : 3; 41 | u32 render_layer : 3; 42 | u32 random_flip : 1; 43 | u32 padding : 6; 44 | u32 tintindex : 6; 45 | u32 frametime : 9; 46 | u32 interpolated : 1; 47 | }; 48 | }; 49 | 50 | struct ParsedBlockElement { 51 | ParsedRenderableFace faces[6]; 52 | Vector3f from; 53 | Vector3f to; 54 | 55 | world::ElementRotation rotation; 56 | 57 | struct { 58 | u32 occluding : 1; 59 | u32 shade : 1; 60 | u32 padding : 30; 61 | }; 62 | }; 63 | 64 | struct ParsedBlockModel { 65 | public: 66 | bool Parse(MemoryArena& trans_arena, const char* raw_filename, json_object_s* root); 67 | String GetParentName(json_object_s* root) const; 68 | String ResolveTexture(const String& variable); 69 | 70 | ParsedBlockModel* parent; 71 | bool parsed; 72 | world::BlockModel model; 73 | 74 | ParsedTextureName* texture_names; 75 | 76 | size_t element_count; 77 | ParsedBlockElement elements[48]; 78 | 79 | char filename[256]; 80 | 81 | private: 82 | void ParseTextures(MemoryArena& trans_arena, json_object_s* root); 83 | void ParseElements(json_object_s* root); 84 | }; 85 | 86 | } // namespace asset 87 | } // namespace polymer 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /polymer/bitset.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_BITSET_H_ 2 | #define POLYMER_BITSET_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace polymer { 11 | 12 | struct BitSet { 13 | u64* data; 14 | size_t total_bit_count; 15 | 16 | BitSet() : data(nullptr), total_bit_count(0) {} 17 | 18 | BitSet(const BitSet& other) : data(other.data), total_bit_count(other.total_bit_count) {} 19 | 20 | BitSet(MemoryArena& arena, size_t total_bit_count) { 21 | size_t total_slices = total_bit_count / 64; 22 | 23 | if (total_bit_count % 64 != 0) { 24 | ++total_slices; 25 | } 26 | 27 | this->data = memory_arena_push_type_count(&arena, u64, total_slices); 28 | this->total_bit_count = total_bit_count; 29 | 30 | memset(data, 0, sizeof(u64) * total_slices); 31 | } 32 | 33 | bool Read(MemoryArena& arena, RingBuffer& rb) { 34 | u64 length = 0; 35 | 36 | if (!rb.ReadVarInt(&length)) return false; 37 | 38 | total_bit_count = 64 * length; 39 | data = memory_arena_push_type_count(&arena, u64, length); 40 | 41 | for (size_t i = 0; i < length; ++i) { 42 | if (rb.GetReadAmount() < sizeof(u64)) { 43 | return false; 44 | } 45 | 46 | data[i] = rb.ReadU64(); 47 | } 48 | 49 | return true; 50 | } 51 | 52 | inline bool IsSet(size_t bit_index) const { 53 | if (bit_index >= total_bit_count) return false; 54 | 55 | size_t data_index = bit_index / 64; 56 | u64 data_offset = bit_index % 64; 57 | 58 | return data[data_index] & (1LL << data_offset); 59 | } 60 | 61 | inline void Set(size_t bit_index, bool value) { 62 | if (bit_index >= total_bit_count) return; 63 | 64 | size_t data_index = bit_index / 64; 65 | u64 data_offset = bit_index % 64; 66 | 67 | if (value) { 68 | data[data_index] |= (1LL << data_offset); 69 | } else { 70 | data[data_index] &= ~(1LL << data_offset); 71 | } 72 | } 73 | }; 74 | 75 | } // namespace polymer 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /polymer/ui/chat_window.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_UI_CHAT_WINDOW_H_ 2 | #define POLYMER_UI_CHAT_WINDOW_H_ 3 | 4 | #include 5 | 6 | namespace polymer { 7 | 8 | struct Connection; 9 | struct MemoryArena; 10 | 11 | namespace render { 12 | 13 | struct FontRenderer; 14 | 15 | } // namespace render 16 | 17 | namespace ui { 18 | 19 | struct ChatMessage { 20 | wchar message[1024]; 21 | size_t message_length; 22 | u64 timestamp; 23 | }; 24 | 25 | // TODO: Handle all of the different ways of manipulating the input. 26 | struct ChatInput { 27 | wchar message[256]; 28 | size_t length; 29 | bool active; 30 | 31 | ChatInput() { 32 | message[0] = 0; 33 | length = 0; 34 | active = false; 35 | } 36 | 37 | void Clear() { 38 | message[0] = 0; 39 | length = 0; 40 | } 41 | }; 42 | 43 | enum class ChatMoveDirection { Left, Right, Home, End }; 44 | 45 | // TODO: Generalize all of this to input controls. 46 | // TODO: Handle modifiers such as control so entire words can be erased at once. 47 | // TODO: Selection ranges and highlighting. 48 | struct ChatWindow { 49 | MemoryArena& trans_arena; 50 | 51 | ChatMessage messages[50]; 52 | size_t message_count; 53 | 54 | // The index of the next chat message. 55 | size_t message_index; 56 | 57 | bool display_full; 58 | ChatInput input; 59 | size_t input_cursor_index; 60 | 61 | ChatWindow(MemoryArena& trans_arena) : trans_arena(trans_arena) { 62 | message_count = 0; 63 | message_index = 0; 64 | display_full = false; 65 | input_cursor_index = 0; 66 | } 67 | 68 | void Update(render::FontRenderer& font_renderer); 69 | void PushMessage(const wchar* mesg, size_t mesg_length); 70 | 71 | bool ToggleDisplay(); 72 | 73 | void OnInput(wchar codepoint); 74 | void SendInput(Connection& connection); 75 | 76 | void MoveCursor(ChatMoveDirection direction); 77 | void OnDelete(); 78 | 79 | private: 80 | void RenderSlice(render::FontRenderer& font_renderer, size_t start_index, size_t count, bool fade); 81 | void InsertCodepoint(wchar codepoint); 82 | }; 83 | 84 | } // namespace ui 85 | } // namespace polymer 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /polymer/world/world.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_WORLD_H_ 2 | #define POLYMER_WORLD_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace polymer { 12 | namespace world { 13 | 14 | struct World { 15 | // Store the chunk data separately to make render iteration faster 16 | ChunkSection chunks[kChunkCacheSize][kChunkCacheSize]; 17 | ChunkSectionInfo chunk_infos[kChunkCacheSize][kChunkCacheSize]; 18 | ChunkMesh meshes[kChunkCacheSize][kChunkCacheSize][kChunkColumnCount]; 19 | ChunkConnectivityGraph connectivity_graph; 20 | 21 | BlockRegistry& block_registry; 22 | MemoryPool chunk_pool; 23 | render::BlockMesher block_mesher; 24 | 25 | MemoryArena& trans_arena; 26 | render::VulkanRenderer& renderer; 27 | 28 | u32 world_tick = 0; 29 | 30 | World(MemoryArena& trans_arena, render::VulkanRenderer& renderer, asset::AssetSystem& assets, 31 | BlockRegistry& block_registry); 32 | 33 | inline float GetCelestialAngle() const { 34 | float result = (((s32)world_tick - 6000) % 24000) / 24000.0f; 35 | 36 | if (result < 0.0f) result += 1.0f; 37 | if (result > 1.0f) result -= 1.0f; 38 | 39 | return result; 40 | } 41 | 42 | inline float GetSunlight() const { 43 | float angle = GetCelestialAngle(); 44 | float sunlight = 1.0f - (cosf(angle * 3.1415f * 2.0f) * 2.0f + 1.0f); 45 | 46 | sunlight = 1.0f - Clamp(sunlight, 0.0f, 1.0f); 47 | 48 | return sunlight * 0.8f + 0.2f; 49 | } 50 | 51 | void Update(float dt); 52 | 53 | void OnDimensionChange(); 54 | void OnBlockChange(s32 x, s32 y, s32 z, u32 new_bid); 55 | void OnChunkLoad(s32 chunk_x, s32 chunk_z); 56 | void OnChunkUnload(s32 chunk_x, s32 chunk_z); 57 | 58 | void BuildChunkMesh(render::ChunkBuildContext* ctx); 59 | void BuildChunkMesh(render::ChunkBuildContext* ctx, s32 chunk_x, s32 chunk_y, s32 chunk_z); 60 | void EnqueueChunk(s32 chunk_x, s32 chunk_y, s32 chunk_z); 61 | void FreeMeshes(); 62 | }; 63 | 64 | } // namespace world 65 | } // namespace polymer 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /polymer/asset/block_assets.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_ASSET_BLOCK_ASSET_LOADER_ 2 | #define POLYMER_ASSET_BLOCK_ASSET_LOADER_ 3 | 4 | #include 5 | #include 6 | 7 | namespace polymer { 8 | 9 | struct MemoryArena; 10 | struct ZipArchive; 11 | 12 | namespace render { 13 | 14 | struct VulkanTexture; 15 | struct VulkanRenderer; 16 | 17 | } // namespace render 18 | 19 | namespace asset { 20 | 21 | // This describes the data about a block texture, such as the base texture id and the animation data. 22 | struct BlockTextureDescriptor { 23 | // This is the first texture id for the texture. `count` textures are allocated in the texture array to store the 24 | // animation if it's animated. 25 | u32 base_texture_id; 26 | // How many images that make up the texture animation. 27 | // The texture might be repeated depending on the mcmeta frames list. The count will increase and they will be laid 28 | // out again in texture memory even for repeats. 29 | u16 count; 30 | 31 | struct { 32 | u16 animation_time : 15; 33 | // Minecraft supports interpolated frames where it combines two of the textures depending on the animation time. 34 | u16 interpolated : 1; 35 | }; 36 | 37 | bool operator==(const BlockTextureDescriptor& other) const { 38 | return base_texture_id == other.base_texture_id && count == other.count; 39 | } 40 | }; 41 | 42 | typedef HashMap TextureDescriptorMap; 43 | 44 | struct BlockAssets { 45 | TextureDescriptorMap* texture_descriptor_map = nullptr; 46 | render::VulkanTexture* block_textures = nullptr; 47 | world::BlockRegistry* block_registry = nullptr; 48 | }; 49 | 50 | struct BlockAssetLoader { 51 | MemoryArena& perm_arena; 52 | MemoryArena& trans_arena; 53 | 54 | BlockAssets* assets; 55 | 56 | BlockAssetLoader(MemoryArena& perm_arena, MemoryArena& trans_arena) 57 | : perm_arena(perm_arena), trans_arena(trans_arena), assets(nullptr) {} 58 | 59 | bool Load(render::VulkanRenderer& renderer, ZipArchive& archive, const char* blocks_path, 60 | world::BlockRegistry* registry); 61 | }; 62 | 63 | } // namespace asset 64 | } // namespace polymer 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /polymer/zip_archive.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace polymer { 8 | 9 | bool ZipArchive::Open(const char* path) { 10 | mz_zip_zero_struct(&archive); 11 | 12 | return mz_zip_reader_init_file_v2(&archive, path, 0, 0, 0); 13 | } 14 | 15 | bool ZipArchive::OpenFromMemory(String contents) { 16 | mz_zip_zero_struct(&archive); 17 | 18 | return mz_zip_reader_init_mem(&archive, contents.data, contents.size, 0); 19 | } 20 | 21 | void ZipArchive::Close() { 22 | mz_zip_reader_end(&archive); 23 | 24 | mz_zip_zero_struct(&archive); 25 | } 26 | 27 | char* ZipArchive::ReadFile(MemoryArena* arena, const char* filename, size_t* size) { 28 | mz_uint32 index; 29 | 30 | if (mz_zip_reader_locate_file_v2(&archive, filename, nullptr, 0, &index)) { 31 | mz_zip_archive_file_stat stat; 32 | 33 | if (mz_zip_reader_file_stat(&archive, index, &stat)) { 34 | size_t buffer_size = (size_t)stat.m_uncomp_size; 35 | void* buffer = arena->Allocate(buffer_size); 36 | 37 | if (mz_zip_reader_extract_to_mem(&archive, index, buffer, buffer_size, 0)) { 38 | *size = buffer_size; 39 | return (char*)buffer; 40 | } 41 | } 42 | } 43 | 44 | return nullptr; 45 | } 46 | 47 | ZipArchiveElement* ZipArchive::ListFiles(MemoryArena* arena, const char* search, size_t* count) { 48 | mz_uint archive_count = mz_zip_reader_get_num_files(&archive); 49 | mz_zip_archive_file_stat stat; 50 | 51 | if (!mz_zip_reader_file_stat(&archive, 0, &stat)) { 52 | return nullptr; 53 | } 54 | 55 | ZipArchiveElement* elements = memory_arena_push_type_count(arena, ZipArchiveElement, 0); 56 | 57 | size_t match_count = 0; 58 | 59 | for (unsigned int i = 0; i < archive_count; ++i) { 60 | if (!mz_zip_reader_file_stat(&archive, i, &stat)) continue; 61 | if (mz_zip_reader_is_file_a_directory(&archive, i)) continue; 62 | 63 | const char* current = stat.m_filename; 64 | 65 | if (search == nullptr || strstr(current, search) != nullptr) { 66 | arena->Allocate(sizeof(ZipArchiveElement), 1); 67 | 68 | strcpy(elements[match_count++].name, current); 69 | } 70 | } 71 | 72 | *count = match_count; 73 | 74 | return elements; 75 | } 76 | 77 | } // namespace polymer 78 | -------------------------------------------------------------------------------- /polymer/gamestate.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_GAMESTATE_H_ 2 | #define POLYMER_GAMESTATE_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace polymer { 17 | 18 | struct MemoryArena; 19 | 20 | struct Player { 21 | char name[17]; 22 | char uuid[16]; 23 | 24 | u8 ping; 25 | u8 gamemode; 26 | bool listed; 27 | }; 28 | 29 | struct PlayerManager { 30 | Player players[256]; 31 | size_t player_count = 0; 32 | 33 | Player* client_player = nullptr; 34 | char client_name[17]; 35 | 36 | void SetClientPlayer(Player* player) { 37 | client_player = player; 38 | } 39 | 40 | void AddPlayer(const String& name, const String& uuid, u8 ping, u8 gamemode); 41 | void RemovePlayer(const String& uuid); 42 | Player* GetPlayerByUuid(const String& uuid); 43 | 44 | void RenderPlayerList(render::FontRenderer& font_renderer); 45 | }; 46 | 47 | struct GameState { 48 | MemoryArena* perm_arena; 49 | MemoryArena* trans_arena; 50 | 51 | render::VulkanRenderer* renderer; 52 | render::FontRenderer font_renderer; 53 | render::ChunkRenderer chunk_renderer; 54 | 55 | render::RenderPass render_pass; 56 | // TODO: Clean this up 57 | VkCommandBuffer command_buffers[2]; 58 | 59 | asset::AssetSystem assets; 60 | world::DimensionCodec dimension_codec; 61 | world::DimensionType dimension; 62 | 63 | Connection connection; 64 | Camera camera; 65 | world::World world; 66 | 67 | PlayerManager player_manager; 68 | ui::ChatWindow chat_window; 69 | 70 | float position_sync_timer; 71 | float animation_accumulator; 72 | float time_accumulator; 73 | 74 | world::BlockRegistry block_registry; 75 | 76 | GameState(render::VulkanRenderer* renderer, MemoryArena* perm_arena, MemoryArena* trans_arena); 77 | 78 | void OnBlockChange(s32 x, s32 y, s32 z, u32 new_bid); 79 | void OnChunkLoad(s32 chunk_x, s32 chunk_z); 80 | void OnChunkUnload(s32 chunk_x, s32 chunk_z); 81 | void OnPlayerPositionAndLook(const Vector3f& position, const Vector3f& velocity, float yaw, float pitch, u32 flags); 82 | void OnDimensionChange(); 83 | 84 | void OnWindowMouseMove(s32 dx, s32 dy); 85 | 86 | void Update(float dt, InputState* input); 87 | void ProcessMovement(float dt, InputState* input); 88 | 89 | void SubmitFrame(); 90 | }; 91 | 92 | } // namespace polymer 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /polymer/render/chunk_renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_RENDER_CHUNK_RENDERER_H_ 2 | #define POLYMER_RENDER_CHUNK_RENDERER_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace polymer { 10 | 11 | struct MemoryArena; 12 | 13 | namespace world { 14 | 15 | struct World; 16 | 17 | } // namespace world 18 | 19 | namespace render { 20 | 21 | enum class RenderLayer { 22 | Standard, 23 | Flora, 24 | Leaves, 25 | Alpha, 26 | 27 | Count, 28 | }; 29 | 30 | constexpr size_t kRenderLayerCount = (size_t)RenderLayer::Count; 31 | extern const char* kRenderLayerNames[kRenderLayerCount]; 32 | 33 | struct VulkanTexture; 34 | 35 | struct ChunkRenderUBO { 36 | mat4 mvp; 37 | Vector4f camera; 38 | float anim_time; 39 | float sunlight; 40 | u32 alpha_discard; 41 | }; 42 | 43 | struct ChunkVertex { 44 | Vector3f position; 45 | 46 | u32 texture_id; 47 | u32 packed_light; 48 | 49 | u16 packed_uv; 50 | u16 packed_frametime; 51 | }; 52 | 53 | struct ChunkRenderLayout { 54 | VkDescriptorSetLayout descriptor_layout; 55 | VkPipelineLayout pipeline_layout; 56 | 57 | bool Create(VkDevice device); 58 | void Shutdown(VkDevice device); 59 | 60 | DescriptorSet CreateDescriptors(VkDevice device, VkDescriptorPool descriptor_pool); 61 | }; 62 | 63 | struct ChunkFrameCommandBuffers { 64 | VkCommandBuffer command_buffers[kRenderLayerCount]; 65 | }; 66 | 67 | struct ChunkRenderer { 68 | VulkanRenderer* renderer; 69 | RenderPass* render_pass; 70 | 71 | ChunkRenderLayout layout; 72 | VkPipeline pipeline; 73 | VkPipeline alpha_pipeline; 74 | DescriptorSet descriptor_sets[kRenderLayerCount]; 75 | 76 | UniformBuffer opaque_ubo; 77 | UniformBuffer alpha_ubo; 78 | 79 | VkSampler flora_sampler; 80 | VkSampler leaf_sampler; 81 | 82 | ChunkFrameCommandBuffers frame_command_buffers[kMaxFramesInFlight]; 83 | 84 | VulkanTexture* block_textures; 85 | 86 | void Draw(VkCommandBuffer command_buffer, size_t current_frame, world::World& world, Camera& camera, float anim_time, 87 | float sunlight); 88 | 89 | void CreateLayoutSet(VulkanRenderer& renderer, VkDevice device) { 90 | layout.Create(device); 91 | this->renderer = &renderer; 92 | } 93 | 94 | void OnSwapchainCreate(MemoryArena& trans_arena, Swapchain& swapchain, VkDescriptorPool descriptor_pool); 95 | void OnSwapchainDestroy(VkDevice device); 96 | 97 | void Shutdown(VkDevice device) { 98 | layout.Shutdown(device); 99 | } 100 | 101 | private: 102 | void CreateSamplers(VkDevice device); 103 | void CreatePipeline(MemoryArena& arena, VkDevice device, VkExtent2D swap_extent); 104 | void CreateDescriptors(VkDevice device, VkDescriptorPool descriptor_pool); 105 | }; 106 | 107 | } // namespace render 108 | } // namespace polymer 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /polymer/render/texture.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_RENDER_TEXTURE_H_ 2 | #define POLYMER_RENDER_TEXTURE_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace polymer { 8 | namespace render { 9 | 10 | struct TextureConfig { 11 | bool brighten_mipping = false; 12 | bool anisotropy = false; 13 | bool enable_mipping = false; 14 | VkFilter mag_filter = VK_FILTER_NEAREST; 15 | VkFilter min_filter = VK_FILTER_NEAREST; 16 | float min_lod = 0.0f; 17 | float max_lod = 1.0f; 18 | }; 19 | 20 | struct VulkanTexture { 21 | VmaAllocation allocation; 22 | VkImage image; 23 | VkImageView image_view; 24 | VkSampler sampler; 25 | 26 | u16 mips; 27 | u16 depth; 28 | 29 | // Width and height must be the same for texture arrays 30 | u32 width; 31 | u32 height; 32 | 33 | u32 channels; 34 | VkFormat format; 35 | 36 | VulkanTexture* next; 37 | VulkanTexture* prev; 38 | }; 39 | 40 | // Stores state for creating a data push command to fill the texture array. 41 | // This allows it to push all of the textures at once for improved performance. 42 | struct TextureArrayPushState { 43 | enum class Status { Success, ErrorBuffer, Initial }; 44 | 45 | VulkanTexture& texture; 46 | 47 | VkBuffer buffer; 48 | VmaAllocation alloc; 49 | VmaAllocationInfo alloc_info; 50 | Status status; 51 | 52 | // Size of one texture with its mips. 53 | size_t texture_data_size; 54 | 55 | TextureArrayPushState(VulkanTexture& texture) 56 | : texture(texture), buffer(), alloc(), alloc_info(), status(Status::Initial), texture_data_size(0) {} 57 | }; 58 | 59 | struct VulkanTextureManager { 60 | VulkanTexture* textures = nullptr; 61 | VulkanTexture* last = nullptr; 62 | VulkanTexture* free = nullptr; 63 | 64 | VulkanTexture* CreateTexture(MemoryArena& arena) { 65 | VulkanTexture* result = nullptr; 66 | 67 | if (free) { 68 | result = free; 69 | free = free->next; 70 | } else { 71 | result = memory_arena_push_type(&arena, VulkanTexture); 72 | } 73 | 74 | result->next = textures; 75 | result->prev = nullptr; 76 | 77 | if (textures) { 78 | textures->prev = result; 79 | } 80 | textures = result; 81 | 82 | if (last == nullptr) { 83 | last = result; 84 | } 85 | 86 | return result; 87 | } 88 | 89 | void ReleaseTexture(VulkanTexture& texture) { 90 | if (texture.prev) { 91 | texture.prev->next = texture.next; 92 | } 93 | 94 | if (texture.next) { 95 | texture.next->prev = texture.prev; 96 | } 97 | 98 | if (&texture == last) { 99 | last = last->prev; 100 | } 101 | } 102 | 103 | void Clear() { 104 | VulkanTexture* current = textures; 105 | 106 | while (current) { 107 | VulkanTexture* texture = current; 108 | current = current->next; 109 | 110 | texture->next = free; 111 | free = texture; 112 | } 113 | 114 | last = nullptr; 115 | textures = nullptr; 116 | } 117 | }; 118 | 119 | } // namespace render 120 | } // namespace polymer 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polymer 2 | In-development Minecraft client using C++ and Vulkan. 3 | 4 | It can only connect to offline Java servers at the moment, but online mode is planned. There's currently no physics, but there's a spectator-like camera for looking around. 5 | 6 | It uses the original assets that are downloaded from the resources server. 7 | The downloaded assets will be stored in `%appdata%/Polymer/` on Windows and `~/.polymer/` on Linux. 8 | 9 | ### Screenshots 10 | Typical scene in a village. 11 | ![Standard scene](.github/screenshot1.png) 12 | 13 | Rendering inside a cave. 14 | ![Cave lighting](.github/screenshot2.png) 15 | 16 | Rendering with a radius of 32 chunks in a very dense area. 17 | ![Dense chunks](.github/screenshot3.png) 18 | 19 | ### Running 20 | Main development is done on Windows. It can run on Linux, but not tested much. 21 | 22 | - Requires `blocks-1.21.4.json` that is generated from running Minecraft with a [certain flag](https://minecraft.wiki/w/Tutorial:Running_the_data_generator) or from the [polymer release page](https://github.com/atxi/Polymer/releases). 23 | - Requires compiled shaders. Get them from the release page or read the building section below if manually building. 24 | - Requires an internet connection on first launch so it can download the necessary assets. 25 | 26 | Running the exe will connect to localhost with the username 'polymer'. The server must be configured to be in offline mode. 27 | 28 | You can specify the username and server ip by command line. 29 | `polymer.exe -u username -s 127.0.0.1:25565` 30 | 31 | Currently only a spectator camera is implemented for flying around and rendering the world. By default, you will be in the survival gamemode on the server. If you want chunks to load as you move, you need to put yourself in spectator gamemode. You can do this in the server terminal or in game with the command `/gamemode spectator`. 32 | 33 | ### Building 34 | The project is configured to use vcpkg as a dependency manager, so follow the directions below. 35 | 36 | #### Requirements 37 | - C++ compiler (tested with MSVC 2022 and Clang) 38 | - [CMake](https://cmake.org/) at least version 3.28 39 | 40 | #### Windows 41 | - Open terminal in polymer folder. 42 | - `git submodule update --init` 43 | - `cmake -B build -S . --preset msvc` 44 | - MSVC: Open the generated `build/polymer.sln` and build in x64 Release mode. 45 | - The final executable will be in the `build/Release` folder. 46 | 47 | Compiling the shaders requires `glslc`, which can be obtained from [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/). 48 | - Compile the shaders with `compile_shaders.bat`. `VULKAN_SDK` needs to be set in your environment variables. 49 | 50 | #### Linux 51 | Linux uses GLFW for managing the window. Install it with your package manager. 52 | - `sudo apt-get install libglfw3 libglfw3-dev` 53 | - Open terminal in polymer folder. 54 | - `git submodule update --init` 55 | - `cmake -B build -S .` 56 | - `cd build && make` 57 | - The final executable will be in the `build/bin` folder. 58 | 59 | Compiling the shaders requires `glslangValidator`. 60 | - `sudo apt-get install glslang-tools` 61 | - Compile the shaders with `compile_shaders.sh`. 62 | 63 | -------------------------------------------------------------------------------- /polymer/connection.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_CONNECTION_H_ 2 | #define POLYMER_CONNECTION_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace polymer { 11 | 12 | enum class ConnectResult { Success, ErrorSocket, ErrorAddrInfo, ErrorConnect }; 13 | 14 | #ifdef _WIN64 15 | using SocketType = long long; 16 | #else 17 | using SocketType = int; 18 | #endif 19 | 20 | struct PacketBuilder { 21 | enum BuildFlag { 22 | BuildFlag_Compression = (1 << 0), 23 | BuildFlag_OmitCompress = (1 << 1), 24 | }; 25 | using BuildFlags = u32; 26 | 27 | RingBuffer buffer; 28 | BuildFlags flags; 29 | 30 | PacketBuilder(MemoryArena& arena) : buffer(arena, 32767), flags(BuildFlag_OmitCompress) {} 31 | 32 | inline void Commit(RingBuffer& out, u32 pid) { 33 | size_t compress_length_size = (flags & BuildFlag_OmitCompress) ? 0 : GetVarIntSize(0); 34 | size_t total_size = buffer.write_offset + compress_length_size + GetVarIntSize(pid); 35 | 36 | out.WriteVarInt(total_size); 37 | 38 | if (!(flags & BuildFlag_OmitCompress)) { 39 | // TODO: Implement compression 40 | out.WriteVarInt(0); 41 | } 42 | 43 | out.WriteVarInt(pid); 44 | 45 | if (buffer.write_offset > 0) { 46 | out.WriteRawString(String((char*)buffer.data, buffer.write_offset)); 47 | buffer.write_offset = 0; 48 | } 49 | } 50 | 51 | inline void WriteU8(u8 value) { 52 | buffer.WriteU8(value); 53 | } 54 | 55 | inline void WriteU16(u16 value) { 56 | buffer.WriteU16(value); 57 | } 58 | 59 | inline void WriteU32(u32 value) { 60 | buffer.WriteU32(value); 61 | } 62 | 63 | inline void WriteU64(u64 value) { 64 | buffer.WriteU64(value); 65 | } 66 | 67 | inline void WriteVarInt(u64 value) { 68 | buffer.WriteVarInt(value); 69 | } 70 | 71 | inline void WriteFloat(float value) { 72 | buffer.WriteFloat(value); 73 | } 74 | 75 | inline void WriteDouble(double value) { 76 | buffer.WriteDouble(value); 77 | } 78 | 79 | inline void WriteString(const String& str) { 80 | buffer.WriteString(str); 81 | } 82 | 83 | inline void WriteString(const char* str, size_t size) { 84 | buffer.WriteString(str, size); 85 | } 86 | 87 | inline void WriteRawString(const String& str) { 88 | buffer.WriteRawString(str); 89 | } 90 | 91 | inline void WriteRawString(const char* str, size_t size) { 92 | buffer.WriteRawString(str, size); 93 | } 94 | }; 95 | 96 | struct Connection { 97 | enum class TickResult { Success, ConnectionClosed, ConnectionError }; 98 | 99 | SocketType fd = -1; 100 | bool connected = false; 101 | ProtocolState protocol_state = ProtocolState::Handshake; 102 | 103 | RingBuffer read_buffer; 104 | RingBuffer write_buffer; 105 | 106 | PacketBuilder builder; 107 | 108 | struct PacketInterpreter* interpreter; 109 | 110 | Connection(MemoryArena& arena); 111 | 112 | ConnectResult Connect(const char* ip, u16 port); 113 | void Disconnect(); 114 | void SetBlocking(bool blocking); 115 | 116 | TickResult Tick(); 117 | }; 118 | 119 | } // namespace polymer 120 | 121 | #endif 122 | -------------------------------------------------------------------------------- /polymer/types.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_TYPES_H_ 2 | #define POLYMER_TYPES_H_ 3 | 4 | #include 5 | #include 6 | 7 | #define polymer_array_count(arr) (sizeof(arr) / (sizeof(*arr))) 8 | #define POLY_STR(str) \ 9 | String { \ 10 | (char*)str, polymer_array_count(str) - 1 \ 11 | } 12 | 13 | namespace polymer { 14 | 15 | using s8 = int8_t; 16 | using s16 = int16_t; 17 | using s32 = int32_t; 18 | using s64 = int64_t; 19 | 20 | using u8 = uint8_t; 21 | using u16 = uint16_t; 22 | using u32 = uint32_t; 23 | using u64 = uint64_t; 24 | 25 | using wchar = u32; 26 | 27 | struct String { 28 | char* data; 29 | size_t size; 30 | 31 | String() : data(nullptr), size(0) {} 32 | String(char* data) : data(data), size(strlen(data)) {} 33 | String(char* data, size_t size) : data(data), size(size) {} 34 | String(const char* data, size_t size) : data((char*)data), size(size) {} 35 | 36 | bool operator==(const String& other) const; 37 | }; 38 | 39 | struct WString { 40 | wchar* data; 41 | size_t length; 42 | 43 | WString() : data(nullptr), length(0) {} 44 | WString(wchar* data, size_t length) : data(data), length(length) {} 45 | }; 46 | 47 | inline s32 poly_strcmp(const String& str1, const String& str2) { 48 | for (size_t i = 0; i < str1.size && i < str2.size; ++i) { 49 | if (str1.data[i] < str2.data[i]) { 50 | return -1; 51 | } else if (str1.data[i] > str2.data[i]) { 52 | return 1; 53 | } 54 | } 55 | 56 | return str1.size == str2.size ? 0 : -1; 57 | } 58 | 59 | inline bool String::operator==(const String& other) const { 60 | return poly_strcmp(*this, other) == 0; 61 | } 62 | 63 | inline String poly_string(const char* data, size_t size) { 64 | String result; 65 | 66 | result.data = (char*)data; 67 | result.size = size; 68 | 69 | return result; 70 | } 71 | 72 | inline String poly_string(const char* strz) { 73 | return poly_string(strz, strlen(strz)); 74 | } 75 | 76 | inline String poly_strstr(const String& str, const String& find) { 77 | size_t sublen = find.size; 78 | 79 | if (sublen > str.size) { 80 | return String(); 81 | } 82 | 83 | for (size_t i = 0; i <= str.size - sublen; ++i) { 84 | bool found = true; 85 | 86 | for (size_t j = 0; j < sublen; ++j) { 87 | if (str.data[i + j] != find.data[j]) { 88 | found = false; 89 | break; 90 | } 91 | } 92 | 93 | if (found) { 94 | return String(str.data + i, str.size - i); 95 | } 96 | } 97 | 98 | return String(); 99 | } 100 | 101 | inline String poly_strstr(const String& str, const char* substring) { 102 | return poly_strstr(str, String((char*)substring)); 103 | } 104 | 105 | inline bool poly_contains(const String& str, const String& find) { 106 | return poly_strstr(str, find).data != nullptr; 107 | } 108 | 109 | inline bool poly_contains(const String& str, char c) { 110 | for (size_t i = 0; i < str.size; ++i) { 111 | if (str.data[i] == c) { 112 | return true; 113 | } 114 | } 115 | 116 | return false; 117 | } 118 | 119 | } // namespace polymer 120 | 121 | #endif 122 | -------------------------------------------------------------------------------- /polymer/render/swapchain.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_RENDER_H_ 2 | #define POLYMER_RENDER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace polymer { 9 | 10 | struct MemoryArena; 11 | 12 | namespace render { 13 | 14 | constexpr size_t kMaxSwapImages = 6; 15 | 16 | using SwapchainCallback = void (*)(struct Swapchain& swapchain, void* user_data); 17 | 18 | struct SwapChainSupportDetails { 19 | VkSurfaceCapabilitiesKHR capabilities; 20 | 21 | u32 format_count; 22 | VkSurfaceFormatKHR* formats; 23 | 24 | u32 present_mode_count; 25 | VkPresentModeKHR* present_modes; 26 | }; 27 | 28 | struct FramebufferSet { 29 | VkFramebuffer framebuffers[kMaxSwapImages]; 30 | size_t count; 31 | }; 32 | 33 | struct MultisampleState { 34 | VkImage color_image; 35 | VkImageView color_image_view; 36 | VmaAllocation color_image_allocation; 37 | 38 | VkSampleCountFlagBits max_samples; 39 | VkSampleCountFlagBits samples; 40 | }; 41 | 42 | struct Swapchain { 43 | RenderConfig* render_cfg = nullptr; 44 | 45 | VmaAllocator allocator; 46 | VkSwapchainKHR swapchain; 47 | VkDevice device; 48 | 49 | VkFormat format; 50 | VkSampler sampler; 51 | VkExtent2D extent; 52 | 53 | MultisampleState multisample; 54 | 55 | VkImage depth_image; 56 | VkImageView depth_image_view; 57 | VmaAllocation depth_allocation; 58 | 59 | u32 image_count; 60 | VkImage images[kMaxSwapImages]; 61 | VkImageView image_views[kMaxSwapImages]; 62 | VkFence image_fences[kMaxSwapImages]; 63 | 64 | bool supports_linear_mipmap; 65 | 66 | VkPresentModeKHR present_mode; 67 | VkSurfaceFormatKHR surface_format; 68 | SwapChainSupportDetails swapchain_support; 69 | 70 | void InitializeFormat(MemoryArena& trans_arena, VkPhysicalDevice physical_device, VkDevice device, 71 | VkSurfaceKHR surface); 72 | 73 | void Create(MemoryArena& trans_arena, VkPhysicalDevice physical_device, VkDevice device, VkSurfaceKHR surface, 74 | VkExtent2D extent, struct QueueFamilyIndices& indices); 75 | void Cleanup(); 76 | 77 | // Call this to trigger the create callbacks. 78 | void OnCreate(); 79 | 80 | void RegisterCreateCallback(void* user_data, SwapchainCallback callback); 81 | void RegisterCleanupCallback(void* user_data, SwapchainCallback callback); 82 | 83 | FramebufferSet CreateFramebuffers(VkRenderPass render_pass); 84 | 85 | static SwapChainSupportDetails QuerySwapChainSupport(MemoryArena& trans_arena, VkPhysicalDevice device, 86 | VkSurfaceKHR surface); 87 | 88 | private: 89 | void CreateViewBuffers(); 90 | 91 | VkPresentModeKHR ChooseSwapPresentMode(VkPresentModeKHR* present_modes, u32 present_mode_count); 92 | VkSurfaceFormatKHR ChooseSwapSurfaceFormat(VkPhysicalDevice physical_device, VkSurfaceFormatKHR* formats, 93 | u32 format_count); 94 | 95 | struct CallbackRegistration { 96 | void* user_data; 97 | SwapchainCallback callback; 98 | }; 99 | 100 | CallbackRegistration create_callbacks[16]; 101 | size_t create_callback_size = 0; 102 | 103 | CallbackRegistration cleanup_callbacks[16]; 104 | size_t cleanup_callback_size = 0; 105 | }; 106 | 107 | } // namespace render 108 | } // namespace polymer 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /polymer/render/font_renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_RENDER_FONT_RENDERER_H_ 2 | #define POLYMER_RENDER_FONT_RENDERER_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace polymer { 8 | 9 | struct MemoryArena; 10 | 11 | namespace render { 12 | 13 | struct VulkanTexture; 14 | 15 | enum FontStyleFlag { 16 | FontStyle_None = 0, 17 | FontStyle_DropShadow = (1 << 0), 18 | FontStyle_Background = (1 << 1), 19 | FontStyle_Center = (1 << 2), 20 | }; 21 | using FontStyleFlags = u32; 22 | 23 | struct FontRenderUBO { 24 | mat4 mvp; 25 | }; 26 | 27 | struct FontVertex { 28 | Vector3f position; 29 | u32 rgba; 30 | 31 | u16 glyph_id; 32 | u16 uv_xy; 33 | }; 34 | 35 | struct FontPushBuffer { 36 | VkBuffer buffer = VK_NULL_HANDLE; 37 | VmaAllocation buffer_alloc = VK_NULL_HANDLE; 38 | VmaAllocationInfo buffer_alloc_info; 39 | size_t vertex_count = 0; 40 | 41 | inline FontVertex* GetMapped() { 42 | return (FontVertex*)buffer_alloc_info.pMappedData; 43 | } 44 | 45 | inline void Destroy(VmaAllocator allocator) { 46 | vmaDestroyBuffer(allocator, buffer, buffer_alloc); 47 | buffer = nullptr; 48 | } 49 | }; 50 | 51 | struct FontPipelineLayout { 52 | VkDescriptorSetLayout descriptor_layout; 53 | VkPipelineLayout pipeline_layout; 54 | 55 | bool Create(VkDevice device); 56 | void Shutdown(VkDevice device); 57 | 58 | DescriptorSet CreateDescriptors(VkDevice device, VkDescriptorPool descriptor_pool); 59 | }; 60 | 61 | struct FontRenderer { 62 | VulkanRenderer* renderer; 63 | RenderPass* render_pass; 64 | 65 | FontPipelineLayout layout; 66 | VkPipeline render_pipeline; 67 | 68 | UniformBuffer uniform_buffer; 69 | DescriptorSet descriptors; 70 | 71 | FontPushBuffer push_buffer; 72 | VkCommandBuffer command_buffers[kMaxFramesInFlight]; 73 | 74 | VulkanTexture* glyph_page_texture; 75 | u8* glyph_size_table; 76 | 77 | void RenderText(const Vector3f& screen_position, const String& str, FontStyleFlags style = FontStyle_None, 78 | const Vector4f& color = Vector4f(1, 1, 1, 1)); 79 | 80 | void RenderText(const Vector3f& screen_position, const WString& wstr, FontStyleFlags style = FontStyle_None, 81 | const Vector4f& color = Vector4f(1, 1, 1, 1)); 82 | 83 | void RenderBackground(const Vector3f& screen_position, const String& str, 84 | const Vector4f& color = Vector4f(0.2f, 0.2f, 0.2f, 0.5f)); 85 | 86 | void RenderBackground(const Vector3f& screen_position, const Vector2f& size, 87 | const Vector4f& color = Vector4f(0.2f, 0.2f, 0.2f, 0.5f)); 88 | 89 | int GetTextWidth(const String& str); 90 | int GetTextWidth(const WString& wstr); 91 | 92 | bool BeginFrame(size_t current_frame); 93 | void Draw(VkCommandBuffer command_buffer, size_t current_frame); 94 | 95 | void CreateLayoutSet(VulkanRenderer& renderer, VkDevice device); 96 | 97 | void OnSwapchainCreate(MemoryArena& trans_arena, Swapchain& swapchain, VkDescriptorPool descriptor_pool); 98 | void OnSwapchainDestroy(VkDevice device); 99 | 100 | void Shutdown(VkDevice device) { 101 | layout.Shutdown(device); 102 | push_buffer.Destroy(renderer->allocator); 103 | } 104 | 105 | private: 106 | void CreatePipeline(MemoryArena& arena, VkDevice device, VkExtent2D swap_extent); 107 | void CreateDescriptors(VkDevice device, VkDescriptorPool descriptor_pool); 108 | }; 109 | 110 | } // namespace render 111 | } // namespace polymer 112 | 113 | #endif 114 | -------------------------------------------------------------------------------- /polymer/world/chunk.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_CHUNK_H_ 2 | #define POLYMER_CHUNK_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace polymer { 14 | namespace world { 15 | 16 | constexpr size_t kChunkColumnCount = 24; 17 | 18 | struct ChunkCoord { 19 | s32 x; 20 | s32 z; 21 | }; 22 | 23 | struct Chunk { 24 | u32 blocks[16][16][16]; 25 | 26 | // The bottom 4 bits contain the skylight data and the upper 4 bits contain the block 27 | u8 lightmap[16][16][16]; 28 | }; 29 | 30 | struct ChunkSectionInfo { 31 | u32 loaded : 1; 32 | u32 dirty_connectivity_set : 24; 33 | u32 padding : 7; 34 | 35 | u32 dirty_mesh_set : 24; 36 | u32 padding_mesh : 8; 37 | 38 | u32 bitmask; 39 | s32 x; 40 | s32 z; 41 | }; 42 | 43 | struct ChunkSection { 44 | ChunkSectionInfo* info; 45 | Chunk* chunks[kChunkColumnCount]; 46 | }; 47 | 48 | struct ChunkMesh { 49 | render::RenderMesh meshes[render::kRenderLayerCount]; 50 | }; 51 | 52 | constexpr size_t kMaxViewDistance = 32; 53 | // We need to be able to wrap around without overwriting any used chunks. 54 | constexpr size_t kChunkCacheSize = kMaxViewDistance * 2 + 4; 55 | 56 | inline constexpr u32 GetChunkCacheIndex(s32 v) { 57 | return ((v % (s32)kChunkCacheSize) + (s32)kChunkCacheSize) % (s32)kChunkCacheSize; 58 | } 59 | 60 | // This is the connectivity state for each face of a chunk to other faces. 61 | // It is used to determine which chunks need to be rendered. 62 | struct ChunkConnectivitySet { 63 | struct Coord { 64 | s8 x; 65 | s8 y; 66 | s8 z; 67 | s8 pad; 68 | }; 69 | 70 | using VisitSet = std::bitset<16 * 16 * 16>; 71 | 72 | std::bitset<36> connectivity; 73 | 74 | // Computes the new connectivity set and returns whether or not it changed. 75 | bool Build(const struct World& world, const Chunk& chunk); 76 | 77 | u8 FloodFill(const struct World& world, const Chunk& chunk, VisitSet& visited, Coord* queue, s8 start_x, s8 start_y, 78 | s8 start_z); 79 | 80 | inline bool HasFaceConnectivity(BlockFace face) const { 81 | for (size_t i = 0; i < 6; ++i) { 82 | size_t index = (size_t)face * 6 + i; 83 | 84 | if (connectivity.test(index)) return true; 85 | } 86 | 87 | return false; 88 | } 89 | 90 | inline bool IsConnected(BlockFace from, BlockFace to) const { 91 | constexpr size_t kFaceCount = 6; 92 | 93 | // We only need to check one index because it should be set for both when connectivity is set. 94 | size_t index = (size_t)from + ((size_t)to * kFaceCount); 95 | 96 | return connectivity.test(index); 97 | } 98 | 99 | inline void Clear() { 100 | connectivity.reset(); 101 | } 102 | }; 103 | 104 | struct VisibleChunk { 105 | s32 chunk_x; 106 | s32 chunk_y; 107 | s32 chunk_z; 108 | }; 109 | 110 | struct ChunkConnectivityGraph { 111 | ChunkConnectivitySet chunk_connectivity[kChunkCacheSize][kChunkCacheSize][kChunkColumnCount]; 112 | VisibleChunk visible_set[kChunkCacheSize * kChunkCacheSize * kChunkColumnCount]; 113 | size_t visible_count = 0; 114 | 115 | // This computes the connectivity of the provided chunk to the neighboring chunks. 116 | void Build(const struct World& world, const Chunk* chunk, size_t x_index, size_t z_index, s32 chunk_y); 117 | 118 | // Rebuilds the visible set. 119 | void Update(MemoryArena& trans_arena, struct World& world, const Camera& camera); 120 | }; 121 | 122 | } // namespace world 123 | } // namespace polymer 124 | 125 | #endif 126 | -------------------------------------------------------------------------------- /polymer/render/render_pass.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace polymer { 8 | namespace render { 9 | 10 | void RenderPass::Destroy(Swapchain& swapchain) { 11 | for (u32 i = 0; i < swapchain.image_count; i++) { 12 | vkDestroyFramebuffer(swapchain.device, framebuffers.framebuffers[i], nullptr); 13 | } 14 | vkDestroyRenderPass(swapchain.device, render_pass, nullptr); 15 | valid = false; 16 | } 17 | 18 | void RenderPass::CreateSimple(Swapchain& swapchain, VkAttachmentDescription color, VkAttachmentDescription depth, 19 | VkAttachmentDescription color_resolve) { 20 | VkAttachmentReference color_attachment_ref = {}; 21 | color_attachment_ref.attachment = 0; 22 | color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 23 | 24 | VkAttachmentReference depth_attachment_ref = {}; 25 | depth_attachment_ref.attachment = 1; 26 | depth_attachment_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; 27 | 28 | VkAttachmentReference color_attachment_resolve_ref = {}; 29 | color_attachment_resolve_ref.attachment = 2; 30 | color_attachment_resolve_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 31 | 32 | VkAttachmentDescription attachments[] = {color, depth, color_resolve}; 33 | 34 | u32 attachment_count = polymer_array_count(attachments); 35 | 36 | VkSubpassDescription subpass = {}; 37 | subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; 38 | subpass.colorAttachmentCount = 1; 39 | subpass.pColorAttachments = &color_attachment_ref; 40 | subpass.pDepthStencilAttachment = &depth_attachment_ref; 41 | 42 | if (swapchain.multisample.samples != VK_SAMPLE_COUNT_1_BIT) { 43 | subpass.pResolveAttachments = &color_attachment_resolve_ref; 44 | } else { 45 | attachment_count--; 46 | } 47 | 48 | VkSubpassDependency dependencies[2] = {}; 49 | 50 | dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; 51 | dependencies[0].dstSubpass = 0; 52 | dependencies[0].srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; 53 | dependencies[0].dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; 54 | dependencies[0].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; 55 | dependencies[0].dstAccessMask = 56 | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; 57 | 58 | dependencies[1].srcSubpass = VK_SUBPASS_EXTERNAL; 59 | dependencies[1].dstSubpass = 0; 60 | dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; 61 | dependencies[1].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; 62 | dependencies[1].srcAccessMask = 0; 63 | dependencies[1].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; 64 | 65 | VkRenderPassCreateInfo render_pass_info = {}; 66 | render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; 67 | render_pass_info.attachmentCount = attachment_count; 68 | render_pass_info.pAttachments = attachments; 69 | render_pass_info.subpassCount = 1; 70 | render_pass_info.pSubpasses = &subpass; 71 | render_pass_info.dependencyCount = 2; 72 | render_pass_info.pDependencies = dependencies; 73 | 74 | Create(swapchain, &render_pass_info); 75 | } 76 | 77 | void RenderPass::Create(Swapchain& swapchain, VkRenderPassCreateInfo* create_info) { 78 | if (vkCreateRenderPass(swapchain.device, create_info, nullptr, &render_pass) != VK_SUCCESS) { 79 | fprintf(stderr, "Failed to create render pass.\n"); 80 | } 81 | 82 | this->framebuffers = swapchain.CreateFramebuffers(render_pass); 83 | valid = true; 84 | } 85 | 86 | } // namespace render 87 | } // namespace polymer 88 | -------------------------------------------------------------------------------- /polymer/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_MEMORY_H_ 2 | #define POLYMER_MEMORY_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace polymer { 13 | 14 | constexpr size_t Kilobytes(size_t n) { 15 | return n * 1024; 16 | } 17 | 18 | constexpr size_t Megabytes(size_t n) { 19 | return n * Kilobytes(1024); 20 | } 21 | 22 | constexpr size_t Gigabytes(size_t n) { 23 | return n * Megabytes(1024); 24 | } 25 | 26 | using ArenaSnapshot = u8*; 27 | 28 | struct MemoryRevert { 29 | struct MemoryArena& arena; 30 | ArenaSnapshot snapshot; 31 | 32 | inline MemoryRevert(MemoryArena& arena, ArenaSnapshot snapshot) : arena(arena), snapshot(snapshot) {} 33 | inline ~MemoryRevert(); 34 | }; 35 | 36 | struct MemoryArena { 37 | u8* base; 38 | u8* current; 39 | size_t max_size; 40 | 41 | MemoryArena() : base(nullptr), current(nullptr), max_size(0) {} 42 | MemoryArena(u8* memory, size_t max_size); 43 | 44 | u8* Allocate(size_t size, size_t alignment = 4); 45 | void Reset(); 46 | 47 | ArenaSnapshot GetSnapshot() { 48 | return current; 49 | } 50 | 51 | MemoryRevert GetReverter() { 52 | return MemoryRevert(*this, GetSnapshot()); 53 | } 54 | 55 | void Revert(ArenaSnapshot snapshot) { 56 | current = snapshot; 57 | } 58 | 59 | void Destroy(); 60 | 61 | template 62 | T* Construct(Args&&... args) { 63 | T* result = (T*)Allocate(sizeof(T)); 64 | new (result) T(std::forward(args)...); 65 | return result; 66 | } 67 | }; 68 | 69 | inline MemoryRevert::~MemoryRevert() { 70 | arena.Revert(snapshot); 71 | } 72 | 73 | #define memory_arena_push_type(arena, type) (type*)(arena)->Allocate(sizeof(type)) 74 | #define memory_arena_push_type_count(arena, type, count) (type*)(arena)->Allocate(sizeof(type) * count) 75 | 76 | // Allocate virtual pages that mirror the first half 77 | u8* AllocateMirroredBuffer(size_t size); 78 | 79 | MemoryArena CreateArena(size_t size); 80 | 81 | template 82 | struct MemoryPool { 83 | struct Element { 84 | T data; 85 | 86 | Element* next; 87 | }; 88 | 89 | constexpr static size_t kPageSize = 4096; 90 | 91 | Element* freelist = nullptr; 92 | 93 | T* Allocate() { 94 | if (!freelist) { 95 | // This is how many pages are allocated every time the freelist is empty. This is done to try to fit many 96 | // allocations into each platform alloc call for small T. 97 | constexpr size_t kPagesPerAllocCount = 98 | sizeof(Element) >= kPageSize ? ((sizeof(Element) + kPageSize) / kPageSize) : (kPageSize / sizeof(Element)); 99 | 100 | size_t alloc_size = kPagesPerAllocCount * kPageSize; 101 | u8* data = g_Platform.Allocate(alloc_size); 102 | u8* data_end = data + alloc_size; 103 | 104 | if (!data) { 105 | fprintf(stderr, "Failed to allocate data for memory pool (%u).", (u32)sizeof(T)); 106 | return nullptr; 107 | } 108 | 109 | while (data + sizeof(Element) < data_end) { 110 | Element* element = (Element*)data; 111 | 112 | element->next = freelist; 113 | freelist = element; 114 | 115 | data += sizeof(Element); 116 | } 117 | } 118 | 119 | if (!freelist) return nullptr; 120 | 121 | Element* result = freelist; 122 | 123 | freelist = freelist->next; 124 | 125 | memset(&result->data, 0, sizeof(T)); 126 | 127 | return (T*)result; 128 | } 129 | 130 | void Free(T* data) { 131 | // This cast can happen because data is the first member of Element. 132 | Element* element = (Element*)data; 133 | 134 | element->next = freelist; 135 | freelist = element; 136 | } 137 | }; 138 | 139 | } // namespace polymer 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /polymer/memory.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef _WIN32 8 | #include 9 | #else 10 | #include 11 | #include 12 | #endif 13 | 14 | namespace polymer { 15 | 16 | MemoryArena::MemoryArena(u8* memory, size_t max_size) : base(memory), current(memory), max_size(max_size) {} 17 | 18 | u8* MemoryArena::Allocate(size_t size, size_t alignment) { 19 | assert(alignment > 0); 20 | 21 | size_t adj = alignment - 1; 22 | u8* result = (u8*)(((size_t)this->current + adj) & ~adj); 23 | this->current = result + size; 24 | 25 | assert(this->current <= this->base + this->max_size); 26 | if (this->current > this->base + this->max_size) { 27 | this->current -= size; 28 | fprintf(stderr, "Failed to allocate. Arena out of space.\n"); 29 | return nullptr; 30 | } 31 | 32 | return result; 33 | } 34 | 35 | void MemoryArena::Reset() { 36 | this->current = this->base; 37 | } 38 | 39 | void MemoryArena::Destroy() { 40 | g_Platform.Free(this->base); 41 | 42 | this->base = this->current = nullptr; 43 | this->max_size = 0; 44 | } 45 | 46 | MemoryArena CreateArena(size_t size) { 47 | MemoryArena result; 48 | 49 | assert(size > 0); 50 | 51 | result.base = result.current = g_Platform.Allocate(size); 52 | result.max_size = size; 53 | 54 | assert(result.base); 55 | 56 | return result; 57 | } 58 | 59 | u8* AllocateMirroredBuffer(size_t size) { 60 | #ifdef _WIN32 61 | SYSTEM_INFO sys_info = {0}; 62 | 63 | GetSystemInfo(&sys_info); 64 | 65 | size_t granularity = sys_info.dwAllocationGranularity; 66 | 67 | if (((size / granularity) * granularity) != size) { 68 | fprintf(stderr, "Incorrect size. Must be multiple of %zd\n", granularity); 69 | return 0; 70 | } 71 | 72 | HANDLE map_handle = 73 | CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT, 0, (DWORD)size * 2, NULL); 74 | 75 | if (map_handle == NULL) { 76 | return 0; 77 | } 78 | 79 | u8* buffer = (u8*)VirtualAlloc(NULL, size * 2, MEM_RESERVE, PAGE_READWRITE); 80 | 81 | if (buffer == NULL) { 82 | CloseHandle(map_handle); 83 | return 0; 84 | } 85 | 86 | VirtualFree(buffer, 0, MEM_RELEASE); 87 | 88 | u8* view = (u8*)MapViewOfFileEx(map_handle, FILE_MAP_ALL_ACCESS, 0, 0, size, buffer); 89 | 90 | if (view == NULL) { 91 | u32 error = GetLastError(); 92 | CloseHandle(map_handle); 93 | return 0; 94 | } 95 | 96 | u8* mirror_view = (u8*)MapViewOfFileEx(map_handle, FILE_MAP_ALL_ACCESS, 0, 0, size, buffer + size); 97 | 98 | if (mirror_view == NULL) { 99 | u32 error = GetLastError(); 100 | 101 | UnmapViewOfFile(view); 102 | CloseHandle(map_handle); 103 | return 0; 104 | } 105 | 106 | return view; 107 | #else 108 | size_t pagesize = getpagesize(); 109 | int fd = fileno(tmpfile()); 110 | size_t paged_aligned_size = (size / pagesize) * pagesize; 111 | 112 | assert((size / pagesize) == ((size + pagesize - 1) / pagesize)); 113 | 114 | // Resize the file to the requested size so the buffer can be mapped to it. 115 | if (ftruncate(fd, paged_aligned_size) != 0) { 116 | fprintf(stderr, "ftruncate() error\n"); 117 | return nullptr; 118 | } 119 | 120 | // Grab some virtual memory space for the full wrapped buffer. 121 | u8* buffer = (u8*)mmap(NULL, paged_aligned_size * 2, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 122 | 123 | // Map the beginning of the virtual memory to the tmpfile. 124 | mmap(buffer, paged_aligned_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0); 125 | // Map the wrapping section of the buffer back to the tmpfile. 126 | mmap(buffer + paged_aligned_size, paged_aligned_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0); 127 | 128 | return buffer; 129 | #endif 130 | } 131 | 132 | } // namespace polymer 133 | -------------------------------------------------------------------------------- /polymer/hashmap.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_HASHMAP_H_ 2 | #define POLYMER_HASHMAP_H_ 3 | 4 | #include 5 | #include 6 | 7 | namespace polymer { 8 | 9 | // Buckets must be power of 2 10 | template 11 | struct HashMap { 12 | struct Element { 13 | Key key; 14 | Value value; 15 | 16 | Element* next; 17 | }; 18 | 19 | MemoryArena& arena; 20 | Element* elements[buckets]; 21 | Element* free = nullptr; 22 | Hasher hasher; 23 | 24 | HashMap(MemoryArena& arena) : arena(arena) { 25 | for (size_t i = 0; i < buckets; ++i) { 26 | elements[i] = nullptr; 27 | } 28 | 29 | for (size_t i = 0; i < 32; ++i) { 30 | Element* element = memory_arena_push_type(&arena, Element); 31 | element->next = free; 32 | free = element; 33 | } 34 | } 35 | 36 | void Insert(const Key& key, const Value& value) { 37 | u32 bucket = hasher(key) & (buckets - 1); 38 | 39 | Element* element = elements[bucket]; 40 | while (element) { 41 | if (element->value == value) { 42 | break; 43 | } 44 | element = element->next; 45 | } 46 | 47 | if (element == nullptr) { 48 | element = Allocate(); 49 | 50 | element->key = key; 51 | 52 | element->next = elements[bucket]; 53 | elements[bucket] = element; 54 | } 55 | 56 | element->value = value; 57 | } 58 | 59 | Value* Remove(const Key& key) { 60 | u32 bucket = hasher(key) & (buckets - 1); 61 | Element* element = elements[bucket]; 62 | Element* previous = nullptr; 63 | 64 | while (element) { 65 | if (element->key == key) { 66 | if (previous) { 67 | previous->next = element->next; 68 | } else { 69 | elements[bucket] = element->next; 70 | } 71 | 72 | return &element->value; 73 | } 74 | 75 | previous = element; 76 | element = element->next; 77 | } 78 | 79 | return nullptr; 80 | } 81 | 82 | Value* Find(const Key& key) { 83 | u32 bucket = hasher(key) & (buckets - 1); 84 | Element* element = elements[bucket]; 85 | 86 | while (element) { 87 | if (element->key == key) { 88 | return &element->value; 89 | } 90 | 91 | element = element->next; 92 | } 93 | 94 | return nullptr; 95 | } 96 | 97 | void Clear() { 98 | for (size_t i = 0; i < buckets; ++i) { 99 | Element* element = elements[i]; 100 | 101 | while (element) { 102 | Element* next = element->next; 103 | 104 | element->next = free; 105 | free = element; 106 | 107 | element = next; 108 | } 109 | 110 | elements[i] = nullptr; 111 | } 112 | } 113 | 114 | private: 115 | Element* Allocate() { 116 | Element* result = nullptr; 117 | 118 | if (free) { 119 | result = free; 120 | free = free->next; 121 | } else { 122 | result = memory_arena_push_type(&arena, Element); 123 | } 124 | 125 | result->next = nullptr; 126 | return result; 127 | } 128 | }; 129 | 130 | struct MapStringKey { 131 | String key; 132 | 133 | MapStringKey(const String& str) : key(str) {} 134 | MapStringKey(const char* str, size_t size) { 135 | key = poly_string(str, size); 136 | } 137 | 138 | bool operator==(const MapStringKey& other) { 139 | return poly_strcmp(key, other.key) == 0; 140 | } 141 | }; 142 | 143 | struct MapStringHasher { 144 | inline u32 operator()(const MapStringKey& key) { 145 | return Hash(key.key); 146 | } 147 | 148 | inline u32 Hash(const String& str) { 149 | u32 hash = 5381; 150 | 151 | for (size_t i = 0; i < str.size; ++i) { 152 | char c = str.data[i]; 153 | hash = hash * 33 ^ c; 154 | } 155 | 156 | return hash; 157 | } 158 | }; 159 | 160 | } // namespace polymer 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /shaders/chunk_shader.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform UniformBufferObject { 5 | mat4 mvp; 6 | vec4 camera; 7 | float anim_time; 8 | float sunlight; 9 | uint alpha_discard; 10 | } ubo; 11 | 12 | layout(location = 0) in vec3 inPosition; 13 | layout(location = 1) in uint inTexId; 14 | layout(location = 2) in uint inPackedLight; 15 | layout(location = 3) in uint inTexCoord; 16 | layout(location = 4) in uint inFrametime; 17 | 18 | layout(location = 0) out vec2 fragTexCoord; 19 | layout(location = 1) flat out uint fragTexId; 20 | layout(location = 2) out vec4 fragColorMod; 21 | layout(location = 3) flat out uint fragTexIdInterpolate; 22 | layout(location = 4) flat out float interpolate_t; 23 | 24 | #define GRASS_TINTINDEX 0 25 | #define LEAF_TINTINDEX 1 26 | #define SPRUCE_LEAF_TINTINDEX 2 27 | #define BIRCH_LEAF_TINTINDEX 3 28 | #define WATER_TINTINDEX 50 29 | 30 | #define WATER_TINT vec4(0.247, 0.463, 0.894, 1.0) 31 | 32 | #define JUNGLE_GRASS_TINT vec4(0.349, 0.788, 0.235, 1.0) 33 | #define JUNGLE_LEAF_TINT vec4(0.188, 0.733, 0.043, 1.0) 34 | 35 | #define TAIGA_GRASS_TINT vec4(0.525, 0.718, 0.514, 1.0) 36 | #define TAIGA_LEAF_TINT vec4(0.408, 0.643, 0.392, 1.0) 37 | 38 | #define PLAINS_GRASS_TINT vec4(0.569, 0.741, 0.349, 1.0) 39 | #define PLAINS_LEAF_TINT vec4(0.467, 0.671, 0.184, 1.0) 40 | 41 | // Spruce and birch seem to be not affected by biome coloring 42 | #define SPRUCE_LEAF_TINT vec4(0.380, 0.600, 0.380, 1.0) 43 | #define BIRCH_LEAF_TINT vec4(0.502, 0.655, 0.333, 1.0) 44 | 45 | void main() { 46 | uint animCount = inPackedLight >> 24; 47 | 48 | gl_Position = ubo.mvp * vec4(inPosition - ubo.camera.xyz, 1.0); 49 | fragTexCoord.x = (inTexCoord >> 5) / 16.0; 50 | fragTexCoord.y = (inTexCoord & 0x1F) / 16.0; 51 | 52 | uint frametime = inFrametime & 0x7FFF; 53 | uint interpolated = inFrametime >> 15; 54 | 55 | float frame_t = ubo.anim_time * (24.0f / frametime); 56 | uint frame = uint(frame_t) % animCount; 57 | 58 | fragTexId = inTexId + frame; 59 | fragColorMod = vec4(1, 1, 1, 1); 60 | fragTexIdInterpolate = 0; 61 | interpolate_t = 0.0; 62 | 63 | if (interpolated != 0) { 64 | fragTexIdInterpolate = inTexId + ((frame + 1) % animCount); 65 | float frame_index = 0; 66 | interpolate_t = modf(frame_t, frame_index); 67 | } 68 | 69 | // TODO: Remove this and sample biome from foliage/grass png 70 | uint tintindex = (inPackedLight >> 16) & 0x7F; 71 | uint ao = inPackedLight & 3; 72 | 73 | uint skylight_value = (inPackedLight >> 2) & 0x3F; 74 | uint blocklight_value = (inPackedLight >> 8) & 0x3F; 75 | 76 | if (tintindex == GRASS_TINTINDEX) { 77 | fragColorMod = PLAINS_GRASS_TINT; 78 | } else if (tintindex == LEAF_TINTINDEX) { 79 | fragColorMod = PLAINS_LEAF_TINT; 80 | } else if (tintindex == SPRUCE_LEAF_TINTINDEX) { 81 | fragColorMod = SPRUCE_LEAF_TINT; 82 | } else if (tintindex == BIRCH_LEAF_TINTINDEX) { 83 | fragColorMod = BIRCH_LEAF_TINT; 84 | } else if (tintindex == WATER_TINTINDEX) { 85 | fragColorMod = WATER_TINT; 86 | } 87 | 88 | // Convert back down into correct brightness 89 | if (tintindex <= WATER_TINTINDEX) { 90 | fragColorMod.rgb *= (1.0 / 0.9); 91 | } 92 | 93 | float skylight_percent = (float(skylight_value) / 60.0) * ubo.sunlight * 0.85; 94 | float blocklight_percent = blocklight_value / 60.0; 95 | float light_intensity = max(blocklight_percent, skylight_percent) * 0.85 + 0.15; 96 | 97 | uint shaded_axis = (inPackedLight >> 14) & 1; 98 | uint vertical_face = (inPackedLight >> 15) & 1; 99 | 100 | // Vary shading of vertical faces by difference between camera and the vertex. 101 | if (vertical_face > 0) { 102 | float height_difference = (ubo.camera.y - inPosition.y); 103 | float shading_modifier = max((abs(height_difference) / 15.0), 1.0); 104 | light_intensity *= max(1.0 - shading_modifier, 0.8); 105 | } 106 | 107 | light_intensity *= 1.0 - (shaded_axis * 0.2); 108 | 109 | float ao_intensity = (0.25 + float(ao) * 0.25); 110 | fragColorMod.rgb *= (ao_intensity * light_intensity); 111 | } 112 | -------------------------------------------------------------------------------- /polymer/platform/args.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace polymer { 9 | 10 | struct ArgPair { 11 | String name; 12 | String value; 13 | }; 14 | 15 | struct ArgParser { 16 | ArgPair args[16]; 17 | size_t arg_count; 18 | 19 | inline bool HasValue(const String& name) const { 20 | for (size_t i = 0; i < arg_count; ++i) { 21 | if (poly_strcmp(name, args[i].name) == 0) { 22 | return true; 23 | } 24 | } 25 | 26 | return false; 27 | } 28 | 29 | inline bool HasValue(const String* lookups, size_t count) const { 30 | for (size_t i = 0; i < count; ++i) { 31 | bool result = HasValue(lookups[i]); 32 | 33 | if (result) { 34 | return true; 35 | } 36 | } 37 | 38 | return false; 39 | } 40 | 41 | inline String GetValue(const String& name) const { 42 | for (size_t i = 0; i < arg_count; ++i) { 43 | if (poly_strcmp(name, args[i].name) == 0) { 44 | return args[i].value; 45 | } 46 | } 47 | 48 | return String(); 49 | } 50 | 51 | inline String GetValue(const String* lookups, size_t count) const { 52 | for (size_t i = 0; i < count; ++i) { 53 | String result = GetValue(lookups[i]); 54 | 55 | if (result.size > 0) { 56 | return result; 57 | } 58 | } 59 | 60 | return String(); 61 | } 62 | 63 | static ArgParser Parse(int argc, char* argv[]) { 64 | ArgParser result = {}; 65 | 66 | for (int i = 0; i < argc && i < polymer_array_count(args); ++i) { 67 | char* current = argv[i]; 68 | 69 | if (current[0] == '-') { 70 | ++current; 71 | 72 | if (*current == '-') ++current; 73 | 74 | ArgPair& pair = result.args[result.arg_count++]; 75 | 76 | pair.name = String(current); 77 | pair.value = String(); 78 | 79 | if (i < argc - 1) { 80 | char* next = argv[i + 1]; 81 | 82 | if (*next != '-') { 83 | pair.value = String(argv[i + 1]); 84 | ++i; 85 | } 86 | } 87 | } 88 | } 89 | 90 | return result; 91 | } 92 | }; 93 | 94 | struct LaunchArgs { 95 | String username; 96 | String server; 97 | u16 server_port; 98 | bool help; 99 | 100 | static LaunchArgs Create(ArgParser& args) { 101 | const String kUsernameArgs[] = {POLY_STR("username"), POLY_STR("user"), POLY_STR("u")}; 102 | const String kServerArgs[] = {POLY_STR("server"), POLY_STR("s")}; 103 | const String kHelpArgs[] = {POLY_STR("help"), POLY_STR("h")}; 104 | 105 | constexpr const char* kDefaultServerIp = "127.0.0.1"; 106 | constexpr u16 kDefaultServerPort = 25565; 107 | 108 | constexpr const char* kDefaultUsername = "polymer"; 109 | constexpr size_t kMaxUsernameSize = 16; 110 | 111 | LaunchArgs result = {}; 112 | 113 | result.username = args.GetValue(kUsernameArgs, polymer_array_count(kUsernameArgs)); 114 | result.server = args.GetValue(kServerArgs, polymer_array_count(kServerArgs)); 115 | 116 | if (result.username.size == 0) { 117 | result.username = String((char*)kDefaultUsername); 118 | } 119 | 120 | if (result.username.size > kMaxUsernameSize) { 121 | result.username.size = kMaxUsernameSize; 122 | } 123 | 124 | result.server_port = kDefaultServerPort; 125 | 126 | if (result.server.size == 0) { 127 | result.server = String((char*)kDefaultServerIp); 128 | } else { 129 | for (size_t i = 0; i < result.server.size; ++i) { 130 | if (result.server.data[i] == ':') { 131 | char* port_str = result.server.data + i + 1; 132 | 133 | result.server_port = (u16)strtol(port_str, nullptr, 10); 134 | result.server.size = i; 135 | result.server.data[i] = 0; 136 | break; 137 | } 138 | } 139 | } 140 | 141 | result.help = args.HasValue(kHelpArgs, polymer_array_count(kHelpArgs)); 142 | 143 | return result; 144 | } 145 | }; 146 | 147 | inline void PrintUsage() { 148 | printf("Polymer\n\n"); 149 | printf("Usage:\n\tpolymer.exe [OPTIONS]\n\n"); 150 | printf("OPTIONS:\n"); 151 | printf("\t-u, --user, --username\tOffline username. Default: polymer\n"); 152 | printf("\t-s, --server\t\tDirect server. Default: 127.0.0.1:25565\n"); 153 | } 154 | 155 | } // namespace polymer 156 | -------------------------------------------------------------------------------- /polymer/asset/asset_system.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace polymer { 14 | namespace asset { 15 | 16 | AssetSystem::AssetSystem() {} 17 | 18 | BlockTextureDescriptor AssetSystem::GetTextureRange(const String& texture_path) { 19 | if (block_assets->texture_descriptor_map) { 20 | BlockTextureDescriptor* find = block_assets->texture_descriptor_map->Find(MapStringKey(texture_path)); 21 | 22 | if (find) { 23 | return *find; 24 | } 25 | } 26 | 27 | BlockTextureDescriptor empty = {}; 28 | 29 | return empty; 30 | } 31 | 32 | bool AssetSystem::Load(render::VulkanRenderer& renderer, const char* jar_path, const char* blocks_path, 33 | world::BlockRegistry* registry) { 34 | ZipArchive archive; 35 | 36 | if (!archive.Open(jar_path)) { 37 | return false; 38 | } 39 | 40 | // Destroy any existing perm arena in case this gets called twice. 41 | if (perm_arena.current) { 42 | perm_arena.Destroy(); 43 | } 44 | 45 | perm_arena = CreateArena(Megabytes(256 + 64)); 46 | 47 | MemoryArena trans_arena = CreateArena(Megabytes(128)); 48 | BlockAssetLoader block_loader(perm_arena, trans_arena); 49 | 50 | if (!block_loader.Load(renderer, archive, blocks_path, registry)) { 51 | trans_arena.Destroy(); 52 | perm_arena.Destroy(); 53 | 54 | return false; 55 | } 56 | 57 | this->block_assets = block_loader.assets; 58 | 59 | trans_arena.Reset(); 60 | 61 | if (!LoadFont(renderer, perm_arena, trans_arena)) { 62 | this->glyph_page_texture = nullptr; 63 | fprintf(stderr, "Failed to load fonts.\n"); 64 | } 65 | 66 | archive.Close(); 67 | trans_arena.Destroy(); 68 | 69 | return true; 70 | } 71 | 72 | bool AssetSystem::LoadFont(render::VulkanRenderer& renderer, MemoryArena& perm_arena, MemoryArena& trans_arena) { 73 | constexpr size_t kGlyphPageWidth = 256; 74 | constexpr size_t kGlyphPageHeight = 256; 75 | constexpr size_t kGlyphPageCount = 256; 76 | 77 | MemoryRevert trans_arena_revert = trans_arena.GetReverter(); 78 | 79 | // Create a texture array to store the glyphs 80 | glyph_page_texture = renderer.CreateTextureArray(kGlyphPageWidth, kGlyphPageHeight, kGlyphPageCount, 1, false); 81 | 82 | if (!glyph_page_texture) { 83 | return false; 84 | } 85 | 86 | size_t table_size = kGlyphPageCount * kGlyphPageCount; 87 | 88 | glyph_size_table = memory_arena_push_type_count(&perm_arena, u8, table_size); 89 | memset(glyph_size_table, 0, table_size); 90 | 91 | UnihexFont font(glyph_size_table, kGlyphPageWidth, kGlyphPageWidth, kGlyphPageCount); 92 | 93 | String font_zip = asset_store->LoadObject(trans_arena, POLY_STR("minecraft/font/unifont.zip")); 94 | ZipArchive zip = {}; 95 | 96 | if (!zip.OpenFromMemory(font_zip)) { 97 | fprintf(stderr, "AssetSystem: Failed to open 'minecraft/font/unifont.zip' from memory.\n"); 98 | return false; 99 | } 100 | 101 | size_t zip_file_count; 102 | ZipArchiveElement* zip_file_elements = zip.ListFiles(&trans_arena, ".hex", &zip_file_count); 103 | 104 | if (zip_file_count == 0) { 105 | fprintf(stderr, "AssetSystem: Failed to find '*.hex' file in 'minecraft/font/unifont.zip'."); 106 | return false; 107 | } 108 | 109 | size_t unifont_size = 0; 110 | char* unifont_data = zip.ReadFile(&trans_arena, zip_file_elements[0].name, &unifont_size); 111 | 112 | if (!unifont_data) { 113 | fprintf(stderr, "AssetSystem: Failed to read '%s' in 'minecraft/font/unifont.zip'", zip_file_elements->name); 114 | return false; 115 | } 116 | 117 | if (!font.Load(perm_arena, trans_arena, String(unifont_data, unifont_size))) { 118 | return false; 119 | } 120 | 121 | render::TextureConfig texture_cfg; 122 | 123 | render::TextureArrayPushState glyph_page_push = renderer.BeginTexturePush(*glyph_page_texture); 124 | for (size_t i = 0; i < kGlyphPageCount; ++i) { 125 | u8* page_start = font.images + (256 * 256) * i; 126 | renderer.PushArrayTexture(trans_arena, glyph_page_push, page_start, i, texture_cfg); 127 | } 128 | 129 | renderer.CommitTexturePush(glyph_page_push); 130 | 131 | return true; 132 | } 133 | 134 | } // namespace asset 135 | } // namespace polymer 136 | -------------------------------------------------------------------------------- /polymer/asset/unihex_font.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace polymer { 9 | namespace asset { 10 | 11 | bool UnihexFont::Load(const char* filename, MemoryArena& perm_arena, MemoryArena& trans_arena) { 12 | String file_data = ReadEntireFile(filename, trans_arena); 13 | if (!file_data.data) { 14 | fprintf(stderr, "Failed to load font file '%s'.\n", filename); 15 | return false; 16 | } 17 | 18 | return Load(perm_arena, trans_arena, file_data); 19 | } 20 | 21 | bool UnihexFont::Load(MemoryArena& perm_arena, MemoryArena& trans_arena, String file_data) { 22 | unifont_data = file_data.data; 23 | unifont_end = file_data.data + file_data.size; 24 | 25 | images = (u8*)trans_arena.Allocate(glyph_page_width * glyph_page_height * glyph_page_count); 26 | 27 | codepoint_str = String(unifont_data, 0); 28 | 29 | // TODO: Refactor font renderer to have indirect lookups so it can handle higher than 0xFFFF codepoints. 30 | while (unifont_data < unifont_end) { 31 | char c = *unifont_data++; 32 | 33 | if (state == ReadState::Codepoint) { 34 | if (!ProcessCodepoint(c)) { 35 | fprintf(stderr, "UnihexFont: Invalid format while processing codepoint data.\n"); 36 | return false; 37 | } 38 | } else { 39 | if (!ProcessData(c)) { 40 | fprintf(stderr, "UnihexFont: Invalid format while processing image data.\n"); 41 | return false; 42 | } 43 | } 44 | } 45 | 46 | return true; 47 | } 48 | 49 | bool UnihexFont::ProcessCodepoint(char c) { 50 | if (c == '\n') { 51 | // Invalid font file. 52 | return false; 53 | } 54 | 55 | if (c == ':') { 56 | codepoint = strtol(codepoint_str.data, nullptr, 16); 57 | state = ReadState::Data; 58 | 59 | data_str.data = unifont_data; 60 | data_str.size = 0; 61 | } 62 | 63 | return true; 64 | } 65 | 66 | bool UnihexFont::ProcessData(char c) { 67 | if (c == ':') { 68 | // Invalid font file. 69 | return false; 70 | } 71 | 72 | if ((c == '\n' || c == 0)) { 73 | // Skip over handling any high codepoints since the system doesn't currently support them. 74 | if (codepoint > 0xFFFF) { 75 | codepoint_str.data = unifont_data; 76 | state = ReadState::Codepoint; 77 | return true; 78 | } 79 | 80 | size_t page_index = codepoint / 256; 81 | size_t relative_index = codepoint % 256; 82 | u8* page_start = images + (256 * 256) * page_index; 83 | u8* glyph_start = page_start + (16 * 16) * relative_index; 84 | 85 | int glyph_start_x = ((int)relative_index % 16) * 16; 86 | int glyph_start_y = ((int)relative_index / 16) * 16; 87 | 88 | if (data_str.size % 2 != 0) { 89 | // Invalid font file. 90 | return false; 91 | } 92 | 93 | // Height of all glyphs is 16. 94 | int height = 16; 95 | // Width is calculated with number of bits used divided by the required height of 16. 96 | int width = (((int)data_str.size / 2) * 8) / height; 97 | 98 | // Only 8 and 16 are supported here. 99 | if (width <= 16) { 100 | // Convert hex string into bitstream. 101 | int glyph_x = 0; 102 | int glyph_y = 0; 103 | 104 | int min_glyph_x = 15; 105 | int max_glyph_x = 0; 106 | 107 | for (int i = 0; i < data_str.size; i += 2) { 108 | char temp[3] = {}; 109 | 110 | temp[0] = data_str.data[i]; 111 | temp[1] = data_str.data[i + 1]; 112 | temp[2] = 0; 113 | 114 | u8 value = (u8)strtol(temp, nullptr, 16); 115 | 116 | for (int j = 0; j < 8; ++j) { 117 | int absolute_x = glyph_start_x + glyph_x; 118 | int absolute_y = glyph_start_y + glyph_y; 119 | 120 | bool has_value = (value & (1 << (7 - j))); 121 | page_start[absolute_y * glyph_page_width + absolute_x] = has_value ? 0xFF : 0; 122 | 123 | if (has_value) { 124 | if (glyph_x < min_glyph_x) { 125 | min_glyph_x = glyph_x; 126 | } 127 | 128 | if (glyph_x > max_glyph_x) { 129 | max_glyph_x = glyph_x; 130 | } 131 | } 132 | 133 | if (++glyph_x >= width) { 134 | glyph_x = 0; 135 | ++glyph_y; 136 | } 137 | } 138 | } 139 | 140 | glyph_size_table[codepoint] = (min_glyph_x << 4) | max_glyph_x; 141 | } 142 | 143 | if (c == 0) { 144 | return false; 145 | } 146 | 147 | codepoint_str.data = unifont_data; 148 | state = ReadState::Codepoint; 149 | } 150 | 151 | ++data_str.size; 152 | return true; 153 | } 154 | 155 | } // namespace asset 156 | } // namespace polymer 157 | -------------------------------------------------------------------------------- /polymer/connection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #ifdef _WIN32 13 | #include 14 | #include 15 | #define POLY_EWOULDBLOCK WSAEWOULDBLOCK 16 | #else 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #define closesocket close 26 | 27 | #include 28 | #define POLY_EWOULDBLOCK EWOULDBLOCK 29 | #endif 30 | 31 | namespace polymer { 32 | 33 | static int GetLastErrorCode() { 34 | #if defined(_WIN32) || defined(WIN32) 35 | int err = WSAGetLastError(); 36 | #else 37 | int err = errno; 38 | #endif 39 | 40 | return err; 41 | } 42 | 43 | Connection::Connection(MemoryArena& arena) 44 | : read_buffer(arena, 0), write_buffer(arena, 0), interpreter(nullptr), builder(arena) {} 45 | 46 | Connection::TickResult Connection::Tick() { 47 | RingBuffer* rb = &read_buffer; 48 | RingBuffer* wb = &write_buffer; 49 | 50 | while (wb->write_offset != wb->read_offset) { 51 | int bytes_sent = 0; 52 | 53 | if (wb->write_offset > wb->read_offset) { 54 | // Send up to the write offset 55 | bytes_sent = send(fd, (char*)wb->data + wb->read_offset, (int)(wb->write_offset - wb->read_offset), 0); 56 | } else if (wb->write_offset < wb->read_offset) { 57 | // Send up to the end of the buffer then loop again to send the remaining up to the write offset 58 | bytes_sent = send(fd, (char*)wb->data + wb->read_offset, (int)(wb->size - wb->read_offset), 0); 59 | } 60 | 61 | if (bytes_sent > 0) { 62 | wb->read_offset = (wb->read_offset + bytes_sent) % wb->size; 63 | } 64 | } 65 | 66 | int bytes_recv = 1; 67 | while (bytes_recv > 0) { 68 | bytes_recv = recv(fd, (char*)rb->data + rb->write_offset, (u32)rb->GetFreeSize(), 0); 69 | 70 | if (bytes_recv == 0) { 71 | this->connected = false; 72 | return TickResult::ConnectionClosed; 73 | } else if (bytes_recv < 0) { 74 | int err = GetLastErrorCode(); 75 | 76 | if (err == POLY_EWOULDBLOCK) { 77 | return TickResult::Success; 78 | } 79 | 80 | fprintf(stderr, "Unexpected socket error: %d\n", err); 81 | this->Disconnect(); 82 | return TickResult::ConnectionError; 83 | } else if (bytes_recv > 0) { 84 | rb->write_offset = (rb->write_offset + bytes_recv) % rb->size; 85 | 86 | assert(interpreter); 87 | 88 | if (interpreter->Interpret() == 0) break; 89 | } 90 | } 91 | 92 | return TickResult::Success; 93 | } 94 | 95 | ConnectResult Connection::Connect(const char* ip, u16 port) { 96 | addrinfo hints = {0}, *result = nullptr; 97 | 98 | hints.ai_family = AF_INET; 99 | hints.ai_socktype = SOCK_STREAM; 100 | hints.ai_protocol = IPPROTO_TCP; 101 | 102 | this->fd = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); 103 | 104 | if (this->fd < 0) { 105 | return ConnectResult::ErrorSocket; 106 | } 107 | 108 | char service[32]; 109 | 110 | sprintf(service, "%hu", port); 111 | 112 | if (getaddrinfo(ip, service, &hints, &result) != 0) { 113 | return ConnectResult::ErrorAddrInfo; 114 | } 115 | 116 | addrinfo* info = nullptr; 117 | 118 | for (info = result; info != nullptr; info = info->ai_next) { 119 | sockaddr_in* sockaddr = (sockaddr_in*)info->ai_addr; 120 | 121 | if (::connect(this->fd, (struct sockaddr*)sockaddr, sizeof(sockaddr_in)) == 0) { 122 | break; 123 | } 124 | } 125 | 126 | freeaddrinfo(result); 127 | 128 | if (!info) { 129 | return ConnectResult::ErrorConnect; 130 | } 131 | 132 | this->connected = true; 133 | 134 | return ConnectResult::Success; 135 | } 136 | 137 | void Connection::Disconnect() { 138 | closesocket(this->fd); 139 | this->connected = false; 140 | } 141 | 142 | void Connection::SetBlocking(bool blocking) { 143 | unsigned long mode = blocking ? 0 : 1; 144 | 145 | #ifdef _WIN32 146 | ioctlsocket(this->fd, FIONBIO, &mode); 147 | #else 148 | int flags = fcntl(this->fd, F_GETFL, 0); 149 | 150 | flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); 151 | 152 | fcntl(this->fd, F_SETFL, flags); 153 | #endif 154 | } 155 | 156 | #ifdef _WIN32 157 | struct NetworkInitializer { 158 | NetworkInitializer() { 159 | WSADATA wsa; 160 | 161 | if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { 162 | fprintf(stderr, "Error WSAStartup: %d\n", WSAGetLastError()); 163 | exit(1); 164 | } 165 | } 166 | }; 167 | NetworkInitializer _net_init; 168 | #endif 169 | 170 | } // namespace polymer 171 | -------------------------------------------------------------------------------- /polymer/render/block_mesher.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_RENDER_BLOCKMESHER_H_ 2 | #define POLYMER_RENDER_BLOCKMESHER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace polymer { 10 | 11 | namespace world { 12 | 13 | struct BlockRegistry; 14 | struct ChunkSection; 15 | struct World; 16 | 17 | } // namespace world 18 | 19 | namespace render { 20 | 21 | struct ChunkBuildContext { 22 | s32 chunk_x; 23 | s32 chunk_z; 24 | 25 | u32 x_index = 0; 26 | u32 z_index = 0; 27 | 28 | world::ChunkSection* section = nullptr; 29 | world::ChunkSection* east_section = nullptr; 30 | world::ChunkSection* west_section = nullptr; 31 | world::ChunkSection* north_section = nullptr; 32 | world::ChunkSection* south_section = nullptr; 33 | world::ChunkSection* south_east_section = nullptr; 34 | world::ChunkSection* south_west_section = nullptr; 35 | world::ChunkSection* north_east_section = nullptr; 36 | world::ChunkSection* north_west_section = nullptr; 37 | 38 | ChunkBuildContext(s32 chunk_x, s32 chunk_z) : chunk_x(chunk_x), chunk_z(chunk_z) {} 39 | 40 | bool IsBuildable() const; 41 | bool GetNeighbors(world::World* world); 42 | }; 43 | 44 | struct ChunkVertexData { 45 | u8* vertices[render::kRenderLayerCount]; 46 | size_t vertex_count[render::kRenderLayerCount]; 47 | 48 | u16* indices[render::kRenderLayerCount]; 49 | size_t index_count[render::kRenderLayerCount]; 50 | 51 | ChunkVertexData() { 52 | for (size_t i = 0; i < render::kRenderLayerCount; ++i) { 53 | vertices[i] = nullptr; 54 | vertex_count[i] = 0; 55 | 56 | indices[i] = nullptr; 57 | index_count[i] = 0; 58 | } 59 | } 60 | 61 | inline void SetVertices(render::RenderLayer layer, u8* new_vertices, size_t new_vertex_count) { 62 | vertices[(size_t)layer] = new_vertices; 63 | vertex_count[(size_t)layer] = new_vertex_count; 64 | } 65 | 66 | inline void SetIndices(render::RenderLayer layer, u16* new_indices, size_t new_index_count) { 67 | indices[(size_t)layer] = new_indices; 68 | index_count[(size_t)layer] = new_index_count; 69 | } 70 | }; 71 | 72 | struct BlockMesherMapping { 73 | world::BlockIdRange water_range; 74 | world::BlockIdRange kelp_range; 75 | world::BlockIdRange seagrass_range; 76 | world::BlockIdRange tall_seagrass_range; 77 | world::BlockIdRange lava_range; 78 | world::BlockIdRange lily_pad_range; 79 | world::BlockIdRange cave_air_range; 80 | world::BlockIdRange void_air_range; 81 | world::BlockIdRange dirt_path_range; 82 | 83 | void Initialize(world::BlockRegistry& registry) { 84 | Load(registry, POLY_STR("minecraft:water"), &water_range); 85 | Load(registry, POLY_STR("minecraft:kelp"), &kelp_range); 86 | Load(registry, POLY_STR("minecraft:seagrass"), &seagrass_range); 87 | Load(registry, POLY_STR("minecraft:tall_seagrass"), &tall_seagrass_range); 88 | Load(registry, POLY_STR("minecraft:lava"), &lava_range); 89 | Load(registry, POLY_STR("minecraft:lily_pad"), &lily_pad_range); 90 | Load(registry, POLY_STR("minecraft:cave_air"), &cave_air_range); 91 | Load(registry, POLY_STR("minecraft:void_air"), &void_air_range); 92 | Load(registry, POLY_STR("minecraft:dirt_path"), &dirt_path_range); 93 | } 94 | 95 | private: 96 | inline void Load(world::BlockRegistry& registry, const String& str, world::BlockIdRange* out) { 97 | world::BlockIdRange* range = registry.name_map.Find(str); 98 | 99 | if (range) { 100 | *out = *range; 101 | } 102 | } 103 | }; 104 | 105 | struct BlockMesher { 106 | MemoryArena& trans_arena; 107 | asset::AssetSystem& assets; 108 | world::BlockRegistry& block_registry; 109 | 110 | MemoryArena vertex_arenas[render::kRenderLayerCount]; 111 | MemoryArena index_arenas[render::kRenderLayerCount]; 112 | 113 | BlockMesherMapping mapping; 114 | 115 | BlockMesher(MemoryArena& trans_arena, asset::AssetSystem& assets, world::BlockRegistry& block_registry) 116 | : trans_arena(trans_arena), assets(assets), block_registry(block_registry) { 117 | for (size_t i = 0; i < render::kRenderLayerCount; ++i) { 118 | vertex_arenas[i] = CreateArena(Megabytes(16)); 119 | index_arenas[i] = CreateArena(Megabytes(4)); 120 | } 121 | } 122 | 123 | ~BlockMesher() { 124 | for (size_t i = 0; i < render::kRenderLayerCount; ++i) { 125 | vertex_arenas[i].Destroy(); 126 | index_arenas[i].Destroy(); 127 | } 128 | } 129 | 130 | void Reset() { 131 | for (size_t i = 0; i < render::kRenderLayerCount; ++i) { 132 | vertex_arenas[i].Reset(); 133 | index_arenas[i].Reset(); 134 | } 135 | } 136 | 137 | ChunkVertexData CreateMesh(ChunkBuildContext* ctx, s32 chunk_y); 138 | }; 139 | 140 | } // namespace render 141 | } // namespace polymer 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /polymer/world/dimension.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace polymer { 9 | namespace world { 10 | 11 | inline void ProcessDimensionFlag(DimensionType& type, nbt::TagCompound& element_compound, String name, u32 flag) { 12 | nbt::Tag* flag_tag = element_compound.GetNamedTag(name); 13 | 14 | if (flag_tag && flag_tag->type == nbt::TagType::Byte) { 15 | nbt::TagByte* data_tag = (nbt::TagByte*)flag_tag->tag; 16 | 17 | if (data_tag->data) { 18 | type.flags |= flag; 19 | } 20 | } 21 | } 22 | 23 | template 24 | inline void ProcessDimensionData(DimensionType& type, nbt::TagCompound& element_compound, String name, 25 | nbt::TagType tag_type, T* out) { 26 | nbt::Tag* named_tag = element_compound.GetNamedTag(name); 27 | 28 | if (named_tag && named_tag->type == tag_type) { 29 | DataTag* data_tag = (DataTag*)named_tag->tag; 30 | 31 | *out = data_tag->data; 32 | } 33 | } 34 | 35 | inline void ProcessDimensionInt(DimensionType& type, nbt::TagCompound& element_compound, String name, int* out) { 36 | ProcessDimensionData(type, element_compound, name, nbt::TagType::Int, out); 37 | } 38 | 39 | inline void ProcessDimensionFloat(DimensionType& type, nbt::TagCompound& element_compound, String name, float* out) { 40 | ProcessDimensionData(type, element_compound, name, nbt::TagType::Float, out); 41 | } 42 | 43 | inline void ProcessDimensionDouble(DimensionType& type, nbt::TagCompound& element_compound, String name, double* out) { 44 | ProcessDimensionData(type, element_compound, name, nbt::TagType::Double, out); 45 | } 46 | 47 | inline void ProcessDimensionLong(DimensionType& type, nbt::TagCompound& element_compound, String name, u64* out) { 48 | ProcessDimensionData(type, element_compound, name, nbt::TagType::Long, out); 49 | } 50 | 51 | void DimensionCodec::Initialize(MemoryArena& arena, size_t size) { 52 | type_count = size; 53 | types = memory_arena_push_type_count(&arena, DimensionType, size); 54 | memset(types, 0, sizeof(DimensionType) * size); 55 | } 56 | 57 | void DimensionCodec::ParseType(MemoryArena& arena, nbt::TagCompound& nbt, DimensionType& type) { 58 | ProcessDimensionFlag(type, nbt, POLY_STR("piglin_safe"), DimensionFlag_PiglinSafe); 59 | ProcessDimensionFlag(type, nbt, POLY_STR("natural"), DimensionFlag_Natural); 60 | ProcessDimensionFlag(type, nbt, POLY_STR("respawn_anchor_works"), DimensionFlag_RespawnAnchor); 61 | ProcessDimensionFlag(type, nbt, POLY_STR("has_skylight"), DimensionFlag_HasSkylight); 62 | ProcessDimensionFlag(type, nbt, POLY_STR("bed_works"), DimensionFlag_BedWorks); 63 | ProcessDimensionFlag(type, nbt, POLY_STR("has_raids"), DimensionFlag_HasRaids); 64 | ProcessDimensionFlag(type, nbt, POLY_STR("ultrawarm"), DimensionFlag_Ultrawarm); 65 | ProcessDimensionFlag(type, nbt, POLY_STR("has_ceiling"), DimensionFlag_HasCeiling); 66 | 67 | ProcessDimensionInt(type, nbt, POLY_STR("min_y"), &type.min_y); 68 | ProcessDimensionInt(type, nbt, POLY_STR("height"), &type.height); 69 | ProcessDimensionInt(type, nbt, POLY_STR("logical_height"), &type.logical_height); 70 | 71 | ProcessDimensionFloat(type, nbt, POLY_STR("ambient_light"), &type.ambient_light); 72 | ProcessDimensionDouble(type, nbt, POLY_STR("coordinate_scale"), &type.coordinate_scale); 73 | 74 | ProcessDimensionLong(type, nbt, POLY_STR("fixed_time"), &type.fixed_time); 75 | 76 | // TODO: effects, infiniburn 77 | } 78 | 79 | void DimensionCodec::ParseDefaultType(MemoryArena& arena, size_t index) { 80 | if (index >= 4) { 81 | fprintf(stderr, "Failed to receive dimension data for non-core dimension.\n"); 82 | exit(1); 83 | } 84 | 85 | static const DimensionType kCoreTypes[] = { 86 | DimensionType{POLY_STR("minecraft:overworld"), String(), String(), 0, 58, -64, 384, 384, 0.0f, 1.0f, 0}, 87 | DimensionType{POLY_STR("minecraft:overworld_caves"), String(), String(), 1, 186, -64, 384, 384, 0.0f, 1.0f, 0}, 88 | DimensionType{POLY_STR("minecraft:the_end"), String(), String(), 2, 32, 0, 256, 256, 0.0f, 1.0f, 6000}, 89 | DimensionType{POLY_STR("minecraft:the_nether"), String(), String(), 3, 197, 0, 256, 128, 0.1f, 8.0f, 18000}, 90 | }; 91 | 92 | types[index] = kCoreTypes[index]; 93 | } 94 | 95 | DimensionType* DimensionCodec::GetDimensionTypeById(s32 id) { 96 | for (size_t i = 0; i < type_count; ++i) { 97 | DimensionType* type = types + i; 98 | 99 | if (type->id == id) return type; 100 | } 101 | 102 | return nullptr; 103 | } 104 | 105 | DimensionType* DimensionCodec::GetDimensionTypeByName(const String& identifier) { 106 | for (size_t i = 0; i < type_count; ++i) { 107 | DimensionType* type = types + i; 108 | 109 | if (poly_strcmp(identifier, type->name) == 0) { 110 | return type; 111 | } 112 | } 113 | 114 | return nullptr; 115 | } 116 | 117 | } // namespace world 118 | } // namespace polymer 119 | -------------------------------------------------------------------------------- /polymer/world/block.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_BLOCK_H_ 2 | #define POLYMER_BLOCK_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace polymer { 9 | namespace world { 10 | 11 | enum class BlockFace { Down, Up, North, South, West, East }; 12 | 13 | inline BlockFace GetOppositeFace(BlockFace face) { 14 | switch (face) { 15 | case BlockFace::Down: 16 | return BlockFace::Up; 17 | case BlockFace::Up: 18 | return BlockFace::Down; 19 | case BlockFace::North: 20 | return BlockFace::South; 21 | case BlockFace::South: 22 | return BlockFace::North; 23 | case BlockFace::West: 24 | return BlockFace::East; 25 | case BlockFace::East: 26 | return BlockFace::West; 27 | } 28 | 29 | return BlockFace::Down; 30 | } 31 | 32 | inline Vector3f GetFaceDirection(BlockFace face) { 33 | static const Vector3f kDirections[] = { 34 | Vector3f(0, -1, 0), Vector3f(0, 1, 0), Vector3f(0, 0, -1), 35 | Vector3f(0, 0, 1), Vector3f(-1, 0, 0), Vector3f(1, 0, 0), 36 | }; 37 | 38 | return kDirections[(size_t)face]; 39 | } 40 | 41 | struct FaceQuad { 42 | Vector3f bl_pos; 43 | Vector3f br_pos; 44 | Vector3f tl_pos; 45 | Vector3f tr_pos; 46 | 47 | Vector2f bl_uv; 48 | Vector2f br_uv; 49 | Vector2f tl_uv; 50 | Vector2f tr_uv; 51 | 52 | inline BoundingBox GetBoundsAt(const Vector3f& v) { 53 | BoundingBox result; 54 | 55 | result.min.x = ChooseMin(bl_pos.x, br_pos.x, tl_pos.x, tr_pos.x) + v.x; 56 | result.min.y = ChooseMin(bl_pos.y, br_pos.y, tl_pos.y, tr_pos.y) + v.y; 57 | 58 | result.max.x = ChooseMax(bl_pos.x, br_pos.x, tl_pos.x, tr_pos.x) + v.x; 59 | result.max.y = ChooseMax(bl_pos.y, br_pos.y, tl_pos.y, tr_pos.y) + v.y; 60 | 61 | return result; 62 | } 63 | 64 | private: 65 | inline static float ChooseMin(float x0, float x1, float x2, float x3) { 66 | float m1 = x0 < x1 ? x0 : x1; 67 | float m2 = x2 < x3 ? x2 : x3; 68 | return m1 < m2 ? m1 : m2; 69 | } 70 | 71 | inline static float ChooseMax(float x0, float x1, float x2, float x3) { 72 | float m1 = x0 > x1 ? x0 : x1; 73 | float m2 = x2 > x3 ? x2 : x3; 74 | return m1 > m2 ? m1 : m2; 75 | } 76 | }; 77 | 78 | constexpr u32 kHighestTintIndex = 0b111111; 79 | struct RenderableFace { 80 | Vector2f uv_from; 81 | Vector2f uv_to; 82 | FaceQuad* quad; 83 | 84 | struct { 85 | u32 texture_id : 31; 86 | u32 render : 1; 87 | 88 | u32 transparency : 1; 89 | u32 cullface : 3; 90 | u32 render_layer : 3; 91 | u32 random_flip : 1; 92 | u32 frame_count : 7; 93 | u32 full_occlusion : 1; 94 | u32 tintindex : 6; 95 | u32 frametime : 9; 96 | u32 interpolated : 1; 97 | }; 98 | }; 99 | 100 | struct ElementRotation { 101 | Vector3f origin; 102 | Vector3f axis; 103 | int angle; 104 | 105 | u8 rescale : 1; 106 | u8 uvlock : 1; 107 | u8 padding : 6; 108 | }; 109 | 110 | struct BlockElement { 111 | RenderableFace faces[6]; 112 | Vector3f from; 113 | Vector3f to; 114 | 115 | struct { 116 | u32 occluding : 1; 117 | u32 shade : 1; 118 | u32 rescale : 1; 119 | u32 padding : 29; 120 | }; 121 | 122 | inline RenderableFace& GetFace(BlockFace face) { 123 | return faces[(size_t)face]; 124 | } 125 | }; 126 | 127 | struct BlockModel { 128 | size_t element_count; 129 | BlockElement elements[48]; 130 | 131 | u32 has_occluding : 1; 132 | u32 has_transparency : 1; 133 | u32 has_shaded : 1; 134 | u32 has_leaves : 1; 135 | u32 has_glass : 1; 136 | u32 has_variant_rotation : 1; 137 | u32 ambient_occlusion : 1; 138 | u32 random_horizontal_offset : 1; 139 | u32 random_vertical_offset : 1; 140 | u32 random_vertical_uv : 1; 141 | u32 is_cube : 1; 142 | u32 padding : 21; 143 | 144 | inline bool HasOccluding() const { 145 | return has_occluding; 146 | } 147 | 148 | inline bool HasTransparency() const { 149 | return has_transparency; 150 | } 151 | 152 | inline bool HasShadedElement() const { 153 | return has_shaded; 154 | } 155 | }; 156 | 157 | struct BlockStateInfo { 158 | char name[48]; 159 | size_t name_length; 160 | }; 161 | 162 | struct BlockState { 163 | u32 id; 164 | BlockStateInfo* info; 165 | 166 | BlockModel model; 167 | float x; 168 | float y; 169 | 170 | struct { 171 | u32 uvlock : 1; 172 | // TODO: Need a better way of storing the properties. Just using the blockstate flag field for fluid levels until a 173 | // better way is implemented. 174 | u32 leveled : 1; 175 | u32 level : 4; 176 | u32 padding : 26; 177 | }; 178 | }; 179 | 180 | struct BlockIdRange { 181 | u32 base; 182 | u32 count; 183 | 184 | BlockIdRange() : base(0), count(0) {} 185 | BlockIdRange(u32 base, u32 count) : base(base), count(count) {} 186 | 187 | bool Contains(u32 bid) const { 188 | return bid >= base && bid < base + count; 189 | } 190 | 191 | bool operator==(const BlockIdRange& other) const { 192 | return base == other.base && count == other.count; 193 | } 194 | }; 195 | 196 | struct BlockRegistry { 197 | size_t state_count = 0; 198 | BlockState* states; 199 | 200 | size_t info_count = 0; 201 | BlockStateInfo* infos; 202 | 203 | size_t property_count = 0; 204 | String* properties; 205 | 206 | // Map block name to block id 207 | HashMap name_map; 208 | 209 | BlockRegistry(MemoryArena& arena) : name_map(arena) {} 210 | }; 211 | 212 | } // namespace world 213 | } // namespace polymer 214 | 215 | #endif 216 | -------------------------------------------------------------------------------- /polymer/protocol.cpp: -------------------------------------------------------------------------------- 1 | #include "protocol.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace polymer { 7 | 8 | namespace outbound { 9 | namespace handshake { 10 | 11 | void SendHandshake(Connection& connection, u32 version, const char* address, size_t address_size, u16 port, 12 | ProtocolState state_request) { 13 | auto& builder = connection.builder; 14 | 15 | builder.WriteVarInt(version); 16 | builder.WriteString(address, address_size); 17 | builder.WriteU16(port); 18 | builder.WriteVarInt((u64)state_request); 19 | 20 | builder.Commit(connection.write_buffer, 0x00); 21 | connection.protocol_state = state_request; 22 | } 23 | 24 | } // namespace handshake 25 | 26 | namespace login { 27 | 28 | void SendLoginStart(Connection& connection, const char* username, size_t username_size) { 29 | auto& builder = connection.builder; 30 | 31 | builder.WriteString(username, username_size); 32 | builder.WriteU64(0); // UUID start 33 | builder.WriteU64(0); // UUID end 34 | 35 | builder.Commit(connection.write_buffer, (u32)ProtocolId::LoginStart); 36 | } 37 | 38 | void SendAcknowledged(Connection& connection) { 39 | auto& builder = connection.builder; 40 | 41 | builder.Commit(connection.write_buffer, (u32)ProtocolId::LoginAcknowledged); 42 | } 43 | 44 | } // namespace login 45 | 46 | namespace configuration { 47 | 48 | void SendClientInformation(Connection& connection, u8 view_distance, u8 skin_bitmask, u8 main_hand) { 49 | auto& builder = connection.builder; 50 | 51 | builder.WriteString(POLY_STR("en_GB")); // Locale 52 | builder.WriteU8(view_distance); 53 | builder.WriteVarInt(0); // ChatMode enabled 54 | builder.WriteU8(1); // Chat Colors 55 | builder.WriteU8(skin_bitmask); 56 | builder.WriteVarInt(main_hand); 57 | builder.WriteU8(0); // Text filtering 58 | builder.WriteU8(1); // Allow listing 59 | 60 | enum class ParticleMode { 61 | All, 62 | Decreased, 63 | Minimal, 64 | }; 65 | 66 | ParticleMode particle_mode = ParticleMode::All; 67 | 68 | builder.WriteVarInt((u64)particle_mode); 69 | 70 | builder.Commit(connection.write_buffer, (u32)ProtocolId::ClientInformation); 71 | } 72 | 73 | void SendKeepAlive(Connection& connection, u64 id) { 74 | auto& builder = connection.builder; 75 | 76 | builder.WriteU64(id); 77 | 78 | builder.Commit(connection.write_buffer, (u32)ProtocolId::KeepAlive); 79 | } 80 | 81 | void SendPong(Connection& connection, u32 id) { 82 | auto& builder = connection.builder; 83 | 84 | builder.WriteU32(id); 85 | 86 | builder.Commit(connection.write_buffer, (u32)ProtocolId::Pong); 87 | } 88 | 89 | void SendFinish(Connection& connection) { 90 | auto& builder = connection.builder; 91 | 92 | builder.Commit(connection.write_buffer, (u32)ProtocolId::AcknowledgeFinish); 93 | } 94 | 95 | void SendKnownPacks(Connection& connection) { 96 | auto& builder = connection.builder; 97 | 98 | builder.WriteVarInt(1); 99 | builder.WriteString(POLY_STR("minecraft")); 100 | builder.WriteString(POLY_STR("core")); 101 | builder.WriteString(POLY_STR("1.21")); 102 | 103 | builder.Commit(connection.write_buffer, (u32)ProtocolId::KnownPacks); 104 | } 105 | 106 | } // namespace configuration 107 | 108 | namespace play { 109 | 110 | void SendKeepAlive(Connection& connection, u64 id) { 111 | auto& builder = connection.builder; 112 | 113 | builder.WriteU64(id); 114 | 115 | builder.Commit(connection.write_buffer, (u32)ProtocolId::KeepAlive); 116 | } 117 | 118 | void SendTeleportConfirm(Connection& connection, u64 id) { 119 | auto& builder = connection.builder; 120 | 121 | builder.WriteVarInt(id); 122 | 123 | builder.Commit(connection.write_buffer, (u32)ProtocolId::TeleportConfirm); 124 | } 125 | 126 | void SendPlayerPositionAndRotation(Connection& connection, const Vector3f& position, float yaw, float pitch, 127 | PlayerMoveFlags flags) { 128 | auto& builder = connection.builder; 129 | 130 | builder.WriteDouble(position.x); 131 | builder.WriteDouble(position.y); 132 | builder.WriteDouble(position.z); 133 | 134 | builder.WriteFloat(yaw); 135 | builder.WriteFloat(pitch); 136 | 137 | builder.WriteU8(flags); 138 | 139 | builder.Commit(connection.write_buffer, (u32)ProtocolId::PlayPositionAndRotation); 140 | } 141 | 142 | void SendChatMessage(Connection& connection, const String& message) { 143 | auto& builder = connection.builder; 144 | 145 | u64 timestamp = 0; 146 | u64 salt = 0; 147 | u64 message_count = 0; 148 | 149 | builder.WriteString(message); 150 | builder.WriteU64(timestamp); 151 | builder.WriteU64(salt); 152 | builder.WriteU8(0); // HasSignature 153 | builder.WriteVarInt(message_count); 154 | 155 | const u32 kBitsetSize = 20; 156 | const u32 kEmptyBitsetBytes = (kBitsetSize + 8) / 8; 157 | 158 | for (size_t i = 0; i < kEmptyBitsetBytes; ++i) { 159 | builder.WriteU8(0); 160 | } 161 | 162 | builder.Commit(connection.write_buffer, (u32)ProtocolId::ChatMessage); 163 | } 164 | 165 | void SendChatCommand(Connection& connection, const String& message) { 166 | auto& builder = connection.builder; 167 | 168 | builder.WriteString(message); 169 | 170 | builder.Commit(connection.write_buffer, (u32)ProtocolId::ChatCommand); 171 | } 172 | 173 | void SendChunkBatchReceived(Connection& connection, float chunks_per_tick) { 174 | auto& builder = connection.builder; 175 | 176 | builder.WriteFloat(chunks_per_tick); 177 | 178 | builder.Commit(connection.write_buffer, (u32)ProtocolId::ChunkBatchReceived); 179 | } 180 | 181 | void SendClientStatus(Connection& connection, ClientStatusAction action) { 182 | auto& builder = connection.builder; 183 | 184 | builder.WriteVarInt((u64)action); 185 | 186 | builder.Commit(connection.write_buffer, (u32)ProtocolId::ClientStatus); 187 | } 188 | 189 | } // namespace play 190 | 191 | } // namespace outbound 192 | 193 | } // namespace polymer 194 | -------------------------------------------------------------------------------- /polymer/render/render.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_RENDER_RENDER_H_ 2 | #define POLYMER_RENDER_RENDER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace polymer { 15 | namespace render { 16 | 17 | constexpr size_t kMaxFramesInFlight = 2; 18 | 19 | struct QueueFamilyIndices { 20 | u32 graphics; 21 | bool has_graphics; 22 | 23 | u32 present; 24 | bool has_present; 25 | 26 | bool IsComplete() { 27 | return has_graphics && has_present; 28 | } 29 | }; 30 | 31 | struct RenderMesh { 32 | VkBuffer vertex_buffer; 33 | VmaAllocation vertex_allocation; 34 | u32 vertex_count; 35 | 36 | VkBuffer index_buffer; 37 | VmaAllocation index_allocation; 38 | u32 index_count; 39 | }; 40 | 41 | struct UniformBuffer { 42 | VmaAllocator allocator; 43 | 44 | VkBuffer uniform_buffers[kMaxFramesInFlight]; 45 | VmaAllocation uniform_allocations[kMaxFramesInFlight]; 46 | 47 | void Create(VmaAllocator allocator, size_t size); 48 | 49 | inline void Destroy() { 50 | for (u32 i = 0; i < kMaxFramesInFlight; ++i) { 51 | vmaDestroyBuffer(allocator, uniform_buffers[i], uniform_allocations[i]); 52 | } 53 | } 54 | 55 | void Set(size_t frame, void* data, size_t data_size); 56 | }; 57 | 58 | struct DescriptorSet { 59 | VkDescriptorSet descriptors[kMaxFramesInFlight]; 60 | 61 | inline VkDescriptorSet& operator[](size_t index) { 62 | return descriptors[index]; 63 | } 64 | }; 65 | 66 | struct VulkanRenderer { 67 | Platform* platform = nullptr; 68 | RenderConfig render_config; 69 | 70 | MemoryArena* trans_arena = nullptr; 71 | MemoryArena* perm_arena = nullptr; 72 | PolymerWindow hwnd; 73 | ExtensionRequest extension_request; 74 | 75 | VkInstance instance; 76 | VkDebugUtilsMessengerEXT debug_messenger; 77 | VkSurfaceKHR surface; 78 | VkPhysicalDevice physical_device; 79 | VkDevice device; 80 | 81 | Swapchain swapchain; 82 | 83 | VkQueue graphics_queue; 84 | VkQueue present_queue; 85 | 86 | VmaAllocator allocator; 87 | 88 | // TODO: This should be pulled out to be managed per-thread 89 | VkDescriptorPool descriptor_pool; 90 | // TODO: This should be pulled out to be managed per-thread 91 | VkCommandPool command_pool; 92 | 93 | VkSemaphore render_complete_semaphores[kMaxFramesInFlight]; 94 | VkSemaphore image_available_semaphores[kMaxFramesInFlight]; 95 | VkFence frame_fences[kMaxFramesInFlight]; 96 | 97 | VulkanTextureManager texture_manager; 98 | 99 | size_t current_frame = 0; 100 | u32 current_image = 0; 101 | bool render_paused; 102 | bool invalid_swapchain; 103 | 104 | // TODO: Pull this out into a freelist so multiple oneshots can be built up at once. 105 | // TODO: This should be pulled out to be managed per-thread 106 | VkCommandBuffer oneshot_command_buffer; 107 | 108 | // A list of staging buffers that need to be freed after pushing the oneshot allocation command buffer. 109 | VkBuffer staging_buffers[2048]; 110 | VmaAllocation staging_allocs[2048]; 111 | size_t staging_buffer_count = 0; 112 | 113 | bool Initialize(PolymerWindow window, RenderConfig cfg); 114 | void RecreateSwapchain(); 115 | bool BeginFrame(); 116 | 117 | void Render(); 118 | void Shutdown(); 119 | 120 | // Uses staging buffer to push data to the gpu and returns the allocation buffers. 121 | RenderMesh AllocateMesh(u8* vertex_data, size_t vertex_data_size, size_t vertex_count, u16* index_data, 122 | size_t index_count); 123 | void FreeMesh(RenderMesh* mesh); 124 | 125 | VulkanTexture* CreateTexture(TextureConfig cfg, u32 width, u32 height, VkImageType image_type, 126 | VkImageViewType view_type, VkFormat format, VkImageTiling tiling); 127 | 128 | TextureArrayPushState BeginTexturePush(VulkanTexture& texture); 129 | void CommitTexturePush(TextureArrayPushState& state); 130 | 131 | VulkanTexture* CreateTextureArray(size_t width, size_t height, size_t layers, int channels = 4, 132 | bool enable_mips = true); 133 | void PushArrayTexture(MemoryArena& temp_arena, TextureArrayPushState& state, u8* texture, size_t index, 134 | const TextureConfig& cfg); 135 | void FreeTextureArray(VulkanTexture& texture); 136 | 137 | void BeginMeshAllocation(); 138 | void EndMeshAllocation(); 139 | 140 | void WaitForIdle(); 141 | 142 | inline const VkExtent2D& GetExtent() const { 143 | return swapchain.extent; 144 | } 145 | 146 | private: 147 | bool PushStagingBuffer(u8* data, size_t data_size, VkBuffer* buffer, VmaAllocation* allocation, 148 | VkBufferUsageFlagBits usage_type); 149 | 150 | u32 FindMemoryType(u32 type_filter, VkMemoryPropertyFlags properties); 151 | 152 | void GenerateArrayMipmaps(VulkanTexture& texture, u32 index); 153 | void TransitionImageLayout(VkImage image, VkFormat format, VkImageLayout old_layout, VkImageLayout new_layout, 154 | u32 base_layer, u32 layer_count, u32 mips); 155 | void BeginOneShotCommandBuffer(); 156 | void EndOneShotCommandBuffer(); 157 | 158 | void CreateSyncObjects(); 159 | void CreateCommandPool(); 160 | void CreateDescriptorPool(); 161 | 162 | u32 AddUniqueQueue(VkDeviceQueueCreateInfo* infos, u32 count, u32 queue_index); 163 | void CreateLogicalDevice(); 164 | QueueFamilyIndices FindQueueFamilies(VkPhysicalDevice device); 165 | bool IsDeviceSuitable(VkPhysicalDevice device); 166 | bool DeviceHasExtensions(VkPhysicalDevice device); 167 | bool PickPhysicalDevice(); 168 | void SetupDebugMessenger(); 169 | bool CheckValidationLayerSupport(); 170 | bool CreateInstance(); 171 | }; 172 | 173 | } // namespace render 174 | } // namespace polymer 175 | 176 | #endif 177 | -------------------------------------------------------------------------------- /polymer/render/render_util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace polymer { 9 | namespace render { 10 | 11 | inline float GetColorGamma(int color) { 12 | return powf((color & 0xFF) / 255.0f, 2.2f); 13 | } 14 | 15 | inline float GetColorGamma(int a, int b, int c, int d) { 16 | float an = a / 255.0f; 17 | float bn = b / 255.0f; 18 | float cn = c / 255.0f; 19 | float dn = d / 255.0f; 20 | 21 | return (powf(an, 2.2f) + powf(bn, 2.2f) + powf(cn, 2.2f) + powf(dn, 2.2f)) / 4.0f; 22 | } 23 | 24 | // Blend four samples into a final result after doing gamma conversions 25 | inline int GammaBlend(int a, int b, int c, int d) { 26 | float result = powf(GetColorGamma(a, b, c, d), 1.0f / 2.2f); 27 | 28 | return static_cast(255.0f * result); 29 | } 30 | 31 | inline u32 GetLinearColor(u32 c) { 32 | int a = (int)(powf(((c >> 24) & 0xFF) / 255.0f, 2.2f) * 255.0f); 33 | int b = (int)(powf(((c >> 16) & 0xFF) / 255.0f, 2.2f) * 255.0f); 34 | int g = (int)(powf(((c >> 8) & 0xFF) / 255.0f, 2.2f) * 255.0f); 35 | int r = (int)(powf(((c >> 0) & 0xFF) / 255.0f, 2.2f) * 255.0f); 36 | 37 | return a << 24 | b << 16 | g << 8 | r << 0; 38 | } 39 | 40 | // Perform blend in linear space by multiplying the samples by their alpha and dividing by the accumulated alpha. 41 | inline int AlphaBlend(int c0, int c1, int c2, int c3, int a0, int a1, int a2, int a3, int f, int d, int shift) { 42 | int t = ((c0 >> shift & 0xFF) * a0 + (c1 >> shift & 0xFF) * a1 + (c2 >> shift & 0xFF) * a2 + 43 | (c3 >> shift & 0xFF) * a3 + f); 44 | return (t / d); 45 | } 46 | 47 | void BoxFilterMipmap(u8* previous, u8* data, size_t data_size, size_t dim, bool brighten_mipping) { 48 | size_t size_per_tex = dim * dim * 4; 49 | size_t count = data_size / size_per_tex; 50 | size_t prev_dim = dim * 2; 51 | 52 | bool has_transparent = false; 53 | 54 | if (brighten_mipping) { 55 | for (size_t i = 0; i < data_size; i += 4) { 56 | if (data[i + 3] == 0) { 57 | has_transparent = true; 58 | break; 59 | } 60 | } 61 | } 62 | 63 | unsigned int* pixel = (unsigned int*)data; 64 | for (size_t i = 0; i < count; ++i) { 65 | unsigned char* prev_tex = previous + i * (prev_dim * prev_dim * 4); 66 | 67 | Mipmap source(prev_tex, prev_dim); 68 | 69 | for (size_t y = 0; y < dim; ++y) { 70 | for (size_t x = 0; x < dim; ++x) { 71 | int red, green, blue, alpha; 72 | 73 | const size_t red_index = 0; 74 | const size_t green_index = 1; 75 | const size_t blue_index = 2; 76 | const size_t alpha_index = 3; 77 | 78 | if (has_transparent) { 79 | u32 full_samples[4] = {source.SampleFull(x * 2, y * 2), source.SampleFull(x * 2 + 1, y * 2), 80 | source.SampleFull(x * 2, y * 2 + 1), source.SampleFull(x * 2 + 1, y * 2 + 1)}; 81 | // Convert the fetched samples into linear space 82 | u32 c[4] = { 83 | GetLinearColor(full_samples[0]), 84 | GetLinearColor(full_samples[1]), 85 | GetLinearColor(full_samples[2]), 86 | GetLinearColor(full_samples[3]), 87 | }; 88 | 89 | int a0 = (c[0] >> 24) & 0xFF; 90 | int a1 = (c[1] >> 24) & 0xFF; 91 | int a2 = (c[2] >> 24) & 0xFF; 92 | int a3 = (c[3] >> 24) & 0xFF; 93 | 94 | int alpha_sum = a0 + a1 + a2 + a3; 95 | 96 | int d; 97 | if (alpha_sum != 0) { 98 | d = alpha_sum; 99 | } else { 100 | d = 4; 101 | a3 = a2 = a1 = a0 = 1; 102 | } 103 | 104 | int f = (d + 1) / 2; 105 | 106 | u32 la = (alpha_sum + 2) / 4; 107 | u32 lb = AlphaBlend(c[0], c[1], c[2], c[3], a0, a1, a2, a3, f, d, 16); 108 | u32 lg = AlphaBlend(c[0], c[1], c[2], c[3], a0, a1, a2, a3, f, d, 8); 109 | u32 lr = AlphaBlend(c[0], c[1], c[2], c[3], a0, a1, a2, a3, f, d, 0); 110 | 111 | // Convert back into gamma space 112 | alpha = (u32)(powf(la / 255.0f, 1.0f / 2.2f) * 255.0f); 113 | red = (u32)(powf(lr / 255.0f, 1.0f / 2.2f) * 255.0f); 114 | green = (u32)(powf(lg / 255.0f, 1.0f / 2.2f) * 255.0f); 115 | blue = (u32)(powf(lb / 255.0f, 1.0f / 2.2f) * 255.0f); 116 | } else { 117 | red = GammaBlend(source.Sample(x * 2, y * 2, red_index), source.Sample(x * 2 + 1, y * 2, red_index), 118 | source.Sample(x * 2, y * 2 + 1, red_index), source.Sample(x * 2 + 1, y * 2 + 1, red_index)); 119 | 120 | green = GammaBlend(source.Sample(x * 2, y * 2, green_index), source.Sample(x * 2 + 1, y * 2, green_index), 121 | source.Sample(x * 2, y * 2 + 1, green_index), 122 | source.Sample(x * 2 + 1, y * 2 + 1, green_index)); 123 | 124 | blue = 125 | GammaBlend(source.Sample(x * 2, y * 2, blue_index), source.Sample(x * 2 + 1, y * 2, blue_index), 126 | source.Sample(x * 2, y * 2 + 1, blue_index), source.Sample(x * 2 + 1, y * 2 + 1, blue_index)); 127 | 128 | alpha = GammaBlend(source.Sample(x * 2, y * 2, alpha_index), source.Sample(x * 2 + 1, y * 2, alpha_index), 129 | source.Sample(x * 2, y * 2 + 1, alpha_index), 130 | source.Sample(x * 2 + 1, y * 2 + 1, alpha_index)); 131 | } 132 | 133 | // AA BB GG RR 134 | *pixel = ((alpha & 0xFF) << 24) | ((blue & 0xFF) << 16) | ((green & 0xFF) << 8) | (red & 0xFF); 135 | ++pixel; 136 | } 137 | } 138 | } 139 | } 140 | 141 | VkShaderModule CreateShaderModule(VkDevice device, String code) { 142 | VkShaderModuleCreateInfo create_info{}; 143 | 144 | create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; 145 | create_info.codeSize = code.size; 146 | create_info.pCode = (u32*)code.data; 147 | 148 | VkShaderModule shader; 149 | 150 | if (vkCreateShaderModule(device, &create_info, nullptr, &shader) != VK_SUCCESS) { 151 | fprintf(stderr, "Failed to create shader module.\n"); 152 | } 153 | 154 | return shader; 155 | } 156 | 157 | } // namespace render 158 | } // namespace polymer 159 | -------------------------------------------------------------------------------- /polymer/protocol.h: -------------------------------------------------------------------------------- 1 | #ifndef POLYMER_PROTOCOL_H_ 2 | #define POLYMER_PROTOCOL_H_ 3 | 4 | #include 5 | 6 | namespace polymer { 7 | 8 | // TODO: Generate protocol ids and packet data from some standardized source. 9 | 10 | struct Vector3f; 11 | 12 | constexpr u32 kProtocolVersion = 769; 13 | 14 | enum class ProtocolState { Handshake, Status, Login, Configuration, Play }; 15 | 16 | enum class ClientStatusAction { Respawn, Stats }; 17 | 18 | struct Connection; 19 | 20 | enum PlayerMoveFlag { 21 | PlayerMoveFlag_Position = (1 << 0), 22 | PlayerMoveFlag_Look = (1 << 1), 23 | }; 24 | typedef u8 PlayerMoveFlags; 25 | 26 | enum TeleportFlag { 27 | TeleportFlag_RelativeX = (1 << 0), 28 | TeleportFlag_RelativeY = (1 << 1), 29 | TeleportFlag_RelativeZ = (1 << 2), 30 | TeleportFlag_RelativeYaw = (1 << 3), 31 | TeleportFlag_RelativePitch = (1 << 4), 32 | TeleportFlag_RelativeVelocityX = (1 << 5), 33 | TeleportFlag_RelativeVelocityY = (1 << 6), 34 | TeleportFlag_RelativeVelocityZ = (1 << 7), 35 | TeleportFlag_RotateDelta = (1 << 8), 36 | }; 37 | typedef u32 TeleportFlags; 38 | 39 | namespace inbound { 40 | namespace status { 41 | 42 | enum class ProtocolId { Response, Pong, Count }; 43 | 44 | } // namespace status 45 | 46 | namespace login { 47 | 48 | enum class ProtocolId { 49 | Disconnect, 50 | EncryptionRequest, 51 | LoginSuccess, 52 | SetCompression, 53 | LoginPluginRequest, 54 | CookieRequest, 55 | Count 56 | }; 57 | 58 | } // namespace login 59 | 60 | namespace configuration { 61 | enum class ProtocolId { 62 | CookieRequest, 63 | PluginMessage, 64 | Disconnect, 65 | Finish, 66 | KeepAlive, 67 | Ping, 68 | ResetChat, 69 | RegistryData, 70 | RemoveResourcePack, 71 | AddResourcePack, 72 | StoreCookie, 73 | Transfer, 74 | FeatureFlags, 75 | UpdateTags, 76 | KnownPacks, 77 | CustomReportDetails, 78 | ServerLinks, 79 | Count 80 | }; 81 | 82 | } // namespace configuration 83 | 84 | namespace play { 85 | 86 | enum class ProtocolId { 87 | BundleDelimiter, 88 | SpawnEntity, 89 | SpawnExperienceOrb, 90 | EntityAnimation, 91 | AwardStatistics, 92 | AcknowledgeBlockChange, 93 | SetBlockDestroyStage, 94 | BlockEntityData, 95 | BlockAction, 96 | BlockUpdate, 97 | BossBar, 98 | ChangeDifficulty, 99 | ChunkBatchFinished, 100 | ChunkBatchStart, 101 | ChunkBiomes, 102 | ClearTitles, 103 | CommandSuggestionsResponse, 104 | Commands, 105 | CloseContainer, 106 | SetContainerContent, 107 | SetContainerProperty, 108 | SetContainerSlot, 109 | CookieRequest, 110 | SetCooldown, 111 | ChatSuggestions, 112 | PluginMessage, 113 | DamageEvent, 114 | DebugSample, 115 | DeleteMessage, 116 | Disconnect, 117 | DisguisedChatMessage, 118 | EntityEvent, 119 | TeleportEntity, 120 | Explosion, 121 | UnloadChunk, 122 | GameEvent, 123 | OpenHorseScreen, 124 | HurtAnimation, 125 | InitializeWorldBorder, 126 | KeepAlive, 127 | ChunkData, 128 | WorldEvent, 129 | Particle, 130 | UpdateLight, 131 | Login, 132 | MapData, 133 | MerchantOffers, 134 | EntityPosition, 135 | EntityPositionAndRotation, 136 | MoveMinecart, 137 | EntityRotation, 138 | VehicleMove, 139 | OpenBook, 140 | OpenScreen, 141 | OpenSignEditor, 142 | Ping, 143 | PingResponse, 144 | PlaceGhostRecipe, 145 | PlayerAbilities, 146 | PlayerChatMessage, 147 | EndCombatEvent, 148 | EnterCombatEvent, 149 | DeathCombatEvent, 150 | PlayerInfoRemove, 151 | PlayerInfoUpdate, 152 | LookAt, 153 | PlayerPositionAndLook, 154 | PlayerRotation, 155 | RecipeBookAdd, 156 | RecipeBookRemove, 157 | RecipeBookSettings, 158 | RemoveEntities, 159 | RemoveEntityEffect, 160 | ResetScore, 161 | RemoveResourcePack, 162 | AddResourcePack, 163 | Respawn, 164 | SetHeadRotation, 165 | UpdateSectionBlocks, 166 | SelectAdvancementTab, 167 | ServerData, 168 | SetActionBarText, 169 | WorldBorderCenter, 170 | WorldBorderLerpSize, 171 | WorldBorderSize, 172 | WorldBorderWarningDelay, 173 | WorldBorderWarningDistance, 174 | Camera, 175 | SetCenterChunk, 176 | SetRenderDistance, 177 | SetCursorItem, 178 | SetDefaultSpawnPosition, 179 | DisplayObjective, 180 | EntityMetadata, 181 | LinkEntities, 182 | EntityVelocity, 183 | EntityEquipment, 184 | SetExperience, 185 | UpdateHealth, 186 | SetHeldItem, 187 | UpdateObjectives, 188 | SetPassengers, 189 | SetPlayerInventorySlot, 190 | UpdateTeams, 191 | UpdateScore, 192 | UpdateSimulationDistance, 193 | SetSubtitleText, 194 | TimeUpdate, 195 | SetTitleText, 196 | SetTitleAnimationTimes, 197 | EntitySoundEffect, 198 | SoundEffect, 199 | StartConfiguration, 200 | StopSound, 201 | StoreCookie, 202 | SystemChatMessage, 203 | PlayerListHeaderAndFooter, 204 | NBTQueryResponse, 205 | CollectItem, 206 | SynchronizeVehiclePosition, 207 | SetTickingState, 208 | StepTick, 209 | Transfer, 210 | UpdateAdvancements, 211 | UpdateAttributes, 212 | EntityEffect, 213 | UpdateRecipes, 214 | Tags, 215 | ProjectilePower, 216 | CustomReportDetails, 217 | ServerLinks, 218 | 219 | Count 220 | }; 221 | 222 | } // namespace play 223 | } // namespace inbound 224 | 225 | namespace outbound { 226 | namespace handshake { 227 | 228 | enum class ProtocolId { Handshake, Count }; 229 | 230 | void SendHandshake(Connection& connection, u32 version, const char* address, size_t address_size, u16 port, 231 | ProtocolState state_request); 232 | 233 | } // namespace handshake 234 | 235 | namespace login { 236 | 237 | enum class ProtocolId { LoginStart, EncryptionResponse, LoginPluginResponse, LoginAcknowledged, CookieResponse, Count }; 238 | 239 | void SendLoginStart(Connection& connection, const char* username, size_t username_size); 240 | void SendAcknowledged(Connection& connection); 241 | 242 | } // namespace login 243 | 244 | namespace configuration { 245 | 246 | enum class ProtocolId { 247 | ClientInformation, 248 | CookieResponse, 249 | PluginMessage, 250 | AcknowledgeFinish, 251 | KeepAlive, 252 | Pong, 253 | ResourcePack, 254 | KnownPacks, 255 | Count 256 | }; 257 | 258 | void SendClientInformation(Connection& connection, u8 view_distance, u8 skin_bitmask, u8 main_hand); 259 | void SendKeepAlive(Connection& connection, u64 id); 260 | void SendPong(Connection& connection, u32 id); 261 | void SendFinish(Connection& connection); 262 | void SendKnownPacks(Connection& connection); 263 | 264 | } // namespace configuration 265 | 266 | namespace play { 267 | 268 | enum class ProtocolId { 269 | TeleportConfirm = 0x00, 270 | KeepAlive = 0x1A, 271 | PlayPositionAndRotation = 0x1D, 272 | ChatMessage = 0x07, 273 | ChatCommand = 0x05, 274 | ChunkBatchReceived = 0x09, 275 | ClientStatus = 0x0A, 276 | Count 277 | }; 278 | 279 | void SendKeepAlive(Connection& connection, u64 id); 280 | void SendTeleportConfirm(Connection& connection, u64 id); 281 | void SendPlayerPositionAndRotation(Connection& connection, const Vector3f& position, float yaw, float pitch, 282 | PlayerMoveFlags flags); 283 | void SendChatMessage(Connection& connection, const String& message); 284 | void SendChatCommand(Connection& connection, const String& message); 285 | void SendChunkBatchReceived(Connection& connection, float chunks_per_tick); 286 | void SendClientStatus(Connection& connection, ClientStatusAction action); 287 | 288 | } // namespace play 289 | } // namespace outbound 290 | 291 | } // namespace polymer 292 | 293 | #endif 294 | -------------------------------------------------------------------------------- /polymer/ui/chat_window.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace polymer { 12 | namespace ui { 13 | 14 | const size_t kChatMessageQueueSize = polymer_array_count(ChatWindow::messages); 15 | const u64 kSecondNanoseconds = 1000LL * 1000LL * 1000LL; 16 | const u64 kDisplayNanoseconds = kSecondNanoseconds * 10LL; 17 | const int kLineHeight = 18; 18 | 19 | inline static u64 GetNow() { 20 | return std::chrono::high_resolution_clock::now().time_since_epoch().count(); 21 | } 22 | 23 | void ChatWindow::RenderSlice(render::FontRenderer& font_renderer, size_t start_index, size_t count, bool fade) { 24 | const int kEmptyLinesBelow = 4; 25 | u64 now = GetNow(); 26 | 27 | for (size_t i = 0; i < count && i < message_count; ++i) { 28 | size_t index = (start_index - i - 1 + kChatMessageQueueSize) % kChatMessageQueueSize; 29 | 30 | ChatMessage* chat_message = messages + index; 31 | 32 | float y = (float)(font_renderer.renderer->GetExtent().height - 8 - (i + kEmptyLinesBelow) * kLineHeight); 33 | Vector3f position(8, y, 0); 34 | 35 | render::FontStyleFlags style = render::FontStyle_DropShadow; 36 | 37 | float alpha = 1.0f; 38 | 39 | if (fade) { 40 | u64 delta_ns = now - chat_message->timestamp; 41 | 42 | // Stop rendering anything outside of display time range. 43 | if (delta_ns > kDisplayNanoseconds) break; 44 | 45 | if (kDisplayNanoseconds - delta_ns < kSecondNanoseconds) { 46 | alpha = (kDisplayNanoseconds - delta_ns) / (float)kSecondNanoseconds; 47 | } 48 | } 49 | 50 | Vector4f color(1, 1, 1, alpha); 51 | Vector4f bg_color(0, 0, 0, 0.4f * alpha); 52 | 53 | float background_width = 660; 54 | if (position.x + background_width > font_renderer.renderer->GetExtent().width) { 55 | background_width = (float)font_renderer.renderer->GetExtent().width - 8; 56 | } 57 | 58 | font_renderer.RenderBackground(position + Vector3f(-4, 0, 0), Vector2f(background_width, kLineHeight), bg_color); 59 | font_renderer.RenderText(position, WString(chat_message->message, chat_message->message_length), style, color); 60 | } 61 | } 62 | 63 | void ChatWindow::Update(render::FontRenderer& font_renderer) { 64 | if (display_full) { 65 | RenderSlice(font_renderer, message_index, 20, false); 66 | 67 | float bottom = (float)font_renderer.renderer->GetExtent().height - 22.0f; 68 | float background_width = (float)font_renderer.renderer->GetExtent().width - 8; 69 | 70 | render::FontStyleFlags style = render::FontStyle_DropShadow; 71 | Vector4f color(1, 1, 1, 1); 72 | Vector4f bg_color(0, 0, 0, 0.4f); 73 | 74 | font_renderer.RenderBackground(Vector3f(4, bottom, 0), Vector2f(background_width, kLineHeight), bg_color); 75 | if (input.length > 0) { 76 | font_renderer.RenderText(Vector3f(8, bottom, 0), WString(input.message, input.length), style, color); 77 | } 78 | 79 | u64 now_ms = GetNow() / (1000 * 1000); 80 | 81 | if (now_ms % 500 < 250) { 82 | float text_width = (float)font_renderer.GetTextWidth(WString(input.message, input_cursor_index)); 83 | float left_spacing = 12; 84 | 85 | if (input_cursor_index >= input.length) { 86 | if (input_cursor_index == 0) { 87 | left_spacing = 8; 88 | } 89 | font_renderer.RenderText(Vector3f(left_spacing + text_width, bottom, 0), POLY_STR("_"), style, color); 90 | } else { 91 | font_renderer.RenderText(Vector3f(left_spacing - 4 + text_width, bottom, 0), POLY_STR("|"), style, color); 92 | } 93 | } 94 | 95 | input.active = true; 96 | } else { 97 | RenderSlice(font_renderer, message_index, 10, true); 98 | } 99 | } 100 | 101 | void ChatWindow::OnDelete() { 102 | if (input_cursor_index >= input.length || input.length == 0) return; 103 | 104 | memmove(input.message + input_cursor_index, input.message + input_cursor_index + 1, 105 | (input.length - input_cursor_index) * sizeof(wchar)); 106 | 107 | --input.length; 108 | } 109 | 110 | void ChatWindow::SendInput(Connection& connection) { 111 | if (input.length <= 0) return; 112 | 113 | String utf8 = Unicode::ToUTF8(trans_arena, WString(input.message, input.length)); 114 | 115 | if (input.message[0] == '/') { 116 | if (input.length > 1) { 117 | outbound::play::SendChatCommand(connection, String(utf8.data + 1, utf8.size - 1)); 118 | } 119 | } else { 120 | outbound::play::SendChatMessage(connection, utf8); 121 | } 122 | 123 | input_cursor_index = 0; 124 | input.Clear(); 125 | } 126 | 127 | void ChatWindow::OnInput(wchar codepoint) { 128 | // TODO: Handle more than ascii 129 | // TODO: Remove magic numbers 130 | 131 | if (!input.active) return; 132 | 133 | if (input_cursor_index > 0 && codepoint == 0x08) { 134 | // Backspace 135 | memmove(input.message + input_cursor_index - 1, input.message + input_cursor_index, 136 | (input.length - input_cursor_index) * sizeof(wchar)); 137 | --input.length; 138 | --input_cursor_index; 139 | input.message[input.length] = 0; 140 | } 141 | 142 | if (codepoint < 0x20) return; 143 | 144 | if (input.length < polymer_array_count(input.message)) { 145 | InsertCodepoint(codepoint); 146 | } 147 | } 148 | 149 | void ChatWindow::MoveCursor(ChatMoveDirection direction) { 150 | if (direction == ChatMoveDirection::Left) { 151 | if (input_cursor_index > 0) { 152 | --input_cursor_index; 153 | } 154 | } else if (direction == ChatMoveDirection::Right) { 155 | if (input_cursor_index < input.length) { 156 | ++input_cursor_index; 157 | } 158 | } else if (direction == ChatMoveDirection::Home) { 159 | input_cursor_index = 0; 160 | } else if (direction == ChatMoveDirection::End) { 161 | input_cursor_index = input.length; 162 | } 163 | } 164 | 165 | void ChatWindow::InsertCodepoint(wchar codepoint) { 166 | if (input.length >= polymer_array_count(input.message)) return; 167 | 168 | if (input_cursor_index < input.length) { 169 | memmove(input.message + input_cursor_index + 1, input.message + input_cursor_index, 170 | (input.length - input_cursor_index) * sizeof(wchar)); 171 | } 172 | 173 | input.message[input_cursor_index] = codepoint; 174 | 175 | ++input_cursor_index; 176 | ++input.length; 177 | } 178 | 179 | bool ChatWindow::ToggleDisplay() { 180 | display_full = !display_full; 181 | 182 | if (display_full) { 183 | input.Clear(); 184 | input_cursor_index = 0; 185 | } else { 186 | input.active = false; 187 | } 188 | 189 | return display_full; 190 | } 191 | 192 | void ChatWindow::PushMessage(const wchar* mesg, size_t mesg_length) { 193 | ChatMessage* chat_message = nullptr; 194 | 195 | if (message_count < polymer_array_count(messages)) { 196 | chat_message = messages + message_count++; 197 | } else { 198 | chat_message = messages + message_index; 199 | } 200 | 201 | message_index = (message_index + 1) % polymer_array_count(messages); 202 | 203 | if (mesg_length > polymer_array_count(chat_message->message)) { 204 | mesg_length = polymer_array_count(chat_message->message); 205 | } 206 | 207 | memcpy(chat_message->message, mesg, mesg_length * sizeof(wchar)); 208 | chat_message->message_length = mesg_length; 209 | chat_message->timestamp = GetNow(); 210 | } 211 | 212 | } // namespace ui 213 | } // namespace polymer 214 | -------------------------------------------------------------------------------- /polymer/polymer.cpp: -------------------------------------------------------------------------------- 1 | #include "polymer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace polymer { 14 | 15 | // Window surface width 16 | constexpr u32 kWidth = 1280; 17 | // Window surface height 18 | constexpr u32 kHeight = 720; 19 | 20 | using ms_float = std::chrono::duration; 21 | 22 | Polymer::Polymer(MemoryArena& perm_arena, MemoryArena& trans_arena, int argc, char** argv) 23 | : perm_arena(perm_arena), trans_arena(trans_arena) { 24 | ArgParser arg_parser = ArgParser::Parse(argc, argv); 25 | args = LaunchArgs::Create(arg_parser); 26 | 27 | renderer.perm_arena = &perm_arena; 28 | renderer.trans_arena = &trans_arena; 29 | } 30 | 31 | int Polymer::Run(InputState* input) { 32 | constexpr size_t kMirrorBufferSize = 65536 * 32; 33 | 34 | renderer.platform = &platform; 35 | 36 | if (!platform.GetPlatformName) { 37 | fprintf(stderr, "Polymer cannot run without a platform implementation.\n"); 38 | return 1; 39 | } 40 | 41 | if (args.help) { 42 | PrintUsage(); 43 | return 0; 44 | } 45 | 46 | const char* platform_name = platform.GetPlatformName(); 47 | printf("Polymer: %s\n", platform_name); 48 | fflush(stdout); 49 | 50 | this->game = perm_arena.Construct(&renderer, &perm_arena, &trans_arena); 51 | 52 | NetworkQueue net_queue = {}; 53 | 54 | if (!net_queue.Initialize()) { 55 | return 1; 56 | } 57 | 58 | game->assets.asset_store = perm_arena.Construct(platform, perm_arena, trans_arena, net_queue); 59 | game->assets.asset_store->Initialize(); 60 | 61 | // TODO: This should be running during a separate scene so download progress can be rendered. 62 | while (!net_queue.IsEmpty()) { 63 | net_queue.Run(); 64 | } 65 | 66 | net_queue.Clear(); 67 | 68 | PacketInterpreter interpreter(game); 69 | Connection* connection = &game->connection; 70 | 71 | connection->interpreter = &interpreter; 72 | 73 | // Allocate mirrored ring buffers so they can always be inflated 74 | connection->read_buffer.size = kMirrorBufferSize; 75 | connection->read_buffer.data = AllocateMirroredBuffer(connection->read_buffer.size); 76 | connection->write_buffer.size = kMirrorBufferSize; 77 | connection->write_buffer.data = AllocateMirroredBuffer(connection->write_buffer.size); 78 | 79 | assert(connection->read_buffer.data); 80 | assert(connection->write_buffer.data); 81 | 82 | this->window = platform.WindowCreate(kWidth, kHeight); 83 | 84 | render::RenderConfig render_config = {}; 85 | 86 | render_config.desired_present_mode = VK_PRESENT_MODE_MAILBOX_KHR; 87 | 88 | // Enable this for vsync 89 | // render_config.desired_present_mode = VK_PRESENT_MODE_FIFO_KHR; 90 | 91 | render_config.desired_msaa_samples = VK_SAMPLE_COUNT_4_BIT; 92 | render_config.sample_shading = true; 93 | 94 | render_config.view_distance = 16; 95 | #ifdef _DEBUG 96 | render_config.view_distance = 3; 97 | #endif 98 | 99 | renderer.Initialize(window, render_config); 100 | 101 | { 102 | auto start = std::chrono::high_resolution_clock::now(); 103 | 104 | char* client_path = game->assets.asset_store->GetClientPath(trans_arena); 105 | if (!game->assets.Load(renderer, client_path, BLOCKS_FILENAME, &game->block_registry)) { 106 | fprintf(stderr, "Failed to load minecraft assets. Requires %s and %s.\n", BLOCKS_FILENAME, client_path); 107 | return 1; 108 | } 109 | 110 | auto end = std::chrono::high_resolution_clock::now(); 111 | auto frame_time = std::chrono::duration_cast(end - start).count(); 112 | 113 | printf("Asset time: %f\n", frame_time); 114 | fflush(stdout); 115 | 116 | game->chunk_renderer.block_textures = game->assets.block_assets->block_textures; 117 | game->font_renderer.glyph_page_texture = game->assets.glyph_page_texture; 118 | game->font_renderer.glyph_size_table = game->assets.glyph_size_table; 119 | 120 | game->world.block_mesher.mapping.Initialize(game->block_registry); 121 | } 122 | 123 | game->chunk_renderer.CreateLayoutSet(renderer, renderer.device); 124 | game->font_renderer.CreateLayoutSet(renderer, renderer.device); 125 | renderer.RecreateSwapchain(); 126 | 127 | printf("Connecting to '%.*s:%hu' with username '%.*s'.\n", (u32)args.server.size, args.server.data, args.server_port, 128 | (u32)args.username.size, args.username.data); 129 | fflush(stdout); 130 | 131 | ConnectResult connect_result = connection->Connect(args.server.data, args.server_port); 132 | 133 | switch (connect_result) { 134 | case ConnectResult::ErrorSocket: { 135 | fprintf(stderr, "Failed to create socket\n"); 136 | return 1; 137 | } 138 | case ConnectResult::ErrorAddrInfo: { 139 | fprintf(stderr, "Failed to get address info\n"); 140 | return 1; 141 | } 142 | case ConnectResult::ErrorConnect: { 143 | fprintf(stderr, "Failed to connect\n"); 144 | return 1; 145 | } 146 | default: 147 | break; 148 | } 149 | 150 | printf("Connected to server.\n"); 151 | 152 | connection->SetBlocking(false); 153 | 154 | outbound::handshake::SendHandshake(*connection, kProtocolVersion, args.server.data, args.server.size, 155 | args.server_port, ProtocolState::Login); 156 | 157 | outbound::login::SendLoginStart(*connection, args.username.data, args.username.size); 158 | 159 | memcpy(game->player_manager.client_name, args.username.data, args.username.size); 160 | game->player_manager.client_name[args.username.size] = 0; 161 | 162 | fflush(stdout); 163 | 164 | ui::DebugTextSystem debug(game->font_renderer); 165 | 166 | float average_frame_time = 0.0f; 167 | float frame_time = 0.0f; 168 | 169 | while (connection->connected) { 170 | auto start = std::chrono::high_resolution_clock::now(); 171 | 172 | trans_arena.Reset(); 173 | 174 | Connection::TickResult result = connection->Tick(); 175 | 176 | if (result == Connection::TickResult::ConnectionClosed) { 177 | fprintf(stderr, "Connection closed by server.\n"); 178 | } 179 | 180 | if (renderer.BeginFrame()) { 181 | game->font_renderer.BeginFrame(renderer.current_frame); 182 | 183 | game->Update(frame_time / 1000.0f, input); 184 | 185 | debug.position = Vector2f(8, 8); 186 | debug.color = Vector4f(1.0f, 0.67f, 0.0f, 1.0f); 187 | 188 | debug.Write("Polymer %s [%s]", POLYMER_VERSION, game->player_manager.client_name); 189 | 190 | debug.color = Vector4f(1, 1, 1, 1); 191 | 192 | debug.Write("minecraft: %s", MINECRAFT_VERSION); 193 | debug.Write("platform: %s", platform_name); 194 | debug.Write("dimension: %.*s", (u32)game->dimension.name.size, game->dimension.name.data); 195 | 196 | int fps = (average_frame_time > 0.0f) ? (u32)(1000.0f / average_frame_time) : 0; 197 | debug.Write("fps: %d", fps); 198 | debug.Write("(%.02f, %.02f, %.02f)", game->camera.position.x, game->camera.position.y, game->camera.position.z); 199 | 200 | debug.Write("world tick: %u", game->world.world_tick); 201 | 202 | debug.Write("multisampling: %u", game->renderer->swapchain.multisample.samples); 203 | debug.Write("visible chunks: %zu", game->world.connectivity_graph.visible_count); 204 | 205 | game->font_renderer.Draw(game->command_buffers[renderer.current_frame], renderer.current_frame); 206 | game->SubmitFrame(); 207 | renderer.Render(); 208 | } 209 | 210 | platform.WindowPump(window); 211 | 212 | auto end = std::chrono::high_resolution_clock::now(); 213 | 214 | frame_time = std::chrono::duration_cast(end - start).count(); 215 | average_frame_time = average_frame_time * 0.9f + frame_time * 0.1f; 216 | } 217 | 218 | vkDeviceWaitIdle(renderer.device); 219 | game->world.FreeMeshes(); 220 | 221 | game->font_renderer.Shutdown(renderer.device); 222 | game->chunk_renderer.Shutdown(renderer.device); 223 | 224 | renderer.Shutdown(); 225 | 226 | return 0; 227 | } 228 | 229 | } // namespace polymer 230 | -------------------------------------------------------------------------------- /polymer/nbt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace polymer { 10 | namespace nbt { 11 | 12 | bool ParseTag(RingBuffer& rb, Tag& tag, MemoryArena& arena); 13 | 14 | Tag* TagCompound::GetNamedTag(const String& str) { 15 | for (size_t i = 0; i < ntags; ++i) { 16 | Tag* tag = tags + i; 17 | String tag_name(tag->name, tag->name_length); 18 | 19 | if (poly_strcmp(str, tag_name) == 0) { 20 | return tag; 21 | } 22 | } 23 | 24 | return nullptr; 25 | } 26 | 27 | bool ReadLengthString(RingBuffer& rb, char** data, size_t* size, MemoryArena& arena) { 28 | u16 length; 29 | 30 | if (rb.GetReadAmount() < sizeof(length)) { 31 | return false; 32 | } 33 | 34 | length = rb.ReadU16(); 35 | 36 | *size = length; 37 | *data = (char*)arena.Allocate(*size); 38 | 39 | String str; 40 | str.data = *data; 41 | str.size = *size; 42 | 43 | if (rb.GetReadAmount() < *size) { 44 | return false; 45 | } 46 | 47 | rb.ReadRawString(&str, *size); 48 | 49 | return true; 50 | } 51 | 52 | bool ParseCompound(RingBuffer& rb, TagCompound& compound, MemoryArena& arena) { 53 | compound.ntags = 0; 54 | 55 | TagType type = TagType::Unknown; 56 | 57 | while (type != TagType::End) { 58 | if (rb.GetReadAmount() < 1) { 59 | return false; 60 | } 61 | 62 | type = (TagType)rb.ReadU8(); 63 | 64 | if (type == TagType::End) { 65 | break; 66 | } 67 | 68 | Tag* tag = compound.tags + compound.ntags++; 69 | 70 | tag->tag = NULL; 71 | tag->type = type; 72 | 73 | if (!ReadLengthString(rb, &tag->name, &tag->name_length, arena)) { 74 | return false; 75 | } 76 | 77 | if (!ParseTag(rb, *tag, arena)) { 78 | return false; 79 | } 80 | } 81 | 82 | return true; 83 | } 84 | 85 | bool ParseTag(RingBuffer& rb, Tag& tag, MemoryArena& arena) { 86 | switch (tag.type) { 87 | case TagType::End: { 88 | } break; 89 | case TagType::Byte: { 90 | TagByte* byte_tag = memory_arena_push_type(&arena, TagByte); 91 | 92 | if (rb.GetReadAmount() < sizeof(u8)) { 93 | return false; 94 | } 95 | 96 | byte_tag->data = rb.ReadU8(); 97 | 98 | tag.tag = byte_tag; 99 | } break; 100 | case TagType::Short: { 101 | TagShort* short_tag = memory_arena_push_type(&arena, TagShort); 102 | 103 | if (rb.GetReadAmount() < sizeof(u16)) { 104 | return false; 105 | } 106 | 107 | short_tag->data = rb.ReadU16(); 108 | 109 | tag.tag = short_tag; 110 | } break; 111 | case TagType::Int: { 112 | TagInt* int_tag = memory_arena_push_type(&arena, TagInt); 113 | 114 | if (rb.GetReadAmount() < sizeof(u32)) { 115 | return false; 116 | } 117 | 118 | int_tag->data = rb.ReadU32(); 119 | 120 | tag.tag = int_tag; 121 | } break; 122 | case TagType::Long: { 123 | TagLong* long_tag = memory_arena_push_type(&arena, TagLong); 124 | 125 | if (rb.GetReadAmount() < sizeof(u64)) { 126 | return false; 127 | } 128 | 129 | long_tag->data = rb.ReadU64(); 130 | 131 | tag.tag = long_tag; 132 | } break; 133 | case TagType::Float: { 134 | TagFloat* float_tag = memory_arena_push_type(&arena, TagFloat); 135 | 136 | if (rb.GetReadAmount() < sizeof(float)) { 137 | return false; 138 | } 139 | 140 | float_tag->data = rb.ReadFloat(); 141 | 142 | tag.tag = float_tag; 143 | } break; 144 | case TagType::Double: { 145 | TagDouble* double_tag = memory_arena_push_type(&arena, TagDouble); 146 | 147 | if (rb.GetReadAmount() < sizeof(double)) { 148 | return false; 149 | } 150 | 151 | double_tag->data = rb.ReadDouble(); 152 | 153 | tag.tag = double_tag; 154 | } break; 155 | case TagType::ByteArray: { 156 | TagByteArray* byte_array_tag = memory_arena_push_type(&arena, TagByteArray); 157 | 158 | byte_array_tag->length = 0; 159 | byte_array_tag->data = NULL; 160 | 161 | if (rb.GetReadAmount() < sizeof(u32)) { 162 | return false; 163 | } 164 | 165 | byte_array_tag->length = rb.ReadU32(); 166 | 167 | if (rb.GetReadAmount() < byte_array_tag->length) { 168 | return false; 169 | } 170 | 171 | byte_array_tag->data = (s8*)arena.Allocate(byte_array_tag->length * sizeof(u8)); 172 | 173 | // Read all of the contained bytes in one read. 174 | String str; 175 | str.data = (char*)byte_array_tag->data; 176 | str.size = byte_array_tag->length; 177 | 178 | rb.ReadRawString(&str, str.size); 179 | 180 | tag.tag = byte_array_tag; 181 | } break; 182 | case TagType::String: { 183 | TagString* string_tag = memory_arena_push_type(&arena, TagString); 184 | 185 | if (!ReadLengthString(rb, &string_tag->data, &string_tag->length, arena)) { 186 | return false; 187 | } 188 | 189 | tag.tag = string_tag; 190 | } break; 191 | case TagType::List: { 192 | TagList* list_tag = memory_arena_push_type(&arena, TagList); 193 | 194 | list_tag->length = 0; 195 | list_tag->tags = NULL; 196 | 197 | if (rb.GetReadAmount() < sizeof(u8) + sizeof(u32)) { 198 | return false; 199 | } 200 | 201 | list_tag->type = (TagType)rb.ReadU8(); 202 | list_tag->length = rb.ReadU32(); 203 | 204 | if (list_tag->length > 0) { 205 | // Allocate space for all of the tags. 206 | list_tag->tags = memory_arena_push_type_count(&arena, Tag, list_tag->length); 207 | } 208 | 209 | for (size_t i = 0; i < list_tag->length; ++i) { 210 | Tag* data_tag = list_tag->tags + i; 211 | 212 | data_tag->name = NULL; 213 | data_tag->name_length = 0; 214 | data_tag->type = list_tag->type; 215 | 216 | // TODO: This probably shouldn't be called recursively otherwise bad 217 | // actors could blow out the stack with nested lists. 218 | if (!ParseTag(rb, *data_tag, arena)) { 219 | return false; 220 | } 221 | } 222 | 223 | tag.tag = list_tag; 224 | } break; 225 | case TagType::Compound: { 226 | TagCompound* compound_tag = memory_arena_push_type(&arena, TagCompound); 227 | 228 | compound_tag->name = NULL; 229 | compound_tag->name_length = 0; 230 | compound_tag->ntags = 0; 231 | 232 | // TODO: This probably shouldn't be called recursively otherwise bad 233 | // actors could blow out the stack with nested lists. 234 | if (!ParseCompound(rb, *compound_tag, arena)) { 235 | return false; 236 | } 237 | 238 | tag.tag = compound_tag; 239 | } break; 240 | case TagType::IntArray: { 241 | TagIntArray* int_array_tag = memory_arena_push_type(&arena, TagIntArray); 242 | 243 | int_array_tag->length = 0; 244 | int_array_tag->data = NULL; 245 | 246 | if (rb.GetReadAmount() < sizeof(u32)) { 247 | return false; 248 | } 249 | 250 | int_array_tag->length = rb.ReadU32(); 251 | 252 | int_array_tag->data = memory_arena_push_type_count(&arena, s32, int_array_tag->length); 253 | 254 | for (size_t i = 0; i < int_array_tag->length; ++i) { 255 | s32* int_data = int_array_tag->data + i; 256 | 257 | if (rb.GetReadAmount() < sizeof(u32)) { 258 | return false; 259 | } 260 | 261 | *int_data = rb.ReadU32(); 262 | } 263 | 264 | tag.tag = int_array_tag; 265 | } break; 266 | case TagType::LongArray: { 267 | TagLongArray* long_array_tag = memory_arena_push_type(&arena, TagLongArray); 268 | 269 | long_array_tag->length = 0; 270 | long_array_tag->data = NULL; 271 | 272 | if (rb.GetReadAmount() < sizeof(u32)) { 273 | return false; 274 | } 275 | 276 | long_array_tag->length = rb.ReadU32(); 277 | 278 | long_array_tag->data = memory_arena_push_type_count(&arena, s64, long_array_tag->length); 279 | 280 | for (size_t i = 0; i < long_array_tag->length; ++i) { 281 | s64* long_data = long_array_tag->data + i; 282 | 283 | if (rb.GetReadAmount() < sizeof(s64)) { 284 | return false; 285 | } 286 | 287 | *long_data = rb.ReadU64(); 288 | } 289 | 290 | tag.tag = long_array_tag; 291 | } break; 292 | default: { 293 | fprintf(stderr, "Unknown NBT type: %d\n", (int)tag.type); 294 | return 0; 295 | } 296 | } 297 | 298 | return 1; 299 | } 300 | 301 | bool Parse(bool network_nbt, RingBuffer& rb, MemoryArena& arena, TagCompound* result) { 302 | TagType type = TagType::Unknown; 303 | 304 | if (rb.GetReadAmount() < sizeof(u8)) { 305 | return false; 306 | } 307 | 308 | type = (TagType)rb.ReadU8(); 309 | 310 | if (type == TagType::End) { 311 | return true; 312 | } 313 | 314 | if (type != TagType::Compound) { 315 | return false; 316 | } 317 | 318 | if (!network_nbt) { 319 | if (!ReadLengthString(rb, &result->name, &result->name_length, arena)) { 320 | return false; 321 | } 322 | } 323 | 324 | if (!ParseCompound(rb, *result, arena)) { 325 | return false; 326 | } 327 | 328 | return true; 329 | } 330 | 331 | } // namespace nbt 332 | } // namespace polymer 333 | -------------------------------------------------------------------------------- /polymer/network_queue.cpp: -------------------------------------------------------------------------------- 1 | #include "network_queue.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace polymer { 10 | 11 | void NetworkResponse::SaveToFile(const char* filename) { 12 | FILE* f = CreateAndOpenFile(filename, "wb"); 13 | 14 | if (!f) { 15 | fprintf(stderr, "Failed to open file to save NetworkResponse.\n"); 16 | return; 17 | } 18 | 19 | NetworkChunk* chunk = this->chunks; 20 | while (chunk) { 21 | fwrite(chunk->data, 1, chunk->size, f); 22 | chunk = chunk->next; 23 | } 24 | 25 | fclose(f); 26 | } 27 | 28 | void NetworkResponse::SaveToFile(String filename) { 29 | char* z_filename = (char*)malloc(filename.size + 1); 30 | if (!z_filename) { 31 | fprintf(stderr, "Failed allocate space for saving NetworkResponse.\n"); 32 | return; 33 | } 34 | 35 | memcpy(z_filename, filename.data, filename.size); 36 | z_filename[filename.size] = 0; 37 | 38 | SaveToFile(z_filename); 39 | free(z_filename); 40 | } 41 | 42 | static size_t OnCurlWrite(char* data, size_t n, size_t l, void* userp) { 43 | NetworkActiveRequest* request = (NetworkActiveRequest*)userp; 44 | size_t recv_size = n * l; 45 | 46 | if (request->chunks == nullptr) { 47 | NetworkChunk* chunk = request->queue->AllocateChunk(); 48 | 49 | request->chunks = chunk; 50 | request->last_chunk = chunk; 51 | } 52 | 53 | size_t remaining_chunk_space = sizeof(NetworkChunk::data) - request->last_chunk->size; 54 | 55 | if (remaining_chunk_space >= recv_size) { 56 | memcpy(request->last_chunk->data + request->last_chunk->size, data, recv_size); 57 | request->last_chunk->size += recv_size; 58 | } else { 59 | // Write to the end of this chunk 60 | memcpy(request->last_chunk->data + request->last_chunk->size, data, remaining_chunk_space); 61 | request->last_chunk->size += remaining_chunk_space; 62 | 63 | size_t written = remaining_chunk_space; 64 | while (written < recv_size) { 65 | // Allocate a new chunk and stick it into the active request chunk list. 66 | NetworkChunk* chunk = request->queue->AllocateChunk(); 67 | 68 | request->last_chunk->next = chunk; 69 | request->last_chunk = chunk; 70 | 71 | // Try to write the entire remaining data. 72 | size_t write_size = recv_size - written; 73 | // If the write size is more than this chunk, then cap it to this chunk and loop again to allocate a new one. 74 | if (write_size > sizeof(NetworkChunk::data)) { 75 | write_size = sizeof(NetworkChunk::data); 76 | } 77 | 78 | memcpy(request->last_chunk->data, data + written, write_size); 79 | request->last_chunk->size += write_size; 80 | 81 | written += write_size; 82 | } 83 | } 84 | 85 | request->size += recv_size; 86 | 87 | return recv_size; 88 | } 89 | 90 | bool NetworkQueue::Initialize() { 91 | if (curl_global_init(CURL_GLOBAL_ALL)) { 92 | fprintf(stderr, "network_queue: Failed to initialize curl.\n"); 93 | return false; 94 | } 95 | 96 | curl_multi = curl_multi_init(); 97 | if (!curl_multi) { 98 | fprintf(stderr, "network_queue: Failed to create curl_multi.\n"); 99 | return false; 100 | } 101 | 102 | if (curl_multi_setopt(curl_multi, CURLMOPT_MAXCONNECTS, kParallelRequests)) { 103 | fprintf(stderr, "network_queue: Failed to setup curl_multi.\n"); 104 | return false; 105 | } 106 | 107 | return true; 108 | } 109 | 110 | NetworkRequest* NetworkQueue::AllocateRequest() { 111 | NetworkRequest* request = nullptr; 112 | 113 | if (!free) { 114 | free = (NetworkRequest*)malloc(sizeof(NetworkRequest)); 115 | if (!free) { 116 | fprintf(stderr, "network_queue: Failed to allocate NetworkRequest.\n"); 117 | return nullptr; 118 | } 119 | 120 | free->next = nullptr; 121 | } 122 | 123 | request = free; 124 | free = free->next; 125 | 126 | request->next = nullptr; 127 | return request; 128 | } 129 | 130 | NetworkRequest* NetworkQueue::PushRequest(String url, void* userp, NetworkCompleteCallback callback) { 131 | NetworkRequest* request = AllocateRequest(); 132 | 133 | if (!request) return nullptr; 134 | 135 | memcpy(request->url, url.data, url.size); 136 | request->url[url.size] = 0; 137 | 138 | request->userp = userp; 139 | request->callback = callback; 140 | 141 | if (waiting_queue_end) { 142 | waiting_queue_end->next = request; 143 | waiting_queue_end = request; 144 | } else { 145 | waiting_queue = request; 146 | waiting_queue_end = request; 147 | } 148 | 149 | return request; 150 | } 151 | 152 | NetworkRequest* NetworkQueue::PushRequest(const char* url, void* userp, NetworkCompleteCallback callback) { 153 | NetworkRequest* request = AllocateRequest(); 154 | 155 | if (!request) return nullptr; 156 | 157 | strcpy(request->url, url); 158 | request->userp = userp; 159 | request->callback = callback; 160 | 161 | if (waiting_queue_end) { 162 | waiting_queue_end->next = request; 163 | waiting_queue_end = request; 164 | } else { 165 | waiting_queue = request; 166 | waiting_queue_end = request; 167 | } 168 | 169 | return request; 170 | } 171 | 172 | void NetworkQueue::Run() { 173 | ProcessWaitingQueue(); 174 | 175 | if (active_request_count == 0) return; 176 | 177 | int still_alive = 1; 178 | curl_multi_perform(curl_multi, &still_alive); 179 | 180 | CURLMsg* msg = nullptr; 181 | 182 | int msg_count = 0; 183 | 184 | while ((msg = curl_multi_info_read(curl_multi, &msg_count))) { 185 | if (msg->msg == CURLMSG_DONE) { 186 | NetworkActiveRequest* active_request = nullptr; 187 | 188 | CURL* e = msg->easy_handle; 189 | long http_code = 0; 190 | 191 | curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &active_request); 192 | curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &http_code); 193 | 194 | if (active_request) { 195 | NetworkResponse response; 196 | 197 | response.http_code = (int)http_code; 198 | response.transfer_code = msg->data.result; 199 | response.size = active_request->size; 200 | response.chunks = active_request->chunks; 201 | 202 | printf("net_queue: Completed download of '%s'. Size: %u.\n", active_request->request->url, (u32)response.size); 203 | 204 | if (active_request->request->callback) { 205 | active_request->request->callback(active_request->request, &response); 206 | } 207 | 208 | NetworkChunk* chunk = response.chunks; 209 | while (chunk) { 210 | NetworkChunk* current = chunk; 211 | chunk = chunk->next; 212 | 213 | FreeChunk(current); 214 | } 215 | 216 | active_request->active = false; 217 | } 218 | 219 | curl_multi_remove_handle(curl_multi, e); 220 | curl_easy_cleanup(e); 221 | --active_request_count; 222 | } else { 223 | fprintf(stderr, "E: CURLMsg (%d)\n", msg->msg); 224 | } 225 | } 226 | } 227 | 228 | void NetworkQueue::ProcessWaitingQueue() { 229 | // Loop over active requests looking for an empty spot for the waiting request. 230 | // It will stop looping when the waiting queue is empty. 231 | for (size_t i = 0; waiting_queue && i < polymer_array_count(active_requests); ++i) { 232 | if (!active_requests[i].active) { 233 | active_requests[i].queue = this; 234 | active_requests[i].request = waiting_queue; 235 | active_requests[i].active = true; 236 | active_requests[i].size = 0; 237 | active_requests[i].chunks = active_requests[i].last_chunk = nullptr; 238 | 239 | CURL* eh = curl_easy_init(); 240 | 241 | long enable = 1; 242 | 243 | curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, OnCurlWrite); 244 | curl_easy_setopt(eh, CURLOPT_URL, waiting_queue->url); 245 | curl_easy_setopt(eh, CURLOPT_PRIVATE, active_requests + i); 246 | curl_easy_setopt(eh, CURLOPT_WRITEDATA, active_requests + i); 247 | curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, &enable); 248 | 249 | printf("net_queue: Requesting url '%s'.\n", waiting_queue->url); 250 | 251 | curl_multi_add_handle(curl_multi, eh); 252 | 253 | if (waiting_queue == waiting_queue_end) { 254 | waiting_queue_end = nullptr; 255 | } 256 | 257 | waiting_queue = waiting_queue->next; 258 | ++active_request_count; 259 | } 260 | } 261 | } 262 | 263 | inline void FreeRequests(NetworkRequest* request) { 264 | while (request) { 265 | NetworkRequest* current = request; 266 | 267 | request = request->next; 268 | 269 | ::free(current); 270 | } 271 | } 272 | 273 | void NetworkQueue::Clear() { 274 | FreeRequests(free); 275 | FreeRequests(waiting_queue); 276 | 277 | free = nullptr; 278 | waiting_queue = nullptr; 279 | waiting_queue_end = nullptr; 280 | 281 | NetworkChunk* chunk = free_chunks; 282 | while (chunk) { 283 | NetworkChunk* current = chunk; 284 | 285 | chunk = chunk->next; 286 | 287 | ::free(current); 288 | } 289 | 290 | free_chunks = nullptr; 291 | } 292 | 293 | bool NetworkQueue::IsEmpty() const { 294 | return active_request_count == 0 && !waiting_queue; 295 | } 296 | 297 | NetworkChunk* NetworkQueue::AllocateChunk() { 298 | if (!free_chunks) { 299 | NetworkChunk* chunk = (NetworkChunk*)malloc(sizeof(NetworkChunk)); 300 | if (chunk) { 301 | chunk->size = 0; 302 | chunk->next = nullptr; 303 | } else { 304 | fprintf(stderr, "network_queue: Failed to allocate NetworkChunk.\n"); 305 | exit(1); 306 | } 307 | 308 | free_chunks = chunk; 309 | } 310 | 311 | NetworkChunk* result = free_chunks; 312 | 313 | free_chunks = free_chunks->next; 314 | 315 | result->size = 0; 316 | result->next = nullptr; 317 | 318 | return result; 319 | } 320 | 321 | void NetworkQueue::FreeChunk(NetworkChunk* chunk) { 322 | chunk->next = free_chunks; 323 | free_chunks = chunk; 324 | } 325 | 326 | } // namespace polymer 327 | -------------------------------------------------------------------------------- /polymer/platform/unix/unix_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #define GLFW_INCLUDE_VULKAN 14 | #include 15 | 16 | #define VOLK_IMPLEMENTATION 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace polymer { 25 | 26 | static Polymer* g_application; 27 | static InputState g_input = {}; 28 | static bool g_display_cursor = false; 29 | 30 | struct CursorPosition { 31 | int x = 0; 32 | int y = 0; 33 | }; 34 | 35 | CursorPosition g_last_cursor; 36 | bool g_frame_chat_open = false; 37 | 38 | inline void ToggleCursor() { 39 | GLFWwindow* window = (GLFWwindow*)g_application->window; 40 | g_display_cursor = !g_display_cursor; 41 | 42 | int mode = g_display_cursor ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED; 43 | glfwSetInputMode(window, GLFW_CURSOR, mode); 44 | } 45 | 46 | static void ResetLastCursor(GLFWwindow* window) { 47 | double xpos, ypos; 48 | 49 | glfwGetCursorPos(window, &xpos, &ypos); 50 | 51 | g_last_cursor.x = (int)xpos; 52 | g_last_cursor.y = (int)ypos; 53 | } 54 | 55 | static void OnWindowResize(GLFWwindow* window, int width, int height) { 56 | g_application->renderer.invalid_swapchain = true; 57 | } 58 | 59 | static void OnCursorPosition(GLFWwindow* window, double xpos, double ypos) { 60 | if (!g_display_cursor) { 61 | int dx = xpos - g_last_cursor.x; 62 | int dy = ypos - g_last_cursor.y; 63 | 64 | g_application->game->OnWindowMouseMove(dx, dy); 65 | 66 | g_last_cursor.x = xpos; 67 | g_last_cursor.y = ypos; 68 | } 69 | } 70 | 71 | void OnCharacterPress(GLFWwindow* window, unsigned int codepoint) { 72 | if (g_frame_chat_open) { 73 | g_frame_chat_open = false; 74 | return; 75 | } 76 | 77 | g_frame_chat_open = false; 78 | 79 | if (g_application->game->chat_window.display_full) { 80 | g_application->game->chat_window.OnInput(codepoint); 81 | } 82 | } 83 | 84 | static void OnKeyDown(GLFWwindow* window, int key, int scancode, int action, int mods) { 85 | GameState* game = g_application->game; 86 | 87 | switch (key) { 88 | case GLFW_KEY_ESCAPE: { 89 | if (action == GLFW_PRESS) { 90 | ToggleCursor(); 91 | game->chat_window.ToggleDisplay(); 92 | g_frame_chat_open = true; 93 | memset(&g_input, 0, sizeof(g_input)); 94 | ResetLastCursor(window); 95 | } 96 | } break; 97 | } 98 | 99 | if (!game->chat_window.display_full) { 100 | switch (key) { 101 | case GLFW_KEY_SLASH: 102 | case GLFW_KEY_T: { 103 | if (action == GLFW_PRESS && !game->chat_window.display_full) { 104 | ToggleCursor(); 105 | game->chat_window.ToggleDisplay(); 106 | g_frame_chat_open = true; 107 | memset(&g_input, 0, sizeof(g_input)); 108 | 109 | if (key == GLFW_KEY_SLASH) { 110 | game->chat_window.input.active = true; 111 | game->chat_window.OnInput('/'); 112 | } 113 | } 114 | } break; 115 | case GLFW_KEY_W: { 116 | g_input.forward = action != GLFW_RELEASE; 117 | } break; 118 | case GLFW_KEY_S: { 119 | g_input.backward = action != GLFW_RELEASE; 120 | } break; 121 | case GLFW_KEY_A: { 122 | g_input.left = action != GLFW_RELEASE; 123 | } break; 124 | case GLFW_KEY_D: { 125 | g_input.right = action != GLFW_RELEASE; 126 | } break; 127 | case GLFW_KEY_SPACE: { 128 | g_input.climb = action != GLFW_RELEASE; 129 | } break; 130 | case GLFW_KEY_LEFT_SHIFT: 131 | case GLFW_KEY_RIGHT_SHIFT: { 132 | g_input.fall = action != GLFW_RELEASE; 133 | } break; 134 | case GLFW_KEY_LEFT_CONTROL: 135 | case GLFW_KEY_RIGHT_CONTROL: { 136 | g_input.sprint = action != GLFW_RELEASE; 137 | } break; 138 | case GLFW_KEY_TAB: { 139 | g_input.display_players = action != GLFW_RELEASE; 140 | } break; 141 | } 142 | } else if (action != GLFW_RELEASE) { 143 | switch (key) { 144 | case GLFW_KEY_ENTER: { 145 | fflush(stdout); 146 | ToggleCursor(); 147 | game->chat_window.SendInput(game->connection); 148 | game->chat_window.ToggleDisplay(); 149 | ResetLastCursor(window); 150 | } break; 151 | case GLFW_KEY_LEFT: { 152 | game->chat_window.MoveCursor(ui::ChatMoveDirection::Left); 153 | } break; 154 | case GLFW_KEY_RIGHT: { 155 | game->chat_window.MoveCursor(ui::ChatMoveDirection::Right); 156 | } break; 157 | case GLFW_KEY_HOME: { 158 | game->chat_window.MoveCursor(ui::ChatMoveDirection::Home); 159 | } break; 160 | case GLFW_KEY_END: { 161 | game->chat_window.MoveCursor(ui::ChatMoveDirection::End); 162 | } break; 163 | case GLFW_KEY_DELETE: { 164 | game->chat_window.OnDelete(); 165 | } break; 166 | case GLFW_KEY_BACKSPACE: { 167 | game->chat_window.OnInput(0x08); 168 | } break; 169 | } 170 | } 171 | } 172 | 173 | Platform g_Platform; 174 | 175 | static const char* UnixGetPlatformName() { 176 | return "Linux"; 177 | } 178 | 179 | static PolymerWindow UnixWindowCreate(int width, int height) { 180 | GLFWwindow* window = glfwCreateWindow(width, height, "Polymer", NULL, NULL); 181 | if (!window) { 182 | fprintf(stderr, "Failed to create glfw window\n"); 183 | glfwTerminate(); 184 | return nullptr; 185 | } 186 | 187 | glfwSetWindowSizeCallback(window, OnWindowResize); 188 | glfwSetKeyCallback(window, OnKeyDown); 189 | glfwSetCursorPosCallback(window, OnCursorPosition); 190 | glfwSetCharCallback(window, OnCharacterPress); 191 | 192 | if (glfwRawMouseMotionSupported()) { 193 | glfwSetInputMode(window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); 194 | } 195 | 196 | glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); 197 | 198 | return (PolymerWindow)window; 199 | } 200 | 201 | static bool UnixWindowCreateSurface(PolymerWindow window, void* surface) { 202 | VkResult result = 203 | glfwCreateWindowSurface(g_application->renderer.instance, (GLFWwindow*)window, nullptr, (VkSurfaceKHR*)surface); 204 | 205 | return result == VK_SUCCESS; 206 | } 207 | 208 | static IntRect UnixWindowGetRect(PolymerWindow window) { 209 | IntRect rect = {}; 210 | 211 | glfwGetWindowSize((GLFWwindow*)window, &rect.right, &rect.bottom); 212 | 213 | return rect; 214 | } 215 | 216 | static void UnixWindowPump(PolymerWindow window) { 217 | glfwPollEvents(); 218 | 219 | if (glfwWindowShouldClose((GLFWwindow*)window)) { 220 | g_application->game->connection.Disconnect(); 221 | } 222 | } 223 | 224 | static const char* kDeviceExtensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; 225 | // static const char* kValidationLayers[] = {"VK_LAYER_KHRONOS_validation"}; 226 | static const char* kValidationLayers[] = {}; 227 | 228 | static ExtensionRequest UnixGetExtensionRequest() { 229 | uint32_t count; 230 | const char** extensions = glfwGetRequiredInstanceExtensions(&count); 231 | 232 | ExtensionRequest extension_request; 233 | 234 | extension_request.extensions = extensions; 235 | extension_request.extension_count = count; 236 | 237 | extension_request.device_extension_count = polymer_array_count(kDeviceExtensions); 238 | extension_request.device_extensions = kDeviceExtensions; 239 | 240 | extension_request.validation_layer_count = polymer_array_count(kValidationLayers); 241 | extension_request.validation_layers = kValidationLayers; 242 | 243 | return extension_request; 244 | } 245 | 246 | static String UnixGetAssetStorePath(MemoryArena& arena) { 247 | const char* homedir; 248 | 249 | if ((homedir = getenv("HOME")) == NULL) { 250 | homedir = getpwuid(getuid())->pw_dir; 251 | } 252 | 253 | if (!homedir) { 254 | fprintf(stderr, "Failed to get home directory.\n"); 255 | exit(1); 256 | } 257 | 258 | size_t length = strlen(homedir); 259 | 260 | const char* kAssetStoreName = "/.polymer/"; 261 | size_t name_length = sizeof(kAssetStoreName); 262 | 263 | size_t total_size = length + name_length + 2; 264 | char* path_storage = memory_arena_push_type_count(&arena, char, total_size); 265 | 266 | sprintf(path_storage, "%s%s/", homedir, kAssetStoreName); 267 | 268 | return String(path_storage, total_size); 269 | } 270 | 271 | static bool UnixFolderExists(const char* path) { 272 | struct stat s = {}; 273 | 274 | if (stat(path, &s)) { 275 | return false; 276 | } 277 | 278 | return (s.st_mode & S_IFDIR); 279 | } 280 | 281 | static bool UnixCreateFolder(const char* path) { 282 | return mkdir(path, 0700) == 0; 283 | } 284 | 285 | static u8* UnixAllocate(size_t size) { 286 | u8* result = (u8*)malloc(size); 287 | return result; 288 | } 289 | 290 | static void UnixFree(u8* ptr) { 291 | free(ptr); 292 | } 293 | 294 | } // namespace polymer 295 | 296 | int main(int argc, char* argv[]) { 297 | using namespace polymer; 298 | 299 | if (volkInitialize() != VK_SUCCESS) { 300 | fprintf(stderr, "Failed to get Vulkan loader.\n"); 301 | fflush(stderr); 302 | return 1; 303 | } 304 | 305 | constexpr size_t kPermanentSize = Gigabytes(1); 306 | constexpr size_t kTransientSize = Megabytes(256); 307 | 308 | u8* perm_memory = (u8*)malloc(kPermanentSize); 309 | u8* trans_memory = (u8*)malloc(kTransientSize); 310 | 311 | MemoryArena perm_arena(perm_memory, kPermanentSize); 312 | MemoryArena trans_arena(trans_memory, kTransientSize); 313 | 314 | Polymer* polymer = perm_arena.Construct(perm_arena, trans_arena, argc, argv); 315 | 316 | polymer->platform = {UnixGetPlatformName, UnixWindowCreate, UnixWindowCreateSurface, 317 | UnixWindowGetRect, UnixWindowPump, UnixGetExtensionRequest, 318 | UnixGetAssetStorePath, UnixFolderExists, UnixCreateFolder, 319 | UnixAllocate, UnixFree}; 320 | 321 | g_Platform = polymer->platform; 322 | g_application = polymer; 323 | 324 | if (!glfwInit()) { 325 | fprintf(stderr, "Failed to initialize glfw.\n"); 326 | return 1; 327 | } 328 | 329 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 330 | 331 | return polymer->Run(&g_input); 332 | } 333 | -------------------------------------------------------------------------------- /polymer/world/world.cpp: -------------------------------------------------------------------------------- 1 | #include "world.h" 2 | 3 | namespace polymer { 4 | namespace world { 5 | 6 | World::World(MemoryArena& trans_arena, render::VulkanRenderer& renderer, asset::AssetSystem& assets, 7 | BlockRegistry& block_registry) 8 | : trans_arena(trans_arena), renderer(renderer), block_registry(block_registry), 9 | block_mesher(trans_arena, assets, block_registry) { 10 | for (u32 chunk_z = 0; chunk_z < kChunkCacheSize; ++chunk_z) { 11 | for (u32 chunk_x = 0; chunk_x < kChunkCacheSize; ++chunk_x) { 12 | ChunkSection* section = &chunks[chunk_z][chunk_x]; 13 | ChunkSectionInfo* section_info = &chunk_infos[chunk_z][chunk_x]; 14 | ChunkMesh* meshes = this->meshes[chunk_z][chunk_x]; 15 | 16 | section->info = section_info; 17 | 18 | for (u32 chunk_y = 0; chunk_y < kChunkColumnCount; ++chunk_y) { 19 | section->chunks[chunk_y] = nullptr; 20 | 21 | for (s32 i = 0; i < render::kRenderLayerCount; ++i) { 22 | meshes[chunk_y].meshes[i].vertex_count = 0; 23 | } 24 | } 25 | } 26 | } 27 | } 28 | 29 | void World::Update(float dt) {} 30 | 31 | void World::OnBlockChange(s32 x, s32 y, s32 z, u32 new_bid) { 32 | s32 chunk_x = (s32)floorf(x / 16.0f); 33 | s32 chunk_z = (s32)floorf(z / 16.0f); 34 | s32 chunk_y = (s32)floorf(y / 16.0f) + 4; 35 | 36 | u32 x_index = GetChunkCacheIndex(chunk_x); 37 | u32 z_index = GetChunkCacheIndex(chunk_z); 38 | 39 | ChunkSection* section = &chunks[z_index][x_index]; 40 | 41 | if (!section->info->loaded || (section->info->x != chunk_x || section->info->z != chunk_z)) return; 42 | 43 | s32 relative_x = x % 16; 44 | s32 relative_y = y % 16; 45 | s32 relative_z = z % 16; 46 | 47 | if (relative_x < 0) { 48 | relative_x += 16; 49 | } 50 | 51 | if (relative_y < 0) { 52 | relative_y += 16; 53 | } 54 | 55 | if (relative_z < 0) { 56 | relative_z += 16; 57 | } 58 | 59 | u32 old_bid = 0; 60 | 61 | if (section->chunks[chunk_y]) { 62 | old_bid = section->chunks[chunk_y]->blocks[relative_y][relative_z][relative_x]; 63 | } 64 | 65 | if (new_bid != 0) { 66 | ChunkSectionInfo* section_info = &chunk_infos[z_index][x_index]; 67 | 68 | section_info->bitmask |= (1 << chunk_y); 69 | section_info->loaded = true; 70 | 71 | if (!section->chunks[chunk_y]) { 72 | section->chunks[chunk_y] = chunk_pool.Allocate(); 73 | } 74 | } 75 | 76 | if (section->chunks[chunk_y]) { 77 | section->chunks[chunk_y]->blocks[relative_y][relative_z][relative_x] = (u32)new_bid; 78 | } 79 | 80 | EnqueueChunk(chunk_x, chunk_y, chunk_z); 81 | 82 | if (relative_x == 0) { 83 | // Rebuild west 84 | EnqueueChunk(chunk_x - 1, chunk_y, chunk_z); 85 | } else if (relative_x == 15) { 86 | // Rebuild east 87 | EnqueueChunk(chunk_x + 1, chunk_y, chunk_z); 88 | } 89 | 90 | if (relative_z == 0) { 91 | // Rebuild north 92 | EnqueueChunk(chunk_x, chunk_y, chunk_z - 1); 93 | } else if (relative_z == 15) { 94 | // Rebuild south 95 | EnqueueChunk(chunk_x, chunk_y, chunk_z + 1); 96 | } 97 | 98 | if (relative_y == 0 && chunk_y > 0) { 99 | // Rebuild below 100 | EnqueueChunk(chunk_x, chunk_y - 1, chunk_z); 101 | } else if (relative_y == 15 && chunk_y < 15) { 102 | // Rebuild above 103 | EnqueueChunk(chunk_x, chunk_y + 1, chunk_z); 104 | } 105 | } 106 | 107 | void World::OnChunkLoad(s32 chunk_x, s32 chunk_z) { 108 | u32 x_index = GetChunkCacheIndex(chunk_x); 109 | u32 z_index = GetChunkCacheIndex(chunk_z); 110 | 111 | ChunkSectionInfo* section_info = &chunk_infos[z_index][x_index]; 112 | ChunkMesh* meshes = this->meshes[z_index][x_index]; 113 | 114 | if (section_info->loaded) { 115 | printf("Got chunk %d, %d with existing chunk %d, %d.\n", chunk_x, chunk_z, section_info->x, section_info->z); 116 | renderer.WaitForIdle(); 117 | 118 | // Force clear any existing meshes 119 | for (s32 chunk_y = 0; chunk_y < kChunkColumnCount; ++chunk_y) { 120 | ChunkMesh* mesh = meshes + chunk_y; 121 | 122 | for (s32 i = 0; i < render::kRenderLayerCount; ++i) { 123 | if (mesh->meshes[i].vertex_count > 0) { 124 | renderer.FreeMesh(&mesh->meshes[i]); 125 | mesh->meshes[i].vertex_count = 0; 126 | } 127 | } 128 | } 129 | } 130 | 131 | section_info->loaded = true; 132 | section_info->x = chunk_x; 133 | section_info->z = chunk_z; 134 | 135 | section_info->dirty_connectivity_set = 0xFFFFFF; 136 | section_info->dirty_mesh_set = 0xFFFFFF; 137 | } 138 | 139 | void World::OnChunkUnload(s32 chunk_x, s32 chunk_z) { 140 | u32 x_index = GetChunkCacheIndex(chunk_x); 141 | u32 z_index = GetChunkCacheIndex(chunk_z); 142 | ChunkSection* section = &chunks[z_index][x_index]; 143 | ChunkSectionInfo* section_info = &chunk_infos[z_index][x_index]; 144 | 145 | // It's possible to receive an unload packet after receiving a new chunk that would take this chunk's position in 146 | // the cache, so it needs to be checked before anything is changed in the cache. 147 | if (section_info->x != chunk_x || section_info->z != chunk_z) { 148 | return; 149 | } 150 | 151 | section_info->loaded = false; 152 | section_info->bitmask = 0; 153 | section_info->dirty_connectivity_set = 0xFFFFFF; 154 | section_info->dirty_mesh_set = 0; 155 | 156 | for (s32 chunk_y = 0; chunk_y < kChunkColumnCount; ++chunk_y) { 157 | if (section->chunks[chunk_y]) { 158 | chunk_pool.Free(section->chunks[chunk_y]); 159 | section->chunks[chunk_y] = nullptr; 160 | } 161 | } 162 | 163 | ChunkMesh* meshes = this->meshes[z_index][x_index]; 164 | 165 | renderer.WaitForIdle(); 166 | 167 | for (s32 chunk_y = 0; chunk_y < kChunkColumnCount; ++chunk_y) { 168 | for (s32 i = 0; i < render::kRenderLayerCount; ++i) { 169 | if (meshes[chunk_y].meshes[i].vertex_count > 0) { 170 | renderer.FreeMesh(&meshes[chunk_y].meshes[i]); 171 | meshes[chunk_y].meshes[i].vertex_count = 0; 172 | } 173 | } 174 | } 175 | } 176 | 177 | void World::OnDimensionChange() { 178 | renderer.WaitForIdle(); 179 | 180 | for (s32 chunk_z = 0; chunk_z < kChunkCacheSize; ++chunk_z) { 181 | for (s32 chunk_x = 0; chunk_x < kChunkCacheSize; ++chunk_x) { 182 | ChunkSectionInfo* section_info = &chunk_infos[chunk_z][chunk_x]; 183 | ChunkMesh* meshes = this->meshes[chunk_z][chunk_x]; 184 | 185 | section_info->loaded = false; 186 | section_info->dirty_connectivity_set = 0xFFFFFF; 187 | section_info->dirty_mesh_set = 0; 188 | section_info->bitmask = 0; 189 | 190 | for (s32 chunk_y = 0; chunk_y < kChunkColumnCount; ++chunk_y) { 191 | ChunkMesh* mesh = meshes + chunk_y; 192 | 193 | for (s32 i = 0; i < render::kRenderLayerCount; ++i) { 194 | if (mesh->meshes[i].vertex_count > 0) { 195 | renderer.FreeMesh(&mesh->meshes[i]); 196 | mesh->meshes[i].vertex_count = 0; 197 | } 198 | } 199 | } 200 | } 201 | } 202 | } 203 | 204 | void World::BuildChunkMesh(render::ChunkBuildContext* ctx, s32 chunk_x, s32 chunk_y, s32 chunk_z) { 205 | MemoryRevert arena_revert = trans_arena.GetReverter(); 206 | 207 | render::ChunkVertexData vertex_data = block_mesher.CreateMesh(ctx, chunk_y); 208 | 209 | ChunkMesh* meshes = this->meshes[ctx->z_index][ctx->x_index]; 210 | 211 | for (s32 i = 0; i < render::kRenderLayerCount; ++i) { 212 | if (meshes[chunk_y].meshes[i].vertex_count > 0) { 213 | renderer.WaitForIdle(); 214 | renderer.FreeMesh(&meshes[chunk_y].meshes[i]); 215 | meshes[chunk_y].meshes[i].vertex_count = 0; 216 | } 217 | 218 | if (vertex_data.vertex_count[i] > 0) { 219 | assert(vertex_data.vertex_count[i] <= 0xFFFFFFFF); 220 | 221 | const size_t data_size = sizeof(render::ChunkVertex) * vertex_data.vertex_count[i]; 222 | 223 | meshes[chunk_y].meshes[i].vertex_count = (u32)vertex_data.vertex_count[i]; 224 | meshes[chunk_y].meshes[i] = renderer.AllocateMesh(vertex_data.vertices[i], data_size, vertex_data.vertex_count[i], 225 | vertex_data.indices[i], vertex_data.index_count[i]); 226 | } 227 | } 228 | 229 | block_mesher.Reset(); 230 | } 231 | 232 | void World::EnqueueChunk(s32 chunk_x, s32 chunk_y, s32 chunk_z) { 233 | u32 x_index = world::GetChunkCacheIndex(chunk_x); 234 | u32 z_index = world::GetChunkCacheIndex(chunk_z); 235 | 236 | ChunkSectionInfo* section_info = &chunk_infos[z_index][x_index]; 237 | 238 | section_info->dirty_mesh_set |= (1 << chunk_y); 239 | section_info->dirty_connectivity_set |= (1 << chunk_y); 240 | } 241 | 242 | void World::BuildChunkMesh(render::ChunkBuildContext* ctx) { 243 | ChunkSectionInfo* section_info = &chunk_infos[ctx->z_index][ctx->x_index]; 244 | 245 | renderer.BeginMeshAllocation(); 246 | 247 | ChunkMesh* meshes = this->meshes[ctx->z_index][ctx->x_index]; 248 | 249 | for (s32 chunk_y = 0; chunk_y < kChunkColumnCount; ++chunk_y) { 250 | if (section_info->dirty_connectivity_set & (1 << chunk_y)) { 251 | connectivity_graph.Build(*this, this->chunks[ctx->z_index][ctx->x_index].chunks[chunk_y], ctx->x_index, 252 | ctx->z_index, chunk_y); 253 | } 254 | 255 | if (!(section_info->bitmask & (1 << chunk_y))) { 256 | for (s32 i = 0; i < render::kRenderLayerCount; ++i) { 257 | meshes[chunk_y].meshes[i].vertex_count = 0; 258 | } 259 | 260 | continue; 261 | } 262 | 263 | if (section_info->dirty_mesh_set & (1 << chunk_y)) { 264 | BuildChunkMesh(ctx, ctx->chunk_x, chunk_y, ctx->chunk_z); 265 | } 266 | } 267 | 268 | section_info->dirty_mesh_set = 0; 269 | section_info->dirty_connectivity_set = 0; 270 | 271 | renderer.EndMeshAllocation(); 272 | } 273 | 274 | void World::FreeMeshes() { 275 | for (u32 chunk_z = 0; chunk_z < kChunkCacheSize; ++chunk_z) { 276 | for (u32 chunk_x = 0; chunk_x < kChunkCacheSize; ++chunk_x) { 277 | ChunkMesh* meshes = this->meshes[chunk_z][chunk_x]; 278 | 279 | for (u32 chunk_y = 0; chunk_y < kChunkColumnCount; ++chunk_y) { 280 | ChunkMesh* mesh = meshes + chunk_y; 281 | 282 | for (s32 i = 0; i < render::kRenderLayerCount; ++i) { 283 | if (mesh->meshes[i].vertex_count > 0) { 284 | renderer.FreeMesh(&mesh->meshes[i]); 285 | mesh->meshes[i].vertex_count = 0; 286 | } 287 | } 288 | } 289 | } 290 | } 291 | } 292 | 293 | } // namespace world 294 | } // namespace polymer 295 | -------------------------------------------------------------------------------- /polymer/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace polymer { 11 | 12 | FILE* CreateAndOpenFileImpl(char* filename, const char* mode) { 13 | char* current = filename; 14 | 15 | while (*current++) { 16 | char c = *current; 17 | if (c == '/' || c == '\\') { 18 | // Null terminate up to the current folder so it gets checked as existing. 19 | char prev = *(current + 1); 20 | *(current + 1) = 0; 21 | 22 | if (!g_Platform.FolderExists(filename)) { 23 | if (!g_Platform.CreateFolder(filename)) { 24 | fprintf(stderr, "Failed to create folder '%s'.\n", filename); 25 | *(current + 1) = prev; 26 | return nullptr; 27 | } 28 | } 29 | 30 | // Revert to previous value so it can keep processing. 31 | *(current + 1) = prev; 32 | } 33 | } 34 | 35 | return fopen(filename, mode); 36 | } 37 | 38 | FILE* CreateAndOpenFile(String filename, const char* mode) { 39 | char* mirror = (char*)malloc(filename.size + 1); 40 | 41 | if (!mirror) return nullptr; 42 | 43 | memcpy(mirror, filename.data, filename.size); 44 | mirror[filename.size] = 0; 45 | 46 | FILE* f = CreateAndOpenFileImpl(mirror, mode); 47 | 48 | free(mirror); 49 | 50 | return f; 51 | } 52 | 53 | FILE* CreateAndOpenFile(const char* filename, const char* mode) { 54 | size_t name_len = strlen(filename); 55 | char* mirror = (char*)malloc(name_len + 1); 56 | 57 | if (!mirror) return nullptr; 58 | 59 | memcpy(mirror, filename, name_len); 60 | mirror[name_len] = 0; 61 | 62 | FILE* f = CreateAndOpenFileImpl(mirror, mode); 63 | 64 | free(mirror); 65 | 66 | return f; 67 | } 68 | 69 | String ReadEntireFile(const char* filename, MemoryArena& arena) { 70 | String result = {}; 71 | FILE* f = fopen(filename, "rb"); 72 | 73 | if (!f) { 74 | return result; 75 | } 76 | 77 | fseek(f, 0, SEEK_END); 78 | long size = ftell(f); 79 | fseek(f, 0, SEEK_SET); 80 | 81 | char* buffer = memory_arena_push_type_count(&arena, char, size); 82 | 83 | long total_read = 0; 84 | while (total_read < size) { 85 | total_read += (long)fread(buffer + total_read, 1, size - total_read, f); 86 | } 87 | 88 | fclose(f); 89 | 90 | result.data = buffer; 91 | result.size = size; 92 | 93 | return result; 94 | } 95 | 96 | struct Sha1 { 97 | static constexpr size_t kDigestSize = 20; 98 | 99 | struct Context { 100 | uint32_t state[5]; 101 | uint32_t count[2]; 102 | uint8_t buffer[64]; 103 | }; 104 | 105 | typedef uint8_t Digest[kDigestSize]; 106 | 107 | static void Init(Context* context); 108 | static void Update(Context* context, const uint8_t* data, const size_t len); 109 | static void Final(Context* context, uint8_t digest[kDigestSize]); 110 | }; 111 | 112 | /* 113 | SHA-1 114 | Based on the public domain implementation by Steve Reid . 115 | */ 116 | 117 | #define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) 118 | 119 | static void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64]); 120 | 121 | #define blk0(i) \ 122 | (block.l[i] = \ 123 | ((block.c[i * 4] << 24) | (block.c[i * 4 + 1] << 16) | (block.c[i * 4 + 2] << 8) | (block.c[i * 4 + 3]))) 124 | 125 | #define blk(i) \ 126 | (block.l[i & 15] = rol(block.l[(i + 13) & 15] ^ block.l[(i + 8) & 15] ^ block.l[(i + 2) & 15] ^ block.l[i & 15], 1)) 127 | 128 | /* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ 129 | #define R0(v, w, x, y, z, i) \ 130 | z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \ 131 | w = rol(w, 30); 132 | #define R1(v, w, x, y, z, i) \ 133 | z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ 134 | w = rol(w, 30); 135 | #define R2(v, w, x, y, z, i) \ 136 | z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ 137 | w = rol(w, 30); 138 | #define R3(v, w, x, y, z, i) \ 139 | z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ 140 | w = rol(w, 30); 141 | #define R4(v, w, x, y, z, i) \ 142 | z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ 143 | w = rol(w, 30); 144 | 145 | /* Hash a single 512-bit block. This is the core of the algorithm. */ 146 | static void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64]) { 147 | uint32_t a, b, c, d, e; 148 | 149 | typedef union { 150 | uint8_t c[64]; 151 | uint32_t l[16]; 152 | 153 | } CHAR64LONG16; 154 | CHAR64LONG16 block; 155 | 156 | memcpy(&block, buffer, 64); 157 | 158 | /* Copy context->state[] to working vars */ 159 | a = state[0]; 160 | b = state[1]; 161 | c = state[2]; 162 | d = state[3]; 163 | e = state[4]; 164 | 165 | /* 4 rounds of 20 operations each. Loop unrolled. */ 166 | R0(a, b, c, d, e, 0); 167 | R0(e, a, b, c, d, 1); 168 | R0(d, e, a, b, c, 2); 169 | R0(c, d, e, a, b, 3); 170 | R0(b, c, d, e, a, 4); 171 | R0(a, b, c, d, e, 5); 172 | R0(e, a, b, c, d, 6); 173 | R0(d, e, a, b, c, 7); 174 | R0(c, d, e, a, b, 8); 175 | R0(b, c, d, e, a, 9); 176 | R0(a, b, c, d, e, 10); 177 | R0(e, a, b, c, d, 11); 178 | R0(d, e, a, b, c, 12); 179 | R0(c, d, e, a, b, 13); 180 | R0(b, c, d, e, a, 14); 181 | R0(a, b, c, d, e, 15); 182 | R1(e, a, b, c, d, 16); 183 | R1(d, e, a, b, c, 17); 184 | R1(c, d, e, a, b, 18); 185 | R1(b, c, d, e, a, 19); 186 | R2(a, b, c, d, e, 20); 187 | R2(e, a, b, c, d, 21); 188 | R2(d, e, a, b, c, 22); 189 | R2(c, d, e, a, b, 23); 190 | R2(b, c, d, e, a, 24); 191 | R2(a, b, c, d, e, 25); 192 | R2(e, a, b, c, d, 26); 193 | R2(d, e, a, b, c, 27); 194 | R2(c, d, e, a, b, 28); 195 | R2(b, c, d, e, a, 29); 196 | R2(a, b, c, d, e, 30); 197 | R2(e, a, b, c, d, 31); 198 | R2(d, e, a, b, c, 32); 199 | R2(c, d, e, a, b, 33); 200 | R2(b, c, d, e, a, 34); 201 | R2(a, b, c, d, e, 35); 202 | R2(e, a, b, c, d, 36); 203 | R2(d, e, a, b, c, 37); 204 | R2(c, d, e, a, b, 38); 205 | R2(b, c, d, e, a, 39); 206 | R3(a, b, c, d, e, 40); 207 | R3(e, a, b, c, d, 41); 208 | R3(d, e, a, b, c, 42); 209 | R3(c, d, e, a, b, 43); 210 | R3(b, c, d, e, a, 44); 211 | R3(a, b, c, d, e, 45); 212 | R3(e, a, b, c, d, 46); 213 | R3(d, e, a, b, c, 47); 214 | R3(c, d, e, a, b, 48); 215 | R3(b, c, d, e, a, 49); 216 | R3(a, b, c, d, e, 50); 217 | R3(e, a, b, c, d, 51); 218 | R3(d, e, a, b, c, 52); 219 | R3(c, d, e, a, b, 53); 220 | R3(b, c, d, e, a, 54); 221 | R3(a, b, c, d, e, 55); 222 | R3(e, a, b, c, d, 56); 223 | R3(d, e, a, b, c, 57); 224 | R3(c, d, e, a, b, 58); 225 | R3(b, c, d, e, a, 59); 226 | R4(a, b, c, d, e, 60); 227 | R4(e, a, b, c, d, 61); 228 | R4(d, e, a, b, c, 62); 229 | R4(c, d, e, a, b, 63); 230 | R4(b, c, d, e, a, 64); 231 | R4(a, b, c, d, e, 65); 232 | R4(e, a, b, c, d, 66); 233 | R4(d, e, a, b, c, 67); 234 | R4(c, d, e, a, b, 68); 235 | R4(b, c, d, e, a, 69); 236 | R4(a, b, c, d, e, 70); 237 | R4(e, a, b, c, d, 71); 238 | R4(d, e, a, b, c, 72); 239 | R4(c, d, e, a, b, 73); 240 | R4(b, c, d, e, a, 74); 241 | R4(a, b, c, d, e, 75); 242 | R4(e, a, b, c, d, 76); 243 | R4(d, e, a, b, c, 77); 244 | R4(c, d, e, a, b, 78); 245 | R4(b, c, d, e, a, 79); 246 | 247 | /* Add the working vars back into context.state[] */ 248 | state[0] += a; 249 | state[1] += b; 250 | state[2] += c; 251 | state[3] += d; 252 | state[4] += e; 253 | /* Wipe variables */ 254 | a = b = c = d = e = 0; 255 | } 256 | 257 | /* SHA1Init - Initialize new context */ 258 | void Sha1::Init(Context* context) { 259 | /* SHA1 initialization constants */ 260 | context->state[0] = 0x67452301; 261 | context->state[1] = 0xEFCDAB89; 262 | context->state[2] = 0x98BADCFE; 263 | context->state[3] = 0x10325476; 264 | context->state[4] = 0xC3D2E1F0; 265 | context->count[0] = context->count[1] = 0; 266 | } 267 | 268 | /* Run your data through this. */ 269 | void Sha1::Update(Context* context, const uint8_t* data, const size_t len_) { 270 | unsigned int i, j; 271 | 272 | uint32_t len = (uint32_t)len_; 273 | j = (context->count[0] >> 3) & 63; 274 | if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; 275 | context->count[1] += (len >> 29); 276 | if ((j + len) > 63) { 277 | memcpy(&context->buffer[j], data, (i = 64 - j)); 278 | SHA1_Transform(context->state, context->buffer); 279 | for (; i + 63 < len; i += 64) { 280 | SHA1_Transform(context->state, &data[i]); 281 | } 282 | j = 0; 283 | } else 284 | i = 0; 285 | memcpy(&context->buffer[j], &data[i], len - i); 286 | } 287 | 288 | /* Add padding and return the message digest. */ 289 | void Sha1::Final(Context* context, uint8_t digest[kDigestSize]) { 290 | uint32_t i; 291 | uint8_t finalcount[8]; 292 | 293 | for (i = 0; i < 8; i++) { 294 | finalcount[i] = (uint8_t)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ 295 | } 296 | Sha1::Update(context, (uint8_t*)"\200", 1); 297 | while ((context->count[0] & 504) != 448) { 298 | Sha1::Update(context, (uint8_t*)"\0", 1); 299 | } 300 | /* Should cause a SHA1_Transform() */ 301 | Sha1::Update(context, finalcount, 8); 302 | for (i = 0; i < kDigestSize; i++) { 303 | digest[i] = (uint8_t)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); 304 | } 305 | /* Wipe variables */ 306 | i = 0; 307 | memset(context->buffer, 0, 64); 308 | memset(context->state, 0, kDigestSize); 309 | memset(context->count, 0, 8); 310 | memset(&finalcount, 0, 8); 311 | } 312 | 313 | HashSha1 Sha1(const String& contents) { 314 | HashSha1 result = {}; 315 | Sha1::Context ctx; 316 | 317 | Sha1::Init(&ctx); 318 | Sha1::Update(&ctx, (uint8_t*)contents.data, contents.size); 319 | Sha1::Final(&ctx, result.hash); 320 | 321 | return result; 322 | } 323 | 324 | } // namespace polymer 325 | --------------------------------------------------------------------------------