vulkan/triangle_glfw
Overview
Vulkan triangle example
By laytan, source: https://gist.github.com/laytan/ba57af3e5a59ab5cb2fca9e25bcfe262
Compile and run using:
odin run .
This example comes with pre-compiled shaders. During compilation the shaders will be loaded from vert.spv and frag.spv.
If you make any changes to the shader source files (shader.vert or shader.frag), then you must recompile them using glslc:
glslc shader.vert -o vert.spv
glslc shader.frag -o frag.spv
glslc is part of the Vulkan SDK, which you can find here: https://vulkan.lunarg.com/sdk/home
This example uses glfw for window management.
Code
main.odin ¶920 linesSource
1/*
2Vulkan triangle example by laytan, source:
3https://gist.github.com/laytan/ba57af3e5a59ab5cb2fca9e25bcfe262
4
5Compile and run using:
6
7 odin run .
8
9This example comes with pre-compiled shaders. During compilation the shaders
10will be loaded from `vert.spv` and `frag.spv`.
11
12If you make any changes to the shader source files (`shader.vert` or
13`shader.frag`), then you must recompile them using `glslc`:
14
15 glslc shader.vert -o vert.spv
16 glslc shader.frag -o frag.spv
17
18`glslc` is part of the Vulkan SDK, which you can find here:
19https://vulkan.lunarg.com/sdk/home
20
21This example uses glfw for window management.
22*/
23package main
24
25import "base:runtime"
26
27import "core:log"
28import "core:slice"
29import "core:strings"
30
31
32import "vendor:glfw"
33import vk "vendor:vulkan"
34
35when ODIN_OS == .Darwin {
36 // NOTE: just a bogus import of the system library,
37 // needed so we can add a linker flag to point to /usr/local/lib (where vulkan is installed by default)
38 // when trying to load vulkan.
39 @(require, extra_linker_flags = "-rpath /usr/local/lib")
40 foreign import __ "system:System.framework"
41}
42
43SHADER_VERT :: #load("vert.spv")
44SHADER_FRAG :: #load("frag.spv")
45
46// Enables Vulkan debug logging and validation layers.
47ENABLE_VALIDATION_LAYERS :: #config(ENABLE_VALIDATION_LAYERS, ODIN_DEBUG)
48
49MAX_FRAMES_IN_FLIGHT :: 2
50
51g_ctx: runtime.Context
52
53g_window: glfw.WindowHandle
54
55g_framebuffer_resized: bool
56
57g_instance: vk.Instance
58g_physical_device: vk.PhysicalDevice
59g_device: vk.Device
60g_surface: vk.SurfaceKHR
61g_graphics_queue: vk.Queue
62g_present_queue: vk.Queue
63
64g_swapchain: vk.SwapchainKHR
65g_swapchain_images: []vk.Image
66g_swapchain_views: []vk.ImageView
67g_swapchain_format: vk.SurfaceFormatKHR
68g_swapchain_extent: vk.Extent2D
69g_swapchain_frame_buffers: []vk.Framebuffer
70
71g_vert_shader_module: vk.ShaderModule
72g_frag_shader_module: vk.ShaderModule
73g_shader_stages: [2]vk.PipelineShaderStageCreateInfo
74
75g_render_pass: vk.RenderPass
76g_pipeline_layout: vk.PipelineLayout
77g_pipeline: vk.Pipeline
78
79g_command_pool: vk.CommandPool
80g_command_buffers: [MAX_FRAMES_IN_FLIGHT]vk.CommandBuffer
81
82g_image_available_semaphores: [MAX_FRAMES_IN_FLIGHT]vk.Semaphore
83g_render_finished_semaphores: [MAX_FRAMES_IN_FLIGHT]vk.Semaphore
84g_in_flight_fences: [MAX_FRAMES_IN_FLIGHT]vk.Fence
85
86// KHR_PORTABILITY_SUBSET_EXTENSION_NAME :: "VK_KHR_portability_subset"
87
88DEVICE_EXTENSIONS := []cstring {
89 vk.KHR_SWAPCHAIN_EXTENSION_NAME,
90 // KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
91}
92
93main :: proc() {
94 context.logger = log.create_console_logger()
95 g_ctx = context
96
97 // TODO: update vendor bindings to glfw 3.4 and use this to set a custom allocator.
98 // glfw.InitAllocator()
99
100 // TODO: set up Vulkan allocator.
101
102 glfw.SetErrorCallback(glfw_error_callback)
103
104 if !glfw.Init() {log.panic("glfw: could not be initialized")}
105 defer glfw.Terminate()
106
107 glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API)
108 glfw.WindowHint(glfw.RESIZABLE, glfw.TRUE)
109
110 g_window = glfw.CreateWindow(800, 600, "Vulkan", nil, nil)
111 defer glfw.DestroyWindow(g_window)
112
113 glfw.SetFramebufferSizeCallback(g_window, proc "c" (_: glfw.WindowHandle, _, _: i32) {
114 g_framebuffer_resized = true
115 })
116
117 vk.load_proc_addresses_global(rawptr(glfw.GetInstanceProcAddress))
118 assert(vk.CreateInstance != nil, "vulkan function pointers not loaded")
119
120 create_info := vk.InstanceCreateInfo {
121 sType = .INSTANCE_CREATE_INFO,
122 pApplicationInfo = &vk.ApplicationInfo {
123 sType = .APPLICATION_INFO,
124 pApplicationName = "Hello Triangle",
125 applicationVersion = vk.MAKE_VERSION(1, 0, 0),
126 pEngineName = "No Engine",
127 engineVersion = vk.MAKE_VERSION(1, 0, 0),
128 apiVersion = vk.API_VERSION_1_0,
129 },
130 }
131
132 extensions := slice.clone_to_dynamic(glfw.GetRequiredInstanceExtensions(), context.temp_allocator)
133
134 // MacOS is a special snowflake ;)
135 when ODIN_OS == .Darwin {
136 create_info.flags |= {.ENUMERATE_PORTABILITY_KHR}
137 append(&extensions, vk.KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)
138 }
139
140 when ENABLE_VALIDATION_LAYERS {
141 create_info.ppEnabledLayerNames = raw_data([]cstring{"VK_LAYER_KHRONOS_validation"})
142 create_info.enabledLayerCount = 1
143
144 append(&extensions, vk.EXT_DEBUG_UTILS_EXTENSION_NAME)
145
146 // Severity based on logger level.
147 severity: vk.DebugUtilsMessageSeverityFlagsEXT
148 if context.logger.lowest_level <= .Error {
149 severity |= {.ERROR}
150 }
151 if context.logger.lowest_level <= .Warning {
152 severity |= {.WARNING}
153 }
154 if context.logger.lowest_level <= .Info {
155 severity |= {.INFO}
156 }
157 if context.logger.lowest_level <= .Debug {
158 severity |= {.VERBOSE}
159 }
160
161 dbg_create_info := vk.DebugUtilsMessengerCreateInfoEXT {
162 sType = .DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
163 messageSeverity = severity,
164 messageType = {.GENERAL, .VALIDATION, .PERFORMANCE, .DEVICE_ADDRESS_BINDING}, // all of them.
165 pfnUserCallback = vk_messenger_callback,
166 }
167 create_info.pNext = &dbg_create_info
168 }
169
170 create_info.enabledExtensionCount = u32(len(extensions))
171 create_info.ppEnabledExtensionNames = raw_data(extensions)
172
173 must(vk.CreateInstance(&create_info, nil, &g_instance))
174 defer vk.DestroyInstance(g_instance, nil)
175
176 vk.load_proc_addresses_instance(g_instance)
177
178 when ENABLE_VALIDATION_LAYERS {
179 dbg_messenger: vk.DebugUtilsMessengerEXT
180 must(vk.CreateDebugUtilsMessengerEXT(g_instance, &dbg_create_info, nil, &dbg_messenger))
181 defer vk.DestroyDebugUtilsMessengerEXT(g_instance, dbg_messenger, nil)
182 }
183
184 must(glfw.CreateWindowSurface(g_instance, g_window, nil, &g_surface))
185 defer vk.DestroySurfaceKHR(g_instance, g_surface, nil)
186
187 // Pick a suitable GPU.
188 must(pick_physical_device())
189
190 // Setup logical device,
191 indices := find_queue_families(g_physical_device)
192 {
193 // TODO: this is kinda messy.
194 indices_set := make(map[u32]struct {}, allocator = context.temp_allocator)
195 indices_set[indices.graphics.?] = {}
196 indices_set[indices.present.?] = {}
197
198 queue_create_infos := make([dynamic]vk.DeviceQueueCreateInfo, 0, len(indices_set), context.temp_allocator)
199 for _ in indices_set {
200 append(
201 &queue_create_infos,
202 vk.DeviceQueueCreateInfo {
203 sType = .DEVICE_QUEUE_CREATE_INFO,
204 queueFamilyIndex = indices.graphics.?,
205 queueCount = 1,
206 pQueuePriorities = raw_data([]f32{1}),
207 },// Scheduling priority between 0 and 1.
208 )
209 }
210
211 device_create_info := vk.DeviceCreateInfo {
212 sType = .DEVICE_CREATE_INFO,
213 pQueueCreateInfos = raw_data(queue_create_infos),
214 queueCreateInfoCount = u32(len(queue_create_infos)),
215 enabledLayerCount = create_info.enabledLayerCount,
216 ppEnabledLayerNames = create_info.ppEnabledLayerNames,
217 ppEnabledExtensionNames = raw_data(DEVICE_EXTENSIONS),
218 enabledExtensionCount = u32(len(DEVICE_EXTENSIONS)),
219 }
220
221 must(vk.CreateDevice(g_physical_device, &device_create_info, nil, &g_device))
222
223 vk.GetDeviceQueue(g_device, indices.graphics.?, 0, &g_graphics_queue)
224 vk.GetDeviceQueue(g_device, indices.present.?, 0, &g_present_queue)
225 }
226 defer vk.DestroyDevice(g_device, nil)
227
228 create_swapchain()
229 defer destroy_swapchain()
230
231 // Load shaders.
232 {
233 g_vert_shader_module = create_shader_module(SHADER_VERT)
234 g_shader_stages[0] = vk.PipelineShaderStageCreateInfo {
235 sType = .PIPELINE_SHADER_STAGE_CREATE_INFO,
236 stage = {.VERTEX},
237 module = g_vert_shader_module,
238 pName = "main",
239 }
240
241 g_frag_shader_module = create_shader_module(SHADER_FRAG)
242 g_shader_stages[1] = vk.PipelineShaderStageCreateInfo {
243 sType = .PIPELINE_SHADER_STAGE_CREATE_INFO,
244 stage = {.FRAGMENT},
245 module = g_frag_shader_module,
246 pName = "main",
247 }
248 }
249 defer vk.DestroyShaderModule(g_device, g_vert_shader_module, nil)
250 defer vk.DestroyShaderModule(g_device, g_frag_shader_module, nil)
251
252 // Set up render pass.
253 {
254 color_attachment := vk.AttachmentDescription {
255 format = g_swapchain_format.format,
256 samples = {._1},
257 loadOp = .CLEAR,
258 storeOp = .STORE,
259 stencilLoadOp = .DONT_CARE,
260 stencilStoreOp = .DONT_CARE,
261 initialLayout = .UNDEFINED,
262 finalLayout = .PRESENT_SRC_KHR,
263 }
264
265 color_attachment_ref := vk.AttachmentReference {
266 attachment = 0,
267 layout = .COLOR_ATTACHMENT_OPTIMAL,
268 }
269
270 subpass := vk.SubpassDescription {
271 pipelineBindPoint = .GRAPHICS,
272 colorAttachmentCount = 1,
273 pColorAttachments = &color_attachment_ref,
274 }
275
276 dependency := vk.SubpassDependency {
277 srcSubpass = vk.SUBPASS_EXTERNAL,
278 dstSubpass = 0,
279 srcStageMask = {.COLOR_ATTACHMENT_OUTPUT},
280 srcAccessMask = {},
281 dstStageMask = {.COLOR_ATTACHMENT_OUTPUT},
282 dstAccessMask = {.COLOR_ATTACHMENT_WRITE},
283 }
284
285 render_pass := vk.RenderPassCreateInfo {
286 sType = .RENDER_PASS_CREATE_INFO,
287 attachmentCount = 1,
288 pAttachments = &color_attachment,
289 subpassCount = 1,
290 pSubpasses = &subpass,
291 dependencyCount = 1,
292 pDependencies = &dependency,
293 }
294
295 must(vk.CreateRenderPass(g_device, &render_pass, nil, &g_render_pass))
296 }
297 defer vk.DestroyRenderPass(g_device, g_render_pass, nil)
298
299 create_framebuffers()
300 defer destroy_framebuffers()
301
302 // Set up pipeline.
303 {
304 dynamic_states := []vk.DynamicState{.VIEWPORT, .SCISSOR}
305 dynamic_state := vk.PipelineDynamicStateCreateInfo {
306 sType = .PIPELINE_DYNAMIC_STATE_CREATE_INFO,
307 dynamicStateCount = 2,
308 pDynamicStates = raw_data(dynamic_states),
309 }
310
311 vertex_input_info := vk.PipelineVertexInputStateCreateInfo {
312 sType = .PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
313 }
314
315 input_assembly := vk.PipelineInputAssemblyStateCreateInfo {
316 sType = .PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
317 topology = .TRIANGLE_LIST,
318 }
319
320 viewport_state := vk.PipelineViewportStateCreateInfo {
321 sType = .PIPELINE_VIEWPORT_STATE_CREATE_INFO,
322 viewportCount = 1,
323 scissorCount = 1,
324 }
325
326 rasterizer := vk.PipelineRasterizationStateCreateInfo {
327 sType = .PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
328 polygonMode = .FILL,
329 lineWidth = 1,
330 cullMode = {.BACK},
331 frontFace = .CLOCKWISE,
332 }
333
334 multisampling := vk.PipelineMultisampleStateCreateInfo {
335 sType = .PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
336 rasterizationSamples = {._1},
337 minSampleShading = 1,
338 }
339
340 color_blend_attachment := vk.PipelineColorBlendAttachmentState {
341 colorWriteMask = {.R, .G, .B, .A},
342 }
343
344 color_blending := vk.PipelineColorBlendStateCreateInfo {
345 sType = .PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
346 attachmentCount = 1,
347 pAttachments = &color_blend_attachment,
348 }
349
350 pipeline_layout := vk.PipelineLayoutCreateInfo {
351 sType = .PIPELINE_LAYOUT_CREATE_INFO,
352 }
353 must(vk.CreatePipelineLayout(g_device, &pipeline_layout, nil, &g_pipeline_layout))
354
355 pipeline := vk.GraphicsPipelineCreateInfo {
356 sType = .GRAPHICS_PIPELINE_CREATE_INFO,
357 stageCount = 2,
358 pStages = &g_shader_stages[0],
359 pVertexInputState = &vertex_input_info,
360 pInputAssemblyState = &input_assembly,
361 pViewportState = &viewport_state,
362 pRasterizationState = &rasterizer,
363 pMultisampleState = &multisampling,
364 pColorBlendState = &color_blending,
365 pDynamicState = &dynamic_state,
366 layout = g_pipeline_layout,
367 renderPass = g_render_pass,
368 subpass = 0,
369 basePipelineIndex = -1,
370 }
371 must(vk.CreateGraphicsPipelines(g_device, 0, 1, &pipeline, nil, &g_pipeline))
372 }
373 defer vk.DestroyPipelineLayout(g_device, g_pipeline_layout, nil)
374 defer vk.DestroyPipeline(g_device, g_pipeline, nil)
375
376 // Create command pool.
377 {
378 pool_info := vk.CommandPoolCreateInfo {
379 sType = .COMMAND_POOL_CREATE_INFO,
380 flags = {.RESET_COMMAND_BUFFER},
381 queueFamilyIndex = indices.graphics.?,
382 }
383 must(vk.CreateCommandPool(g_device, &pool_info, nil, &g_command_pool))
384
385 alloc_info := vk.CommandBufferAllocateInfo {
386 sType = .COMMAND_BUFFER_ALLOCATE_INFO,
387 commandPool = g_command_pool,
388 level = .PRIMARY,
389 commandBufferCount = MAX_FRAMES_IN_FLIGHT,
390 }
391 must(vk.AllocateCommandBuffers(g_device, &alloc_info, &g_command_buffers[0]))
392 }
393 defer vk.DestroyCommandPool(g_device, g_command_pool, nil)
394
395 // Set up sync primitives.
396 {
397 sem_info := vk.SemaphoreCreateInfo {
398 sType = .SEMAPHORE_CREATE_INFO,
399 }
400 fence_info := vk.FenceCreateInfo {
401 sType = .FENCE_CREATE_INFO,
402 flags = {.SIGNALED},
403 }
404 for i in 0 ..< MAX_FRAMES_IN_FLIGHT {
405 must(vk.CreateSemaphore(g_device, &sem_info, nil, &g_image_available_semaphores[i]))
406 must(vk.CreateSemaphore(g_device, &sem_info, nil, &g_render_finished_semaphores[i]))
407 must(vk.CreateFence(g_device, &fence_info, nil, &g_in_flight_fences[i]))
408 }
409 }
410 defer for sem in g_image_available_semaphores {vk.DestroySemaphore(g_device, sem, nil)}
411 defer for sem in g_render_finished_semaphores {vk.DestroySemaphore(g_device, sem, nil)}
412 defer for fence in g_in_flight_fences {vk.DestroyFence(g_device, fence, nil)}
413
414 current_frame := 0
415 for !glfw.WindowShouldClose(g_window) {
416 free_all(context.temp_allocator)
417
418 glfw.PollEvents()
419
420 // Wait for previous frame.
421 must(vk.WaitForFences(g_device, 1, &g_in_flight_fences[current_frame], true, max(u64)))
422
423 // Acquire an image from the swapchain.
424 image_index: u32
425 acquire_result := vk.AcquireNextImageKHR(
426 g_device,
427 g_swapchain,
428 max(u64),
429 g_image_available_semaphores[current_frame],
430 0,
431 &image_index,
432 )
433 #partial switch acquire_result {
434 case .ERROR_OUT_OF_DATE_KHR:
435 recreate_swapchain()
436 continue
437 case .SUCCESS, .SUBOPTIMAL_KHR:
438 case:
439 log.panicf("vulkan: acquire next image failure: %v", acquire_result)
440 }
441
442 must(vk.ResetFences(g_device, 1, &g_in_flight_fences[current_frame]))
443
444 must(vk.ResetCommandBuffer(g_command_buffers[current_frame], {}))
445 record_command_buffer(g_command_buffers[current_frame], image_index)
446
447 // Submit.
448 submit_info := vk.SubmitInfo {
449 sType = .SUBMIT_INFO,
450 waitSemaphoreCount = 1,
451 pWaitSemaphores = &g_image_available_semaphores[current_frame],
452 pWaitDstStageMask = &vk.PipelineStageFlags{.COLOR_ATTACHMENT_OUTPUT},
453 commandBufferCount = 1,
454 pCommandBuffers = &g_command_buffers[current_frame],
455 signalSemaphoreCount = 1,
456 pSignalSemaphores = &g_render_finished_semaphores[current_frame],
457 }
458 must(vk.QueueSubmit(g_graphics_queue, 1, &submit_info, g_in_flight_fences[current_frame]))
459
460 // Present.
461 present_info := vk.PresentInfoKHR {
462 sType = .PRESENT_INFO_KHR,
463 waitSemaphoreCount = 1,
464 pWaitSemaphores = &g_render_finished_semaphores[current_frame],
465 swapchainCount = 1,
466 pSwapchains = &g_swapchain,
467 pImageIndices = &image_index,
468 }
469 present_result := vk.QueuePresentKHR(g_present_queue, &present_info)
470 switch {
471 case present_result == .ERROR_OUT_OF_DATE_KHR || present_result == .SUBOPTIMAL_KHR || g_framebuffer_resized:
472 g_framebuffer_resized = false
473 recreate_swapchain()
474 case present_result == .SUCCESS:
475 case:
476 log.panicf("vulkan: present failure: %v", present_result)
477 }
478
479 current_frame = (current_frame + 1) % MAX_FRAMES_IN_FLIGHT
480 }
481 vk.DeviceWaitIdle(g_device)
482}
483
484@(require_results)
485pick_physical_device :: proc() -> vk.Result {
486
487 score_physical_device :: proc(device: vk.PhysicalDevice) -> (score: int) {
488 props: vk.PhysicalDeviceProperties
489 vk.GetPhysicalDeviceProperties(device, &props)
490
491 name := byte_arr_str(&props.deviceName)
492 log.infof("vulkan: evaluating device %q", name)
493 defer log.infof("vulkan: device %q scored %v", name, score)
494
495 features: vk.PhysicalDeviceFeatures
496 vk.GetPhysicalDeviceFeatures(device, &features)
497
498 // // App can't function without geometry shaders.
499 // if !features.geometryShader {
500 // log.info("vulkan: device does not support geometry shaders")
501 // return 0
502 // }
503
504 // Need certain extensions supported.
505 {
506 extensions, result := physical_device_extensions(device, context.temp_allocator)
507 if result != .SUCCESS {
508 log.infof("vulkan: enumerate device extension properties failed: %v", result)
509 return 0
510 }
511
512 required_loop: for required in DEVICE_EXTENSIONS {
513 for &extension in extensions {
514 extension_name := byte_arr_str(&extension.extensionName)
515 if extension_name == string(required) {
516 continue required_loop
517 }
518 }
519
520 log.infof("vulkan: device does not support required extension %q", required)
521 return 0
522 }
523 }
524
525 // Check if swapchain is adequately supported.
526 {
527 support, result := query_swapchain_support(device, context.temp_allocator)
528 if result != .SUCCESS {
529 log.infof("vulkan: query swapchain support failure: %v", result)
530 return 0
531 }
532
533 // Need at least a format and present mode.
534 if len(support.formats) == 0 || len(support.presentModes) == 0 {
535 log.info("vulkan: device does not support swapchain")
536 return 0
537 }
538 }
539
540 families := find_queue_families(device)
541 if _, has_graphics := families.graphics.?; !has_graphics {
542 log.info("vulkan: device does not have a graphics queue")
543 return 0
544 }
545 if _, has_present := families.present.?; !has_present {
546 log.info("vulkan: device does not have a presentation queue")
547 return 0
548 }
549
550 // Favor GPUs.
551 switch props.deviceType {
552 case .DISCRETE_GPU:
553 score += 300_000
554 case .INTEGRATED_GPU:
555 score += 200_000
556 case .VIRTUAL_GPU:
557 score += 100_000
558 case .CPU, .OTHER:
559 }
560 log.infof("vulkan: scored %i based on device type %v", score, props.deviceType)
561
562 // Maximum texture size.
563 score += int(props.limits.maxImageDimension2D)
564 log.infof(
565 "vulkan: added the max 2D image dimensions (texture size) of %v to the score",
566 props.limits.maxImageDimension2D,
567 )
568 return
569 }
570
571 count: u32
572 vk.EnumeratePhysicalDevices(g_instance, &count, nil) or_return
573 if count == 0 {log.panic("vulkan: no GPU found")}
574
575 devices := make([]vk.PhysicalDevice, count, context.temp_allocator)
576 vk.EnumeratePhysicalDevices(g_instance, &count, raw_data(devices)) or_return
577
578 best_device_score := -1
579 for device in devices {
580 if score := score_physical_device(device); score > best_device_score {
581 g_physical_device = device
582 best_device_score = score
583 }
584 }
585
586 if best_device_score <= 0 {
587 log.panic("vulkan: no suitable GPU found")
588 }
589 return .SUCCESS
590}
591
592Queue_Family_Indices :: struct {
593 graphics: Maybe(u32),
594 present: Maybe(u32),
595}
596
597find_queue_families :: proc(device: vk.PhysicalDevice) -> (ids: Queue_Family_Indices) {
598 count: u32
599 vk.GetPhysicalDeviceQueueFamilyProperties(device, &count, nil)
600
601 families := make([]vk.QueueFamilyProperties, count, context.temp_allocator)
602 vk.GetPhysicalDeviceQueueFamilyProperties(device, &count, raw_data(families))
603
604 for family, i in families {
605 if .GRAPHICS in family.queueFlags {
606 ids.graphics = u32(i)
607 }
608
609 supported: b32
610 vk.GetPhysicalDeviceSurfaceSupportKHR(device, u32(i), g_surface, &supported)
611 if supported {
612 ids.present = u32(i)
613 }
614
615 // Found all needed queues?
616 _, has_graphics := ids.graphics.?
617 _, has_present := ids.present.?
618 if has_graphics && has_present {
619 break
620 }
621 }
622
623 return
624}
625
626Swapchain_Support :: struct {
627 capabilities: vk.SurfaceCapabilitiesKHR,
628 formats: []vk.SurfaceFormatKHR,
629 presentModes: []vk.PresentModeKHR,
630}
631
632query_swapchain_support :: proc(
633 device: vk.PhysicalDevice,
634 allocator := context.temp_allocator,
635) -> (
636 support: Swapchain_Support,
637 result: vk.Result,
638) {
639 // NOTE: looks like a wrong binding with the third arg being a multipointer.
640 vk.GetPhysicalDeviceSurfaceCapabilitiesKHR(device, g_surface, &support.capabilities) or_return
641
642 {
643 count: u32
644 vk.GetPhysicalDeviceSurfaceFormatsKHR(device, g_surface, &count, nil) or_return
645
646 support.formats = make([]vk.SurfaceFormatKHR, count, allocator)
647 vk.GetPhysicalDeviceSurfaceFormatsKHR(device, g_surface, &count, raw_data(support.formats)) or_return
648 }
649
650 {
651 count: u32
652 vk.GetPhysicalDeviceSurfacePresentModesKHR(device, g_surface, &count, nil) or_return
653
654 support.presentModes = make([]vk.PresentModeKHR, count, allocator)
655 vk.GetPhysicalDeviceSurfacePresentModesKHR(device, g_surface, &count, raw_data(support.presentModes)) or_return
656 }
657
658 return
659}
660
661choose_swapchain_surface_format :: proc(formats: []vk.SurfaceFormatKHR) -> vk.SurfaceFormatKHR {
662 for format in formats {
663 if format.format == .B8G8R8A8_SRGB && format.colorSpace == .SRGB_NONLINEAR {
664 return format
665 }
666 }
667
668 // Fallback non optimal.
669 return formats[0]
670}
671
672choose_swapchain_present_mode :: proc(modes: []vk.PresentModeKHR) -> vk.PresentModeKHR {
673 // We would like mailbox for the best tradeoff between tearing and latency.
674 for mode in modes {
675 if mode == .MAILBOX {
676 return .MAILBOX
677 }
678 }
679
680 // As a fallback, fifo (basically vsync) is always available.
681 return .FIFO
682}
683
684choose_swapchain_extent :: proc(capabilities: vk.SurfaceCapabilitiesKHR) -> vk.Extent2D {
685 if capabilities.currentExtent.width != max(u32) {
686 return capabilities.currentExtent
687 }
688
689 width, height := glfw.GetFramebufferSize(g_window)
690 return(
691 vk.Extent2D {
692 width = clamp(u32(width), capabilities.minImageExtent.width, capabilities.maxImageExtent.width),
693 height = clamp(u32(height), capabilities.minImageExtent.height, capabilities.maxImageExtent.height),
694 } \
695 )
696}
697
698glfw_error_callback :: proc "c" (code: i32, description: cstring) {
699 context = g_ctx
700 log.errorf("glfw: %i: %s", code, description)
701}
702
703vk_messenger_callback :: proc "system" (
704 messageSeverity: vk.DebugUtilsMessageSeverityFlagsEXT,
705 messageTypes: vk.DebugUtilsMessageTypeFlagsEXT,
706 pCallbackData: ^vk.DebugUtilsMessengerCallbackDataEXT,
707 pUserData: rawptr,
708) -> b32 {
709 context = g_ctx
710
711 level: log.Level
712 if .ERROR in messageSeverity {
713 level = .Error
714 } else if .WARNING in messageSeverity {
715 level = .Warning
716 } else if .INFO in messageSeverity {
717 level = .Info
718 } else {
719 level = .Debug
720 }
721
722 log.logf(level, "vulkan[%v]: %s", messageTypes, pCallbackData.pMessage)
723 return false
724}
725
726physical_device_extensions :: proc(
727 device: vk.PhysicalDevice,
728 allocator := context.temp_allocator,
729) -> (
730 exts: []vk.ExtensionProperties,
731 res: vk.Result,
732) {
733 count: u32
734 vk.EnumerateDeviceExtensionProperties(device, nil, &count, nil) or_return
735
736 exts = make([]vk.ExtensionProperties, count, allocator)
737 vk.EnumerateDeviceExtensionProperties(device, nil, &count, raw_data(exts)) or_return
738
739 return
740}
741
742create_swapchain :: proc() {
743 indices := find_queue_families(g_physical_device)
744
745 // Setup swapchain.
746 {
747 support, result := query_swapchain_support(g_physical_device, context.temp_allocator)
748 if result != .SUCCESS {
749 log.panicf("vulkan: query swapchain failed: %v", result)
750 }
751
752 surface_format := choose_swapchain_surface_format(support.formats)
753 present_mode := choose_swapchain_present_mode(support.presentModes)
754 extent := choose_swapchain_extent(support.capabilities)
755
756 g_swapchain_format = surface_format
757 g_swapchain_extent = extent
758
759 image_count := support.capabilities.minImageCount + 1
760 if support.capabilities.maxImageCount > 0 && image_count > support.capabilities.maxImageCount {
761 image_count = support.capabilities.maxImageCount
762 }
763
764 create_info := vk.SwapchainCreateInfoKHR {
765 sType = .SWAPCHAIN_CREATE_INFO_KHR,
766 surface = g_surface,
767 minImageCount = image_count,
768 imageFormat = surface_format.format,
769 imageColorSpace = surface_format.colorSpace,
770 imageExtent = extent,
771 imageArrayLayers = 1,
772 imageUsage = {.COLOR_ATTACHMENT},
773 preTransform = support.capabilities.currentTransform,
774 compositeAlpha = {.OPAQUE},
775 presentMode = present_mode,
776 clipped = true,
777 }
778
779 if indices.graphics != indices.present {
780 create_info.imageSharingMode = .CONCURRENT
781 create_info.queueFamilyIndexCount = 2
782 create_info.pQueueFamilyIndices = raw_data([]u32{indices.graphics.?, indices.present.?})
783 }
784
785 must(vk.CreateSwapchainKHR(g_device, &create_info, nil, &g_swapchain))
786 }
787
788 // Setup swapchain images.
789 {
790 count: u32
791 must(vk.GetSwapchainImagesKHR(g_device, g_swapchain, &count, nil))
792
793 g_swapchain_images = make([]vk.Image, count)
794 g_swapchain_views = make([]vk.ImageView, count)
795 must(vk.GetSwapchainImagesKHR(g_device, g_swapchain, &count, raw_data(g_swapchain_images)))
796
797 for image, i in g_swapchain_images {
798 create_info := vk.ImageViewCreateInfo {
799 sType = .IMAGE_VIEW_CREATE_INFO,
800 image = image,
801 viewType = .D2,
802 format = g_swapchain_format.format,
803 subresourceRange = {aspectMask = {.COLOR}, levelCount = 1, layerCount = 1},
804 }
805 must(vk.CreateImageView(g_device, &create_info, nil, &g_swapchain_views[i]))
806 }
807 }
808}
809
810destroy_swapchain :: proc() {
811 for view in g_swapchain_views {
812 vk.DestroyImageView(g_device, view, nil)
813 }
814 delete(g_swapchain_views)
815 delete(g_swapchain_images)
816 vk.DestroySwapchainKHR(g_device, g_swapchain, nil)
817}
818
819create_framebuffers :: proc() {
820 g_swapchain_frame_buffers = make([]vk.Framebuffer, len(g_swapchain_views))
821 for view, i in g_swapchain_views {
822 attachments := []vk.ImageView{view}
823
824 frame_buffer := vk.FramebufferCreateInfo {
825 sType = .FRAMEBUFFER_CREATE_INFO,
826 renderPass = g_render_pass,
827 attachmentCount = 1,
828 pAttachments = raw_data(attachments),
829 width = g_swapchain_extent.width,
830 height = g_swapchain_extent.height,
831 layers = 1,
832 }
833 must(vk.CreateFramebuffer(g_device, &frame_buffer, nil, &g_swapchain_frame_buffers[i]))
834 }
835}
836
837destroy_framebuffers :: proc() {
838 for frame_buffer in g_swapchain_frame_buffers {vk.DestroyFramebuffer(g_device, frame_buffer, nil)}
839 delete(g_swapchain_frame_buffers)
840}
841
842recreate_swapchain :: proc() {
843 // Don't do anything when minimized.
844 for w, h := glfw.GetFramebufferSize(g_window); w == 0 || h == 0; w, h = glfw.GetFramebufferSize(g_window) {
845 glfw.WaitEvents()
846
847 // Handle closing while minimized.
848 if glfw.WindowShouldClose(g_window) { break }
849 }
850
851 vk.DeviceWaitIdle(g_device)
852
853 destroy_framebuffers()
854 destroy_swapchain()
855
856 create_swapchain()
857 create_framebuffers()
858}
859
860create_shader_module :: proc(code: []byte) -> (module: vk.ShaderModule) {
861 as_u32 := slice.reinterpret([]u32, code)
862
863 create_info := vk.ShaderModuleCreateInfo {
864 sType = .SHADER_MODULE_CREATE_INFO,
865 codeSize = len(code),
866 pCode = raw_data(as_u32),
867 }
868 must(vk.CreateShaderModule(g_device, &create_info, nil, &module))
869 return
870}
871
872record_command_buffer :: proc(command_buffer: vk.CommandBuffer, image_index: u32) {
873 begin_info := vk.CommandBufferBeginInfo {
874 sType = .COMMAND_BUFFER_BEGIN_INFO,
875 }
876 must(vk.BeginCommandBuffer(command_buffer, &begin_info))
877
878 clear_color := vk.ClearValue{}
879 clear_color.color.float32 = {0.0, 0.0, 0.0, 1.0}
880
881 render_pass_info := vk.RenderPassBeginInfo {
882 sType = .RENDER_PASS_BEGIN_INFO,
883 renderPass = g_render_pass,
884 framebuffer = g_swapchain_frame_buffers[image_index],
885 renderArea = {extent = g_swapchain_extent},
886 clearValueCount = 1,
887 pClearValues = &clear_color,
888 }
889 vk.CmdBeginRenderPass(command_buffer, &render_pass_info, .INLINE)
890
891 vk.CmdBindPipeline(command_buffer, .GRAPHICS, g_pipeline)
892
893 viewport := vk.Viewport {
894 width = f32(g_swapchain_extent.width),
895 height = f32(g_swapchain_extent.height),
896 maxDepth = 1.0,
897 }
898 vk.CmdSetViewport(command_buffer, 0, 1, &viewport)
899
900 scissor := vk.Rect2D {
901 extent = g_swapchain_extent,
902 }
903 vk.CmdSetScissor(command_buffer, 0, 1, &scissor)
904
905 vk.CmdDraw(command_buffer, 3, 1, 0, 0)
906
907 vk.CmdEndRenderPass(command_buffer)
908
909 must(vk.EndCommandBuffer(command_buffer))
910}
911
912byte_arr_str :: proc(arr: ^[$N]byte) -> string {
913 return strings.truncate_to_byte(string(arr[:]), 0)
914}
915
916must :: proc(result: vk.Result, loc := #caller_location) {
917 if result != .SUCCESS {
918 log.panicf("vulkan failure %v", result, location = loc)
919 }
920}
Shaders
shader.frag ¶9 linesSource
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}
shader.vert ¶20 linesSource
#version 450
layout(location = 0) out vec3 fragColor;
vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
fragColor = colors[gl_VertexIndex];
}
Declarations Used 161
- base:runtime Context
- core:log create_console_logger · errorf · info · infof · Level · logf · panic · panicf
- core:slice clone_to_dynamic · reinterpret
- core:strings truncate_to_byte
- vendor:glfw CLIENT_API · CreateWindow · CreateWindowSurface · DestroyWindow · GetFramebufferSize · GetInstanceProcAddress · GetRequiredInstanceExtensions · Init · NO_API · PollEvents · RESIZABLE · SetErrorCallback · SetFramebufferSizeCallback · Terminate · TRUE · WaitEvents · WindowHandle · WindowHint · WindowShouldClose
- vendor:vulkan AcquireNextImageKHR · AllocateCommandBuffers · API_VERSION_1_0 · ApplicationInfo · AttachmentDescription · AttachmentReference · BeginCommandBuffer · ClearValue · CmdBeginRenderPass · CmdBindPipeline · CmdDraw · CmdEndRenderPass · CmdSetScissor · CmdSetViewport · CommandBuffer · CommandBufferAllocateInfo · CommandBufferBeginInfo · CommandPool · CommandPoolCreateInfo · CreateCommandPool · CreateDebugUtilsMessengerEXT · CreateDevice · CreateFence · CreateFramebuffer · CreateGraphicsPipelines · CreateImageView · CreateInstance · CreatePipelineLayout · CreateRenderPass · CreateSemaphore · CreateShaderModule · CreateSwapchainKHR · DebugUtilsMessageSeverityFlagsEXT · DebugUtilsMessageTypeFlagsEXT · DebugUtilsMessengerCallbackDataEXT · DebugUtilsMessengerCreateInfoEXT · DebugUtilsMessengerEXT · DestroyCommandPool · DestroyDebugUtilsMessengerEXT · DestroyDevice · DestroyFence · DestroyFramebuffer · DestroyImageView · DestroyInstance · DestroyPipeline · DestroyPipelineLayout · DestroyRenderPass · DestroySemaphore · DestroyShaderModule · DestroySurfaceKHR · DestroySwapchainKHR · Device · DeviceCreateInfo · DeviceQueueCreateInfo · DeviceWaitIdle · DynamicState · EndCommandBuffer · EnumerateDeviceExtensionProperties · EnumeratePhysicalDevices · EXT_DEBUG_UTILS_EXTENSION_NAME · ExtensionProperties · Extent2D · Fence · FenceCreateInfo · Framebuffer · FramebufferCreateInfo · GetDeviceQueue · GetPhysicalDeviceFeatures · GetPhysicalDeviceProperties · GetPhysicalDeviceQueueFamilyProperties · GetPhysicalDeviceSurfaceCapabilitiesKHR · GetPhysicalDeviceSurfaceFormatsKHR · GetPhysicalDeviceSurfacePresentModesKHR · GetPhysicalDeviceSurfaceSupportKHR · GetSwapchainImagesKHR · GraphicsPipelineCreateInfo · Image · ImageView · ImageViewCreateInfo · Instance · InstanceCreateInfo · KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME · KHR_SWAPCHAIN_EXTENSION_NAME · load_proc_addresses_global · load_proc_addresses_instance · MAKE_VERSION · PhysicalDevice · PhysicalDeviceFeatures · PhysicalDeviceProperties · Pipeline · PipelineColorBlendAttachmentState · PipelineColorBlendStateCreateInfo · PipelineDynamicStateCreateInfo · PipelineInputAssemblyStateCreateInfo · PipelineLayout · PipelineLayoutCreateInfo · PipelineMultisampleStateCreateInfo · PipelineRasterizationStateCreateInfo · PipelineShaderStageCreateInfo · PipelineStageFlags · PipelineVertexInputStateCreateInfo · PipelineViewportStateCreateInfo · PresentInfoKHR · PresentModeKHR · Queue · QueueFamilyProperties · QueuePresentKHR · QueueSubmit · Rect2D · RenderPass · RenderPassBeginInfo · RenderPassCreateInfo · ResetCommandBuffer · ResetFences · Result · Semaphore · SemaphoreCreateInfo · ShaderModule · ShaderModuleCreateInfo · SubmitInfo · SUBPASS_EXTERNAL · SubpassDependency · SubpassDescription · SurfaceCapabilitiesKHR · SurfaceFormatKHR · SurfaceKHR · SwapchainCreateInfoKHR · SwapchainKHR · Viewport · WaitForFences