├── .gitignore ├── .gitmodules ├── external └── glad │ ├── CMakeLists.txt │ ├── include │ ├── KHR │ │ └── khrplatform.h │ └── glad │ │ └── glad.h │ └── src │ └── glad.c ├── license.md ├── readme.md └── src ├── 01-window-creation ├── CMakeLists.txt ├── readme.md └── src │ └── XMain.cpp ├── 02-events └── src │ └── XMain.cpp ├── 03-open-save-alert └── src │ └── XMain.cpp └── 04-cross-platform-hello-triangle ├── CMakeLists.txt ├── assets ├── cover.jpg └── shaders │ ├── triangle.frag │ ├── triangle.frag.glsl │ ├── triangle.frag.hlsl │ ├── triangle.frag.msl │ ├── triangle.frag.spv │ ├── triangle.vert │ ├── triangle.vert.glsl │ ├── triangle.vert.hlsl │ ├── triangle.vert.msl │ └── triangle.vert.spv ├── readme.md └── src ├── DirectX11Renderer.cpp ├── DirectX12Renderer.cpp ├── MetalRenderer.mm ├── OpenGLRenderer.cpp ├── Renderer.h ├── VulkanRenderer.cpp └── 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/spirv-cross"] 8 | path = external/spirv-cross 9 | url = https://github.com/KhronosGroup/spirv-cross.git 10 | [submodule "external/glslang"] 11 | path = external/glslang 12 | url = https://github.com/KhronosGroup/glslang.git 13 | [submodule "external/vectormath"] 14 | path = external/vectormath 15 | url = https://github.com/glampert/vectormath.git 16 | [submodule "external/opengl-registry"] 17 | path = external/opengl-registry 18 | url = https://github.com/KhronosGroup/opengl-registry.git 19 | -------------------------------------------------------------------------------- /external/glad/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6 FATAL_ERROR) 2 | cmake_policy(VERSION 3.6) 3 | project(Glad 4 | VERSION 1.0.0.0 5 | LANGUAGES C CXX 6 | ) 7 | 8 | set( 9 | GLAD_SOURCES 10 | ${CMAKE_CURRENT_SOURCE_DIR}/src/glad.c 11 | ) 12 | 13 | add_library( 14 | ${PROJECT_NAME} 15 | "${GLAD_SOURCES}" 16 | ) 17 | 18 | target_include_directories( 19 | Glad 20 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include 21 | ) -------------------------------------------------------------------------------- /external/glad/include/KHR/khrplatform.h: -------------------------------------------------------------------------------- 1 | #ifndef __khrplatform_h_ 2 | #define __khrplatform_h_ 3 | 4 | /* 5 | ** Copyright (c) 2008-2018 The Khronos Group Inc. 6 | ** 7 | ** Permission is hereby granted, free of charge, to any person obtaining a 8 | ** copy of this software and/or associated documentation files (the 9 | ** "Materials"), to deal in the Materials without restriction, including 10 | ** without limitation the rights to use, copy, modify, merge, publish, 11 | ** distribute, sublicense, and/or sell copies of the Materials, and to 12 | ** permit persons to whom the Materials are furnished to do so, subject to 13 | ** the following conditions: 14 | ** 15 | ** The above copyright notice and this permission notice shall be included 16 | ** in all copies or substantial portions of the Materials. 17 | ** 18 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 25 | */ 26 | 27 | /* Khronos platform-specific types and definitions. 28 | * 29 | * The master copy of khrplatform.h is maintained in the Khronos EGL 30 | * Registry repository at https://github.com/KhronosGroup/EGL-Registry 31 | * The last semantic modification to khrplatform.h was at commit ID: 32 | * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 33 | * 34 | * Adopters may modify this file to suit their platform. Adopters are 35 | * encouraged to submit platform specific modifications to the Khronos 36 | * group so that they can be included in future versions of this file. 37 | * Please submit changes by filing pull requests or issues on 38 | * the EGL Registry repository linked above. 39 | * 40 | * 41 | * See the Implementer's Guidelines for information about where this file 42 | * should be located on your system and for more details of its use: 43 | * http://www.khronos.org/registry/implementers_guide.pdf 44 | * 45 | * This file should be included as 46 | * #include 47 | * by Khronos client API header files that use its types and defines. 48 | * 49 | * The types in khrplatform.h should only be used to define API-specific types. 50 | * 51 | * Types defined in khrplatform.h: 52 | * khronos_int8_t signed 8 bit 53 | * khronos_uint8_t unsigned 8 bit 54 | * khronos_int16_t signed 16 bit 55 | * khronos_uint16_t unsigned 16 bit 56 | * khronos_int32_t signed 32 bit 57 | * khronos_uint32_t unsigned 32 bit 58 | * khronos_int64_t signed 64 bit 59 | * khronos_uint64_t unsigned 64 bit 60 | * khronos_intptr_t signed same number of bits as a pointer 61 | * khronos_uintptr_t unsigned same number of bits as a pointer 62 | * khronos_ssize_t signed size 63 | * khronos_usize_t unsigned size 64 | * khronos_float_t signed 32 bit floating point 65 | * khronos_time_ns_t unsigned 64 bit time in nanoseconds 66 | * khronos_utime_nanoseconds_t unsigned time interval or absolute time in 67 | * nanoseconds 68 | * khronos_stime_nanoseconds_t signed time interval in nanoseconds 69 | * khronos_boolean_enum_t enumerated boolean type. This should 70 | * only be used as a base type when a client API's boolean type is 71 | * an enum. Client APIs which use an integer or other type for 72 | * booleans cannot use this as the base type for their boolean. 73 | * 74 | * Tokens defined in khrplatform.h: 75 | * 76 | * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. 77 | * 78 | * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. 79 | * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. 80 | * 81 | * Calling convention macros defined in this file: 82 | * KHRONOS_APICALL 83 | * KHRONOS_APIENTRY 84 | * KHRONOS_APIATTRIBUTES 85 | * 86 | * These may be used in function prototypes as: 87 | * 88 | * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( 89 | * int arg1, 90 | * int arg2) KHRONOS_APIATTRIBUTES; 91 | */ 92 | 93 | /*------------------------------------------------------------------------- 94 | * Definition of KHRONOS_APICALL 95 | *------------------------------------------------------------------------- 96 | * This precedes the return type of the function in the function prototype. 97 | */ 98 | #if defined(_WIN32) && !defined(__SCITECH_SNAP__) 99 | # define KHRONOS_APICALL __declspec(dllimport) 100 | #elif defined (__SYMBIAN32__) 101 | # define KHRONOS_APICALL IMPORT_C 102 | #elif defined(__ANDROID__) 103 | # define KHRONOS_APICALL __attribute__((visibility("default"))) 104 | #else 105 | # define KHRONOS_APICALL 106 | #endif 107 | 108 | /*------------------------------------------------------------------------- 109 | * Definition of KHRONOS_APIENTRY 110 | *------------------------------------------------------------------------- 111 | * This follows the return type of the function and precedes the function 112 | * name in the function prototype. 113 | */ 114 | #if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) 115 | /* Win32 but not WinCE */ 116 | # define KHRONOS_APIENTRY __stdcall 117 | #else 118 | # define KHRONOS_APIENTRY 119 | #endif 120 | 121 | /*------------------------------------------------------------------------- 122 | * Definition of KHRONOS_APIATTRIBUTES 123 | *------------------------------------------------------------------------- 124 | * This follows the closing parenthesis of the function prototype arguments. 125 | */ 126 | #if defined (__ARMCC_2__) 127 | #define KHRONOS_APIATTRIBUTES __softfp 128 | #else 129 | #define KHRONOS_APIATTRIBUTES 130 | #endif 131 | 132 | /*------------------------------------------------------------------------- 133 | * basic type definitions 134 | *-----------------------------------------------------------------------*/ 135 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) 136 | 137 | 138 | /* 139 | * Using 140 | */ 141 | #include 142 | typedef int32_t khronos_int32_t; 143 | typedef uint32_t khronos_uint32_t; 144 | typedef int64_t khronos_int64_t; 145 | typedef uint64_t khronos_uint64_t; 146 | #define KHRONOS_SUPPORT_INT64 1 147 | #define KHRONOS_SUPPORT_FLOAT 1 148 | 149 | #elif defined(__VMS ) || defined(__sgi) 150 | 151 | /* 152 | * Using 153 | */ 154 | #include 155 | typedef int32_t khronos_int32_t; 156 | typedef uint32_t khronos_uint32_t; 157 | typedef int64_t khronos_int64_t; 158 | typedef uint64_t khronos_uint64_t; 159 | #define KHRONOS_SUPPORT_INT64 1 160 | #define KHRONOS_SUPPORT_FLOAT 1 161 | 162 | #elif defined(_WIN32) && !defined(__SCITECH_SNAP__) 163 | 164 | /* 165 | * Win32 166 | */ 167 | typedef __int32 khronos_int32_t; 168 | typedef unsigned __int32 khronos_uint32_t; 169 | typedef __int64 khronos_int64_t; 170 | typedef unsigned __int64 khronos_uint64_t; 171 | #define KHRONOS_SUPPORT_INT64 1 172 | #define KHRONOS_SUPPORT_FLOAT 1 173 | 174 | #elif defined(__sun__) || defined(__digital__) 175 | 176 | /* 177 | * Sun or Digital 178 | */ 179 | typedef int khronos_int32_t; 180 | typedef unsigned int khronos_uint32_t; 181 | #if defined(__arch64__) || defined(_LP64) 182 | typedef long int khronos_int64_t; 183 | typedef unsigned long int khronos_uint64_t; 184 | #else 185 | typedef long long int khronos_int64_t; 186 | typedef unsigned long long int khronos_uint64_t; 187 | #endif /* __arch64__ */ 188 | #define KHRONOS_SUPPORT_INT64 1 189 | #define KHRONOS_SUPPORT_FLOAT 1 190 | 191 | #elif 0 192 | 193 | /* 194 | * Hypothetical platform with no float or int64 support 195 | */ 196 | typedef int khronos_int32_t; 197 | typedef unsigned int khronos_uint32_t; 198 | #define KHRONOS_SUPPORT_INT64 0 199 | #define KHRONOS_SUPPORT_FLOAT 0 200 | 201 | #else 202 | 203 | /* 204 | * Generic fallback 205 | */ 206 | #include 207 | typedef int32_t khronos_int32_t; 208 | typedef uint32_t khronos_uint32_t; 209 | typedef int64_t khronos_int64_t; 210 | typedef uint64_t khronos_uint64_t; 211 | #define KHRONOS_SUPPORT_INT64 1 212 | #define KHRONOS_SUPPORT_FLOAT 1 213 | 214 | #endif 215 | 216 | 217 | /* 218 | * Types that are (so far) the same on all platforms 219 | */ 220 | typedef signed char khronos_int8_t; 221 | typedef unsigned char khronos_uint8_t; 222 | typedef signed short int khronos_int16_t; 223 | typedef unsigned short int khronos_uint16_t; 224 | 225 | /* 226 | * Types that differ between LLP64 and LP64 architectures - in LLP64, 227 | * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears 228 | * to be the only LLP64 architecture in current use. 229 | */ 230 | #ifdef _WIN64 231 | typedef signed long long int khronos_intptr_t; 232 | typedef unsigned long long int khronos_uintptr_t; 233 | typedef signed long long int khronos_ssize_t; 234 | typedef unsigned long long int khronos_usize_t; 235 | #else 236 | typedef signed long int khronos_intptr_t; 237 | typedef unsigned long int khronos_uintptr_t; 238 | typedef signed long int khronos_ssize_t; 239 | typedef unsigned long int khronos_usize_t; 240 | #endif 241 | 242 | #if KHRONOS_SUPPORT_FLOAT 243 | /* 244 | * Float type 245 | */ 246 | typedef float khronos_float_t; 247 | #endif 248 | 249 | #if KHRONOS_SUPPORT_INT64 250 | /* Time types 251 | * 252 | * These types can be used to represent a time interval in nanoseconds or 253 | * an absolute Unadjusted System Time. Unadjusted System Time is the number 254 | * of nanoseconds since some arbitrary system event (e.g. since the last 255 | * time the system booted). The Unadjusted System Time is an unsigned 256 | * 64 bit value that wraps back to 0 every 584 years. Time intervals 257 | * may be either signed or unsigned. 258 | */ 259 | typedef khronos_uint64_t khronos_utime_nanoseconds_t; 260 | typedef khronos_int64_t khronos_stime_nanoseconds_t; 261 | #endif 262 | 263 | /* 264 | * Dummy value used to pad enum types to 32 bits. 265 | */ 266 | #ifndef KHRONOS_MAX_ENUM 267 | #define KHRONOS_MAX_ENUM 0x7FFFFFFF 268 | #endif 269 | 270 | /* 271 | * Enumerated boolean type 272 | * 273 | * Values other than zero should be considered to be true. Therefore 274 | * comparisons should not be made against KHRONOS_TRUE. 275 | */ 276 | typedef enum { 277 | KHRONOS_FALSE = 0, 278 | KHRONOS_TRUE = 1, 279 | KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM 280 | } khronos_boolean_enum_t; 281 | 282 | #endif /* __khrplatform_h_ */ 283 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # CrossWindow Demos 2 | 3 | [![cmake-img]][cmake-url] 4 | [![License][license-img]][license-url] 5 | 6 | A variety of demos showcasing how to use CrossWindow to build cross platform applications. 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/crosswindow-demos --recurse-submodules 15 | 16 | # 💿 go inside the folder 17 | cd crosswindow-demos 18 | 19 | # 👯 If you forget to `recurse-submodules` you can always run: 20 | git submodule update --init 21 | 22 | # 🔼 Go inside any demo, like for instance the Hello Triangle folder: 23 | cd src/04-cross-platform-hello-triangle/ 24 | 25 | ``` 26 | 27 | ## Demos 28 | 29 | ### Window Creation 30 | 31 | 32 | 33 | A basic example showing how to create a window. 34 | 35 | ### Events 36 | 37 | 38 | 39 | An example using every possible event in CrossWindow. 40 | 41 | ### Open / Save / Alert Dialogs 42 | 43 | 44 | 45 | Creating open / save dialogs for grabbing files. Creating alert messages for warnings, errors, or confirmations in your application. 46 | 47 | ### Cross Platform Hello Triangle 48 | 49 | ![Hello Triangle Cover Image](src/04-cross-platform-hello-triangle/assets/cover.jpg) 50 | 51 | An example showcasing the rendering of a simple triangle in every modern graphics API (Vulkan / DirectX 12 / DirectX 11 / OpenGL / Metal) and operating system. 52 | 53 | [cmake-img]: https://img.shields.io/badge/cmake-3.6-1f9948.svg?style=flat-square 54 | [cmake-url]: https://cmake.org/ 55 | [license-img]: https://img.shields.io/:license-mit-blue.svg?style=flat-square 56 | [license-url]: https://opensource.org/licenses/MIT -------------------------------------------------------------------------------- /src/01-window-creation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project Info 2 | 3 | cmake_minimum_required(VERSION 3.18 FATAL_ERROR) 4 | cmake_policy(VERSION 3.18) 5 | project(WindowCreation 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 | # Dependencies 34 | 35 | # CrossWindow 36 | add_subdirectory(../../external/crosswindow ${CMAKE_BINARY_DIR}/crosswindow) 37 | set_property(TARGET CrossWindow PROPERTY FOLDER "Dependencies") 38 | 39 | # ============================================================= 40 | 41 | # Sources 42 | 43 | file(GLOB_RECURSE FILE_SOURCES RELATIVE 44 | ${CMAKE_CURRENT_SOURCE_DIR} 45 | ${CMAKE_CURRENT_SOURCE_DIR}/src/XMain.cpp 46 | ${CMAKE_CURRENT_SOURCE_DIR}/src/${XGFX_API}Renderer.cpp 47 | ${CMAKE_CURRENT_SOURCE_DIR}/src/${XGFX_API}Renderer.mm 48 | ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h 49 | ) 50 | 51 | # Solution Filters 52 | foreach(source IN LISTS FILE_SOURCES) 53 | get_filename_component(source_path "${source}" PATH) 54 | string(REPLACE "/" "\\" source_path_msvc "${source_path}") 55 | string(REPLACE "src" "" source_path_final "${source_path_msvc}") 56 | source_group("${source_path_final}" FILES "${source}") 57 | endforeach() 58 | 59 | # ============================================================= 60 | 61 | # Finalize App 62 | 63 | xwin_add_executable( 64 | ${PROJECT_NAME} 65 | "${FILE_SOURCES}" 66 | ) 67 | 68 | # ============================================================= 69 | 70 | # Finish Dependencies 71 | 72 | target_link_libraries( 73 | ${PROJECT_NAME} 74 | CrossWindow 75 | ) 76 | 77 | # ============================================================= 78 | 79 | # Finish Settings 80 | 81 | # Change output dir to bin 82 | set_target_properties(${PROJECT_NAME} PROPERTIES 83 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 84 | ) 85 | # Change working directory to bin 86 | if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 87 | set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 88 | endif() 89 | -------------------------------------------------------------------------------- /src/01-window-creation/readme.md: -------------------------------------------------------------------------------- 1 | # Window Creation 2 | 3 | Creating windows in CrossWindow starts by describing exactly what kind of window you want from the start: 4 | 5 | ```cpp 6 | xwin::WindowDesc wdesc; 7 | wdesc.title = "My Window Title"; 8 | // ... 9 | ``` 10 | 11 | Then passing that descriptor struct as an argument to the `xwin::Window::create(xwin::WindowDesc desc)` function. 12 | 13 | ```cpp 14 | // 🖼️ First create a Window 15 | xwin::Window window; 16 | 17 | // 🚅 Create an event queue to process events from that window 18 | xwin::EventQueue eventQueue; 19 | 20 | // 🌟 Then initialize the window with OS specific data: 21 | window.create(wdesc, eventQueue); 22 | ``` 23 | 24 | ## Getting Started 25 | 26 | Be sure to have the following installed: 27 | 28 | - [CMake](https://cmake.org/) 29 | 30 | - An IDE such as [Visual Studio](https://visualstudio.microsoft.com/downloads/), [XCode](https://developer.apple.com/xcode/), or a compiler such as [GCC](https://gcc.gnu.org/). 31 | 32 | Then type the following in your terminal from this folder: 33 | 34 | ```bash 35 | # 👷 Make a build folder 36 | mkdir build 37 | cd build 38 | 39 | # 🖼️ To build your Visual Studio solution on Windows x64 40 | cmake .. -A x64 41 | 42 | # 🍎 To build your XCode project on Mac OS 43 | cmake .. -G Xcode 44 | 45 | # 🐧 To build your .make file on Linux 46 | cmake .. 47 | 48 | # 🔨 Build on any platform: 49 | cmake --build . 50 | ``` 51 | 52 | ### WebAssembly & Android 53 | 54 | For WebAssembly you'll need to have [Emscripten](http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html) installed. Assuming you have the SDK installed, do the following to build a WebAssembly project: 55 | 56 | ```bash 57 | # 🌐 For WebAssembly Projects 58 | mkdir build 59 | cd build 60 | cmake .. -DXWIN_OS=WASM -DCMAKE_TOOLCHAIN_FILE="$EMSDK/emscripten/1.38.1/cmake/Modules/Platform/Emscripten.cmake" -DCMAKE_BUILD_TYPE=Release 61 | 62 | # Run emconfigure with the normal configure command as an argument. 63 | $EMSDK/emscripten/emconfigure ./configure 64 | 65 | # Run emmake with the normal make to generate linked LLVM bitcode. 66 | $EMSDK/emscripten/emmake make 67 | 68 | # Compile the linked bitcode generated by make (project.bc) to JavaScript. 69 | # 'project.bc' should be replaced with the make output for your project (e.g. 'yourproject.so') 70 | $EMSDK/emscripten/emcc project.bc -o project.js 71 | ``` 72 | 73 | For more information visit the [Emscripten Docs on CMake](https://kripken.github.io/emscripten-site/docs/compiling/Building-Projects.html#using-libraries). 74 | 75 | For **Android Studio** you'll need to make a project, then edit your `build.gradle` file. 76 | 77 | ```groovy 78 | // 🤖 To build your Android Studio project 79 | android { 80 | ... 81 | externalNativeBuild { 82 | cmake { 83 | ... 84 | // Use the following syntax when passing arguments to variables: 85 | // arguments "-DVAR_NAME=ARGUMENT". 86 | arguments "-DXWIN_PROTOCOL=ANDROID", 87 | // The following line passes 'rtti' and 'exceptions' to 'ANDROID_CPP_FEATURES'. 88 | "-DANDROID_CPP_FEATURES=rtti exceptions" 89 | } 90 | } 91 | buildTypes {...} 92 | 93 | // Use this block to link Gradle to your CMake build script. 94 | externalNativeBuild { 95 | cmake {...} 96 | } 97 | } 98 | ``` 99 | -------------------------------------------------------------------------------- /src/01-window-creation/src/XMain.cpp: -------------------------------------------------------------------------------- 1 | #include "CrossWindow/CrossWindow.h" 2 | 3 | void xmain(int argc, const char** argv) 4 | { 5 | // 🖼️ Create Window Description 6 | xwin::WindowDesc windowDesc; 7 | windowDesc.name = "Test"; 8 | windowDesc.title = "My Title"; 9 | windowDesc.visible = true; 10 | windowDesc.width = 1280; 11 | windowDesc.height = 720; 12 | 13 | bool closed = false; 14 | 15 | // 🌟 Initialize 16 | xwin::Window window; 17 | xwin::EventQueue eventQueue; 18 | 19 | if (!window.create(windowDesc, eventQueue)) 20 | { return; } 21 | 22 | // 🏁 Engine loop 23 | bool isRunning = true; 24 | 25 | while (isRunning) 26 | { 27 | // ♻️ Update the event queue 28 | eventQueue.update(); 29 | 30 | // 🎈 Iterate through that queue: 31 | while (!eventQueue.empty()) 32 | { 33 | const xwin::Event& event = eventQueue.front(); 34 | 35 | if (event.type == xwin::EventType::MouseMove) 36 | { 37 | const xwin::MouseMoveData mouse = event.data.mouseMove; 38 | } 39 | if (event.type == xwin::EventType::Close) 40 | { 41 | window.close(); 42 | isRunning = false; 43 | } 44 | 45 | eventQueue.pop(); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/02-events/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 | // 🏁 Engine loop 20 | bool isRunning = true; 21 | while (isRunning) 22 | { 23 | // ♻️ Update the event queue 24 | eventQueue.update(); 25 | 26 | // 🎈 Iterate through that queue: 27 | while (!eventQueue.empty()) 28 | { 29 | //Update Events 30 | const xwin::Event& event = eventQueue.front(); 31 | 32 | // Mouse Input 33 | if (event.type == xwin::EventType::MouseInput) 34 | { 35 | const xwin::MouseInputData data = event.data.mouseInput; 36 | } 37 | 38 | // Mouse Wheel 39 | if (event.type == xwin::EventType::MouseWheel) 40 | { 41 | 42 | } 43 | 44 | // Mouse Movement 45 | if (event.type == xwin::EventType::MouseMove) 46 | { 47 | const xwin::MouseMoveData data = event.data.mouseMove; 48 | } 49 | 50 | // Resize 51 | if (event.type == xwin::EventType::Resize) 52 | { 53 | const xwin::ResizeData data = event.data.resize; 54 | } 55 | 56 | // Close 57 | if (event.type == xwin::EventType::Close) 58 | { 59 | window.close(); 60 | } 61 | 62 | eventQueue.pop(); 63 | } 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/03-open-save-alert/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 | // 🏁 Engine loop 20 | bool isRunning = true; 21 | while (isRunning) 22 | { 23 | // ♻️ Update the event queue 24 | eventQueue.update(); 25 | 26 | // 🎈 Iterate through that queue: 27 | while (!eventQueue.empty()) 28 | { 29 | //Update Events 30 | const xwin::Event& event = eventQueue.front(); 31 | 32 | // Mouse Input 33 | if (event.type == xwin::EventType::MouseInput) 34 | { 35 | const xwin::MouseInputData data = event.data.mouseInput; 36 | if (data.state == xwin::ButtonState::Released) 37 | { 38 | if (data.button == xwin::MouseInput::Left) 39 | { 40 | // 💾 Open File Save Dialog 41 | } 42 | 43 | if (data.button == xwin::MouseInput::Right) 44 | { 45 | // 📂 Open File Open Dialog 46 | } 47 | 48 | if (data.button == xwin::MouseInput::Middle) 49 | { 50 | // ❗ Open Alert 51 | } 52 | } 53 | } 54 | 55 | // Close 56 | if (event.type == xwin::EventType::Close) 57 | { 58 | window.close(); 59 | } 60 | 61 | eventQueue.pop(); 62 | } 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project Info 2 | 3 | cmake_minimum_required(VERSION 3.18 FATAL_ERROR) 4 | cmake_policy(VERSION 3.18) 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 | elseif(XGFX_API STREQUAL "OPENGL") 76 | add_subdirectory(../../external/glad ${CMAKE_BINARY_DIR}/glad) 77 | target_include_directories( 78 | Glad 79 | PUBLIC ../../external/opengl-registry/api 80 | ) 81 | set(XGFX_LIBRARY Glad) 82 | set_property(TARGET Glad PROPERTY FOLDER "Dependencies") 83 | elseif(XGFX_API STREQUAL "METAL") 84 | find_library(XGFX_LIBRARY Metal) 85 | endif() 86 | 87 | 88 | # ============================================================= 89 | 90 | # Sources 91 | 92 | file(GLOB_RECURSE FILE_SOURCES RELATIVE 93 | ${CMAKE_CURRENT_SOURCE_DIR} 94 | ${CMAKE_CURRENT_SOURCE_DIR}/src/XMain.cpp 95 | ${CMAKE_CURRENT_SOURCE_DIR}/src/${XGFX_API}Renderer.cpp 96 | ${CMAKE_CURRENT_SOURCE_DIR}/src/${XGFX_API}Renderer.mm 97 | ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h 98 | ) 99 | 100 | # Solution Filters 101 | foreach(source IN LISTS FILE_SOURCES) 102 | get_filename_component(source_path "${source}" PATH) 103 | string(REPLACE "/" "\\" source_path_msvc "${source_path}") 104 | string(REPLACE "src" "" source_path_final "${source_path_msvc}") 105 | source_group("${source_path_final}" FILES "${source}") 106 | endforeach() 107 | 108 | # ============================================================= 109 | 110 | # Finalize App 111 | 112 | xwin_add_executable( 113 | ${PROJECT_NAME} 114 | "${FILE_SOURCES}" 115 | ) 116 | 117 | # ============================================================= 118 | 119 | # Finish Dependencies 120 | 121 | target_link_libraries( 122 | ${PROJECT_NAME} 123 | ${XGFX_LIBRARY} 124 | CrossWindowGraphics 125 | CrossWindow 126 | ) 127 | 128 | target_include_directories( 129 | ${PROJECT_NAME} 130 | PUBLIC "../../external/vectormath" 131 | PUBLIC ${VULKAN_INCLUDE_DIR} 132 | ) 133 | 134 | target_compile_definitions( 135 | ${PROJECT_NAME} 136 | PUBLIC XGFX_${XGFX_API}=1 137 | ) 138 | 139 | # ============================================================= 140 | 141 | # Finish Settings 142 | 143 | # Change output dir to bin 144 | set_target_properties(${PROJECT_NAME} PROPERTIES 145 | RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin 146 | ) 147 | # Change working directory to top dir to access `assets/shaders/` folder 148 | if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 149 | set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/..) 150 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) 151 | endif() 152 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/assets/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alaingalvan/CrossWindow-Demos/1cfd1f36d4e1afaebc9cf1b0ce2c98d05a7482d5/src/04-cross-platform-hello-triangle/assets/cover.jpg -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/assets/shaders/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 | } -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/assets/shaders/triangle.frag.glsl: -------------------------------------------------------------------------------- 1 | #version 310 es 2 | precision mediump float; 3 | precision highp int; 4 | 5 | layout(location = 0) out highp vec4 outFragColor; 6 | layout(location = 0) in highp vec3 inColor; 7 | 8 | void main() 9 | { 10 | outFragColor = vec4(inColor, 1.0); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/assets/shaders/triangle.frag.hlsl: -------------------------------------------------------------------------------- 1 | static float4 outFragColor; 2 | static float3 inColor; 3 | 4 | struct SPIRV_Cross_Input 5 | { 6 | float3 inColor : COLOR; 7 | }; 8 | 9 | struct SPIRV_Cross_Output 10 | { 11 | float4 outFragColor : SV_Target0; 12 | }; 13 | 14 | void frag_main() 15 | { 16 | outFragColor = float4(inColor, 1.0f); 17 | } 18 | 19 | SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) 20 | { 21 | inColor = stage_input.inColor; 22 | frag_main(); 23 | SPIRV_Cross_Output stage_output; 24 | stage_output.outFragColor = outFragColor; 25 | return stage_output; 26 | } 27 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/assets/shaders/triangle.frag.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct main0_in 7 | { 8 | float3 inColor [[user(locn0)]]; 9 | }; 10 | 11 | struct main0_out 12 | { 13 | float4 outFragColor [[color(0)]]; 14 | }; 15 | 16 | fragment main0_out main0(main0_in in [[stage_in]]) 17 | { 18 | main0_out out = {}; 19 | out.outFragColor = float4(in.inColor, 1.0); 20 | return out; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/assets/shaders/triangle.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alaingalvan/CrossWindow-Demos/1cfd1f36d4e1afaebc9cf1b0ce2c98d05a7482d5/src/04-cross-platform-hello-triangle/assets/shaders/triangle.frag.spv -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/assets/shaders/triangle.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #extension GL_ARB_separate_shader_objects : enable 4 | #extension GL_ARB_shading_language_420pack : enable 5 | 6 | layout (location = 0) in vec3 inPos; 7 | layout (location = 1) in vec3 inColor; 8 | 9 | layout (binding = 0) uniform UBO 10 | { 11 | mat4 projectionMatrix; 12 | mat4 modelMatrix; 13 | mat4 viewMatrix; 14 | } ubo; 15 | 16 | layout (location = 0) out vec3 outColor; 17 | 18 | out gl_PerVertex 19 | { 20 | vec4 gl_Position; 21 | }; 22 | 23 | 24 | void main() 25 | { 26 | outColor = inColor; 27 | gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * vec4(inPos.xyz, 1.0); 28 | } -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/assets/shaders/triangle.vert.glsl: -------------------------------------------------------------------------------- 1 | #version 310 es 2 | 3 | layout(binding = 0, std140) uniform UBO 4 | { 5 | mat4 projectionMatrix; 6 | mat4 modelMatrix; 7 | mat4 viewMatrix; 8 | } ubo; 9 | 10 | layout(location = 0) out vec3 outColor; 11 | layout(location = 1) in vec3 inColor; 12 | layout(location = 0) in vec3 inPos; 13 | 14 | void main() 15 | { 16 | outColor = inColor; 17 | gl_Position = ((ubo.projectionMatrix * ubo.viewMatrix) * ubo.modelMatrix) * vec4(inPos, 1.0); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/assets/shaders/triangle.vert.hlsl: -------------------------------------------------------------------------------- 1 | cbuffer ubo : register(b0) 2 | { 3 | row_major float4x4 ubo_projectionMatrix : packoffset(c0); 4 | row_major float4x4 ubo_modelMatrix : packoffset(c4); 5 | row_major float4x4 ubo_viewMatrix : packoffset(c8); 6 | }; 7 | 8 | static float4 gl_Position; 9 | static float3 outColor; 10 | static float3 inColor; 11 | static float3 inPos; 12 | 13 | struct SPIRV_Cross_Input 14 | { 15 | float3 inPos : POSITION; 16 | float3 inColor : COLOR; 17 | }; 18 | 19 | struct SPIRV_Cross_Output 20 | { 21 | float3 outColor : COLOR; 22 | float4 gl_Position : SV_Position; 23 | }; 24 | 25 | void vert_main() 26 | { 27 | outColor = inColor; 28 | gl_Position = mul(float4(inPos, 1.0f), mul(ubo_modelMatrix, mul(ubo_viewMatrix, ubo_projectionMatrix))); 29 | } 30 | 31 | SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) 32 | { 33 | inColor = stage_input.inColor; 34 | inPos = stage_input.inPos; 35 | vert_main(); 36 | SPIRV_Cross_Output stage_output; 37 | stage_output.gl_Position = gl_Position; 38 | stage_output.outColor = outColor; 39 | return stage_output; 40 | } 41 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/assets/shaders/triangle.vert.msl: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace metal; 5 | 6 | struct UBO 7 | { 8 | float4x4 projectionMatrix; 9 | float4x4 modelMatrix; 10 | float4x4 viewMatrix; 11 | }; 12 | 13 | struct main0_in 14 | { 15 | float3 inColor [[attribute(1)]]; 16 | float3 inPos [[attribute(0)]]; 17 | }; 18 | 19 | struct main0_out 20 | { 21 | float3 outColor [[user(locn0)]]; 22 | float4 gl_Position [[position]]; 23 | }; 24 | 25 | vertex main0_out main0(main0_in in [[stage_in]], constant UBO& ubo [[buffer(1)]]) 26 | { 27 | main0_out out = {}; 28 | out.outColor = in.inColor; 29 | out.gl_Position = ((ubo.projectionMatrix * ubo.viewMatrix) * ubo.modelMatrix) * float4(in.inPos, 1.0); 30 | return out; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/assets/shaders/triangle.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alaingalvan/CrossWindow-Demos/1cfd1f36d4e1afaebc9cf1b0ce2c98d05a7482d5/src/04-cross-platform-hello-triangle/assets/shaders/triangle.vert.spv -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/readme.md: -------------------------------------------------------------------------------- 1 | # Hello Triangle 2 | 3 | ![Cover Image](assets/cover.jpg) 4 | 5 | A simple hello triangle example that uses all modern graphics APIs. 6 | 7 | - 🌋 Vulkan 8 | 9 | - ❎ DirectX 12 10 | 11 | - ✖️ DirectX 11 12 | 13 | - ⚪ OpenGL 14 | 15 | - 🤖 Metal 16 | 17 | ## Setup 18 | 19 | ## Getting Started 20 | 21 | Be sure to have the following installed: 22 | 23 | - [CMake](https://cmake.org/) 24 | 25 | - An IDE such as [Visual Studio](https://visualstudio.microsoft.com/downloads/), [XCode](https://developer.apple.com/xcode/), or a compiler such as [GCC](https://gcc.gnu.org/). 26 | 27 | Then type the following in your terminal from this folder: 28 | 29 | ```bash 30 | # 👷 Make a build folder 31 | mkdir build 32 | cd build 33 | 34 | # 🖼️ To build your Visual Studio solution on Windows x64 35 | cmake .. -A x64 36 | 37 | # 🍎 To build your XCode project on Mac OS 38 | cmake .. -G Xcode 39 | 40 | # 🐧 To build your .make file on Linux 41 | cmake .. 42 | 43 | # 🔨 Build on any platform: 44 | cmake --build . 45 | ``` 46 | 47 | ### WebAssembly & Android 48 | 49 | For WebAssembly you'll need to have [Emscripten](http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html) installed. Assuming you have the SDK installed, do the following to build a WebAssembly project: 50 | 51 | ```bash 52 | # 🌐 For WebAssembly Projects 53 | mkdir build 54 | cd build 55 | cmake .. -DXWIN_OS=WASM -DCMAKE_TOOLCHAIN_FILE="$EMSDK/emscripten/1.38.1/cmake/Modules/Platform/Emscripten.cmake" -DCMAKE_BUILD_TYPE=Release 56 | 57 | # Run emconfigure with the normal configure command as an argument. 58 | $EMSDK/emscripten/emconfigure ./configure 59 | 60 | # Run emmake with the normal make to generate linked LLVM bitcode. 61 | $EMSDK/emscripten/emmake make 62 | 63 | # Compile the linked bitcode generated by make (project.bc) to JavaScript. 64 | # 'project.bc' should be replaced with the make output for your project (e.g. 'yourproject.so') 65 | $EMSDK/emscripten/emcc project.bc -o project.js 66 | ``` 67 | 68 | For more information visit the [Emscripten Docs on CMake](https://kripken.github.io/emscripten-site/docs/compiling/Building-Projects.html#using-libraries). 69 | 70 | For **Android Studio** you'll need to make a project, then edit your `build.gradle` file. 71 | 72 | ```groovy 73 | // 🤖 To build your Android Studio project 74 | android { 75 | ... 76 | externalNativeBuild { 77 | cmake { 78 | ... 79 | // Use the following syntax when passing arguments to variables: 80 | // arguments "-DVAR_NAME=ARGUMENT". 81 | arguments "-DXWIN_PROTOCOL=ANDROID", 82 | // The following line passes 'rtti' and 'exceptions' to 'ANDROID_CPP_FEATURES'. 83 | "-DANDROID_CPP_FEATURES=rtti exceptions" 84 | } 85 | } 86 | buildTypes {...} 87 | 88 | // Use this block to link Gradle to your CMake build script. 89 | externalNativeBuild { 90 | cmake {...} 91 | } 92 | } 93 | ``` 94 | 95 | ### **Optional** - Build Shader Compiler/Transpiler 96 | 97 | If you want to edit the shaders used in this example, you'll need to compile / transpile those shaders. If not you can skip this. 98 | 99 | First we're going to need to build our tools to compile our shader, GLSLangValidator (comes with the Vulkan SDK, but is also bundled as a submodule here) and [SPIRV-Cross](https://github.com/KhronosGroup/SPIRV-Cross). 100 | 101 | ```bash 102 | # 🔨 Let's build SPIRV-Cross and GLSLangValidator to compile our shaders 103 | # These were included in the `/external/` folder of this repo: 104 | cd ../../external/spirv-cross 105 | mkdir spirv-cross 106 | cd spirv-cross 107 | cmake .. 108 | cmake --build . --config Release 109 | 110 | cd ../glslangvalidator 111 | mkdir build 112 | cd build 113 | cmake .. 114 | cmake --build . --config Release 115 | 116 | # Go to the shaders folder 117 | cd ../../demos/hello-triangle/assets/shaders 118 | ``` 119 | 120 | #### Compile Shaders 121 | 122 | Shaders are compiled already and bundled in the app, but if you want you can recompile them. 123 | 124 | Sorry for all the going up/down folders, this is to make sure files go where they're expected: 125 | 126 | ```bash 127 | # 🌋 Compile shaders to SPIR-V binary 128 | ../../../../external/glslang/build/StandAlone/Release/glslangValidator -V triangle.vert -o triangle.vert.spv 129 | ../../../../external/glslang/build/StandAlone/Release/glslangValidator -V triangle.frag -o triangle.frag.spv 130 | 131 | # ❎ HLSL 132 | ../../../../external/spirv-cross/spirv-cross/Release/spirv-cross triangle.vert.spv --hlsl --shader-model 50 --set-hlsl-vertex-input-semantic 0 POSITION --set-hlsl-vertex-input-semantic 1 COLOR --output triangle.vert.hlsl 133 | ../../../../external/spirv-cross/spirv-cross/Release/spirv-cross triangle.frag.spv --hlsl --shader-model 50 --set-hlsl-vertex-input-semantic 0 COLOR --output triangle.frag.hlsl 134 | 135 | # ⚪ OpenGL ES 3.1 136 | ../../../../external/spirv-cross/spirv-cross/Release/spirv-cross .triangle.vert.spv --version 310 --es --output .triangle.vert.glsl 137 | ../../../../external/spirv-cross/spirv-cross/Release/spirv-cross .triangle.frag.spv --version 310 --es --output .triangle.frag.glsl 138 | 139 | # 🤖 Metal 140 | ../../../../external/spirv-cross/spirv-cross/Release/spirv-cross .triangle.vert.spv --msl --output .triangle.vert.msl 141 | ../../../../external/spirv-cross/spirv-cross/Release/spirv-cross .triangle.frag.spv --msl --output .triangle.frag.msl 142 | ``` 143 | 144 | > Metal shader bindings don't always correspond with Vulkan shader bindings, so you'll want to change the uniform buffer location from 0 to 1 to let 0 bind to the vertex buffer. 145 | > ```cpp 146 | > // change buffer(0) to buffer(1) 147 | > vertex main0_out main0(main0_in in [[stage_in]], constant UBO& ubo [[buffer(0)]]) 148 | > ``` 149 | 150 | ## Credits 151 | 152 | - [Sascha Willems](https://twitter.com/SaschaWillems2) - His Hello Triangle example was modified to use Vulkan's official C++ API, Sony's VectorMath, and to use CrossWindow to handle window creation. 153 | 154 | - [Bob Brown](https://github.com/bobbrow) - For his work on the [DirectX 12 Samples](https://github.com/Microsoft/DirectX-Graphics-Samples) from which the DirectX 12 renderer is based off. 155 | 156 | - [Matthias Moulin](https://twitter.com/matt77hias) - For his [DirectX 11 Samples](https://github.com/matt77hias/RasterTek) work for with the DirectX 11 renderer is based off of. 157 | 158 | The OpenGL example is adapted from my [OpenGL Seed](https://github.com/alaingalvan/opengl-seed) repo showcasing how to use Qt with OpenGL. -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/src/DirectX11Renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "Renderer.h" 2 | 3 | // DirectX utils 4 | 5 | inline void ThrowIfFailed(HRESULT hr) 6 | { 7 | if (FAILED(hr)) 8 | { 9 | throw std::exception(); 10 | } 11 | } 12 | 13 | // Renderer 14 | 15 | Renderer::Renderer(xwin::Window& window) 16 | { 17 | mVsync = true; 18 | mWindow = nullptr; 19 | 20 | mFactory = nullptr; 21 | mAdapter = nullptr; 22 | mAdapterOutput = nullptr; 23 | mDevice = nullptr; 24 | mDeviceContext = nullptr; 25 | #if defined(_DEBUG) 26 | mDebugController = nullptr; 27 | #endif 28 | 29 | mVertexBuffer = nullptr; 30 | mIndexBuffer = nullptr; 31 | mLayout = nullptr; 32 | mUniformBuffer = nullptr; 33 | mVertexShader = nullptr; 34 | mPixelShader = nullptr; 35 | 36 | mSwapchain = nullptr; 37 | mBackbufferTex = nullptr; 38 | mDepthStencilBuffer = nullptr; 39 | mRenderTargetView = nullptr; 40 | 41 | mDepthStencilState = nullptr; 42 | mDepthStencilView = nullptr; 43 | mRasterState = nullptr; 44 | 45 | initializeAPI(window); 46 | resize(mWidth, mHeight); 47 | initializeResources(); 48 | tStart = std::chrono::high_resolution_clock::now(); 49 | } 50 | 51 | Renderer::~Renderer() 52 | { 53 | if (mSwapchain != nullptr) 54 | { 55 | mSwapchain->SetFullscreenState(false, nullptr); 56 | mSwapchain->Release(); 57 | mSwapchain = nullptr; 58 | } 59 | destroyFrameBuffer(); 60 | destroyResources(); 61 | destroyAPI(); 62 | } 63 | 64 | void Renderer::initializeAPI(xwin::Window& window) 65 | { 66 | // The renderer needs the window when resizing the swapchain 67 | mWindow = &window; 68 | xwin::WindowDesc desc = window.getDesc(); 69 | mWidth = desc.width; 70 | mHeight = desc.height; 71 | 72 | ThrowIfFailed(CreateDXGIFactory(IID_PPV_ARGS(&mFactory))); 73 | 74 | ThrowIfFailed(mFactory->EnumAdapters(0, &mAdapter)); 75 | 76 | ThrowIfFailed(mAdapter->EnumOutputs(0, &mAdapterOutput)); 77 | 78 | // Get the number of modes that fit the DXGI_FORMAT_R8G8B8A8_UNORM display format for the adapter output (monitor). 79 | unsigned int numModes; 80 | DXGI_MODE_DESC* displayModeList; 81 | ThrowIfFailed(mAdapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL)); 82 | 83 | // Create a list to hold all the possible display modes for this monitor/video card combination. 84 | displayModeList = new DXGI_MODE_DESC[numModes]; 85 | 86 | // Now fill the display mode list structures. 87 | ThrowIfFailed(mAdapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList)); 88 | 89 | // Now go through all the display modes and find the one that matches the screen width and height. 90 | // When a match is found store the numerator and denominator of the refresh rate for that monitor. 91 | mNumerator = 0; 92 | mDenominator = 1; 93 | for (size_t i = 0; i < numModes; i++) 94 | { 95 | if (displayModeList[i].Width == mWidth && displayModeList[i].Height == mHeight) 96 | { 97 | mNumerator = displayModeList[i].RefreshRate.Numerator; 98 | mDenominator = displayModeList[i].RefreshRate.Denominator; 99 | break; 100 | } 101 | } 102 | 103 | // Release the display mode list. 104 | delete[] displayModeList; 105 | displayModeList = nullptr; 106 | 107 | D3D_FEATURE_LEVEL featureLevelInputs[7] = 108 | { 109 | D3D_FEATURE_LEVEL_11_1, 110 | D3D_FEATURE_LEVEL_11_0, 111 | D3D_FEATURE_LEVEL_10_1, 112 | D3D_FEATURE_LEVEL_10_0, 113 | D3D_FEATURE_LEVEL_9_3, 114 | D3D_FEATURE_LEVEL_9_2, 115 | D3D_FEATURE_LEVEL_9_1 116 | }; 117 | 118 | D3D_FEATURE_LEVEL featureLevelOutputs = D3D_FEATURE_LEVEL_11_1; 119 | 120 | ThrowIfFailed(D3D11CreateDevice( 121 | mAdapter, 122 | D3D_DRIVER_TYPE_UNKNOWN, 123 | 0, 124 | D3D11_CREATE_DEVICE_SINGLETHREADED | D3D11_CREATE_DEVICE_DEBUG, 125 | featureLevelInputs, 126 | 7u, 127 | D3D11_SDK_VERSION, 128 | &mDevice, 129 | &featureLevelOutputs, 130 | &mDeviceContext 131 | )); 132 | 133 | #if defined(_DEBUG) 134 | // Enable the debug layer (requires the Graphics Tools "optional feature"). 135 | // NOTE: Enabling the debug layer after device creation will invalidate the active device. 136 | ThrowIfFailed(mDevice->QueryInterface(IID_PPV_ARGS(&mDebugController))); 137 | #endif 138 | } 139 | 140 | void Renderer::destroyAPI() 141 | { 142 | mDeviceContext->ClearState(); 143 | mDeviceContext->Flush(); 144 | 145 | mDevice->Release(); 146 | mDevice = nullptr; 147 | 148 | mDeviceContext->Release(); 149 | mDeviceContext = nullptr; 150 | 151 | mAdapterOutput->Release(); 152 | mAdapterOutput = nullptr; 153 | 154 | mAdapter->Release(); 155 | mAdapter = nullptr; 156 | 157 | mFactory->Release(); 158 | mFactory = nullptr; 159 | 160 | #if defined(_DEBUG) 161 | // Report on any remaining objects that haven't been deallocated 162 | 163 | D3D11_RLDO_FLAGS rldoFlags = D3D11_RLDO_SUMMARY | D3D11_RLDO_DETAIL | D3D11_RLDO_IGNORE_INTERNAL; 164 | mDebugController->ReportLiveDeviceObjects(rldoFlags); 165 | 166 | mDebugController->Release(); 167 | mDebugController = nullptr; 168 | #endif 169 | } 170 | 171 | void Renderer::initializeResources() 172 | { 173 | D3D11_INPUT_ELEMENT_DESC polygonLayout[2]; 174 | unsigned int numElements; 175 | D3D11_BUFFER_DESC uniformBufferDesc; 176 | 177 | 178 | // Initialize the pointers this function will use to null. 179 | 180 | ID3DBlob* vertexShader; 181 | ID3DBlob* pixelShader; 182 | ID3DBlob* errors; 183 | 184 | #if defined(_DEBUG) 185 | // Enable better shader debugging with the graphics debugging tools. 186 | UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; 187 | #else 188 | UINT compileFlags = 0; 189 | #endif 190 | std::string path = ""; 191 | char pBuf[1024]; 192 | 193 | _getcwd(pBuf, 1024); 194 | path = pBuf; 195 | path += "\\"; 196 | std::wstring wpath = std::wstring(path.begin(), path.end()); 197 | 198 | std::wstring vertPath = wpath + L"assets/shaders/triangle.vert.hlsl"; 199 | std::wstring fragPath = wpath + L"assets/shaders/triangle.frag.hlsl"; 200 | 201 | try 202 | { 203 | ThrowIfFailed(D3DCompileFromFile(vertPath.c_str(), nullptr, nullptr, "main", "vs_5_0", compileFlags, 0, &vertexShader, &errors)); 204 | ThrowIfFailed(D3DCompileFromFile(fragPath.c_str(), nullptr, nullptr, "main", "ps_5_0", compileFlags, 0, &pixelShader, &errors)); 205 | } 206 | catch (std::exception e) 207 | { 208 | const char* errStr = (const char*)errors->GetBufferPointer(); 209 | std::cout << errStr; 210 | } 211 | 212 | // Create the vertex shader from the buffer. 213 | ThrowIfFailed(mDevice->CreateVertexShader(vertexShader->GetBufferPointer(), vertexShader->GetBufferSize(), NULL, &mVertexShader)); 214 | 215 | // Create the pixel shader from the buffer. 216 | ThrowIfFailed(mDevice->CreatePixelShader(pixelShader->GetBufferPointer(), pixelShader->GetBufferSize(), NULL, &mPixelShader)); 217 | 218 | // Create the vertex input layout description. 219 | // This setup needs to match the VertexType stucture in the ModelClass and in the shader. 220 | polygonLayout[0].SemanticName = "POSITION"; 221 | polygonLayout[0].SemanticIndex = 0; 222 | polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; 223 | polygonLayout[0].InputSlot = 0; 224 | polygonLayout[0].AlignedByteOffset = 0; 225 | polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; 226 | polygonLayout[0].InstanceDataStepRate = 0; 227 | 228 | polygonLayout[1].SemanticName = "COLOR"; 229 | polygonLayout[1].SemanticIndex = 0; 230 | polygonLayout[1].Format = DXGI_FORMAT_R32G32B32_FLOAT; 231 | polygonLayout[1].InputSlot = 0; 232 | polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; 233 | polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; 234 | polygonLayout[1].InstanceDataStepRate = 0; 235 | 236 | // Get a count of the elements in the layout. 237 | numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); 238 | 239 | // Create the vertex input layout. 240 | ThrowIfFailed(mDevice->CreateInputLayout(polygonLayout, numElements, vertexShader->GetBufferPointer(), vertexShader->GetBufferSize(), &mLayout)); 241 | 242 | // Release the vertex shader buffer and pixel shader buffer since they are no longer needed. 243 | vertexShader->Release(); 244 | vertexShader = 0; 245 | 246 | pixelShader->Release(); 247 | pixelShader = 0; 248 | 249 | // Setup the description of the dynamic matrix constant buffer that is in the vertex shader. 250 | uniformBufferDesc.Usage = D3D11_USAGE_DYNAMIC; 251 | uniformBufferDesc.ByteWidth = sizeof(uboVS); 252 | uniformBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; 253 | uniformBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; 254 | uniformBufferDesc.MiscFlags = 0; 255 | uniformBufferDesc.StructureByteStride = 0; 256 | 257 | // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class. 258 | ThrowIfFailed(mDevice->CreateBuffer(&uniformBufferDesc, NULL, &mUniformBuffer)); 259 | 260 | D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc; 261 | D3D11_SUBRESOURCE_DATA vertexData, indexData; 262 | 263 | // Set up the description of the static vertex buffer. 264 | vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; 265 | vertexBufferDesc.ByteWidth = sizeof(Vertex) * 3; 266 | vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; 267 | vertexBufferDesc.CPUAccessFlags = 0; 268 | vertexBufferDesc.MiscFlags = 0; 269 | vertexBufferDesc.StructureByteStride = 0; 270 | 271 | // Give the subresource structure a pointer to the vertex data. 272 | vertexData.pSysMem = mVertexBufferData; 273 | vertexData.SysMemPitch = 0; 274 | vertexData.SysMemSlicePitch = 0; 275 | 276 | // Now create the vertex buffer. 277 | ThrowIfFailed(mDevice->CreateBuffer(&vertexBufferDesc, &vertexData, &mVertexBuffer)); 278 | 279 | // Set up the description of the static index buffer. 280 | indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; 281 | indexBufferDesc.ByteWidth = sizeof(unsigned) * 3; 282 | indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; 283 | indexBufferDesc.CPUAccessFlags = 0; 284 | indexBufferDesc.MiscFlags = 0; 285 | indexBufferDesc.StructureByteStride = 0; 286 | 287 | // Give the subresource structure a pointer to the index data. 288 | indexData.pSysMem = mIndexBufferData; 289 | indexData.SysMemPitch = 0; 290 | indexData.SysMemSlicePitch = 0; 291 | 292 | // Create the index buffer. 293 | ThrowIfFailed(mDevice->CreateBuffer(&indexBufferDesc, &indexData, &mIndexBuffer)); 294 | 295 | // Graphics Pipeline 296 | D3D11_RASTERIZER_DESC rasterDesc; 297 | 298 | // Setup the raster description which will determine how and what polygons will be drawn. 299 | rasterDesc.AntialiasedLineEnable = false; 300 | rasterDesc.CullMode = D3D11_CULL_NONE; 301 | rasterDesc.DepthBias = 0; 302 | rasterDesc.DepthBiasClamp = 0.0f; 303 | rasterDesc.DepthClipEnable = true; 304 | rasterDesc.FillMode = D3D11_FILL_SOLID; 305 | rasterDesc.FrontCounterClockwise = false; 306 | rasterDesc.MultisampleEnable = false; 307 | rasterDesc.ScissorEnable = false; 308 | rasterDesc.SlopeScaledDepthBias = 0.0f; 309 | 310 | // Create the rasterizer state from the description we just filled out. 311 | ThrowIfFailed(mDevice->CreateRasterizerState(&rasterDesc, &mRasterState)); 312 | 313 | // Now set the rasterizer state. 314 | mDeviceContext->RSSetState(mRasterState); 315 | } 316 | 317 | void Renderer::destroyResources() 318 | { 319 | if (mVertexBuffer) 320 | { 321 | mVertexBuffer->Release(); 322 | mVertexBuffer = nullptr; 323 | } 324 | 325 | if (mIndexBuffer) 326 | { 327 | mIndexBuffer->Release(); 328 | mIndexBuffer = nullptr; 329 | } 330 | 331 | if (mLayout) 332 | { 333 | mLayout->Release(); 334 | mLayout = nullptr; 335 | } 336 | 337 | if (mUniformBuffer) 338 | { 339 | mUniformBuffer->Release(); 340 | mUniformBuffer = nullptr; 341 | } 342 | 343 | if (mVertexShader) 344 | { 345 | mVertexShader->Release(); 346 | mVertexShader = nullptr; 347 | } 348 | 349 | if (mPixelShader) 350 | { 351 | mPixelShader->Release(); 352 | mPixelShader = nullptr; 353 | } 354 | 355 | if (mRasterState) 356 | { 357 | mRasterState->Release(); 358 | mRasterState = nullptr; 359 | } 360 | } 361 | 362 | void Renderer::initFrameBuffer() 363 | { 364 | xwin::WindowDesc desc = mWindow->getDesc(); 365 | 366 | // Get the pointer to the back buffer. 367 | ThrowIfFailed(mSwapchain->GetBuffer(0, IID_PPV_ARGS(&mBackbufferTex))); 368 | 369 | // Create the render target view with the back buffer pointer. 370 | ThrowIfFailed(mDevice->CreateRenderTargetView(mBackbufferTex, NULL, &mRenderTargetView)); 371 | 372 | D3D11_TEXTURE2D_DESC depthBufferDesc; 373 | D3D11_DEPTH_STENCIL_DESC depthStencilDesc; 374 | D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc; 375 | 376 | // Initialize the description of the depth buffer. 377 | ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc)); 378 | 379 | // Set up the description of the depth buffer. 380 | depthBufferDesc.Width = mWidth; 381 | depthBufferDesc.Height = mHeight; 382 | depthBufferDesc.MipLevels = 1; 383 | depthBufferDesc.ArraySize = 1; 384 | depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; 385 | depthBufferDesc.SampleDesc.Count = 1; 386 | depthBufferDesc.SampleDesc.Quality = 0; 387 | depthBufferDesc.Usage = D3D11_USAGE_DEFAULT; 388 | depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; 389 | depthBufferDesc.CPUAccessFlags = 0; 390 | depthBufferDesc.MiscFlags = 0; 391 | 392 | // Create the texture for the depth buffer using the filled out description. 393 | ThrowIfFailed(mDevice->CreateTexture2D(&depthBufferDesc, NULL, &mDepthStencilBuffer)); 394 | 395 | // Initialize the description of the stencil state. 396 | ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc)); 397 | 398 | // Set up the description of the stencil state. 399 | depthStencilDesc.DepthEnable = true; 400 | depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; 401 | depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS; 402 | 403 | depthStencilDesc.StencilEnable = true; 404 | depthStencilDesc.StencilReadMask = 0xFF; 405 | depthStencilDesc.StencilWriteMask = 0xFF; 406 | 407 | // Stencil operations if pixel is front-facing. 408 | depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; 409 | depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; 410 | depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; 411 | depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; 412 | 413 | // Stencil operations if pixel is back-facing. 414 | depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; 415 | depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; 416 | depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; 417 | depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; 418 | 419 | // Create the depth stencil state. 420 | ThrowIfFailed(mDevice->CreateDepthStencilState(&depthStencilDesc, &mDepthStencilState)); 421 | 422 | // Set the depth stencil state. 423 | mDeviceContext->OMSetDepthStencilState(mDepthStencilState, 1); 424 | 425 | // Initialize the depth stencil view. 426 | ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc)); 427 | 428 | // Set up the depth stencil view description. 429 | depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; 430 | depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; 431 | depthStencilViewDesc.Texture2D.MipSlice = 0; 432 | 433 | // Create the depth stencil view. 434 | ThrowIfFailed(mDevice->CreateDepthStencilView(mDepthStencilBuffer, &depthStencilViewDesc, &mDepthStencilView)); 435 | 436 | // Bind the render target view and depth stencil buffer to the output render pipeline. 437 | mDeviceContext->OMSetRenderTargets(1, &mRenderTargetView, mDepthStencilView); 438 | 439 | mDeviceContext->RSSetViewports(1, &mViewport); 440 | 441 | } 442 | 443 | void Renderer::destroyFrameBuffer() 444 | { 445 | if (mBackbufferTex) 446 | { 447 | mBackbufferTex->Release(); 448 | mBackbufferTex = nullptr; 449 | } 450 | 451 | if (mDepthStencilView) 452 | { 453 | mDepthStencilView->Release(); 454 | mDepthStencilView = nullptr; 455 | } 456 | 457 | if (mDepthStencilState) 458 | { 459 | mDepthStencilState->Release(); 460 | mDepthStencilState = nullptr; 461 | } 462 | 463 | if (mDepthStencilBuffer) 464 | { 465 | mDepthStencilBuffer->Release(); 466 | mDepthStencilBuffer = nullptr; 467 | } 468 | 469 | if (mRenderTargetView) 470 | { 471 | mRenderTargetView->Release(); 472 | mRenderTargetView = nullptr; 473 | } 474 | } 475 | 476 | void Renderer::setupSwapchain(unsigned width, unsigned height) 477 | { 478 | mViewport.TopLeftX = 0.0f; 479 | mViewport.TopLeftY = 0.0f; 480 | mViewport.Width = static_cast(mWidth); 481 | mViewport.Height = static_cast(mHeight); 482 | mViewport.MinDepth = 0.0f; 483 | mViewport.MaxDepth = 1.0f; 484 | 485 | // Update Uniforms 486 | float zoom = -2.5f; 487 | 488 | // Update matrices 489 | uboVS.projectionMatrix = Matrix4::perspective(45.0f, static_cast(mWidth) / static_cast(mHeight), 0.01f, 1024.0f); 490 | 491 | uboVS.viewMatrix = Matrix4::translation(Vector3(0.0f, 0.0f, zoom)) * Matrix4::rotationZ(3.14f); 492 | 493 | uboVS.modelMatrix = Matrix4::identity(); 494 | 495 | if (mSwapchain != nullptr) 496 | { 497 | mSwapchain->SetFullscreenState(false, nullptr); 498 | mSwapchain->Release(); 499 | mSwapchain = nullptr; 500 | } 501 | 502 | DXGI_SWAP_CHAIN_DESC swapchainDesc; 503 | ZeroMemory(&swapchainDesc, sizeof(DXGI_SWAP_CHAIN_DESC)); 504 | 505 | swapchainDesc.BufferCount = 1; 506 | 507 | // Set the width and height of the back buffer. 508 | swapchainDesc.BufferDesc.Width = mWidth; 509 | swapchainDesc.BufferDesc.Height = mHeight; 510 | 511 | // Set regular 32-bit surface for the back buffer. 512 | swapchainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 513 | 514 | // Set the refresh rate of the back buffer. 515 | 516 | if (mVsync) 517 | { 518 | swapchainDesc.BufferDesc.RefreshRate.Numerator = mNumerator; 519 | swapchainDesc.BufferDesc.RefreshRate.Denominator = mDenominator; 520 | } 521 | else 522 | { 523 | swapchainDesc.BufferDesc.RefreshRate.Numerator = 0; 524 | swapchainDesc.BufferDesc.RefreshRate.Denominator = 1; 525 | } 526 | 527 | // Set the usage of the back buffer. 528 | swapchainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 529 | 530 | // Turn multisampling off. 531 | swapchainDesc.SampleDesc.Count = 1; 532 | swapchainDesc.SampleDesc.Quality = 0; 533 | 534 | // Set the scan line ordering and scaling to unspecified. 535 | swapchainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; 536 | swapchainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; 537 | 538 | // Discard the back buffer contents after presenting. 539 | swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; 540 | 541 | // Don't set the advanced flags. 542 | swapchainDesc.Flags = 0; 543 | 544 | mSwapchain = xgfx::createSwapchain(mWindow, mFactory, mDevice, &swapchainDesc); 545 | } 546 | 547 | void Renderer::resize(unsigned width, unsigned height) 548 | { 549 | mWidth = clamp(width, 1u, 0xffffu); 550 | mHeight = clamp(height, 1u, 0xffffu); 551 | 552 | destroyFrameBuffer(); 553 | setupSwapchain(width, height); 554 | initFrameBuffer(); 555 | } 556 | 557 | void Renderer::render() 558 | { 559 | // Framelimit set to 60 fps 560 | tEnd = std::chrono::high_resolution_clock::now(); 561 | float time = std::chrono::duration(tEnd - tStart).count(); 562 | if (time < (1000.0f / 60.0f)) 563 | { 564 | return; 565 | } 566 | tStart = std::chrono::high_resolution_clock::now(); 567 | 568 | { 569 | // Update Uniforms 570 | mElapsedTime += 0.001f * time; 571 | mElapsedTime = fmodf(mElapsedTime, 6.283185307179586f); 572 | uboVS.modelMatrix = Matrix4::rotationY(mElapsedTime); 573 | 574 | D3D11_MAPPED_SUBRESOURCE mappedResource; 575 | unsigned int bufferNumber; 576 | 577 | 578 | // Lock the constant buffer so it can be written to. 579 | ThrowIfFailed(mDeviceContext->Map(mUniformBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource)); 580 | 581 | // Get a pointer to the data in the constant buffer. 582 | memcpy(mappedResource.pData, &uboVS, sizeof(uboVS)); 583 | 584 | // Unlock the constant buffer. 585 | mDeviceContext->Unmap(mUniformBuffer, 0); 586 | 587 | // Set the position of the constant buffer in the vertex shader. 588 | bufferNumber = 0; 589 | 590 | // Finanly set the constant buffer in the vertex shader with the updated values. 591 | mDeviceContext->VSSetConstantBuffers(bufferNumber, 1, &mUniformBuffer); 592 | 593 | } 594 | 595 | float color[4] = { 0.2f, 0.2f, 0.2f, 1.0f }; 596 | mDeviceContext->ClearRenderTargetView(mRenderTargetView, color); 597 | 598 | // Clear the depth buffer. 599 | mDeviceContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0); 600 | 601 | // Set the vertex input layout. 602 | mDeviceContext->IASetInputLayout(mLayout); 603 | 604 | // Set the vertex and pixel shaders that will be used to render this assets/shaders/triangle. 605 | mDeviceContext->VSSetShader(mVertexShader, NULL, 0); 606 | mDeviceContext->PSSetShader(mPixelShader, NULL, 0); 607 | 608 | // Set the vertex buffer to active in the input assembler so it can be rendered. 609 | unsigned stride = sizeof(Vertex); 610 | unsigned offset = 0; 611 | mDeviceContext->IASetVertexBuffers(0, 1, &mVertexBuffer, &stride, &offset); 612 | 613 | // Set the index buffer to active in the input assembler so it can be rendered. 614 | mDeviceContext->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_R32_UINT, 0); 615 | 616 | // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles. 617 | mDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); 618 | 619 | // Render the assets/shaders/triangle. 620 | mDeviceContext->DrawIndexed(3, 0, 0); 621 | 622 | if (mVsync) 623 | { 624 | mSwapchain->Present(1, 0); 625 | } 626 | else 627 | { 628 | mSwapchain->Present(0, 0); 629 | } 630 | } 631 | 632 | /** 633 | * While most modern graphics APIs have a command queue, sync, and render passes, DirectX 11 does not. 634 | * So these functions are just stubs: 635 | */ 636 | 637 | void Renderer::createCommands() 638 | { 639 | // DirectX 11 doesn't have a queue, but rather a context that's set at render time 640 | } 641 | 642 | void Renderer::setupCommands() 643 | { 644 | // DirectX 11 doesn't have commands 645 | } 646 | 647 | void Renderer::destroyCommands() 648 | { 649 | //DirectX 11 doesn't have commands 650 | } 651 | 652 | void Renderer::createRenderPass() 653 | { 654 | // DirectX 11 doesn't have render passes, just framebuffer outputs 655 | } 656 | 657 | void Renderer::createSynchronization() 658 | { 659 | // DirectX 11 doesn't have synchronization primitives 660 | } 661 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/src/DirectX12Renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "Renderer.h" 2 | 3 | // Helper functions 4 | 5 | inline void ThrowIfFailed(HRESULT hr) 6 | { 7 | if (FAILED(hr)) 8 | { 9 | throw std::exception(); 10 | } 11 | } 12 | 13 | // Renderer 14 | 15 | Renderer::Renderer(xwin::Window& window) 16 | { 17 | mWindow; 18 | 19 | // Initialization 20 | mFactory = nullptr; 21 | mAdapter = nullptr; 22 | #if defined(_DEBUG) 23 | mDebugController = nullptr; 24 | #endif 25 | mDevice = nullptr; 26 | mCommandQueue = nullptr; 27 | mCommandAllocator = nullptr; 28 | mCommandList = nullptr; 29 | mSwapchain = nullptr; 30 | 31 | // Resources 32 | 33 | mVertexBuffer = nullptr; 34 | mIndexBuffer = nullptr; 35 | 36 | mUniformBuffer = nullptr; 37 | mUniformBufferHeap = nullptr; 38 | mMappedUniformBuffer = nullptr; 39 | 40 | mRootSignature = nullptr; 41 | mPipelineState = nullptr; 42 | 43 | // Current Frame 44 | mRtvHeap = nullptr; 45 | for (size_t i = 0; i < backbufferCount; ++i) 46 | { 47 | mRenderTargets[i] = nullptr; 48 | } 49 | // Sync 50 | mFence = nullptr; 51 | 52 | initializeAPI(window); 53 | initializeResources(); 54 | setupCommands(); 55 | tStart = std::chrono::high_resolution_clock::now(); 56 | } 57 | 58 | Renderer::~Renderer() 59 | { 60 | if (mSwapchain != nullptr) 61 | { 62 | mSwapchain->SetFullscreenState(false, nullptr); 63 | mSwapchain->Release(); 64 | mSwapchain = nullptr; 65 | } 66 | 67 | destroyCommands(); 68 | destroyFrameBuffer(); 69 | destroyResources(); 70 | destroyAPI(); 71 | } 72 | 73 | void Renderer::initializeAPI(xwin::Window& window) 74 | { 75 | // The renderer needs the window when resizing the swapchain 76 | mWindow = &window; 77 | 78 | // Create Factory 79 | 80 | UINT dxgiFactoryFlags = 0; 81 | #if defined(_DEBUG) 82 | ID3D12Debug* debugController; 83 | ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))); 84 | ThrowIfFailed(debugController->QueryInterface(IID_PPV_ARGS(&mDebugController))); 85 | mDebugController->EnableDebugLayer(); 86 | mDebugController->SetEnableGPUBasedValidation(true); 87 | 88 | dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG; 89 | 90 | debugController->Release(); 91 | debugController = nullptr; 92 | 93 | #endif 94 | ThrowIfFailed(CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&mFactory))); 95 | 96 | // Create Adapter 97 | 98 | for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != mFactory->EnumAdapters1(adapterIndex, &mAdapter); ++adapterIndex) 99 | { 100 | DXGI_ADAPTER_DESC1 desc; 101 | mAdapter->GetDesc1(&desc); 102 | 103 | if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) 104 | { 105 | // Don't select the Basic Render Driver adapter. 106 | continue; 107 | } 108 | 109 | // Check to see if the adapter supports Direct3D 12, but don't create the 110 | // actual device yet. 111 | if (SUCCEEDED(D3D12CreateDevice(mAdapter, D3D_FEATURE_LEVEL_12_0, _uuidof(ID3D12Device), nullptr))) 112 | { 113 | break; 114 | } 115 | 116 | // We won't use this adapter, so release it 117 | mAdapter->Release(); 118 | } 119 | 120 | // Create Device 121 | ID3D12Device *pDev = nullptr; 122 | ThrowIfFailed(D3D12CreateDevice( 123 | mAdapter, 124 | D3D_FEATURE_LEVEL_12_0, 125 | IID_PPV_ARGS(&mDevice) 126 | )); 127 | 128 | mDevice->SetName(L"Hello Triangle Device"); 129 | 130 | #if defined(_DEBUG) 131 | // Get debug device 132 | ThrowIfFailed(mDevice->QueryInterface(&mDebugDevice)); 133 | #endif 134 | 135 | // Create Command Queue 136 | D3D12_COMMAND_QUEUE_DESC queueDesc = {}; 137 | queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; 138 | queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; 139 | 140 | ThrowIfFailed(mDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue))); 141 | 142 | 143 | // Create Command Allocator 144 | ThrowIfFailed(mDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCommandAllocator))); 145 | 146 | // Sync 147 | ThrowIfFailed(mDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence))); 148 | 149 | // Create Swapchain 150 | const xwin::WindowDesc wdesc = window.getDesc(); 151 | resize(wdesc.width, wdesc.height); 152 | } 153 | 154 | void Renderer::destroyAPI() 155 | { 156 | if (mFence) 157 | { 158 | mFence->Release(); 159 | mFence = nullptr; 160 | } 161 | 162 | if (mCommandAllocator) 163 | { 164 | ThrowIfFailed(mCommandAllocator->Reset()); 165 | mCommandAllocator->Release(); 166 | mCommandAllocator = nullptr; 167 | } 168 | 169 | if (mCommandQueue) 170 | { 171 | mCommandQueue->Release(); 172 | mCommandQueue = nullptr; 173 | } 174 | 175 | if (mDevice) 176 | { 177 | mDevice->Release(); 178 | mDevice = nullptr; 179 | } 180 | 181 | if (mAdapter) 182 | { 183 | mAdapter->Release(); 184 | mAdapter = nullptr; 185 | } 186 | 187 | if (mFactory) 188 | { 189 | mFactory->Release(); 190 | mFactory = nullptr; 191 | } 192 | 193 | #if defined(_DEBUG) 194 | if (mDebugController) 195 | { 196 | mDebugController->Release(); 197 | mDebugController = nullptr; 198 | } 199 | 200 | D3D12_RLDO_FLAGS flags = D3D12_RLDO_SUMMARY | D3D12_RLDO_DETAIL | D3D12_RLDO_IGNORE_INTERNAL; 201 | 202 | mDebugDevice->ReportLiveDeviceObjects(flags); 203 | 204 | if (mDebugDevice) 205 | { 206 | mDebugDevice->Release(); 207 | mDebugDevice = nullptr; 208 | } 209 | #endif 210 | } 211 | 212 | void Renderer::initFrameBuffer() 213 | { 214 | mCurrentBuffer = mSwapchain->GetCurrentBackBufferIndex(); 215 | 216 | // Create descriptor heaps. 217 | { 218 | // Describe and create a render target view (RTV) descriptor heap. 219 | D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; 220 | rtvHeapDesc.NumDescriptors = backbufferCount; 221 | rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; 222 | rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; 223 | ThrowIfFailed(mDevice->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&mRtvHeap))); 224 | 225 | mRtvDescriptorSize = mDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); 226 | } 227 | 228 | // Create frame resources. 229 | { 230 | D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart()); 231 | 232 | // Create a RTV for each frame. 233 | for (UINT n = 0; n < backbufferCount; n++) 234 | { 235 | ThrowIfFailed(mSwapchain->GetBuffer(n, IID_PPV_ARGS(&mRenderTargets[n]))); 236 | mDevice->CreateRenderTargetView(mRenderTargets[n], nullptr, rtvHandle); 237 | rtvHandle.ptr += (1 * mRtvDescriptorSize); 238 | } 239 | } 240 | } 241 | 242 | void Renderer::destroyFrameBuffer() 243 | { 244 | for (size_t i = 0; i < backbufferCount; ++i) 245 | { 246 | if (mRenderTargets[i]) 247 | { 248 | mRenderTargets[i]->Release(); 249 | mRenderTargets[i] = 0; 250 | } 251 | } 252 | if (mRtvHeap) 253 | { 254 | mRtvHeap->Release(); 255 | mRtvHeap = nullptr; 256 | } 257 | } 258 | 259 | void Renderer::initializeResources() 260 | { 261 | // Create the root signature. 262 | { 263 | D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {}; 264 | 265 | // This is the highest version the sample supports. If CheckFeatureSupport succeeds, the HighestVersion returned will not be greater than this. 266 | featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1; 267 | 268 | if (FAILED(mDevice->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData)))) 269 | { 270 | featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0; 271 | } 272 | 273 | D3D12_DESCRIPTOR_RANGE1 ranges[1]; 274 | ranges[0].BaseShaderRegister = 0; 275 | ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; 276 | ranges[0].NumDescriptors = 1; 277 | ranges[0].RegisterSpace = 0; 278 | ranges[0].OffsetInDescriptorsFromTableStart = 0; 279 | ranges[0].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE; 280 | 281 | D3D12_ROOT_PARAMETER1 rootParameters[1]; 282 | rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; 283 | rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; 284 | 285 | rootParameters[0].DescriptorTable.NumDescriptorRanges = 1; 286 | rootParameters[0].DescriptorTable.pDescriptorRanges = ranges; 287 | 288 | D3D12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc; 289 | rootSignatureDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1; 290 | rootSignatureDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; 291 | rootSignatureDesc.Desc_1_1.NumParameters = 1; 292 | rootSignatureDesc.Desc_1_1.pParameters = rootParameters; 293 | rootSignatureDesc.Desc_1_1.NumStaticSamplers = 0; 294 | rootSignatureDesc.Desc_1_1.pStaticSamplers = nullptr; 295 | 296 | ID3DBlob* signature; 297 | ID3DBlob* error; 298 | try 299 | { 300 | ThrowIfFailed(D3D12SerializeVersionedRootSignature(&rootSignatureDesc, &signature, &error)); 301 | ThrowIfFailed(mDevice->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&mRootSignature))); 302 | mRootSignature->SetName(L"Hello Triangle Root Signature"); 303 | } 304 | catch (std::exception e) 305 | { 306 | const char* errStr = (const char*)error->GetBufferPointer(); 307 | std::cout << errStr; 308 | error->Release(); 309 | error = nullptr; 310 | } 311 | 312 | if (signature) 313 | { 314 | signature->Release(); 315 | signature = nullptr; 316 | } 317 | } 318 | 319 | // Create the pipeline state, which includes compiling and loading shaders. 320 | { 321 | ID3DBlob* vertexShader = nullptr; 322 | ID3DBlob* pixelShader = nullptr; 323 | ID3DBlob* errors = nullptr; 324 | 325 | #if defined(_DEBUG) 326 | // Enable better shader debugging with the graphics debugging tools. 327 | UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; 328 | #else 329 | UINT compileFlags = 0; 330 | #endif 331 | std::string path = ""; 332 | char pBuf[1024]; 333 | 334 | _getcwd(pBuf, 1024); 335 | path = pBuf; 336 | path += "\\"; 337 | std::wstring wpath = std::wstring(path.begin(), path.end()); 338 | 339 | std::wstring vertPath = wpath + L"assets/shaders/triangle.vert.hlsl"; 340 | std::wstring fragPath = wpath + L"assets/shaders/triangle.frag.hlsl"; 341 | 342 | try 343 | { 344 | ThrowIfFailed(D3DCompileFromFile(vertPath.c_str(), nullptr, nullptr, "main", "vs_5_0", compileFlags, 0, &vertexShader, &errors)); 345 | ThrowIfFailed(D3DCompileFromFile(fragPath.c_str(), nullptr, nullptr, "main", "ps_5_0", compileFlags, 0, &pixelShader, &errors)); 346 | } 347 | catch (std::exception e) 348 | { 349 | const char* errStr = (const char*)errors->GetBufferPointer(); 350 | std::cout << errStr; 351 | errors->Release(); 352 | errors = nullptr; 353 | } 354 | 355 | // Define the vertex input layout. 356 | D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = 357 | { 358 | { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, 359 | { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 } 360 | }; 361 | 362 | // Create the UBO. 363 | { 364 | // Note: using upload heaps to transfer static data like vert buffers is not 365 | // recommended. Every time the GPU needs it, the upload heap will be marshalled 366 | // over. Please read up on Default Heap usage. An upload heap is used here for 367 | // code simplicity and because there are very few verts to actually transfer. 368 | D3D12_HEAP_PROPERTIES heapProps; 369 | heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; 370 | heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; 371 | heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; 372 | heapProps.CreationNodeMask = 1; 373 | heapProps.VisibleNodeMask = 1; 374 | 375 | D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {}; 376 | heapDesc.NumDescriptors = 1; 377 | heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; 378 | heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; 379 | ThrowIfFailed(mDevice->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&mUniformBufferHeap))); 380 | 381 | D3D12_RESOURCE_DESC uboResourceDesc; 382 | uboResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; 383 | uboResourceDesc.Alignment = 0; 384 | uboResourceDesc.Width = (sizeof(uboVS) + 255) & ~255; 385 | uboResourceDesc.Height = 1; 386 | uboResourceDesc.DepthOrArraySize = 1; 387 | uboResourceDesc.MipLevels = 1; 388 | uboResourceDesc.Format = DXGI_FORMAT_UNKNOWN; 389 | uboResourceDesc.SampleDesc.Count = 1; 390 | uboResourceDesc.SampleDesc.Quality = 0; 391 | uboResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; 392 | uboResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; 393 | 394 | 395 | ThrowIfFailed(mDevice->CreateCommittedResource( 396 | &heapProps, 397 | D3D12_HEAP_FLAG_NONE, 398 | &uboResourceDesc, 399 | D3D12_RESOURCE_STATE_GENERIC_READ, 400 | nullptr, 401 | IID_PPV_ARGS(&mUniformBuffer))); 402 | mUniformBufferHeap->SetName(L"Constant Buffer Upload Resource Heap"); 403 | 404 | D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {}; 405 | cbvDesc.BufferLocation = mUniformBuffer->GetGPUVirtualAddress(); 406 | cbvDesc.SizeInBytes = (sizeof(uboVS) + 255) & ~255; // CB size is required to be 256-byte aligned. 407 | 408 | D3D12_CPU_DESCRIPTOR_HANDLE cbvHandle(mUniformBufferHeap->GetCPUDescriptorHandleForHeapStart()); 409 | cbvHandle.ptr = cbvHandle.ptr + mDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) * 0; 410 | 411 | mDevice->CreateConstantBufferView(&cbvDesc, cbvHandle); 412 | 413 | // We do not intend to read from this resource on the CPU. (End is less than or equal to begin) 414 | D3D12_RANGE readRange; 415 | readRange.Begin = 0; 416 | readRange.End = 0; 417 | 418 | ThrowIfFailed(mUniformBuffer->Map(0, &readRange, reinterpret_cast(&mMappedUniformBuffer))); 419 | memcpy(mMappedUniformBuffer, &uboVS, sizeof(uboVS)); 420 | mUniformBuffer->Unmap(0, &readRange); 421 | } 422 | 423 | // Describe and create the graphics pipeline state object (PSO). 424 | D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; 425 | psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) }; 426 | psoDesc.pRootSignature = mRootSignature; 427 | 428 | D3D12_SHADER_BYTECODE vsBytecode; 429 | vsBytecode.pShaderBytecode = vertexShader->GetBufferPointer(); 430 | vsBytecode.BytecodeLength = vertexShader->GetBufferSize(); 431 | 432 | psoDesc.VS = vsBytecode; 433 | 434 | D3D12_SHADER_BYTECODE psBytecode; 435 | psBytecode.pShaderBytecode = pixelShader->GetBufferPointer(); 436 | psBytecode.BytecodeLength = pixelShader->GetBufferSize(); 437 | 438 | psoDesc.PS = psBytecode; 439 | 440 | D3D12_RASTERIZER_DESC rasterDesc; 441 | rasterDesc.FillMode = D3D12_FILL_MODE_SOLID; 442 | rasterDesc.CullMode = D3D12_CULL_MODE_NONE; 443 | rasterDesc.FrontCounterClockwise = FALSE; 444 | rasterDesc.DepthBias = D3D12_DEFAULT_DEPTH_BIAS; 445 | rasterDesc.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP; 446 | rasterDesc.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS; 447 | rasterDesc.DepthClipEnable = TRUE; 448 | rasterDesc.MultisampleEnable = FALSE; 449 | rasterDesc.AntialiasedLineEnable = FALSE; 450 | rasterDesc.ForcedSampleCount = 0; 451 | rasterDesc.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; 452 | 453 | psoDesc.RasterizerState = rasterDesc; 454 | 455 | D3D12_BLEND_DESC blendDesc; 456 | blendDesc.AlphaToCoverageEnable = FALSE; 457 | blendDesc.IndependentBlendEnable = FALSE; 458 | const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = 459 | { 460 | FALSE,FALSE, 461 | D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, 462 | D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, 463 | D3D12_LOGIC_OP_NOOP, 464 | D3D12_COLOR_WRITE_ENABLE_ALL, 465 | }; 466 | for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) 467 | blendDesc.RenderTarget[i] = defaultRenderTargetBlendDesc; 468 | 469 | psoDesc.BlendState = blendDesc; 470 | psoDesc.DepthStencilState.DepthEnable = FALSE; 471 | psoDesc.DepthStencilState.StencilEnable = FALSE; 472 | psoDesc.SampleMask = UINT_MAX; 473 | psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; 474 | psoDesc.NumRenderTargets = 1; 475 | psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; 476 | psoDesc.SampleDesc.Count = 1; 477 | try 478 | { 479 | ThrowIfFailed(mDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPipelineState))); 480 | } 481 | catch (std::exception e) 482 | { 483 | std::cout << "Failed to create Graphics Pipeline!"; 484 | } 485 | 486 | if (vertexShader) 487 | { 488 | vertexShader->Release(); 489 | vertexShader = nullptr; 490 | } 491 | 492 | if (pixelShader) 493 | { 494 | pixelShader->Release(); 495 | pixelShader = nullptr; 496 | } 497 | } 498 | 499 | createCommands(); 500 | 501 | // Command lists are created in the recording state, but there is nothing 502 | // to record yet. The main loop expects it to be closed, so close it now. 503 | ThrowIfFailed(mCommandList->Close()); 504 | 505 | // Create the vertex buffer. 506 | { 507 | const UINT vertexBufferSize = sizeof(mVertexBufferData); 508 | 509 | // Note: using upload heaps to transfer static data like vert buffers is not 510 | // recommended. Every time the GPU needs it, the upload heap will be marshalled 511 | // over. Please read up on Default Heap usage. An upload heap is used here for 512 | // code simplicity and because there are very few verts to actually transfer. 513 | D3D12_HEAP_PROPERTIES heapProps; 514 | heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; 515 | heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; 516 | heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; 517 | heapProps.CreationNodeMask = 1; 518 | heapProps.VisibleNodeMask = 1; 519 | 520 | D3D12_RESOURCE_DESC vertexBufferResourceDesc; 521 | vertexBufferResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; 522 | vertexBufferResourceDesc.Alignment = 0; 523 | vertexBufferResourceDesc.Width = vertexBufferSize; 524 | vertexBufferResourceDesc.Height = 1; 525 | vertexBufferResourceDesc.DepthOrArraySize = 1; 526 | vertexBufferResourceDesc.MipLevels = 1; 527 | vertexBufferResourceDesc.Format = DXGI_FORMAT_UNKNOWN; 528 | vertexBufferResourceDesc.SampleDesc.Count = 1; 529 | vertexBufferResourceDesc.SampleDesc.Quality = 0; 530 | vertexBufferResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; 531 | vertexBufferResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; 532 | 533 | ThrowIfFailed(mDevice->CreateCommittedResource( 534 | &heapProps, 535 | D3D12_HEAP_FLAG_NONE, 536 | &vertexBufferResourceDesc, 537 | D3D12_RESOURCE_STATE_GENERIC_READ, 538 | nullptr, 539 | IID_PPV_ARGS(&mVertexBuffer))); 540 | 541 | // Copy the triangle data to the vertex buffer. 542 | UINT8* pVertexDataBegin; 543 | 544 | // We do not intend to read from this resource on the CPU. 545 | D3D12_RANGE readRange; 546 | readRange.Begin = 0; 547 | readRange.End = 0; 548 | 549 | ThrowIfFailed(mVertexBuffer->Map(0, &readRange, reinterpret_cast(&pVertexDataBegin))); 550 | memcpy(pVertexDataBegin, mVertexBufferData, sizeof(mVertexBufferData)); 551 | mVertexBuffer->Unmap(0, nullptr); 552 | 553 | // Initialize the vertex buffer view. 554 | mVertexBufferView.BufferLocation = mVertexBuffer->GetGPUVirtualAddress(); 555 | mVertexBufferView.StrideInBytes = sizeof(Vertex); 556 | mVertexBufferView.SizeInBytes = vertexBufferSize; 557 | } 558 | 559 | // Create the index buffer. 560 | { 561 | const UINT indexBufferSize = sizeof(mIndexBufferData); 562 | 563 | // Note: using upload heaps to transfer static data like vert buffers is not 564 | // recommended. Every time the GPU needs it, the upload heap will be marshalled 565 | // over. Please read up on Default Heap usage. An upload heap is used here for 566 | // code simplicity and because there are very few verts to actually transfer. 567 | D3D12_HEAP_PROPERTIES heapProps; 568 | heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; 569 | heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; 570 | heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; 571 | heapProps.CreationNodeMask = 1; 572 | heapProps.VisibleNodeMask = 1; 573 | 574 | D3D12_RESOURCE_DESC vertexBufferResourceDesc; 575 | vertexBufferResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; 576 | vertexBufferResourceDesc.Alignment = 0; 577 | vertexBufferResourceDesc.Width = indexBufferSize; 578 | vertexBufferResourceDesc.Height = 1; 579 | vertexBufferResourceDesc.DepthOrArraySize = 1; 580 | vertexBufferResourceDesc.MipLevels = 1; 581 | vertexBufferResourceDesc.Format = DXGI_FORMAT_UNKNOWN; 582 | vertexBufferResourceDesc.SampleDesc.Count = 1; 583 | vertexBufferResourceDesc.SampleDesc.Quality = 0; 584 | vertexBufferResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; 585 | vertexBufferResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; 586 | 587 | ThrowIfFailed(mDevice->CreateCommittedResource( 588 | &heapProps, 589 | D3D12_HEAP_FLAG_NONE, 590 | &vertexBufferResourceDesc, 591 | D3D12_RESOURCE_STATE_GENERIC_READ, 592 | nullptr, 593 | IID_PPV_ARGS(&mIndexBuffer))); 594 | 595 | // Copy the triangle data to the vertex buffer. 596 | UINT8* pVertexDataBegin; 597 | 598 | // We do not intend to read from this resource on the CPU. 599 | D3D12_RANGE readRange; 600 | readRange.Begin = 0; 601 | readRange.End = 0; 602 | 603 | ThrowIfFailed(mIndexBuffer->Map(0, &readRange, reinterpret_cast(&pVertexDataBegin))); 604 | memcpy(pVertexDataBegin, mIndexBufferData, sizeof(mIndexBufferData)); 605 | mIndexBuffer->Unmap(0, nullptr); 606 | 607 | // Initialize the vertex buffer view. 608 | mIndexBufferView.BufferLocation = mIndexBuffer->GetGPUVirtualAddress(); 609 | mIndexBufferView.Format = DXGI_FORMAT_R32_UINT; 610 | mIndexBufferView.SizeInBytes = indexBufferSize; 611 | } 612 | 613 | // Create synchronization objects and wait until assets have been uploaded to the GPU. 614 | { 615 | mFenceValue = 1; 616 | 617 | // Create an event handle to use for frame synchronization. 618 | mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); 619 | if (mFenceEvent == nullptr) 620 | { 621 | ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError())); 622 | } 623 | 624 | // Wait for the command list to execute; we are reusing the same command 625 | // list in our main loop but for now, we just want to wait for setup to 626 | // complete before continuing. 627 | // Signal and increment the fence value. 628 | const UINT64 fence = mFenceValue; 629 | ThrowIfFailed(mCommandQueue->Signal(mFence, fence)); 630 | mFenceValue++; 631 | 632 | // Wait until the previous frame is finished. 633 | if (mFence->GetCompletedValue() < fence) 634 | { 635 | ThrowIfFailed(mFence->SetEventOnCompletion(fence, mFenceEvent)); 636 | WaitForSingleObject(mFenceEvent, INFINITE); 637 | } 638 | 639 | mFrameIndex = mSwapchain->GetCurrentBackBufferIndex(); 640 | } 641 | } 642 | 643 | void Renderer::destroyResources() 644 | { 645 | // Sync 646 | CloseHandle(mFenceEvent); 647 | 648 | if (mPipelineState) 649 | { 650 | mPipelineState->Release(); 651 | mPipelineState = nullptr; 652 | } 653 | 654 | if (mRootSignature) 655 | { 656 | mRootSignature->Release(); 657 | mRootSignature = nullptr; 658 | } 659 | 660 | if (mVertexBuffer) 661 | { 662 | mVertexBuffer->Release(); 663 | mVertexBuffer = nullptr; 664 | } 665 | 666 | if (mIndexBuffer) 667 | { 668 | mIndexBuffer->Release(); 669 | mIndexBuffer = nullptr; 670 | } 671 | 672 | if (mUniformBuffer) 673 | { 674 | mUniformBuffer->Release(); 675 | mUniformBuffer = nullptr; 676 | } 677 | 678 | if (mUniformBufferHeap) 679 | { 680 | mUniformBufferHeap->Release(); 681 | mUniformBufferHeap = nullptr; 682 | } 683 | } 684 | 685 | void Renderer::createCommands() 686 | { 687 | // Create the command list. 688 | ThrowIfFailed(mDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mCommandAllocator, mPipelineState, IID_PPV_ARGS(&mCommandList))); 689 | mCommandList->SetName(L"Hello Triangle Command List"); 690 | } 691 | 692 | void Renderer::setupCommands() 693 | { 694 | // Command list allocators can only be reset when the associated 695 | // command lists have finished execution on the GPU; apps should use 696 | // fences to determine GPU execution progress. 697 | ThrowIfFailed(mCommandAllocator->Reset()); 698 | 699 | // However, when ExecuteCommandList() is called on a particular command 700 | // list, that command list can then be reset at any time and must be before 701 | // re-recording. 702 | ThrowIfFailed(mCommandList->Reset(mCommandAllocator, mPipelineState)); 703 | 704 | // Set necessary state. 705 | mCommandList->SetGraphicsRootSignature(mRootSignature); 706 | mCommandList->RSSetViewports(1, &mViewport); 707 | mCommandList->RSSetScissorRects(1, &mSurfaceSize); 708 | 709 | ID3D12DescriptorHeap *pDescriptorHeaps[] = { mUniformBufferHeap }; 710 | mCommandList->SetDescriptorHeaps(_countof(pDescriptorHeaps), pDescriptorHeaps); 711 | 712 | D3D12_GPU_DESCRIPTOR_HANDLE srvHandle(mUniformBufferHeap->GetGPUDescriptorHandleForHeapStart()); 713 | mCommandList->SetGraphicsRootDescriptorTable(0, srvHandle); 714 | 715 | // Indicate that the back buffer will be used as a render target. 716 | D3D12_RESOURCE_BARRIER renderTargetBarrier; 717 | renderTargetBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; 718 | renderTargetBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; 719 | renderTargetBarrier.Transition.pResource = mRenderTargets[mFrameIndex]; 720 | renderTargetBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; 721 | renderTargetBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; 722 | renderTargetBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; 723 | 724 | mCommandList->ResourceBarrier(1, &renderTargetBarrier); 725 | 726 | D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart()); 727 | rtvHandle.ptr = rtvHandle.ptr + (mFrameIndex * mRtvDescriptorSize); 728 | mCommandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); 729 | 730 | // Record commands. 731 | const float clearColor[] = { 0.2f, 0.2f, 0.2f, 1.0f }; 732 | mCommandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); 733 | mCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); 734 | mCommandList->IASetVertexBuffers(0, 1, &mVertexBufferView); 735 | mCommandList->IASetIndexBuffer(&mIndexBufferView); 736 | 737 | mCommandList->DrawIndexedInstanced(3, 1, 0, 0, 0); 738 | 739 | // Indicate that the back buffer will now be used to present. 740 | D3D12_RESOURCE_BARRIER presentBarrier; 741 | presentBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; 742 | presentBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; 743 | presentBarrier.Transition.pResource = mRenderTargets[mFrameIndex]; 744 | presentBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; 745 | presentBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; 746 | presentBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; 747 | 748 | mCommandList->ResourceBarrier(1, &presentBarrier); 749 | 750 | ThrowIfFailed(mCommandList->Close()); 751 | } 752 | 753 | void Renderer::destroyCommands() 754 | { 755 | if (mCommandList) 756 | { 757 | mCommandList->Reset(mCommandAllocator, mPipelineState); 758 | mCommandList->ClearState(mPipelineState); 759 | ThrowIfFailed(mCommandList->Close()); 760 | ID3D12CommandList* ppCommandLists[] = { mCommandList }; 761 | mCommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists); 762 | 763 | // Wait for GPU to finish work 764 | const UINT64 fence = mFenceValue; 765 | ThrowIfFailed(mCommandQueue->Signal(mFence, fence)); 766 | mFenceValue++; 767 | if (mFence->GetCompletedValue() < fence) 768 | { 769 | ThrowIfFailed(mFence->SetEventOnCompletion(fence, mFenceEvent)); 770 | WaitForSingleObject(mFenceEvent, INFINITE); 771 | } 772 | 773 | mCommandList->Release(); 774 | mCommandList = nullptr; 775 | } 776 | } 777 | 778 | void Renderer::setupSwapchain(unsigned width, unsigned height) 779 | { 780 | 781 | mSurfaceSize.left = 0; 782 | mSurfaceSize.top = 0; 783 | mSurfaceSize.right = static_cast(mWidth); 784 | mSurfaceSize.bottom = static_cast(mHeight); 785 | 786 | mViewport.TopLeftX = 0.0f; 787 | mViewport.TopLeftY = 0.0f; 788 | mViewport.Width = static_cast(mWidth); 789 | mViewport.Height = static_cast(mHeight); 790 | mViewport.MinDepth = .1f; 791 | mViewport.MaxDepth = 1000.f; 792 | 793 | // Update Uniforms 794 | float zoom = -2.5f; 795 | 796 | // Update matrices 797 | uboVS.projectionMatrix = Matrix4::perspective(45.0f, mViewport.Width / mViewport.Height, 0.01f, 1024.0f); 798 | 799 | uboVS.viewMatrix = Matrix4::translation(Vector3(0.0f, 0.0f, zoom)) * Matrix4::rotationZ(3.14f); 800 | 801 | uboVS.modelMatrix = Matrix4::identity(); 802 | 803 | if (mSwapchain != nullptr) 804 | { 805 | mSwapchain->ResizeBuffers(backbufferCount, mWidth, mHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0); 806 | } 807 | else 808 | { 809 | DXGI_SWAP_CHAIN_DESC1 swapchainDesc = {}; 810 | swapchainDesc.BufferCount = backbufferCount; 811 | swapchainDesc.Width = width; 812 | swapchainDesc.Height = height; 813 | swapchainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 814 | swapchainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 815 | swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; 816 | swapchainDesc.SampleDesc.Count = 1; 817 | 818 | IDXGISwapChain1* swapchain = xgfx::createSwapchain(mWindow, mFactory, mCommandQueue, &swapchainDesc); 819 | HRESULT swapchainSupport = swapchain->QueryInterface(__uuidof(IDXGISwapChain3), (void**)&swapchain); 820 | if (SUCCEEDED(swapchainSupport)) 821 | { 822 | mSwapchain = (IDXGISwapChain3*)swapchain; 823 | } 824 | } 825 | mFrameIndex = mSwapchain->GetCurrentBackBufferIndex(); 826 | } 827 | 828 | void Renderer::resize(unsigned width, unsigned height) 829 | { 830 | mWidth = clamp(width, 1u, 0xffffu); 831 | mHeight = clamp(height, 1u, 0xffffu); 832 | 833 | // WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE. 834 | // This is code implemented as such for simplicity. The D3D12HelloFrameBuffering 835 | // sample illustrates how to use fences for efficient resource usage and to 836 | // maximize GPU utilization. 837 | 838 | // Signal and increment the fence value. 839 | const UINT64 fence = mFenceValue; 840 | ThrowIfFailed(mCommandQueue->Signal(mFence, fence)); 841 | mFenceValue++; 842 | 843 | // Wait until the previous frame is finished. 844 | if (mFence->GetCompletedValue() < fence) 845 | { 846 | ThrowIfFailed(mFence->SetEventOnCompletion(fence, mFenceEvent)); 847 | WaitForSingleObjectEx(mFenceEvent, INFINITE, false); 848 | } 849 | 850 | destroyFrameBuffer(); 851 | setupSwapchain(width, height); 852 | initFrameBuffer(); 853 | } 854 | 855 | void Renderer::render() 856 | { 857 | // Framelimit set to 60 fps 858 | tEnd = std::chrono::high_resolution_clock::now(); 859 | float time = std::chrono::duration(tEnd - tStart).count(); 860 | if (time < (1000.0f / 60.0f)) 861 | { 862 | return; 863 | } 864 | tStart = std::chrono::high_resolution_clock::now(); 865 | 866 | { 867 | // Update Uniforms 868 | mElapsedTime += 0.001f * time; 869 | mElapsedTime = fmodf(mElapsedTime, 6.283185307179586f); 870 | uboVS.modelMatrix = Matrix4::rotationY(mElapsedTime); 871 | 872 | D3D12_RANGE readRange; 873 | readRange.Begin = 0; 874 | readRange.End = 0; 875 | 876 | ThrowIfFailed(mUniformBuffer->Map(0, &readRange, reinterpret_cast(&mMappedUniformBuffer))); 877 | memcpy(mMappedUniformBuffer, &uboVS, sizeof(uboVS)); 878 | mUniformBuffer->Unmap(0, &readRange); 879 | } 880 | 881 | // Record all the commands we need to render the scene into the command list. 882 | setupCommands(); 883 | 884 | // Execute the command list. 885 | ID3D12CommandList* ppCommandLists[] = { mCommandList }; 886 | mCommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists); 887 | mSwapchain->Present(1, 0); 888 | 889 | // WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE. 890 | 891 | // Signal and increment the fence value. 892 | const UINT64 fence = mFenceValue; 893 | ThrowIfFailed(mCommandQueue->Signal(mFence, fence)); 894 | mFenceValue++; 895 | 896 | // Wait until the previous frame is finished. 897 | if (mFence->GetCompletedValue() < fence) 898 | { 899 | ThrowIfFailed(mFence->SetEventOnCompletion(fence, mFenceEvent)); 900 | WaitForSingleObject(mFenceEvent, INFINITE); 901 | } 902 | 903 | mFrameIndex = mSwapchain->GetCurrentBackBufferIndex(); 904 | } -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/src/MetalRenderer.mm: -------------------------------------------------------------------------------- 1 | #include "Renderer.h" 2 | #import 3 | #import 4 | 5 | Renderer::Renderer(xwin::Window& window) 6 | { 7 | initializeAPI(window); 8 | initializeResources(); 9 | setupCommands(); 10 | tStart = std::chrono::high_resolution_clock::now(); 11 | } 12 | 13 | Renderer::~Renderer() 14 | { 15 | destroyResources(); 16 | destroyAPI(); 17 | } 18 | 19 | void Renderer::initializeAPI(xwin::Window& window) 20 | { 21 | xgfx::createMetalLayer(&window); 22 | xwin::WindowDelegate& del = window.getDelegate(); 23 | CAMetalLayer* layer = (CAMetalLayer*)del.layer; 24 | mLayer = layer; 25 | 26 | layer.device = MTLCreateSystemDefaultDevice(); 27 | mDevice = layer.device; 28 | 29 | // Create the command queue 30 | mCommandQueue = [(id)mDevice newCommandQueue]; 31 | 32 | xwin::WindowDesc desc = window.getDesc(); 33 | 34 | mViewportSize[0] = desc.width; 35 | mViewportSize[1] = desc.height; 36 | } 37 | 38 | void Renderer::destroyAPI() 39 | { 40 | if ((id)mCommandBuffer != nil) 41 | { 42 | [(id)mCommandBuffer release]; 43 | } 44 | 45 | [(id)mCommandQueue release]; 46 | 47 | [(id)mDevice release]; 48 | } 49 | 50 | void Renderer::initializeResources() 51 | { 52 | // Create Vertex Buffer 53 | 54 | mVertexBuffer = [(id)mDevice newBufferWithLength:sizeof(Vertex) * 3 55 | options:MTLResourceOptionCPUCacheModeDefault]; 56 | [(id)mVertexBuffer setLabel:@"VBO"]; 57 | memcpy(((id)mVertexBuffer).contents, mVertexBufferData, sizeof(Vertex) * 3); 58 | 59 | // Create Index Buffer 60 | 61 | mIndexBuffer = [(id)mDevice newBufferWithLength:sizeof(unsigned) * 3 62 | options:MTLResourceOptionCPUCacheModeDefault]; 63 | [(id)mIndexBuffer setLabel:@"IBO"]; 64 | memcpy(((id)mIndexBuffer).contents, mIndexBufferData, sizeof(unsigned) * 3); 65 | 66 | // Create Uniform Buffer 67 | mUniformBuffer = [(id)mDevice newBufferWithLength:(sizeof(uboVS) + 255) & ~255 68 | options:MTLResourceOptionCPUCacheModeDefault]; 69 | [(id)mUniformBuffer setLabel:@"UBO"]; 70 | 71 | // Update Uniforms 72 | float zoom = -2.5f; 73 | 74 | // Update matrices 75 | uboVS.projectionMatrix = Matrix4::perspective(45.0f, (float)mViewportSize[0] / (float)mViewportSize[1], 0.01f, 1024.0f); 76 | 77 | uboVS.viewMatrix = Matrix4::translation(Vector3(0.0f, 0.0f, zoom)) * Matrix4::rotationZ(3.14f); 78 | 79 | uboVS.modelMatrix = Matrix4::identity(); 80 | 81 | size_t uboSize = sizeof(uboVS); 82 | 83 | memcpy(((id)mUniformBuffer).contents, &uboVS, uboSize); 84 | 85 | // Load all the shader files with a .msl file extension in the project 86 | NSError* err = nil; 87 | 88 | // Load shader files, add null terminator to the end. 89 | std::vector vertSource = readFile("triangle.vert.msl"); 90 | vertSource.emplace_back(0); 91 | std::vector fragSource = readFile("triangle.frag.msl"); 92 | fragSource.emplace_back(0); 93 | 94 | { 95 | NSString* vertPath = [NSString stringWithCString:vertSource.data() encoding:[NSString defaultCStringEncoding]]; 96 | id vLibrary = [(id)mDevice newLibraryWithSource:vertPath options:nil error:&err]; 97 | vertLibrary = vLibrary; 98 | } 99 | 100 | { 101 | NSString* fragPath = [NSString stringWithCString:fragSource.data() encoding:[NSString defaultCStringEncoding]]; 102 | id fLibrary = [(id)mDevice newLibraryWithSource:fragPath options:nil error:&err]; 103 | fragLibrary = fLibrary; 104 | } 105 | 106 | // Load the vertex function from the library 107 | vertexFunction = [(id)vertLibrary newFunctionWithName:@"main0"]; 108 | 109 | // Load the fragment function from the library 110 | fragmentFunction = [(id)fragLibrary newFunctionWithName:@"main0"]; 111 | 112 | // Configure a pipeline descriptor that is used to create a pipeline state 113 | MTLRenderPipelineDescriptor* pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; 114 | pipelineStateDescriptor.label = @"Simple Pipeline"; 115 | pipelineStateDescriptor.vertexFunction = (id)vertexFunction; 116 | pipelineStateDescriptor.fragmentFunction = (id)fragmentFunction; 117 | pipelineStateDescriptor.colorAttachments[0].pixelFormat = ((CAMetalLayer*)mLayer).pixelFormat; 118 | 119 | MTLVertexDescriptor* vertexDesc = [MTLVertexDescriptor vertexDescriptor]; 120 | vertexDesc.attributes[0].format = MTLVertexFormatFloat3; 121 | vertexDesc.attributes[0].offset = 0; 122 | vertexDesc.attributes[0].bufferIndex = 0; 123 | vertexDesc.attributes[1].format = MTLVertexFormatFloat3; 124 | vertexDesc.attributes[1].offset = sizeof(float) * 3; 125 | vertexDesc.attributes[1].bufferIndex = 0; 126 | vertexDesc.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; 127 | vertexDesc.layouts[0].stride = sizeof(Vertex); 128 | 129 | pipelineStateDescriptor.vertexDescriptor = vertexDesc; 130 | NSError* error = nil; 131 | 132 | mPipelineState = [(id)mDevice newRenderPipelineStateWithDescriptor:pipelineStateDescriptor 133 | error:&error]; 134 | if (!mPipelineState) 135 | { 136 | // Pipeline State creation could fail if we haven't properly set up our pipeline descriptor. 137 | // If the Metal API validation is enabled, we can find out more information about what 138 | // went wrong. (Metal API validation is enabled by default when a debug build is run 139 | // from Xcode) 140 | NSLog(@"Failed to created pipeline state, error %@", error); 141 | return; 142 | } 143 | 144 | } 145 | 146 | void Renderer::destroyResources() 147 | { 148 | [(id)fragmentFunction release]; 149 | [(id)vertexFunction release]; 150 | 151 | [(id)vertLibrary release]; 152 | [(id)fragLibrary release]; 153 | 154 | [(id)mVertexBuffer release]; 155 | [(id)mIndexBuffer release]; 156 | [(id)mUniformBuffer release]; 157 | 158 | [(id)mPipelineState release]; 159 | } 160 | 161 | void Renderer::resize(unsigned int width, unsigned int height) 162 | { 163 | mViewportSize[0] = width; 164 | mViewportSize[1] = height; 165 | } 166 | 167 | void Renderer::setupCommands() 168 | { 169 | // Commands are set at render time 170 | } 171 | 172 | void Renderer::render() 173 | { 174 | // Framelimit set to 60 fps 175 | tEnd = std::chrono::high_resolution_clock::now(); 176 | float time = std::chrono::duration(tEnd - tStart).count(); 177 | if (time < (1000.0f / 60.0f)) 178 | { return; } 179 | tStart = std::chrono::high_resolution_clock::now(); 180 | 181 | // Update uniforms 182 | 183 | mElapsedTime += 0.001f * time; 184 | mElapsedTime = fmodf(mElapsedTime, 6.283185307179586f); 185 | 186 | uboVS.modelMatrix = Matrix4::rotationY(mElapsedTime); 187 | memcpy(((id)mUniformBuffer).contents, &uboVS, sizeof(uboVS)); 188 | 189 | // Create a new command buffer for each render pass to the current drawable 190 | 191 | if (mCommandBuffer != nil) 192 | { [(id)mCommandBuffer release]; } 193 | mCommandBuffer = [(id)mCommandQueue commandBuffer]; 194 | ((id)mCommandBuffer).label = @"MyCommand"; 195 | 196 | // Build renderPassDescriptor generated from the view's drawable textures 197 | id drawable = ((CAMetalLayer*)mLayer).nextDrawable; 198 | 199 | MTLRenderPassDescriptor* renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; 200 | renderPassDescriptor.colorAttachments[0].texture = drawable.texture; 201 | renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; 202 | 203 | MTLClearColor clearCol; 204 | clearCol.red = 0.2; 205 | clearCol.green = 0.2; 206 | clearCol.blue = 0.2; 207 | clearCol.alpha = 1.0; 208 | renderPassDescriptor.colorAttachments[0].clearColor = clearCol; 209 | 210 | if(renderPassDescriptor != nil) 211 | { 212 | // Create a render command encoder so we can render into something 213 | id renderEncoder = 214 | [(id)mCommandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; 215 | renderEncoder.label = @"MyRenderEncoder"; 216 | 217 | // Set the region of the drawable to which we'll draw. 218 | [renderEncoder setViewport:(MTLViewport){0.0, 0.0, static_cast(mViewportSize[0]), static_cast(mViewportSize[1]), 0.1, 1000.0 }]; 219 | 220 | [renderEncoder setRenderPipelineState: (id)mPipelineState]; 221 | 222 | [renderEncoder setCullMode:MTLCullModeNone]; 223 | 224 | [renderEncoder setVertexBuffer:(id)mUniformBuffer offset:0 atIndex:1]; 225 | 226 | [renderEncoder setVertexBuffer:(id)mVertexBuffer offset:0 atIndex:0]; 227 | 228 | 229 | [renderEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle indexCount:3 indexType:MTLIndexTypeUInt32 indexBuffer:(id)mIndexBuffer indexBufferOffset:0]; 230 | 231 | [renderEncoder endEncoding]; 232 | 233 | [(id)mCommandBuffer presentDrawable:drawable]; 234 | 235 | [(id)mCommandBuffer commit]; 236 | 237 | 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/src/OpenGLRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Renderer.h" 3 | 4 | Renderer::Renderer(xwin::Window& window) 5 | { 6 | xwin::WindowDesc desc = window.getDesc(); 7 | mWidth = clamp(desc.width, 1u, 0xffffu); 8 | mHeight = clamp(desc.height, 1u, 0xffffu); 9 | 10 | initializeAPI(window); 11 | initializeResources(); 12 | setupCommands(); 13 | tStart = std::chrono::high_resolution_clock::now(); 14 | } 15 | 16 | Renderer::~Renderer() 17 | { 18 | destroyFrameBuffer(); 19 | 20 | destroyResources(); 21 | 22 | destroyAPI(); 23 | } 24 | 25 | void Renderer::initializeAPI(xwin::Window& window) 26 | { 27 | xgfx::OpenGLDesc ogldesc; 28 | mOGLState = xgfx::createContext(&window, ogldesc); 29 | xgfx::setContext(mOGLState); 30 | if (!gladLoadGL()) 31 | { 32 | // Failed 33 | std::cout << "Failed to load OpenGL."; 34 | return; 35 | } 36 | #if defined(_DEBUG) 37 | GLenum err = glGetError(); 38 | if (err != GL_NO_ERROR) 39 | { 40 | std::cout << "Error loading OpenGL."; 41 | } 42 | #endif 43 | } 44 | 45 | void Renderer::destroyAPI() 46 | { 47 | xgfx::unsetContext(mOGLState); 48 | xgfx::destroyContext(mOGLState); 49 | } 50 | 51 | void Renderer::initializeResources() 52 | { 53 | // OpenGL global setup 54 | glClearColor(0.2f, 0.2f, 0.2f, 1.0f); 55 | glEnable(GL_DEPTH_TEST); 56 | 57 | glGenVertexArrays(1, &mVertexArray); 58 | glBindVertexArray(mVertexArray); 59 | glEnableVertexAttribArray(0); 60 | 61 | auto checkShaderCompilation = [&](GLuint shader) 62 | { 63 | #if defined(_DEBUG) 64 | GLint isCompiled = 0; 65 | glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled); 66 | if (isCompiled == GL_FALSE) 67 | { 68 | GLint maxLength = 0; 69 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); 70 | std::vector errorLog(maxLength); 71 | glGetShaderInfoLog(shader, maxLength, &maxLength, &errorLog[0]); 72 | glDeleteShader(shader); 73 | 74 | std::cout << errorLog.data(); 75 | 76 | return false; 77 | } 78 | #endif 79 | return true; 80 | }; 81 | 82 | std::vector vertShaderCode = readFile("assets/shaders/triangle.vert.glsl"); 83 | GLchar* vertStr = vertShaderCode.data(); 84 | GLint vertLen = static_cast(vertShaderCode.size()); 85 | std::vector fragShaderCode = readFile("assets/shaders/triangle.frag.glsl"); 86 | GLchar* fragStr = fragShaderCode.data(); 87 | GLint fragLen = static_cast(fragShaderCode.size()); 88 | 89 | mVertexShader = glCreateShader(GL_VERTEX_SHADER); 90 | glShaderSource(mVertexShader, 1, &vertStr, &vertLen); 91 | glCompileShader(mVertexShader); 92 | if (!checkShaderCompilation(mVertexShader)) return; 93 | 94 | mFragmentShader = glCreateShader(GL_FRAGMENT_SHADER); 95 | glShaderSource(mFragmentShader, 1, &fragStr, &fragLen); 96 | glCompileShader(mFragmentShader); 97 | if (!checkShaderCompilation(mFragmentShader)) return; 98 | 99 | mProgram = glCreateProgram(); 100 | glAttachShader(mProgram, mVertexShader); 101 | glAttachShader(mProgram, mFragmentShader); 102 | glLinkProgram(mProgram); 103 | 104 | GLint result = 0; 105 | glGetProgramiv(mProgram, GL_LINK_STATUS, &result); 106 | #if defined(_DEBUG) 107 | if (result != GL_TRUE) { 108 | std::cout << "Program failed to link."; 109 | return; 110 | } 111 | #endif 112 | glUseProgram(mProgram); 113 | 114 | glGenBuffers(1, &mVertexBuffer); 115 | glGenBuffers(1, &mIndexBuffer); 116 | 117 | glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer); 118 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer); 119 | 120 | glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * 3, mVertexBufferData, GL_STATIC_DRAW); 121 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * 3, mIndexBufferData, GL_STATIC_DRAW); 122 | 123 | mPositionAttrib = glGetAttribLocation(mProgram, "inPos"); 124 | mColorAttrib = glGetAttribLocation(mProgram, "inColor"); 125 | glEnableVertexAttribArray(mPositionAttrib); 126 | glEnableVertexAttribArray(mColorAttrib); 127 | glVertexAttribPointer(mPositionAttrib, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position)); 128 | glVertexAttribPointer(mColorAttrib, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, color)); 129 | 130 | GLuint matrixBlockIndex = glGetUniformBlockIndex(mProgram, "UBO"); 131 | glUniformBlockBinding(mProgram, matrixBlockIndex, 0); 132 | 133 | glGenBuffers(1, &mUniformUBO); 134 | glBindBuffer(GL_UNIFORM_BUFFER, mUniformUBO); 135 | glBufferData(GL_UNIFORM_BUFFER, sizeof(uboVS), &uboVS, GL_DYNAMIC_DRAW); 136 | glBindBufferRange(GL_UNIFORM_BUFFER, 0, mUniformUBO, 0, sizeof(uboVS)); 137 | // Update Uniforms 138 | uboVS.projectionMatrix = Matrix4::perspective(45.0f, (float)1280 / (float)720, 0.01f, 1024.0f); 139 | uboVS.viewMatrix = Matrix4::translation(Vector3(0.0f, 0.0f, -2.5f)) * Matrix4::rotationZ(3.14f); 140 | uboVS.modelMatrix = Matrix4::identity(); 141 | 142 | GLvoid* p = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY); 143 | memcpy(p, &uboVS, sizeof(uboVS)); 144 | glUnmapBuffer(GL_UNIFORM_BUFFER); 145 | 146 | } 147 | 148 | void Renderer::destroyResources() 149 | { 150 | glDisableVertexAttribArray(mPositionAttrib); 151 | glDisableVertexAttribArray(mColorAttrib); 152 | glDeleteShader(mVertexShader); 153 | glDeleteShader(mFragmentShader); 154 | glDeleteProgram(mProgram); 155 | glDeleteVertexArrays(1, &mVertexArray); 156 | glDeleteBuffers(1, &mVertexBuffer); 157 | glDeleteBuffers(1, &mIndexBuffer); 158 | glDeleteBuffers(1, &mUniformUBO); 159 | } 160 | 161 | void Renderer::render() 162 | { 163 | // Framelimit set to 60 fps 164 | tEnd = std::chrono::high_resolution_clock::now(); 165 | float time = std::chrono::duration(tEnd - tStart).count(); 166 | if (time < (1000.0f / 60.0f)) 167 | { 168 | return; 169 | } 170 | tStart = std::chrono::high_resolution_clock::now(); 171 | 172 | xgfx::swapBuffers(mOGLState); 173 | 174 | // Update Uniforms 175 | mElapsedTime += 0.001f * time; 176 | mElapsedTime = fmodf(mElapsedTime, 6.283185307179586f); 177 | 178 | uboVS.modelMatrix = Matrix4::rotationY(mElapsedTime); 179 | GLvoid* p = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY); 180 | memcpy(p, &uboVS, sizeof(uboVS)); 181 | glUnmapBuffer(GL_UNIFORM_BUFFER); 182 | 183 | // Draw 184 | glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffer); 185 | glViewport(0, 0, mWidth, mHeight); 186 | 187 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 188 | glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0); 189 | 190 | // Blit framebuffer to window 191 | glBindFramebuffer(GL_READ_FRAMEBUFFER, mFrameBuffer); 192 | glReadBuffer(GL_COLOR_ATTACHMENT0); 193 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); 194 | glViewport(0, 0, mWidth, mHeight); 195 | glBlitFramebuffer(0, 0, mWidth, mHeight, 0, 0, mWidth, mHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); 196 | } 197 | 198 | void Renderer::resize(unsigned width, unsigned height) 199 | { 200 | mWidth = clamp(width, 1u, 0xffffu); 201 | mHeight = clamp(height, 1u, 0xffffu); 202 | 203 | // Update Unforms 204 | uboVS.projectionMatrix = Matrix4::perspective(45.0f, static_cast(mWidth) / static_cast(mHeight), 0.01f, 1024.0f); 205 | GLvoid* p = glMapBuffer(GL_UNIFORM_BUFFER, GL_WRITE_ONLY); 206 | memcpy(p, &uboVS, sizeof(uboVS)); 207 | glUnmapBuffer(GL_UNIFORM_BUFFER); 208 | 209 | destroyFrameBuffer(); 210 | initFrameBuffer(); 211 | } 212 | 213 | void Renderer::initFrameBuffer() 214 | { 215 | glGenTextures(1, &mFrameBufferTex); 216 | glBindTexture(GL_TEXTURE_2D, mFrameBufferTex); 217 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 218 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 219 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); 220 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); 221 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, mWidth, mHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); 222 | glBindTexture(GL_TEXTURE_2D, 0); 223 | 224 | glGenRenderbuffers(1, &mRenderBufferDepth); 225 | glBindRenderbuffer(GL_RENDERBUFFER, mRenderBufferDepth); 226 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, mWidth, mHeight); 227 | 228 | glGenFramebuffers(1, &mFrameBuffer); 229 | glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffer); 230 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFrameBufferTex, 0); 231 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, mRenderBufferDepth); 232 | 233 | GLenum DrawBuffers[1] = { GL_COLOR_ATTACHMENT0 }; 234 | glDrawBuffers(1, DrawBuffers); 235 | #if defined(_DEBUG) 236 | GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); 237 | if (status != GL_FRAMEBUFFER_COMPLETE) 238 | { 239 | std::cout << "Frame Buffer Failed to be Created!"; 240 | } 241 | #endif 242 | glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffer); 243 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 244 | } 245 | 246 | void Renderer::destroyFrameBuffer() 247 | { 248 | glDeleteTextures(1, &mFrameBufferTex); 249 | glDeleteRenderbuffers(1, &mRenderBufferDepth); 250 | glDeleteFramebuffers(1, &mFrameBuffer); 251 | } 252 | 253 | /** 254 | * While most modern graphics APIs have a swapchains, command queue, sync, and render passes, OpenGL does not. 255 | * OpenGL does have frame buffers, but it creates one by default. 256 | * So these functions are just stubs: 257 | */ 258 | 259 | void Renderer::setupSwapchain(unsigned width, unsigned height) 260 | { 261 | // Driver sets up swapchain 262 | } 263 | 264 | void Renderer::createCommands() 265 | { 266 | // Driver creates commands in OpenGL, you just set state 267 | } 268 | 269 | void Renderer::setupCommands() 270 | { 271 | // Driver creates commands in OpenGL, you just set state 272 | } 273 | 274 | void Renderer::destroyCommands() 275 | { 276 | // Driver destroys commands 277 | } 278 | 279 | void Renderer::createRenderPass() 280 | { 281 | // Render passes exist at the driver level 282 | } 283 | 284 | void Renderer::createSynchronization() 285 | { 286 | // Driver handles sync 287 | } 288 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/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 | #if defined(XGFX_VULKAN) 137 | // Initialization 138 | vk::Instance mInstance; 139 | vk::PhysicalDevice mPhysicalDevice; 140 | vk::Device mDevice; 141 | 142 | vk::SwapchainKHR mSwapchain; 143 | vk::SurfaceKHR mSurface; 144 | 145 | float mQueuePriority; 146 | vk::Queue mQueue; 147 | uint32_t mQueueFamilyIndex; 148 | 149 | vk::CommandPool mCommandPool; 150 | std::vector mCommandBuffers; 151 | uint32_t mCurrentBuffer; 152 | 153 | vk::Extent2D mSurfaceSize; 154 | vk::Rect2D mRenderArea; 155 | vk::Viewport mViewport; 156 | 157 | // Resources 158 | vk::Format mSurfaceColorFormat; 159 | vk::ColorSpaceKHR mSurfaceColorSpace; 160 | vk::Format mSurfaceDepthFormat; 161 | vk::Image mDepthImage; 162 | vk::DeviceMemory mDepthImageMemory; 163 | 164 | vk::DescriptorPool mDescriptorPool; 165 | std::vector mDescriptorSetLayouts; 166 | std::vector mDescriptorSets; 167 | 168 | vk::ShaderModule mVertModule; 169 | vk::ShaderModule mFragModule; 170 | 171 | vk::RenderPass mRenderPass; 172 | 173 | vk::Buffer mVertexBuffer; 174 | vk::Buffer mIndexBuffer; 175 | 176 | vk::PipelineCache mPipelineCache; 177 | vk::Pipeline mPipeline; 178 | vk::PipelineLayout mPipelineLayout; 179 | 180 | // Sync 181 | vk::Semaphore mPresentCompleteSemaphore; 182 | vk::Semaphore mRenderCompleteSemaphore; 183 | std::vector mWaitFences; 184 | 185 | // Swpachain 186 | struct SwapChainBuffer { 187 | vk::Image image; 188 | std::array views; 189 | vk::Framebuffer frameBuffer; 190 | }; 191 | 192 | std::vector mSwapchainBuffers; 193 | 194 | // Vertex buffer and attributes 195 | struct { 196 | vk::DeviceMemory memory; // Handle to the device memory for this buffer 197 | vk::Buffer buffer; // Handle to the Vulkan buffer object that the memory is bound to 198 | vk::PipelineVertexInputStateCreateInfo inputState; 199 | vk::VertexInputBindingDescription inputBinding; 200 | std::vector inputAttributes; 201 | } mVertices; 202 | 203 | // Index buffer 204 | struct 205 | { 206 | vk::DeviceMemory memory; 207 | vk::Buffer buffer; 208 | uint32_t count; 209 | } mIndices; 210 | 211 | // Uniform block object 212 | struct { 213 | vk::DeviceMemory memory; 214 | vk::Buffer buffer; 215 | vk::DescriptorBufferInfo descriptor; 216 | } mUniformDataVS; 217 | 218 | #elif defined(XGFX_DIRECTX12) 219 | 220 | static const UINT backbufferCount = 2; 221 | 222 | xwin::Window* mWindow; 223 | unsigned mWidth, mHeight; 224 | 225 | // Initialization 226 | IDXGIFactory4* mFactory; 227 | IDXGIAdapter1* mAdapter; 228 | #if defined(_DEBUG) 229 | ID3D12Debug1* mDebugController; 230 | ID3D12DebugDevice* mDebugDevice; 231 | #endif 232 | ID3D12Device* mDevice; 233 | ID3D12CommandQueue* mCommandQueue; 234 | ID3D12CommandAllocator* mCommandAllocator; 235 | ID3D12GraphicsCommandList* mCommandList; 236 | 237 | // Current Frame 238 | UINT mCurrentBuffer; 239 | ID3D12DescriptorHeap* mRtvHeap; 240 | ID3D12Resource* mRenderTargets[backbufferCount]; 241 | IDXGISwapChain3* mSwapchain; 242 | 243 | // Resources 244 | D3D12_VIEWPORT mViewport; 245 | D3D12_RECT mSurfaceSize; 246 | 247 | ID3D12Resource* mVertexBuffer; 248 | ID3D12Resource* mIndexBuffer; 249 | 250 | ID3D12Resource* mUniformBuffer; 251 | ID3D12DescriptorHeap* mUniformBufferHeap; 252 | UINT8* mMappedUniformBuffer; 253 | 254 | D3D12_VERTEX_BUFFER_VIEW mVertexBufferView; 255 | D3D12_INDEX_BUFFER_VIEW mIndexBufferView; 256 | 257 | UINT mRtvDescriptorSize; 258 | ID3D12RootSignature* mRootSignature; 259 | ID3D12PipelineState* mPipelineState; 260 | 261 | // Sync 262 | UINT mFrameIndex; 263 | HANDLE mFenceEvent; 264 | ID3D12Fence* mFence; 265 | UINT64 mFenceValue; 266 | 267 | #elif defined(XGFX_DIRECTX11) 268 | 269 | bool mVsync; 270 | xwin::Window* mWindow; 271 | unsigned mWidth, mHeight; 272 | D3D11_VIEWPORT mViewport; 273 | 274 | IDXGIFactory* mFactory; 275 | IDXGIAdapter* mAdapter; 276 | #if defined(_DEBUG) 277 | ID3D11Debug* mDebugController; 278 | #endif 279 | IDXGIOutput* mAdapterOutput; 280 | unsigned mNumerator, mDenominator; 281 | ID3D11Device* mDevice; 282 | ID3D11DeviceContext* mDeviceContext; 283 | 284 | IDXGISwapChain* mSwapchain; 285 | ID3D11Texture2D* mBackbufferTex; 286 | ID3D11Texture2D* mDepthStencilBuffer; 287 | ID3D11DepthStencilView* mDepthStencilView; 288 | ID3D11RenderTargetView* mRenderTargetView; 289 | 290 | ID3D11DepthStencilState* mDepthStencilState; 291 | ID3D11RasterizerState* mRasterState; 292 | 293 | ID3D11Buffer *mVertexBuffer, *mIndexBuffer; 294 | ID3D11InputLayout* mLayout; 295 | ID3D11Buffer* mUniformBuffer; 296 | ID3D11VertexShader* mVertexShader; 297 | ID3D11PixelShader* mPixelShader; 298 | 299 | #elif defined(XGFX_OPENGL) 300 | //Initialization 301 | xgfx::OpenGLState mOGLState; 302 | 303 | unsigned mWidth, mHeight; 304 | 305 | GLuint mFrameBuffer; 306 | GLuint mFrameBufferTex; 307 | GLuint mRenderBufferDepth; 308 | 309 | // Resources 310 | GLuint mVertexShader; 311 | GLuint mFragmentShader; 312 | GLuint mProgram; 313 | GLuint mVertexArray; 314 | GLuint mVertexBuffer; 315 | GLuint mIndexBuffer; 316 | 317 | GLuint mUniformUBO; 318 | 319 | GLint mPositionAttrib; 320 | GLint mColorAttrib; 321 | 322 | #elif defined(XGFX_METAL) 323 | // MTLDevice - The device (aka GPU) we're using to render 324 | void* mDevice; 325 | //CAMetalLayer 326 | void* mLayer; 327 | //MTLCommandQueue - The command Queue from which we'll obtain command buffers 328 | void* mCommandQueue; 329 | // The current size of our view so we can use this in our render pipeline 330 | unsigned mViewportSize[2]; 331 | 332 | //Resources 333 | 334 | //MTLLibrary 335 | void* vertLibrary; 336 | //MTLLibrary 337 | void* fragLibrary; 338 | //MTLFunction 339 | void* vertexFunction; 340 | //MTLFunction 341 | void* fragmentFunction; 342 | //MTLBuffer 343 | void* mVertexBuffer; 344 | //MTLBuffer 345 | void* mIndexBuffer; 346 | //MTLBuffer 347 | void* mUniformBuffer; 348 | //MTLRenderPipelineState 349 | void* mPipelineState; 350 | //MTLCommandBuffer 351 | void* mCommandBuffer; 352 | 353 | #endif 354 | }; 355 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/src/VulkanRenderer.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/shaders/triangle.vert.spv"); 933 | std::vector fragShaderCode = readFile("assets/shaders/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 | 1275 | -------------------------------------------------------------------------------- /src/04-cross-platform-hello-triangle/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 | } --------------------------------------------------------------------------------