├── .clang-format ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── README.md ├── external └── CMakeLists.txt ├── imgui.ini ├── include └── voxel-engine │ ├── camera.hpp │ ├── common.hpp │ ├── filesystem.hpp │ ├── pch.hpp │ ├── renderer.hpp │ ├── shader_compiler.hpp │ ├── timer.hpp │ ├── types.hpp │ ├── voxel.hpp │ └── window.hpp ├── setup_dxc_and_agility_sdk.bat ├── shaders ├── gpu_culling_shader.hlsl ├── interop │ └── render_resources.hlsli ├── triangle_shader.hlsl └── voxel_shader.hlsl └── src ├── CMakeLists.txt ├── camera.cpp ├── filesystem.cpp ├── main.cpp ├── renderer.cpp ├── shader_compiler.cpp ├── timer.cpp ├── voxel.cpp └── window.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Microsoft 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: C++ CMake 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | SOLUTION_FILE_PATH: . 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: windows-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Add MSBuild to PATH 21 | uses: microsoft/setup-msbuild@v1.0.2 22 | 23 | - name: Cmake Configure 24 | run: cmake -B ${{github.workspace}}/build 25 | 26 | - name: Build-Debug 27 | run: cmake --build ${{github.workspace}}/build --config debug 28 | 29 | - name: Build-Release 30 | run: cmake --build ${{github.workspace}}/build --config release 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | external/DirectXAgilitySDK 2 | external/DirectXShaderCompiler 3 | agility.zip 4 | dxc.zip 5 | build/ 6 | .vs/ 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | 3 | set(CMAKE_CXX_STANDARD 20) 4 | 5 | project(voxel-engine 6 | VERSION 0.1 7 | DESCRIPTION "A voxel engine made for learning purposes" 8 | LANGUAGES CXX) 9 | 10 | 11 | # For simplicity, all the outputs will lie in the same path (build//bin) 12 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/debug/bin) 13 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/debug/bin) 14 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/debug/bin) 15 | 16 | 17 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/release/bin) 18 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/release/bin) 19 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/release/bin) 20 | 21 | add_subdirectory(external) 22 | add_subdirectory(src) 23 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\build", 9 | "installRoot": "${projectDir}\\out\\install\\${name}", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "", 12 | "ctestCommandArgs": "" 13 | }, 14 | { 15 | "name": "release", 16 | "generator": "Ninja", 17 | "configurationType": "Release", 18 | "inheritEnvironments": [ "msvc_x64_x64" ], 19 | "buildRoot": "${projectDir}\\build", 20 | "installRoot": "${projectDir}\\out\\install\\${name}", 21 | "cmakeCommandArgs": "", 22 | "buildCommandArgs": "", 23 | "ctestCommandArgs": "" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # voxel-engine 2 | A simple voxel engine made for learning about voxels and advanced GPU / CPU optimization techniques. 3 | Uses C++, HLSL and D3D12. 4 | 5 | # Features 6 | * Bindless rendering (SM 6.6) 7 | * Reverse Z 8 | * Multi-threaded, async copy queue chunk loading system 9 | * Indirect rendering 10 | * GPU Culling 11 | 12 | # Gallery 13 | [Link : Click here, or on the Image below!](https://youtu.be/E0T0UMnOggg) 14 | 15 | [![Youtube link](https://img.youtube.com/vi/E0T0UMnOggg/hqdefault.jpg)](https://youtu.be/E0T0UMnOggg) 16 | 17 | # Building 18 | + This project uses CMake as a build system, and all third party libs are setup using CMake's FetchContent(). 19 | + There is a custom script that can be used to setup DirectX Shader Compiler (DXC) and the Agility SDK. 20 | + After cloning the project, build the project using CMake : 21 | ``` 22 | 1. Run the setup_dxc_and_agility_sdk.bat file, 23 | 24 | 2. 25 | cmake -S . -B build 26 | cmake --build build --config release 27 | ``` 28 | 29 | # Controls 30 | + WASD -> Move camera. 31 | + Arrow keys -> Modify camera orientation. 32 | + Escape -> Exit editor. -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare( 4 | imgui 5 | GIT_REPOSITORY https://github.com/ocornut/imgui 6 | GIT_TAG v1.90.9 7 | GIT_PROGRESS TRUE 8 | ) 9 | 10 | FetchContent_Declare( 11 | thread-pool 12 | GIT_REPOSITORY https://github.com/bshoshany/thread-pool 13 | GIT_PROGRESS TRUE 14 | ) 15 | 16 | FetchContent_MakeAvailable(imgui thread-pool) 17 | 18 | add_library(libimgui 19 | ${imgui_SOURCE_DIR}/imgui.cpp 20 | ${imgui_SOURCE_DIR}/imgui_demo.cpp 21 | ${imgui_SOURCE_DIR}/imgui_draw.cpp 22 | ${imgui_SOURCE_DIR}/imgui_widgets.cpp 23 | ${imgui_SOURCE_DIR}/imgui_tables.cpp 24 | ${imgui_SOURCE_DIR}/backends/imgui_impl_win32.cpp 25 | ${imgui_SOURCE_DIR}/backends/imgui_impl_dx12.cpp 26 | ) 27 | 28 | target_include_directories(libimgui PUBLIC 29 | ${imgui_SOURCE_DIR} 30 | ${imgui_SOURCE_DIR}/backends 31 | ) 32 | 33 | target_link_libraries(libimgui PUBLIC d3d12 dxguid dxgi) 34 | 35 | add_library(external INTERFACE) 36 | target_link_libraries(external INTERFACE libimgui) 37 | target_include_directories(external INTERFACE ${thread-pool_SOURCE_DIR}) -------------------------------------------------------------------------------- /imgui.ini: -------------------------------------------------------------------------------- 1 | [Window][Debug##Default] 2 | Pos=60,60 3 | Size=400,400 4 | 5 | [Window][Debug Controller] 6 | Pos=964,203 7 | Size=409,265 8 | 9 | [Window][Dear ImGui Metrics/Debugger] 10 | Pos=1063,473 11 | Size=339,341 12 | 13 | -------------------------------------------------------------------------------- /include/voxel-engine/camera.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Camera 4 | { 5 | public: 6 | DirectX::XMMATRIX update_and_get_view_matrix(const float delta_time); 7 | 8 | public: 9 | DirectX::XMFLOAT4 m_position{0.0f, 0.0f, -5.0f, 1.0f}; 10 | 11 | // The up vector can be calculated using right and front vector. 12 | DirectX::XMFLOAT4 m_right{1.0f, 0.0f, 0.0f, 0.0f}; 13 | DirectX::XMFLOAT4 m_front{0.0f, 0.0f, 1.0f, 0.0f}; 14 | 15 | float m_movement_speed{500.0f}; 16 | float m_rotation_speed{1.0f}; 17 | 18 | // Used to determine how 'smooth' the camera behaves. 19 | // For now, both rotation and movement use the same friction value, purely for simplicity. 20 | float m_friction{0.30f}; 21 | 22 | float m_pitch{}; 23 | float m_yaw{}; 24 | }; -------------------------------------------------------------------------------- /include/voxel-engine/common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.hpp" 4 | 5 | // Helper function to print to console in debug mode if the passed Hresult has failed. 6 | static inline void throw_if_failed(const HRESULT hr, 7 | const std::source_location src_loc = std::source_location::current()) 8 | { 9 | if constexpr (VX_DEBUG_MODE) 10 | { 11 | if (FAILED(hr)) 12 | { 13 | printf("Hresult Failed!\nFile name :: %s\nLine number :: %u\nColumn number :: %u\nFunction name :: %s\n", 14 | src_loc.file_name(), src_loc.line(), src_loc.column(), src_loc.function_name()); 15 | } 16 | } 17 | } 18 | 19 | // Helper functions to go from 1d to 3d and vice versa. 20 | static inline size_t convert_to_1d(const DirectX::XMUINT3 index_3d, const size_t N) 21 | { 22 | return index_3d.x + N * (index_3d.y + (index_3d.z * N)); 23 | } 24 | 25 | static inline DirectX::XMUINT3 convert_to_3d(const size_t index, const size_t N) 26 | { 27 | // For reference, index = x + y * N + z * N * N. 28 | const u32 z = static_cast(index / (N * N)); 29 | const u32 index_2d = static_cast(index - z * N * N); 30 | const u32 y = static_cast(index_2d / N); 31 | const u32 x = static_cast(index_2d % N); 32 | 33 | return {x, y, z}; 34 | } 35 | 36 | static inline void name_d3d12_object(ID3D12Object *const object, const std::wstring_view name) 37 | { 38 | if constexpr (VX_DEBUG_MODE) 39 | { 40 | object->SetName(std::wstring(name).c_str()); 41 | } 42 | } 43 | 44 | static inline size_t round_up_to_multiple(size_t a, size_t multiple) 45 | { 46 | if (a % multiple == 0) 47 | { 48 | return a; 49 | } 50 | else 51 | { 52 | return a + multiple - (a % multiple); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /include/voxel-engine/filesystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // A simple class that can get the relative path of a file / folder with respect to the executable. 4 | // NOTE : Makes the assumption that between the executable and the project root directory, there is NO OTHER folder with 5 | // the name "voxel-engine". 6 | class FileSystem 7 | { 8 | public: 9 | static FileSystem &instance() 10 | { 11 | static FileSystem fs{}; 12 | return fs; 13 | } 14 | 15 | inline std::string get_relative_path(const std::string_view path) const 16 | { 17 | return m_root_directory + std::string(path); 18 | } 19 | 20 | inline std::wstring get_relative_path_wstr(const std::wstring_view path) const 21 | { 22 | return std::filesystem::path(m_root_directory).wstring() + std::wstring(path); 23 | } 24 | 25 | inline std::string executable_path() const 26 | { 27 | return std::filesystem::current_path().string(); 28 | } 29 | 30 | private: 31 | explicit FileSystem(); 32 | 33 | private: 34 | std::string m_root_directory{}; 35 | }; -------------------------------------------------------------------------------- /include/voxel-engine/pch.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _DEBUG 4 | constexpr bool VX_DEBUG_MODE = true; 5 | #else 6 | constexpr bool VX_DEBUG_MODE = false; 7 | #endif 8 | 9 | // Windows includes. 10 | #define WIN32_LEAN_AND_MEAN 11 | #include 12 | 13 | // Dx12 / Com headers. 14 | #include 15 | #include 16 | #include 17 | 18 | // Shader compiler related headers. 19 | #include 20 | #include 21 | 22 | // Simd - math library. 23 | #include 24 | 25 | // stdlib headers. 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | // Custom includes. 46 | #include "common.hpp" 47 | #include "types.hpp" -------------------------------------------------------------------------------- /include/voxel-engine/renderer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct StructuredBuffer 4 | { 5 | Microsoft::WRL::ComPtr resource{}; 6 | size_t srv_index{}; 7 | }; 8 | 9 | struct ConstantBuffer 10 | { 11 | Microsoft::WRL::ComPtr resource{}; 12 | size_t cbv_index{}; 13 | size_t size_in_bytes{}; 14 | 15 | u8 *resource_mapped_ptr{}; 16 | 17 | inline void update(const void *data) const 18 | { 19 | memcpy(resource_mapped_ptr, data, size_in_bytes); 20 | } 21 | }; 22 | 23 | struct IndexBuffer 24 | { 25 | Microsoft::WRL::ComPtr resource{}; 26 | size_t indices_count{}; 27 | D3D12_INDEX_BUFFER_VIEW index_buffer_view{}; 28 | }; 29 | 30 | // The command buffer is a bit different. It internally has two resources, a default and upload heap. 31 | // the update function is not similar to constant buffer, as here data is copied from the upload to default resource. 32 | // The command buffer contains its ID3D12Resource directly since the same command buffer is used for the entire engine. 33 | struct CommandBuffer 34 | { 35 | Microsoft::WRL::ComPtr default_resource{}; 36 | Microsoft::WRL::ComPtr upload_resource{}; 37 | Microsoft::WRL::ComPtr zeroed_counter_buffer_resource{}; 38 | 39 | u8 *upload_resource_mapped_ptr{}; 40 | size_t upload_resource_srv_index{}; 41 | size_t default_resource_uav_index{}; 42 | size_t counter_offset{}; 43 | }; 44 | 45 | // A simple & straight forward high level renderer abstraction. 46 | struct Renderer 47 | { 48 | // Nested struct definitions. 49 | private: 50 | // A simple descriptor heap abstraction. 51 | // Provides simple methods to offset current descriptor to make creation of resources easier. 52 | struct DescriptorHeap 53 | { 54 | Microsoft::WRL::ComPtr descriptor_heap{}; 55 | 56 | D3D12_CPU_DESCRIPTOR_HANDLE current_cpu_descriptor_handle{}; 57 | D3D12_GPU_DESCRIPTOR_HANDLE current_gpu_descriptor_handle{}; 58 | 59 | size_t current_descriptor_handle_index{}; 60 | 61 | size_t descriptor_handle_size{}; 62 | 63 | D3D12_GPU_DESCRIPTOR_HANDLE get_gpu_descriptor_handle_at_index(const size_t index) const; 64 | D3D12_CPU_DESCRIPTOR_HANDLE get_cpu_descriptor_handle_at_index(const size_t index) const; 65 | 66 | void offset_current_descriptor_handles(); 67 | 68 | void create(ID3D12Device *const device, const size_t num_descriptors, 69 | const D3D12_DESCRIPTOR_HEAP_TYPE descriptor_heap_type, 70 | const D3D12_DESCRIPTOR_HEAP_FLAGS descriptor_heap_flags); 71 | }; 72 | 73 | public: 74 | explicit Renderer(const HWND window_handle, const u16 window_width, const u16 window_height); 75 | 76 | // Resource creation functions. 77 | // The functions return a buffer and intermediate resource (which can be discarded once the CopyResource operation 78 | // is complete). 79 | struct IndexBufferWithIntermediateResource 80 | { 81 | IndexBuffer index_buffer; 82 | Microsoft::WRL::ComPtr intermediate_resource; 83 | }; 84 | 85 | IndexBufferWithIntermediateResource create_index_buffer(const void *data, const size_t stride, 86 | const size_t indices_count, 87 | const std::wstring_view buffer_name); 88 | 89 | struct StucturedBufferWithIntermediateResource 90 | { 91 | StructuredBuffer structured_buffer; 92 | Microsoft::WRL::ComPtr intermediate_resource; 93 | }; 94 | 95 | StucturedBufferWithIntermediateResource create_structured_buffer(const void *data, const size_t stride, 96 | const size_t num_elements, 97 | const std::wstring_view buffer_name); 98 | 99 | CommandBuffer create_command_buffer(const size_t stride, const size_t max_number_of_elements, 100 | const std::wstring_view buffer_name); 101 | 102 | ConstantBuffer internal_create_constant_buffer(const size_t size_in_bytes, const std::wstring_view buffer_name); 103 | 104 | template 105 | std::array create_constant_buffer(const size_t size_in_bytes, 106 | const std::wstring_view buffer_name); 107 | 108 | private: 109 | // This function automatically offset's the current descriptor handle of descriptor heap. 110 | size_t create_constant_buffer_view(ID3D12Resource *const resource, size_t size); 111 | size_t create_shader_resource_view(ID3D12Resource *const resource, const size_t stride, const size_t num_elements); 112 | size_t create_unordered_access_view(ID3D12Resource *const resource, const size_t stride, const size_t num_elements, 113 | const bool use_counter = false, 114 | const size_t counter_offset = D3D12_UAV_COUNTER_PLACEMENT_ALIGNMENT); 115 | 116 | public: 117 | // Static globals. 118 | static inline constexpr u8 NUMBER_OF_BACKBUFFERS = 3u; 119 | static inline constexpr DXGI_FORMAT BACKBUFFER_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM; 120 | 121 | static inline constexpr u8 COPY_QUEUE_RING_BUFFER_SIZE = 10u; 122 | 123 | public: 124 | // Core D3D12 and DXGI objects. 125 | Microsoft::WRL::ComPtr m_debug_device{}; 126 | Microsoft::WRL::ComPtr m_dxgi_factory{}; 127 | Microsoft::WRL::ComPtr m_dxgi_adapter{}; 128 | 129 | Microsoft::WRL::ComPtr m_device{}; 130 | 131 | Microsoft::WRL::ComPtr m_swapchain{}; 132 | 133 | std::array m_swapchain_backbuffer_cpu_descriptor_handles{}; 134 | std::array, NUMBER_OF_BACKBUFFERS> m_swapchain_backbuffer_resources{}; 135 | 136 | DescriptorHeap m_cbv_srv_uav_descriptor_heap{}; 137 | DescriptorHeap m_rtv_descriptor_heap{}; 138 | DescriptorHeap m_dsv_descriptor_heap{}; 139 | 140 | u8 m_swapchain_backbuffer_index{}; 141 | 142 | // Bindless root signature, that is shared by all pipelines. 143 | Microsoft::WRL::ComPtr m_bindless_root_signature{}; 144 | 145 | // Mutex used for resource creation. 146 | std::mutex m_resource_mutex{}; 147 | 148 | // Command queue abstraction that holds the queue, allocators, command list and sync primitives. 149 | // Each queue type has its own struct since they operate in different ways (copy queue is async and requires thread 150 | // sync primitives, while the direct queue is not for now). 151 | struct DirectCommandQueue 152 | { 153 | std::array, NUMBER_OF_BACKBUFFERS> m_command_allocators{}; 154 | Microsoft::WRL::ComPtr m_command_queue{}; 155 | Microsoft::WRL::ComPtr m_command_list{}; 156 | 157 | Microsoft::WRL::ComPtr m_fence{}; 158 | u64 m_monotonic_fence_value{}; 159 | std::array m_frame_fence_values{}; 160 | 161 | void create(ID3D12Device *const device); 162 | 163 | void reset(const u8 index) const; 164 | void execute_command_list() const; 165 | void wait_for_fence_value_at_index(const u8 index); 166 | void signal_fence(const u8 index); 167 | 168 | void flush_queue(); 169 | }; 170 | 171 | struct CopyCommandQueue 172 | { 173 | struct CommandAllocatorListPair 174 | { 175 | Microsoft::WRL::ComPtr m_command_allocator{}; 176 | Microsoft::WRL::ComPtr m_command_list{}; 177 | u64 m_fence_value{}; 178 | }; 179 | 180 | std::queue m_command_allocator_list_queue{}; 181 | Microsoft::WRL::ComPtr m_command_queue{}; 182 | 183 | Microsoft::WRL::ComPtr m_fence{}; 184 | u64 m_monotonic_fence_value{}; 185 | 186 | void create(ID3D12Device *const device); 187 | 188 | // If there is a allocator / list pair that has completed execution, return it. Else, create a new one. 189 | CommandAllocatorListPair get_command_allocator_list_pair(ID3D12Device *const device); 190 | 191 | // Execute command list and move the allocator list pair back to the queue. 192 | void execute_command_list(CommandAllocatorListPair &&alloc_list_pair); 193 | 194 | void flush_queue(); 195 | }; 196 | 197 | DirectCommandQueue m_direct_queue{}; 198 | CopyCommandQueue m_copy_queue{}; 199 | }; 200 | 201 | template 202 | inline std::array Renderer::create_constant_buffer(const size_t size_in_bytes, 203 | const std::wstring_view buffer_name) 204 | { 205 | std::array constant_buffers{}; 206 | for (size_t i = 0; i < T; i++) 207 | { 208 | constant_buffers[i] = 209 | internal_create_constant_buffer(size_in_bytes, std::wstring(buffer_name) + std::to_wstring(i)); 210 | } 211 | 212 | return constant_buffers; 213 | } 214 | -------------------------------------------------------------------------------- /include/voxel-engine/shader_compiler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // The naming convention of namespaces is being broken here, mostly because this 4 | // "namespace" is a smart way of simulating static class behaviour. 5 | // DXC is used for shader compilation. 6 | namespace ShaderCompiler 7 | { 8 | // note(rtarun9) : Use std::wstring instead? 9 | IDxcBlob *compile(const wchar_t *const file_path, const wchar_t *const entry_point, const wchar_t *const target); 10 | } // namespace ShaderCompiler -------------------------------------------------------------------------------- /include/voxel-engine/timer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Timer 4 | { 5 | public: 6 | explicit Timer(); 7 | 8 | void start(); 9 | void stop(); 10 | 11 | float get_delta_time() const; 12 | 13 | private: 14 | LARGE_INTEGER m_performance_frequency{}; 15 | float m_seconds_per_count{}; 16 | 17 | LARGE_INTEGER m_start_time{}; 18 | LARGE_INTEGER m_end_time{}; 19 | }; 20 | -------------------------------------------------------------------------------- /include/voxel-engine/types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // A collection of typedefs used throughout the project. 4 | 5 | using u8 = uint8_t; 6 | using u16 = uint16_t; 7 | using u32 = uint32_t; 8 | using u64 = uint64_t; 9 | 10 | using i8 = int8_t; 11 | using i16 = int16_t; 12 | using i32 = int32_t; 13 | using i64 = int64_t; 14 | -------------------------------------------------------------------------------- /include/voxel-engine/voxel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "include/BS_thread_pool.hpp" 4 | #include "voxel-engine/renderer.hpp" 5 | 6 | // A voxel is just a value on a regular 3D grid. Think of it as the corners where the cells meet in a 3d grid. 7 | // For 3d visualization of voxels, A cube is rendered for each voxel where the front lower left corner is the 'voxel 8 | // position' and has a edge length as specified in the class below. 9 | struct Voxel 10 | { 11 | static constexpr u32 EDGE_LENGTH{640 * 8u}; 12 | bool m_active{true}; 13 | }; 14 | 15 | struct Chunk 16 | { 17 | explicit Chunk(); 18 | 19 | Chunk(const Chunk &other) = delete; 20 | Chunk &operator=(Chunk &other) = delete; 21 | 22 | Chunk(Chunk &&other) noexcept; 23 | Chunk &operator=(Chunk &&other) noexcept; 24 | 25 | ~Chunk(); 26 | 27 | static constexpr u32 NUMBER_OF_VOXELS_PER_DIMENSION = 8u; 28 | static constexpr size_t NUMBER_OF_VOXELS = 29 | NUMBER_OF_VOXELS_PER_DIMENSION * NUMBER_OF_VOXELS_PER_DIMENSION * NUMBER_OF_VOXELS_PER_DIMENSION; 30 | 31 | static constexpr u32 CHUNK_LENGTH = Voxel::EDGE_LENGTH * Chunk::NUMBER_OF_VOXELS_PER_DIMENSION; 32 | 33 | // A flattened 3d array of Voxels. 34 | Voxel *m_voxels{}; 35 | size_t m_chunk_index{}; 36 | }; 37 | 38 | // A class that contains a collection of chunks and associated data. 39 | // The states a chunk can be in: 40 | // (i) Loaded -> Ready to be rendered. 41 | // (ii) Setup -> Chunk mesh is ready, but associated buffers may or maynot be ready. Once the buffers are ready, these 42 | // chunks are moved into the loaded chunks hashmap. 43 | struct ChunkManager 44 | { 45 | // Constructor creates the shared position buffer. 46 | explicit ChunkManager(Renderer &renderer); 47 | 48 | struct SetupChunkData 49 | { 50 | Chunk m_chunk{}; 51 | 52 | Renderer::IndexBufferWithIntermediateResource m_chunk_index_buffer{}; 53 | Renderer::StucturedBufferWithIntermediateResource m_chunk_color_buffer{}; 54 | 55 | // A strange design decision, but rather than accessing the render resources via root constants, render 56 | // resources will now be embedded into the chunk constant buffer. 57 | // This is done to make the indirect rendering & GPU culling process simpler. 58 | ConstantBuffer m_chunk_constant_buffer{}; 59 | 60 | std::vector m_chunk_indices_data{}; 61 | std::vector m_chunk_color_data{}; 62 | }; 63 | 64 | private: 65 | // internal_mt : Internal multithreaded. 66 | SetupChunkData internal_mt_setup_chunk(Renderer &renderer, const size_t index); 67 | 68 | public: 69 | void add_chunk_to_setup_stack(const size_t chunk_index); 70 | void create_chunks_from_setup_stack(Renderer &renderer); 71 | 72 | void transfer_chunks_from_setup_to_loaded_state(const u64 current_copy_queue_fence_value); 73 | 74 | static constexpr u32 NUMBER_OF_CHUNKS_PER_DIMENSION = 2048u; 75 | static constexpr size_t NUMBER_OF_CHUNKS = 76 | NUMBER_OF_CHUNKS_PER_DIMENSION * NUMBER_OF_CHUNKS_PER_DIMENSION * NUMBER_OF_CHUNKS_PER_DIMENSION; 77 | 78 | // Determines how many chunks are loaded around the player. 79 | static constexpr u32 CHUNKS_LOADED_AROUND_PLAYER = 6u; 80 | 81 | // Determines how many chunks are deleted per frame. 82 | static constexpr u32 CHUNKS_TO_UNLOAD_PER_FRAME = 64u * 4u; 83 | 84 | // Determine how many chunks can be loaded at once. If a chunk is to be loaded and loaded chunks is already at the 85 | // limit, re-use of memory happens. 86 | static constexpr u32 CHUNK_RENDER_DISTANCE = CHUNKS_LOADED_AROUND_PLAYER; 87 | 88 | // Chunks to create per frame : How many chunks are setup (i.e the meshing processes occurs). 89 | static constexpr u32 NUMBER_OF_CHUNKS_TO_CREATE_PER_FRAME = 16u; 90 | 91 | // Chunks to load per frame : How many setup chunks are moved into the loaded chunk hash map. 92 | static constexpr u32 NUMBER_OF_CHUNKS_TO_LOAD_PER_FRAME = 64u; 93 | 94 | std::unordered_map m_loaded_chunks{}; 95 | 96 | // NOTE : Chunks are considered to be setup when : 97 | // (i) The result of async call (i.e the future) is ready, 98 | // (ii) The fence value is < the current copy queue fence value. 99 | // The setup chunks future stack consist of pairs of fence values , futures. 100 | std::queue>> m_setup_chunk_futures_queue{}; 101 | 102 | // Why is there also a stack? 103 | // Use the stack to store chunk indices that at any given point in time are close to the player. 104 | // Then, each from from this stack, add elements into the queue. 105 | std::stack m_chunks_to_setup_stack{}; 106 | 107 | // A unordered set to keep track of chunks that are currently in process of being setup. 108 | // This is required in case create_chunk is called for a chunk that is being setup but not loaded. We do not want to 109 | // load this chunk again. 110 | std::unordered_set m_chunk_indices_that_are_being_setup{}; 111 | 112 | std::unordered_map m_chunk_index_buffers{}; 113 | std::unordered_map m_chunk_color_buffers{}; 114 | std::unordered_map m_chunk_constant_buffers{}; 115 | 116 | // All chunks only have a index buffer with them. The indices 'index' into this common shared chunk constant buffer. 117 | // The data in this buffer is ordered vertex wise, voxel wise. 118 | StructuredBuffer m_shared_chunk_position_buffer{}; 119 | 120 | // Threadpool from which std::futures are obtained. 121 | BS::thread_pool m_thread_pool; 122 | }; 123 | -------------------------------------------------------------------------------- /include/voxel-engine/window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Simple abstraction class over win32 window. 4 | class Window 5 | { 6 | public: 7 | // For now window is only created in full screen mode. 8 | explicit Window(); 9 | virtual ~Window(); 10 | 11 | inline u16 get_width() const 12 | { 13 | return m_width; 14 | } 15 | 16 | inline u16 get_height() const 17 | { 18 | return m_height; 19 | } 20 | 21 | inline HWND get_handle() const 22 | { 23 | return m_handle; 24 | } 25 | 26 | private: 27 | static inline constexpr const char WINDOW_CLASS_NAME[] = "Base Window Class"; 28 | 29 | private: 30 | HWND m_handle{nullptr}; 31 | 32 | u16 m_width{}; 33 | u16 m_height{}; 34 | }; -------------------------------------------------------------------------------- /setup_dxc_and_agility_sdk.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | powershell -Command "Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Microsoft.Direct3D.D3D12/1.711.3-preview -OutFile agility.zip" 4 | powershell -Command "& {Expand-Archive agility.zip external/DirectXAgilitySDK}" 5 | 6 | xcopy external\DirectXAgilitySDK\build\native\bin\x64\* build\debug\bin\D3D12\ 7 | xcopy external\DirectXAgilitySDK\build\native\bin\x64\* build\release\bin\D3D12\ 8 | 9 | powershell -Command "Invoke-WebRequest -Uri https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.7.2212/dxc_2022_12_16.zip -OutFile dxc.zip" 10 | powershell -Command "& {Expand-Archive dxc.zip external/DirectXShaderCompiler}" 11 | 12 | xcopy External\DirectXShaderCompiler\bin\x64\* build\debug\bin\ 13 | xcopy External\DirectXShaderCompiler\bin\x64\* build\release\bin\ 14 | -------------------------------------------------------------------------------- /shaders/gpu_culling_shader.hlsl: -------------------------------------------------------------------------------- 1 | #include "interop/render_resources.hlsli" 2 | 3 | ConstantBuffer render_resources : register(b0); 4 | 5 | [numthreads(32, 1, 1)] void cs_main(uint dispatch_thread_id 6 | : SV_DispatchThreadID) { 7 | if (dispatch_thread_id < render_resources.number_of_chunks) 8 | { 9 | StructuredBuffer indirect_command = 10 | ResourceDescriptorHeap[render_resources.indirect_command_srv_index]; 11 | 12 | AppendStructuredBuffer output_commands = 13 | ResourceDescriptorHeap[render_resources.output_command_uav_index]; 14 | 15 | ConstantBuffer scene_constant_buffer = 16 | ResourceDescriptorHeap[render_resources.scene_constant_buffer_index]; 17 | 18 | ConstantBuffer chunk_constant_buffer = 19 | ResourceDescriptorHeap[indirect_command[dispatch_thread_id] 20 | .voxel_render_resources.chunk_constant_buffer_index]; 21 | 22 | // For each vertex, find the clip space coord and check if AABB vertex is culled. 23 | uint culled_vertices = 0; 24 | for (int i = 0; i < 8; i++) 25 | { 26 | float4 clip_space_coords = 27 | mul(mul(scene_constant_buffer.aabb_vertices[i] + chunk_constant_buffer.translation_vector - 28 | float4(scene_constant_buffer.camera_position.xyz, 0.0f), 29 | scene_constant_buffer.view_matrix), 30 | scene_constant_buffer.projection_matrix); 31 | 32 | clip_space_coords.x /= clip_space_coords.w; 33 | clip_space_coords.y /= clip_space_coords.w; 34 | clip_space_coords.z /= clip_space_coords.w; 35 | 36 | bool is_visible = 37 | (-clip_space_coords.w <= clip_space_coords.x) && (clip_space_coords.x <= clip_space_coords.w) && 38 | (-clip_space_coords.w <= clip_space_coords.y) && (clip_space_coords.y <= clip_space_coords.w) && 39 | (0 <= clip_space_coords.z) && (clip_space_coords.z <= clip_space_coords.w); 40 | 41 | if (!is_visible) 42 | { 43 | ++culled_vertices; 44 | } 45 | } 46 | 47 | if (culled_vertices < 7) 48 | { 49 | output_commands.Append(indirect_command[dispatch_thread_id]); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /shaders/interop/render_resources.hlsli: -------------------------------------------------------------------------------- 1 | #ifndef __RENDER_RESOURCES_HLSLI__ 2 | #define __RENDER_RESOURCES_HLSLI__ 3 | 4 | #ifdef __cplusplus 5 | 6 | #define float4x4 DirectX::XMMATRIX 7 | #define float4 DirectX::XMFLOAT4 8 | #define float3 DirectX::XMFLOAT3 9 | #define uint u32 10 | #define uint4 DirectX::XMUINT4 11 | #define ConstantBufferStruct struct alignas(256) 12 | 13 | #else 14 | 15 | #pragma pack_matrix(row_major) 16 | #define ConstantBufferStruct struct 17 | 18 | #endif 19 | 20 | // clang-format off 21 | 22 | struct TriangleRenderResources 23 | { 24 | uint position_buffer_index; 25 | uint color_buffer_index; 26 | }; 27 | 28 | struct VoxelRenderResources 29 | { 30 | uint scene_constant_buffer_index; 31 | uint chunk_constant_buffer_index; 32 | }; 33 | 34 | ConstantBufferStruct 35 | SceneConstantBuffer 36 | { 37 | float4x4 view_matrix; 38 | float4x4 projection_matrix; 39 | 40 | // note(rtarun9) : Putting this here because scene depends on chunk edge length, which determines the AABB vertices. 41 | float4 aabb_vertices[8]; 42 | float4 camera_position; 43 | }; 44 | 45 | ConstantBufferStruct 46 | ChunkConstantBuffer 47 | { 48 | uint4 translation_vector; 49 | 50 | uint position_buffer_index; 51 | 52 | uint color_buffer_index; 53 | }; 54 | 55 | // D3D12_DRAW_INDEXED_ARGUMENTS has 5 32 bit members, which is why draw arguments is split into a uint4 and uint. 56 | struct GPUIndirectCommand 57 | { 58 | VoxelRenderResources voxel_render_resources; 59 | uint4 index_buffer_view; 60 | uint4 draw_arguments_1; 61 | uint draw_arguments_2; 62 | uint padding; 63 | }; 64 | 65 | struct GPUCullRenderResources 66 | { 67 | uint number_of_chunks; 68 | uint indirect_command_srv_index; 69 | uint output_command_uav_index; 70 | uint scene_constant_buffer_index; 71 | }; 72 | 73 | #endif -------------------------------------------------------------------------------- /shaders/triangle_shader.hlsl: -------------------------------------------------------------------------------- 1 | #include "interop/render_resources.hlsli" 2 | 3 | struct VSOutput 4 | { 5 | float4 position : SV_Position; 6 | float4 color : VertexColor; 7 | }; 8 | 9 | ConstantBuffer render_resources : register(b0); 10 | 11 | VSOutput vs_main(uint vertex_id : SV_VertexID) 12 | { 13 | StructuredBuffer position_buffer = ResourceDescriptorHeap[render_resources.position_buffer_index]; 14 | StructuredBuffer color_buffer = ResourceDescriptorHeap[render_resources.color_buffer_index]; 15 | 16 | VSOutput output; 17 | output.position = float4(position_buffer[vertex_id], 1.0f); 18 | output.color = float4(color_buffer[vertex_id], 1.0f); 19 | 20 | return output; 21 | } 22 | 23 | float4 ps_main(VSOutput input) : SV_Target 24 | { 25 | return input.color; 26 | } -------------------------------------------------------------------------------- /shaders/voxel_shader.hlsl: -------------------------------------------------------------------------------- 1 | #include "interop/render_resources.hlsli" 2 | 3 | struct VSOutput 4 | { 5 | float4 position : SV_Position; 6 | }; 7 | 8 | ConstantBuffer render_resources : register(b0); 9 | 10 | VSOutput vs_main(uint vertex_id : SV_VertexID) 11 | { 12 | ConstantBuffer chunk_constant_buffer = 13 | ResourceDescriptorHeap[render_resources.chunk_constant_buffer_index]; 14 | 15 | ConstantBuffer scene_buffer = 16 | ResourceDescriptorHeap[render_resources.scene_constant_buffer_index]; 17 | 18 | StructuredBuffer position_buffer = ResourceDescriptorHeap[chunk_constant_buffer.position_buffer_index]; 19 | 20 | const float3 position = 21 | position_buffer[vertex_id] + -scene_buffer.camera_position.xyz + chunk_constant_buffer.translation_vector.xyz; 22 | 23 | VSOutput output; 24 | output.position = mul(mul(float4(position, 1.0f), scene_buffer.view_matrix), scene_buffer.projection_matrix); 25 | 26 | return output; 27 | } 28 | 29 | float4 ps_main(VSOutput input, uint primitive_id : SV_PrimitiveID) : SV_Target 30 | { 31 | ConstantBuffer chunk_constant_buffer = 32 | ResourceDescriptorHeap[render_resources.chunk_constant_buffer_index]; 33 | 34 | StructuredBuffer color_buffer = ResourceDescriptorHeap[chunk_constant_buffer.color_buffer_index]; 35 | return float4(color_buffer[primitive_id], 1.0f); 36 | } 37 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (SRC_FILES 2 | "main.cpp" 3 | "window.cpp" 4 | "timer.cpp" 5 | "filesystem.cpp" 6 | "camera.cpp" 7 | "renderer.cpp" 8 | "shader_compiler.cpp" 9 | "voxel.cpp" 10 | ) 11 | 12 | set (HEADER_FILES 13 | 14 | ${CMAKE_SOURCE_DIR}/include/voxel-engine/common.hpp 15 | ${CMAKE_SOURCE_DIR}/include/voxel-engine/timer.hpp 16 | ${CMAKE_SOURCE_DIR}/include/voxel-engine/types.hpp 17 | ${CMAKE_SOURCE_DIR}/include/voxel-engine/window.hpp 18 | ${CMAKE_SOURCE_DIR}/include/voxel-engine/renderer.hpp 19 | ${CMAKE_SOURCE_DIR}/include/voxel-engine/filesystem.hpp 20 | ${CMAKE_SOURCE_DIR}/include/voxel-engine/camera.hpp 21 | ${CMAKE_SOURCE_DIR}/include/voxel-engine/shader_compiler.hpp 22 | ${CMAKE_SOURCE_DIR}/include/voxel-engine/voxel.hpp 23 | ) 24 | 25 | add_executable(voxel-engine ${SRC_FILES} ${HEADER_FILES}) 26 | target_link_libraries(voxel-engine PUBLIC d3d12 dxgi dxguid dxcompiler external) 27 | 28 | set_property(TARGET voxel-engine PROPERTY COMPILE_WARNING_AS_ERROR ON) 29 | 30 | # Setup PCH. 31 | target_precompile_headers(voxel-engine PUBLIC ${CMAKE_SOURCE_DIR}/include/voxel-engine/pch.hpp) 32 | 33 | target_include_directories(voxel-engine PRIVATE ${CMAKE_SOURCE_DIR}/include) 34 | target_include_directories(voxel-engine PRIVATE ${CMAKE_SOURCE_DIR}/) -------------------------------------------------------------------------------- /src/camera.cpp: -------------------------------------------------------------------------------- 1 | #include "voxel-engine/camera.hpp" 2 | 3 | DirectX::XMMATRIX Camera::update_and_get_view_matrix(const float delta_time) 4 | { 5 | // For operator overloads. 6 | using namespace DirectX; 7 | 8 | const float movement_speed = m_movement_speed * delta_time; 9 | const float rotation_speed = m_rotation_speed * delta_time; 10 | 11 | // For making the camera 'smooth', the yaw / pitch / position vector values are not set based on the players input 12 | // at a particular instance. Instead, these values lerp to the new values. The static variables help persist that 13 | // data between multiple frames / instances. 14 | static DirectX::XMVECTOR move_to_position_vector = DirectX::XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f); 15 | static float pitch_to = 0.0f; 16 | static float yaw_to = 0.0f; 17 | 18 | // First load data into SIMD datatypes. 19 | DirectX::XMVECTOR position_vector = DirectX::XMLoadFloat4(&m_position); 20 | 21 | DirectX::XMVECTOR front_vector = DirectX::XMLoadFloat4(&m_front); 22 | DirectX::XMVECTOR right_vector = DirectX::XMLoadFloat4(&m_right); 23 | 24 | // Index is the virutal key code. 25 | // If higher order bit is 1 (0x8000), the key is down. 26 | if (GetKeyState((int)'A') & 0x8000) 27 | { 28 | move_to_position_vector -= right_vector * movement_speed; 29 | } 30 | 31 | if (GetKeyState((int)'D') & 0x8000) 32 | { 33 | move_to_position_vector += right_vector * movement_speed; 34 | } 35 | 36 | if (GetKeyState((int)'W') & 0x8000) 37 | { 38 | move_to_position_vector += front_vector * movement_speed; 39 | } 40 | 41 | if (GetKeyState((int)'S') & 0x8000) 42 | { 43 | move_to_position_vector -= front_vector * movement_speed; 44 | } 45 | 46 | if (GetKeyState(VK_UP) & 0x8000) 47 | { 48 | pitch_to -= rotation_speed; 49 | } 50 | else if (GetKeyState(VK_DOWN) & 0x8000) 51 | { 52 | pitch_to += rotation_speed; 53 | } 54 | 55 | if (GetKeyState(VK_LEFT) & 0x8000) 56 | { 57 | yaw_to -= rotation_speed; 58 | } 59 | else if (GetKeyState(VK_RIGHT) & 0x8000) 60 | { 61 | yaw_to += rotation_speed; 62 | } 63 | 64 | // The static variables are lerped (between current value and 0, with the 'lerp factor' being the friction member 65 | // variable). 66 | pitch_to = std::lerp(pitch_to, 0.0f, m_friction); 67 | yaw_to = std::lerp(yaw_to, 0.0f, m_friction); 68 | move_to_position_vector = 69 | DirectX::XMVectorLerp(move_to_position_vector, DirectX::XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f), m_friction); 70 | 71 | position_vector += move_to_position_vector * movement_speed; 72 | m_pitch += pitch_to; 73 | m_yaw += yaw_to; 74 | 75 | // Compute rotation matrix. 76 | static const DirectX::XMVECTOR world_up_vector = DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); 77 | static const DirectX::XMVECTOR world_right_vector = DirectX::XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f); 78 | static const DirectX::XMVECTOR world_front_vector = DirectX::XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); 79 | 80 | const DirectX::XMMATRIX rotation_matrix = DirectX::XMMatrixRotationRollPitchYaw(m_pitch, m_yaw, 0.0); 81 | 82 | right_vector = DirectX::XMVector3Normalize(DirectX::XMVector3Transform(world_right_vector, rotation_matrix)); 83 | front_vector = DirectX::XMVector3Normalize(DirectX::XMVector3Transform(world_front_vector, rotation_matrix)); 84 | 85 | DirectX::XMVECTOR up_vector = DirectX::XMVector3Normalize(DirectX::XMVector3Cross(front_vector, right_vector)); 86 | 87 | // Store results back to the member variables. 88 | DirectX::XMStoreFloat4(&m_right, right_vector); 89 | DirectX::XMStoreFloat4(&m_front, front_vector); 90 | 91 | DirectX::XMStoreFloat4(&m_position, position_vector); 92 | 93 | // The 'camera position' in the view matrix is a zero vector. In the shader, the vertex position subtracts the 94 | // position vector so that the camera is ALWAYS at the origin. 95 | // This should help a lot with precision. 96 | return DirectX::XMMatrixLookAtLH(DirectX::XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f), 97 | DirectX::XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f) + front_vector, up_vector); 98 | } 99 | -------------------------------------------------------------------------------- /src/filesystem.cpp: -------------------------------------------------------------------------------- 1 | #include "voxel-engine/filesystem.hpp" 2 | 3 | FileSystem::FileSystem() 4 | { 5 | // Start from the executable directory, and keep moving up until you find the project root directory. 6 | std::filesystem::path current_path = std::filesystem::current_path(); 7 | while (!std::filesystem::exists(current_path / "voxel-engine")) 8 | { 9 | if (current_path.has_parent_path()) 10 | { 11 | current_path = current_path.parent_path(); 12 | } 13 | else 14 | { 15 | printf("Failed to find root directory voxel-engine."); 16 | } 17 | } 18 | 19 | m_root_directory = (current_path / "voxel-engine/").string(); 20 | 21 | printf("Root directory :: %s\n", m_root_directory.c_str()); 22 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "voxel-engine/camera.hpp" 2 | #include "voxel-engine/filesystem.hpp" 3 | #include "voxel-engine/renderer.hpp" 4 | #include "voxel-engine/shader_compiler.hpp" 5 | #include "voxel-engine/timer.hpp" 6 | #include "voxel-engine/voxel.hpp" 7 | #include "voxel-engine/window.hpp" 8 | 9 | #include "shaders/interop/render_resources.hlsli" 10 | 11 | #include "imgui.h" 12 | #include "imgui_impl_dx12.h" 13 | #include "imgui_impl_win32.h" 14 | 15 | int main() 16 | { 17 | printf("%s\n", FileSystem::instance().executable_path().c_str()); 18 | 19 | const Window window{}; 20 | Renderer renderer(window.get_handle(), window.get_width(), window.get_height()); 21 | 22 | // Setup imgui. 23 | { 24 | IMGUI_CHECKVERSION(); 25 | ImGui::CreateContext(); 26 | 27 | ImGui::StyleColorsDark(); 28 | 29 | D3D12_CPU_DESCRIPTOR_HANDLE cpu_descriptor_handle = 30 | renderer.m_cbv_srv_uav_descriptor_heap.current_cpu_descriptor_handle; 31 | 32 | D3D12_GPU_DESCRIPTOR_HANDLE gpu_descriptor_handle = 33 | renderer.m_cbv_srv_uav_descriptor_heap.current_gpu_descriptor_handle; 34 | 35 | renderer.m_cbv_srv_uav_descriptor_heap.offset_current_descriptor_handles(); 36 | 37 | // Setup platform / renderer backend. 38 | ImGui_ImplWin32_Init(window.get_handle()); 39 | ImGui_ImplDX12_Init(renderer.m_device.Get(), Renderer::NUMBER_OF_BACKBUFFERS, Renderer::BACKBUFFER_FORMAT, 40 | renderer.m_cbv_srv_uav_descriptor_heap.descriptor_heap.Get(), cpu_descriptor_handle, 41 | gpu_descriptor_handle); 42 | } 43 | 44 | ChunkManager chunk_manager{renderer}; 45 | 46 | SceneConstantBuffer scene_buffer_data{}; 47 | 48 | // Setup the AABB data for scene buffer. 49 | 50 | // AABB for chunk. 51 | static constexpr std::array aabb_vertices{ 52 | DirectX::XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f), 53 | DirectX::XMFLOAT4(0.0f, Chunk::CHUNK_LENGTH, 0.0f, 1.0f), 54 | DirectX::XMFLOAT4(Chunk::CHUNK_LENGTH, Chunk::CHUNK_LENGTH, 0.0f, 1.0f), 55 | DirectX::XMFLOAT4(Chunk::CHUNK_LENGTH, 0.0f, 0.0f, 1.0f), 56 | DirectX::XMFLOAT4(0.0f, 0.0f, Chunk::CHUNK_LENGTH, 1.0f), 57 | DirectX::XMFLOAT4(0.0f, Chunk::CHUNK_LENGTH, Chunk::CHUNK_LENGTH, 1.0f), 58 | DirectX::XMFLOAT4(Chunk::CHUNK_LENGTH, Chunk::CHUNK_LENGTH, Chunk::CHUNK_LENGTH, 1.0f), 59 | DirectX::XMFLOAT4(Chunk::CHUNK_LENGTH, 0.0f, Chunk::CHUNK_LENGTH, 1.0f), 60 | }; 61 | 62 | for (int i = 0; i < 8; i++) 63 | { 64 | scene_buffer_data.aabb_vertices[i] = aabb_vertices[i]; 65 | } 66 | 67 | auto scene_buffers = renderer.create_constant_buffer(sizeof(SceneConstantBuffer), 68 | L"Scene constant buffer"); 69 | 70 | // Compile the vertex and pixel shader. 71 | Microsoft::WRL::ComPtr vertex_shader_blob = ShaderCompiler::compile( 72 | FileSystem::instance().get_relative_path_wstr(L"shaders/voxel_shader.hlsl").c_str(), L"vs_main", L"vs_6_6"); 73 | 74 | Microsoft::WRL::ComPtr pixel_shader_blob = ShaderCompiler::compile( 75 | FileSystem::instance().get_relative_path_wstr(L"shaders/voxel_shader.hlsl").c_str(), L"ps_main", L"ps_6_6"); 76 | 77 | // Setup depth buffer. 78 | Microsoft::WRL::ComPtr depth_buffer_resource{}; 79 | const D3D12_RESOURCE_DESC depth_buffer_resource_desc = { 80 | .Dimension = D3D12_RESOURCE_DIMENSION::D3D12_RESOURCE_DIMENSION_TEXTURE2D, 81 | .Width = window.get_width(), 82 | .Height = window.get_height(), 83 | .DepthOrArraySize = 1u, 84 | .MipLevels = 1u, 85 | .Format = DXGI_FORMAT_D32_FLOAT, 86 | .SampleDesc = {1u, 0u}, 87 | .Layout = D3D12_TEXTURE_LAYOUT::D3D12_TEXTURE_LAYOUT_UNKNOWN, 88 | .Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL, 89 | }; 90 | 91 | const D3D12_HEAP_PROPERTIES depth_buffer_heap_properties = { 92 | .Type = D3D12_HEAP_TYPE_DEFAULT, 93 | .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, 94 | .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, 95 | .CreationNodeMask = 0u, 96 | .VisibleNodeMask = 0u, 97 | }; 98 | 99 | const D3D12_CLEAR_VALUE depth_buffer_optimized_clear_value = { 100 | .Format = DXGI_FORMAT_D32_FLOAT, 101 | .DepthStencil = {.Depth = 0.0f, .Stencil = 0u}, 102 | }; 103 | 104 | throw_if_failed(renderer.m_device->CreateCommittedResource( 105 | &depth_buffer_heap_properties, D3D12_HEAP_FLAG_NONE, &depth_buffer_resource_desc, 106 | D3D12_RESOURCE_STATE_DEPTH_WRITE, &depth_buffer_optimized_clear_value, IID_PPV_ARGS(&depth_buffer_resource))); 107 | 108 | // Create DSV. 109 | D3D12_CPU_DESCRIPTOR_HANDLE dsv_handle = renderer.m_dsv_descriptor_heap.current_cpu_descriptor_handle; 110 | { 111 | const D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc = { 112 | .Format = DXGI_FORMAT_D32_FLOAT, 113 | .ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D, 114 | .Flags = D3D12_DSV_FLAG_NONE, 115 | .Texture2D = 116 | { 117 | 118 | .MipSlice = 0u, 119 | }, 120 | }; 121 | 122 | renderer.m_dsv_descriptor_heap.offset_current_descriptor_handles(); 123 | 124 | renderer.m_device->CreateDepthStencilView(depth_buffer_resource.Get(), &dsv_desc, dsv_handle); 125 | } 126 | 127 | // Create the PSO. 128 | Microsoft::WRL::ComPtr pso{}; 129 | const D3D12_GRAPHICS_PIPELINE_STATE_DESC graphics_pso_desc = { 130 | .pRootSignature = renderer.m_bindless_root_signature.Get(), 131 | .VS = 132 | { 133 | .pShaderBytecode = vertex_shader_blob->GetBufferPointer(), 134 | .BytecodeLength = vertex_shader_blob->GetBufferSize(), 135 | }, 136 | .PS = 137 | { 138 | .pShaderBytecode = pixel_shader_blob->GetBufferPointer(), 139 | .BytecodeLength = pixel_shader_blob->GetBufferSize(), 140 | }, 141 | .BlendState = 142 | { 143 | .AlphaToCoverageEnable = FALSE, 144 | .IndependentBlendEnable = FALSE, 145 | .RenderTarget = 146 | { 147 | D3D12_RENDER_TARGET_BLEND_DESC{ 148 | .BlendEnable = FALSE, 149 | .RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL, 150 | }, 151 | }, 152 | }, 153 | .SampleMask = 0xffff'ffff, 154 | .RasterizerState = 155 | { 156 | .FillMode = D3D12_FILL_MODE_SOLID, 157 | .CullMode = D3D12_CULL_MODE_BACK, 158 | .FrontCounterClockwise = FALSE, 159 | .DepthClipEnable = TRUE, 160 | }, 161 | .DepthStencilState = 162 | { 163 | .DepthEnable = TRUE, 164 | .DepthWriteMask = D3D12_DEPTH_WRITE_MASK::D3D12_DEPTH_WRITE_MASK_ALL, 165 | .DepthFunc = D3D12_COMPARISON_FUNC_GREATER, 166 | .StencilEnable = FALSE, 167 | }, 168 | .InputLayout = 169 | { 170 | .NumElements = 0u, 171 | }, 172 | .PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE, 173 | .NumRenderTargets = 1u, 174 | .RTVFormats = 175 | { 176 | Renderer::BACKBUFFER_FORMAT, 177 | }, 178 | .DSVFormat = DXGI_FORMAT_D32_FLOAT, 179 | .SampleDesc = 180 | { 181 | 1u, 182 | 0u, 183 | }, 184 | .NodeMask = 0u, 185 | }; 186 | throw_if_failed(renderer.m_device->CreateGraphicsPipelineState(&graphics_pso_desc, IID_PPV_ARGS(&pso))); 187 | 188 | // Setup the gpu culling compute shader. 189 | Microsoft::WRL::ComPtr gpu_culling_compute_shader_blob = ShaderCompiler::compile( 190 | FileSystem::instance().get_relative_path_wstr(L"shaders/gpu_culling_shader.hlsl").c_str(), L"cs_main", 191 | L"cs_6_6"); 192 | 193 | Microsoft::WRL::ComPtr gpu_culling_pso{}; 194 | const D3D12_COMPUTE_PIPELINE_STATE_DESC gpu_culling_compute_pso_desc = { 195 | .pRootSignature = renderer.m_bindless_root_signature.Get(), 196 | .CS = 197 | { 198 | .pShaderBytecode = gpu_culling_compute_shader_blob->GetBufferPointer(), 199 | .BytecodeLength = gpu_culling_compute_shader_blob->GetBufferSize(), 200 | }, 201 | .NodeMask = 0u, 202 | .Flags = D3D12_PIPELINE_STATE_FLAG_NONE, 203 | }; 204 | throw_if_failed( 205 | renderer.m_device->CreateComputePipelineState(&gpu_culling_compute_pso_desc, IID_PPV_ARGS(&gpu_culling_pso))); 206 | 207 | // Indirect command struct : command signature must match this struct. 208 | // Each chunk will have its own IndirectCommand, with 3 arguments. The render resources struct root constants, index 209 | // buffer view and a draw call. 210 | struct IndirectCommand 211 | { 212 | VoxelRenderResources render_resources{}; 213 | D3D12_INDEX_BUFFER_VIEW index_buffer_view{}; 214 | D3D12_DRAW_INDEXED_ARGUMENTS draw_arguments{}; 215 | float padding; 216 | }; 217 | 218 | printf("Size of indirect command : %zd\n", sizeof(IndirectCommand)); 219 | 220 | // Create the command signature, which tells the GPU how to interpret the data passed in the ExecuteIndirect call. 221 | const std::array argument_descs = { 222 | D3D12_INDIRECT_ARGUMENT_DESC{ 223 | .Type = D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT, 224 | .Constant = 225 | { 226 | .RootParameterIndex = 0u, 227 | .DestOffsetIn32BitValues = 0u, 228 | .Num32BitValuesToSet = sizeof(VoxelRenderResources) / sizeof(u32), 229 | }, 230 | }, 231 | D3D12_INDIRECT_ARGUMENT_DESC{ 232 | .Type = D3D12_INDIRECT_ARGUMENT_TYPE_INDEX_BUFFER_VIEW, 233 | }, 234 | D3D12_INDIRECT_ARGUMENT_DESC{ 235 | .Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED, 236 | }, 237 | }; 238 | 239 | Microsoft::WRL::ComPtr command_signature{}; 240 | const D3D12_COMMAND_SIGNATURE_DESC command_signature_desc = { 241 | .ByteStride = sizeof(IndirectCommand), 242 | .NumArgumentDescs = argument_descs.size(), 243 | .pArgumentDescs = argument_descs.data(), 244 | .NodeMask = 0u, 245 | }; 246 | 247 | throw_if_failed(renderer.m_device->CreateCommandSignature( 248 | &command_signature_desc, renderer.m_bindless_root_signature.Get(), IID_PPV_ARGS(&command_signature))); 249 | 250 | // Command buffer that will be used to store the indirect command args. 251 | static constexpr size_t MAX_CHUNKS_TO_BE_DRAWN = 10'00'000; 252 | std::vector indirect_command_vector{}; 253 | 254 | CommandBuffer indirect_command_buffer = 255 | renderer.create_command_buffer(sizeof(IndirectCommand), MAX_CHUNKS_TO_BE_DRAWN, L"Indirect Command Buffer"); 256 | 257 | // Create viewport and scissor. 258 | const D3D12_VIEWPORT viewport = { 259 | .TopLeftX = 0.0f, 260 | .TopLeftY = 0.0f, 261 | .Width = (float)window.get_width(), 262 | .Height = (float)window.get_height(), 263 | .MinDepth = 0.0f, 264 | .MaxDepth = 1.0f, 265 | }; 266 | 267 | // The default config is used if we want to mask the entire viewport for drawing. 268 | const D3D12_RECT scissor_rect = { 269 | .left = 0u, 270 | .top = 0u, 271 | .right = LONG_MAX, 272 | .bottom = LONG_MAX, 273 | }; 274 | 275 | // Execute and flush gpu so resources required for rendering (before the first frame) are ready. 276 | renderer.m_copy_queue.flush_queue(); 277 | 278 | renderer.m_direct_queue.execute_command_list(); 279 | renderer.m_direct_queue.flush_queue(); 280 | 281 | // Precompute the offset to a chunk index X, using which we can load chunks within the CHUNK_RENDER_DISTANCE volume 282 | // around the player at any given moment. 283 | // For precomputation, X is assumed to be zero. These values will be added to the current chunk index. 284 | // NOTE : The current player chunk is loaded first, then the chunks 1 distance away, then 2 distance away, etc. 285 | // note(rtarun9) : Make this more efficient? 286 | std::vector chunk_render_distance_offsets = {}; 287 | chunk_render_distance_offsets.push_back(DirectX::XMINT3{0, 0, 0}); 288 | 289 | for (i32 z = -1 * ChunkManager::CHUNK_RENDER_DISTANCE; z <= (i32)ChunkManager::CHUNK_RENDER_DISTANCE; z++) 290 | { 291 | for (i32 y = -ChunkManager::CHUNK_RENDER_DISTANCE; y <= (i32)ChunkManager::CHUNK_RENDER_DISTANCE; y++) 292 | { 293 | for (i32 x = -ChunkManager::CHUNK_RENDER_DISTANCE; x <= (i32)ChunkManager::CHUNK_RENDER_DISTANCE; x++) 294 | { 295 | if ((z == -ChunkManager::CHUNK_RENDER_DISTANCE || z == ChunkManager::CHUNK_RENDER_DISTANCE) || 296 | (y == -ChunkManager::CHUNK_RENDER_DISTANCE || y == ChunkManager::CHUNK_RENDER_DISTANCE) || 297 | (x == -ChunkManager::CHUNK_RENDER_DISTANCE || x == ChunkManager::CHUNK_RENDER_DISTANCE)) 298 | { 299 | chunk_render_distance_offsets.emplace_back(DirectX::XMINT3{x, y, z}); 300 | } 301 | } 302 | } 303 | } 304 | std::sort(chunk_render_distance_offsets.begin(), chunk_render_distance_offsets.end(), 305 | [](const DirectX::XMINT3 &a, const DirectX::XMINT3 &b) { 306 | return a.x * a.x + a.y * a.y + a.z * a.z < b.x * b.x + b.y * b.y + b.z * b.z; 307 | }); 308 | 309 | Camera camera{}; 310 | const u64 chunk_grid_middle = Chunk::CHUNK_LENGTH * ChunkManager::NUMBER_OF_CHUNKS_PER_DIMENSION / 2u; 311 | camera.m_position = {chunk_grid_middle, chunk_grid_middle, chunk_grid_middle, 1.0f}; 312 | 313 | std::queue chunks_to_unload{}; 314 | 315 | Timer timer{}; 316 | float delta_time = 0.0f; 317 | 318 | bool setup_chunks{false}; 319 | 320 | u64 frame_count = 0; 321 | 322 | bool quit{false}; 323 | while (!quit) 324 | { 325 | static float near_plane = 1.0f; 326 | static float far_plane = 1000000.0f; 327 | 328 | // Get the player's current chunk index. 329 | 330 | const DirectX::XMUINT3 current_chunk_3d_index = { 331 | (u32)(floor((camera.m_position.x) / Chunk::CHUNK_LENGTH)), 332 | (u32)(floor((camera.m_position.y) / Chunk::CHUNK_LENGTH)), 333 | (u32)(floor((camera.m_position.z) / Chunk::CHUNK_LENGTH)), 334 | }; 335 | 336 | const u64 current_chunk_index = 337 | convert_to_1d(current_chunk_3d_index, ChunkManager::NUMBER_OF_CHUNKS_PER_DIMENSION); 338 | 339 | if (setup_chunks) 340 | { 341 | // Load chunks around the player (ChunkManager::CHUNK_RENDER_DISTANCE) determines how many of these 342 | // chunks to load. 343 | for (const auto &offset : chunk_render_distance_offsets) 344 | { 345 | const DirectX::XMUINT3 chunk_3d_index = { 346 | current_chunk_3d_index.x + offset.x, 347 | current_chunk_3d_index.y + offset.y, 348 | current_chunk_3d_index.z + offset.z, 349 | }; 350 | 351 | chunk_manager.add_chunk_to_setup_stack( 352 | convert_to_1d(chunk_3d_index, ChunkManager::NUMBER_OF_CHUNKS_PER_DIMENSION)); 353 | } 354 | } 355 | 356 | chunk_manager.create_chunks_from_setup_stack(renderer); 357 | 358 | timer.start(); 359 | 360 | MSG message = {}; 361 | if (PeekMessageA(&message, NULL, 0u, 0u, PM_REMOVE)) 362 | { 363 | TranslateMessage(&message); 364 | DispatchMessageA(&message); 365 | } 366 | 367 | if (message.message == WM_QUIT) 368 | { 369 | quit = true; 370 | } 371 | 372 | chunk_manager.transfer_chunks_from_setup_to_loaded_state(renderer.m_copy_queue.m_fence->GetCompletedValue()); 373 | 374 | const float window_aspect_ratio = static_cast(window.get_width()) / window.get_height(); 375 | 376 | // Article followed for reverse Z: 377 | // https://iolite-engine.com/blog_posts/reverse_z_cheatsheet 378 | 379 | // https://github.com/microsoft/DirectXMath/issues/158 link that shows the projection matrix for infinite far 380 | // plane. 381 | // Note : This code is taken from the directxmath source code for perspective projection fov lh, but modified 382 | // for infinite far plane. 383 | 384 | float sin_fov{}; 385 | float cos_fov{}; 386 | DirectX::XMScalarSinCos(&sin_fov, &cos_fov, 0.5f * DirectX::XMConvertToRadians(45.0f)); 387 | 388 | float height = cos_fov / sin_fov; 389 | float width = height / window_aspect_ratio; 390 | 391 | const DirectX::XMMATRIX projection_matrix = DirectX::XMMatrixSet( 392 | width, 0.0f, 0.0f, 0.0f, 0.0f, height, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, near_plane, 0.0f); 393 | 394 | scene_buffer_data.view_matrix = camera.update_and_get_view_matrix(delta_time); 395 | scene_buffer_data.projection_matrix = projection_matrix; 396 | scene_buffer_data.camera_position = camera.m_position; 397 | 398 | ConstantBuffer &scene_buffer = scene_buffers[renderer.m_swapchain_backbuffer_index]; 399 | scene_buffer.update(&scene_buffer_data); 400 | 401 | const auto &swapchain_index = renderer.m_swapchain_backbuffer_index; 402 | 403 | // Reset command allocator and command list. 404 | renderer.m_direct_queue.reset(swapchain_index); 405 | 406 | const auto &command_list = renderer.m_direct_queue.m_command_list; 407 | 408 | const auto &rtv_handle = renderer.m_swapchain_backbuffer_cpu_descriptor_handles[swapchain_index]; 409 | const Microsoft::WRL::ComPtr swapchain_resource = 410 | renderer.m_swapchain_backbuffer_resources[swapchain_index]; 411 | 412 | // Transition the backbuffer from presentation mode to render target mode. 413 | const D3D12_RESOURCE_BARRIER presentation_to_render_target_barrier = { 414 | .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, 415 | .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, 416 | .Transition = 417 | { 418 | .pResource = swapchain_resource.Get(), 419 | .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, 420 | .StateBefore = D3D12_RESOURCE_STATE_PRESENT, 421 | .StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET, 422 | }, 423 | }; 424 | 425 | command_list->ResourceBarrier(1u, &presentation_to_render_target_barrier); 426 | 427 | // Now, clear the RTV and DSV. 428 | const float clear_color[4] = {0.1f, 0.1f, 0.1f, 1.0f}; 429 | command_list->ClearRenderTargetView(rtv_handle, clear_color, 0u, nullptr); 430 | command_list->ClearDepthStencilView(dsv_handle, D3D12_CLEAR_FLAG_DEPTH, 0.0f, 0u, 0u, nullptr); 431 | 432 | // Set viewport. 433 | command_list->RSSetViewports(1u, &viewport); 434 | command_list->RSSetScissorRects(1u, &scissor_rect); 435 | 436 | // Evict the chunks that are out of range of render distance. 437 | // Because evicting chunks seems to have such a high overhead (more specifically freeing allocated id3d12 438 | // resources) each frame only a certain number of chunks are unloaded. 439 | for (const auto &[i, chunk] : chunk_manager.m_loaded_chunks) 440 | { 441 | const DirectX::XMUINT3 chunk_index_3d = convert_to_3d(i, ChunkManager::NUMBER_OF_CHUNKS_PER_DIMENSION); 442 | if (std::abs((i32)chunk_index_3d.x - (i32)current_chunk_3d_index.x) > 443 | ChunkManager::CHUNK_RENDER_DISTANCE * 8 || 444 | std::abs((i32)chunk_index_3d.y - (i32)current_chunk_3d_index.y) > 445 | ChunkManager::CHUNK_RENDER_DISTANCE * 8 || 446 | std::abs((i32)chunk_index_3d.z - (i32)current_chunk_3d_index.z) > 447 | ChunkManager::CHUNK_RENDER_DISTANCE * 8) 448 | { 449 | chunks_to_unload.push(i); 450 | } 451 | } 452 | 453 | /* 454 | size_t unloaded_chunks = 0; 455 | while (false && !chunks_to_unload.empty() && unloaded_chunks < ChunkManager::CHUNKS_TO_UNLOAD_PER_FRAME) 456 | { 457 | const auto chunk_to_unload = chunks_to_unload.front(); 458 | chunks_to_unload.pop(); 459 | 460 | chunk_manager.m_loaded_chunks.erase(chunk_to_unload); 461 | 462 | chunk_manager.m_chunk_index_buffers.erase(chunk_to_unload); 463 | chunk_manager.m_chunk_index_buffers[chunk_to_unload].resource.Reset(); 464 | 465 | chunk_manager.m_chunk_color_buffers.erase(chunk_to_unload); 466 | chunk_manager.m_chunk_color_buffers[chunk_to_unload].resource.Reset(); 467 | 468 | chunk_manager.m_chunk_constant_buffers[chunk_to_unload].resource.Reset(); 469 | chunk_manager.m_chunk_constant_buffers.erase(chunk_to_unload); 470 | 471 | ++unloaded_chunks; 472 | } 473 | */ 474 | 475 | // Setup indirect command vector. 476 | indirect_command_vector.clear(); 477 | indirect_command_vector.reserve(chunk_manager.m_loaded_chunks.size()); 478 | for (const auto &[i, chunk] : chunk_manager.m_loaded_chunks) 479 | { 480 | const VoxelRenderResources render_resources = { 481 | .scene_constant_buffer_index = static_cast(scene_buffer.cbv_index), 482 | .chunk_constant_buffer_index = static_cast(chunk_manager.m_chunk_constant_buffers[i].cbv_index), 483 | }; 484 | 485 | indirect_command_vector.emplace_back(IndirectCommand{ 486 | .render_resources = render_resources, 487 | .index_buffer_view = chunk_manager.m_chunk_index_buffers[i].index_buffer_view, 488 | .draw_arguments = 489 | D3D12_DRAW_INDEXED_ARGUMENTS{ 490 | .IndexCountPerInstance = (u32)chunk_manager.m_chunk_index_buffers[i].indices_count, 491 | .InstanceCount = 1u, 492 | .StartIndexLocation = 0u, 493 | .BaseVertexLocation = 0u, 494 | .StartInstanceLocation = 0u, 495 | }, 496 | }); 497 | } 498 | 499 | ID3D12DescriptorHeap *const *shader_visible_descriptor_heaps = { 500 | renderer.m_cbv_srv_uav_descriptor_heap.descriptor_heap.GetAddressOf(), 501 | }; 502 | 503 | command_list->SetDescriptorHeaps(1u, shader_visible_descriptor_heaps); 504 | 505 | // Prepare rendering commands. 506 | command_list->OMSetRenderTargets(1u, &rtv_handle, FALSE, &dsv_handle); 507 | 508 | // Run the culling compute shader, followed by voxel rendering shader. 509 | if (!indirect_command_vector.empty()) 510 | { 511 | const D3D12_RESOURCE_BARRIER indirect_argument_to_copy_dest_state = { 512 | .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, 513 | .Flags = D3D12_RESOURCE_BARRIER_FLAGS::D3D12_RESOURCE_BARRIER_FLAG_NONE, 514 | .Transition = 515 | D3D12_RESOURCE_TRANSITION_BARRIER{ 516 | .pResource = indirect_command_buffer.default_resource.Get(), 517 | .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, 518 | .StateBefore = D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT, 519 | .StateAfter = D3D12_RESOURCE_STATE_COPY_DEST, 520 | }, 521 | }; 522 | command_list->ResourceBarrier(1u, &indirect_argument_to_copy_dest_state); 523 | 524 | memcpy(indirect_command_buffer.upload_resource_mapped_ptr, indirect_command_vector.data(), 525 | indirect_command_vector.size() * sizeof(IndirectCommand)); 526 | 527 | GPUCullRenderResources gpu_cull_render_resources = { 528 | .number_of_chunks = static_cast(indirect_command_vector.size()), 529 | .indirect_command_srv_index = static_cast(indirect_command_buffer.upload_resource_srv_index), 530 | .output_command_uav_index = static_cast(indirect_command_buffer.default_resource_uav_index), 531 | .scene_constant_buffer_index = static_cast(scene_buffer.cbv_index), 532 | }; 533 | 534 | command_list->SetDescriptorHeaps(1u, shader_visible_descriptor_heaps); 535 | command_list->SetComputeRootSignature(renderer.m_bindless_root_signature.Get()); 536 | command_list->SetPipelineState(gpu_culling_pso.Get()); 537 | 538 | command_list->SetComputeRoot32BitConstants(0u, 64u, &gpu_cull_render_resources, 0u); 539 | 540 | // Clear the counter associated with UAV. 541 | 542 | command_list->CopyBufferRegion(indirect_command_buffer.default_resource.Get(), 543 | indirect_command_buffer.counter_offset, 544 | indirect_command_buffer.zeroed_counter_buffer_resource.Get(), 0u, 4u); 545 | 546 | const D3D12_RESOURCE_BARRIER copy_dest_to_unordered_access_state = { 547 | .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, 548 | .Flags = D3D12_RESOURCE_BARRIER_FLAGS::D3D12_RESOURCE_BARRIER_FLAG_NONE, 549 | .Transition = 550 | D3D12_RESOURCE_TRANSITION_BARRIER{ 551 | .pResource = indirect_command_buffer.default_resource.Get(), 552 | .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, 553 | .StateBefore = D3D12_RESOURCE_STATE_COPY_DEST, 554 | .StateAfter = D3D12_RESOURCE_STATE_UNORDERED_ACCESS, 555 | }, 556 | 557 | }; 558 | 559 | command_list->ResourceBarrier(1u, ©_dest_to_unordered_access_state); 560 | 561 | command_list->Dispatch((indirect_command_vector.size() + 31) / 32u, 1u, 1u); 562 | 563 | const D3D12_RESOURCE_BARRIER unordered_access_to_indirect_argument_state = { 564 | .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, 565 | .Flags = D3D12_RESOURCE_BARRIER_FLAGS::D3D12_RESOURCE_BARRIER_FLAG_NONE, 566 | .Transition = 567 | D3D12_RESOURCE_TRANSITION_BARRIER{ 568 | .pResource = indirect_command_buffer.default_resource.Get(), 569 | .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, 570 | .StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS, 571 | .StateAfter = D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT, 572 | }, 573 | 574 | }; 575 | 576 | command_list->ResourceBarrier(1u, &unordered_access_to_indirect_argument_state); 577 | 578 | command_list->SetDescriptorHeaps(1u, shader_visible_descriptor_heaps); 579 | command_list->SetGraphicsRootSignature(renderer.m_bindless_root_signature.Get()); 580 | command_list->SetPipelineState(pso.Get()); 581 | 582 | command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); 583 | 584 | command_list->ExecuteIndirect( 585 | command_signature.Get(), MAX_CHUNKS_TO_BE_DRAWN, indirect_command_buffer.default_resource.Get(), 0u, 586 | indirect_command_buffer.default_resource.Get(), indirect_command_buffer.counter_offset); 587 | } 588 | 589 | // Render UI. 590 | // Start the Dear ImGui frame 591 | 592 | ImGui_ImplDX12_NewFrame(); 593 | ImGui_ImplWin32_NewFrame(); 594 | ImGui::NewFrame(); 595 | 596 | ImGui::Begin("Debug Controller"); 597 | ImGui::SliderFloat("movement_speed", &camera.m_movement_speed, 0.0f, 5000.0f); 598 | ImGui::SliderFloat("rotation_speed", &camera.m_rotation_speed, 0.0f, 10.0f); 599 | ImGui::SliderFloat("friction", &camera.m_friction, 0.0f, 1.0f); 600 | ImGui::SliderFloat("near plane", &near_plane, 0.1f, 1.0f); 601 | ImGui::SliderFloat("Far plane", &far_plane, 10.0f, 10000000.0f); 602 | ImGui::Checkbox("Start loading chunks", &setup_chunks); 603 | ImGui::Text("Delta Time: %f", delta_time); 604 | ImGui::Text("Camera Position : %f %f %f", camera.m_position.x, camera.m_position.y, camera.m_position.z); 605 | ImGui::Text("Pitch and Yaw: %f %f", camera.m_pitch, camera.m_yaw); 606 | ImGui::Text("Current Index: %zu", current_chunk_index); 607 | ImGui::Text("Current 3D Index: %zu, %zu, %zu", current_chunk_3d_index.x, current_chunk_3d_index.y, 608 | current_chunk_3d_index.z); 609 | ImGui::Text("Number of loaded chunks: %zu", chunk_manager.m_loaded_chunks.size()); 610 | ImGui::Text("Number of rendered chunks: %zu", indirect_command_vector.size()); 611 | ImGui::Text("Number of copy alloc / list pairs : %zu", 612 | renderer.m_copy_queue.m_command_allocator_list_queue.size()); 613 | ImGui::Text("Voxel edge length : %zu", Voxel::EDGE_LENGTH); 614 | ImGui::Text("Number of threads in pool : %zu", chunk_manager.m_thread_pool.get_thread_count()); 615 | ImGui::Text("Number of queued threads in pool : %zu", chunk_manager.m_thread_pool.get_tasks_queued()); 616 | 617 | ImGui::ShowMetricsWindow(); 618 | ImGui::End(); 619 | 620 | command_list->SetDescriptorHeaps(1u, shader_visible_descriptor_heaps); 621 | ImGui::Render(); 622 | ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), command_list.Get()); 623 | 624 | // Now, transition back to presentation mode. 625 | const D3D12_RESOURCE_BARRIER render_target_to_presentation_barrier = { 626 | .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, 627 | .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, 628 | .Transition = 629 | { 630 | .pResource = swapchain_resource.Get(), 631 | .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, 632 | .StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET, 633 | .StateAfter = D3D12_RESOURCE_STATE_PRESENT, 634 | }, 635 | }; 636 | 637 | command_list->ResourceBarrier(1u, &render_target_to_presentation_barrier); 638 | 639 | // Submit command list to queue for execution. 640 | renderer.m_direct_queue.execute_command_list(); 641 | 642 | // Now, present the rendertarget and signal command queue. 643 | throw_if_failed(renderer.m_swapchain->Present(1u, 0u)); 644 | renderer.m_direct_queue.signal_fence(renderer.m_swapchain_backbuffer_index); 645 | 646 | renderer.m_swapchain_backbuffer_index = static_cast(renderer.m_swapchain->GetCurrentBackBufferIndex()); 647 | 648 | // Wait for the previous frame (that is presenting to 649 | // swpachain_backbuffer_index) to complete execution. 650 | renderer.m_direct_queue.wait_for_fence_value_at_index(renderer.m_swapchain_backbuffer_index); 651 | 652 | ++frame_count; 653 | 654 | timer.stop(); 655 | delta_time = timer.get_delta_time(); 656 | } 657 | 658 | // Cleanup 659 | ImGui_ImplDX12_Shutdown(); 660 | ImGui_ImplWin32_Shutdown(); 661 | ImGui::DestroyContext(); 662 | 663 | renderer.m_direct_queue.flush_queue(); 664 | renderer.m_copy_queue.flush_queue(); 665 | 666 | return 0; 667 | } -------------------------------------------------------------------------------- /src/renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "voxel-engine/renderer.hpp" 2 | 3 | // Agility SDK setup. 4 | // Setting the Agility SDK parameters. 5 | extern "C" 6 | { 7 | __declspec(dllexport) extern const UINT D3D12SDKVersion = 711u; 8 | } 9 | 10 | extern "C" 11 | { 12 | __declspec(dllexport) extern const char *D3D12SDKPath = ".\\D3D12\\"; 13 | } 14 | 15 | D3D12_GPU_DESCRIPTOR_HANDLE Renderer::DescriptorHeap::get_gpu_descriptor_handle_at_index(const size_t index) const 16 | { 17 | D3D12_GPU_DESCRIPTOR_HANDLE handle = descriptor_heap->GetGPUDescriptorHandleForHeapStart(); 18 | handle.ptr += index * descriptor_handle_size; 19 | 20 | return handle; 21 | } 22 | 23 | D3D12_CPU_DESCRIPTOR_HANDLE Renderer::DescriptorHeap::get_cpu_descriptor_handle_at_index(const size_t index) const 24 | { 25 | D3D12_CPU_DESCRIPTOR_HANDLE handle = descriptor_heap->GetCPUDescriptorHandleForHeapStart(); 26 | handle.ptr += index * descriptor_handle_size; 27 | 28 | return handle; 29 | } 30 | 31 | void Renderer::DescriptorHeap::offset_current_descriptor_handles() 32 | { 33 | current_cpu_descriptor_handle.ptr += descriptor_handle_size; 34 | current_gpu_descriptor_handle.ptr += descriptor_handle_size; 35 | 36 | current_descriptor_handle_index++; 37 | } 38 | 39 | void Renderer::DescriptorHeap::create(ID3D12Device *const device, const size_t num_descriptors, 40 | const D3D12_DESCRIPTOR_HEAP_TYPE descriptor_heap_type, 41 | const D3D12_DESCRIPTOR_HEAP_FLAGS descriptor_heap_flags) 42 | { 43 | const D3D12_DESCRIPTOR_HEAP_DESC descriptor_heap_desc = { 44 | .Type = descriptor_heap_type, 45 | .NumDescriptors = static_cast(num_descriptors), 46 | .Flags = descriptor_heap_flags, 47 | .NodeMask = 0u, 48 | }; 49 | 50 | throw_if_failed(device->CreateDescriptorHeap(&descriptor_heap_desc, IID_PPV_ARGS(&descriptor_heap))); 51 | 52 | current_cpu_descriptor_handle = descriptor_heap->GetCPUDescriptorHandleForHeapStart(); 53 | 54 | if (descriptor_heap_flags == D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE) 55 | { 56 | current_gpu_descriptor_handle = descriptor_heap->GetGPUDescriptorHandleForHeapStart(); 57 | } 58 | 59 | current_descriptor_handle_index = 0u; 60 | 61 | descriptor_handle_size = device->GetDescriptorHandleIncrementSize(descriptor_heap_type); 62 | } 63 | 64 | Renderer::Renderer(const HWND window_handle, const u16 window_width, const u16 window_height) 65 | { 66 | // Enable the debug layer in debug mode. 67 | if constexpr (VX_DEBUG_MODE) 68 | { 69 | throw_if_failed(D3D12GetDebugInterface(IID_PPV_ARGS(&m_debug_device))); 70 | m_debug_device->EnableDebugLayer(); 71 | 72 | Microsoft::WRL::ComPtr debug_1{}; 73 | throw_if_failed(m_debug_device->QueryInterface(IID_PPV_ARGS(&debug_1))); 74 | debug_1->SetEnableSynchronizedCommandQueueValidation(TRUE); 75 | 76 | // Note : This cannot be set, because of the warning message 77 | // contains a shader op (Draw/Dispatch/ExecuteIndirect) recorded while using Shader Patch Mode NONE, or contains 78 | // an ExecuteIndirect that changes VB/IB/Root bindings. Hence, all further GPU-based validation may 79 | // undervalidate or produce true GBV errors with imprecise tracked state (labelled with 'Possibly imprecise') 80 | // for resources in the COMMON state or Promoted-from-COMMON state at the time of execute. 81 | 82 | debug_1->SetEnableGPUBasedValidation(FALSE); 83 | } 84 | 85 | // Create the DXGI Factory so we get access to DXGI objects (like adapters). 86 | u32 dxgi_factory_creation_flags = 0u; 87 | 88 | if constexpr (VX_DEBUG_MODE) 89 | { 90 | dxgi_factory_creation_flags = DXGI_CREATE_FACTORY_DEBUG; 91 | } 92 | 93 | throw_if_failed(CreateDXGIFactory2(dxgi_factory_creation_flags, IID_PPV_ARGS(&m_dxgi_factory))); 94 | 95 | // Get the adapter with best performance. Print the selected adapter's details to console. 96 | throw_if_failed(m_dxgi_factory->EnumAdapterByGpuPreference(0u, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, 97 | IID_PPV_ARGS(&m_dxgi_adapter))); 98 | 99 | DXGI_ADAPTER_DESC adapter_desc{}; 100 | throw_if_failed(m_dxgi_adapter->GetDesc(&adapter_desc)); 101 | printf("Selected adapter desc :: %ls.\n", adapter_desc.Description); 102 | 103 | // Create the d3d12 device (logical adapter : All d3d objects require d3d12 device for creation). 104 | throw_if_failed(D3D12CreateDevice(m_dxgi_adapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&m_device))); 105 | 106 | // In debug mode, setup the info queue so breakpoint is placed whenever a error / warning occurs that is d3d 107 | // related. 108 | Microsoft::WRL::ComPtr info_queue{}; 109 | if constexpr (VX_DEBUG_MODE) 110 | { 111 | throw_if_failed(m_device.As(&info_queue)); 112 | 113 | throw_if_failed(info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE)); 114 | throw_if_failed(info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE)); 115 | throw_if_failed(info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE)); 116 | } 117 | 118 | // Setup the copy queue & direct queue primitives. 119 | m_direct_queue.create(m_device.Get()); 120 | m_copy_queue.create(m_device.Get()); 121 | 122 | // Create descriptor heaps. 123 | m_cbv_srv_uav_descriptor_heap.create(m_device.Get(), D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_1, 124 | D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 125 | D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE); 126 | 127 | m_rtv_descriptor_heap.create(m_device.Get(), 10u, D3D12_DESCRIPTOR_HEAP_TYPE_RTV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE); 128 | 129 | m_dsv_descriptor_heap.create(m_device.Get(), 1u, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE); 130 | 131 | // Create the dxgi swapchain. 132 | { 133 | Microsoft::WRL::ComPtr swapchain_1{}; 134 | const DXGI_SWAP_CHAIN_DESC1 swapchain_desc = { 135 | .Width = static_cast(window_width), 136 | .Height = static_cast(window_height), 137 | .Format = BACKBUFFER_FORMAT, 138 | .Stereo = FALSE, 139 | .SampleDesc = {1, 0}, 140 | .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT, 141 | .BufferCount = NUMBER_OF_BACKBUFFERS, 142 | .Scaling = DXGI_SCALING_NONE, 143 | .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD, 144 | .AlphaMode = DXGI_ALPHA_MODE_IGNORE, 145 | .Flags = 0u, 146 | }; 147 | throw_if_failed(m_dxgi_factory->CreateSwapChainForHwnd(m_direct_queue.m_command_queue.Get(), window_handle, 148 | &swapchain_desc, nullptr, nullptr, &swapchain_1)); 149 | 150 | throw_if_failed(swapchain_1.As(&m_swapchain)); 151 | } 152 | 153 | // Create the render target view for the swapchain back buffer. 154 | for (u8 i = 0; i < NUMBER_OF_BACKBUFFERS; i++) 155 | { 156 | Microsoft::WRL::ComPtr swapchain_resource{}; 157 | throw_if_failed(m_swapchain->GetBuffer(i, IID_PPV_ARGS(&swapchain_resource))); 158 | m_swapchain_backbuffer_cpu_descriptor_handles[i] = m_rtv_descriptor_heap.get_cpu_descriptor_handle_at_index(i); 159 | 160 | m_device->CreateRenderTargetView(swapchain_resource.Get(), nullptr, 161 | m_swapchain_backbuffer_cpu_descriptor_handles[i]); 162 | 163 | m_swapchain_backbuffer_resources[i] = (std::move(swapchain_resource)); 164 | } 165 | 166 | m_swapchain_backbuffer_index = static_cast(m_swapchain->GetCurrentBackBufferIndex()); 167 | 168 | // Create and setup the bindless root signature that is shared by all pipelines. 169 | 170 | const D3D12_ROOT_PARAMETER1 shader_constant_root_parameter = { 171 | .ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS, 172 | .Constants = 173 | { 174 | .ShaderRegister = 0u, 175 | .RegisterSpace = 0u, 176 | .Num32BitValues = 64u, 177 | }, 178 | .ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL, 179 | }; 180 | 181 | Microsoft::WRL::ComPtr bindless_root_signature_blob{}; 182 | const D3D12_VERSIONED_ROOT_SIGNATURE_DESC root_signature_desc = { 183 | .Version = D3D_ROOT_SIGNATURE_VERSION_1_1, 184 | .Desc_1_1 = 185 | { 186 | .NumParameters = 1u, 187 | .pParameters = &shader_constant_root_parameter, 188 | .NumStaticSamplers = 0u, 189 | .pStaticSamplers = nullptr, 190 | .Flags = D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED | 191 | D3D12_ROOT_SIGNATURE_FLAG_SAMPLER_HEAP_DIRECTLY_INDEXED, 192 | }, 193 | }; 194 | 195 | // Serialize root signature. 196 | throw_if_failed(D3D12SerializeVersionedRootSignature(&root_signature_desc, &bindless_root_signature_blob, nullptr)); 197 | throw_if_failed(m_device->CreateRootSignature(0u, bindless_root_signature_blob->GetBufferPointer(), 198 | bindless_root_signature_blob->GetBufferSize(), 199 | IID_PPV_ARGS(&m_bindless_root_signature))); 200 | } 201 | 202 | Renderer::IndexBufferWithIntermediateResource Renderer::create_index_buffer(const void *data, const size_t stride, 203 | const size_t indices_count, 204 | const std::wstring_view buffer_name) 205 | { 206 | const size_t size_in_bytes = stride * indices_count; 207 | 208 | u8 *resource_ptr{}; 209 | Microsoft::WRL::ComPtr buffer_resource{}; 210 | Microsoft::WRL::ComPtr intermediate_buffer_resource{}; 211 | 212 | // First, create a upload buffer (that is placed in memory accesible by both GPU and CPU). 213 | // Then create a GPU only buffer, and copy data from the previous buffer to GPU only one. 214 | const D3D12_HEAP_PROPERTIES upload_heap_properties = { 215 | .Type = D3D12_HEAP_TYPE_UPLOAD, 216 | .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, 217 | .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, 218 | .CreationNodeMask = 0u, 219 | .VisibleNodeMask = 0u, 220 | }; 221 | 222 | const D3D12_RESOURCE_DESC buffer_resource_desc = { 223 | .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, 224 | .Width = size_in_bytes, 225 | .Height = 1u, 226 | .DepthOrArraySize = 1u, 227 | .MipLevels = 1u, 228 | .Format = DXGI_FORMAT_UNKNOWN, 229 | .SampleDesc = {1u, 0u}, 230 | .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 231 | .Flags = D3D12_RESOURCE_FLAG_NONE, 232 | }; 233 | 234 | throw_if_failed(m_device->CreateCommittedResource( 235 | &upload_heap_properties, D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES, &buffer_resource_desc, 236 | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&intermediate_buffer_resource))); 237 | 238 | // Now that a resource is created, copy CPU data to this upload buffer. 239 | const D3D12_RANGE read_range{.Begin = 0u, .End = 0u}; 240 | 241 | throw_if_failed(intermediate_buffer_resource->Map(0u, &read_range, (void **)&resource_ptr)); 242 | 243 | memcpy(resource_ptr, data, size_in_bytes); 244 | 245 | // Create the final resource and transfer the data from upload buffer to the final buffer. 246 | // The heap type is : Default (no CPU access). 247 | const D3D12_HEAP_PROPERTIES default_heap_properties = { 248 | .Type = D3D12_HEAP_TYPE_DEFAULT, 249 | .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, 250 | .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, 251 | .CreationNodeMask = 0u, 252 | .VisibleNodeMask = 0u, 253 | }; 254 | 255 | throw_if_failed(m_device->CreateCommittedResource( 256 | &default_heap_properties, D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES, &buffer_resource_desc, 257 | D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&buffer_resource))); 258 | 259 | std::scoped_lock scoped_lock(m_resource_mutex); 260 | 261 | name_d3d12_object(buffer_resource.Get(), buffer_name); 262 | name_d3d12_object(intermediate_buffer_resource.Get(), std::wstring(buffer_name) + std::wstring(L" [intermediate]")); 263 | 264 | auto command_allocator_list_pair = m_copy_queue.get_command_allocator_list_pair(m_device.Get()); 265 | 266 | command_allocator_list_pair.m_command_list->CopyResource(buffer_resource.Get(), intermediate_buffer_resource.Get()); 267 | m_copy_queue.execute_command_list(std::move(command_allocator_list_pair)); 268 | 269 | const D3D12_INDEX_BUFFER_VIEW index_buffer_view = { 270 | .BufferLocation = buffer_resource->GetGPUVirtualAddress(), 271 | .SizeInBytes = static_cast(size_in_bytes), 272 | .Format = DXGI_FORMAT_R16_UINT, 273 | }; 274 | 275 | return { 276 | IndexBuffer{ 277 | .resource = buffer_resource, 278 | .indices_count = indices_count, 279 | .index_buffer_view = index_buffer_view, 280 | }, 281 | intermediate_buffer_resource, 282 | }; 283 | } 284 | 285 | Renderer::StucturedBufferWithIntermediateResource Renderer::create_structured_buffer( 286 | const void *data, const size_t stride, const size_t num_elements, const std::wstring_view buffer_name) 287 | { 288 | const size_t size_in_bytes = stride * num_elements; 289 | 290 | u8 *resource_ptr{}; 291 | Microsoft::WRL::ComPtr buffer_resource{}; 292 | Microsoft::WRL::ComPtr intermediate_buffer_resource{}; 293 | 294 | // First, create a upload buffer (that is placed in memory accesible by both GPU and CPU). 295 | // Then create a GPU only buffer, and copy data from the previous buffer to GPU only one. 296 | const D3D12_HEAP_PROPERTIES upload_heap_properties = { 297 | .Type = D3D12_HEAP_TYPE_UPLOAD, 298 | .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, 299 | .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, 300 | .CreationNodeMask = 0u, 301 | .VisibleNodeMask = 0u, 302 | }; 303 | 304 | const D3D12_RESOURCE_DESC buffer_resource_desc = { 305 | .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, 306 | .Width = size_in_bytes, 307 | .Height = 1u, 308 | .DepthOrArraySize = 1u, 309 | .MipLevels = 1u, 310 | .Format = DXGI_FORMAT_UNKNOWN, 311 | .SampleDesc = {1u, 0u}, 312 | .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 313 | .Flags = D3D12_RESOURCE_FLAG_NONE, 314 | }; 315 | 316 | throw_if_failed(m_device->CreateCommittedResource( 317 | &upload_heap_properties, D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES | D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, 318 | &buffer_resource_desc, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, nullptr, 319 | IID_PPV_ARGS(&intermediate_buffer_resource))); 320 | 321 | // Now that a resource is created, copy CPU data to this upload buffer. 322 | const D3D12_RANGE read_range{.Begin = 0u, .End = 0u}; 323 | 324 | throw_if_failed(intermediate_buffer_resource->Map(0u, &read_range, (void **)&resource_ptr)); 325 | 326 | memcpy(resource_ptr, data, size_in_bytes); 327 | 328 | // Create the final resource and transfer the data from upload buffer to the final buffer. 329 | // The heap type is : Default (no CPU access). 330 | const D3D12_HEAP_PROPERTIES default_heap_properties = { 331 | .Type = D3D12_HEAP_TYPE_DEFAULT, 332 | .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, 333 | .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, 334 | .CreationNodeMask = 0u, 335 | .VisibleNodeMask = 0u, 336 | }; 337 | 338 | throw_if_failed(m_device->CreateCommittedResource( 339 | &default_heap_properties, D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES | D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, 340 | &buffer_resource_desc, D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&buffer_resource))); 341 | 342 | std::scoped_lock scoped_lock(m_resource_mutex); 343 | 344 | name_d3d12_object(buffer_resource.Get(), buffer_name); 345 | name_d3d12_object(intermediate_buffer_resource.Get(), std::wstring(buffer_name) + std::wstring(L" [intermediate]")); 346 | 347 | auto command_allocator_list_pair = m_copy_queue.get_command_allocator_list_pair(m_device.Get()); 348 | 349 | command_allocator_list_pair.m_command_list->CopyResource(buffer_resource.Get(), intermediate_buffer_resource.Get()); 350 | m_copy_queue.execute_command_list(std::move(command_allocator_list_pair)); 351 | 352 | // Create structured buffer view. 353 | size_t srv_index = create_shader_resource_view(buffer_resource.Get(), stride, num_elements); 354 | 355 | return { 356 | StructuredBuffer{ 357 | .resource = buffer_resource, 358 | .srv_index = srv_index, 359 | }, 360 | intermediate_buffer_resource, 361 | }; 362 | } 363 | 364 | ConstantBuffer Renderer::internal_create_constant_buffer(const size_t size_in_bytes, 365 | const std::wstring_view buffer_name) 366 | { 367 | u8 *resource_ptr{}; 368 | Microsoft::WRL::ComPtr buffer_resource{}; 369 | 370 | const D3D12_HEAP_PROPERTIES upload_heap_properties = { 371 | .Type = D3D12_HEAP_TYPE_UPLOAD, 372 | .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, 373 | .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, 374 | .CreationNodeMask = 0u, 375 | .VisibleNodeMask = 0u, 376 | }; 377 | 378 | const D3D12_RESOURCE_DESC buffer_resource_desc = { 379 | .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, 380 | .Alignment = 0u, 381 | .Width = size_in_bytes, 382 | .Height = 1u, 383 | .DepthOrArraySize = 1u, 384 | .MipLevels = 1u, 385 | .Format = DXGI_FORMAT_UNKNOWN, 386 | .SampleDesc = {1u, 0u}, 387 | .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 388 | .Flags = D3D12_RESOURCE_FLAG_NONE, 389 | }; 390 | 391 | throw_if_failed(m_device->CreateCommittedResource( 392 | &upload_heap_properties, D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES | D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, 393 | &buffer_resource_desc, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, nullptr, 394 | IID_PPV_ARGS(&buffer_resource))); 395 | 396 | // Now that a resource is created, copy CPU data to this upload buffer. 397 | const D3D12_RANGE read_range{.Begin = 0u, .End = 0u}; 398 | 399 | throw_if_failed(buffer_resource->Map(0u, &read_range, (void **)&resource_ptr)); 400 | 401 | std::scoped_lock scoped_lock(m_resource_mutex); 402 | 403 | name_d3d12_object(buffer_resource.Get(), buffer_name); 404 | 405 | // Create Constant buffer view. 406 | size_t cbv_index = create_constant_buffer_view(buffer_resource.Get(), size_in_bytes); 407 | 408 | return ConstantBuffer{ 409 | .resource = buffer_resource, 410 | .cbv_index = cbv_index, 411 | .size_in_bytes = size_in_bytes, 412 | .resource_mapped_ptr = resource_ptr, 413 | }; 414 | } 415 | 416 | CommandBuffer Renderer::create_command_buffer(const size_t stride, const size_t max_number_of_elements, 417 | const std::wstring_view buffer_name) 418 | { 419 | // Note that counter offset must be multiple of d3d12 uav counter placement alignment. 420 | size_t counter_offset = 421 | round_up_to_multiple(stride * max_number_of_elements, D3D12_UAV_COUNTER_PLACEMENT_ALIGNMENT); 422 | const size_t size_in_bytes = counter_offset + 4u; 423 | 424 | u8 *resource_ptr{}; 425 | Microsoft::WRL::ComPtr buffer_resource{}; 426 | Microsoft::WRL::ComPtr intermediate_buffer_resource{}; 427 | Microsoft::WRL::ComPtr zeroed_counter_buffer_resource{}; 428 | 429 | // First, create a upload buffer (that is placed in memory accesible by both GPU and CPU). 430 | // Then create a GPU only buffer, and copy data from the previous buffer to GPU only one. 431 | const D3D12_HEAP_PROPERTIES upload_heap_properties = { 432 | .Type = D3D12_HEAP_TYPE_UPLOAD, 433 | .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, 434 | .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, 435 | .CreationNodeMask = 0u, 436 | .VisibleNodeMask = 0u, 437 | }; 438 | 439 | const D3D12_RESOURCE_DESC upload_buffer_resource_desc = { 440 | .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, 441 | .Width = size_in_bytes, 442 | .Height = 1u, 443 | .DepthOrArraySize = 1u, 444 | .MipLevels = 1u, 445 | .Format = DXGI_FORMAT_UNKNOWN, 446 | .SampleDesc = {1u, 0u}, 447 | .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 448 | .Flags = D3D12_RESOURCE_FLAG_NONE, 449 | }; 450 | 451 | throw_if_failed(m_device->CreateCommittedResource( 452 | &upload_heap_properties, D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES | D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, 453 | &upload_buffer_resource_desc, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, nullptr, 454 | IID_PPV_ARGS(&intermediate_buffer_resource))); 455 | 456 | // Now that a resource is created, copy CPU data to this upload buffer. 457 | const D3D12_RANGE read_range{.Begin = 0u, .End = 0u}; 458 | 459 | throw_if_failed(intermediate_buffer_resource->Map(0u, &read_range, (void **)&resource_ptr)); 460 | 461 | // Create a small resource that only has a single uint -> whose value is always zero. 462 | // This is used each frame to reset the counter value to 0 for the uav. 463 | { 464 | 465 | const D3D12_RESOURCE_DESC zeroed_counter_buffer_resource_desc = { 466 | .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, 467 | .Width = 4u, 468 | .Height = 1u, 469 | .DepthOrArraySize = 1u, 470 | .MipLevels = 1u, 471 | .Format = DXGI_FORMAT_UNKNOWN, 472 | .SampleDesc = {1u, 0u}, 473 | .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 474 | .Flags = D3D12_RESOURCE_FLAG_NONE, 475 | }; 476 | 477 | u8 *zeroed_counter_buffer_ptr = nullptr; 478 | throw_if_failed(m_device->CreateCommittedResource( 479 | &upload_heap_properties, D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES | D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, 480 | &upload_buffer_resource_desc, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, nullptr, 481 | IID_PPV_ARGS(&zeroed_counter_buffer_resource))); 482 | 483 | throw_if_failed(zeroed_counter_buffer_resource->Map(0u, &read_range, (void **)&zeroed_counter_buffer_ptr)); 484 | u32 zero = 0u; 485 | memcpy(zeroed_counter_buffer_ptr, &zero, sizeof(u32)); 486 | zeroed_counter_buffer_resource->Unmap(0u, nullptr); 487 | } 488 | 489 | const D3D12_RESOURCE_DESC buffer_resource_desc = { 490 | .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, 491 | .Width = size_in_bytes, 492 | .Height = 1u, 493 | .DepthOrArraySize = 1u, 494 | .MipLevels = 1u, 495 | .Format = DXGI_FORMAT_UNKNOWN, 496 | .SampleDesc = {1u, 0u}, 497 | .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, 498 | .Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, 499 | }; 500 | 501 | // Create the final resource and transfer the data from upload buffer to the final buffer. 502 | // The heap type is : Default (no CPU access). 503 | const D3D12_HEAP_PROPERTIES default_heap_properties = { 504 | .Type = D3D12_HEAP_TYPE_DEFAULT, 505 | .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, 506 | .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, 507 | .CreationNodeMask = 0u, 508 | .VisibleNodeMask = 0u, 509 | }; 510 | 511 | throw_if_failed(m_device->CreateCommittedResource( 512 | &default_heap_properties, D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES | D3D12_HEAP_FLAG_CREATE_NOT_ZEROED, 513 | &buffer_resource_desc, D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&buffer_resource))); 514 | 515 | name_d3d12_object(buffer_resource.Get(), buffer_name); 516 | name_d3d12_object(intermediate_buffer_resource.Get(), std::wstring(buffer_name) + std::wstring(L" [intermediate]")); 517 | 518 | // Create the SRV. 519 | const size_t upload_resource_srv_index = 520 | create_shader_resource_view(intermediate_buffer_resource.Get(), stride, max_number_of_elements); 521 | 522 | // Create the UAV. 523 | const size_t default_resource_uav_index = 524 | create_unordered_access_view(buffer_resource.Get(), stride, max_number_of_elements, true, counter_offset); 525 | 526 | return CommandBuffer{ 527 | .default_resource = buffer_resource, 528 | .upload_resource = intermediate_buffer_resource, 529 | .zeroed_counter_buffer_resource = zeroed_counter_buffer_resource, 530 | .upload_resource_mapped_ptr = resource_ptr, 531 | .upload_resource_srv_index = upload_resource_srv_index, 532 | .default_resource_uav_index = default_resource_uav_index, 533 | .counter_offset = counter_offset, 534 | }; 535 | } 536 | 537 | size_t Renderer::create_constant_buffer_view(ID3D12Resource *const resource, const size_t size) 538 | { 539 | const D3D12_CPU_DESCRIPTOR_HANDLE handle = m_cbv_srv_uav_descriptor_heap.current_cpu_descriptor_handle; 540 | 541 | const D3D12_CONSTANT_BUFFER_VIEW_DESC cbv_desc = { 542 | .BufferLocation = resource->GetGPUVirtualAddress(), 543 | .SizeInBytes = static_cast(size), 544 | }; 545 | 546 | m_device->CreateConstantBufferView(&cbv_desc, handle); 547 | 548 | const size_t cbv_index = m_cbv_srv_uav_descriptor_heap.current_descriptor_handle_index; 549 | 550 | m_cbv_srv_uav_descriptor_heap.offset_current_descriptor_handles(); 551 | 552 | return cbv_index; 553 | } 554 | 555 | size_t Renderer::create_shader_resource_view(ID3D12Resource *const resource, const size_t stride, 556 | const size_t num_elements) 557 | { 558 | const D3D12_CPU_DESCRIPTOR_HANDLE handle = m_cbv_srv_uav_descriptor_heap.current_cpu_descriptor_handle; 559 | 560 | const D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = { 561 | .Format = DXGI_FORMAT_UNKNOWN, 562 | .ViewDimension = D3D12_SRV_DIMENSION_BUFFER, 563 | .Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, 564 | .Buffer{ 565 | .FirstElement = 0u, 566 | .NumElements = static_cast(num_elements), 567 | .StructureByteStride = static_cast(stride), 568 | }, 569 | }; 570 | 571 | m_device->CreateShaderResourceView(resource, &srv_desc, handle); 572 | 573 | const size_t srv_index = m_cbv_srv_uav_descriptor_heap.current_descriptor_handle_index; 574 | 575 | m_cbv_srv_uav_descriptor_heap.offset_current_descriptor_handles(); 576 | 577 | return srv_index; 578 | } 579 | 580 | size_t Renderer::create_unordered_access_view(ID3D12Resource *const resource, const size_t stride, 581 | const size_t num_elements, const bool use_counter, 582 | const size_t counter_offset) 583 | { 584 | const D3D12_CPU_DESCRIPTOR_HANDLE handle = m_cbv_srv_uav_descriptor_heap.current_cpu_descriptor_handle; 585 | 586 | D3D12_UNORDERED_ACCESS_VIEW_DESC uav_desc = { 587 | .Format = DXGI_FORMAT_UNKNOWN, 588 | .ViewDimension = D3D12_UAV_DIMENSION_BUFFER, 589 | .Buffer{ 590 | .FirstElement = 0u, 591 | .NumElements = static_cast(num_elements), 592 | .StructureByteStride = static_cast(stride), 593 | .CounterOffsetInBytes = 0u, 594 | .Flags = D3D12_BUFFER_UAV_FLAGS::D3D12_BUFFER_UAV_FLAG_NONE, 595 | }, 596 | }; 597 | 598 | if (!use_counter) 599 | { 600 | m_device->CreateUnorderedAccessView(resource, nullptr, &uav_desc, handle); 601 | } 602 | else 603 | { 604 | uav_desc.Buffer.CounterOffsetInBytes = counter_offset; 605 | m_device->CreateUnorderedAccessView(resource, resource, &uav_desc, handle); 606 | } 607 | 608 | const size_t uav_index = m_cbv_srv_uav_descriptor_heap.current_descriptor_handle_index; 609 | 610 | m_cbv_srv_uav_descriptor_heap.offset_current_descriptor_handles(); 611 | 612 | return uav_index; 613 | } 614 | 615 | void Renderer::DirectCommandQueue::create(ID3D12Device *const device) 616 | { 617 | const D3D12_COMMAND_QUEUE_DESC command_queue_desc = { 618 | .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, 619 | .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, 620 | .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, 621 | .NodeMask = 0u, 622 | }; 623 | throw_if_failed(device->CreateCommandQueue(&command_queue_desc, IID_PPV_ARGS(&m_command_queue))); 624 | 625 | // Create the command allocator (the underlying allocation where gpu commands will be stored after being 626 | // recorded by command list). Each frame has its own command allocator. 627 | for (u8 i = 0; i < NUMBER_OF_BACKBUFFERS; i++) 628 | { 629 | throw_if_failed( 630 | device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_command_allocators[i]))); 631 | } 632 | 633 | // Create the graphics command list. 634 | throw_if_failed(device->CreateCommandList(0u, D3D12_COMMAND_LIST_TYPE_DIRECT, m_command_allocators[0].Get(), 635 | nullptr, IID_PPV_ARGS(&m_command_list))); 636 | 637 | // Create a fence for CPU GPU synchronization. 638 | throw_if_failed(device->CreateFence(0u, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence))); 639 | } 640 | 641 | void Renderer::DirectCommandQueue::reset(const u8 index) const 642 | { 643 | const auto &allocator = m_command_allocators[index]; 644 | const auto &command_list = m_command_list; 645 | 646 | // Reset command allocator and command list. 647 | throw_if_failed(allocator->Reset()); 648 | throw_if_failed(command_list->Reset(allocator.Get(), nullptr)); 649 | } 650 | 651 | void Renderer::DirectCommandQueue::execute_command_list() const 652 | { 653 | 654 | throw_if_failed(m_command_list->Close()); 655 | 656 | ID3D12CommandList *const command_lists_to_execute[1] = {m_command_list.Get()}; 657 | 658 | m_command_queue->ExecuteCommandLists(1u, command_lists_to_execute); 659 | } 660 | 661 | void Renderer::DirectCommandQueue::wait_for_fence_value_at_index(const u8 index) 662 | { 663 | if (m_fence->GetCompletedValue() >= m_frame_fence_values[index]) 664 | { 665 | return; 666 | } 667 | else 668 | { 669 | throw_if_failed(m_fence->SetEventOnCompletion(m_frame_fence_values[index], nullptr)); 670 | } 671 | } 672 | 673 | void Renderer::DirectCommandQueue::signal_fence(const u8 index) 674 | { 675 | throw_if_failed(m_command_queue->Signal(m_fence.Get(), ++m_monotonic_fence_value)); 676 | m_frame_fence_values[index] = m_monotonic_fence_value; 677 | } 678 | 679 | void Renderer::DirectCommandQueue::flush_queue() 680 | { 681 | 682 | signal_fence(0); 683 | 684 | for (u32 i = 0; i < NUMBER_OF_BACKBUFFERS; i++) 685 | { 686 | m_frame_fence_values[i] = m_monotonic_fence_value; 687 | } 688 | 689 | wait_for_fence_value_at_index(0); 690 | } 691 | 692 | void Renderer::CopyCommandQueue::create(ID3D12Device *const device) 693 | { 694 | const D3D12_COMMAND_QUEUE_DESC command_queue_desc = { 695 | .Type = D3D12_COMMAND_LIST_TYPE_COPY, 696 | .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, 697 | .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, 698 | .NodeMask = 0u, 699 | }; 700 | throw_if_failed(device->CreateCommandQueue(&command_queue_desc, IID_PPV_ARGS(&m_command_queue))); 701 | 702 | // Create a fence for CPU GPU synchronization. 703 | throw_if_failed(device->CreateFence(0u, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence))); 704 | } 705 | 706 | Renderer::CopyCommandQueue::CommandAllocatorListPair Renderer::CopyCommandQueue::get_command_allocator_list_pair( 707 | ID3D12Device *const device) 708 | { 709 | if (!m_command_allocator_list_queue.empty() && 710 | m_command_allocator_list_queue.front().m_fence_value <= m_fence->GetCompletedValue()) 711 | { 712 | const auto front = m_command_allocator_list_queue.front(); 713 | m_command_allocator_list_queue.pop(); 714 | 715 | // Reset list and allocator. 716 | throw_if_failed(front.m_command_allocator->Reset()); 717 | throw_if_failed(front.m_command_list->Reset(front.m_command_allocator.Get(), nullptr)); 718 | 719 | return front; 720 | } 721 | else 722 | { 723 | CommandAllocatorListPair command_allocator_list_pair{}; 724 | throw_if_failed(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_COPY, 725 | IID_PPV_ARGS(&command_allocator_list_pair.m_command_allocator))); 726 | throw_if_failed(device->CreateCommandList(0u, D3D12_COMMAND_LIST_TYPE_COPY, 727 | command_allocator_list_pair.m_command_allocator.Get(), nullptr, 728 | IID_PPV_ARGS(&command_allocator_list_pair.m_command_list))); 729 | 730 | return command_allocator_list_pair; 731 | } 732 | } 733 | 734 | void Renderer::CopyCommandQueue::execute_command_list(CommandAllocatorListPair &&alloc_list_pair) 735 | { 736 | 737 | throw_if_failed(alloc_list_pair.m_command_list->Close()); 738 | 739 | ID3D12CommandList *const command_lists_to_execute[1] = {alloc_list_pair.m_command_list.Get()}; 740 | 741 | m_command_queue->ExecuteCommandLists(1u, command_lists_to_execute); 742 | 743 | throw_if_failed(m_command_queue->Signal(m_fence.Get(), ++m_monotonic_fence_value)); 744 | 745 | alloc_list_pair.m_fence_value = m_monotonic_fence_value; 746 | 747 | m_command_allocator_list_queue.push(alloc_list_pair); 748 | } 749 | 750 | void Renderer::CopyCommandQueue::flush_queue() 751 | { 752 | throw_if_failed(m_command_queue->Signal(m_fence.Get(), ++m_monotonic_fence_value)); 753 | throw_if_failed(m_fence->SetEventOnCompletion(m_monotonic_fence_value, nullptr)); 754 | } 755 | -------------------------------------------------------------------------------- /src/shader_compiler.cpp: -------------------------------------------------------------------------------- 1 | #include "voxel-engine/shader_compiler.hpp" 2 | 3 | namespace ShaderCompiler 4 | { 5 | // Core DXC objects. 6 | Microsoft::WRL::ComPtr g_utils{}; 7 | Microsoft::WRL::ComPtr g_compiler{}; 8 | Microsoft::WRL::ComPtr g_include_handler{}; 9 | 10 | IDxcBlob *compile(const wchar_t *const file_path, const wchar_t *const entry_point, const wchar_t *const target) 11 | { 12 | // Check if the compiler object has been created. 13 | if (!g_utils) 14 | { 15 | throw_if_failed(DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&g_utils))); 16 | throw_if_failed(DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&g_compiler))); 17 | g_utils->CreateDefaultIncludeHandler(&g_include_handler); 18 | } 19 | 20 | std::vector compiler_arguments = { 21 | file_path, L"-E", entry_point, L"-T", target, DXC_ARG_PACK_MATRIX_ROW_MAJOR, DXC_ARG_WARNINGS_ARE_ERRORS}; 22 | if constexpr (VX_DEBUG_MODE) 23 | { 24 | compiler_arguments.push_back(DXC_ARG_DEBUG); 25 | } 26 | else 27 | { 28 | compiler_arguments.emplace_back(DXC_ARG_OPTIMIZATION_LEVEL3); 29 | } 30 | 31 | // Open the source file. 32 | Microsoft::WRL::ComPtr source{}; 33 | throw_if_failed(g_utils->LoadFile(file_path, nullptr, &source)); 34 | const DxcBuffer source_buffer = { 35 | .Ptr = source->GetBufferPointer(), 36 | .Size = source->GetBufferSize(), 37 | .Encoding = DXC_CP_ACP, 38 | }; 39 | 40 | // Compile the shader. 41 | Microsoft::WRL::ComPtr results{}; 42 | throw_if_failed(g_compiler->Compile(&source_buffer, compiler_arguments.data(), 43 | static_cast(compiler_arguments.size()), g_include_handler.Get(), 44 | IID_PPV_ARGS(&results))); 45 | 46 | // Check for errors. 47 | Microsoft::WRL::ComPtr error_blob{}; 48 | throw_if_failed(results->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&error_blob), nullptr)); 49 | 50 | if (error_blob && error_blob->GetStringLength() > 0) 51 | { 52 | wprintf(L"Shader : %s has warnings and errors:\n%S\n", file_path, error_blob->GetStringPointer()); 53 | } 54 | 55 | // Get the shader object and return. 56 | IDxcBlob *shader_blob{nullptr}; 57 | throw_if_failed(results->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&shader_blob), nullptr)); 58 | return shader_blob; 59 | } 60 | } // namespace ShaderCompiler -------------------------------------------------------------------------------- /src/timer.cpp: -------------------------------------------------------------------------------- 1 | #include "voxel-engine/timer.hpp" 2 | 3 | Timer::Timer() 4 | { 5 | // Get the performance counter frequency (in seconds). 6 | QueryPerformanceFrequency(&m_performance_frequency); 7 | 8 | m_seconds_per_count = 1.0f / (float)m_performance_frequency.QuadPart; 9 | } 10 | 11 | void Timer::start() 12 | { 13 | QueryPerformanceCounter(&m_start_time); 14 | } 15 | 16 | void Timer::stop() 17 | { 18 | QueryPerformanceCounter(&m_end_time); 19 | } 20 | 21 | float Timer::get_delta_time() const 22 | { 23 | return (m_end_time.QuadPart - m_start_time.QuadPart) * m_seconds_per_count; 24 | } -------------------------------------------------------------------------------- /src/voxel.cpp: -------------------------------------------------------------------------------- 1 | #include "voxel-engine/voxel.hpp" 2 | 3 | #include "shaders/interop/render_resources.hlsli" 4 | 5 | Chunk::Chunk() 6 | { 7 | m_voxels = new Voxel[NUMBER_OF_VOXELS]; 8 | } 9 | Chunk::Chunk(Chunk &&other) noexcept : m_voxels(std::move(other.m_voxels)), m_chunk_index(other.m_chunk_index) 10 | 11 | { 12 | other.m_voxels = nullptr; 13 | } 14 | 15 | Chunk &Chunk::operator=(Chunk &&other) noexcept 16 | { 17 | this->m_voxels = std::move(other.m_voxels); 18 | this->m_chunk_index = other.m_chunk_index; 19 | 20 | other.m_voxels = nullptr; 21 | 22 | return *this; 23 | } 24 | 25 | Chunk::~Chunk() 26 | { 27 | if (m_voxels) 28 | { 29 | delete[] m_voxels; 30 | } 31 | } 32 | 33 | ChunkManager::ChunkManager(Renderer &renderer) 34 | { 35 | // Create the position buffer. 36 | std::vector chunk_position_data{}; 37 | 38 | static constexpr std::array chunk_voxel_vertices{ 39 | DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f), 40 | DirectX::XMFLOAT3(0.0f, Voxel::EDGE_LENGTH, 0.0f), 41 | DirectX::XMFLOAT3(Voxel::EDGE_LENGTH, Voxel::EDGE_LENGTH, 0.0f), 42 | DirectX::XMFLOAT3(Voxel::EDGE_LENGTH, 0.0f, 0.0f), 43 | DirectX::XMFLOAT3(0.0f, 0.0f, Voxel::EDGE_LENGTH), 44 | DirectX::XMFLOAT3(0.0f, Voxel::EDGE_LENGTH, Voxel::EDGE_LENGTH), 45 | DirectX::XMFLOAT3(Voxel::EDGE_LENGTH, Voxel::EDGE_LENGTH, Voxel::EDGE_LENGTH), 46 | DirectX::XMFLOAT3(Voxel::EDGE_LENGTH, 0.0f, Voxel::EDGE_LENGTH), 47 | }; 48 | 49 | for (size_t i = 0; i < Chunk::NUMBER_OF_VOXELS; i++) 50 | { 51 | const DirectX::XMUINT3 index_3d = convert_to_3d(i, Chunk::NUMBER_OF_VOXELS_PER_DIMENSION); 52 | const DirectX::XMFLOAT3 offset = DirectX::XMFLOAT3( 53 | index_3d.x * Voxel::EDGE_LENGTH, index_3d.y * Voxel::EDGE_LENGTH, index_3d.z * Voxel::EDGE_LENGTH); 54 | 55 | for (const auto &vertex : chunk_voxel_vertices) 56 | { 57 | chunk_position_data.push_back({vertex.x + offset.x, vertex.y + offset.y, vertex.z + offset.z}); 58 | } 59 | } 60 | 61 | const auto result = renderer.create_structured_buffer(chunk_position_data.data(), sizeof(DirectX::XMFLOAT3), 62 | chunk_position_data.size(), L"shared chunk position buffer"); 63 | 64 | renderer.m_copy_queue.flush_queue(); 65 | m_shared_chunk_position_buffer = result.structured_buffer; 66 | 67 | m_thread_pool.reset(6); 68 | } 69 | 70 | ChunkManager::SetupChunkData ChunkManager::internal_mt_setup_chunk(Renderer &renderer, const size_t index) 71 | { 72 | SetupChunkData setup_chunk_data{}; 73 | 74 | // Iterate over each voxel in chunk and setup the chunk index and color buffer. 75 | std::vector chunk_index_data{}; 76 | std::vector color_data{}; 77 | 78 | std::random_device random_device{}; 79 | std::mt19937 engine(random_device()); 80 | std::uniform_real_distribution dist(0.0f, 1.0f); 81 | 82 | // note(rtarun9) : Only for demo purposes. 83 | const DirectX::XMFLOAT3 chunk_color = { 84 | dist(engine), 85 | dist(engine), 86 | dist(engine), 87 | }; 88 | 89 | for (size_t i = 0; i < Chunk::NUMBER_OF_VOXELS; i++) 90 | { 91 | if (!setup_chunk_data.m_chunk.m_voxels[i].m_active) 92 | { 93 | continue; 94 | } 95 | 96 | const auto voxel_color = chunk_color; 97 | 98 | const DirectX::XMUINT3 index_3d = convert_to_3d(i, Chunk::NUMBER_OF_VOXELS_PER_DIMENSION); 99 | const u16 shared_index_buffer_offset = i * 8u; 100 | 101 | // Check if there is a voxel that blocks the front face of current voxel. 102 | { 103 | 104 | const bool is_front_face_covered = 105 | (index_3d.z != 0 && setup_chunk_data.m_chunk 106 | .m_voxels[convert_to_1d({index_3d.x, index_3d.y, index_3d.z - 1}, 107 | Chunk::NUMBER_OF_VOXELS_PER_DIMENSION)] 108 | .m_active); 109 | 110 | if (!is_front_face_covered) 111 | { 112 | color_data.emplace_back(voxel_color); 113 | for (const auto &vertex_index : {0u, 1u, 2u, 0u, 2u, 3u}) 114 | { 115 | chunk_index_data.push_back(vertex_index + shared_index_buffer_offset); 116 | } 117 | } 118 | } 119 | 120 | // Check if there is a voxel that blocks the back face of current voxel. 121 | { 122 | 123 | const bool is_back_face_covered = (index_3d.z != Chunk::NUMBER_OF_VOXELS_PER_DIMENSION - 1u && 124 | setup_chunk_data.m_chunk 125 | .m_voxels[convert_to_1d({index_3d.x, index_3d.y, index_3d.z + 1}, 126 | Chunk::NUMBER_OF_VOXELS_PER_DIMENSION)] 127 | .m_active); 128 | 129 | color_data.emplace_back(voxel_color); 130 | if (!is_back_face_covered) 131 | { 132 | for (const auto &vertex_index : {4u, 6u, 5u, 4u, 7u, 6u}) 133 | { 134 | chunk_index_data.push_back(vertex_index + shared_index_buffer_offset); 135 | } 136 | } 137 | } 138 | 139 | // Check if there is a voxel that blocks the left hand side face of current voxel. 140 | { 141 | 142 | const bool is_left_face_covered = 143 | (index_3d.x != 0u && setup_chunk_data.m_chunk 144 | .m_voxels[convert_to_1d({index_3d.x - 1, index_3d.y, index_3d.z}, 145 | Chunk::NUMBER_OF_VOXELS_PER_DIMENSION)] 146 | .m_active); 147 | 148 | color_data.emplace_back(voxel_color); 149 | if (!is_left_face_covered) 150 | { 151 | for (const auto &vertex_index : {4u, 5u, 1u, 4u, 1u, 0u}) 152 | { 153 | chunk_index_data.push_back(vertex_index + shared_index_buffer_offset); 154 | } 155 | } 156 | } 157 | 158 | // Check if there is a voxel that blocks the right hand side face of current voxel. 159 | { 160 | 161 | const bool is_right_face_covered = (index_3d.x != Chunk::NUMBER_OF_VOXELS_PER_DIMENSION - 1u && 162 | setup_chunk_data.m_chunk 163 | .m_voxels[convert_to_1d({index_3d.x + 1, index_3d.y, index_3d.z}, 164 | Chunk::NUMBER_OF_VOXELS_PER_DIMENSION)] 165 | .m_active); 166 | 167 | if (!is_right_face_covered) 168 | { 169 | color_data.emplace_back(voxel_color); 170 | for (const auto &vertex_index : {3u, 2u, 6u, 3u, 6u, 7u}) 171 | { 172 | chunk_index_data.push_back(vertex_index + shared_index_buffer_offset); 173 | } 174 | } 175 | } 176 | 177 | // Check if there is a voxel that blocks the top side face of current voxel. 178 | { 179 | 180 | const bool is_top_face_covered = (index_3d.y != Chunk::NUMBER_OF_VOXELS_PER_DIMENSION - 1 && 181 | setup_chunk_data.m_chunk 182 | .m_voxels[convert_to_1d({index_3d.x, index_3d.y + 1, index_3d.z}, 183 | Chunk::NUMBER_OF_VOXELS_PER_DIMENSION)] 184 | .m_active); 185 | 186 | if (!is_top_face_covered) 187 | { 188 | color_data.emplace_back(voxel_color); 189 | for (const auto &vertex_index : {1u, 5u, 6u, 1u, 6u, 2u}) 190 | { 191 | chunk_index_data.push_back(vertex_index + shared_index_buffer_offset); 192 | } 193 | } 194 | } 195 | 196 | // Check if there is a voxel that blocks the bottom side face of current voxel. 197 | { 198 | 199 | const bool is_bottom_face_covered = 200 | (index_3d.y != 0u && setup_chunk_data.m_chunk 201 | .m_voxels[convert_to_1d({index_3d.x, index_3d.y - 1, index_3d.z}, 202 | Chunk::NUMBER_OF_VOXELS_PER_DIMENSION)] 203 | .m_active); 204 | 205 | if (!is_bottom_face_covered) 206 | { 207 | color_data.emplace_back(voxel_color); 208 | for (const auto &vertex_index : {4u, 0u, 3u, 4u, 3u, 7u}) 209 | { 210 | chunk_index_data.push_back(vertex_index + shared_index_buffer_offset); 211 | } 212 | } 213 | } 214 | } 215 | 216 | setup_chunk_data.m_chunk_indices_data = std::move(chunk_index_data); 217 | setup_chunk_data.m_chunk_color_data = std::move(color_data); 218 | 219 | if (!setup_chunk_data.m_chunk_indices_data.empty()) 220 | { 221 | 222 | setup_chunk_data.m_chunk_index_buffer = 223 | renderer.create_index_buffer((void *)setup_chunk_data.m_chunk_indices_data.data(), sizeof(u16), 224 | setup_chunk_data.m_chunk_indices_data.size(), 225 | std::wstring(L"Chunk Index buffer : ") + std::to_wstring(index)); 226 | setup_chunk_data.m_chunk_color_buffer = 227 | renderer.create_structured_buffer((void *)setup_chunk_data.m_chunk_color_data.data(), 228 | sizeof(DirectX::XMFLOAT3), setup_chunk_data.m_chunk_color_data.size(), 229 | std::wstring(L"Chunk color buffer : ") + std::to_wstring(index)); 230 | 231 | setup_chunk_data.m_chunk_constant_buffer = renderer.create_constant_buffer<1>( 232 | sizeof(ChunkConstantBuffer), std::wstring(L"Chunk constant buffer : ") + std::to_wstring(index))[0]; 233 | } 234 | 235 | setup_chunk_data.m_chunk.m_chunk_index = index; 236 | return setup_chunk_data; 237 | } 238 | 239 | void ChunkManager::add_chunk_to_setup_stack(const u64 index) 240 | { 241 | if (m_loaded_chunks.contains(index) || m_chunk_indices_that_are_being_setup.contains(index)) 242 | { 243 | return; 244 | } 245 | 246 | m_chunk_indices_that_are_being_setup.insert(index); 247 | m_chunks_to_setup_stack.push(index); 248 | } 249 | 250 | void ChunkManager::create_chunks_from_setup_stack(Renderer &renderer) 251 | { 252 | u64 chunks_that_are_setup = 0u; 253 | while (chunks_that_are_setup++ < ChunkManager::NUMBER_OF_CHUNKS_TO_CREATE_PER_FRAME && 254 | !m_chunks_to_setup_stack.empty()) 255 | { 256 | const size_t top = m_chunks_to_setup_stack.top(); 257 | m_chunks_to_setup_stack.pop(); 258 | 259 | m_setup_chunk_futures_queue.emplace(std::pair{ 260 | renderer.m_copy_queue.m_monotonic_fence_value + 1, 261 | m_thread_pool.submit_task([this, &renderer, top]() { return internal_mt_setup_chunk(renderer, top); })}); 262 | } 263 | } 264 | 265 | void ChunkManager::transfer_chunks_from_setup_to_loaded_state(const u64 current_copy_queue_fence_value) 266 | { 267 | using namespace std::chrono_literals; 268 | 269 | u64 chunks_loaded = 0u; 270 | while (!m_setup_chunk_futures_queue.empty() && chunks_loaded < ChunkManager::NUMBER_OF_CHUNKS_TO_LOAD_PER_FRAME) 271 | { 272 | auto &setup_chunk_data = m_setup_chunk_futures_queue.front(); 273 | 274 | switch (std::future_status status = setup_chunk_data.second.wait_for(0s); status) 275 | { 276 | case std::future_status::timeout: { 277 | 278 | return; 279 | } 280 | break; 281 | 282 | case std::future_status::ready: { 283 | // If this condition is satisfied, the buffers are ready, so chunk is ready to be loaded :) 284 | if (setup_chunk_data.first <= current_copy_queue_fence_value) 285 | { 286 | SetupChunkData chunk_to_load = setup_chunk_data.second.get(); 287 | 288 | const size_t num_vertices = chunk_to_load.m_chunk_indices_data.size(); 289 | const size_t chunk_index = chunk_to_load.m_chunk.m_chunk_index; 290 | 291 | m_chunk_index_buffers[chunk_index] = std::move(chunk_to_load.m_chunk_index_buffer.index_buffer); 292 | m_chunk_color_buffers[chunk_index] = std::move(chunk_to_load.m_chunk_color_buffer.structured_buffer); 293 | m_chunk_constant_buffers[chunk_index] = std::move(chunk_to_load.m_chunk_constant_buffer); 294 | 295 | const DirectX::XMUINT3 chunk_index_3d = 296 | convert_to_3d(chunk_index, ChunkManager::NUMBER_OF_CHUNKS_PER_DIMENSION); 297 | 298 | const DirectX::XMUINT3 chunk_offset = 299 | DirectX::XMUINT3(chunk_index_3d.x * Voxel::EDGE_LENGTH * Chunk::NUMBER_OF_VOXELS_PER_DIMENSION, 300 | chunk_index_3d.y * Voxel::EDGE_LENGTH * Chunk::NUMBER_OF_VOXELS_PER_DIMENSION, 301 | chunk_index_3d.z * Voxel::EDGE_LENGTH * Chunk::NUMBER_OF_VOXELS_PER_DIMENSION); 302 | 303 | const ChunkConstantBuffer chunk_constant_buffer_data = { 304 | .translation_vector = {chunk_offset.x, chunk_offset.y, chunk_offset.z, 0u}, 305 | .position_buffer_index = static_cast(m_shared_chunk_position_buffer.srv_index), 306 | .color_buffer_index = static_cast(m_chunk_color_buffers[chunk_index].srv_index), 307 | }; 308 | 309 | m_chunk_constant_buffers[chunk_index].update(&chunk_constant_buffer_data); 310 | 311 | m_setup_chunk_futures_queue.pop(); 312 | 313 | m_chunk_indices_that_are_being_setup.erase(chunk_index); 314 | m_loaded_chunks[chunk_index] = std::move(chunk_to_load.m_chunk); 315 | } 316 | else 317 | { 318 | return; 319 | } 320 | } 321 | break; 322 | } 323 | 324 | ++chunks_loaded; 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/window.cpp: -------------------------------------------------------------------------------- 1 | #include "voxel-engine/window.hpp" 2 | 3 | #include 4 | extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND window_handle, UINT message, WPARAM w_param, 5 | LPARAM l_param); 6 | 7 | static LRESULT CALLBACK window_proc(HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param) 8 | { 9 | if (ImGui_ImplWin32_WndProcHandler(window_handle, message, w_param, l_param)) 10 | { 11 | return true; 12 | } 13 | 14 | switch (message) 15 | { 16 | // Handle case when the close button / alt f4 is pressed. 17 | case WM_CLOSE: { 18 | DestroyWindow(window_handle); 19 | } 20 | break; 21 | 22 | // Handle case when destroy window is called. 23 | case WM_DESTROY: { 24 | // This adds a WM_QUIT message to the queue. 25 | PostQuitMessage(0); 26 | return 0; 27 | } 28 | break; 29 | 30 | case WM_KEYDOWN: { 31 | if (w_param == VK_ESCAPE) 32 | { 33 | PostQuitMessage(0); 34 | return 0; 35 | } 36 | } 37 | break; 38 | } 39 | 40 | return DefWindowProcA(window_handle, message, w_param, l_param); 41 | } 42 | 43 | Window::Window() 44 | { 45 | // Get screen dimension. 46 | const i32 screen_width = GetSystemMetrics(SM_CXSCREEN); 47 | const i32 screen_height = GetSystemMetrics(SM_CYSCREEN); 48 | 49 | RECT window_rect = { 50 | 0u, 51 | 0u, 52 | screen_width, 53 | screen_height, 54 | }; 55 | 56 | // Calculate required size of window rect based on client rectangle size. 57 | AdjustWindowRect(&window_rect, WS_OVERLAPPEDWINDOW, FALSE); 58 | 59 | m_width = static_cast(window_rect.right - window_rect.left); 60 | m_height = static_cast(window_rect.bottom - window_rect.top); 61 | 62 | // Register the window class. 63 | // This represents a set of common behavious that several windows may have. 64 | const HINSTANCE instance_handle = GetModuleHandle(NULL); 65 | 66 | const WNDCLASSA window_class = { 67 | .lpfnWndProc = window_proc, 68 | .hInstance = instance_handle, 69 | .lpszClassName = WINDOW_CLASS_NAME, 70 | }; 71 | 72 | RegisterClassA(&window_class); 73 | 74 | m_handle = CreateWindowExA(0, WINDOW_CLASS_NAME, "voxel-engine", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 75 | m_width, m_height, NULL, NULL, instance_handle, NULL); 76 | 77 | if (m_handle != 0) 78 | { 79 | ShowWindow(m_handle, SW_SHOW); 80 | } 81 | else 82 | { 83 | printf("Failed to create win32 window."); 84 | exit(EXIT_FAILURE); 85 | } 86 | } 87 | 88 | Window::~Window() 89 | { 90 | UnregisterClassA(WINDOW_CLASS_NAME, GetModuleHandle(NULL)); 91 | } 92 | --------------------------------------------------------------------------------