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.

image

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