├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── assets ├── bitmap.frag ├── bitmap.frag.spv ├── bitmap.vert ├── bitmap.vert.spv ├── triangle.frag ├── triangle.frag.spv ├── triangle.vert └── triangle.vert.spv ├── license.md ├── readme.md └── src ├── Renderer.cpp ├── Renderer.h └── XMain.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | # CMake 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | Makefile 7 | cmake_install.cmake 8 | install_manifest.txt 9 | compile_commands.json 10 | CTestTestfile.cmake 11 | *.tcl 12 | *_autogen 13 | *.dir 14 | build/ 15 | visualstudio/ 16 | xcode/ 17 | androidstudio/ 18 | make/ 19 | webassembly/ 20 | 21 | ## Visual Studio 22 | # User-specific files 23 | *.vs 24 | *.vscode 25 | *.sln 26 | *.vcxproj 27 | *.filters 28 | *.suo 29 | *.user 30 | *.userosscache 31 | *.sln.docstates 32 | 33 | 34 | # User-specific files (MonoDevelop/Xamarin Studio) 35 | *.userprefs 36 | 37 | # Build results 38 | [Dd]ebug/ 39 | [Dd]ebugPublic/ 40 | [Rr]elease/ 41 | [Rr]eleases/ 42 | x64/ 43 | x86/ 44 | bld/ 45 | [Bb]in/ 46 | [Oo]bj/ 47 | [Ll]og/ 48 | 49 | # Windows 50 | 51 | *.DS_Store 52 | 53 | # Visual Studio 2015 cache/options directory 54 | .vs/ 55 | # Uncomment if you have tasks that create the project's static files in wwwroot 56 | #wwwroot/ 57 | 58 | # MSTest test Results 59 | [Tt]est[Rr]esult*/ 60 | [Bb]uild[Ll]og.* 61 | 62 | # NUNIT 63 | *.VisualState.xml 64 | TestResult.xml 65 | 66 | # Build Results of an ATL Project 67 | [Dd]ebugPS/ 68 | [Rr]eleasePS/ 69 | dlldata.c 70 | 71 | # Benchmark Results 72 | BenchmarkDotNet.Artifacts/ 73 | 74 | # .NET Core 75 | project.lock.json 76 | project.fragment.lock.json 77 | artifacts/ 78 | **/Properties/launchSettings.json 79 | 80 | *_i.c 81 | *_p.c 82 | *_i.h 83 | *.ilk 84 | *.meta 85 | *.obj 86 | *.pch 87 | *.pdb 88 | *.pgc 89 | *.pgd 90 | *.rsp 91 | *.sbr 92 | *.tlb 93 | *.tli 94 | *.tlh 95 | *.tmp 96 | *.tmp_proj 97 | *.log 98 | *.vspscc 99 | *.vssscc 100 | .builds 101 | *.pidb 102 | *.svclog 103 | *.scc 104 | 105 | # Chutzpah Test files 106 | _Chutzpah* 107 | 108 | # Visual C++ cache files 109 | ipch/ 110 | *.aps 111 | *.ncb 112 | *.opendb 113 | *.opensdf 114 | *.sdf 115 | *.cachefile 116 | *.VC.db 117 | *.VC.VC.opendb 118 | 119 | # Visual Studio profiler 120 | *.psess 121 | *.vsp 122 | *.vspx 123 | *.sap 124 | 125 | # TFS 2012 Local Workspace 126 | $tf/ 127 | 128 | # Guidance Automation Toolkit 129 | *.gpState 130 | 131 | # ReSharper is a .NET coding add-in 132 | _ReSharper*/ 133 | *.[Rr]e[Ss]harper 134 | *.DotSettings.user 135 | 136 | # JustCode is a .NET coding add-in 137 | .JustCode 138 | 139 | # TeamCity is a build add-in 140 | _TeamCity* 141 | 142 | # DotCover is a Code Coverage Tool 143 | *.dotCover 144 | 145 | # Visual Studio code coverage results 146 | *.coverage 147 | *.coveragexml 148 | 149 | # NCrunch 150 | _NCrunch_* 151 | .*crunch*.local.xml 152 | nCrunchTemp_* 153 | 154 | # MightyMoose 155 | *.mm.* 156 | AutoTest.Net/ 157 | 158 | # Web workbench (sass) 159 | .sass-cache/ 160 | 161 | # Installshield output folder 162 | [Ee]xpress/ 163 | 164 | # DocProject is a documentation generator add-in 165 | DocProject/buildhelp/ 166 | DocProject/Help/*.HxT 167 | DocProject/Help/*.HxC 168 | DocProject/Help/*.hhc 169 | DocProject/Help/*.hhk 170 | DocProject/Help/*.hhp 171 | DocProject/Help/Html2 172 | DocProject/Help/html 173 | 174 | # Click-Once directory 175 | publish/ 176 | 177 | # Publish Web Output 178 | *.[Pp]ublish.xml 179 | *.azurePubxml 180 | # Note: Comment the next line if you want to checkin your web deploy settings, 181 | # but database connection strings (with potential passwords) will be unencrypted 182 | *.pubxml 183 | *.publishproj 184 | 185 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 186 | # checkin your Azure Web App publish settings, but sensitive information contained 187 | # in these scripts will be unencrypted 188 | PublishScripts/ 189 | 190 | # NuGet Packages 191 | *.nupkg 192 | # The packages folder can be ignored because of Package Restore 193 | **/packages/* 194 | # except build/, which is used as an MSBuild target. 195 | !**/packages/build/ 196 | # Uncomment if necessary however generally it will be regenerated when needed 197 | #!**/packages/repositories.config 198 | # NuGet v3's project.json files produces more ignorable files 199 | *.nuget.props 200 | *.nuget.targets 201 | 202 | # Microsoft Azure Build Output 203 | csx/ 204 | *.build.csdef 205 | 206 | # Microsoft Azure Emulator 207 | ecf/ 208 | rcf/ 209 | 210 | # Windows Store app package directories and files 211 | AppPackages/ 212 | BundleArtifacts/ 213 | Package.StoreAssociation.xml 214 | _pkginfo.txt 215 | *.appx 216 | 217 | # Visual Studio cache files 218 | # files ending in .cache can be ignored 219 | *.[Cc]ache 220 | # but keep track of directories ending in .cache 221 | !*.[Cc]ache/ 222 | 223 | # Others 224 | ClientBin/ 225 | ~$* 226 | *~ 227 | *.dbmdl 228 | *.dbproj.schemaview 229 | *.jfm 230 | *.pfx 231 | *.publishsettings 232 | orleans.codegen.cs 233 | 234 | # Since there are multiple workflows, uncomment next line to ignore bower_components 235 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 236 | #bower_components/ 237 | 238 | # RIA/Silverlight projects 239 | Generated_Code/ 240 | 241 | # Backup & report files from converting an old project file 242 | # to a newer Visual Studio version. Backup files are not needed, 243 | # because we have git 244 | _UpgradeReport_Files/ 245 | Backup*/ 246 | UpgradeLog*.XML 247 | UpgradeLog*.htm 248 | 249 | # SQL Server files 250 | *.mdf 251 | *.ldf 252 | *.ndf 253 | 254 | # Business Intelligence projects 255 | *.rdl.data 256 | *.bim.layout 257 | *.bim_*.settings 258 | 259 | # Microsoft Fakes 260 | FakesAssemblies/ 261 | 262 | # GhostDoc plugin setting file 263 | *.GhostDoc.xml 264 | 265 | # Node.js Tools for Visual Studio 266 | .ntvs_analysis.dat 267 | node_modules/ 268 | 269 | # Typescript v1 declaration files 270 | typings/ 271 | 272 | # Visual Studio 6 build log 273 | *.plg 274 | 275 | # Visual Studio 6 workspace options file 276 | *.opt 277 | 278 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 279 | *.vbw 280 | 281 | # Visual Studio LightSwitch build output 282 | **/*.HTMLClient/GeneratedArtifacts 283 | **/*.DesktopClient/GeneratedArtifacts 284 | **/*.DesktopClient/ModelManifest.xml 285 | **/*.Server/GeneratedArtifacts 286 | **/*.Server/ModelManifest.xml 287 | _Pvt_Extensions 288 | 289 | # Paket dependency manager 290 | .paket/paket.exe 291 | paket-files/ 292 | 293 | # FAKE - F# Make 294 | .fake/ 295 | 296 | # JetBrains Rider 297 | .idea/ 298 | *.sln.iml 299 | 300 | # CodeRush 301 | .cr/ 302 | 303 | # Python Tools for Visual Studio (PTVS) 304 | __pycache__/ 305 | *.pyc 306 | 307 | # Cake - Uncomment if you are using it 308 | # tools/** 309 | # !tools/packages.config 310 | 311 | # Tabs Studio 312 | *.tss 313 | 314 | # Telerik's JustMock configuration file 315 | *.jmconfig 316 | 317 | # BizTalk build output 318 | *.btp.cs 319 | *.btm.cs 320 | *.odx.cs 321 | *.xsd.cs 322 | 323 | # Xcode 324 | # 325 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 326 | 327 | ## Build generated 328 | build/ 329 | DerivedData/ 330 | 331 | ## Various settings 332 | *.pbxuser 333 | !default.pbxuser 334 | *.mode1v3 335 | !default.mode1v3 336 | *.mode2v3 337 | !default.mode2v3 338 | *.perspectivev3 339 | !default.perspectivev3 340 | xcuserdata/ 341 | 342 | ## Other 343 | *.moved-aside 344 | *.xccheckout 345 | *.xcscmblueprint 346 | *.db 347 | 348 | ## Xcode 349 | *.xcworkspace 350 | *.xcodeproj -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/CrossWindow"] 2 | path = external/CrossWindow 3 | url = https://github.com/alaingalvan/CrossWindow.git 4 | [submodule "external/CrossWindow-Graphics"] 5 | path = external/CrossWindow-Graphics 6 | url = https://github.com/alaingalvan/CrossWindow-Graphics.git 7 | [submodule "external/vectormath"] 8 | path = external/vectormath 9 | url = https://github.com/glampert/vectormath.git -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project Info 2 | 3 | cmake_minimum_required(VERSION 3.6 FATAL_ERROR) 4 | cmake_policy(VERSION 3.6) 5 | project(HelloTriangle 6 | VERSION 1.0.0.0 7 | LANGUAGES C CXX 8 | ) 9 | 10 | # ============================================================= 11 | 12 | # CMake Settings 13 | 14 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 15 | set(CMAKE_SUPPRESS_REGENERATION true) 16 | set(DCMAKE_GENERATOR_PLATFORM "x64") 17 | set(CMAKE_CXX_STANDARD 14) 18 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 19 | set(CMAKE_CXX_EXTENSIONS OFF) 20 | SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) 21 | SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin) 22 | SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin) 23 | SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin) 24 | SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin) 25 | SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin) 26 | SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin) 27 | if(NOT CMAKE_DEBUG_POSTFIX) 28 | set(CMAKE_DEBUG_POSTFIX d) 29 | endif() 30 | 31 | # ============================================================= 32 | 33 | # Options 34 | 35 | set(XGFX_API VULKAN CACHE STRING "Which graphics API to use, defaults to AUTO, can be NOOP, VULKAN, OPENGL, DIRECTX12, or METAL.") 36 | set_property( 37 | CACHE 38 | XGFX_API PROPERTY 39 | STRINGS NOOP VULKAN OPENGL DIRECTX12 METAL 40 | ) 41 | 42 | # ============================================================= 43 | 44 | # Dependencies 45 | 46 | # CrossWindow 47 | add_subdirectory(external/crosswindow ${CMAKE_BINARY_DIR}/crosswindow) 48 | set_property(TARGET CrossWindow PROPERTY FOLDER "Dependencies") 49 | 50 | # CrossWindow-Graphics 51 | add_subdirectory(external/crosswindow-graphics ${CMAKE_BINARY_DIR}/crosswindow-graphics) 52 | 53 | # Cross Graphics Dependencies 54 | if(XGFX_API STREQUAL "VULKAN") 55 | find_path(VULKAN_INCLUDE_DIR NAMES vulkan/vulkan.h HINTS 56 | "$ENV{VULKAN_SDK}/include" 57 | "$ENV{VULKAN_SDK}/Include" 58 | "$ENV{VK_SDK_PATH}/Include") 59 | if (CMAKE_SIZEOF_VOID_P EQUAL 8) 60 | find_library(XGFX_LIBRARY 61 | NAMES vulkan-1 vulkan vulkan.1 62 | HINTS 63 | "$ENV{VULKAN_SDK}/lib" 64 | "$ENV{VULKAN_SDK}/Lib" 65 | "$ENV{VULKAN_SDK}/Bin" 66 | "$ENV{VK_SDK_PATH}/Bin") 67 | else() 68 | find_library(XGFX_LIBRARY 69 | NAMES vulkan-1 vulkan vulkan.1 70 | HINTS 71 | "$ENV{VULKAN_SDK}/Lib32" 72 | "$ENV{VULKAN_SDK}/Bin32" 73 | "$ENV{VK_SDK_PATH}/Bin32") 74 | endif() 75 | endif() 76 | 77 | 78 | # ============================================================= 79 | 80 | # Sources 81 | 82 | file(GLOB_RECURSE FILE_SOURCES RELATIVE 83 | ${CMAKE_CURRENT_SOURCE_DIR} 84 | ${CMAKE_CURRENT_SOURCE_DIR}/src/XMain.cpp 85 | ${CMAKE_CURRENT_SOURCE_DIR}/src/${XGFX_API}Renderer.cpp 86 | ${CMAKE_CURRENT_SOURCE_DIR}/src/${XGFX_API}Renderer.mm 87 | ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h 88 | ) 89 | 90 | # Solution Filters 91 | foreach(source IN LISTS FILE_SOURCES) 92 | get_filename_component(source_path "${source}" PATH) 93 | string(REPLACE "/" "\\" source_path_msvc "${source_path}") 94 | string(REPLACE "src" "" source_path_final "${source_path_msvc}") 95 | source_group("${source_path_final}" FILES "${source}") 96 | endforeach() 97 | 98 | # ============================================================= 99 | 100 | # Finalize App 101 | 102 | xwin_add_executable( 103 | ${PROJECT_NAME} 104 | "${FILE_SOURCES}" 105 | ) 106 | 107 | # ============================================================= 108 | 109 | # Finish Dependencies 110 | 111 | target_link_libraries( 112 | ${PROJECT_NAME} 113 | ${XGFX_LIBRARY} 114 | CrossWindowGraphics 115 | CrossWindow 116 | ) 117 | 118 | target_include_directories( 119 | ${PROJECT_NAME} 120 | PUBLIC "external/vectormath" 121 | PUBLIC ${VULKAN_INCLUDE_DIR} 122 | ) 123 | 124 | target_compile_definitions( 125 | ${PROJECT_NAME} 126 | PUBLIC XGFX_${XGFX_API}=1 127 | ) 128 | 129 | # ============================================================= 130 | 131 | # Finish Settings 132 | 133 | # Change output dir to bin 134 | set_target_properties(${PROJECT_NAME} PROPERTIES 135 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 136 | ) 137 | # Change working directory to top dir to access `assets/shaders/` folder 138 | if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 139 | set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/..) 140 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) 141 | endif() -------------------------------------------------------------------------------- /assets/bitmap.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects : enable 4 | #extension GL_ARB_shading_language_420pack : enable 5 | 6 | // Varying 7 | layout (location = 0) in vec3 inColor; 8 | 9 | // Return Output 10 | layout (location = 0) out vec4 outFragColor; 11 | 12 | void main() 13 | { 14 | outFragColor = vec4(inColor, 1.0); 15 | } -------------------------------------------------------------------------------- /assets/bitmap.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alaingalvan/raw-vulkan/837a6fb89d1591e94c6878fa3be3fdd891a61284/assets/bitmap.frag.spv -------------------------------------------------------------------------------- /assets/bitmap.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects : enable 4 | #extension GL_ARB_shading_language_420pack : enable 5 | 6 | // Attributes 7 | layout (location = 0) in vec3 inPos; 8 | layout (location = 1) in vec3 inColor; 9 | 10 | // Varyings 11 | layout (location = 0) out vec3 outColor; 12 | 13 | out gl_PerVertex 14 | { 15 | vec4 gl_Position; 16 | }; 17 | 18 | 19 | void main() 20 | { 21 | outColor = inColor; 22 | gl_Position = .25 * vec4(inPos.xyz, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /assets/bitmap.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alaingalvan/raw-vulkan/837a6fb89d1591e94c6878fa3be3fdd891a61284/assets/bitmap.vert.spv -------------------------------------------------------------------------------- /assets/triangle.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects : enable 4 | #extension GL_ARB_shading_language_420pack : enable 5 | 6 | // Varying 7 | layout (location = 0) in vec3 inColor; 8 | 9 | // Return Output 10 | layout (location = 0) out vec4 outFragColor; 11 | 12 | void main() 13 | { 14 | outFragColor = vec4(inColor, 1.0); 15 | } -------------------------------------------------------------------------------- /assets/triangle.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alaingalvan/raw-vulkan/837a6fb89d1591e94c6878fa3be3fdd891a61284/assets/triangle.frag.spv -------------------------------------------------------------------------------- /assets/triangle.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects : enable 4 | #extension GL_ARB_shading_language_420pack : enable 5 | 6 | // Attributes 7 | layout (location = 0) in vec3 inPos; 8 | layout (location = 1) in vec3 inColor; 9 | 10 | // Varyings 11 | layout (location = 0) out vec3 outColor; 12 | 13 | out gl_PerVertex 14 | { 15 | vec4 gl_Position; 16 | }; 17 | 18 | 19 | void main() 20 | { 21 | outColor = inColor; 22 | gl_Position = .25 * vec4(inPos.xyz, 1.0); 23 | } 24 | -------------------------------------------------------------------------------- /assets/triangle.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alaingalvan/raw-vulkan/837a6fb89d1591e94c6878fa3be3fdd891a61284/assets/triangle.vert.spv -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alain Galvan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Raw Vulkan 2 | 3 | [![cmake-img]][cmake-url] 4 | [![License][license-img]][license-url] 5 | 6 | An example seed project introducing programming a C++ Vulkan application. 7 | 8 | ## Getting Started 9 | 10 | First install [Git](https://git-scm.com/downloads), then open any terminal such as [Hyper](https://hyper.is/) in any folder and type: 11 | 12 | ```bash 13 | # 🐑 Clone the repo 14 | git clone https://github.com/alaingalvan/raw-vulkan --recurse-submodules 15 | 16 | # 💿 go inside the folder 17 | cd raw-vulkan 18 | 19 | # 👯 If you forget to `recurse-submodules` you can always run: 20 | git submodule update --init 21 | 22 | ``` 23 | 24 | ## Examples 25 | 26 | 35 | 36 | ### Hello triangle 37 | 38 | Renders out a triangle to the target OS windowing system and updates it every frame. 39 | 40 | 73 | 74 | [cmake-img]: https://img.shields.io/badge/cmake-3.6-1f9948.svg?style=flat-square 75 | [cmake-url]: https://cmake.org/ 76 | [license-img]: https://img.shields.io/:license-mit-blue.svg?style=flat-square 77 | [license-url]: https://opensource.org/licenses/MIT 78 | -------------------------------------------------------------------------------- /src/Renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "Renderer.h" 2 | 3 | // Vulkan Utils 4 | 5 | void findBestExtensions(const std::vector& installed, const std::vector& wanted, std::vector& out) 6 | { 7 | for (const char* const& w : wanted) { 8 | for (vk::ExtensionProperties const& i : installed) { 9 | if (std::string(i.extensionName).compare(w) == 0) { 10 | out.emplace_back(w); 11 | break; 12 | } 13 | } 14 | } 15 | } 16 | 17 | void findBestLayers(const std::vector& installed, const std::vector& wanted, std::vector& out) 18 | { 19 | for (const char* const& w : wanted) { 20 | for (vk::LayerProperties const& i : installed) { 21 | if (std::string(i.layerName).compare(w) == 0) { 22 | out.emplace_back(w); 23 | break; 24 | } 25 | } 26 | } 27 | } 28 | 29 | uint32_t getQueueIndex(vk::PhysicalDevice& physicalDevice, vk::QueueFlagBits flags) 30 | { 31 | std::vector queueProps = physicalDevice.getQueueFamilyProperties(); 32 | 33 | for (size_t i = 0; i < queueProps.size(); ++i) 34 | { 35 | if (queueProps[i].queueFlags & flags) { 36 | return static_cast(i); 37 | } 38 | } 39 | 40 | // Default queue index 41 | return 0; 42 | } 43 | 44 | uint32_t getMemoryTypeIndex(vk::PhysicalDevice& physicalDevice, uint32_t typeBits, vk::MemoryPropertyFlags properties) 45 | { 46 | auto gpuMemoryProps = physicalDevice.getMemoryProperties(); 47 | for (uint32_t i = 0; i < gpuMemoryProps.memoryTypeCount; i++) 48 | { 49 | if ((typeBits & 1) == 1) 50 | { 51 | if ((gpuMemoryProps.memoryTypes[i].propertyFlags & properties) == properties) 52 | { 53 | return i; 54 | } 55 | } 56 | typeBits >>= 1; 57 | } 58 | return 0; 59 | }; 60 | 61 | 62 | // Renderer 63 | 64 | Renderer::Renderer(xwin::Window& window) 65 | { 66 | initializeAPI(window); 67 | initializeResources(); 68 | setupCommands(); 69 | tStart = std::chrono::high_resolution_clock::now(); 70 | } 71 | 72 | Renderer::~Renderer() 73 | { 74 | mDevice.waitIdle(); 75 | 76 | destroyCommands(); 77 | destroyResources(); 78 | destroyAPI(); 79 | } 80 | 81 | void Renderer::destroyAPI() 82 | { 83 | // Command Pool 84 | mDevice.destroyCommandPool(mCommandPool); 85 | 86 | // Device 87 | mDevice.destroy(); 88 | 89 | // Surface 90 | mInstance.destroySurfaceKHR(mSurface); 91 | 92 | // Instance 93 | mInstance.destroy(); 94 | } 95 | 96 | void Renderer::destroyFrameBuffer() 97 | { 98 | // Depth Attachment 99 | mDevice.freeMemory(mDepthImageMemory); 100 | mDevice.destroyImage(mDepthImage); 101 | if (!mSwapchainBuffers.empty()) 102 | { 103 | mDevice.destroyImageView(mSwapchainBuffers[0].views[1]); 104 | } 105 | 106 | // Image Attachments 107 | for (size_t i = 0; i < mSwapchainBuffers.size(); ++i) 108 | { 109 | mDevice.destroyImageView(mSwapchainBuffers[i].views[0]); 110 | mDevice.destroyFramebuffer(mSwapchainBuffers[i].frameBuffer); 111 | } 112 | } 113 | 114 | void Renderer::destroyCommands() 115 | { 116 | mDevice.freeCommandBuffers(mCommandPool, mCommandBuffers); 117 | } 118 | 119 | void Renderer::initializeAPI(xwin::Window& window) 120 | { 121 | /** 122 | * Initialize the Vulkan API by creating its various API entry points: 123 | */ 124 | 125 | 126 | // 🔍 Find the best Instance Extensions 127 | 128 | std::vector installedExtensions = vk::enumerateInstanceExtensionProperties(); 129 | 130 | std::vector wantedExtensions = 131 | { 132 | VK_KHR_SURFACE_EXTENSION_NAME, 133 | #if defined(VK_USE_PLATFORM_WIN32_KHR) 134 | VK_KHR_WIN32_SURFACE_EXTENSION_NAME 135 | #elif defined(VK_USE_PLATFORM_MACOS_MVK) 136 | VK_MVK_MACOS_SURFACE_EXTENSION_NAME 137 | #elif defined(VK_USE_PLATFORM_XCB_KHR) 138 | VK_KHR_XCB_SURFACE_EXTENSION_NAME 139 | #elif defined(VK_USE_PLATFORM_ANDROID_KHR) 140 | VK_KHR_ANDROID_SURFACE_EXTENSION_NAME 141 | #elif defined(VK_USE_PLATFORM_XLIB_KHR) 142 | VK_KHR_XLIB_SURFACE_EXTENSION_NAME 143 | #elif defined(VK_USE_PLATFORM_XCB_KHR) 144 | VK_KHR_XCB_SURFACE_EXTENSION_NAME 145 | #elif defined(VK_USE_PLATFORM_WAYLAND_KHR) 146 | VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME 147 | #elif defined(VK_USE_PLATFORM_MIR_KHR) || defined(VK_USE_PLATFORM_DISPLAY_KHR) 148 | VK_KHR_DISPLAY_EXTENSION_NAME 149 | #elif defined(VK_USE_PLATFORM_ANDROID_KHR) 150 | VK_KHR_ANDROID_SURFACE_EXTENSION_NAME 151 | #elif defined(VK_USE_PLATFORM_IOS_MVK) 152 | VK_MVK_IOS_SURFACE_EXTENSION_NAME 153 | #endif 154 | }; 155 | 156 | std::vector extensions = {}; 157 | 158 | findBestExtensions(installedExtensions, wantedExtensions, extensions); 159 | 160 | std::vectorwantedLayers = 161 | { 162 | #ifdef _DEBUG 163 | "VK_LAYER_LUNARG_standard_validation" 164 | #endif 165 | }; 166 | 167 | std::vector installedLayers = vk::enumerateInstanceLayerProperties(); 168 | 169 | std::vector layers = {}; 170 | 171 | findBestLayers(installedLayers, wantedLayers, layers); 172 | 173 | // ⚪ Instance 174 | vk::ApplicationInfo appInfo( 175 | "Hello Triangle", 176 | 0, 177 | "HelloTriangleEngine", 178 | 0, 179 | VK_API_VERSION_1_0 180 | ); 181 | 182 | vk::InstanceCreateInfo info( 183 | vk::InstanceCreateFlags(), 184 | &appInfo, 185 | static_cast(layers.size()), 186 | layers.data(), 187 | static_cast(extensions.size()), 188 | extensions.data() 189 | ); 190 | 191 | mInstance = vk::createInstance(info); 192 | 193 | // Physical Device 194 | std::vector physicalDevices = mInstance.enumeratePhysicalDevices(); 195 | mPhysicalDevice = physicalDevices[0]; 196 | 197 | // Queue Family 198 | mQueueFamilyIndex = getQueueIndex(mPhysicalDevice, vk::QueueFlagBits::eGraphics); 199 | 200 | // Surface 201 | mSurface = xgfx::getSurface(&window, mInstance); 202 | if (!mPhysicalDevice.getSurfaceSupportKHR(mQueueFamilyIndex, mSurface)) 203 | { 204 | // Check if queueFamily supports this surface 205 | return; 206 | } 207 | 208 | // Queue Creation 209 | vk::DeviceQueueCreateInfo qcinfo; 210 | qcinfo.setQueueFamilyIndex(mQueueFamilyIndex); 211 | qcinfo.setQueueCount(1); 212 | mQueuePriority = 0.5f; 213 | qcinfo.setPQueuePriorities(&mQueuePriority); 214 | 215 | // Logical Device 216 | std::vector installedDeviceExtensions = mPhysicalDevice.enumerateDeviceExtensionProperties(); 217 | 218 | std::vector wantedDeviceExtensions = 219 | { 220 | VK_KHR_SWAPCHAIN_EXTENSION_NAME 221 | }; 222 | 223 | std::vector deviceExtensions = {}; 224 | 225 | findBestExtensions(installedDeviceExtensions, wantedDeviceExtensions, deviceExtensions); 226 | 227 | vk::DeviceCreateInfo dinfo; 228 | dinfo.setPQueueCreateInfos(&qcinfo); 229 | dinfo.setQueueCreateInfoCount(1); 230 | dinfo.setPpEnabledExtensionNames(deviceExtensions.data()); 231 | dinfo.setEnabledExtensionCount(static_cast(deviceExtensions.size())); 232 | mDevice = mPhysicalDevice.createDevice(dinfo); 233 | 234 | 235 | 236 | // Queue 237 | mQueue = mDevice.getQueue(mQueueFamilyIndex, 0); 238 | 239 | // Command Pool 240 | mCommandPool = mDevice.createCommandPool( 241 | vk::CommandPoolCreateInfo( 242 | vk::CommandPoolCreateFlags(vk::CommandPoolCreateFlagBits::eResetCommandBuffer), 243 | mQueueFamilyIndex 244 | ) 245 | ); 246 | 247 | // Surface Attachement Formats 248 | 249 | std::vector surfaceFormats = mPhysicalDevice.getSurfaceFormatsKHR(mSurface); 250 | 251 | if (surfaceFormats.size() == 1 && surfaceFormats[0].format == vk::Format::eUndefined) 252 | mSurfaceColorFormat = vk::Format::eB8G8R8A8Unorm; 253 | else 254 | mSurfaceColorFormat = surfaceFormats[0].format; 255 | 256 | mSurfaceColorSpace = surfaceFormats[0].colorSpace; 257 | 258 | // Since all depth formats may be optional, we need to find a suitable depth format to use 259 | // Start with the highest precision packed format 260 | std::vector depthFormats = 261 | { 262 | vk::Format::eD32SfloatS8Uint, 263 | vk::Format::eD32Sfloat, 264 | vk::Format::eD24UnormS8Uint, 265 | vk::Format::eD16UnormS8Uint, 266 | vk::Format::eD16Unorm 267 | }; 268 | 269 | for (vk::Format& format : depthFormats) 270 | { 271 | vk::FormatProperties depthFormatProperties = mPhysicalDevice.getFormatProperties(format); 272 | 273 | // Format must support depth stencil attachment for optimal tiling 274 | if (depthFormatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment) 275 | { 276 | mSurfaceDepthFormat = format; 277 | break; 278 | } 279 | } 280 | 281 | //Swapchain 282 | const xwin::WindowDesc wdesc = window.getDesc(); 283 | setupSwapchain(wdesc.width, wdesc.height); 284 | mCurrentBuffer = 0; 285 | 286 | // Command Buffers 287 | createCommands(); 288 | 289 | // Sync 290 | createSynchronization(); 291 | } 292 | 293 | void Renderer::setupSwapchain(unsigned width, unsigned height) 294 | { 295 | // Setup viewports, Vsync 296 | vk::Extent2D swapchainSize = vk::Extent2D(width, height); 297 | 298 | // All framebuffers / attachments will be the same size as the surface 299 | vk::SurfaceCapabilitiesKHR surfaceCapabilities = mPhysicalDevice.getSurfaceCapabilitiesKHR(mSurface); 300 | if (!(surfaceCapabilities.currentExtent.width == -1 || surfaceCapabilities.currentExtent.height == -1)) { 301 | swapchainSize = surfaceCapabilities.currentExtent; 302 | mRenderArea = vk::Rect2D(vk::Offset2D(), swapchainSize); 303 | mViewport = vk::Viewport(0.0f, 0.0f, static_cast(swapchainSize.width), static_cast(swapchainSize.height), 0, 1.0f); 304 | } 305 | 306 | // VSync 307 | std::vector surfacePresentModes = mPhysicalDevice.getSurfacePresentModesKHR(mSurface); 308 | vk::PresentModeKHR presentMode = vk::PresentModeKHR::eImmediate; 309 | 310 | for (vk::PresentModeKHR& pm : surfacePresentModes) { 311 | if (pm == vk::PresentModeKHR::eMailbox) { 312 | presentMode = vk::PresentModeKHR::eMailbox; 313 | break; 314 | } 315 | } 316 | 317 | // Create Swapchain, Images, Frame Buffers 318 | 319 | mDevice.waitIdle(); 320 | vk::SwapchainKHR oldSwapchain = mSwapchain; 321 | 322 | // Some devices can support more than 2 buffers, but during my tests they would crash on fullscreen ~ ag 323 | // Tested on an NVIDIA 1080 and 165 Hz 2K display 324 | uint32_t backbufferCount = clamp(surfaceCapabilities.maxImageCount, 1U, 2U); 325 | 326 | mSwapchain = mDevice.createSwapchainKHR( 327 | vk::SwapchainCreateInfoKHR( 328 | vk::SwapchainCreateFlagsKHR(), 329 | mSurface, 330 | backbufferCount, 331 | mSurfaceColorFormat, 332 | mSurfaceColorSpace, 333 | swapchainSize, 334 | 1, 335 | vk::ImageUsageFlagBits::eColorAttachment, 336 | vk::SharingMode::eExclusive, 337 | 1, 338 | &mQueueFamilyIndex, 339 | vk::SurfaceTransformFlagBitsKHR::eIdentity, 340 | vk::CompositeAlphaFlagBitsKHR::eOpaque, 341 | presentMode, 342 | VK_TRUE, 343 | oldSwapchain 344 | ) 345 | ); 346 | 347 | mSurfaceSize = vk::Extent2D(clamp(swapchainSize.width, 1U, 8192U), clamp(swapchainSize.height, 1U, 8192U)); 348 | mRenderArea = vk::Rect2D(vk::Offset2D(), mSurfaceSize); 349 | mViewport = vk::Viewport(0.0f, 0.0f, static_cast(mSurfaceSize.width), static_cast(mSurfaceSize.height), 0, 1.0f); 350 | 351 | 352 | // Destroy previous swapchain 353 | if (oldSwapchain != vk::SwapchainKHR(nullptr)) 354 | { 355 | mDevice.destroySwapchainKHR(oldSwapchain); 356 | } 357 | 358 | // Resize swapchain buffers for use later 359 | mSwapchainBuffers.resize(backbufferCount); 360 | } 361 | 362 | void Renderer::initFrameBuffer() 363 | { 364 | // Create Depth Image Data 365 | mDepthImage = mDevice.createImage( 366 | vk::ImageCreateInfo( 367 | vk::ImageCreateFlags(), 368 | vk::ImageType::e2D, 369 | mSurfaceDepthFormat, 370 | vk::Extent3D(mSurfaceSize.width, mSurfaceSize.height, 1), 371 | 1U, 372 | 1U, 373 | vk::SampleCountFlagBits::e1, 374 | vk::ImageTiling::eOptimal, 375 | vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransferSrc, 376 | vk::SharingMode::eExclusive, 377 | 1, 378 | &mQueueFamilyIndex, 379 | vk::ImageLayout::eUndefined 380 | ) 381 | ); 382 | 383 | vk::MemoryRequirements depthMemoryReq = mDevice.getImageMemoryRequirements(mDepthImage); 384 | 385 | // Search through GPU memory properies to see if this can be device local. 386 | 387 | mDepthImageMemory = mDevice.allocateMemory( 388 | vk::MemoryAllocateInfo( 389 | depthMemoryReq.size, 390 | getMemoryTypeIndex(mPhysicalDevice, depthMemoryReq.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal) 391 | ) 392 | ); 393 | 394 | mDevice.bindImageMemory( 395 | mDepthImage, 396 | mDepthImageMemory, 397 | 0 398 | ); 399 | 400 | vk::ImageView depthImageView = mDevice.createImageView( 401 | vk::ImageViewCreateInfo( 402 | vk::ImageViewCreateFlags(), 403 | mDepthImage, 404 | vk::ImageViewType::e2D, 405 | mSurfaceDepthFormat, 406 | vk::ComponentMapping(), 407 | vk::ImageSubresourceRange( 408 | vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil, 409 | 0, 410 | 1, 411 | 0, 412 | 1 413 | ) 414 | ) 415 | ); 416 | 417 | std::vector swapchainImages = mDevice.getSwapchainImagesKHR(mSwapchain); 418 | 419 | for (size_t i = 0; i < swapchainImages.size(); i++) 420 | { 421 | mSwapchainBuffers[i].image = swapchainImages[i]; 422 | 423 | // Color 424 | mSwapchainBuffers[i].views[0] = 425 | mDevice.createImageView( 426 | vk::ImageViewCreateInfo( 427 | vk::ImageViewCreateFlags(), 428 | swapchainImages[i], 429 | vk::ImageViewType::e2D, 430 | mSurfaceColorFormat, 431 | vk::ComponentMapping(), 432 | vk::ImageSubresourceRange( 433 | vk::ImageAspectFlagBits::eColor, 434 | 0, 435 | 1, 436 | 0, 437 | 1 438 | ) 439 | ) 440 | ); 441 | 442 | // Depth 443 | mSwapchainBuffers[i].views[1] = depthImageView; 444 | 445 | mSwapchainBuffers[i].frameBuffer = mDevice.createFramebuffer( 446 | vk::FramebufferCreateInfo( 447 | vk::FramebufferCreateFlags(), 448 | mRenderPass, 449 | static_cast(mSwapchainBuffers[i].views.size()), 450 | mSwapchainBuffers[i].views.data(), 451 | mSurfaceSize.width, mSurfaceSize.height, 452 | 1 453 | ) 454 | ); 455 | } 456 | } 457 | 458 | void Renderer::createRenderPass() 459 | { 460 | std::vector attachmentDescriptions = 461 | { 462 | vk::AttachmentDescription( 463 | vk::AttachmentDescriptionFlags(), 464 | mSurfaceColorFormat, 465 | vk::SampleCountFlagBits::e1, 466 | vk::AttachmentLoadOp::eClear, 467 | vk::AttachmentStoreOp::eStore, 468 | vk::AttachmentLoadOp::eDontCare, 469 | vk::AttachmentStoreOp::eDontCare, 470 | vk::ImageLayout::eUndefined, 471 | vk::ImageLayout::ePresentSrcKHR 472 | ), 473 | vk::AttachmentDescription( 474 | vk::AttachmentDescriptionFlags(), 475 | mSurfaceDepthFormat, 476 | vk::SampleCountFlagBits::e1, 477 | vk::AttachmentLoadOp::eClear, 478 | vk::AttachmentStoreOp::eDontCare, 479 | vk::AttachmentLoadOp::eDontCare, 480 | vk::AttachmentStoreOp::eDontCare, 481 | vk::ImageLayout::eUndefined, 482 | vk::ImageLayout::eDepthStencilAttachmentOptimal 483 | ) 484 | }; 485 | 486 | std::vector colorReferences = 487 | { 488 | vk::AttachmentReference(0, vk::ImageLayout::eColorAttachmentOptimal) 489 | }; 490 | 491 | std::vector depthReferences = { 492 | vk::AttachmentReference(1, vk::ImageLayout::eDepthStencilAttachmentOptimal) 493 | }; 494 | 495 | std::vector subpasses = 496 | { 497 | vk::SubpassDescription( 498 | vk::SubpassDescriptionFlags(), 499 | vk::PipelineBindPoint::eGraphics, 500 | 0, 501 | nullptr, 502 | static_cast(colorReferences.size()), 503 | colorReferences.data(), 504 | nullptr, 505 | depthReferences.data(), 506 | 0, 507 | nullptr 508 | ) 509 | }; 510 | 511 | std::vector dependencies = 512 | { 513 | vk::SubpassDependency( 514 | ~0U, 515 | 0, 516 | vk::PipelineStageFlagBits::eBottomOfPipe, 517 | vk::PipelineStageFlagBits::eColorAttachmentOutput, 518 | vk::AccessFlagBits::eMemoryRead, 519 | vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite, 520 | vk::DependencyFlagBits::eByRegion 521 | ), 522 | vk::SubpassDependency( 523 | 0, 524 | ~0U, 525 | vk::PipelineStageFlagBits::eColorAttachmentOutput, 526 | vk::PipelineStageFlagBits::eBottomOfPipe, 527 | vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite, 528 | vk::AccessFlagBits::eMemoryRead, 529 | vk::DependencyFlagBits::eByRegion 530 | ) 531 | }; 532 | 533 | mRenderPass = mDevice.createRenderPass( 534 | vk::RenderPassCreateInfo( 535 | vk::RenderPassCreateFlags(), 536 | static_cast(attachmentDescriptions.size()), 537 | attachmentDescriptions.data(), 538 | static_cast(subpasses.size()), 539 | subpasses.data(), 540 | static_cast(dependencies.size()), 541 | dependencies.data() 542 | ) 543 | ); 544 | } 545 | 546 | void Renderer::createSynchronization() 547 | { 548 | // Semaphore used to ensures that image presentation is complete before starting to submit again 549 | mPresentCompleteSemaphore = mDevice.createSemaphore(vk::SemaphoreCreateInfo()); 550 | 551 | // Semaphore used to ensures that all commands submitted have been finished before submitting the image to the queue 552 | mRenderCompleteSemaphore = mDevice.createSemaphore(vk::SemaphoreCreateInfo()); 553 | 554 | // Fence for command buffer completion 555 | mWaitFences.resize(mSwapchainBuffers.size()); 556 | 557 | for (size_t i = 0; i < mWaitFences.size(); i++) 558 | { 559 | mWaitFences[i] = mDevice.createFence(vk::FenceCreateInfo(vk::FenceCreateFlagBits::eSignaled)); 560 | } 561 | } 562 | 563 | void Renderer::initializeResources() 564 | { 565 | /** 566 | * Create Shader uniform binding data structures: 567 | */ 568 | 569 | //Descriptor Pool 570 | std::vector descriptorPoolSizes = 571 | { 572 | vk::DescriptorPoolSize( 573 | vk::DescriptorType::eUniformBuffer, 574 | 1 575 | ) 576 | }; 577 | 578 | mDescriptorPool = mDevice.createDescriptorPool( 579 | vk::DescriptorPoolCreateInfo( 580 | vk::DescriptorPoolCreateFlags(), 581 | 1, 582 | static_cast(descriptorPoolSizes.size()), 583 | descriptorPoolSizes.data() 584 | ) 585 | ); 586 | 587 | //Descriptor Set Layout 588 | // Binding 0: Uniform buffer (Vertex shader) 589 | std::vector descriptorSetLayoutBindings = 590 | { 591 | vk::DescriptorSetLayoutBinding( 592 | 0, 593 | vk::DescriptorType::eUniformBuffer, 594 | 1, 595 | vk::ShaderStageFlagBits::eVertex, 596 | nullptr 597 | ) 598 | }; 599 | 600 | mDescriptorSetLayouts = { 601 | mDevice.createDescriptorSetLayout( 602 | vk::DescriptorSetLayoutCreateInfo( 603 | vk::DescriptorSetLayoutCreateFlags(), 604 | static_cast(descriptorSetLayoutBindings.size()), 605 | descriptorSetLayoutBindings.data() 606 | ) 607 | ) 608 | }; 609 | 610 | mDescriptorSets = mDevice.allocateDescriptorSets( 611 | vk::DescriptorSetAllocateInfo( 612 | mDescriptorPool, 613 | static_cast(mDescriptorSetLayouts.size()), 614 | mDescriptorSetLayouts.data() 615 | ) 616 | ); 617 | 618 | mPipelineLayout = mDevice.createPipelineLayout( 619 | vk::PipelineLayoutCreateInfo( 620 | vk::PipelineLayoutCreateFlags(), 621 | static_cast(mDescriptorSetLayouts.size()), 622 | mDescriptorSetLayouts.data(), 623 | 0, 624 | nullptr 625 | ) 626 | ); 627 | 628 | // Setup vertices data 629 | uint32_t vertexBufferSize = static_cast(3) * sizeof(Vertex); 630 | 631 | // Setup mIndices data 632 | mIndices.count = 3; 633 | uint32_t indexBufferSize = mIndices.count * sizeof(uint32_t); 634 | 635 | void *data; 636 | // Static data like vertex and index buffer should be stored on the device memory 637 | // for optimal (and fastest) access by the GPU 638 | // 639 | // To achieve this we use so-called "staging buffers" : 640 | // - Create a buffer that's visible to the host (and can be mapped) 641 | // - Copy the data to this buffer 642 | // - Create another buffer that's local on the device (VRAM) with the same size 643 | // - Copy the data from the host to the device using a command buffer 644 | // - Delete the host visible (staging) buffer 645 | // - Use the device local buffers for rendering 646 | 647 | struct StagingBuffer { 648 | vk::DeviceMemory memory; 649 | vk::Buffer buffer; 650 | }; 651 | 652 | struct { 653 | StagingBuffer vertices; 654 | StagingBuffer indices; 655 | } stagingBuffers; 656 | 657 | // Vertex buffer 658 | stagingBuffers.vertices.buffer = mDevice.createBuffer( 659 | vk::BufferCreateInfo( 660 | vk::BufferCreateFlags(), 661 | vertexBufferSize, 662 | vk::BufferUsageFlagBits::eTransferSrc, 663 | vk::SharingMode::eExclusive, 664 | 1, 665 | &mQueueFamilyIndex 666 | ) 667 | ); 668 | 669 | auto memReqs = mDevice.getBufferMemoryRequirements(stagingBuffers.vertices.buffer); 670 | 671 | // Request a host visible memory type that can be used to copy our data do 672 | // Also request it to be coherent, so that writes are visible to the GPU right after unmapping the buffer 673 | stagingBuffers.vertices.memory = mDevice.allocateMemory( 674 | vk::MemoryAllocateInfo( 675 | memReqs.size, 676 | getMemoryTypeIndex(mPhysicalDevice, memReqs.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) 677 | ) 678 | ); 679 | 680 | // Map and copy 681 | data = mDevice.mapMemory(stagingBuffers.vertices.memory, 0, memReqs.size, vk::MemoryMapFlags()); 682 | memcpy(data, mVertexBufferData, vertexBufferSize); 683 | mDevice.unmapMemory(stagingBuffers.vertices.memory); 684 | mDevice.bindBufferMemory(stagingBuffers.vertices.buffer, stagingBuffers.vertices.memory, 0); 685 | 686 | // Create a device local buffer to which the (host local) vertex data will be copied and which will be used for rendering 687 | mVertices.buffer = mDevice.createBuffer( 688 | vk::BufferCreateInfo( 689 | vk::BufferCreateFlags(), 690 | vertexBufferSize, 691 | vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, 692 | vk::SharingMode::eExclusive, 693 | 1, 694 | &mQueueFamilyIndex 695 | ) 696 | ); 697 | 698 | memReqs = mDevice.getBufferMemoryRequirements(mVertices.buffer); 699 | 700 | mVertices.memory = mDevice.allocateMemory( 701 | vk::MemoryAllocateInfo( 702 | memReqs.size, 703 | getMemoryTypeIndex(mPhysicalDevice, memReqs.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal) 704 | ) 705 | ); 706 | 707 | mDevice.bindBufferMemory(mVertices.buffer, mVertices.memory, 0); 708 | 709 | // Index buffer 710 | // Copy index data to a buffer visible to the host (staging buffer) 711 | stagingBuffers.indices.buffer = mDevice.createBuffer( 712 | vk::BufferCreateInfo( 713 | vk::BufferCreateFlags(), 714 | indexBufferSize, 715 | vk::BufferUsageFlagBits::eTransferSrc, 716 | vk::SharingMode::eExclusive, 717 | 1, 718 | &mQueueFamilyIndex 719 | ) 720 | ); 721 | memReqs = mDevice.getBufferMemoryRequirements(stagingBuffers.indices.buffer); 722 | stagingBuffers.indices.memory = mDevice.allocateMemory( 723 | vk::MemoryAllocateInfo( 724 | memReqs.size, 725 | getMemoryTypeIndex(mPhysicalDevice, memReqs.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent) 726 | ) 727 | ); 728 | 729 | data = mDevice.mapMemory(stagingBuffers.indices.memory, 0, indexBufferSize, vk::MemoryMapFlags()); 730 | memcpy(data, mIndexBufferData, indexBufferSize); 731 | mDevice.unmapMemory(stagingBuffers.indices.memory); 732 | mDevice.bindBufferMemory(stagingBuffers.indices.buffer, stagingBuffers.indices.memory, 0); 733 | 734 | // Create destination buffer with device only visibility 735 | mIndices.buffer = mDevice.createBuffer( 736 | vk::BufferCreateInfo( 737 | vk::BufferCreateFlags(), 738 | indexBufferSize, 739 | vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, 740 | vk::SharingMode::eExclusive, 741 | 0, 742 | nullptr 743 | ) 744 | ); 745 | 746 | memReqs = mDevice.getBufferMemoryRequirements(mIndices.buffer); 747 | mIndices.memory = mDevice.allocateMemory( 748 | vk::MemoryAllocateInfo( 749 | memReqs.size, 750 | getMemoryTypeIndex(mPhysicalDevice, memReqs.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal 751 | ) 752 | ) 753 | ); 754 | 755 | mDevice.bindBufferMemory(mIndices.buffer, mIndices.memory, 0); 756 | 757 | auto getCommandBuffer = [&](bool begin) 758 | { 759 | vk::CommandBuffer cmdBuffer = mDevice.allocateCommandBuffers( 760 | vk::CommandBufferAllocateInfo( 761 | mCommandPool, 762 | vk::CommandBufferLevel::ePrimary, 763 | 1) 764 | )[0]; 765 | 766 | // If requested, also start the new command buffer 767 | if (begin) 768 | { 769 | cmdBuffer.begin( 770 | vk::CommandBufferBeginInfo() 771 | ); 772 | } 773 | 774 | return cmdBuffer; 775 | }; 776 | 777 | // Buffer copies have to be submitted to a queue, so we need a command buffer for them 778 | // Note: Some devices offer a dedicated transfer queue (with only the transfer bit set) that may be faster when doing lots of copies 779 | vk::CommandBuffer copyCmd = getCommandBuffer(true); 780 | 781 | // Put buffer region copies into command buffer 782 | std::vector copyRegions = 783 | { 784 | vk::BufferCopy(0, 0, vertexBufferSize) 785 | }; 786 | 787 | // Vertex buffer 788 | copyCmd.copyBuffer(stagingBuffers.vertices.buffer, mVertices.buffer, copyRegions); 789 | 790 | // Index buffer 791 | copyRegions = 792 | { 793 | vk::BufferCopy(0, 0, indexBufferSize) 794 | }; 795 | 796 | copyCmd.copyBuffer(stagingBuffers.indices.buffer, mIndices.buffer, copyRegions); 797 | 798 | // Flushing the command buffer will also submit it to the queue and uses a fence to ensure that all commands have been executed before returning 799 | auto flushCommandBuffer = [&](vk::CommandBuffer commandBuffer) 800 | { 801 | commandBuffer.end(); 802 | 803 | std::vector submitInfos = { 804 | vk::SubmitInfo(0, nullptr, nullptr, 1, &commandBuffer, 0, nullptr) 805 | }; 806 | 807 | // Create fence to ensure that the command buffer has finished executing 808 | vk::Fence fence = mDevice.createFence(vk::FenceCreateInfo()); 809 | 810 | // Submit to the queue 811 | mQueue.submit(submitInfos, fence); 812 | // Wait for the fence to signal that command buffer has finished executing 813 | mDevice.waitForFences(1, &fence, VK_TRUE, UINT_MAX); 814 | mDevice.destroyFence(fence); 815 | mDevice.freeCommandBuffers(mCommandPool, 1, &commandBuffer); 816 | }; 817 | 818 | flushCommandBuffer(copyCmd); 819 | 820 | // Destroy staging buffers 821 | // Note: Staging buffer must not be deleted before the copies have been submitted and executed 822 | mDevice.destroyBuffer(stagingBuffers.vertices.buffer); 823 | mDevice.freeMemory(stagingBuffers.vertices.memory); 824 | mDevice.destroyBuffer(stagingBuffers.indices.buffer); 825 | mDevice.freeMemory(stagingBuffers.indices.memory); 826 | 827 | 828 | // Vertex input binding 829 | mVertices.inputBinding.binding = 0; 830 | mVertices.inputBinding.stride = sizeof(Vertex); 831 | mVertices.inputBinding.inputRate = vk::VertexInputRate::eVertex; 832 | 833 | // Inpute attribute binding describe shader attribute locations and memory layouts 834 | // These match the following shader layout (see assets/shaders/triangle.vert): 835 | // layout (location = 0) in vec3 inPos; 836 | // layout (location = 1) in vec3 inColor; 837 | mVertices.inputAttributes.resize(2); 838 | // Attribute location 0: Position 839 | mVertices.inputAttributes[0].binding = 0; 840 | mVertices.inputAttributes[0].location = 0; 841 | mVertices.inputAttributes[0].format = vk::Format::eR32G32B32Sfloat; 842 | mVertices.inputAttributes[0].offset = offsetof(Vertex, position); 843 | // Attribute location 1: Color 844 | mVertices.inputAttributes[1].binding = 0; 845 | mVertices.inputAttributes[1].location = 1; 846 | mVertices.inputAttributes[1].format = vk::Format::eR32G32B32Sfloat; 847 | mVertices.inputAttributes[1].offset = offsetof(Vertex, color); 848 | 849 | // Assign to the vertex input state used for pipeline creation 850 | mVertices.inputState.flags = vk::PipelineVertexInputStateCreateFlags(); 851 | mVertices.inputState.vertexBindingDescriptionCount = 1; 852 | mVertices.inputState.pVertexBindingDescriptions = &mVertices.inputBinding; 853 | mVertices.inputState.vertexAttributeDescriptionCount = static_cast(mVertices.inputAttributes.size()); 854 | mVertices.inputState.pVertexAttributeDescriptions = mVertices.inputAttributes.data(); 855 | 856 | // Prepare and initialize a uniform buffer block containing shader uniforms 857 | // Single uniforms like in OpenGL are no longer present in Vulkan. All Shader uniforms are passed via uniform buffer blocks 858 | 859 | // Vertex shader uniform buffer block 860 | vk::MemoryAllocateInfo allocInfo = {}; 861 | allocInfo.pNext = nullptr; 862 | allocInfo.allocationSize = 0; 863 | allocInfo.memoryTypeIndex = 0; 864 | 865 | // Create a new buffer 866 | mUniformDataVS.buffer = mDevice.createBuffer( 867 | vk::BufferCreateInfo( 868 | vk::BufferCreateFlags(), 869 | sizeof(uboVS), 870 | vk::BufferUsageFlagBits::eUniformBuffer 871 | ) 872 | ); 873 | // Get memory requirements including size, alignment and memory type 874 | memReqs = mDevice.getBufferMemoryRequirements(mUniformDataVS.buffer); 875 | allocInfo.allocationSize = memReqs.size; 876 | // Get the memory type index that supports host visible memory access 877 | // Most implementations offer multiple memory types and selecting the correct one to allocate memory from is crucial 878 | // We also want the buffer to be host coherent so we don't have to flush (or sync after every update. 879 | // Note: This may affect performance so you might not want to do this in a real world application that updates buffers on a regular base 880 | allocInfo.memoryTypeIndex = getMemoryTypeIndex(mPhysicalDevice, memReqs.memoryTypeBits, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); 881 | // Allocate memory for the uniform buffer 882 | mUniformDataVS.memory = mDevice.allocateMemory(allocInfo); 883 | // Bind memory to buffer 884 | mDevice.bindBufferMemory(mUniformDataVS.buffer, mUniformDataVS.memory, 0); 885 | 886 | // Store information in the uniform's descriptor that is used by the descriptor set 887 | mUniformDataVS.descriptor.buffer = mUniformDataVS.buffer; 888 | mUniformDataVS.descriptor.offset = 0; 889 | mUniformDataVS.descriptor.range = sizeof(uboVS); 890 | 891 | // Update Uniforms 892 | float zoom = -2.5f; 893 | 894 | // Update matrices 895 | uboVS.projectionMatrix = Matrix4::perspective(45.0f, (float)mViewport.width / (float)mViewport.height, 0.01f, 1024.0f); 896 | 897 | uboVS.viewMatrix = Matrix4::translation(Vector3(0.0f, 0.0f, zoom)); 898 | 899 | uboVS.modelMatrix = Matrix4::identity(); 900 | 901 | // Map uniform buffer and update it 902 | void *pData; 903 | pData = mDevice.mapMemory(mUniformDataVS.memory, 0, sizeof(uboVS)); 904 | memcpy(pData, &uboVS, sizeof(uboVS)); 905 | mDevice.unmapMemory(mUniformDataVS.memory); 906 | 907 | 908 | std::vector descriptorWrites = 909 | { 910 | vk::WriteDescriptorSet( 911 | mDescriptorSets[0], 912 | 0, 913 | 0, 914 | 1, 915 | vk::DescriptorType::eUniformBuffer, 916 | nullptr, 917 | &mUniformDataVS.descriptor, 918 | nullptr 919 | ) 920 | }; 921 | 922 | mDevice.updateDescriptorSets(descriptorWrites, nullptr); 923 | 924 | // Create Render Pass 925 | 926 | createRenderPass(); 927 | 928 | initFrameBuffer(); 929 | 930 | // Create Graphics Pipeline 931 | 932 | std::vector vertShaderCode = readFile("assets/triangle.vert.spv"); 933 | std::vector fragShaderCode = readFile("assets/triangle.frag.spv"); 934 | 935 | mVertModule = mDevice.createShaderModule( 936 | vk::ShaderModuleCreateInfo( 937 | vk::ShaderModuleCreateFlags(), 938 | vertShaderCode.size(), 939 | (uint32_t*)vertShaderCode.data() 940 | ) 941 | ); 942 | 943 | mFragModule = mDevice.createShaderModule( 944 | vk::ShaderModuleCreateInfo( 945 | vk::ShaderModuleCreateFlags(), 946 | fragShaderCode.size(), 947 | (uint32_t*)fragShaderCode.data() 948 | ) 949 | ); 950 | 951 | mPipelineCache = mDevice.createPipelineCache(vk::PipelineCacheCreateInfo()); 952 | 953 | std::vector pipelineShaderStages = { 954 | vk::PipelineShaderStageCreateInfo( 955 | vk::PipelineShaderStageCreateFlags(), 956 | vk::ShaderStageFlagBits::eVertex, 957 | mVertModule, 958 | "main", 959 | nullptr 960 | ), 961 | vk::PipelineShaderStageCreateInfo( 962 | vk::PipelineShaderStageCreateFlags(), 963 | vk::ShaderStageFlagBits::eFragment, 964 | mFragModule, 965 | "main", 966 | nullptr 967 | ) 968 | }; 969 | 970 | vk::PipelineVertexInputStateCreateInfo pvi = mVertices.inputState; 971 | 972 | vk::PipelineInputAssemblyStateCreateInfo pia( 973 | vk::PipelineInputAssemblyStateCreateFlags(), 974 | vk::PrimitiveTopology::eTriangleList 975 | ); 976 | 977 | vk::PipelineViewportStateCreateInfo pv( 978 | vk::PipelineViewportStateCreateFlagBits(), 979 | 1, 980 | &mViewport, 981 | 1, 982 | &mRenderArea 983 | ); 984 | 985 | vk::PipelineRasterizationStateCreateInfo pr( 986 | vk::PipelineRasterizationStateCreateFlags(), 987 | VK_FALSE, 988 | VK_FALSE, 989 | vk::PolygonMode::eFill, 990 | vk::CullModeFlagBits::eNone, 991 | vk::FrontFace::eCounterClockwise, 992 | VK_FALSE, 993 | 0, 994 | 0, 995 | 0, 996 | 1.0f 997 | ); 998 | 999 | vk::PipelineMultisampleStateCreateInfo pm( 1000 | vk::PipelineMultisampleStateCreateFlags(), 1001 | vk::SampleCountFlagBits::e1 1002 | ); 1003 | 1004 | // Dept and Stencil state for primative compare/test operations 1005 | 1006 | vk::PipelineDepthStencilStateCreateInfo pds = vk::PipelineDepthStencilStateCreateInfo( 1007 | vk::PipelineDepthStencilStateCreateFlags(), 1008 | VK_TRUE, 1009 | VK_TRUE, 1010 | vk::CompareOp::eLessOrEqual, 1011 | VK_FALSE, 1012 | VK_FALSE, 1013 | vk::StencilOpState(), 1014 | vk::StencilOpState(), 1015 | 0, 1016 | 0 1017 | ); 1018 | 1019 | // Blend State - How two primatives should draw on top of each other. 1020 | std::vector colorBlendAttachments = 1021 | { 1022 | vk::PipelineColorBlendAttachmentState( 1023 | VK_FALSE, 1024 | vk::BlendFactor::eZero, 1025 | vk::BlendFactor::eOne, 1026 | vk::BlendOp::eAdd, 1027 | vk::BlendFactor::eZero, 1028 | vk::BlendFactor::eZero, 1029 | vk::BlendOp::eAdd, 1030 | vk::ColorComponentFlags(vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA) 1031 | ) 1032 | }; 1033 | 1034 | vk::PipelineColorBlendStateCreateInfo pbs( 1035 | vk::PipelineColorBlendStateCreateFlags(), 1036 | 0, 1037 | vk::LogicOp::eClear, 1038 | static_cast(colorBlendAttachments.size()), 1039 | colorBlendAttachments.data() 1040 | ); 1041 | 1042 | std::vector dynamicStates = 1043 | { 1044 | vk::DynamicState::eViewport, 1045 | vk::DynamicState::eScissor 1046 | }; 1047 | 1048 | vk::PipelineDynamicStateCreateInfo pdy( 1049 | vk::PipelineDynamicStateCreateFlags(), 1050 | static_cast(dynamicStates.size()), 1051 | dynamicStates.data() 1052 | ); 1053 | 1054 | mPipeline = mDevice.createGraphicsPipeline( 1055 | mPipelineCache, 1056 | vk::GraphicsPipelineCreateInfo( 1057 | vk::PipelineCreateFlags(), 1058 | static_cast(pipelineShaderStages.size()), 1059 | pipelineShaderStages.data(), 1060 | &pvi, 1061 | &pia, 1062 | nullptr, 1063 | &pv, 1064 | &pr, 1065 | &pm, 1066 | &pds, 1067 | &pbs, 1068 | &pdy, 1069 | mPipelineLayout, 1070 | mRenderPass, 1071 | 0 1072 | ) 1073 | ); 1074 | } 1075 | 1076 | void Renderer::destroyResources() 1077 | { 1078 | // Vertices 1079 | mDevice.freeMemory(mVertices.memory); 1080 | mDevice.destroyBuffer(mVertices.buffer); 1081 | 1082 | // Index buffer 1083 | mDevice.freeMemory(mIndices.memory); 1084 | mDevice.destroyBuffer(mIndices.buffer); 1085 | 1086 | // Shader Module 1087 | mDevice.destroyShaderModule(mVertModule); 1088 | mDevice.destroyShaderModule(mFragModule); 1089 | 1090 | // Render Pass 1091 | mDevice.destroyRenderPass(mRenderPass); 1092 | 1093 | // Graphics Pipeline 1094 | mDevice.destroyPipelineCache(mPipelineCache); 1095 | mDevice.destroyPipeline(mPipeline); 1096 | mDevice.destroyPipelineLayout(mPipelineLayout); 1097 | 1098 | // Descriptor Pool 1099 | mDevice.destroyDescriptorPool(mDescriptorPool); 1100 | for (vk::DescriptorSetLayout& dsl : mDescriptorSetLayouts) 1101 | { 1102 | mDevice.destroyDescriptorSetLayout(dsl); 1103 | } 1104 | 1105 | // Uniform block object 1106 | mDevice.freeMemory(mUniformDataVS.memory); 1107 | mDevice.destroyBuffer(mUniformDataVS.buffer); 1108 | 1109 | // Destroy Framebuffers, Image Views 1110 | destroyFrameBuffer(); 1111 | mDevice.destroySwapchainKHR(mSwapchain); 1112 | 1113 | // Sync 1114 | mDevice.destroySemaphore(mPresentCompleteSemaphore); 1115 | mDevice.destroySemaphore(mRenderCompleteSemaphore); 1116 | for (vk::Fence& f : mWaitFences) 1117 | { 1118 | mDevice.destroyFence(f); 1119 | } 1120 | 1121 | } 1122 | 1123 | void Renderer::createCommands() 1124 | { 1125 | mCommandBuffers = mDevice.allocateCommandBuffers( 1126 | vk::CommandBufferAllocateInfo( 1127 | mCommandPool, 1128 | vk::CommandBufferLevel::ePrimary, 1129 | static_cast(mSwapchainBuffers.size()) 1130 | ) 1131 | ); 1132 | } 1133 | 1134 | void Renderer::setupCommands() 1135 | { 1136 | std::vector clearValues = 1137 | { 1138 | vk::ClearColorValue( 1139 | std::array{0.2f, 0.2f, 0.2f, 1.0f}), 1140 | vk::ClearDepthStencilValue(1.0f, 0) 1141 | }; 1142 | 1143 | for (size_t i = 0; i < mCommandBuffers.size(); ++i) 1144 | { 1145 | vk::CommandBuffer& cmd = mCommandBuffers[i]; 1146 | cmd.reset(vk::CommandBufferResetFlagBits::eReleaseResources); 1147 | cmd.begin(vk::CommandBufferBeginInfo()); 1148 | cmd.beginRenderPass( 1149 | vk::RenderPassBeginInfo( 1150 | mRenderPass, 1151 | mSwapchainBuffers[i].frameBuffer, 1152 | mRenderArea, 1153 | static_cast(clearValues.size()), 1154 | clearValues.data()), 1155 | vk::SubpassContents::eInline); 1156 | 1157 | cmd.setViewport(0, 1, &mViewport); 1158 | 1159 | cmd.setScissor(0, 1, &mRenderArea); 1160 | 1161 | // Bind Descriptor Sets, these are attribute/uniform "descriptions" 1162 | cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, mPipeline); 1163 | 1164 | cmd.bindDescriptorSets( 1165 | vk::PipelineBindPoint::eGraphics, 1166 | mPipelineLayout, 1167 | 0, 1168 | mDescriptorSets, 1169 | nullptr 1170 | ); 1171 | 1172 | vk::DeviceSize offsets = 0; 1173 | cmd.bindVertexBuffers(0, 1, &mVertices.buffer, &offsets); 1174 | cmd.bindIndexBuffer(mIndices.buffer, 0, vk::IndexType::eUint32); 1175 | cmd.drawIndexed(mIndices.count, 1, 0, 0, 1); 1176 | cmd.endRenderPass(); 1177 | cmd.end(); 1178 | } 1179 | } 1180 | 1181 | void Renderer::render() 1182 | { 1183 | // Framelimit set to 60 fps 1184 | tEnd = std::chrono::high_resolution_clock::now(); 1185 | float time = std::chrono::duration(tEnd - tStart).count(); 1186 | if (time < (1000.0f / 60.0f)) 1187 | { 1188 | return; 1189 | } 1190 | tStart = std::chrono::high_resolution_clock::now(); 1191 | 1192 | // Swap backbuffers 1193 | vk::Result result; 1194 | 1195 | result = mDevice.acquireNextImageKHR(mSwapchain, UINT64_MAX, mPresentCompleteSemaphore, nullptr, &mCurrentBuffer); 1196 | if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR) 1197 | { 1198 | // Swapchain lost, we'll try again next poll 1199 | resize(mSurfaceSize.width, mSurfaceSize.height); 1200 | return; 1201 | 1202 | } 1203 | if (result == vk::Result::eErrorDeviceLost) 1204 | { 1205 | // driver lost, we'll crash in this case: 1206 | exit(1); 1207 | } 1208 | 1209 | // Update Uniforms 1210 | mElapsedTime += 0.001f * time; 1211 | mElapsedTime = fmodf(mElapsedTime, 6.283185307179586f); 1212 | uboVS.modelMatrix = Matrix4::rotationY(mElapsedTime); 1213 | 1214 | void *pData; 1215 | pData = mDevice.mapMemory(mUniformDataVS.memory, 0, sizeof(uboVS)); 1216 | memcpy(pData, &uboVS, sizeof(uboVS)); 1217 | mDevice.unmapMemory(mUniformDataVS.memory); 1218 | 1219 | // Wait for Fences 1220 | mDevice.waitForFences(1, &mWaitFences[mCurrentBuffer], VK_TRUE, UINT64_MAX); 1221 | mDevice.resetFences(1, &mWaitFences[mCurrentBuffer]); 1222 | 1223 | vk::SubmitInfo submitInfo; 1224 | vk::PipelineStageFlags waitDstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; 1225 | submitInfo 1226 | .setWaitSemaphoreCount(1) 1227 | .setPWaitSemaphores(&mPresentCompleteSemaphore) 1228 | .setPWaitDstStageMask(&waitDstStageMask) 1229 | .setCommandBufferCount(1) 1230 | .setPCommandBuffers(&mCommandBuffers[mCurrentBuffer]) 1231 | .setSignalSemaphoreCount(1) 1232 | .setPSignalSemaphores(&mRenderCompleteSemaphore); 1233 | result = mQueue.submit(1, &submitInfo, mWaitFences[mCurrentBuffer]); 1234 | 1235 | if (result == vk::Result::eErrorDeviceLost) 1236 | { 1237 | // driver lost, we'll crash in this case: 1238 | exit(1); 1239 | } 1240 | 1241 | result = mQueue.presentKHR( 1242 | vk::PresentInfoKHR( 1243 | 1, 1244 | &mRenderCompleteSemaphore, 1245 | 1, 1246 | &mSwapchain, 1247 | &mCurrentBuffer, 1248 | nullptr 1249 | ) 1250 | ); 1251 | 1252 | if (result == vk::Result::eErrorOutOfDateKHR || result == vk::Result::eSuboptimalKHR) 1253 | { 1254 | // Swapchain lost, we'll try again next poll 1255 | resize(mSurfaceSize.width, mSurfaceSize.height); 1256 | return; 1257 | } 1258 | } 1259 | 1260 | void Renderer::resize(unsigned width, unsigned height) 1261 | { 1262 | mDevice.waitIdle(); 1263 | destroyFrameBuffer(); 1264 | setupSwapchain(width, height); 1265 | initFrameBuffer(); 1266 | destroyCommands(); 1267 | createCommands(); 1268 | setupCommands(); 1269 | mDevice.waitIdle(); 1270 | 1271 | // Uniforms 1272 | uboVS.projectionMatrix = Matrix4::perspective(45.0f, (float)mViewport.width / (float)mViewport.height, 0.01f, 1024.0f); 1273 | } 1274 | -------------------------------------------------------------------------------- /src/Renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CrossWindow/CrossWindow.h" 4 | #include "CrossWindow/Graphics.h" 5 | #include "vectormath.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #if defined(XWIN_WIN32) 14 | #include 15 | #else 16 | #include 17 | #endif 18 | 19 | // Common Utils 20 | 21 | inline std::vector readFile(const std::string& filename) { 22 | std::string path = filename; 23 | char pBuf[1024]; 24 | #ifdef XWIN_WIN32 25 | 26 | _getcwd(pBuf, 1024); 27 | path = pBuf; 28 | path += "\\"; 29 | #else 30 | getcwd(pBuf, 1024); 31 | path = pBuf; 32 | path += "/"; 33 | #endif 34 | path += filename; 35 | std::ifstream file(path, std::ios::ate | std::ios::binary); 36 | bool exists = (bool)file; 37 | 38 | if (!exists || !file.is_open()) { 39 | throw std::runtime_error("failed to open file!"); 40 | } 41 | 42 | size_t fileSize = (size_t)file.tellg(); 43 | std::vector buffer(fileSize); 44 | 45 | file.seekg(0); 46 | file.read(buffer.data(), fileSize); 47 | 48 | file.close(); 49 | 50 | return buffer; 51 | }; 52 | 53 | template 54 | inline T clamp(const T& value, const T& low, const T& high) 55 | { 56 | return value < low ? low : (value > high ? high : value); 57 | } 58 | 59 | 60 | // Renderer 61 | 62 | class Renderer 63 | { 64 | public: 65 | Renderer(xwin::Window& window); 66 | 67 | ~Renderer(); 68 | 69 | // Render onto the render target 70 | void render(); 71 | 72 | // Resize the window and internal data structures 73 | void resize(unsigned width, unsigned height); 74 | 75 | protected: 76 | 77 | // Initialize your Graphics API 78 | void initializeAPI(xwin::Window& window); 79 | 80 | // Destroy any Graphics API data structures used in this example 81 | void destroyAPI(); 82 | 83 | // Initialize any resources such as VBOs, IBOs, used in this example 84 | void initializeResources(); 85 | 86 | // Destroy any resources used in this example 87 | void destroyResources(); 88 | 89 | // Create graphics API specific data structures to send commands to the GPU 90 | void createCommands(); 91 | 92 | // Set up commands used when rendering frame by this app 93 | void setupCommands(); 94 | 95 | // Destroy all commands 96 | void destroyCommands(); 97 | 98 | // Set up the FrameBuffer 99 | void initFrameBuffer(); 100 | 101 | void destroyFrameBuffer(); 102 | 103 | // Set up the RenderPass 104 | void createRenderPass(); 105 | 106 | void createSynchronization(); 107 | 108 | // Set up the swapchain 109 | void setupSwapchain(unsigned width, unsigned height); 110 | 111 | struct Vertex 112 | { 113 | float position[3]; 114 | float color[3]; 115 | }; 116 | 117 | Vertex mVertexBufferData[3] = 118 | { 119 | { { 1.0f, 1.0f, 0.0f },{ 1.0f, 0.0f, 0.0f } }, 120 | { { -1.0f, 1.0f, 0.0f },{ 0.0f, 1.0f, 0.0f } }, 121 | { { 0.0f, -1.0f, 0.0f },{ 0.0f, 0.0f, 1.0f } } 122 | }; 123 | 124 | uint32_t mIndexBufferData[3] = { 0, 1, 2 }; 125 | 126 | std::chrono::time_point tStart, tEnd; 127 | float mElapsedTime = 0.0f; 128 | 129 | // Uniform data 130 | struct { 131 | Matrix4 projectionMatrix; 132 | Matrix4 modelMatrix; 133 | Matrix4 viewMatrix; 134 | } uboVS; 135 | 136 | // Initialization 137 | vk::Instance mInstance; 138 | vk::PhysicalDevice mPhysicalDevice; 139 | vk::Device mDevice; 140 | 141 | vk::SwapchainKHR mSwapchain; 142 | vk::SurfaceKHR mSurface; 143 | 144 | float mQueuePriority; 145 | vk::Queue mQueue; 146 | uint32_t mQueueFamilyIndex; 147 | 148 | vk::CommandPool mCommandPool; 149 | std::vector mCommandBuffers; 150 | uint32_t mCurrentBuffer; 151 | 152 | vk::Extent2D mSurfaceSize; 153 | vk::Rect2D mRenderArea; 154 | vk::Viewport mViewport; 155 | 156 | // Resources 157 | vk::Format mSurfaceColorFormat; 158 | vk::ColorSpaceKHR mSurfaceColorSpace; 159 | vk::Format mSurfaceDepthFormat; 160 | vk::Image mDepthImage; 161 | vk::DeviceMemory mDepthImageMemory; 162 | 163 | vk::DescriptorPool mDescriptorPool; 164 | std::vector mDescriptorSetLayouts; 165 | std::vector mDescriptorSets; 166 | 167 | vk::ShaderModule mVertModule; 168 | vk::ShaderModule mFragModule; 169 | 170 | vk::RenderPass mRenderPass; 171 | 172 | vk::Buffer mVertexBuffer; 173 | vk::Buffer mIndexBuffer; 174 | 175 | vk::PipelineCache mPipelineCache; 176 | vk::Pipeline mPipeline; 177 | vk::PipelineLayout mPipelineLayout; 178 | 179 | // Sync 180 | vk::Semaphore mPresentCompleteSemaphore; 181 | vk::Semaphore mRenderCompleteSemaphore; 182 | std::vector mWaitFences; 183 | 184 | // Swpachain 185 | struct SwapChainBuffer { 186 | vk::Image image; 187 | std::array views; 188 | vk::Framebuffer frameBuffer; 189 | }; 190 | 191 | std::vector mSwapchainBuffers; 192 | 193 | // Vertex buffer and attributes 194 | struct { 195 | vk::DeviceMemory memory; // Handle to the device memory for this buffer 196 | vk::Buffer buffer; // Handle to the Vulkan buffer object that the memory is bound to 197 | vk::PipelineVertexInputStateCreateInfo inputState; 198 | vk::VertexInputBindingDescription inputBinding; 199 | std::vector inputAttributes; 200 | } mVertices; 201 | 202 | // Index buffer 203 | struct 204 | { 205 | vk::DeviceMemory memory; 206 | vk::Buffer buffer; 207 | uint32_t count; 208 | } mIndices; 209 | 210 | // Uniform block object 211 | struct { 212 | vk::DeviceMemory memory; 213 | vk::Buffer buffer; 214 | vk::DescriptorBufferInfo descriptor; 215 | } mUniformDataVS; 216 | }; -------------------------------------------------------------------------------- /src/XMain.cpp: -------------------------------------------------------------------------------- 1 | #include "CrossWindow/CrossWindow.h" 2 | #include "Renderer.h" 3 | 4 | void xmain(int argc, const char** argv) 5 | { 6 | // 🖼️ Create a window 7 | xwin::EventQueue eventQueue; 8 | xwin::Window window; 9 | 10 | xwin::WindowDesc windowDesc; 11 | windowDesc.name = "MainWindow"; 12 | windowDesc.title = "Hello Triangle"; 13 | windowDesc.visible = true; 14 | windowDesc.width = 1280; 15 | windowDesc.height = 720; 16 | //windowDesc.fullscreen = true; 17 | window.create(windowDesc, eventQueue); 18 | 19 | // 📸 Create a renderer 20 | Renderer renderer(window); 21 | 22 | // 🏁 Engine loop 23 | bool isRunning = true; 24 | while (isRunning) 25 | { 26 | bool shouldRender = true; 27 | 28 | // ♻️ Update the event queue 29 | eventQueue.update(); 30 | 31 | // 🎈 Iterate through that queue: 32 | while (!eventQueue.empty()) 33 | { 34 | //Update Events 35 | const xwin::Event& event = eventQueue.front(); 36 | 37 | if (event.type == xwin::EventType::Resize) 38 | { 39 | const xwin::ResizeData data = event.data.resize; 40 | renderer.resize(data.width, data.height); 41 | shouldRender = false; 42 | } 43 | 44 | if (event.type == xwin::EventType::Close) 45 | { 46 | window.close(); 47 | shouldRender = false; 48 | isRunning = false; 49 | } 50 | 51 | eventQueue.pop(); 52 | } 53 | 54 | // ✨ Update Visuals 55 | if (shouldRender) 56 | { 57 | renderer.render(); 58 | } 59 | } 60 | 61 | } --------------------------------------------------------------------------------