├── .gitignore ├── vulkan-meme.jpg ├── shaders ├── vertex.spv ├── fragment.spv ├── shader.frag ├── compile-shader-cmds.txt └── shader.vert ├── rainbow-triangle.png ├── README.md └── rainbow-triangle.odin /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *~ 3 | *.pdb -------------------------------------------------------------------------------- /vulkan-meme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/10aded/Odin-Vulkan-GLFW-Rainbow-Triangle/HEAD/vulkan-meme.jpg -------------------------------------------------------------------------------- /shaders/vertex.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/10aded/Odin-Vulkan-GLFW-Rainbow-Triangle/HEAD/shaders/vertex.spv -------------------------------------------------------------------------------- /rainbow-triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/10aded/Odin-Vulkan-GLFW-Rainbow-Triangle/HEAD/rainbow-triangle.png -------------------------------------------------------------------------------- /shaders/fragment.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/10aded/Odin-Vulkan-GLFW-Rainbow-Triangle/HEAD/shaders/fragment.spv -------------------------------------------------------------------------------- /shaders/shader.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec3 fragColor; 4 | 5 | layout(location = 0) out vec4 outColor; 6 | 7 | void main() { 8 | outColor = vec4(fragColor, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /shaders/compile-shader-cmds.txt: -------------------------------------------------------------------------------- 1 | The files vertex.spv and fragment.spv in this directory were creating by running the glslc.exe binary, available as part of the standard Vulkan SDK, on the .glsl files in this directory using the following commands. 2 | 3 | glslc.exe vertex.glsl -o vertex.spv 4 | glslc.exe fragment.glsl -o fragment.spv 5 | 6 | -------------------------------------------------------------------------------- /shaders/shader.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) out vec3 fragColor; 4 | 5 | vec2 positions[3] = vec2[]( 6 | vec2(0.0, -0.5), 7 | vec2(0.5, 0.5), 8 | vec2(-0.5, 0.5) 9 | ); 10 | 11 | vec3 colors[3] = vec3[]( 12 | vec3(1.0, 0.0, 0.0), 13 | vec3(0.0, 1.0, 0.0), 14 | vec3(0.0, 0.0, 1.0) 15 | ); 16 | 17 | void main() { 18 | gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); 19 | fragColor = colors[gl_VertexIndex]; 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vulkan Rainbow Triangle in Odin with GLFW 2 | 3 | This is a "minimal" example of a Vulkan app in Odin which renders a rainbow triangle. 4 | 5 | ![Screenshot](rainbow-triangle.png "A rainbow triangle, render on Windows 11 with Vulkan.") 6 | 7 | ## Build 8 | 9 | The code in `rainbow-triangle.odin` can be compiled and ran from a terminal 10 | with the SINGLE command: 11 | 12 | `odin run rainbow-triangle.odin -file -o:speed` 13 | 14 | The code can be compiled and ran in debug mode with 15 | 16 | `odin run rainbow-triangle.odin -file -debug` 17 | 18 | which in particular (by default) prints out information throughout the setup process. 19 | 20 | ## Summary of writing this program 21 | 22 | ![Screenshot](vulkan-meme.jpg "A meme illustrating the painful experience of drawing a triangle with Vulkan.") -------------------------------------------------------------------------------- /rainbow-triangle.odin: -------------------------------------------------------------------------------- 1 | // A "minimal" example of a Vulkan app in Odin which renders a rainbow triangle. 2 | // 3 | // Created by 10aded March 2024 - June 2024. 4 | // 5 | // The code in this file can be compiled and ran from a terminal 6 | // with the SINGLE command: 7 | // 8 | // odin run rainbow-triangle.odin -file -o:speed 9 | // 10 | // where odin is the Odin compiler, available at 11 | // 12 | // https://odin-lang.org/ 13 | // 14 | // The most recent version of the compiler that the project was tested with 15 | // is: dev-2024-06:c07a46a 16 | // 17 | // When compiled as above, the project does not require any external dependencies 18 | // (Vulkan drivers aside). Odin's vendor:glfw and vendor:vulkan libraries --- 19 | // included with the compiler by default --- are enough. 20 | // 21 | // The entire setup process / logic has been annotated with print statements, 22 | // which are activated when compiled (and ran) in -debug mode. 23 | // (For example, printing out the the available extensions of the selected 24 | // physical device (graphics card).) 25 | // 26 | // The code can be compiled and ran in debug mode with 27 | // 28 | // odin run rainbow-triangle.odin -file -debug 29 | // 30 | // Debug mode does, however, require the "VK_LAYER_KHRONOS_validation" layer, 31 | // which is included with the default Vulkan SDK available at: 32 | // 33 | // https://vulkan.lunarg.com/ 34 | // 35 | // (Of course, should you wish to compile the program in -debug mode without 36 | // downloading the SDK, you can simply comment the line containing 37 | // "VK_LAYER_KHRONOS_validation" below.) 38 | // 39 | // This code was written by following the canonical "Vulkan Tutorial" by 40 | // (presumably) Alexander Overvoorde (bizarrely the home page does not state 41 | // who the author of the tutorial actually is) at: 42 | // 43 | // https://vulkan-tutorial.com/ 44 | // 45 | // The code there, however, is in C++, so has been adapted to Odin. 46 | // 47 | // This code is available on github at: 48 | // 49 | // https://github.com/10aded/Odin-Vulkan-GLFW-Rainbow-Triangle 50 | // 51 | // and was posted for education purposes. 52 | // 53 | // USED VERBATIM, THE CODE HERE IS LIKELY UNSUITABLE AS PRODUCTION CODE. 54 | // (It may help get you started, though.) 55 | // 56 | // +--------------+ 57 | // | Code Summary | 58 | // +--------------+ 59 | // 60 | // "A journey of a thousand miles begins with a single step" 61 | // --- Laozi 6th cent. BCE 62 | // 0. Spawn a .glfw window. 63 | // 1. Load vulkan functions needed to setup an instance. 64 | // 2. Specify the application info. 65 | // 3. Check that the required / available layers and extensions are compatible. 66 | // a. Get / specify the required layers, a list of strings. 67 | // b. Get / specify the required extensions, a list of strings. 68 | // c. Get the available layers, a list of vk structs. 69 | // d. Get the available extensions, a list of vk structs. 70 | // e. Compare the above arrays. 71 | // 4. Create an instance. 72 | // 5. Load the rest of the vulkan functions. 73 | // 6. Create a window surface. 74 | // 7. Select a graphics card (physical device). 75 | // a. Create a list of the available physical devices. 76 | // b. Get / specify the required physical device extensions. 77 | // c. For each physical device, get its supported extensions. 78 | // d. Choose a physical device that contains the desired extensions. 79 | // (In this code, we choose the first that contains VK_KHR_swapchain.) 80 | // 8. Check that the window surface is compatible with a swapchain, and choose 81 | // its settings. 82 | // a. Get the surface capabilities. 83 | // b. Get the possible surface formats. 84 | // c. Get the possible present modes. 85 | // d. Check that there is at least 1 surface format and present mode. 86 | // e. Choose a surface format for the swapchain. 87 | // f. Choose a present mode for the swapchain. 88 | // g. Choose a swap extent for the swapchain. 89 | // h. Choose an image count. 90 | // 9. Choose appropriate queue families. 91 | // a. Get the available queue families. 92 | // b. Find a queue family that supports GRAPHICS. 93 | // c. Find a queue family that can present to the surface. 94 | // 10. Set up a logical device to interact with the physical device 95 | // for each queue family. 96 | // a. Specify the desired graphics queues to be created. 97 | // b. Specify the desired presentation queues to be created. 98 | // c. Specify the set of device features to be used, using queue info in a, b. 99 | // d. Create a logical device using the struct in c. 100 | // 11. Obtain a handle to the queues from the logical device. 101 | // 12. Create the swapchain using the settings chosen in 8. 102 | // 13. Obtain vk.ImageView s for the swapchain. 103 | // a. Obtain handles to the vk.Images in the swapchain. 104 | // b. Create a vk.ImageView for each vk.Image 105 | // 14. Create graphics pipeline. 106 | // a. Include shaders in the pipeline. 107 | // i. Create shader modules. 108 | // ii. Create pipeline shader create infos. 109 | // b. Specify the dynamic states (or not). 110 | // c. Describe the format of the vertex data, and how to use triangles. 111 | // d. Specify viewports and scissors. 112 | // e. Specify the rasterizer. 113 | // f. Specify multisampling. 114 | // g. Specify depth and stencil testing. [skipped] 115 | // h. Specify color blending. 116 | // i. Specify pipeline layout (uniforms) 117 | // j. Create the render pass. 118 | // i. Specify the framebuffer attachments. 119 | // ii. Specify the subpasses and dependencies. 120 | // iii. Create the render pass. 121 | // k. Finally, create the pipeline with a massive struct using the above! 122 | // 15. Create swapchain framebuffers. 123 | // 16. Record drawing commands in a command buffer. 124 | // a. Create a command pool. 125 | // b. Create a command buffer. 126 | // c. Draw the commands to the command buffer. 127 | // i. Begin the command buffer. 128 | // ii. Start a render pass. 129 | // iii. Bind pipeline. 130 | // iv. Draw commands. 131 | // v. End render pass and command buffer. 132 | // 17. Initialize synchronization objects. 133 | // 18. Write the procedure that renders a frame. 134 | // a. Wait for the previous frame to finish. 135 | // b. Acquire a swapchain image, reset objects. 136 | // c. Record drawing commands with the swapchain image. 137 | // d. Submit the command buffer. 138 | // e. Present the frame to the swapchain. 139 | 140 | package vulkan_tutorial 141 | 142 | import f "core:fmt" 143 | import "core:dynlib" 144 | import "core:strings" 145 | import glfw "vendor:glfw" 146 | import vk "vendor:vulkan" 147 | 148 | WINDOW_WIDTH :: 800 149 | WINDOW_HIDTH :: 800 150 | 151 | // Change to false to disable verbose debug printing. 152 | DEBUG_VERBOSE :: true 153 | 154 | DB :: "DEBUG:" 155 | DBVB :: "DEBUG (VERBOSE):" 156 | ERROR :: "ERROR:" 157 | WARNING :: "WARNING:" 158 | 159 | // Load the compiled SPIR-V vertex and fragment shader bytecode at compile-time 160 | // as constants. 161 | vertex_shader_bytecode :: #load("./shaders/vertex.spv") 162 | fragment_shader_bytecode :: #load("./shaders/fragment.spv") 163 | 164 | // Globals. 165 | window_handle : glfw.WindowHandle 166 | instance : vk.Instance 167 | device : vk.Device 168 | graphics_queue : vk.Queue 169 | present_queue : vk.Queue 170 | surface : vk.SurfaceKHR 171 | extent : vk.Extent2D 172 | format : vk.Format 173 | swapchain : vk.SwapchainKHR 174 | swapchain_images : [dynamic] vk.Image 175 | imageviews : [dynamic] vk.ImageView 176 | vertex_shader_mod : vk.ShaderModule 177 | fragment_shader_mod : vk.ShaderModule 178 | render_pass : vk.RenderPass 179 | pipeline_layout : vk.PipelineLayout 180 | graphics_pipeline : vk.Pipeline 181 | swapchain_fbs : [dynamic] vk.Framebuffer 182 | command_pool : vk.CommandPool 183 | command_buffer : vk.CommandBuffer 184 | 185 | // Synchronization objects 186 | image_available_semaphore : vk.Semaphore 187 | render_finished_semaphore : vk.Semaphore 188 | in_flight_fence : vk.Fence 189 | 190 | init_vulkan :: proc() { 191 | // 1. Load vulkan functions needed to setup an instance. 192 | 193 | // !!! !!! 194 | // !!! Important Step !!! 195 | // !!! !!! 196 | 197 | // (vk.CreateInstance will otherwise not work.) 198 | // 199 | // See "Initializing Vulkan with GLFW" in #help-forum in the Odin Discord. 200 | // for more information. 201 | 202 | vulkan_lib, loaded := dynlib.load_library("vulkan-1.dll") 203 | assert(loaded) 204 | 205 | vkGetInstanceProcAddr, found := dynlib.symbol_address(vulkan_lib, "vkGetInstanceProcAddr") 206 | assert(found) 207 | 208 | // Load a the Vulkan functions needed to setup an instance. 209 | vk.load_proc_addresses_global(vkGetInstanceProcAddr) 210 | 211 | // 2. Specify the application info. 212 | // (Techncially optional.) 213 | appinfo : vk.ApplicationInfo 214 | appinfo.sType = vk.StructureType.APPLICATION_INFO 215 | appinfo.pApplicationName = "Rainbow Triangle" 216 | appinfo.applicationVersion = vk.MAKE_VERSION(1,0,0) 217 | appinfo.pEngineName = "Minimal Example Engine" 218 | appinfo.engineVersion = vk.MAKE_VERSION(1,0,0) 219 | appinfo.apiVersion = vk.API_VERSION_1_0 220 | 221 | // 3. Check that the required / available Vulkan layers 222 | // and extensions are compatible. 223 | 224 | // 3a. Get / specify the required layers, a list of strings. 225 | required_layers : [dynamic] cstring 226 | defer delete(required_layers) 227 | 228 | when ODIN_DEBUG { 229 | append(&required_layers, "VK_LAYER_KHRONOS_validation") 230 | } 231 | 232 | // 3b. Get / specify the required extensions, a list of strings. 233 | required_extensions : [] cstring 234 | required_extensions = glfw.GetRequiredInstanceExtensions() 235 | 236 | // In -debug VERBOSE mode, print out the required layer names and extensions. 237 | when ODIN_DEBUG && DEBUG_VERBOSE { 238 | f.println("") 239 | f.println(DBVB, "Required layers:") 240 | for rlayer in required_layers { 241 | f.printf(" %s\n", rlayer) 242 | } 243 | f.println("") 244 | 245 | f.println(DBVB, "Required extensions:") 246 | for rextension in required_extensions { 247 | f.printf(" %s\n", rextension) 248 | } 249 | f.println("") 250 | } 251 | 252 | // 3c. Get the available layers, a list of vk structs. 253 | // Make a dynamic lists of the supported layers and extensions. 254 | supported_layers : [dynamic] vk.LayerProperties 255 | supported_layer_count : u32 256 | 257 | // Create, then populate, supported_layers. 258 | // Note: query the number supported layers by leaving the last parameter empty. 259 | slc_ok := vk.EnumerateInstanceLayerProperties(&supported_layer_count, nil) 260 | vkok(slc_ok) 261 | 262 | supported_layers = make([dynamic] vk.LayerProperties, supported_layer_count) 263 | defer delete(supported_layers) 264 | 265 | // Fill the empty list with the layer properties. 266 | slfill_ok := vk.EnumerateInstanceLayerProperties(&supported_layer_count, raw_data(supported_layers)) 267 | vkok(slfill_ok) 268 | 269 | 270 | // 3d. Get the available extensions, a list of vk structs. 271 | // Basically the same process as in 3c. 272 | supported_extensions : [dynamic] vk.ExtensionProperties 273 | supported_extension_count : u32 274 | sec_ok := vk.EnumerateInstanceExtensionProperties(nil, &supported_extension_count, nil) 275 | vkok(sec_ok) 276 | supported_extensions = make([dynamic] vk.ExtensionProperties, supported_extension_count) 277 | defer delete(supported_extensions) 278 | secfill_ok := vk.EnumerateInstanceExtensionProperties(nil, &supported_extension_count, raw_data(supported_extensions)) 279 | vkok(secfill_ok) 280 | 281 | // In -debug VERBOSE mode, print out the supported layers and extensions. 282 | when ODIN_DEBUG && DEBUG_VERBOSE { 283 | f.println(DBVB, "Supported layers:") 284 | // Iterate in a by-reference manner. 285 | for layer in supported_layers { 286 | hardcoded_bytes := layer.layerName 287 | name_slice := hardcoded_bytes[:] 288 | lname := cstring(raw_data(name_slice)) 289 | f.println(lname) 290 | } 291 | f.println("") 292 | 293 | f.println(DBVB, "Supported extensions:") 294 | // Iterate in a by-reference manner. 295 | for extension in supported_extensions { 296 | hardcoded_bytes := extension.extensionName 297 | name_slice := hardcoded_bytes[:] 298 | ename := cstring(raw_data(name_slice)) 299 | f.println(ename) 300 | } 301 | f.println("") 302 | } 303 | 304 | // 3e. Compare the above arrays. 305 | // Check that all of the required layers and glfwExtensions are actually in 306 | // supported_layers / supported_extensions. 307 | 308 | // Layers first. 309 | required_layers_available := true 310 | l1: for layername1 in required_layers { 311 | for sl in supported_layers { 312 | hardcoded_bytes := sl.layerName 313 | name_slice := hardcoded_bytes[:] 314 | layername2 := cstring(raw_data(name_slice)) 315 | if layername1 == layername2 { continue l1 } 316 | } 317 | // If execution has reached here, a required layer has not been found. 318 | f.println(ERROR, "A required layer:", layername1, "is missing!") 319 | required_layers_available = false 320 | break l1 321 | } 322 | 323 | // Now check extensions, same as above. 324 | required_extensions_available := true 325 | l2: for extensionname1 in required_extensions { 326 | for sl in supported_extensions { 327 | hardcoded_bytes := sl.extensionName 328 | name_slice := hardcoded_bytes[:] 329 | extensionname2 := cstring(raw_data(name_slice)) 330 | if extensionname1 == extensionname2 { continue l2 } 331 | } 332 | f.println(ERROR, "A required extension:", extensionname1, "is missing!") 333 | required_extensions_available = false 334 | break l2 335 | } 336 | 337 | assert(required_layers_available && required_extensions_available) 338 | 339 | when ODIN_DEBUG { 340 | f.println(DB, "The required layers and extensions are in the supported layers and extensions.\n") 341 | } 342 | 343 | // +-------------------------------------------------------------------+ 344 | // | Now that we have verified the required layers and extensions are | 345 | // | supported, we can make a vk.Instance. | 346 | // +-------------------------------------------------------------------+ 347 | // 348 | // 4. Create an instance. 349 | 350 | // Set up the info required to make an instance. 351 | icreateinfo : vk.InstanceCreateInfo 352 | icreateinfo.sType = vk.StructureType.INSTANCE_CREATE_INFO 353 | icreateinfo.pApplicationInfo = &appinfo 354 | icreateinfo.enabledLayerCount = u32(len(required_layers)) 355 | icreateinfo.ppEnabledLayerNames = raw_data(required_layers) 356 | icreateinfo.enabledExtensionCount = u32(len(required_extensions)) 357 | icreateinfo.ppEnabledExtensionNames = raw_data(required_extensions) 358 | 359 | // Create the Instance (finally)! 360 | ci_ok := vk.CreateInstance(&icreateinfo, nil, &instance) 361 | vkok(ci_ok) 362 | 363 | // 5. Load the rest of the vulkan functions. 364 | // +----------------------------------------------------------------+ 365 | // | REMEMBER TO LOAD THE OTHER VULKAN FUNCTIONS WITH THE INSTANCE. | 366 | // +----------------------------------------------------------------+ 367 | vk.load_proc_addresses(instance) 368 | 369 | // 6. Create a window surface. 370 | ws_ok := glfw.CreateWindowSurface(instance, window_handle, nil, &surface) 371 | vkok(ws_ok) 372 | 373 | // 7. Select a graphics card (physical device) or whatever's available. 374 | physical_device : vk.PhysicalDevice 375 | physical_devices : [dynamic] vk.PhysicalDevice 376 | pds_count : u32 377 | 378 | // 7a. Create a list of the available physical devices. 379 | // Note: query the number of physical devices by making the last param nil. 380 | pdc_ok := vk.EnumeratePhysicalDevices(instance, &pds_count, nil) 381 | vkok(pdc_ok) 382 | if pds_count == 0 { 383 | f.eprintln(ERROR, "Number of physical devices is 0!") ; assert(false) 384 | } 385 | physical_devices = make([dynamic] vk.PhysicalDevice, pds_count) 386 | 387 | // Get the pds. Physical devices are just rawptrs. 388 | pdfill_ok := vk.EnumeratePhysicalDevices(instance, &pds_count, raw_data(physical_devices)) 389 | vkok(pdfill_ok) 390 | 391 | // In -debug VERBOSE mode, print out the available physical devices. 392 | when ODIN_DEBUG && DEBUG_VERBOSE { 393 | f.println(DBVB, "Possible physical devices:") 394 | for pd in physical_devices { 395 | pd_props : vk.PhysicalDeviceProperties 396 | vk.GetPhysicalDeviceProperties(pd, &pd_props) 397 | f.printf(" %s\n", pd_props.deviceName) 398 | } 399 | f.println("") 400 | } 401 | 402 | // 7b. Get / specify the required physical device extensions. 403 | pdevice_required_extensions := [] cstring{ 404 | vk.KHR_SWAPCHAIN_EXTENSION_NAME, 405 | } 406 | 407 | // 7c. For each physical device, get its supported extensions. 408 | at_least_one_pdevice_okay := false 409 | okay_pdevice : vk.PhysicalDevice 410 | pdevice_properties : vk.PhysicalDeviceProperties 411 | 412 | for pdevice in physical_devices { 413 | pd_properties : vk.PhysicalDeviceProperties 414 | // Bizarrely, this doesn't return a vk.Result 415 | vk.GetPhysicalDeviceProperties(pdevice, &pd_properties) 416 | 417 | pdevice_supported_extensions : [dynamic] vk.ExtensionProperties 418 | pdevice_supported_extensions_count : u32 419 | 420 | pdec_ok := vk.EnumerateDeviceExtensionProperties(pdevice, nil, &pdevice_supported_extensions_count, nil) 421 | vkok(pdec_ok) 422 | 423 | pdevice_supported_extensions = make([dynamic] vk.ExtensionProperties, pdevice_supported_extensions_count) 424 | depfill_ok := vk.EnumerateDeviceExtensionProperties(pdevice, nil, &pdevice_supported_extensions_count, raw_data(pdevice_supported_extensions)) 425 | vkok(depfill_ok) 426 | 427 | pdname := transmute(cstring) &pd_properties.deviceName 428 | 429 | when ODIN_DEBUG && DEBUG_VERBOSE { 430 | f.println(DBVB, "Supported extensions of", pdname, ":") 431 | // Iterate in a by-reference manner. 432 | for pse in pdevice_supported_extensions { 433 | hardcoded_bytes := pse.extensionName 434 | name_slice := hardcoded_bytes[:] 435 | ename := cstring(raw_data(name_slice)) 436 | f.printf(" %s\n", ename) 437 | } 438 | f.println("") 439 | } 440 | 441 | // 7d. Choose a physical device that contains the desired extensions. 442 | // (In our case, choose the first such physical device.) 443 | required_pdevice_extensions_available := true 444 | l3: for extensionname1 in pdevice_required_extensions { 445 | for pse in pdevice_supported_extensions { 446 | hardcoded_bytes := pse.extensionName 447 | name_slice := hardcoded_bytes[:] 448 | extensionname2 := cstring(raw_data(name_slice)) 449 | if extensionname1 == extensionname2 { continue l3 } 450 | } 451 | f.println(WARNING, "A required extension:", extensionname1, "is missing from:", pdname) 452 | required_pdevice_extensions_available = false 453 | break l3 454 | } 455 | if required_pdevice_extensions_available { 456 | at_least_one_pdevice_okay = true 457 | okay_pdevice = pdevice 458 | pdevice_properties = pd_properties 459 | } else { 460 | continue 461 | } 462 | } 463 | 464 | if ! at_least_one_pdevice_okay { 465 | f.eprintln("No physical devices have all the required extensions") 466 | assert(false) 467 | } 468 | 469 | pdevice := okay_pdevice 470 | 471 | when ODIN_DEBUG { 472 | f.println(DB, "A physical device has been chosen.") 473 | f.println(DB, "The physical device has name / type:") 474 | name := transmute(cstring) &pdevice_properties.deviceName 475 | f.printf(" %s", name) 476 | f.println(" / ", pdevice_properties.deviceType) 477 | f.println("") 478 | } 479 | 480 | // 8. Check that the window surface is compatible with a swapchain, and choose 481 | // its settings. 482 | 483 | // Note: In a more advanced implementation, this could be factored into the 484 | // physical device selection itself, but we omit this for simplicity. 485 | 486 | surface_capabilities : vk.SurfaceCapabilitiesKHR 487 | supported_surface_formats : [dynamic] vk.SurfaceFormatKHR 488 | supported_present_modes : [dynamic] vk.PresentModeKHR 489 | supported_surface_formats_count : u32 490 | supported_present_modes_count : u32 491 | 492 | // 8a. Get the surface capabilities. 493 | vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(pdevice, surface, &surface_capabilities) 494 | 495 | // 8b. Get the possible surface formats. 496 | sfc_ok := vk.GetPhysicalDeviceSurfaceFormatsKHR(pdevice, surface, &supported_surface_formats_count, nil) 497 | vkok(sfc_ok) 498 | 499 | if supported_surface_formats_count != 0 { 500 | supported_surface_formats = make([dynamic] vk.SurfaceFormatKHR, supported_surface_formats_count) 501 | gpdsf_ok := vk.GetPhysicalDeviceSurfaceFormatsKHR(pdevice, surface, &supported_surface_formats_count, raw_data(supported_surface_formats)) 502 | vkok(gpdsf_ok) 503 | } 504 | 505 | // 8c. Get the possible present modes. 506 | pmc_ok := vk.GetPhysicalDeviceSurfacePresentModesKHR(pdevice, surface, &supported_present_modes_count, nil) 507 | vkok(pmc_ok) 508 | 509 | if supported_present_modes_count != 0 { 510 | supported_present_modes = make([dynamic] vk.PresentModeKHR, supported_present_modes_count) 511 | gpdspm_ok := vk.GetPhysicalDeviceSurfacePresentModesKHR(pdevice, surface, &supported_present_modes_count, raw_data(supported_present_modes)) 512 | vkok(gpdspm_ok) 513 | } 514 | 515 | when ODIN_DEBUG && DEBUG_VERBOSE { 516 | f.println(DBVB, "Supported surface capabilities:") 517 | f.printf(" ") 518 | f.println(surface_capabilities) 519 | f.println("") 520 | 521 | f.println(DBVB, "Supported surface formats:") 522 | for sf in supported_surface_formats { 523 | f.printf(" ") 524 | f.println(sf) 525 | } 526 | f.println("") 527 | 528 | f.println(DBVB, "Supported present modes:") 529 | for pm in supported_present_modes { 530 | f.printf(" ") 531 | f.println(pm) 532 | } 533 | f.println("") 534 | } 535 | 536 | // 8d. Check that there is at least 1 surface format and present mode. 537 | if supported_surface_formats_count == 0 { 538 | f.eprint(ERROR, "There are no surface formats!") 539 | assert(false) 540 | } 541 | if supported_present_modes_count == 0 { 542 | f.eprint(ERROR, "There are no present modes!") 543 | assert(false) 544 | } 545 | 546 | // 8e. Choose a surface format for the swapchain. 547 | surface_format := supported_surface_formats[0] 548 | desired_surface_format := vk.SurfaceFormatKHR{ 549 | format = .B8G8R8A8_SRGB, 550 | colorSpace = .SRGB_NONLINEAR, 551 | } 552 | 553 | for sf in supported_surface_formats { 554 | if sf == desired_surface_format { 555 | surface_format = desired_surface_format 556 | break 557 | } 558 | } 559 | 560 | // Set the global format 561 | format = surface_format.format 562 | 563 | when ODIN_DEBUG { 564 | f.println(DB, "Surface format selected:") 565 | f.printf(" ") 566 | f.println(surface_format) 567 | f.println("") 568 | } 569 | 570 | // 8f. Choose a present mode for the swapchain. 571 | // Note: Per the VkPresentModeKHR(3) manual page, 572 | // FIFO is required to be supported, so the initial assignment below is safe. 573 | present_mode := vk.PresentModeKHR.FIFO 574 | desired_present_mode := vk.PresentModeKHR.MAILBOX 575 | 576 | for pm in supported_present_modes { 577 | if pm == desired_present_mode { 578 | present_mode = desired_present_mode 579 | } 580 | } 581 | 582 | when ODIN_DEBUG { 583 | f.println(DB, "Present mode selected:") 584 | f.printf(" ") 585 | f.println(present_mode) 586 | f.println("") 587 | } 588 | 589 | // 8g. Choose a swap extent for the swapchain. 590 | extent_special_value := vk.Extent2D{ 591 | max(u32), 592 | max(u32), 593 | } 594 | // Per the VkSurfaceCapabilitiesKHR(3) man page, 595 | // if the current extent is extent_special_value, 596 | // then we have to specify this ourselves. 597 | // In such a case, we'll just choose the .minImageExtent. 598 | 599 | extent = surface_capabilities.currentExtent 600 | if extent == extent_special_value { 601 | extent = surface_capabilities.minImageExtent 602 | } 603 | 604 | when ODIN_DEBUG { 605 | f.println(DB, "Extent chosen:") 606 | f.printf(" ") 607 | f.println(extent) 608 | f.println("") 609 | } 610 | 611 | // 8h. Choose an image count. 612 | surface_image_count := min(surface_capabilities.minImageCount + 1, surface_capabilities.maxImageCount) 613 | 614 | // 9. Choose appropriate queue families. 615 | 616 | // 9a. Get the available queue families. 617 | qf_properties : [dynamic] vk.QueueFamilyProperties 618 | queue_count : u32 619 | 620 | // Note: query the number of extensions by leaving the last parameter empty. 621 | // Bizarrely, this doesn't return a .SUCCESS value. 622 | vk.GetPhysicalDeviceQueueFamilyProperties(pdevice, &queue_count, nil) 623 | qf_properties = make([dynamic] vk.QueueFamilyProperties, queue_count) 624 | vk.GetPhysicalDeviceQueueFamilyProperties(pdevice, &queue_count, raw_data(qf_properties)) 625 | 626 | // IN -debug VERBOSE mode, print out the available queue families. 627 | when ODIN_DEBUG && DEBUG_VERBOSE { 628 | f.println(DBVB, "Queue family properties:") 629 | for qf, qfi in qf_properties { 630 | f.printf(" Queue %d:\n", qfi) 631 | f.printf(" Flags:\t") 632 | f.println(qf.queueFlags) 633 | f.printf(" Count:\t") 634 | f.println(qf.queueCount) 635 | f.printf(" timestampValidBits / minImageTransferGranularity:\t") 636 | f.println(qf.timestampValidBits, qf.minImageTransferGranularity) 637 | } 638 | f.println("") 639 | } 640 | 641 | // The queue families supporting graphics and presentation may actually 642 | // be different. (But in many cases they are the same.) 643 | // As such, find each individually. 644 | 645 | // 9b. Find a queue family that supports GRAPHICS. 646 | // Recall the "e in A" Odin syntax for bit_sets. 647 | graphics_family_queue_index : u32 648 | found_gqf := false 649 | 650 | for qf, i in qf_properties { 651 | if vk.QueueFlag.GRAPHICS in qf.queueFlags { 652 | graphics_family_queue_index = u32(i) 653 | found_gqf = true 654 | break 655 | } 656 | } 657 | 658 | if ! found_gqf { 659 | f.eprintln(ERROR, "Unable to find a valid .GRAPHICS Queue family!") 660 | assert(false) 661 | } 662 | 663 | when ODIN_DEBUG { 664 | f.println(DB, "Graphics Family Queue Index Found:", graphics_family_queue_index) 665 | f.println("") 666 | } 667 | 668 | // 9c. Find a queue family that can present to the surface. 669 | surface_presentation_queue_index : u32 670 | found_pqf := false 671 | 672 | for qf, i in qf_properties { 673 | qf_supports_window : b32 674 | qf_window_support_check_ok := vk.GetPhysicalDeviceSurfaceSupportKHR(pdevice, u32(i), surface, &qf_supports_window) 675 | vkok(qf_window_support_check_ok) 676 | 677 | if qf_supports_window { 678 | surface_presentation_queue_index = u32(i) 679 | found_pqf = true 680 | break 681 | } 682 | } 683 | 684 | if ! found_pqf { 685 | f.eprintln(ERROR, "Unable to find a valid Queue family for surface presentation!") 686 | assert(false) 687 | } 688 | when ODIN_DEBUG { 689 | f.println(DB, "Surface Presentation Family Queue Index Found:", surface_presentation_queue_index) 690 | f.println("") 691 | } 692 | 693 | // 10. Set up a logical device to interact with the physical device 694 | // for each queue family. 695 | 696 | // 10a. Specify the desired graphics queues to be created. 697 | qci_graphics : vk.DeviceQueueCreateInfo 698 | queue_priority_graphics : f32 = 1 699 | 700 | qci_graphics.sType = vk.StructureType.DEVICE_QUEUE_CREATE_INFO 701 | qci_graphics.queueFamilyIndex = graphics_family_queue_index 702 | qci_graphics.queueCount = 1.0 703 | qci_graphics.pQueuePriorities = &queue_priority_graphics 704 | 705 | // 10b. Specify the desired presentation queues to be created. 706 | qci_present := qci_graphics 707 | qci_present.queueFamilyIndex = surface_presentation_queue_index 708 | 709 | // 10c. Specify the set of device features to be used, using queue info in a, b. 710 | 711 | // !! !! 712 | // !! Important !! 713 | // !! !! 714 | // 715 | // NOTE: Per the vkDeviceCreateInfo(3) manual page, 716 | // 717 | // "The queueFamilyIndex member of each element of pQueueCreateInfos must 718 | // be unique within pQueueCreateInfos, except that two members can share 719 | // the same queueFamilyIndex if one describes protected-capable queues 720 | // and one describes queues that are not protected-capable." 721 | // 722 | // As such, if graphics_family_queue_index == surface_presentation_queue_index, 723 | // we should only pass in a single qci when calling vk.CreateDevice. 724 | 725 | queue_create_infos := make([dynamic] vk.DeviceQueueCreateInfo) 726 | defer delete(queue_create_infos) 727 | 728 | append(&queue_create_infos, qci_graphics) 729 | 730 | if (graphics_family_queue_index != surface_presentation_queue_index) { 731 | append(&queue_create_infos, qci_present) 732 | } 733 | 734 | // To be filled in later when we begin to do interesting things. 735 | device_features : vk.PhysicalDeviceFeatures 736 | 737 | // Specify the device features for the graphics and presenation queue. 738 | dci : vk.DeviceCreateInfo 739 | dci.sType = vk.StructureType.DEVICE_CREATE_INFO 740 | dci.queueCreateInfoCount = u32(len(queue_create_infos)) 741 | dci.pQueueCreateInfos = raw_data(queue_create_infos) 742 | dci.pEnabledFeatures = &device_features 743 | dci.enabledExtensionCount = u32(len(pdevice_required_extensions)) 744 | dci.ppEnabledExtensionNames = raw_data(pdevice_required_extensions) 745 | 746 | // In up-to-date implementations, 747 | // dci.enabledLayerCount and dci.ppEnabledLayerNames can be ignored. 748 | // (these would otherwise be the same as the required_extensions in 749 | // the instance setup). 750 | 751 | // 10d. Create a logical device using the struct in c. 752 | cd_ok := vk.CreateDevice(pdevice, &dci, nil, &device) 753 | vkok(cd_ok) 754 | 755 | // 11. Obtain a handle to the queues from the logical device. 756 | // Queues are created with the logical device, so obtain a handle to them. 757 | // Note: the procedure below doesn't return a value. 758 | vk.GetDeviceQueue(device, graphics_family_queue_index, 0, &graphics_queue) 759 | vk.GetDeviceQueue(device, graphics_family_queue_index, 0, &present_queue) 760 | 761 | when ODIN_DEBUG && DEBUG_VERBOSE { 762 | f.println(DBVB, "Graphics queue handle:", graphics_queue, "\n") 763 | f.println(DBVB, "Present queue handle:", present_queue, "\n") 764 | } 765 | 766 | // 12. Create the swapchain using the settings chosen in 8. 767 | 768 | swpcnci : vk.SwapchainCreateInfoKHR 769 | swpcnci.sType = .SWAPCHAIN_CREATE_INFO_KHR 770 | swpcnci.surface = surface 771 | swpcnci.minImageCount = surface_image_count 772 | swpcnci.imageFormat = format 773 | swpcnci.imageColorSpace = surface_format.colorSpace 774 | swpcnci.imageExtent = extent 775 | swpcnci.imageArrayLayers = 1 // 1 for non-stereoscopic-3D apps, per man page. 776 | swpcnci.imageUsage = { vk.ImageUsageFlag.COLOR_ATTACHMENT } 777 | 778 | // The next three fields of swpcnci depend on whether or not 779 | // the graphics_family_queue and the surface_presentation_queue 780 | // are the same, similar to 10c. 781 | if graphics_family_queue_index != surface_presentation_queue_index { 782 | swpcnci.imageSharingMode = vk.SharingMode.CONCURRENT 783 | swpcnci.queueFamilyIndexCount = 2 784 | swpcnci.pQueueFamilyIndices = raw_data([]u32{ 785 | graphics_family_queue_index, 786 | surface_presentation_queue_index, 787 | }) 788 | } else { 789 | swpcnci.imageSharingMode = .EXCLUSIVE 790 | } 791 | swpcnci.preTransform = surface_capabilities.currentTransform 792 | swpcnci.compositeAlpha = { vk.CompositeAlphaFlagKHR.OPAQUE } 793 | swpcnci.presentMode = present_mode 794 | swpcnci.clipped = true 795 | 796 | // Create the swapchain! 797 | swapchaincreation_ok := vk.CreateSwapchainKHR(device, &swpcnci, nil, &swapchain) 798 | vkok(swapchaincreation_ok) 799 | when ODIN_DEBUG { 800 | f.println(DB, "Swapchain successfully created!") 801 | } 802 | 803 | // 13. Obtain vk.ImageView s for the swapchain. 804 | 805 | // 13a. Obtain handles to the vk.Images in the swapchain. 806 | // The swapchain may have increased the surface_image_count, so we need to 807 | // get its (possibly) new value. 808 | device_image_count := surface_image_count 809 | dic_ok := vk.GetSwapchainImagesKHR(device, swapchain, &device_image_count, nil) 810 | vkok(dic_ok) 811 | 812 | swapchain_images = make([dynamic] vk.Image, device_image_count) 813 | gsci_ok := vk.GetSwapchainImagesKHR(device, swapchain, &device_image_count, raw_data(swapchain_images)) 814 | vkok(gsci_ok) 815 | 816 | when ODIN_DEBUG && DEBUG_VERBOSE { 817 | f.println(DBVB, "Number of swapchain images:", device_image_count) 818 | f.println("") 819 | } 820 | 821 | // 13b. Create a vk.ImageView for each vk.Image 822 | imageviews = make([dynamic] vk.ImageView, device_image_count) 823 | for i in 0..