├── .gitignore ├── README.md ├── libs └── stb │ └── stb_impl.c ├── resources ├── texture.jpg └── viking_room.png ├── src ├── 22_shader_ubo.frag ├── 09_shader_base.frag ├── 18_shader_vertexbuffer.frag ├── 18_shader_vertexbuffer.vert ├── 27_shader_depth.frag ├── 26_shader_textures.frag ├── 22_shader_ubo.vert ├── 09_shader_base.vert ├── 27_shader_depth.vert ├── 26_shader_textures.vert ├── 00_base_code.zig ├── 01_instance_creation.zig ├── 02_validation_layers.zig ├── 03_physical_device_selection.zig ├── 04_logical_device.zig ├── 05_window_surface.zig ├── 06_swap_chain_creation.zig ├── 07_image_views.zig ├── 08_graphics_pipeline.zig ├── 09_shader_module.zig ├── 10_fixed_functions.zig └── 11_render_passes.zig ├── zigmod.lock ├── zigmod.yml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | **/zig-cache 2 | **/zig-out 3 | **/.zigmod 4 | deps.zig 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulfox/vulkan-tutorial-zig/HEAD/README.md -------------------------------------------------------------------------------- /libs/stb/stb_impl.c: -------------------------------------------------------------------------------- 1 | #define STB_IMAGE_IMPLEMENTATION 2 | #include "stb_image.h" 3 | -------------------------------------------------------------------------------- /resources/texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulfox/vulkan-tutorial-zig/HEAD/resources/texture.jpg -------------------------------------------------------------------------------- /resources/viking_room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vulfox/vulkan-tutorial-zig/HEAD/resources/viking_room.png -------------------------------------------------------------------------------- /src/22_shader_ubo.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 | -------------------------------------------------------------------------------- /src/09_shader_base.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 | -------------------------------------------------------------------------------- /src/18_shader_vertexbuffer.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 | -------------------------------------------------------------------------------- /src/18_shader_vertexbuffer.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec2 inPosition; 4 | layout(location = 1) in vec3 inColor; 5 | 6 | layout(location = 0) out vec3 fragColor; 7 | 8 | void main() { 9 | gl_Position = vec4(inPosition, 0.0, 1.0); 10 | fragColor = inColor; 11 | } 12 | -------------------------------------------------------------------------------- /src/27_shader_depth.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 1) uniform sampler2D texSampler; 4 | 5 | layout(location = 0) in vec3 fragColor; 6 | layout(location = 1) in vec2 fragTexCoord; 7 | 8 | layout(location = 0) out vec4 outColor; 9 | 10 | void main() { 11 | outColor = texture(texSampler, fragTexCoord); 12 | } 13 | -------------------------------------------------------------------------------- /src/26_shader_textures.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 1) uniform sampler2D texSampler; 4 | 5 | layout(location = 0) in vec3 fragColor; 6 | layout(location = 1) in vec2 fragTexCoord; 7 | 8 | layout(location = 0) out vec4 outColor; 9 | 10 | void main() { 11 | outColor = texture(texSampler, fragTexCoord); 12 | } 13 | -------------------------------------------------------------------------------- /src/22_shader_ubo.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 0) uniform UniformBufferObject { 4 | mat4 model; 5 | mat4 view; 6 | mat4 proj; 7 | } ubo; 8 | 9 | layout(location = 0) in vec2 inPosition; 10 | layout(location = 1) in vec3 inColor; 11 | 12 | layout(location = 0) out vec3 fragColor; 13 | 14 | void main() { 15 | gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); 16 | fragColor = inColor; 17 | } 18 | -------------------------------------------------------------------------------- /src/09_shader_base.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 | -------------------------------------------------------------------------------- /zigmod.lock: -------------------------------------------------------------------------------- 1 | 2 2 | git https://github.com/kooparse/zalgebra commit-e42edd142eb4ade38802663debf19bd6252224c1 3 | git https://github.com/Vulfox/wavefront-obj commit-867cffa4a74f0ee4d659a701ca2777c298591412 4 | git https://github.com/ziglibs/zlm commit-9b1b59220b7d971282b02dc00b6463ed8352ee7c 5 | git https://github.com/hexops/mach-glfw commit-b30ec077363bbd6eefec02dd3984c125a71ed8af 6 | git https://github.com/Snektron/vulkan-zig commit-5b5b7d047f627b4b9d3b56c609ac0bef8d5aaa1a 7 | git https://github.com/nothings/stb commit-af1a5bc352164740c1cc1354942b1c6b72eacb8a 8 | -------------------------------------------------------------------------------- /src/27_shader_depth.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 0) uniform UniformBufferObject { 4 | mat4 model; 5 | mat4 view; 6 | mat4 proj; 7 | } ubo; 8 | 9 | layout(location = 0) in vec3 inPosition; 10 | layout(location = 1) in vec3 inColor; 11 | layout(location = 2) in vec2 inTexCoord; 12 | 13 | layout(location = 0) out vec3 fragColor; 14 | layout(location = 1) out vec2 fragTexCoord; 15 | 16 | void main() { 17 | gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); 18 | fragColor = inColor; 19 | fragTexCoord = inTexCoord; 20 | } 21 | -------------------------------------------------------------------------------- /src/26_shader_textures.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(binding = 0) uniform UniformBufferObject { 4 | mat4 model; 5 | mat4 view; 6 | mat4 proj; 7 | } ubo; 8 | 9 | layout(location = 0) in vec2 inPosition; 10 | layout(location = 1) in vec3 inColor; 11 | layout(location = 2) in vec2 inTexCoord; 12 | 13 | layout(location = 0) out vec3 fragColor; 14 | layout(location = 1) out vec2 fragTexCoord; 15 | 16 | void main() { 17 | gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); 18 | fragColor = inColor; 19 | fragTexCoord = inTexCoord; 20 | } 21 | -------------------------------------------------------------------------------- /zigmod.yml: -------------------------------------------------------------------------------- 1 | name: vulkan-tutorial-zig 2 | root_dependencies: 3 | - src: git https://github.com/kooparse/zalgebra 4 | - src: git https://github.com/Vulfox/wavefront-obj 5 | version: branch-stage2 6 | 7 | build_dependencies: 8 | - src: git https://github.com/hexops/mach-glfw 9 | main: build.zig 10 | name: build_glfw 11 | - src: git https://github.com/Snektron/vulkan-zig 12 | main: generator/index.zig 13 | name: vk_gen 14 | - src: git https://github.com/Snektron/vulkan-zig 15 | main: build.zig 16 | name: vk_build 17 | - src: git https://github.com/nothings/stb 18 | name: stb 19 | main: '' 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dustin Taylor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/00_base_code.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const glfw = @import("glfw"); 4 | 5 | const WIDTH: u32 = 800; 6 | const HEIGHT: u32 = 600; 7 | 8 | const HelloTriangleApplication = struct { 9 | const Self = @This(); 10 | 11 | window: ?glfw.Window = null, 12 | 13 | pub fn init() Self { 14 | return Self{}; 15 | } 16 | 17 | pub fn run(self: *Self) !void { 18 | try self.initWindow(); 19 | try self.initVulkan(); 20 | try self.mainLoop(); 21 | } 22 | 23 | fn initWindow(self: *Self) !void { 24 | try glfw.init(.{}); 25 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 26 | .client_api = .no_api, 27 | .resizable = false, 28 | }); 29 | } 30 | 31 | fn initVulkan(_: *Self) !void {} 32 | 33 | fn mainLoop(self: *Self) !void { 34 | while (!self.window.?.shouldClose()) { 35 | try glfw.pollEvents(); 36 | } 37 | } 38 | 39 | pub fn deinit(self: *Self) void { 40 | if (self.window != null) self.window.?.destroy(); 41 | 42 | glfw.terminate(); 43 | } 44 | }; 45 | 46 | pub fn main() void { 47 | var app = HelloTriangleApplication.init(); 48 | defer app.deinit(); 49 | app.run() catch |err| { 50 | std.log.err("application exited with error: {any}", .{err}); 51 | return; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/01_instance_creation.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const glfw = @import("glfw"); 4 | const vk = @import("vulkan"); 5 | 6 | const WIDTH: u32 = 800; 7 | const HEIGHT: u32 = 600; 8 | 9 | const BaseDispatch = vk.BaseWrapper(.{ 10 | .createInstance = true, 11 | }); 12 | 13 | const InstanceDispatch = vk.InstanceWrapper(.{ 14 | .destroyInstance = true, 15 | }); 16 | 17 | const HelloTriangleApplication = struct { 18 | const Self = @This(); 19 | 20 | window: ?glfw.Window = null, 21 | 22 | vkb: BaseDispatch = undefined, 23 | vki: InstanceDispatch = undefined, 24 | 25 | instance: vk.Instance = .null_handle, 26 | 27 | pub fn init() Self { 28 | return Self{}; 29 | } 30 | 31 | pub fn run(self: *Self) !void { 32 | try self.initWindow(); 33 | try self.initVulkan(); 34 | try self.mainLoop(); 35 | } 36 | 37 | fn initWindow(self: *Self) !void { 38 | try glfw.init(.{}); 39 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 40 | .client_api = .no_api, 41 | .resizable = false, 42 | }); 43 | } 44 | 45 | fn initVulkan(self: *Self) !void { 46 | try self.createInstance(); 47 | } 48 | 49 | fn mainLoop(self: *Self) !void { 50 | while (!self.window.?.shouldClose()) { 51 | try glfw.pollEvents(); 52 | } 53 | } 54 | 55 | pub fn deinit(self: *Self) void { 56 | if (self.instance != .null_handle) self.vki.destroyInstance(self.instance, null); 57 | 58 | if (self.window != null) self.window.?.destroy(); 59 | 60 | glfw.terminate(); 61 | } 62 | 63 | fn createInstance(self: *Self) !void { 64 | const vk_proc = @ptrCast(*const fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, &glfw.getInstanceProcAddress); 65 | self.vkb = try BaseDispatch.load(vk_proc); 66 | 67 | const app_info = vk.ApplicationInfo{ 68 | .p_application_name = "Hello Triangle", 69 | .application_version = vk.makeApiVersion(1, 0, 0, 0), 70 | .p_engine_name = "No Engine", 71 | .engine_version = vk.makeApiVersion(1, 0, 0, 0), 72 | .api_version = vk.API_VERSION_1_2, 73 | }; 74 | 75 | const glfw_extensions = try glfw.getRequiredInstanceExtensions(); 76 | 77 | const create_info = vk.InstanceCreateInfo{ 78 | .flags = .{}, 79 | .p_application_info = &app_info, 80 | .enabled_layer_count = 0, 81 | .pp_enabled_layer_names = undefined, 82 | .enabled_extension_count = @intCast(u32, glfw_extensions.len), 83 | .pp_enabled_extension_names = glfw_extensions.ptr, 84 | }; 85 | 86 | self.instance = try self.vkb.createInstance(&create_info, null); 87 | 88 | self.vki = try InstanceDispatch.load(self.instance, vk_proc); 89 | } 90 | }; 91 | 92 | pub fn main() void { 93 | var app = HelloTriangleApplication.init(); 94 | defer app.deinit(); 95 | app.run() catch |err| { 96 | std.log.err("application exited with error: {any}", .{err}); 97 | return; 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /src/02_validation_layers.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | const glfw = @import("glfw"); 6 | const vk = @import("vulkan"); 7 | 8 | const WIDTH: u32 = 800; 9 | const HEIGHT: u32 = 600; 10 | 11 | const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; 12 | 13 | const enable_validation_layers: bool = switch (builtin.mode) { 14 | .Debug, .ReleaseSafe => true, 15 | else => false, 16 | }; 17 | 18 | const BaseDispatch = vk.BaseWrapper(.{ 19 | .createInstance = true, 20 | .enumerateInstanceLayerProperties = true, 21 | }); 22 | 23 | const InstanceDispatch = vk.InstanceWrapper(.{ 24 | .createDebugUtilsMessengerEXT = enable_validation_layers, 25 | .destroyDebugUtilsMessengerEXT = enable_validation_layers, 26 | .destroyInstance = true, 27 | }); 28 | 29 | const HelloTriangleApplication = struct { 30 | const Self = @This(); 31 | allocator: Allocator, 32 | 33 | window: ?glfw.Window = null, 34 | 35 | vkb: BaseDispatch = undefined, 36 | vki: InstanceDispatch = undefined, 37 | 38 | instance: vk.Instance = .null_handle, 39 | debug_messenger: vk.DebugUtilsMessengerEXT = .null_handle, 40 | 41 | pub fn init(allocator: Allocator) Self { 42 | return Self{ .allocator = allocator }; 43 | } 44 | 45 | pub fn run(self: *Self) !void { 46 | try self.initWindow(); 47 | try self.initVulkan(); 48 | try self.mainLoop(); 49 | } 50 | 51 | fn initWindow(self: *Self) !void { 52 | try glfw.init(.{}); 53 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 54 | .client_api = .no_api, 55 | .resizable = false, 56 | }); 57 | } 58 | 59 | fn initVulkan(self: *Self) !void { 60 | try self.createInstance(); 61 | try self.setupDebugMessenger(); 62 | } 63 | 64 | fn mainLoop(self: *Self) !void { 65 | while (!self.window.?.shouldClose()) { 66 | try glfw.pollEvents(); 67 | } 68 | } 69 | 70 | pub fn deinit(self: *Self) void { 71 | if (enable_validation_layers and self.debug_messenger != .null_handle) self.vki.destroyDebugUtilsMessengerEXT(self.instance, self.debug_messenger, null); 72 | 73 | if (self.instance != .null_handle) self.vki.destroyInstance(self.instance, null); 74 | 75 | if (self.window != null) self.window.?.destroy(); 76 | 77 | glfw.terminate(); 78 | } 79 | 80 | fn createInstance(self: *Self) !void { 81 | const vk_proc = @ptrCast(*const fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, &glfw.getInstanceProcAddress); 82 | self.vkb = try BaseDispatch.load(vk_proc); 83 | 84 | if (enable_validation_layers and !try self.checkValidationLayerSupport()) { 85 | return error.MissingValidationLayer; 86 | } 87 | 88 | const app_info = vk.ApplicationInfo{ 89 | .p_application_name = "Hello Triangle", 90 | .application_version = vk.makeApiVersion(1, 0, 0, 0), 91 | .p_engine_name = "No Engine", 92 | .engine_version = vk.makeApiVersion(1, 0, 0, 0), 93 | .api_version = vk.API_VERSION_1_2, 94 | }; 95 | 96 | const extensions = try getRequiredExtensions(self.allocator); 97 | defer extensions.deinit(); 98 | 99 | var create_info = vk.InstanceCreateInfo{ 100 | .flags = .{}, 101 | .p_application_info = &app_info, 102 | .enabled_layer_count = 0, 103 | .pp_enabled_layer_names = undefined, 104 | .enabled_extension_count = @intCast(u32, extensions.items.len), 105 | .pp_enabled_extension_names = extensions.items.ptr, 106 | }; 107 | 108 | if (enable_validation_layers) { 109 | create_info.enabled_layer_count = validation_layers.len; 110 | create_info.pp_enabled_layer_names = &validation_layers; 111 | 112 | var debug_create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 113 | populateDebugMessengerCreateInfo(&debug_create_info); 114 | create_info.p_next = &debug_create_info; 115 | } 116 | 117 | self.instance = try self.vkb.createInstance(&create_info, null); 118 | 119 | self.vki = try InstanceDispatch.load(self.instance, vk_proc); 120 | } 121 | 122 | fn populateDebugMessengerCreateInfo(create_info: *vk.DebugUtilsMessengerCreateInfoEXT) void { 123 | create_info.* = .{ 124 | .flags = .{}, 125 | .message_severity = .{ 126 | .verbose_bit_ext = true, 127 | .warning_bit_ext = true, 128 | .error_bit_ext = true, 129 | }, 130 | .message_type = .{ 131 | .general_bit_ext = true, 132 | .validation_bit_ext = true, 133 | .performance_bit_ext = true, 134 | }, 135 | .pfn_user_callback = debugCallback, 136 | .p_user_data = null, 137 | }; 138 | } 139 | 140 | fn setupDebugMessenger(self: *Self) !void { 141 | if (!enable_validation_layers) return; 142 | 143 | var create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 144 | populateDebugMessengerCreateInfo(&create_info); 145 | 146 | self.debug_messenger = try self.vki.createDebugUtilsMessengerEXT(self.instance, &create_info, null); 147 | } 148 | 149 | fn getRequiredExtensions(allocator: Allocator) !std.ArrayListAligned([*:0]const u8, null) { 150 | var extensions = std.ArrayList([*:0]const u8).init(allocator); 151 | try extensions.appendSlice(try glfw.getRequiredInstanceExtensions()); 152 | 153 | if (enable_validation_layers) { 154 | try extensions.append(vk.extension_info.ext_debug_utils.name); 155 | } 156 | 157 | return extensions; 158 | } 159 | 160 | fn checkValidationLayerSupport(self: *Self) !bool { 161 | var layer_count: u32 = undefined; 162 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, null); 163 | 164 | var available_layers = try self.allocator.alloc(vk.LayerProperties, layer_count); 165 | defer self.allocator.free(available_layers); 166 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr); 167 | 168 | for (validation_layers) |layer_name| { 169 | var layer_found: bool = false; 170 | 171 | for (available_layers) |layer_properties| { 172 | const available_len = std.mem.indexOfScalar(u8, &layer_properties.layer_name, 0).?; 173 | const available_layer_name = layer_properties.layer_name[0..available_len]; 174 | if (std.mem.eql(u8, std.mem.span(layer_name), available_layer_name)) { 175 | layer_found = true; 176 | break; 177 | } 178 | } 179 | 180 | if (!layer_found) { 181 | return false; 182 | } 183 | } 184 | 185 | return true; 186 | } 187 | 188 | fn debugCallback(_: vk.DebugUtilsMessageSeverityFlagsEXT, _: vk.DebugUtilsMessageTypeFlagsEXT, p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque) callconv(vk.vulkan_call_conv) vk.Bool32 { 189 | if (p_callback_data != null) { 190 | std.log.debug("validation layer: {s}", .{p_callback_data.?.p_message}); 191 | } 192 | 193 | return vk.FALSE; 194 | } 195 | }; 196 | 197 | pub fn main() void { 198 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 199 | defer { 200 | const leaked = gpa.deinit(); 201 | if (leaked) std.log.err("MemLeak", .{}); 202 | } 203 | const allocator = gpa.allocator(); 204 | 205 | var app = HelloTriangleApplication.init(allocator); 206 | defer app.deinit(); 207 | app.run() catch |err| { 208 | std.log.err("application exited with error: {any}", .{err}); 209 | return; 210 | }; 211 | } 212 | -------------------------------------------------------------------------------- /src/03_physical_device_selection.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | const glfw = @import("glfw"); 6 | const vk = @import("vulkan"); 7 | 8 | const WIDTH: u32 = 800; 9 | const HEIGHT: u32 = 600; 10 | 11 | const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; 12 | 13 | const enable_validation_layers: bool = switch (builtin.mode) { 14 | .Debug, .ReleaseSafe => true, 15 | else => false, 16 | }; 17 | 18 | const BaseDispatch = vk.BaseWrapper(.{ 19 | .createInstance = true, 20 | .enumerateInstanceLayerProperties = true, 21 | }); 22 | 23 | const InstanceDispatch = vk.InstanceWrapper(.{ 24 | .createDebugUtilsMessengerEXT = enable_validation_layers, 25 | .destroyDebugUtilsMessengerEXT = enable_validation_layers, 26 | .destroyInstance = true, 27 | .enumeratePhysicalDevices = true, 28 | .getPhysicalDeviceQueueFamilyProperties = true, 29 | }); 30 | 31 | const QueueFamilyIndices = struct { 32 | graphics_family: ?u32 = null, 33 | 34 | fn isComplete(self: *const QueueFamilyIndices) bool { 35 | return self.graphics_family != null; 36 | } 37 | }; 38 | 39 | const HelloTriangleApplication = struct { 40 | const Self = @This(); 41 | allocator: Allocator, 42 | 43 | window: ?glfw.Window = null, 44 | 45 | vkb: BaseDispatch = undefined, 46 | vki: InstanceDispatch = undefined, 47 | 48 | instance: vk.Instance = .null_handle, 49 | debug_messenger: vk.DebugUtilsMessengerEXT = .null_handle, 50 | 51 | physical_device: vk.PhysicalDevice = .null_handle, 52 | 53 | pub fn init(allocator: Allocator) Self { 54 | return Self{ .allocator = allocator }; 55 | } 56 | 57 | pub fn run(self: *Self) !void { 58 | try self.initWindow(); 59 | try self.initVulkan(); 60 | try self.mainLoop(); 61 | } 62 | 63 | fn initWindow(self: *Self) !void { 64 | try glfw.init(.{}); 65 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 66 | .client_api = .no_api, 67 | .resizable = false, 68 | }); 69 | } 70 | 71 | fn initVulkan(self: *Self) !void { 72 | try self.createInstance(); 73 | try self.setupDebugMessenger(); 74 | try self.pickPhysicalDevice(); 75 | } 76 | 77 | fn mainLoop(self: *Self) !void { 78 | while (!self.window.?.shouldClose()) { 79 | try glfw.pollEvents(); 80 | } 81 | } 82 | 83 | pub fn deinit(self: *Self) void { 84 | if (enable_validation_layers and self.debug_messenger != .null_handle) self.vki.destroyDebugUtilsMessengerEXT(self.instance, self.debug_messenger, null); 85 | 86 | if (self.instance != .null_handle) self.vki.destroyInstance(self.instance, null); 87 | 88 | if (self.window != null) self.window.?.destroy(); 89 | 90 | glfw.terminate(); 91 | } 92 | 93 | fn createInstance(self: *Self) !void { 94 | const vk_proc = @ptrCast(*const fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, &glfw.getInstanceProcAddress); 95 | self.vkb = try BaseDispatch.load(vk_proc); 96 | 97 | if (enable_validation_layers and !try self.checkValidationLayerSupport()) { 98 | return error.MissingValidationLayer; 99 | } 100 | 101 | const app_info = vk.ApplicationInfo{ 102 | .p_application_name = "Hello Triangle", 103 | .application_version = vk.makeApiVersion(1, 0, 0, 0), 104 | .p_engine_name = "No Engine", 105 | .engine_version = vk.makeApiVersion(1, 0, 0, 0), 106 | .api_version = vk.API_VERSION_1_2, 107 | }; 108 | 109 | const extensions = try getRequiredExtensions(self.allocator); 110 | defer extensions.deinit(); 111 | 112 | var create_info = vk.InstanceCreateInfo{ 113 | .flags = .{}, 114 | .p_application_info = &app_info, 115 | .enabled_layer_count = 0, 116 | .pp_enabled_layer_names = undefined, 117 | .enabled_extension_count = @intCast(u32, extensions.items.len), 118 | .pp_enabled_extension_names = extensions.items.ptr, 119 | }; 120 | 121 | if (enable_validation_layers) { 122 | create_info.enabled_layer_count = validation_layers.len; 123 | create_info.pp_enabled_layer_names = &validation_layers; 124 | 125 | var debug_create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 126 | populateDebugMessengerCreateInfo(&debug_create_info); 127 | create_info.p_next = &debug_create_info; 128 | } 129 | 130 | self.instance = try self.vkb.createInstance(&create_info, null); 131 | 132 | self.vki = try InstanceDispatch.load(self.instance, vk_proc); 133 | } 134 | 135 | fn populateDebugMessengerCreateInfo(create_info: *vk.DebugUtilsMessengerCreateInfoEXT) void { 136 | create_info.* = .{ 137 | .flags = .{}, 138 | .message_severity = .{ 139 | .verbose_bit_ext = true, 140 | .warning_bit_ext = true, 141 | .error_bit_ext = true, 142 | }, 143 | .message_type = .{ 144 | .general_bit_ext = true, 145 | .validation_bit_ext = true, 146 | .performance_bit_ext = true, 147 | }, 148 | .pfn_user_callback = debugCallback, 149 | .p_user_data = null, 150 | }; 151 | } 152 | 153 | fn setupDebugMessenger(self: *Self) !void { 154 | if (!enable_validation_layers) return; 155 | 156 | var create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 157 | populateDebugMessengerCreateInfo(&create_info); 158 | 159 | self.debug_messenger = try self.vki.createDebugUtilsMessengerEXT(self.instance, &create_info, null); 160 | } 161 | 162 | fn pickPhysicalDevice(self: *Self) !void { 163 | var device_count: u32 = undefined; 164 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, null); 165 | 166 | if (device_count == 0) { 167 | return error.NoGPUsSupportVulkan; 168 | } 169 | 170 | const devices = try self.allocator.alloc(vk.PhysicalDevice, device_count); 171 | defer self.allocator.free(devices); 172 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, devices.ptr); 173 | 174 | for (devices) |device| { 175 | if (try self.isDeviceSuitable(device)) { 176 | self.physical_device = device; 177 | break; 178 | } 179 | } 180 | 181 | if (self.physical_device == .null_handle) { 182 | return error.NoSuitableDevice; 183 | } 184 | } 185 | 186 | fn isDeviceSuitable(self: *Self, device: vk.PhysicalDevice) !bool { 187 | const indices = try self.findQueueFamilies(device); 188 | 189 | return indices.isComplete(); 190 | } 191 | 192 | fn findQueueFamilies(self: *Self, device: vk.PhysicalDevice) !QueueFamilyIndices { 193 | var indices: QueueFamilyIndices = .{}; 194 | 195 | var queue_family_count: u32 = 0; 196 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, null); 197 | 198 | const queue_families = try self.allocator.alloc(vk.QueueFamilyProperties, queue_family_count); 199 | defer self.allocator.free(queue_families); 200 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.ptr); 201 | 202 | for (queue_families) |queue_family, i| { 203 | if (queue_family.queue_flags.graphics_bit) { 204 | indices.graphics_family = @intCast(u32, i); 205 | } 206 | 207 | if (indices.isComplete()) { 208 | break; 209 | } 210 | } 211 | 212 | return indices; 213 | } 214 | 215 | fn getRequiredExtensions(allocator: Allocator) !std.ArrayListAligned([*:0]const u8, null) { 216 | var extensions = std.ArrayList([*:0]const u8).init(allocator); 217 | try extensions.appendSlice(try glfw.getRequiredInstanceExtensions()); 218 | 219 | if (enable_validation_layers) { 220 | try extensions.append(vk.extension_info.ext_debug_utils.name); 221 | } 222 | 223 | return extensions; 224 | } 225 | 226 | fn checkValidationLayerSupport(self: *Self) !bool { 227 | var layer_count: u32 = undefined; 228 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, null); 229 | 230 | var available_layers = try self.allocator.alloc(vk.LayerProperties, layer_count); 231 | defer self.allocator.free(available_layers); 232 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr); 233 | 234 | for (validation_layers) |layer_name| { 235 | var layer_found: bool = false; 236 | 237 | for (available_layers) |layer_properties| { 238 | const available_len = std.mem.indexOfScalar(u8, &layer_properties.layer_name, 0).?; 239 | const available_layer_name = layer_properties.layer_name[0..available_len]; 240 | if (std.mem.eql(u8, std.mem.span(layer_name), available_layer_name)) { 241 | layer_found = true; 242 | break; 243 | } 244 | } 245 | 246 | if (!layer_found) { 247 | return false; 248 | } 249 | } 250 | 251 | return true; 252 | } 253 | 254 | fn debugCallback(_: vk.DebugUtilsMessageSeverityFlagsEXT, _: vk.DebugUtilsMessageTypeFlagsEXT, p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque) callconv(vk.vulkan_call_conv) vk.Bool32 { 255 | if (p_callback_data != null) { 256 | std.log.debug("validation layer: {s}", .{p_callback_data.?.p_message}); 257 | } 258 | 259 | return vk.FALSE; 260 | } 261 | }; 262 | 263 | pub fn main() void { 264 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 265 | defer { 266 | const leaked = gpa.deinit(); 267 | if (leaked) std.log.err("MemLeak", .{}); 268 | } 269 | const allocator = gpa.allocator(); 270 | 271 | var app = HelloTriangleApplication.init(allocator); 272 | defer app.deinit(); 273 | app.run() catch |err| { 274 | std.log.err("application exited with error: {any}", .{err}); 275 | return; 276 | }; 277 | } 278 | -------------------------------------------------------------------------------- /src/04_logical_device.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | const glfw = @import("glfw"); 6 | const vk = @import("vulkan"); 7 | 8 | const WIDTH: u32 = 800; 9 | const HEIGHT: u32 = 600; 10 | 11 | const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; 12 | 13 | const enable_validation_layers: bool = switch (builtin.mode) { 14 | .Debug, .ReleaseSafe => true, 15 | else => false, 16 | }; 17 | 18 | const BaseDispatch = vk.BaseWrapper(.{ 19 | .createInstance = true, 20 | .enumerateInstanceLayerProperties = true, 21 | }); 22 | 23 | const InstanceDispatch = vk.InstanceWrapper(.{ 24 | .createDebugUtilsMessengerEXT = enable_validation_layers, 25 | .createDevice = true, 26 | .destroyDebugUtilsMessengerEXT = enable_validation_layers, 27 | .destroyInstance = true, 28 | .enumeratePhysicalDevices = true, 29 | .getDeviceProcAddr = true, 30 | .getPhysicalDeviceQueueFamilyProperties = true, 31 | }); 32 | 33 | const DeviceDispatch = vk.DeviceWrapper(.{ 34 | .destroyDevice = true, 35 | .getDeviceQueue = true, 36 | }); 37 | 38 | const QueueFamilyIndices = struct { 39 | graphics_family: ?u32 = null, 40 | 41 | fn isComplete(self: *const QueueFamilyIndices) bool { 42 | return self.graphics_family != null; 43 | } 44 | }; 45 | 46 | const HelloTriangleApplication = struct { 47 | const Self = @This(); 48 | allocator: Allocator, 49 | 50 | window: ?glfw.Window = null, 51 | 52 | vkb: BaseDispatch = undefined, 53 | vki: InstanceDispatch = undefined, 54 | vkd: DeviceDispatch = undefined, 55 | 56 | instance: vk.Instance = .null_handle, 57 | debug_messenger: vk.DebugUtilsMessengerEXT = .null_handle, 58 | 59 | physical_device: vk.PhysicalDevice = .null_handle, 60 | device: vk.Device = .null_handle, 61 | 62 | graphics_queue: vk.Queue = .null_handle, 63 | 64 | pub fn init(allocator: Allocator) Self { 65 | return Self{ .allocator = allocator }; 66 | } 67 | 68 | pub fn run(self: *Self) !void { 69 | try self.initWindow(); 70 | try self.initVulkan(); 71 | try self.mainLoop(); 72 | } 73 | 74 | fn initWindow(self: *Self) !void { 75 | try glfw.init(.{}); 76 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 77 | .client_api = .no_api, 78 | .resizable = false, 79 | }); 80 | } 81 | 82 | fn initVulkan(self: *Self) !void { 83 | try self.createInstance(); 84 | try self.setupDebugMessenger(); 85 | try self.pickPhysicalDevice(); 86 | try self.createLogicalDevice(); 87 | } 88 | 89 | fn mainLoop(self: *Self) !void { 90 | while (!self.window.?.shouldClose()) { 91 | try glfw.pollEvents(); 92 | } 93 | } 94 | 95 | pub fn deinit(self: *Self) void { 96 | if (self.device != .null_handle) self.vkd.destroyDevice(self.device, null); 97 | 98 | if (enable_validation_layers and self.debug_messenger != .null_handle) self.vki.destroyDebugUtilsMessengerEXT(self.instance, self.debug_messenger, null); 99 | 100 | if (self.instance != .null_handle) self.vki.destroyInstance(self.instance, null); 101 | 102 | if (self.window != null) self.window.?.destroy(); 103 | 104 | glfw.terminate(); 105 | } 106 | 107 | fn createInstance(self: *Self) !void { 108 | const vk_proc = @ptrCast(*const fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, &glfw.getInstanceProcAddress); 109 | self.vkb = try BaseDispatch.load(vk_proc); 110 | 111 | if (enable_validation_layers and !try self.checkValidationLayerSupport()) { 112 | return error.MissingValidationLayer; 113 | } 114 | 115 | const app_info = vk.ApplicationInfo{ 116 | .p_application_name = "Hello Triangle", 117 | .application_version = vk.makeApiVersion(1, 0, 0, 0), 118 | .p_engine_name = "No Engine", 119 | .engine_version = vk.makeApiVersion(1, 0, 0, 0), 120 | .api_version = vk.API_VERSION_1_2, 121 | }; 122 | 123 | const extensions = try getRequiredExtensions(self.allocator); 124 | defer extensions.deinit(); 125 | 126 | var create_info = vk.InstanceCreateInfo{ 127 | .flags = .{}, 128 | .p_application_info = &app_info, 129 | .enabled_layer_count = 0, 130 | .pp_enabled_layer_names = undefined, 131 | .enabled_extension_count = @intCast(u32, extensions.items.len), 132 | .pp_enabled_extension_names = extensions.items.ptr, 133 | }; 134 | 135 | if (enable_validation_layers) { 136 | create_info.enabled_layer_count = validation_layers.len; 137 | create_info.pp_enabled_layer_names = &validation_layers; 138 | 139 | var debug_create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 140 | populateDebugMessengerCreateInfo(&debug_create_info); 141 | create_info.p_next = &debug_create_info; 142 | } 143 | 144 | self.instance = try self.vkb.createInstance(&create_info, null); 145 | 146 | self.vki = try InstanceDispatch.load(self.instance, vk_proc); 147 | } 148 | 149 | fn populateDebugMessengerCreateInfo(create_info: *vk.DebugUtilsMessengerCreateInfoEXT) void { 150 | create_info.* = .{ 151 | .flags = .{}, 152 | .message_severity = .{ 153 | .verbose_bit_ext = true, 154 | .warning_bit_ext = true, 155 | .error_bit_ext = true, 156 | }, 157 | .message_type = .{ 158 | .general_bit_ext = true, 159 | .validation_bit_ext = true, 160 | .performance_bit_ext = true, 161 | }, 162 | .pfn_user_callback = debugCallback, 163 | .p_user_data = null, 164 | }; 165 | } 166 | 167 | fn setupDebugMessenger(self: *Self) !void { 168 | if (!enable_validation_layers) return; 169 | 170 | var create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 171 | populateDebugMessengerCreateInfo(&create_info); 172 | 173 | self.debug_messenger = try self.vki.createDebugUtilsMessengerEXT(self.instance, &create_info, null); 174 | } 175 | 176 | fn pickPhysicalDevice(self: *Self) !void { 177 | var device_count: u32 = undefined; 178 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, null); 179 | 180 | if (device_count == 0) { 181 | return error.NoGPUsSupportVulkan; 182 | } 183 | 184 | const devices = try self.allocator.alloc(vk.PhysicalDevice, device_count); 185 | defer self.allocator.free(devices); 186 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, devices.ptr); 187 | 188 | for (devices) |device| { 189 | if (try self.isDeviceSuitable(device)) { 190 | self.physical_device = device; 191 | break; 192 | } 193 | } 194 | 195 | if (self.physical_device == .null_handle) { 196 | return error.NoSuitableDevice; 197 | } 198 | } 199 | 200 | fn createLogicalDevice(self: *Self) !void { 201 | const indices = try self.findQueueFamilies(self.physical_device); 202 | const queue_priority = [_]f32{1}; 203 | 204 | var queue_create_info = [_]vk.DeviceQueueCreateInfo{.{ 205 | .flags = .{}, 206 | .queue_family_index = indices.graphics_family.?, 207 | .queue_count = 1, 208 | .p_queue_priorities = &queue_priority, 209 | }}; 210 | 211 | var create_info = vk.DeviceCreateInfo{ 212 | .flags = .{}, 213 | .queue_create_info_count = queue_create_info.len, 214 | .p_queue_create_infos = &queue_create_info, 215 | .enabled_layer_count = 0, 216 | .pp_enabled_layer_names = undefined, 217 | .enabled_extension_count = 0, 218 | .pp_enabled_extension_names = undefined, 219 | .p_enabled_features = null, 220 | }; 221 | 222 | if (enable_validation_layers) { 223 | create_info.enabled_layer_count = validation_layers.len; 224 | create_info.pp_enabled_layer_names = &validation_layers; 225 | } 226 | 227 | self.device = try self.vki.createDevice(self.physical_device, &create_info, null); 228 | 229 | self.vkd = try DeviceDispatch.load(self.device, self.vki.dispatch.vkGetDeviceProcAddr); 230 | 231 | self.graphics_queue = self.vkd.getDeviceQueue(self.device, indices.graphics_family.?, 0); 232 | } 233 | 234 | fn isDeviceSuitable(self: *Self, device: vk.PhysicalDevice) !bool { 235 | const indices = try self.findQueueFamilies(device); 236 | 237 | return indices.isComplete(); 238 | } 239 | 240 | fn findQueueFamilies(self: *Self, device: vk.PhysicalDevice) !QueueFamilyIndices { 241 | var indices: QueueFamilyIndices = .{}; 242 | 243 | var queue_family_count: u32 = 0; 244 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, null); 245 | 246 | const queue_families = try self.allocator.alloc(vk.QueueFamilyProperties, queue_family_count); 247 | defer self.allocator.free(queue_families); 248 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.ptr); 249 | 250 | for (queue_families) |queue_family, i| { 251 | if (queue_family.queue_flags.graphics_bit) { 252 | indices.graphics_family = @intCast(u32, i); 253 | } 254 | 255 | if (indices.isComplete()) { 256 | break; 257 | } 258 | } 259 | 260 | return indices; 261 | } 262 | 263 | fn getRequiredExtensions(allocator: Allocator) !std.ArrayListAligned([*:0]const u8, null) { 264 | var extensions = std.ArrayList([*:0]const u8).init(allocator); 265 | try extensions.appendSlice(try glfw.getRequiredInstanceExtensions()); 266 | 267 | if (enable_validation_layers) { 268 | try extensions.append(vk.extension_info.ext_debug_utils.name); 269 | } 270 | 271 | return extensions; 272 | } 273 | 274 | fn checkValidationLayerSupport(self: *Self) !bool { 275 | var layer_count: u32 = undefined; 276 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, null); 277 | 278 | var available_layers = try self.allocator.alloc(vk.LayerProperties, layer_count); 279 | defer self.allocator.free(available_layers); 280 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr); 281 | 282 | for (validation_layers) |layer_name| { 283 | var layer_found: bool = false; 284 | 285 | for (available_layers) |layer_properties| { 286 | const available_len = std.mem.indexOfScalar(u8, &layer_properties.layer_name, 0).?; 287 | const available_layer_name = layer_properties.layer_name[0..available_len]; 288 | if (std.mem.eql(u8, std.mem.span(layer_name), available_layer_name)) { 289 | layer_found = true; 290 | break; 291 | } 292 | } 293 | 294 | if (!layer_found) { 295 | return false; 296 | } 297 | } 298 | 299 | return true; 300 | } 301 | 302 | fn debugCallback(_: vk.DebugUtilsMessageSeverityFlagsEXT, _: vk.DebugUtilsMessageTypeFlagsEXT, p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque) callconv(vk.vulkan_call_conv) vk.Bool32 { 303 | if (p_callback_data != null) { 304 | std.log.debug("validation layer: {s}", .{p_callback_data.?.p_message}); 305 | } 306 | 307 | return vk.FALSE; 308 | } 309 | }; 310 | 311 | pub fn main() void { 312 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 313 | defer { 314 | const leaked = gpa.deinit(); 315 | if (leaked) std.log.err("MemLeak", .{}); 316 | } 317 | const allocator = gpa.allocator(); 318 | 319 | var app = HelloTriangleApplication.init(allocator); 320 | defer app.deinit(); 321 | app.run() catch |err| { 322 | std.log.err("application exited with error: {any}", .{err}); 323 | return; 324 | }; 325 | } 326 | -------------------------------------------------------------------------------- /src/05_window_surface.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | const glfw = @import("glfw"); 6 | const vk = @import("vulkan"); 7 | 8 | const WIDTH: u32 = 800; 9 | const HEIGHT: u32 = 600; 10 | 11 | const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; 12 | 13 | const enable_validation_layers: bool = switch (builtin.mode) { 14 | .Debug, .ReleaseSafe => true, 15 | else => false, 16 | }; 17 | 18 | const BaseDispatch = vk.BaseWrapper(.{ 19 | .createInstance = true, 20 | .enumerateInstanceLayerProperties = true, 21 | }); 22 | 23 | const InstanceDispatch = vk.InstanceWrapper(.{ 24 | .createDebugUtilsMessengerEXT = enable_validation_layers, 25 | .createDevice = true, 26 | .destroyDebugUtilsMessengerEXT = enable_validation_layers, 27 | .destroyInstance = true, 28 | .destroySurfaceKHR = true, 29 | .enumeratePhysicalDevices = true, 30 | .getDeviceProcAddr = true, 31 | .getPhysicalDeviceQueueFamilyProperties = true, 32 | .getPhysicalDeviceSurfaceSupportKHR = true, 33 | }); 34 | 35 | const DeviceDispatch = vk.DeviceWrapper(.{ 36 | .destroyDevice = true, 37 | .getDeviceQueue = true, 38 | }); 39 | 40 | const QueueFamilyIndices = struct { 41 | graphics_family: ?u32 = null, 42 | present_family: ?u32 = null, 43 | 44 | fn isComplete(self: *const QueueFamilyIndices) bool { 45 | return self.graphics_family != null and self.present_family != null; 46 | } 47 | }; 48 | 49 | const HelloTriangleApplication = struct { 50 | const Self = @This(); 51 | allocator: Allocator, 52 | 53 | window: ?glfw.Window = null, 54 | 55 | vkb: BaseDispatch = undefined, 56 | vki: InstanceDispatch = undefined, 57 | vkd: DeviceDispatch = undefined, 58 | 59 | instance: vk.Instance = .null_handle, 60 | debug_messenger: vk.DebugUtilsMessengerEXT = .null_handle, 61 | surface: vk.SurfaceKHR = .null_handle, 62 | 63 | physical_device: vk.PhysicalDevice = .null_handle, 64 | device: vk.Device = .null_handle, 65 | 66 | graphics_queue: vk.Queue = .null_handle, 67 | present_queue: vk.Queue = .null_handle, 68 | 69 | pub fn init(allocator: Allocator) Self { 70 | return Self{ .allocator = allocator }; 71 | } 72 | 73 | pub fn run(self: *Self) !void { 74 | try self.initWindow(); 75 | try self.initVulkan(); 76 | try self.mainLoop(); 77 | } 78 | 79 | fn initWindow(self: *Self) !void { 80 | try glfw.init(.{}); 81 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 82 | .client_api = .no_api, 83 | .resizable = false, 84 | }); 85 | } 86 | 87 | fn initVulkan(self: *Self) !void { 88 | try self.createInstance(); 89 | try self.setupDebugMessenger(); 90 | try self.createSurface(); 91 | try self.pickPhysicalDevice(); 92 | try self.createLogicalDevice(); 93 | } 94 | 95 | fn mainLoop(self: *Self) !void { 96 | while (!self.window.?.shouldClose()) { 97 | try glfw.pollEvents(); 98 | } 99 | } 100 | 101 | pub fn deinit(self: *Self) void { 102 | if (self.device != .null_handle) self.vkd.destroyDevice(self.device, null); 103 | 104 | if (enable_validation_layers and self.debug_messenger != .null_handle) self.vki.destroyDebugUtilsMessengerEXT(self.instance, self.debug_messenger, null); 105 | 106 | if (self.surface != .null_handle) self.vki.destroySurfaceKHR(self.instance, self.surface, null); 107 | if (self.instance != .null_handle) self.vki.destroyInstance(self.instance, null); 108 | 109 | if (self.window != null) self.window.?.destroy(); 110 | 111 | glfw.terminate(); 112 | } 113 | 114 | fn createInstance(self: *Self) !void { 115 | const vk_proc = @ptrCast(*const fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, &glfw.getInstanceProcAddress); 116 | self.vkb = try BaseDispatch.load(vk_proc); 117 | 118 | if (enable_validation_layers and !try self.checkValidationLayerSupport()) { 119 | return error.MissingValidationLayer; 120 | } 121 | 122 | const app_info = vk.ApplicationInfo{ 123 | .p_application_name = "Hello Triangle", 124 | .application_version = vk.makeApiVersion(1, 0, 0, 0), 125 | .p_engine_name = "No Engine", 126 | .engine_version = vk.makeApiVersion(1, 0, 0, 0), 127 | .api_version = vk.API_VERSION_1_2, 128 | }; 129 | 130 | const extensions = try getRequiredExtensions(self.allocator); 131 | defer extensions.deinit(); 132 | 133 | var create_info = vk.InstanceCreateInfo{ 134 | .flags = .{}, 135 | .p_application_info = &app_info, 136 | .enabled_layer_count = 0, 137 | .pp_enabled_layer_names = undefined, 138 | .enabled_extension_count = @intCast(u32, extensions.items.len), 139 | .pp_enabled_extension_names = extensions.items.ptr, 140 | }; 141 | 142 | if (enable_validation_layers) { 143 | create_info.enabled_layer_count = validation_layers.len; 144 | create_info.pp_enabled_layer_names = &validation_layers; 145 | 146 | var debug_create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 147 | populateDebugMessengerCreateInfo(&debug_create_info); 148 | create_info.p_next = &debug_create_info; 149 | } 150 | 151 | self.instance = try self.vkb.createInstance(&create_info, null); 152 | 153 | self.vki = try InstanceDispatch.load(self.instance, vk_proc); 154 | } 155 | 156 | fn populateDebugMessengerCreateInfo(create_info: *vk.DebugUtilsMessengerCreateInfoEXT) void { 157 | create_info.* = .{ 158 | .flags = .{}, 159 | .message_severity = .{ 160 | .verbose_bit_ext = true, 161 | .warning_bit_ext = true, 162 | .error_bit_ext = true, 163 | }, 164 | .message_type = .{ 165 | .general_bit_ext = true, 166 | .validation_bit_ext = true, 167 | .performance_bit_ext = true, 168 | }, 169 | .pfn_user_callback = debugCallback, 170 | .p_user_data = null, 171 | }; 172 | } 173 | 174 | fn setupDebugMessenger(self: *Self) !void { 175 | if (!enable_validation_layers) return; 176 | 177 | var create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 178 | populateDebugMessengerCreateInfo(&create_info); 179 | 180 | self.debug_messenger = try self.vki.createDebugUtilsMessengerEXT(self.instance, &create_info, null); 181 | } 182 | 183 | fn createSurface(self: *Self) !void { 184 | if ((try glfw.createWindowSurface(self.instance, self.window.?, null, &self.surface)) != @enumToInt(vk.Result.success)) { 185 | return error.SurfaceInitFailed; 186 | } 187 | } 188 | 189 | fn pickPhysicalDevice(self: *Self) !void { 190 | var device_count: u32 = undefined; 191 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, null); 192 | 193 | if (device_count == 0) { 194 | return error.NoGPUsSupportVulkan; 195 | } 196 | 197 | const devices = try self.allocator.alloc(vk.PhysicalDevice, device_count); 198 | defer self.allocator.free(devices); 199 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, devices.ptr); 200 | 201 | for (devices) |device| { 202 | if (try self.isDeviceSuitable(device)) { 203 | self.physical_device = device; 204 | break; 205 | } 206 | } 207 | 208 | if (self.physical_device == .null_handle) { 209 | return error.NoSuitableDevice; 210 | } 211 | } 212 | 213 | fn createLogicalDevice(self: *Self) !void { 214 | const indices = try self.findQueueFamilies(self.physical_device); 215 | const queue_priority = [_]f32{1}; 216 | 217 | var queue_create_info = [_]vk.DeviceQueueCreateInfo{ 218 | .{ 219 | .flags = .{}, 220 | .queue_family_index = indices.graphics_family.?, 221 | .queue_count = 1, 222 | .p_queue_priorities = &queue_priority, 223 | }, 224 | .{ 225 | .flags = .{}, 226 | .queue_family_index = indices.present_family.?, 227 | .queue_count = 1, 228 | .p_queue_priorities = &queue_priority, 229 | }, 230 | }; 231 | 232 | var create_info = vk.DeviceCreateInfo{ 233 | .flags = .{}, 234 | .queue_create_info_count = queue_create_info.len, 235 | .p_queue_create_infos = &queue_create_info, 236 | .enabled_layer_count = 0, 237 | .pp_enabled_layer_names = undefined, 238 | .enabled_extension_count = 0, 239 | .pp_enabled_extension_names = undefined, 240 | .p_enabled_features = null, 241 | }; 242 | 243 | if (enable_validation_layers) { 244 | create_info.enabled_layer_count = validation_layers.len; 245 | create_info.pp_enabled_layer_names = &validation_layers; 246 | } 247 | 248 | self.device = try self.vki.createDevice(self.physical_device, &create_info, null); 249 | 250 | self.vkd = try DeviceDispatch.load(self.device, self.vki.dispatch.vkGetDeviceProcAddr); 251 | 252 | self.graphics_queue = self.vkd.getDeviceQueue(self.device, indices.graphics_family.?, 0); 253 | self.present_queue = self.vkd.getDeviceQueue(self.device, indices.present_family.?, 0); 254 | } 255 | 256 | fn isDeviceSuitable(self: *Self, device: vk.PhysicalDevice) !bool { 257 | const indices = try self.findQueueFamilies(device); 258 | 259 | return indices.isComplete(); 260 | } 261 | 262 | fn findQueueFamilies(self: *Self, device: vk.PhysicalDevice) !QueueFamilyIndices { 263 | var indices: QueueFamilyIndices = .{}; 264 | 265 | var queue_family_count: u32 = 0; 266 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, null); 267 | 268 | const queue_families = try self.allocator.alloc(vk.QueueFamilyProperties, queue_family_count); 269 | defer self.allocator.free(queue_families); 270 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.ptr); 271 | 272 | for (queue_families) |queue_family, i| { 273 | if (indices.graphics_family == null and queue_family.queue_flags.graphics_bit) { 274 | indices.graphics_family = @intCast(u32, i); 275 | } else if (indices.present_family == null and (try self.vki.getPhysicalDeviceSurfaceSupportKHR(device, @intCast(u32, i), self.surface)) == vk.TRUE) { 276 | indices.present_family = @intCast(u32, i); 277 | } 278 | 279 | if (indices.isComplete()) { 280 | break; 281 | } 282 | } 283 | 284 | return indices; 285 | } 286 | 287 | fn getRequiredExtensions(allocator: Allocator) !std.ArrayListAligned([*:0]const u8, null) { 288 | var extensions = std.ArrayList([*:0]const u8).init(allocator); 289 | try extensions.appendSlice(try glfw.getRequiredInstanceExtensions()); 290 | 291 | if (enable_validation_layers) { 292 | try extensions.append(vk.extension_info.ext_debug_utils.name); 293 | } 294 | 295 | return extensions; 296 | } 297 | 298 | fn checkValidationLayerSupport(self: *Self) !bool { 299 | var layer_count: u32 = undefined; 300 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, null); 301 | 302 | var available_layers = try self.allocator.alloc(vk.LayerProperties, layer_count); 303 | defer self.allocator.free(available_layers); 304 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr); 305 | 306 | for (validation_layers) |layer_name| { 307 | var layer_found: bool = false; 308 | 309 | for (available_layers) |layer_properties| { 310 | const available_len = std.mem.indexOfScalar(u8, &layer_properties.layer_name, 0).?; 311 | const available_layer_name = layer_properties.layer_name[0..available_len]; 312 | if (std.mem.eql(u8, std.mem.span(layer_name), available_layer_name)) { 313 | layer_found = true; 314 | break; 315 | } 316 | } 317 | 318 | if (!layer_found) { 319 | return false; 320 | } 321 | } 322 | 323 | return true; 324 | } 325 | 326 | fn debugCallback(_: vk.DebugUtilsMessageSeverityFlagsEXT, _: vk.DebugUtilsMessageTypeFlagsEXT, p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque) callconv(vk.vulkan_call_conv) vk.Bool32 { 327 | if (p_callback_data != null) { 328 | std.log.debug("validation layer: {s}", .{p_callback_data.?.p_message}); 329 | } 330 | 331 | return vk.FALSE; 332 | } 333 | }; 334 | 335 | pub fn main() void { 336 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 337 | defer { 338 | const leaked = gpa.deinit(); 339 | if (leaked) std.log.err("MemLeak", .{}); 340 | } 341 | const allocator = gpa.allocator(); 342 | 343 | var app = HelloTriangleApplication.init(allocator); 344 | defer app.deinit(); 345 | app.run() catch |err| { 346 | std.log.err("application exited with error: {any}", .{err}); 347 | return; 348 | }; 349 | } 350 | -------------------------------------------------------------------------------- /src/06_swap_chain_creation.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | const glfw = @import("glfw"); 6 | const vk = @import("vulkan"); 7 | 8 | const WIDTH: u32 = 800; 9 | const HEIGHT: u32 = 600; 10 | 11 | const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; 12 | 13 | const device_extensions = [_][*:0]const u8{vk.extension_info.khr_swapchain.name}; 14 | 15 | const enable_validation_layers: bool = switch (builtin.mode) { 16 | .Debug, .ReleaseSafe => true, 17 | else => false, 18 | }; 19 | 20 | const BaseDispatch = vk.BaseWrapper(.{ 21 | .createInstance = true, 22 | .enumerateInstanceLayerProperties = true, 23 | }); 24 | 25 | const InstanceDispatch = vk.InstanceWrapper(.{ 26 | .createDebugUtilsMessengerEXT = enable_validation_layers, 27 | .createDevice = true, 28 | .destroyDebugUtilsMessengerEXT = enable_validation_layers, 29 | .destroyInstance = true, 30 | .destroySurfaceKHR = true, 31 | .enumerateDeviceExtensionProperties = true, 32 | .enumeratePhysicalDevices = true, 33 | .getDeviceProcAddr = true, 34 | .getPhysicalDeviceQueueFamilyProperties = true, 35 | .getPhysicalDeviceSurfaceCapabilitiesKHR = true, 36 | .getPhysicalDeviceSurfaceFormatsKHR = true, 37 | .getPhysicalDeviceSurfacePresentModesKHR = true, 38 | .getPhysicalDeviceSurfaceSupportKHR = true, 39 | }); 40 | 41 | const DeviceDispatch = vk.DeviceWrapper(.{ 42 | .createSwapchainKHR = true, 43 | .destroyDevice = true, 44 | .destroySwapchainKHR = true, 45 | .getDeviceQueue = true, 46 | .getSwapchainImagesKHR = true, 47 | }); 48 | 49 | const QueueFamilyIndices = struct { 50 | graphics_family: ?u32 = null, 51 | present_family: ?u32 = null, 52 | 53 | fn isComplete(self: *const QueueFamilyIndices) bool { 54 | return self.graphics_family != null and self.present_family != null; 55 | } 56 | }; 57 | 58 | pub const SwapChainSupportDetails = struct { 59 | allocator: Allocator, 60 | capabilities: vk.SurfaceCapabilitiesKHR = undefined, 61 | formats: ?[]vk.SurfaceFormatKHR = null, 62 | present_modes: ?[]vk.PresentModeKHR = null, 63 | 64 | pub fn init(allocator: Allocator) SwapChainSupportDetails { 65 | return .{ .allocator = allocator }; 66 | } 67 | 68 | pub fn deinit(self: SwapChainSupportDetails) void { 69 | if (self.formats != null) self.allocator.free(self.formats.?); 70 | if (self.present_modes != null) self.allocator.free(self.present_modes.?); 71 | } 72 | }; 73 | 74 | const HelloTriangleApplication = struct { 75 | const Self = @This(); 76 | allocator: Allocator, 77 | 78 | window: ?glfw.Window = null, 79 | 80 | vkb: BaseDispatch = undefined, 81 | vki: InstanceDispatch = undefined, 82 | vkd: DeviceDispatch = undefined, 83 | 84 | instance: vk.Instance = .null_handle, 85 | debug_messenger: vk.DebugUtilsMessengerEXT = .null_handle, 86 | surface: vk.SurfaceKHR = .null_handle, 87 | 88 | physical_device: vk.PhysicalDevice = .null_handle, 89 | device: vk.Device = .null_handle, 90 | 91 | graphics_queue: vk.Queue = .null_handle, 92 | present_queue: vk.Queue = .null_handle, 93 | 94 | swap_chain: vk.SwapchainKHR = .null_handle, 95 | swap_chain_images: ?[]vk.Image = null, 96 | swap_chain_image_format: vk.Format = .@"undefined", 97 | swap_chain_extent: vk.Extent2D = .{ .width = 0, .height = 0 }, 98 | 99 | pub fn init(allocator: Allocator) Self { 100 | return Self{ .allocator = allocator }; 101 | } 102 | 103 | pub fn run(self: *Self) !void { 104 | try self.initWindow(); 105 | try self.initVulkan(); 106 | try self.mainLoop(); 107 | } 108 | 109 | fn initWindow(self: *Self) !void { 110 | try glfw.init(.{}); 111 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 112 | .client_api = .no_api, 113 | .resizable = false, 114 | }); 115 | } 116 | 117 | fn initVulkan(self: *Self) !void { 118 | try self.createInstance(); 119 | try self.setupDebugMessenger(); 120 | try self.createSurface(); 121 | try self.pickPhysicalDevice(); 122 | try self.createLogicalDevice(); 123 | try self.createSwapChain(); 124 | } 125 | 126 | fn mainLoop(self: *Self) !void { 127 | while (!self.window.?.shouldClose()) { 128 | try glfw.pollEvents(); 129 | } 130 | } 131 | 132 | pub fn deinit(self: *Self) void { 133 | if (self.swap_chain_images != null) self.allocator.free(self.swap_chain_images.?); 134 | if (self.swap_chain != .null_handle) self.vkd.destroySwapchainKHR(self.device, self.swap_chain, null); 135 | if (self.device != .null_handle) self.vkd.destroyDevice(self.device, null); 136 | 137 | if (enable_validation_layers and self.debug_messenger != .null_handle) self.vki.destroyDebugUtilsMessengerEXT(self.instance, self.debug_messenger, null); 138 | 139 | if (self.surface != .null_handle) self.vki.destroySurfaceKHR(self.instance, self.surface, null); 140 | if (self.instance != .null_handle) self.vki.destroyInstance(self.instance, null); 141 | 142 | if (self.window != null) self.window.?.destroy(); 143 | 144 | glfw.terminate(); 145 | } 146 | 147 | fn createInstance(self: *Self) !void { 148 | const vk_proc = @ptrCast(*const fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, &glfw.getInstanceProcAddress); 149 | self.vkb = try BaseDispatch.load(vk_proc); 150 | 151 | if (enable_validation_layers and !try self.checkValidationLayerSupport()) { 152 | return error.MissingValidationLayer; 153 | } 154 | 155 | const app_info = vk.ApplicationInfo{ 156 | .p_application_name = "Hello Triangle", 157 | .application_version = vk.makeApiVersion(1, 0, 0, 0), 158 | .p_engine_name = "No Engine", 159 | .engine_version = vk.makeApiVersion(1, 0, 0, 0), 160 | .api_version = vk.API_VERSION_1_2, 161 | }; 162 | 163 | const extensions = try getRequiredExtensions(self.allocator); 164 | defer extensions.deinit(); 165 | 166 | var create_info = vk.InstanceCreateInfo{ 167 | .flags = .{}, 168 | .p_application_info = &app_info, 169 | .enabled_layer_count = 0, 170 | .pp_enabled_layer_names = undefined, 171 | .enabled_extension_count = @intCast(u32, extensions.items.len), 172 | .pp_enabled_extension_names = extensions.items.ptr, 173 | }; 174 | 175 | if (enable_validation_layers) { 176 | create_info.enabled_layer_count = validation_layers.len; 177 | create_info.pp_enabled_layer_names = &validation_layers; 178 | 179 | var debug_create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 180 | populateDebugMessengerCreateInfo(&debug_create_info); 181 | create_info.p_next = &debug_create_info; 182 | } 183 | 184 | self.instance = try self.vkb.createInstance(&create_info, null); 185 | 186 | self.vki = try InstanceDispatch.load(self.instance, vk_proc); 187 | } 188 | 189 | fn populateDebugMessengerCreateInfo(create_info: *vk.DebugUtilsMessengerCreateInfoEXT) void { 190 | create_info.* = .{ 191 | .flags = .{}, 192 | .message_severity = .{ 193 | .verbose_bit_ext = true, 194 | .warning_bit_ext = true, 195 | .error_bit_ext = true, 196 | }, 197 | .message_type = .{ 198 | .general_bit_ext = true, 199 | .validation_bit_ext = true, 200 | .performance_bit_ext = true, 201 | }, 202 | .pfn_user_callback = debugCallback, 203 | .p_user_data = null, 204 | }; 205 | } 206 | 207 | fn setupDebugMessenger(self: *Self) !void { 208 | if (!enable_validation_layers) return; 209 | 210 | var create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 211 | populateDebugMessengerCreateInfo(&create_info); 212 | 213 | self.debug_messenger = try self.vki.createDebugUtilsMessengerEXT(self.instance, &create_info, null); 214 | } 215 | 216 | fn createSurface(self: *Self) !void { 217 | if ((try glfw.createWindowSurface(self.instance, self.window.?, null, &self.surface)) != @enumToInt(vk.Result.success)) { 218 | return error.SurfaceInitFailed; 219 | } 220 | } 221 | 222 | fn pickPhysicalDevice(self: *Self) !void { 223 | var device_count: u32 = undefined; 224 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, null); 225 | 226 | if (device_count == 0) { 227 | return error.NoGPUsSupportVulkan; 228 | } 229 | 230 | const devices = try self.allocator.alloc(vk.PhysicalDevice, device_count); 231 | defer self.allocator.free(devices); 232 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, devices.ptr); 233 | 234 | for (devices) |device| { 235 | if (try self.isDeviceSuitable(device)) { 236 | self.physical_device = device; 237 | break; 238 | } 239 | } 240 | 241 | if (self.physical_device == .null_handle) { 242 | return error.NoSuitableDevice; 243 | } 244 | } 245 | 246 | fn createLogicalDevice(self: *Self) !void { 247 | const indices = try self.findQueueFamilies(self.physical_device); 248 | const queue_priority = [_]f32{1}; 249 | 250 | var queue_create_info = [_]vk.DeviceQueueCreateInfo{ 251 | .{ 252 | .flags = .{}, 253 | .queue_family_index = indices.graphics_family.?, 254 | .queue_count = 1, 255 | .p_queue_priorities = &queue_priority, 256 | }, 257 | .{ 258 | .flags = .{}, 259 | .queue_family_index = indices.present_family.?, 260 | .queue_count = 1, 261 | .p_queue_priorities = &queue_priority, 262 | }, 263 | }; 264 | 265 | var create_info = vk.DeviceCreateInfo{ 266 | .flags = .{}, 267 | .queue_create_info_count = queue_create_info.len, 268 | .p_queue_create_infos = &queue_create_info, 269 | .enabled_layer_count = 0, 270 | .pp_enabled_layer_names = undefined, 271 | .enabled_extension_count = device_extensions.len, 272 | .pp_enabled_extension_names = &device_extensions, 273 | .p_enabled_features = null, 274 | }; 275 | 276 | if (enable_validation_layers) { 277 | create_info.enabled_layer_count = validation_layers.len; 278 | create_info.pp_enabled_layer_names = &validation_layers; 279 | } 280 | 281 | self.device = try self.vki.createDevice(self.physical_device, &create_info, null); 282 | 283 | self.vkd = try DeviceDispatch.load(self.device, self.vki.dispatch.vkGetDeviceProcAddr); 284 | 285 | self.graphics_queue = self.vkd.getDeviceQueue(self.device, indices.graphics_family.?, 0); 286 | self.present_queue = self.vkd.getDeviceQueue(self.device, indices.present_family.?, 0); 287 | } 288 | 289 | fn createSwapChain(self: *Self) !void { 290 | const swap_chain_support = try self.querySwapChainSupport(self.physical_device); 291 | defer swap_chain_support.deinit(); 292 | 293 | const surface_format: vk.SurfaceFormatKHR = chooseSwapSurfaceFormat(swap_chain_support.formats.?); 294 | const present_mode: vk.PresentModeKHR = chooseSwapPresentMode(swap_chain_support.present_modes.?); 295 | const extent: vk.Extent2D = try self.chooseSwapExtent(swap_chain_support.capabilities); 296 | 297 | var image_count = swap_chain_support.capabilities.min_image_count + 1; 298 | if (swap_chain_support.capabilities.max_image_count > 0) { 299 | image_count = std.math.min(image_count, swap_chain_support.capabilities.max_image_count); 300 | } 301 | 302 | const indices = try self.findQueueFamilies(self.physical_device); 303 | const queue_family_indices = [_]u32{ indices.graphics_family.?, indices.present_family.? }; 304 | const sharing_mode: vk.SharingMode = if (indices.graphics_family.? != indices.present_family.?) 305 | .concurrent 306 | else 307 | .exclusive; 308 | 309 | self.swap_chain = try self.vkd.createSwapchainKHR(self.device, &.{ 310 | .flags = .{}, 311 | .surface = self.surface, 312 | .min_image_count = image_count, 313 | .image_format = surface_format.format, 314 | .image_color_space = surface_format.color_space, 315 | .image_extent = extent, 316 | .image_array_layers = 1, 317 | .image_usage = .{ .color_attachment_bit = true }, 318 | .image_sharing_mode = sharing_mode, 319 | .queue_family_index_count = queue_family_indices.len, 320 | .p_queue_family_indices = &queue_family_indices, 321 | .pre_transform = swap_chain_support.capabilities.current_transform, 322 | .composite_alpha = .{ .opaque_bit_khr = true }, 323 | .present_mode = present_mode, 324 | .clipped = vk.TRUE, 325 | .old_swapchain = .null_handle, 326 | }, null); 327 | 328 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, null); 329 | self.swap_chain_images = try self.allocator.alloc(vk.Image, image_count); 330 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, self.swap_chain_images.?.ptr); 331 | 332 | self.swap_chain_image_format = surface_format.format; 333 | self.swap_chain_extent = extent; 334 | } 335 | 336 | fn chooseSwapSurfaceFormat(available_formats: []vk.SurfaceFormatKHR) vk.SurfaceFormatKHR { 337 | for (available_formats) |available_format| { 338 | if (available_format.format == .b8g8r8a8_srgb and available_format.color_space == .srgb_nonlinear_khr) { 339 | return available_format; 340 | } 341 | } 342 | 343 | return available_formats[0]; 344 | } 345 | 346 | fn chooseSwapPresentMode(available_present_modes: []vk.PresentModeKHR) vk.PresentModeKHR { 347 | for (available_present_modes) |available_present_mode| { 348 | if (available_present_mode == .mailbox_khr) { 349 | return available_present_mode; 350 | } 351 | } 352 | 353 | return .fifo_khr; 354 | } 355 | 356 | fn chooseSwapExtent(self: *Self, capabilities: vk.SurfaceCapabilitiesKHR) !vk.Extent2D { 357 | if (capabilities.current_extent.width != 0xFFFF_FFFF) { 358 | return capabilities.current_extent; 359 | } else { 360 | const window_size = try self.window.?.getFramebufferSize(); 361 | 362 | return vk.Extent2D{ 363 | .width = std.math.clamp(window_size.width, capabilities.min_image_extent.width, capabilities.max_image_extent.width), 364 | .height = std.math.clamp(window_size.height, capabilities.min_image_extent.height, capabilities.max_image_extent.height), 365 | }; 366 | } 367 | } 368 | 369 | fn querySwapChainSupport(self: *Self, device: vk.PhysicalDevice) !SwapChainSupportDetails { 370 | var details = SwapChainSupportDetails.init(self.allocator); 371 | 372 | details.capabilities = try self.vki.getPhysicalDeviceSurfaceCapabilitiesKHR(device, self.surface); 373 | 374 | var format_count: u32 = undefined; 375 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, null); 376 | 377 | if (format_count != 0) { 378 | details.formats = try details.allocator.alloc(vk.SurfaceFormatKHR, format_count); 379 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, details.formats.?.ptr); 380 | } 381 | 382 | var present_mode_count: u32 = undefined; 383 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, null); 384 | 385 | if (present_mode_count != 0) { 386 | details.present_modes = try details.allocator.alloc(vk.PresentModeKHR, present_mode_count); 387 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, details.present_modes.?.ptr); 388 | } 389 | 390 | return details; 391 | } 392 | 393 | fn isDeviceSuitable(self: *Self, device: vk.PhysicalDevice) !bool { 394 | const indices = try self.findQueueFamilies(device); 395 | 396 | const extensions_supported = try self.checkDeviceExtensionSupport(device); 397 | 398 | var swap_chain_adequate = false; 399 | if (extensions_supported) { 400 | const swap_chain_support = try self.querySwapChainSupport(device); 401 | defer swap_chain_support.deinit(); 402 | 403 | swap_chain_adequate = swap_chain_support.formats != null and swap_chain_support.present_modes != null; 404 | } 405 | 406 | return indices.isComplete() and extensions_supported and swap_chain_adequate; 407 | } 408 | 409 | fn checkDeviceExtensionSupport(self: *Self, device: vk.PhysicalDevice) !bool { 410 | var extension_count: u32 = undefined; 411 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, null); 412 | 413 | const available_extensions = try self.allocator.alloc(vk.ExtensionProperties, extension_count); 414 | defer self.allocator.free(available_extensions); 415 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, available_extensions.ptr); 416 | 417 | const required_extensions = device_extensions[0..]; 418 | 419 | for (required_extensions) |required_extension| { 420 | for (available_extensions) |available_extension| { 421 | const len = std.mem.indexOfScalar(u8, &available_extension.extension_name, 0).?; 422 | const available_extension_name = available_extension.extension_name[0..len]; 423 | if (std.mem.eql(u8, std.mem.span(required_extension), available_extension_name)) { 424 | break; 425 | } 426 | } else { 427 | return false; 428 | } 429 | } 430 | 431 | return true; 432 | } 433 | 434 | fn findQueueFamilies(self: *Self, device: vk.PhysicalDevice) !QueueFamilyIndices { 435 | var indices: QueueFamilyIndices = .{}; 436 | 437 | var queue_family_count: u32 = 0; 438 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, null); 439 | 440 | const queue_families = try self.allocator.alloc(vk.QueueFamilyProperties, queue_family_count); 441 | defer self.allocator.free(queue_families); 442 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.ptr); 443 | 444 | for (queue_families) |queue_family, i| { 445 | if (indices.graphics_family == null and queue_family.queue_flags.graphics_bit) { 446 | indices.graphics_family = @intCast(u32, i); 447 | } else if (indices.present_family == null and (try self.vki.getPhysicalDeviceSurfaceSupportKHR(device, @intCast(u32, i), self.surface)) == vk.TRUE) { 448 | indices.present_family = @intCast(u32, i); 449 | } 450 | 451 | if (indices.isComplete()) { 452 | break; 453 | } 454 | } 455 | 456 | return indices; 457 | } 458 | 459 | fn getRequiredExtensions(allocator: Allocator) !std.ArrayListAligned([*:0]const u8, null) { 460 | var extensions = std.ArrayList([*:0]const u8).init(allocator); 461 | try extensions.appendSlice(try glfw.getRequiredInstanceExtensions()); 462 | 463 | if (enable_validation_layers) { 464 | try extensions.append(vk.extension_info.ext_debug_utils.name); 465 | } 466 | 467 | return extensions; 468 | } 469 | 470 | fn checkValidationLayerSupport(self: *Self) !bool { 471 | var layer_count: u32 = undefined; 472 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, null); 473 | 474 | var available_layers = try self.allocator.alloc(vk.LayerProperties, layer_count); 475 | defer self.allocator.free(available_layers); 476 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr); 477 | 478 | for (validation_layers) |layer_name| { 479 | var layer_found: bool = false; 480 | 481 | for (available_layers) |layer_properties| { 482 | const available_len = std.mem.indexOfScalar(u8, &layer_properties.layer_name, 0).?; 483 | const available_layer_name = layer_properties.layer_name[0..available_len]; 484 | if (std.mem.eql(u8, std.mem.span(layer_name), available_layer_name)) { 485 | layer_found = true; 486 | break; 487 | } 488 | } 489 | 490 | if (!layer_found) { 491 | return false; 492 | } 493 | } 494 | 495 | return true; 496 | } 497 | 498 | fn debugCallback(_: vk.DebugUtilsMessageSeverityFlagsEXT, _: vk.DebugUtilsMessageTypeFlagsEXT, p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque) callconv(vk.vulkan_call_conv) vk.Bool32 { 499 | if (p_callback_data != null) { 500 | std.log.debug("validation layer: {s}", .{p_callback_data.?.p_message}); 501 | } 502 | 503 | return vk.FALSE; 504 | } 505 | }; 506 | 507 | pub fn main() void { 508 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 509 | defer { 510 | const leaked = gpa.deinit(); 511 | if (leaked) std.log.err("MemLeak", .{}); 512 | } 513 | const allocator = gpa.allocator(); 514 | 515 | var app = HelloTriangleApplication.init(allocator); 516 | defer app.deinit(); 517 | app.run() catch |err| { 518 | std.log.err("application exited with error: {any}", .{err}); 519 | return; 520 | }; 521 | } 522 | -------------------------------------------------------------------------------- /src/07_image_views.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | const glfw = @import("glfw"); 6 | const vk = @import("vulkan"); 7 | 8 | const WIDTH: u32 = 800; 9 | const HEIGHT: u32 = 600; 10 | 11 | const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; 12 | 13 | const device_extensions = [_][*:0]const u8{vk.extension_info.khr_swapchain.name}; 14 | 15 | const enable_validation_layers: bool = switch (builtin.mode) { 16 | .Debug, .ReleaseSafe => true, 17 | else => false, 18 | }; 19 | 20 | const BaseDispatch = vk.BaseWrapper(.{ 21 | .createInstance = true, 22 | .enumerateInstanceLayerProperties = true, 23 | }); 24 | 25 | const InstanceDispatch = vk.InstanceWrapper(.{ 26 | .createDebugUtilsMessengerEXT = enable_validation_layers, 27 | .createDevice = true, 28 | .destroyDebugUtilsMessengerEXT = enable_validation_layers, 29 | .destroyInstance = true, 30 | .destroySurfaceKHR = true, 31 | .enumerateDeviceExtensionProperties = true, 32 | .enumeratePhysicalDevices = true, 33 | .getDeviceProcAddr = true, 34 | .getPhysicalDeviceQueueFamilyProperties = true, 35 | .getPhysicalDeviceSurfaceCapabilitiesKHR = true, 36 | .getPhysicalDeviceSurfaceFormatsKHR = true, 37 | .getPhysicalDeviceSurfacePresentModesKHR = true, 38 | .getPhysicalDeviceSurfaceSupportKHR = true, 39 | }); 40 | 41 | const DeviceDispatch = vk.DeviceWrapper(.{ 42 | .createImageView = true, 43 | .createSwapchainKHR = true, 44 | .destroyDevice = true, 45 | .destroyImageView = true, 46 | .destroySwapchainKHR = true, 47 | .getDeviceQueue = true, 48 | .getSwapchainImagesKHR = true, 49 | }); 50 | 51 | const QueueFamilyIndices = struct { 52 | graphics_family: ?u32 = null, 53 | present_family: ?u32 = null, 54 | 55 | fn isComplete(self: *const QueueFamilyIndices) bool { 56 | return self.graphics_family != null and self.present_family != null; 57 | } 58 | }; 59 | 60 | pub const SwapChainSupportDetails = struct { 61 | allocator: Allocator, 62 | capabilities: vk.SurfaceCapabilitiesKHR = undefined, 63 | formats: ?[]vk.SurfaceFormatKHR = null, 64 | present_modes: ?[]vk.PresentModeKHR = null, 65 | 66 | pub fn init(allocator: Allocator) SwapChainSupportDetails { 67 | return .{ .allocator = allocator }; 68 | } 69 | 70 | pub fn deinit(self: SwapChainSupportDetails) void { 71 | if (self.formats != null) self.allocator.free(self.formats.?); 72 | if (self.present_modes != null) self.allocator.free(self.present_modes.?); 73 | } 74 | }; 75 | 76 | const HelloTriangleApplication = struct { 77 | const Self = @This(); 78 | allocator: Allocator, 79 | 80 | window: ?glfw.Window = null, 81 | 82 | vkb: BaseDispatch = undefined, 83 | vki: InstanceDispatch = undefined, 84 | vkd: DeviceDispatch = undefined, 85 | 86 | instance: vk.Instance = .null_handle, 87 | debug_messenger: vk.DebugUtilsMessengerEXT = .null_handle, 88 | surface: vk.SurfaceKHR = .null_handle, 89 | 90 | physical_device: vk.PhysicalDevice = .null_handle, 91 | device: vk.Device = .null_handle, 92 | 93 | graphics_queue: vk.Queue = .null_handle, 94 | present_queue: vk.Queue = .null_handle, 95 | 96 | swap_chain: vk.SwapchainKHR = .null_handle, 97 | swap_chain_images: ?[]vk.Image = null, 98 | swap_chain_image_format: vk.Format = .@"undefined", 99 | swap_chain_extent: vk.Extent2D = .{ .width = 0, .height = 0 }, 100 | swap_chain_image_views: ?[]vk.ImageView = null, 101 | 102 | pub fn init(allocator: Allocator) Self { 103 | return Self{ .allocator = allocator }; 104 | } 105 | 106 | pub fn run(self: *Self) !void { 107 | try self.initWindow(); 108 | try self.initVulkan(); 109 | try self.mainLoop(); 110 | } 111 | 112 | fn initWindow(self: *Self) !void { 113 | try glfw.init(.{}); 114 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 115 | .client_api = .no_api, 116 | .resizable = false, 117 | }); 118 | } 119 | 120 | fn initVulkan(self: *Self) !void { 121 | try self.createInstance(); 122 | try self.setupDebugMessenger(); 123 | try self.createSurface(); 124 | try self.pickPhysicalDevice(); 125 | try self.createLogicalDevice(); 126 | try self.createSwapChain(); 127 | try self.createImageViews(); 128 | } 129 | 130 | fn mainLoop(self: *Self) !void { 131 | while (!self.window.?.shouldClose()) { 132 | try glfw.pollEvents(); 133 | } 134 | } 135 | 136 | pub fn deinit(self: *Self) void { 137 | if (self.swap_chain_image_views != null) { 138 | for (self.swap_chain_image_views.?) |image_view| { 139 | self.vkd.destroyImageView(self.device, image_view, null); 140 | } 141 | self.allocator.free(self.swap_chain_image_views.?); 142 | } 143 | 144 | if (self.swap_chain_images != null) self.allocator.free(self.swap_chain_images.?); 145 | if (self.swap_chain != .null_handle) self.vkd.destroySwapchainKHR(self.device, self.swap_chain, null); 146 | if (self.device != .null_handle) self.vkd.destroyDevice(self.device, null); 147 | 148 | if (enable_validation_layers and self.debug_messenger != .null_handle) self.vki.destroyDebugUtilsMessengerEXT(self.instance, self.debug_messenger, null); 149 | 150 | if (self.surface != .null_handle) self.vki.destroySurfaceKHR(self.instance, self.surface, null); 151 | if (self.instance != .null_handle) self.vki.destroyInstance(self.instance, null); 152 | 153 | if (self.window != null) self.window.?.destroy(); 154 | 155 | glfw.terminate(); 156 | } 157 | 158 | fn createInstance(self: *Self) !void { 159 | const vk_proc = @ptrCast(*const fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, &glfw.getInstanceProcAddress); 160 | self.vkb = try BaseDispatch.load(vk_proc); 161 | 162 | if (enable_validation_layers and !try self.checkValidationLayerSupport()) { 163 | return error.MissingValidationLayer; 164 | } 165 | 166 | const app_info = vk.ApplicationInfo{ 167 | .p_application_name = "Hello Triangle", 168 | .application_version = vk.makeApiVersion(1, 0, 0, 0), 169 | .p_engine_name = "No Engine", 170 | .engine_version = vk.makeApiVersion(1, 0, 0, 0), 171 | .api_version = vk.API_VERSION_1_2, 172 | }; 173 | 174 | const extensions = try getRequiredExtensions(self.allocator); 175 | defer extensions.deinit(); 176 | 177 | var create_info = vk.InstanceCreateInfo{ 178 | .flags = .{}, 179 | .p_application_info = &app_info, 180 | .enabled_layer_count = 0, 181 | .pp_enabled_layer_names = undefined, 182 | .enabled_extension_count = @intCast(u32, extensions.items.len), 183 | .pp_enabled_extension_names = extensions.items.ptr, 184 | }; 185 | 186 | if (enable_validation_layers) { 187 | create_info.enabled_layer_count = validation_layers.len; 188 | create_info.pp_enabled_layer_names = &validation_layers; 189 | 190 | var debug_create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 191 | populateDebugMessengerCreateInfo(&debug_create_info); 192 | create_info.p_next = &debug_create_info; 193 | } 194 | 195 | self.instance = try self.vkb.createInstance(&create_info, null); 196 | 197 | self.vki = try InstanceDispatch.load(self.instance, vk_proc); 198 | } 199 | 200 | fn populateDebugMessengerCreateInfo(create_info: *vk.DebugUtilsMessengerCreateInfoEXT) void { 201 | create_info.* = .{ 202 | .flags = .{}, 203 | .message_severity = .{ 204 | .verbose_bit_ext = true, 205 | .warning_bit_ext = true, 206 | .error_bit_ext = true, 207 | }, 208 | .message_type = .{ 209 | .general_bit_ext = true, 210 | .validation_bit_ext = true, 211 | .performance_bit_ext = true, 212 | }, 213 | .pfn_user_callback = debugCallback, 214 | .p_user_data = null, 215 | }; 216 | } 217 | 218 | fn setupDebugMessenger(self: *Self) !void { 219 | if (!enable_validation_layers) return; 220 | 221 | var create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 222 | populateDebugMessengerCreateInfo(&create_info); 223 | 224 | self.debug_messenger = try self.vki.createDebugUtilsMessengerEXT(self.instance, &create_info, null); 225 | } 226 | 227 | fn createSurface(self: *Self) !void { 228 | if ((try glfw.createWindowSurface(self.instance, self.window.?, null, &self.surface)) != @enumToInt(vk.Result.success)) { 229 | return error.SurfaceInitFailed; 230 | } 231 | } 232 | 233 | fn pickPhysicalDevice(self: *Self) !void { 234 | var device_count: u32 = undefined; 235 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, null); 236 | 237 | if (device_count == 0) { 238 | return error.NoGPUsSupportVulkan; 239 | } 240 | 241 | const devices = try self.allocator.alloc(vk.PhysicalDevice, device_count); 242 | defer self.allocator.free(devices); 243 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, devices.ptr); 244 | 245 | for (devices) |device| { 246 | if (try self.isDeviceSuitable(device)) { 247 | self.physical_device = device; 248 | break; 249 | } 250 | } 251 | 252 | if (self.physical_device == .null_handle) { 253 | return error.NoSuitableDevice; 254 | } 255 | } 256 | 257 | fn createLogicalDevice(self: *Self) !void { 258 | const indices = try self.findQueueFamilies(self.physical_device); 259 | const queue_priority = [_]f32{1}; 260 | 261 | var queue_create_info = [_]vk.DeviceQueueCreateInfo{ 262 | .{ 263 | .flags = .{}, 264 | .queue_family_index = indices.graphics_family.?, 265 | .queue_count = 1, 266 | .p_queue_priorities = &queue_priority, 267 | }, 268 | .{ 269 | .flags = .{}, 270 | .queue_family_index = indices.present_family.?, 271 | .queue_count = 1, 272 | .p_queue_priorities = &queue_priority, 273 | }, 274 | }; 275 | 276 | var create_info = vk.DeviceCreateInfo{ 277 | .flags = .{}, 278 | .queue_create_info_count = queue_create_info.len, 279 | .p_queue_create_infos = &queue_create_info, 280 | .enabled_layer_count = 0, 281 | .pp_enabled_layer_names = undefined, 282 | .enabled_extension_count = device_extensions.len, 283 | .pp_enabled_extension_names = &device_extensions, 284 | .p_enabled_features = null, 285 | }; 286 | 287 | if (enable_validation_layers) { 288 | create_info.enabled_layer_count = validation_layers.len; 289 | create_info.pp_enabled_layer_names = &validation_layers; 290 | } 291 | 292 | self.device = try self.vki.createDevice(self.physical_device, &create_info, null); 293 | 294 | self.vkd = try DeviceDispatch.load(self.device, self.vki.dispatch.vkGetDeviceProcAddr); 295 | 296 | self.graphics_queue = self.vkd.getDeviceQueue(self.device, indices.graphics_family.?, 0); 297 | self.present_queue = self.vkd.getDeviceQueue(self.device, indices.present_family.?, 0); 298 | } 299 | 300 | fn createSwapChain(self: *Self) !void { 301 | const swap_chain_support = try self.querySwapChainSupport(self.physical_device); 302 | defer swap_chain_support.deinit(); 303 | 304 | const surface_format: vk.SurfaceFormatKHR = chooseSwapSurfaceFormat(swap_chain_support.formats.?); 305 | const present_mode: vk.PresentModeKHR = chooseSwapPresentMode(swap_chain_support.present_modes.?); 306 | const extent: vk.Extent2D = try self.chooseSwapExtent(swap_chain_support.capabilities); 307 | 308 | var image_count = swap_chain_support.capabilities.min_image_count + 1; 309 | if (swap_chain_support.capabilities.max_image_count > 0) { 310 | image_count = std.math.min(image_count, swap_chain_support.capabilities.max_image_count); 311 | } 312 | 313 | const indices = try self.findQueueFamilies(self.physical_device); 314 | const queue_family_indices = [_]u32{ indices.graphics_family.?, indices.present_family.? }; 315 | const sharing_mode: vk.SharingMode = if (indices.graphics_family.? != indices.present_family.?) 316 | .concurrent 317 | else 318 | .exclusive; 319 | 320 | self.swap_chain = try self.vkd.createSwapchainKHR(self.device, &.{ 321 | .flags = .{}, 322 | .surface = self.surface, 323 | .min_image_count = image_count, 324 | .image_format = surface_format.format, 325 | .image_color_space = surface_format.color_space, 326 | .image_extent = extent, 327 | .image_array_layers = 1, 328 | .image_usage = .{ .color_attachment_bit = true }, 329 | .image_sharing_mode = sharing_mode, 330 | .queue_family_index_count = queue_family_indices.len, 331 | .p_queue_family_indices = &queue_family_indices, 332 | .pre_transform = swap_chain_support.capabilities.current_transform, 333 | .composite_alpha = .{ .opaque_bit_khr = true }, 334 | .present_mode = present_mode, 335 | .clipped = vk.TRUE, 336 | .old_swapchain = .null_handle, 337 | }, null); 338 | 339 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, null); 340 | self.swap_chain_images = try self.allocator.alloc(vk.Image, image_count); 341 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, self.swap_chain_images.?.ptr); 342 | 343 | self.swap_chain_image_format = surface_format.format; 344 | self.swap_chain_extent = extent; 345 | } 346 | 347 | fn createImageViews(self: *Self) !void { 348 | self.swap_chain_image_views = try self.allocator.alloc(vk.ImageView, self.swap_chain_images.?.len); 349 | 350 | for (self.swap_chain_images.?) |image, i| { 351 | self.swap_chain_image_views.?[i] = try self.vkd.createImageView(self.device, &.{ 352 | .flags = .{}, 353 | .image = image, 354 | .view_type = .@"2d", 355 | .format = self.swap_chain_image_format, 356 | .components = .{ .r = .identity, .g = .identity, .b = .identity, .a = .identity }, 357 | .subresource_range = .{ 358 | .aspect_mask = .{ .color_bit = true }, 359 | .base_mip_level = 0, 360 | .level_count = 1, 361 | .base_array_layer = 0, 362 | .layer_count = 1, 363 | }, 364 | }, null); 365 | } 366 | } 367 | 368 | fn chooseSwapSurfaceFormat(available_formats: []vk.SurfaceFormatKHR) vk.SurfaceFormatKHR { 369 | for (available_formats) |available_format| { 370 | if (available_format.format == .b8g8r8a8_srgb and available_format.color_space == .srgb_nonlinear_khr) { 371 | return available_format; 372 | } 373 | } 374 | 375 | return available_formats[0]; 376 | } 377 | 378 | fn chooseSwapPresentMode(available_present_modes: []vk.PresentModeKHR) vk.PresentModeKHR { 379 | for (available_present_modes) |available_present_mode| { 380 | if (available_present_mode == .mailbox_khr) { 381 | return available_present_mode; 382 | } 383 | } 384 | 385 | return .fifo_khr; 386 | } 387 | 388 | fn chooseSwapExtent(self: *Self, capabilities: vk.SurfaceCapabilitiesKHR) !vk.Extent2D { 389 | if (capabilities.current_extent.width != 0xFFFF_FFFF) { 390 | return capabilities.current_extent; 391 | } else { 392 | const window_size = try self.window.?.getFramebufferSize(); 393 | 394 | return vk.Extent2D{ 395 | .width = std.math.clamp(window_size.width, capabilities.min_image_extent.width, capabilities.max_image_extent.width), 396 | .height = std.math.clamp(window_size.height, capabilities.min_image_extent.height, capabilities.max_image_extent.height), 397 | }; 398 | } 399 | } 400 | 401 | fn querySwapChainSupport(self: *Self, device: vk.PhysicalDevice) !SwapChainSupportDetails { 402 | var details = SwapChainSupportDetails.init(self.allocator); 403 | 404 | details.capabilities = try self.vki.getPhysicalDeviceSurfaceCapabilitiesKHR(device, self.surface); 405 | 406 | var format_count: u32 = undefined; 407 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, null); 408 | 409 | if (format_count != 0) { 410 | details.formats = try details.allocator.alloc(vk.SurfaceFormatKHR, format_count); 411 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, details.formats.?.ptr); 412 | } 413 | 414 | var present_mode_count: u32 = undefined; 415 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, null); 416 | 417 | if (present_mode_count != 0) { 418 | details.present_modes = try details.allocator.alloc(vk.PresentModeKHR, present_mode_count); 419 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, details.present_modes.?.ptr); 420 | } 421 | 422 | return details; 423 | } 424 | 425 | fn isDeviceSuitable(self: *Self, device: vk.PhysicalDevice) !bool { 426 | const indices = try self.findQueueFamilies(device); 427 | 428 | const extensions_supported = try self.checkDeviceExtensionSupport(device); 429 | 430 | var swap_chain_adequate = false; 431 | if (extensions_supported) { 432 | const swap_chain_support = try self.querySwapChainSupport(device); 433 | defer swap_chain_support.deinit(); 434 | 435 | swap_chain_adequate = swap_chain_support.formats != null and swap_chain_support.present_modes != null; 436 | } 437 | 438 | return indices.isComplete() and extensions_supported and swap_chain_adequate; 439 | } 440 | 441 | fn checkDeviceExtensionSupport(self: *Self, device: vk.PhysicalDevice) !bool { 442 | var extension_count: u32 = undefined; 443 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, null); 444 | 445 | const available_extensions = try self.allocator.alloc(vk.ExtensionProperties, extension_count); 446 | defer self.allocator.free(available_extensions); 447 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, available_extensions.ptr); 448 | 449 | const required_extensions = device_extensions[0..]; 450 | 451 | for (required_extensions) |required_extension| { 452 | for (available_extensions) |available_extension| { 453 | const len = std.mem.indexOfScalar(u8, &available_extension.extension_name, 0).?; 454 | const available_extension_name = available_extension.extension_name[0..len]; 455 | if (std.mem.eql(u8, std.mem.span(required_extension), available_extension_name)) { 456 | break; 457 | } 458 | } else { 459 | return false; 460 | } 461 | } 462 | 463 | return true; 464 | } 465 | 466 | fn findQueueFamilies(self: *Self, device: vk.PhysicalDevice) !QueueFamilyIndices { 467 | var indices: QueueFamilyIndices = .{}; 468 | 469 | var queue_family_count: u32 = 0; 470 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, null); 471 | 472 | const queue_families = try self.allocator.alloc(vk.QueueFamilyProperties, queue_family_count); 473 | defer self.allocator.free(queue_families); 474 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.ptr); 475 | 476 | for (queue_families) |queue_family, i| { 477 | if (indices.graphics_family == null and queue_family.queue_flags.graphics_bit) { 478 | indices.graphics_family = @intCast(u32, i); 479 | } else if (indices.present_family == null and (try self.vki.getPhysicalDeviceSurfaceSupportKHR(device, @intCast(u32, i), self.surface)) == vk.TRUE) { 480 | indices.present_family = @intCast(u32, i); 481 | } 482 | 483 | if (indices.isComplete()) { 484 | break; 485 | } 486 | } 487 | 488 | return indices; 489 | } 490 | 491 | fn getRequiredExtensions(allocator: Allocator) !std.ArrayListAligned([*:0]const u8, null) { 492 | var extensions = std.ArrayList([*:0]const u8).init(allocator); 493 | try extensions.appendSlice(try glfw.getRequiredInstanceExtensions()); 494 | 495 | if (enable_validation_layers) { 496 | try extensions.append(vk.extension_info.ext_debug_utils.name); 497 | } 498 | 499 | return extensions; 500 | } 501 | 502 | fn checkValidationLayerSupport(self: *Self) !bool { 503 | var layer_count: u32 = undefined; 504 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, null); 505 | 506 | var available_layers = try self.allocator.alloc(vk.LayerProperties, layer_count); 507 | defer self.allocator.free(available_layers); 508 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr); 509 | 510 | for (validation_layers) |layer_name| { 511 | var layer_found: bool = false; 512 | 513 | for (available_layers) |layer_properties| { 514 | const available_len = std.mem.indexOfScalar(u8, &layer_properties.layer_name, 0).?; 515 | const available_layer_name = layer_properties.layer_name[0..available_len]; 516 | if (std.mem.eql(u8, std.mem.span(layer_name), available_layer_name)) { 517 | layer_found = true; 518 | break; 519 | } 520 | } 521 | 522 | if (!layer_found) { 523 | return false; 524 | } 525 | } 526 | 527 | return true; 528 | } 529 | 530 | fn debugCallback(_: vk.DebugUtilsMessageSeverityFlagsEXT, _: vk.DebugUtilsMessageTypeFlagsEXT, p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque) callconv(vk.vulkan_call_conv) vk.Bool32 { 531 | if (p_callback_data != null) { 532 | std.log.debug("validation layer: {s}", .{p_callback_data.?.p_message}); 533 | } 534 | 535 | return vk.FALSE; 536 | } 537 | }; 538 | 539 | pub fn main() void { 540 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 541 | defer { 542 | const leaked = gpa.deinit(); 543 | if (leaked) std.log.err("MemLeak", .{}); 544 | } 545 | const allocator = gpa.allocator(); 546 | 547 | var app = HelloTriangleApplication.init(allocator); 548 | defer app.deinit(); 549 | app.run() catch |err| { 550 | std.log.err("application exited with error: {any}", .{err}); 551 | return; 552 | }; 553 | } 554 | -------------------------------------------------------------------------------- /src/08_graphics_pipeline.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | const glfw = @import("glfw"); 6 | const vk = @import("vulkan"); 7 | 8 | const WIDTH: u32 = 800; 9 | const HEIGHT: u32 = 600; 10 | 11 | const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; 12 | 13 | const device_extensions = [_][*:0]const u8{vk.extension_info.khr_swapchain.name}; 14 | 15 | const enable_validation_layers: bool = switch (builtin.mode) { 16 | .Debug, .ReleaseSafe => true, 17 | else => false, 18 | }; 19 | 20 | const BaseDispatch = vk.BaseWrapper(.{ 21 | .createInstance = true, 22 | .enumerateInstanceLayerProperties = true, 23 | }); 24 | 25 | const InstanceDispatch = vk.InstanceWrapper(.{ 26 | .createDebugUtilsMessengerEXT = enable_validation_layers, 27 | .createDevice = true, 28 | .destroyDebugUtilsMessengerEXT = enable_validation_layers, 29 | .destroyInstance = true, 30 | .destroySurfaceKHR = true, 31 | .enumerateDeviceExtensionProperties = true, 32 | .enumeratePhysicalDevices = true, 33 | .getDeviceProcAddr = true, 34 | .getPhysicalDeviceQueueFamilyProperties = true, 35 | .getPhysicalDeviceSurfaceCapabilitiesKHR = true, 36 | .getPhysicalDeviceSurfaceFormatsKHR = true, 37 | .getPhysicalDeviceSurfacePresentModesKHR = true, 38 | .getPhysicalDeviceSurfaceSupportKHR = true, 39 | }); 40 | 41 | const DeviceDispatch = vk.DeviceWrapper(.{ 42 | .createImageView = true, 43 | .createSwapchainKHR = true, 44 | .destroyDevice = true, 45 | .destroyImageView = true, 46 | .destroySwapchainKHR = true, 47 | .getDeviceQueue = true, 48 | .getSwapchainImagesKHR = true, 49 | }); 50 | 51 | const QueueFamilyIndices = struct { 52 | graphics_family: ?u32 = null, 53 | present_family: ?u32 = null, 54 | 55 | fn isComplete(self: *const QueueFamilyIndices) bool { 56 | return self.graphics_family != null and self.present_family != null; 57 | } 58 | }; 59 | 60 | pub const SwapChainSupportDetails = struct { 61 | allocator: Allocator, 62 | capabilities: vk.SurfaceCapabilitiesKHR = undefined, 63 | formats: ?[]vk.SurfaceFormatKHR = null, 64 | present_modes: ?[]vk.PresentModeKHR = null, 65 | 66 | pub fn init(allocator: Allocator) SwapChainSupportDetails { 67 | return .{ .allocator = allocator }; 68 | } 69 | 70 | pub fn deinit(self: SwapChainSupportDetails) void { 71 | if (self.formats != null) self.allocator.free(self.formats.?); 72 | if (self.present_modes != null) self.allocator.free(self.present_modes.?); 73 | } 74 | }; 75 | 76 | const HelloTriangleApplication = struct { 77 | const Self = @This(); 78 | allocator: Allocator, 79 | 80 | window: ?glfw.Window = null, 81 | 82 | vkb: BaseDispatch = undefined, 83 | vki: InstanceDispatch = undefined, 84 | vkd: DeviceDispatch = undefined, 85 | 86 | instance: vk.Instance = .null_handle, 87 | debug_messenger: vk.DebugUtilsMessengerEXT = .null_handle, 88 | surface: vk.SurfaceKHR = .null_handle, 89 | 90 | physical_device: vk.PhysicalDevice = .null_handle, 91 | device: vk.Device = .null_handle, 92 | 93 | graphics_queue: vk.Queue = .null_handle, 94 | present_queue: vk.Queue = .null_handle, 95 | 96 | swap_chain: vk.SwapchainKHR = .null_handle, 97 | swap_chain_images: ?[]vk.Image = null, 98 | swap_chain_image_format: vk.Format = .@"undefined", 99 | swap_chain_extent: vk.Extent2D = .{ .width = 0, .height = 0 }, 100 | swap_chain_image_views: ?[]vk.ImageView = null, 101 | 102 | pub fn init(allocator: Allocator) Self { 103 | return Self{ .allocator = allocator }; 104 | } 105 | 106 | pub fn run(self: *Self) !void { 107 | try self.initWindow(); 108 | try self.initVulkan(); 109 | try self.mainLoop(); 110 | } 111 | 112 | fn initWindow(self: *Self) !void { 113 | try glfw.init(.{}); 114 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 115 | .client_api = .no_api, 116 | .resizable = false, 117 | }); 118 | } 119 | 120 | fn initVulkan(self: *Self) !void { 121 | try self.createInstance(); 122 | try self.setupDebugMessenger(); 123 | try self.createSurface(); 124 | try self.pickPhysicalDevice(); 125 | try self.createLogicalDevice(); 126 | try self.createSwapChain(); 127 | try self.createImageViews(); 128 | try self.createGraphicsPipeline(); 129 | } 130 | 131 | fn mainLoop(self: *Self) !void { 132 | while (!self.window.?.shouldClose()) { 133 | try glfw.pollEvents(); 134 | } 135 | } 136 | 137 | pub fn deinit(self: *Self) void { 138 | if (self.swap_chain_image_views != null) { 139 | for (self.swap_chain_image_views.?) |image_view| { 140 | self.vkd.destroyImageView(self.device, image_view, null); 141 | } 142 | self.allocator.free(self.swap_chain_image_views.?); 143 | } 144 | 145 | if (self.swap_chain_images != null) self.allocator.free(self.swap_chain_images.?); 146 | if (self.swap_chain != .null_handle) self.vkd.destroySwapchainKHR(self.device, self.swap_chain, null); 147 | if (self.device != .null_handle) self.vkd.destroyDevice(self.device, null); 148 | 149 | if (enable_validation_layers and self.debug_messenger != .null_handle) self.vki.destroyDebugUtilsMessengerEXT(self.instance, self.debug_messenger, null); 150 | 151 | if (self.surface != .null_handle) self.vki.destroySurfaceKHR(self.instance, self.surface, null); 152 | if (self.instance != .null_handle) self.vki.destroyInstance(self.instance, null); 153 | 154 | if (self.window != null) self.window.?.destroy(); 155 | 156 | glfw.terminate(); 157 | } 158 | 159 | fn createInstance(self: *Self) !void { 160 | const vk_proc = @ptrCast(*const fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, &glfw.getInstanceProcAddress); 161 | self.vkb = try BaseDispatch.load(vk_proc); 162 | 163 | if (enable_validation_layers and !try self.checkValidationLayerSupport()) { 164 | return error.MissingValidationLayer; 165 | } 166 | 167 | const app_info = vk.ApplicationInfo{ 168 | .p_application_name = "Hello Triangle", 169 | .application_version = vk.makeApiVersion(1, 0, 0, 0), 170 | .p_engine_name = "No Engine", 171 | .engine_version = vk.makeApiVersion(1, 0, 0, 0), 172 | .api_version = vk.API_VERSION_1_2, 173 | }; 174 | 175 | const extensions = try getRequiredExtensions(self.allocator); 176 | defer extensions.deinit(); 177 | 178 | var create_info = vk.InstanceCreateInfo{ 179 | .flags = .{}, 180 | .p_application_info = &app_info, 181 | .enabled_layer_count = 0, 182 | .pp_enabled_layer_names = undefined, 183 | .enabled_extension_count = @intCast(u32, extensions.items.len), 184 | .pp_enabled_extension_names = extensions.items.ptr, 185 | }; 186 | 187 | if (enable_validation_layers) { 188 | create_info.enabled_layer_count = validation_layers.len; 189 | create_info.pp_enabled_layer_names = &validation_layers; 190 | 191 | var debug_create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 192 | populateDebugMessengerCreateInfo(&debug_create_info); 193 | create_info.p_next = &debug_create_info; 194 | } 195 | 196 | self.instance = try self.vkb.createInstance(&create_info, null); 197 | 198 | self.vki = try InstanceDispatch.load(self.instance, vk_proc); 199 | } 200 | 201 | fn populateDebugMessengerCreateInfo(create_info: *vk.DebugUtilsMessengerCreateInfoEXT) void { 202 | create_info.* = .{ 203 | .flags = .{}, 204 | .message_severity = .{ 205 | .verbose_bit_ext = true, 206 | .warning_bit_ext = true, 207 | .error_bit_ext = true, 208 | }, 209 | .message_type = .{ 210 | .general_bit_ext = true, 211 | .validation_bit_ext = true, 212 | .performance_bit_ext = true, 213 | }, 214 | .pfn_user_callback = debugCallback, 215 | .p_user_data = null, 216 | }; 217 | } 218 | 219 | fn setupDebugMessenger(self: *Self) !void { 220 | if (!enable_validation_layers) return; 221 | 222 | var create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 223 | populateDebugMessengerCreateInfo(&create_info); 224 | 225 | self.debug_messenger = try self.vki.createDebugUtilsMessengerEXT(self.instance, &create_info, null); 226 | } 227 | 228 | fn createSurface(self: *Self) !void { 229 | if ((try glfw.createWindowSurface(self.instance, self.window.?, null, &self.surface)) != @enumToInt(vk.Result.success)) { 230 | return error.SurfaceInitFailed; 231 | } 232 | } 233 | 234 | fn pickPhysicalDevice(self: *Self) !void { 235 | var device_count: u32 = undefined; 236 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, null); 237 | 238 | if (device_count == 0) { 239 | return error.NoGPUsSupportVulkan; 240 | } 241 | 242 | const devices = try self.allocator.alloc(vk.PhysicalDevice, device_count); 243 | defer self.allocator.free(devices); 244 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, devices.ptr); 245 | 246 | for (devices) |device| { 247 | if (try self.isDeviceSuitable(device)) { 248 | self.physical_device = device; 249 | break; 250 | } 251 | } 252 | 253 | if (self.physical_device == .null_handle) { 254 | return error.NoSuitableDevice; 255 | } 256 | } 257 | 258 | fn createLogicalDevice(self: *Self) !void { 259 | const indices = try self.findQueueFamilies(self.physical_device); 260 | const queue_priority = [_]f32{1}; 261 | 262 | var queue_create_info = [_]vk.DeviceQueueCreateInfo{ 263 | .{ 264 | .flags = .{}, 265 | .queue_family_index = indices.graphics_family.?, 266 | .queue_count = 1, 267 | .p_queue_priorities = &queue_priority, 268 | }, 269 | .{ 270 | .flags = .{}, 271 | .queue_family_index = indices.present_family.?, 272 | .queue_count = 1, 273 | .p_queue_priorities = &queue_priority, 274 | }, 275 | }; 276 | 277 | var create_info = vk.DeviceCreateInfo{ 278 | .flags = .{}, 279 | .queue_create_info_count = queue_create_info.len, 280 | .p_queue_create_infos = &queue_create_info, 281 | .enabled_layer_count = 0, 282 | .pp_enabled_layer_names = undefined, 283 | .enabled_extension_count = device_extensions.len, 284 | .pp_enabled_extension_names = &device_extensions, 285 | .p_enabled_features = null, 286 | }; 287 | 288 | if (enable_validation_layers) { 289 | create_info.enabled_layer_count = validation_layers.len; 290 | create_info.pp_enabled_layer_names = &validation_layers; 291 | } 292 | 293 | self.device = try self.vki.createDevice(self.physical_device, &create_info, null); 294 | 295 | self.vkd = try DeviceDispatch.load(self.device, self.vki.dispatch.vkGetDeviceProcAddr); 296 | 297 | self.graphics_queue = self.vkd.getDeviceQueue(self.device, indices.graphics_family.?, 0); 298 | self.present_queue = self.vkd.getDeviceQueue(self.device, indices.present_family.?, 0); 299 | } 300 | 301 | fn createSwapChain(self: *Self) !void { 302 | const swap_chain_support = try self.querySwapChainSupport(self.physical_device); 303 | defer swap_chain_support.deinit(); 304 | 305 | const surface_format: vk.SurfaceFormatKHR = chooseSwapSurfaceFormat(swap_chain_support.formats.?); 306 | const present_mode: vk.PresentModeKHR = chooseSwapPresentMode(swap_chain_support.present_modes.?); 307 | const extent: vk.Extent2D = try self.chooseSwapExtent(swap_chain_support.capabilities); 308 | 309 | var image_count = swap_chain_support.capabilities.min_image_count + 1; 310 | if (swap_chain_support.capabilities.max_image_count > 0) { 311 | image_count = std.math.min(image_count, swap_chain_support.capabilities.max_image_count); 312 | } 313 | 314 | const indices = try self.findQueueFamilies(self.physical_device); 315 | const queue_family_indices = [_]u32{ indices.graphics_family.?, indices.present_family.? }; 316 | const sharing_mode: vk.SharingMode = if (indices.graphics_family.? != indices.present_family.?) 317 | .concurrent 318 | else 319 | .exclusive; 320 | 321 | self.swap_chain = try self.vkd.createSwapchainKHR(self.device, &.{ 322 | .flags = .{}, 323 | .surface = self.surface, 324 | .min_image_count = image_count, 325 | .image_format = surface_format.format, 326 | .image_color_space = surface_format.color_space, 327 | .image_extent = extent, 328 | .image_array_layers = 1, 329 | .image_usage = .{ .color_attachment_bit = true }, 330 | .image_sharing_mode = sharing_mode, 331 | .queue_family_index_count = queue_family_indices.len, 332 | .p_queue_family_indices = &queue_family_indices, 333 | .pre_transform = swap_chain_support.capabilities.current_transform, 334 | .composite_alpha = .{ .opaque_bit_khr = true }, 335 | .present_mode = present_mode, 336 | .clipped = vk.TRUE, 337 | .old_swapchain = .null_handle, 338 | }, null); 339 | 340 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, null); 341 | self.swap_chain_images = try self.allocator.alloc(vk.Image, image_count); 342 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, self.swap_chain_images.?.ptr); 343 | 344 | self.swap_chain_image_format = surface_format.format; 345 | self.swap_chain_extent = extent; 346 | } 347 | 348 | fn createImageViews(self: *Self) !void { 349 | self.swap_chain_image_views = try self.allocator.alloc(vk.ImageView, self.swap_chain_images.?.len); 350 | 351 | for (self.swap_chain_images.?) |image, i| { 352 | self.swap_chain_image_views.?[i] = try self.vkd.createImageView(self.device, &.{ 353 | .flags = .{}, 354 | .image = image, 355 | .view_type = .@"2d", 356 | .format = self.swap_chain_image_format, 357 | .components = .{ .r = .identity, .g = .identity, .b = .identity, .a = .identity }, 358 | .subresource_range = .{ 359 | .aspect_mask = .{ .color_bit = true }, 360 | .base_mip_level = 0, 361 | .level_count = 1, 362 | .base_array_layer = 0, 363 | .layer_count = 1, 364 | }, 365 | }, null); 366 | } 367 | } 368 | 369 | fn createGraphicsPipeline(_: *Self) !void {} 370 | 371 | fn chooseSwapSurfaceFormat(available_formats: []vk.SurfaceFormatKHR) vk.SurfaceFormatKHR { 372 | for (available_formats) |available_format| { 373 | if (available_format.format == .b8g8r8a8_srgb and available_format.color_space == .srgb_nonlinear_khr) { 374 | return available_format; 375 | } 376 | } 377 | 378 | return available_formats[0]; 379 | } 380 | 381 | fn chooseSwapPresentMode(available_present_modes: []vk.PresentModeKHR) vk.PresentModeKHR { 382 | for (available_present_modes) |available_present_mode| { 383 | if (available_present_mode == .mailbox_khr) { 384 | return available_present_mode; 385 | } 386 | } 387 | 388 | return .fifo_khr; 389 | } 390 | 391 | fn chooseSwapExtent(self: *Self, capabilities: vk.SurfaceCapabilitiesKHR) !vk.Extent2D { 392 | if (capabilities.current_extent.width != 0xFFFF_FFFF) { 393 | return capabilities.current_extent; 394 | } else { 395 | const window_size = try self.window.?.getFramebufferSize(); 396 | 397 | return vk.Extent2D{ 398 | .width = std.math.clamp(window_size.width, capabilities.min_image_extent.width, capabilities.max_image_extent.width), 399 | .height = std.math.clamp(window_size.height, capabilities.min_image_extent.height, capabilities.max_image_extent.height), 400 | }; 401 | } 402 | } 403 | 404 | fn querySwapChainSupport(self: *Self, device: vk.PhysicalDevice) !SwapChainSupportDetails { 405 | var details = SwapChainSupportDetails.init(self.allocator); 406 | 407 | details.capabilities = try self.vki.getPhysicalDeviceSurfaceCapabilitiesKHR(device, self.surface); 408 | 409 | var format_count: u32 = undefined; 410 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, null); 411 | 412 | if (format_count != 0) { 413 | details.formats = try details.allocator.alloc(vk.SurfaceFormatKHR, format_count); 414 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, details.formats.?.ptr); 415 | } 416 | 417 | var present_mode_count: u32 = undefined; 418 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, null); 419 | 420 | if (present_mode_count != 0) { 421 | details.present_modes = try details.allocator.alloc(vk.PresentModeKHR, present_mode_count); 422 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, details.present_modes.?.ptr); 423 | } 424 | 425 | return details; 426 | } 427 | 428 | fn isDeviceSuitable(self: *Self, device: vk.PhysicalDevice) !bool { 429 | const indices = try self.findQueueFamilies(device); 430 | 431 | const extensions_supported = try self.checkDeviceExtensionSupport(device); 432 | 433 | var swap_chain_adequate = false; 434 | if (extensions_supported) { 435 | const swap_chain_support = try self.querySwapChainSupport(device); 436 | defer swap_chain_support.deinit(); 437 | 438 | swap_chain_adequate = swap_chain_support.formats != null and swap_chain_support.present_modes != null; 439 | } 440 | 441 | return indices.isComplete() and extensions_supported and swap_chain_adequate; 442 | } 443 | 444 | fn checkDeviceExtensionSupport(self: *Self, device: vk.PhysicalDevice) !bool { 445 | var extension_count: u32 = undefined; 446 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, null); 447 | 448 | const available_extensions = try self.allocator.alloc(vk.ExtensionProperties, extension_count); 449 | defer self.allocator.free(available_extensions); 450 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, available_extensions.ptr); 451 | 452 | const required_extensions = device_extensions[0..]; 453 | 454 | for (required_extensions) |required_extension| { 455 | for (available_extensions) |available_extension| { 456 | const len = std.mem.indexOfScalar(u8, &available_extension.extension_name, 0).?; 457 | const available_extension_name = available_extension.extension_name[0..len]; 458 | if (std.mem.eql(u8, std.mem.span(required_extension), available_extension_name)) { 459 | break; 460 | } 461 | } else { 462 | return false; 463 | } 464 | } 465 | 466 | return true; 467 | } 468 | 469 | fn findQueueFamilies(self: *Self, device: vk.PhysicalDevice) !QueueFamilyIndices { 470 | var indices: QueueFamilyIndices = .{}; 471 | 472 | var queue_family_count: u32 = 0; 473 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, null); 474 | 475 | const queue_families = try self.allocator.alloc(vk.QueueFamilyProperties, queue_family_count); 476 | defer self.allocator.free(queue_families); 477 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.ptr); 478 | 479 | for (queue_families) |queue_family, i| { 480 | if (indices.graphics_family == null and queue_family.queue_flags.graphics_bit) { 481 | indices.graphics_family = @intCast(u32, i); 482 | } else if (indices.present_family == null and (try self.vki.getPhysicalDeviceSurfaceSupportKHR(device, @intCast(u32, i), self.surface)) == vk.TRUE) { 483 | indices.present_family = @intCast(u32, i); 484 | } 485 | 486 | if (indices.isComplete()) { 487 | break; 488 | } 489 | } 490 | 491 | return indices; 492 | } 493 | 494 | fn getRequiredExtensions(allocator: Allocator) !std.ArrayListAligned([*:0]const u8, null) { 495 | var extensions = std.ArrayList([*:0]const u8).init(allocator); 496 | try extensions.appendSlice(try glfw.getRequiredInstanceExtensions()); 497 | 498 | if (enable_validation_layers) { 499 | try extensions.append(vk.extension_info.ext_debug_utils.name); 500 | } 501 | 502 | return extensions; 503 | } 504 | 505 | fn checkValidationLayerSupport(self: *Self) !bool { 506 | var layer_count: u32 = undefined; 507 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, null); 508 | 509 | var available_layers = try self.allocator.alloc(vk.LayerProperties, layer_count); 510 | defer self.allocator.free(available_layers); 511 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr); 512 | 513 | for (validation_layers) |layer_name| { 514 | var layer_found: bool = false; 515 | 516 | for (available_layers) |layer_properties| { 517 | const available_len = std.mem.indexOfScalar(u8, &layer_properties.layer_name, 0).?; 518 | const available_layer_name = layer_properties.layer_name[0..available_len]; 519 | if (std.mem.eql(u8, std.mem.span(layer_name), available_layer_name)) { 520 | layer_found = true; 521 | break; 522 | } 523 | } 524 | 525 | if (!layer_found) { 526 | return false; 527 | } 528 | } 529 | 530 | return true; 531 | } 532 | 533 | fn debugCallback(_: vk.DebugUtilsMessageSeverityFlagsEXT, _: vk.DebugUtilsMessageTypeFlagsEXT, p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque) callconv(vk.vulkan_call_conv) vk.Bool32 { 534 | if (p_callback_data != null) { 535 | std.log.debug("validation layer: {s}", .{p_callback_data.?.p_message}); 536 | } 537 | 538 | return vk.FALSE; 539 | } 540 | }; 541 | 542 | pub fn main() void { 543 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 544 | defer { 545 | const leaked = gpa.deinit(); 546 | if (leaked) std.log.err("MemLeak", .{}); 547 | } 548 | const allocator = gpa.allocator(); 549 | 550 | var app = HelloTriangleApplication.init(allocator); 551 | defer app.deinit(); 552 | app.run() catch |err| { 553 | std.log.err("application exited with error: {any}", .{err}); 554 | return; 555 | }; 556 | } 557 | -------------------------------------------------------------------------------- /src/09_shader_module.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | const glfw = @import("glfw"); 6 | const vk = @import("vulkan"); 7 | const resources = @import("resources"); 8 | 9 | const WIDTH: u32 = 800; 10 | const HEIGHT: u32 = 600; 11 | 12 | const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; 13 | 14 | const device_extensions = [_][*:0]const u8{vk.extension_info.khr_swapchain.name}; 15 | 16 | const enable_validation_layers: bool = switch (builtin.mode) { 17 | .Debug, .ReleaseSafe => true, 18 | else => false, 19 | }; 20 | 21 | const BaseDispatch = vk.BaseWrapper(.{ 22 | .createInstance = true, 23 | .enumerateInstanceLayerProperties = true, 24 | }); 25 | 26 | const InstanceDispatch = vk.InstanceWrapper(.{ 27 | .createDebugUtilsMessengerEXT = enable_validation_layers, 28 | .createDevice = true, 29 | .destroyDebugUtilsMessengerEXT = enable_validation_layers, 30 | .destroyInstance = true, 31 | .destroySurfaceKHR = true, 32 | .enumerateDeviceExtensionProperties = true, 33 | .enumeratePhysicalDevices = true, 34 | .getDeviceProcAddr = true, 35 | .getPhysicalDeviceQueueFamilyProperties = true, 36 | .getPhysicalDeviceSurfaceCapabilitiesKHR = true, 37 | .getPhysicalDeviceSurfaceFormatsKHR = true, 38 | .getPhysicalDeviceSurfacePresentModesKHR = true, 39 | .getPhysicalDeviceSurfaceSupportKHR = true, 40 | }); 41 | 42 | const DeviceDispatch = vk.DeviceWrapper(.{ 43 | .createImageView = true, 44 | .createShaderModule = true, 45 | .createSwapchainKHR = true, 46 | .destroyDevice = true, 47 | .destroyImageView = true, 48 | .destroyShaderModule = true, 49 | .destroySwapchainKHR = true, 50 | .getDeviceQueue = true, 51 | .getSwapchainImagesKHR = true, 52 | }); 53 | 54 | const QueueFamilyIndices = struct { 55 | graphics_family: ?u32 = null, 56 | present_family: ?u32 = null, 57 | 58 | fn isComplete(self: *const QueueFamilyIndices) bool { 59 | return self.graphics_family != null and self.present_family != null; 60 | } 61 | }; 62 | 63 | pub const SwapChainSupportDetails = struct { 64 | allocator: Allocator, 65 | capabilities: vk.SurfaceCapabilitiesKHR = undefined, 66 | formats: ?[]vk.SurfaceFormatKHR = null, 67 | present_modes: ?[]vk.PresentModeKHR = null, 68 | 69 | pub fn init(allocator: Allocator) SwapChainSupportDetails { 70 | return .{ .allocator = allocator }; 71 | } 72 | 73 | pub fn deinit(self: SwapChainSupportDetails) void { 74 | if (self.formats != null) self.allocator.free(self.formats.?); 75 | if (self.present_modes != null) self.allocator.free(self.present_modes.?); 76 | } 77 | }; 78 | 79 | const HelloTriangleApplication = struct { 80 | const Self = @This(); 81 | allocator: Allocator, 82 | 83 | window: ?glfw.Window = null, 84 | 85 | vkb: BaseDispatch = undefined, 86 | vki: InstanceDispatch = undefined, 87 | vkd: DeviceDispatch = undefined, 88 | 89 | instance: vk.Instance = .null_handle, 90 | debug_messenger: vk.DebugUtilsMessengerEXT = .null_handle, 91 | surface: vk.SurfaceKHR = .null_handle, 92 | 93 | physical_device: vk.PhysicalDevice = .null_handle, 94 | device: vk.Device = .null_handle, 95 | 96 | graphics_queue: vk.Queue = .null_handle, 97 | present_queue: vk.Queue = .null_handle, 98 | 99 | swap_chain: vk.SwapchainKHR = .null_handle, 100 | swap_chain_images: ?[]vk.Image = null, 101 | swap_chain_image_format: vk.Format = .@"undefined", 102 | swap_chain_extent: vk.Extent2D = .{ .width = 0, .height = 0 }, 103 | swap_chain_image_views: ?[]vk.ImageView = null, 104 | 105 | pub fn init(allocator: Allocator) Self { 106 | return Self{ .allocator = allocator }; 107 | } 108 | 109 | pub fn run(self: *Self) !void { 110 | try self.initWindow(); 111 | try self.initVulkan(); 112 | try self.mainLoop(); 113 | } 114 | 115 | fn initWindow(self: *Self) !void { 116 | try glfw.init(.{}); 117 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 118 | .client_api = .no_api, 119 | .resizable = false, 120 | }); 121 | } 122 | 123 | fn initVulkan(self: *Self) !void { 124 | try self.createInstance(); 125 | try self.setupDebugMessenger(); 126 | try self.createSurface(); 127 | try self.pickPhysicalDevice(); 128 | try self.createLogicalDevice(); 129 | try self.createSwapChain(); 130 | try self.createImageViews(); 131 | try self.createGraphicsPipeline(); 132 | } 133 | 134 | fn mainLoop(self: *Self) !void { 135 | while (!self.window.?.shouldClose()) { 136 | try glfw.pollEvents(); 137 | } 138 | } 139 | 140 | pub fn deinit(self: *Self) void { 141 | if (self.swap_chain_image_views != null) { 142 | for (self.swap_chain_image_views.?) |image_view| { 143 | self.vkd.destroyImageView(self.device, image_view, null); 144 | } 145 | self.allocator.free(self.swap_chain_image_views.?); 146 | } 147 | 148 | if (self.swap_chain_images != null) self.allocator.free(self.swap_chain_images.?); 149 | if (self.swap_chain != .null_handle) self.vkd.destroySwapchainKHR(self.device, self.swap_chain, null); 150 | if (self.device != .null_handle) self.vkd.destroyDevice(self.device, null); 151 | 152 | if (enable_validation_layers and self.debug_messenger != .null_handle) self.vki.destroyDebugUtilsMessengerEXT(self.instance, self.debug_messenger, null); 153 | 154 | if (self.surface != .null_handle) self.vki.destroySurfaceKHR(self.instance, self.surface, null); 155 | if (self.instance != .null_handle) self.vki.destroyInstance(self.instance, null); 156 | 157 | if (self.window != null) self.window.?.destroy(); 158 | 159 | glfw.terminate(); 160 | } 161 | 162 | fn createInstance(self: *Self) !void { 163 | const vk_proc = @ptrCast(*const fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, &glfw.getInstanceProcAddress); 164 | self.vkb = try BaseDispatch.load(vk_proc); 165 | 166 | if (enable_validation_layers and !try self.checkValidationLayerSupport()) { 167 | return error.MissingValidationLayer; 168 | } 169 | 170 | const app_info = vk.ApplicationInfo{ 171 | .p_application_name = "Hello Triangle", 172 | .application_version = vk.makeApiVersion(1, 0, 0, 0), 173 | .p_engine_name = "No Engine", 174 | .engine_version = vk.makeApiVersion(1, 0, 0, 0), 175 | .api_version = vk.API_VERSION_1_2, 176 | }; 177 | 178 | const extensions = try getRequiredExtensions(self.allocator); 179 | defer extensions.deinit(); 180 | 181 | var create_info = vk.InstanceCreateInfo{ 182 | .flags = .{}, 183 | .p_application_info = &app_info, 184 | .enabled_layer_count = 0, 185 | .pp_enabled_layer_names = undefined, 186 | .enabled_extension_count = @intCast(u32, extensions.items.len), 187 | .pp_enabled_extension_names = extensions.items.ptr, 188 | }; 189 | 190 | if (enable_validation_layers) { 191 | create_info.enabled_layer_count = validation_layers.len; 192 | create_info.pp_enabled_layer_names = &validation_layers; 193 | 194 | var debug_create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 195 | populateDebugMessengerCreateInfo(&debug_create_info); 196 | create_info.p_next = &debug_create_info; 197 | } 198 | 199 | self.instance = try self.vkb.createInstance(&create_info, null); 200 | 201 | self.vki = try InstanceDispatch.load(self.instance, vk_proc); 202 | } 203 | 204 | fn populateDebugMessengerCreateInfo(create_info: *vk.DebugUtilsMessengerCreateInfoEXT) void { 205 | create_info.* = .{ 206 | .flags = .{}, 207 | .message_severity = .{ 208 | .verbose_bit_ext = true, 209 | .warning_bit_ext = true, 210 | .error_bit_ext = true, 211 | }, 212 | .message_type = .{ 213 | .general_bit_ext = true, 214 | .validation_bit_ext = true, 215 | .performance_bit_ext = true, 216 | }, 217 | .pfn_user_callback = debugCallback, 218 | .p_user_data = null, 219 | }; 220 | } 221 | 222 | fn setupDebugMessenger(self: *Self) !void { 223 | if (!enable_validation_layers) return; 224 | 225 | var create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 226 | populateDebugMessengerCreateInfo(&create_info); 227 | 228 | self.debug_messenger = try self.vki.createDebugUtilsMessengerEXT(self.instance, &create_info, null); 229 | } 230 | 231 | fn createSurface(self: *Self) !void { 232 | if ((try glfw.createWindowSurface(self.instance, self.window.?, null, &self.surface)) != @enumToInt(vk.Result.success)) { 233 | return error.SurfaceInitFailed; 234 | } 235 | } 236 | 237 | fn pickPhysicalDevice(self: *Self) !void { 238 | var device_count: u32 = undefined; 239 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, null); 240 | 241 | if (device_count == 0) { 242 | return error.NoGPUsSupportVulkan; 243 | } 244 | 245 | const devices = try self.allocator.alloc(vk.PhysicalDevice, device_count); 246 | defer self.allocator.free(devices); 247 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, devices.ptr); 248 | 249 | for (devices) |device| { 250 | if (try self.isDeviceSuitable(device)) { 251 | self.physical_device = device; 252 | break; 253 | } 254 | } 255 | 256 | if (self.physical_device == .null_handle) { 257 | return error.NoSuitableDevice; 258 | } 259 | } 260 | 261 | fn createLogicalDevice(self: *Self) !void { 262 | const indices = try self.findQueueFamilies(self.physical_device); 263 | const queue_priority = [_]f32{1}; 264 | 265 | var queue_create_info = [_]vk.DeviceQueueCreateInfo{ 266 | .{ 267 | .flags = .{}, 268 | .queue_family_index = indices.graphics_family.?, 269 | .queue_count = 1, 270 | .p_queue_priorities = &queue_priority, 271 | }, 272 | .{ 273 | .flags = .{}, 274 | .queue_family_index = indices.present_family.?, 275 | .queue_count = 1, 276 | .p_queue_priorities = &queue_priority, 277 | }, 278 | }; 279 | 280 | var create_info = vk.DeviceCreateInfo{ 281 | .flags = .{}, 282 | .queue_create_info_count = queue_create_info.len, 283 | .p_queue_create_infos = &queue_create_info, 284 | .enabled_layer_count = 0, 285 | .pp_enabled_layer_names = undefined, 286 | .enabled_extension_count = device_extensions.len, 287 | .pp_enabled_extension_names = &device_extensions, 288 | .p_enabled_features = null, 289 | }; 290 | 291 | if (enable_validation_layers) { 292 | create_info.enabled_layer_count = validation_layers.len; 293 | create_info.pp_enabled_layer_names = &validation_layers; 294 | } 295 | 296 | self.device = try self.vki.createDevice(self.physical_device, &create_info, null); 297 | 298 | self.vkd = try DeviceDispatch.load(self.device, self.vki.dispatch.vkGetDeviceProcAddr); 299 | 300 | self.graphics_queue = self.vkd.getDeviceQueue(self.device, indices.graphics_family.?, 0); 301 | self.present_queue = self.vkd.getDeviceQueue(self.device, indices.present_family.?, 0); 302 | } 303 | 304 | fn createSwapChain(self: *Self) !void { 305 | const swap_chain_support = try self.querySwapChainSupport(self.physical_device); 306 | defer swap_chain_support.deinit(); 307 | 308 | const surface_format: vk.SurfaceFormatKHR = chooseSwapSurfaceFormat(swap_chain_support.formats.?); 309 | const present_mode: vk.PresentModeKHR = chooseSwapPresentMode(swap_chain_support.present_modes.?); 310 | const extent: vk.Extent2D = try self.chooseSwapExtent(swap_chain_support.capabilities); 311 | 312 | var image_count = swap_chain_support.capabilities.min_image_count + 1; 313 | if (swap_chain_support.capabilities.max_image_count > 0) { 314 | image_count = std.math.min(image_count, swap_chain_support.capabilities.max_image_count); 315 | } 316 | 317 | const indices = try self.findQueueFamilies(self.physical_device); 318 | const queue_family_indices = [_]u32{ indices.graphics_family.?, indices.present_family.? }; 319 | const sharing_mode: vk.SharingMode = if (indices.graphics_family.? != indices.present_family.?) 320 | .concurrent 321 | else 322 | .exclusive; 323 | 324 | self.swap_chain = try self.vkd.createSwapchainKHR(self.device, &.{ 325 | .flags = .{}, 326 | .surface = self.surface, 327 | .min_image_count = image_count, 328 | .image_format = surface_format.format, 329 | .image_color_space = surface_format.color_space, 330 | .image_extent = extent, 331 | .image_array_layers = 1, 332 | .image_usage = .{ .color_attachment_bit = true }, 333 | .image_sharing_mode = sharing_mode, 334 | .queue_family_index_count = queue_family_indices.len, 335 | .p_queue_family_indices = &queue_family_indices, 336 | .pre_transform = swap_chain_support.capabilities.current_transform, 337 | .composite_alpha = .{ .opaque_bit_khr = true }, 338 | .present_mode = present_mode, 339 | .clipped = vk.TRUE, 340 | .old_swapchain = .null_handle, 341 | }, null); 342 | 343 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, null); 344 | self.swap_chain_images = try self.allocator.alloc(vk.Image, image_count); 345 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, self.swap_chain_images.?.ptr); 346 | 347 | self.swap_chain_image_format = surface_format.format; 348 | self.swap_chain_extent = extent; 349 | } 350 | 351 | fn createImageViews(self: *Self) !void { 352 | self.swap_chain_image_views = try self.allocator.alloc(vk.ImageView, self.swap_chain_images.?.len); 353 | 354 | for (self.swap_chain_images.?) |image, i| { 355 | self.swap_chain_image_views.?[i] = try self.vkd.createImageView(self.device, &.{ 356 | .flags = .{}, 357 | .image = image, 358 | .view_type = .@"2d", 359 | .format = self.swap_chain_image_format, 360 | .components = .{ .r = .identity, .g = .identity, .b = .identity, .a = .identity }, 361 | .subresource_range = .{ 362 | .aspect_mask = .{ .color_bit = true }, 363 | .base_mip_level = 0, 364 | .level_count = 1, 365 | .base_array_layer = 0, 366 | .layer_count = 1, 367 | }, 368 | }, null); 369 | } 370 | } 371 | 372 | fn createGraphicsPipeline(self: *Self) !void { 373 | const vert_shader_module: vk.ShaderModule = try self.createShaderModule(resources.vert_09); 374 | defer self.vkd.destroyShaderModule(self.device, vert_shader_module, null); 375 | const frag_shader_module: vk.ShaderModule = try self.createShaderModule(resources.frag_09); 376 | defer self.vkd.destroyShaderModule(self.device, frag_shader_module, null); 377 | 378 | const shader_stages = [_]vk.PipelineShaderStageCreateInfo{ 379 | .{ 380 | .flags = .{}, 381 | .stage = .{ .vertex_bit = true }, 382 | .module = vert_shader_module, 383 | .p_name = "main", 384 | .p_specialization_info = null, 385 | }, 386 | .{ 387 | .flags = .{}, 388 | .stage = .{ .fragment_bit = true }, 389 | .module = frag_shader_module, 390 | .p_name = "main", 391 | .p_specialization_info = null, 392 | }, 393 | }; 394 | 395 | std.log.debug("temp hold for compiler error on unused const for shader_stages: {}", .{shader_stages.len}); 396 | } 397 | 398 | fn createShaderModule(self: *Self, code: []const u8) !vk.ShaderModule { 399 | return try self.vkd.createShaderModule(self.device, &.{ 400 | .flags = .{}, 401 | .code_size = code.len, 402 | .p_code = @ptrCast([*]const u32, code), 403 | }, null); 404 | } 405 | 406 | fn chooseSwapSurfaceFormat(available_formats: []vk.SurfaceFormatKHR) vk.SurfaceFormatKHR { 407 | for (available_formats) |available_format| { 408 | if (available_format.format == .b8g8r8a8_srgb and available_format.color_space == .srgb_nonlinear_khr) { 409 | return available_format; 410 | } 411 | } 412 | 413 | return available_formats[0]; 414 | } 415 | 416 | fn chooseSwapPresentMode(available_present_modes: []vk.PresentModeKHR) vk.PresentModeKHR { 417 | for (available_present_modes) |available_present_mode| { 418 | if (available_present_mode == .mailbox_khr) { 419 | return available_present_mode; 420 | } 421 | } 422 | 423 | return .fifo_khr; 424 | } 425 | 426 | fn chooseSwapExtent(self: *Self, capabilities: vk.SurfaceCapabilitiesKHR) !vk.Extent2D { 427 | if (capabilities.current_extent.width != 0xFFFF_FFFF) { 428 | return capabilities.current_extent; 429 | } else { 430 | const window_size = try self.window.?.getFramebufferSize(); 431 | 432 | return vk.Extent2D{ 433 | .width = std.math.clamp(window_size.width, capabilities.min_image_extent.width, capabilities.max_image_extent.width), 434 | .height = std.math.clamp(window_size.height, capabilities.min_image_extent.height, capabilities.max_image_extent.height), 435 | }; 436 | } 437 | } 438 | 439 | fn querySwapChainSupport(self: *Self, device: vk.PhysicalDevice) !SwapChainSupportDetails { 440 | var details = SwapChainSupportDetails.init(self.allocator); 441 | 442 | details.capabilities = try self.vki.getPhysicalDeviceSurfaceCapabilitiesKHR(device, self.surface); 443 | 444 | var format_count: u32 = undefined; 445 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, null); 446 | 447 | if (format_count != 0) { 448 | details.formats = try details.allocator.alloc(vk.SurfaceFormatKHR, format_count); 449 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, details.formats.?.ptr); 450 | } 451 | 452 | var present_mode_count: u32 = undefined; 453 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, null); 454 | 455 | if (present_mode_count != 0) { 456 | details.present_modes = try details.allocator.alloc(vk.PresentModeKHR, present_mode_count); 457 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, details.present_modes.?.ptr); 458 | } 459 | 460 | return details; 461 | } 462 | 463 | fn isDeviceSuitable(self: *Self, device: vk.PhysicalDevice) !bool { 464 | const indices = try self.findQueueFamilies(device); 465 | 466 | const extensions_supported = try self.checkDeviceExtensionSupport(device); 467 | 468 | var swap_chain_adequate = false; 469 | if (extensions_supported) { 470 | const swap_chain_support = try self.querySwapChainSupport(device); 471 | defer swap_chain_support.deinit(); 472 | 473 | swap_chain_adequate = swap_chain_support.formats != null and swap_chain_support.present_modes != null; 474 | } 475 | 476 | return indices.isComplete() and extensions_supported and swap_chain_adequate; 477 | } 478 | 479 | fn checkDeviceExtensionSupport(self: *Self, device: vk.PhysicalDevice) !bool { 480 | var extension_count: u32 = undefined; 481 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, null); 482 | 483 | const available_extensions = try self.allocator.alloc(vk.ExtensionProperties, extension_count); 484 | defer self.allocator.free(available_extensions); 485 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, available_extensions.ptr); 486 | 487 | const required_extensions = device_extensions[0..]; 488 | 489 | for (required_extensions) |required_extension| { 490 | for (available_extensions) |available_extension| { 491 | const len = std.mem.indexOfScalar(u8, &available_extension.extension_name, 0).?; 492 | const available_extension_name = available_extension.extension_name[0..len]; 493 | if (std.mem.eql(u8, std.mem.span(required_extension), available_extension_name)) { 494 | break; 495 | } 496 | } else { 497 | return false; 498 | } 499 | } 500 | 501 | return true; 502 | } 503 | 504 | fn findQueueFamilies(self: *Self, device: vk.PhysicalDevice) !QueueFamilyIndices { 505 | var indices: QueueFamilyIndices = .{}; 506 | 507 | var queue_family_count: u32 = 0; 508 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, null); 509 | 510 | const queue_families = try self.allocator.alloc(vk.QueueFamilyProperties, queue_family_count); 511 | defer self.allocator.free(queue_families); 512 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.ptr); 513 | 514 | for (queue_families) |queue_family, i| { 515 | if (indices.graphics_family == null and queue_family.queue_flags.graphics_bit) { 516 | indices.graphics_family = @intCast(u32, i); 517 | } else if (indices.present_family == null and (try self.vki.getPhysicalDeviceSurfaceSupportKHR(device, @intCast(u32, i), self.surface)) == vk.TRUE) { 518 | indices.present_family = @intCast(u32, i); 519 | } 520 | 521 | if (indices.isComplete()) { 522 | break; 523 | } 524 | } 525 | 526 | return indices; 527 | } 528 | 529 | fn getRequiredExtensions(allocator: Allocator) !std.ArrayListAligned([*:0]const u8, null) { 530 | var extensions = std.ArrayList([*:0]const u8).init(allocator); 531 | try extensions.appendSlice(try glfw.getRequiredInstanceExtensions()); 532 | 533 | if (enable_validation_layers) { 534 | try extensions.append(vk.extension_info.ext_debug_utils.name); 535 | } 536 | 537 | return extensions; 538 | } 539 | 540 | fn checkValidationLayerSupport(self: *Self) !bool { 541 | var layer_count: u32 = undefined; 542 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, null); 543 | 544 | var available_layers = try self.allocator.alloc(vk.LayerProperties, layer_count); 545 | defer self.allocator.free(available_layers); 546 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr); 547 | 548 | for (validation_layers) |layer_name| { 549 | var layer_found: bool = false; 550 | 551 | for (available_layers) |layer_properties| { 552 | const available_len = std.mem.indexOfScalar(u8, &layer_properties.layer_name, 0).?; 553 | const available_layer_name = layer_properties.layer_name[0..available_len]; 554 | if (std.mem.eql(u8, std.mem.span(layer_name), available_layer_name)) { 555 | layer_found = true; 556 | break; 557 | } 558 | } 559 | 560 | if (!layer_found) { 561 | return false; 562 | } 563 | } 564 | 565 | return true; 566 | } 567 | 568 | fn debugCallback(_: vk.DebugUtilsMessageSeverityFlagsEXT, _: vk.DebugUtilsMessageTypeFlagsEXT, p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque) callconv(vk.vulkan_call_conv) vk.Bool32 { 569 | if (p_callback_data != null) { 570 | std.log.debug("validation layer: {s}", .{p_callback_data.?.p_message}); 571 | } 572 | 573 | return vk.FALSE; 574 | } 575 | }; 576 | 577 | pub fn main() void { 578 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 579 | defer { 580 | const leaked = gpa.deinit(); 581 | if (leaked) std.log.err("MemLeak", .{}); 582 | } 583 | const allocator = gpa.allocator(); 584 | 585 | var app = HelloTriangleApplication.init(allocator); 586 | defer app.deinit(); 587 | app.run() catch |err| { 588 | std.log.err("application exited with error: {any}", .{err}); 589 | return; 590 | }; 591 | } 592 | -------------------------------------------------------------------------------- /src/10_fixed_functions.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | const glfw = @import("glfw"); 6 | const vk = @import("vulkan"); 7 | const resources = @import("resources"); 8 | 9 | const WIDTH: u32 = 800; 10 | const HEIGHT: u32 = 600; 11 | 12 | const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; 13 | 14 | const device_extensions = [_][*:0]const u8{vk.extension_info.khr_swapchain.name}; 15 | 16 | const enable_validation_layers: bool = switch (builtin.mode) { 17 | .Debug, .ReleaseSafe => true, 18 | else => false, 19 | }; 20 | 21 | const BaseDispatch = vk.BaseWrapper(.{ 22 | .createInstance = true, 23 | .enumerateInstanceLayerProperties = true, 24 | }); 25 | 26 | const InstanceDispatch = vk.InstanceWrapper(.{ 27 | .createDebugUtilsMessengerEXT = enable_validation_layers, 28 | .createDevice = true, 29 | .destroyDebugUtilsMessengerEXT = enable_validation_layers, 30 | .destroyInstance = true, 31 | .destroySurfaceKHR = true, 32 | .enumerateDeviceExtensionProperties = true, 33 | .enumeratePhysicalDevices = true, 34 | .getDeviceProcAddr = true, 35 | .getPhysicalDeviceQueueFamilyProperties = true, 36 | .getPhysicalDeviceSurfaceCapabilitiesKHR = true, 37 | .getPhysicalDeviceSurfaceFormatsKHR = true, 38 | .getPhysicalDeviceSurfacePresentModesKHR = true, 39 | .getPhysicalDeviceSurfaceSupportKHR = true, 40 | }); 41 | 42 | const DeviceDispatch = vk.DeviceWrapper(.{ 43 | .createImageView = true, 44 | .createPipelineLayout = true, 45 | .createShaderModule = true, 46 | .createSwapchainKHR = true, 47 | .destroyDevice = true, 48 | .destroyImageView = true, 49 | .destroyPipelineLayout = true, 50 | .destroyShaderModule = true, 51 | .destroySwapchainKHR = true, 52 | .getDeviceQueue = true, 53 | .getSwapchainImagesKHR = true, 54 | }); 55 | 56 | const QueueFamilyIndices = struct { 57 | graphics_family: ?u32 = null, 58 | present_family: ?u32 = null, 59 | 60 | fn isComplete(self: *const QueueFamilyIndices) bool { 61 | return self.graphics_family != null and self.present_family != null; 62 | } 63 | }; 64 | 65 | pub const SwapChainSupportDetails = struct { 66 | allocator: Allocator, 67 | capabilities: vk.SurfaceCapabilitiesKHR = undefined, 68 | formats: ?[]vk.SurfaceFormatKHR = null, 69 | present_modes: ?[]vk.PresentModeKHR = null, 70 | 71 | pub fn init(allocator: Allocator) SwapChainSupportDetails { 72 | return .{ .allocator = allocator }; 73 | } 74 | 75 | pub fn deinit(self: SwapChainSupportDetails) void { 76 | if (self.formats != null) self.allocator.free(self.formats.?); 77 | if (self.present_modes != null) self.allocator.free(self.present_modes.?); 78 | } 79 | }; 80 | 81 | const HelloTriangleApplication = struct { 82 | const Self = @This(); 83 | allocator: Allocator, 84 | 85 | window: ?glfw.Window = null, 86 | 87 | vkb: BaseDispatch = undefined, 88 | vki: InstanceDispatch = undefined, 89 | vkd: DeviceDispatch = undefined, 90 | 91 | instance: vk.Instance = .null_handle, 92 | debug_messenger: vk.DebugUtilsMessengerEXT = .null_handle, 93 | surface: vk.SurfaceKHR = .null_handle, 94 | 95 | physical_device: vk.PhysicalDevice = .null_handle, 96 | device: vk.Device = .null_handle, 97 | 98 | graphics_queue: vk.Queue = .null_handle, 99 | present_queue: vk.Queue = .null_handle, 100 | 101 | swap_chain: vk.SwapchainKHR = .null_handle, 102 | swap_chain_images: ?[]vk.Image = null, 103 | swap_chain_image_format: vk.Format = .@"undefined", 104 | swap_chain_extent: vk.Extent2D = .{ .width = 0, .height = 0 }, 105 | swap_chain_image_views: ?[]vk.ImageView = null, 106 | 107 | pipeline_layout: vk.PipelineLayout = .null_handle, 108 | 109 | pub fn init(allocator: Allocator) Self { 110 | return Self{ .allocator = allocator }; 111 | } 112 | 113 | pub fn run(self: *Self) !void { 114 | try self.initWindow(); 115 | try self.initVulkan(); 116 | try self.mainLoop(); 117 | } 118 | 119 | fn initWindow(self: *Self) !void { 120 | try glfw.init(.{}); 121 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 122 | .client_api = .no_api, 123 | .resizable = false, 124 | }); 125 | } 126 | 127 | fn initVulkan(self: *Self) !void { 128 | try self.createInstance(); 129 | try self.setupDebugMessenger(); 130 | try self.createSurface(); 131 | try self.pickPhysicalDevice(); 132 | try self.createLogicalDevice(); 133 | try self.createSwapChain(); 134 | try self.createImageViews(); 135 | try self.createGraphicsPipeline(); 136 | } 137 | 138 | fn mainLoop(self: *Self) !void { 139 | while (!self.window.?.shouldClose()) { 140 | try glfw.pollEvents(); 141 | } 142 | } 143 | 144 | pub fn deinit(self: *Self) void { 145 | if (self.pipeline_layout != .null_handle) self.vkd.destroyPipelineLayout(self.device, self.pipeline_layout, null); 146 | 147 | if (self.swap_chain_image_views != null) { 148 | for (self.swap_chain_image_views.?) |image_view| { 149 | self.vkd.destroyImageView(self.device, image_view, null); 150 | } 151 | self.allocator.free(self.swap_chain_image_views.?); 152 | } 153 | 154 | if (self.swap_chain_images != null) self.allocator.free(self.swap_chain_images.?); 155 | if (self.swap_chain != .null_handle) self.vkd.destroySwapchainKHR(self.device, self.swap_chain, null); 156 | if (self.device != .null_handle) self.vkd.destroyDevice(self.device, null); 157 | 158 | if (enable_validation_layers and self.debug_messenger != .null_handle) self.vki.destroyDebugUtilsMessengerEXT(self.instance, self.debug_messenger, null); 159 | 160 | if (self.surface != .null_handle) self.vki.destroySurfaceKHR(self.instance, self.surface, null); 161 | if (self.instance != .null_handle) self.vki.destroyInstance(self.instance, null); 162 | 163 | if (self.window != null) self.window.?.destroy(); 164 | 165 | glfw.terminate(); 166 | } 167 | 168 | fn createInstance(self: *Self) !void { 169 | const vk_proc = @ptrCast(*const fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, &glfw.getInstanceProcAddress); 170 | self.vkb = try BaseDispatch.load(vk_proc); 171 | 172 | if (enable_validation_layers and !try self.checkValidationLayerSupport()) { 173 | return error.MissingValidationLayer; 174 | } 175 | 176 | const app_info = vk.ApplicationInfo{ 177 | .p_application_name = "Hello Triangle", 178 | .application_version = vk.makeApiVersion(1, 0, 0, 0), 179 | .p_engine_name = "No Engine", 180 | .engine_version = vk.makeApiVersion(1, 0, 0, 0), 181 | .api_version = vk.API_VERSION_1_2, 182 | }; 183 | 184 | const extensions = try getRequiredExtensions(self.allocator); 185 | defer extensions.deinit(); 186 | 187 | var create_info = vk.InstanceCreateInfo{ 188 | .flags = .{}, 189 | .p_application_info = &app_info, 190 | .enabled_layer_count = 0, 191 | .pp_enabled_layer_names = undefined, 192 | .enabled_extension_count = @intCast(u32, extensions.items.len), 193 | .pp_enabled_extension_names = extensions.items.ptr, 194 | }; 195 | 196 | if (enable_validation_layers) { 197 | create_info.enabled_layer_count = validation_layers.len; 198 | create_info.pp_enabled_layer_names = &validation_layers; 199 | 200 | var debug_create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 201 | populateDebugMessengerCreateInfo(&debug_create_info); 202 | create_info.p_next = &debug_create_info; 203 | } 204 | 205 | self.instance = try self.vkb.createInstance(&create_info, null); 206 | 207 | self.vki = try InstanceDispatch.load(self.instance, vk_proc); 208 | } 209 | 210 | fn populateDebugMessengerCreateInfo(create_info: *vk.DebugUtilsMessengerCreateInfoEXT) void { 211 | create_info.* = .{ 212 | .flags = .{}, 213 | .message_severity = .{ 214 | .verbose_bit_ext = true, 215 | .warning_bit_ext = true, 216 | .error_bit_ext = true, 217 | }, 218 | .message_type = .{ 219 | .general_bit_ext = true, 220 | .validation_bit_ext = true, 221 | .performance_bit_ext = true, 222 | }, 223 | .pfn_user_callback = debugCallback, 224 | .p_user_data = null, 225 | }; 226 | } 227 | 228 | fn setupDebugMessenger(self: *Self) !void { 229 | if (!enable_validation_layers) return; 230 | 231 | var create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 232 | populateDebugMessengerCreateInfo(&create_info); 233 | 234 | self.debug_messenger = try self.vki.createDebugUtilsMessengerEXT(self.instance, &create_info, null); 235 | } 236 | 237 | fn createSurface(self: *Self) !void { 238 | if ((try glfw.createWindowSurface(self.instance, self.window.?, null, &self.surface)) != @enumToInt(vk.Result.success)) { 239 | return error.SurfaceInitFailed; 240 | } 241 | } 242 | 243 | fn pickPhysicalDevice(self: *Self) !void { 244 | var device_count: u32 = undefined; 245 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, null); 246 | 247 | if (device_count == 0) { 248 | return error.NoGPUsSupportVulkan; 249 | } 250 | 251 | const devices = try self.allocator.alloc(vk.PhysicalDevice, device_count); 252 | defer self.allocator.free(devices); 253 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, devices.ptr); 254 | 255 | for (devices) |device| { 256 | if (try self.isDeviceSuitable(device)) { 257 | self.physical_device = device; 258 | break; 259 | } 260 | } 261 | 262 | if (self.physical_device == .null_handle) { 263 | return error.NoSuitableDevice; 264 | } 265 | } 266 | 267 | fn createLogicalDevice(self: *Self) !void { 268 | const indices = try self.findQueueFamilies(self.physical_device); 269 | const queue_priority = [_]f32{1}; 270 | 271 | var queue_create_info = [_]vk.DeviceQueueCreateInfo{ 272 | .{ 273 | .flags = .{}, 274 | .queue_family_index = indices.graphics_family.?, 275 | .queue_count = 1, 276 | .p_queue_priorities = &queue_priority, 277 | }, 278 | .{ 279 | .flags = .{}, 280 | .queue_family_index = indices.present_family.?, 281 | .queue_count = 1, 282 | .p_queue_priorities = &queue_priority, 283 | }, 284 | }; 285 | 286 | var create_info = vk.DeviceCreateInfo{ 287 | .flags = .{}, 288 | .queue_create_info_count = queue_create_info.len, 289 | .p_queue_create_infos = &queue_create_info, 290 | .enabled_layer_count = 0, 291 | .pp_enabled_layer_names = undefined, 292 | .enabled_extension_count = device_extensions.len, 293 | .pp_enabled_extension_names = &device_extensions, 294 | .p_enabled_features = null, 295 | }; 296 | 297 | if (enable_validation_layers) { 298 | create_info.enabled_layer_count = validation_layers.len; 299 | create_info.pp_enabled_layer_names = &validation_layers; 300 | } 301 | 302 | self.device = try self.vki.createDevice(self.physical_device, &create_info, null); 303 | 304 | self.vkd = try DeviceDispatch.load(self.device, self.vki.dispatch.vkGetDeviceProcAddr); 305 | 306 | self.graphics_queue = self.vkd.getDeviceQueue(self.device, indices.graphics_family.?, 0); 307 | self.present_queue = self.vkd.getDeviceQueue(self.device, indices.present_family.?, 0); 308 | } 309 | 310 | fn createSwapChain(self: *Self) !void { 311 | const swap_chain_support = try self.querySwapChainSupport(self.physical_device); 312 | defer swap_chain_support.deinit(); 313 | 314 | const surface_format: vk.SurfaceFormatKHR = chooseSwapSurfaceFormat(swap_chain_support.formats.?); 315 | const present_mode: vk.PresentModeKHR = chooseSwapPresentMode(swap_chain_support.present_modes.?); 316 | const extent: vk.Extent2D = try self.chooseSwapExtent(swap_chain_support.capabilities); 317 | 318 | var image_count = swap_chain_support.capabilities.min_image_count + 1; 319 | if (swap_chain_support.capabilities.max_image_count > 0) { 320 | image_count = std.math.min(image_count, swap_chain_support.capabilities.max_image_count); 321 | } 322 | 323 | const indices = try self.findQueueFamilies(self.physical_device); 324 | const queue_family_indices = [_]u32{ indices.graphics_family.?, indices.present_family.? }; 325 | const sharing_mode: vk.SharingMode = if (indices.graphics_family.? != indices.present_family.?) 326 | .concurrent 327 | else 328 | .exclusive; 329 | 330 | self.swap_chain = try self.vkd.createSwapchainKHR(self.device, &.{ 331 | .flags = .{}, 332 | .surface = self.surface, 333 | .min_image_count = image_count, 334 | .image_format = surface_format.format, 335 | .image_color_space = surface_format.color_space, 336 | .image_extent = extent, 337 | .image_array_layers = 1, 338 | .image_usage = .{ .color_attachment_bit = true }, 339 | .image_sharing_mode = sharing_mode, 340 | .queue_family_index_count = queue_family_indices.len, 341 | .p_queue_family_indices = &queue_family_indices, 342 | .pre_transform = swap_chain_support.capabilities.current_transform, 343 | .composite_alpha = .{ .opaque_bit_khr = true }, 344 | .present_mode = present_mode, 345 | .clipped = vk.TRUE, 346 | .old_swapchain = .null_handle, 347 | }, null); 348 | 349 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, null); 350 | self.swap_chain_images = try self.allocator.alloc(vk.Image, image_count); 351 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, self.swap_chain_images.?.ptr); 352 | 353 | self.swap_chain_image_format = surface_format.format; 354 | self.swap_chain_extent = extent; 355 | } 356 | 357 | fn createImageViews(self: *Self) !void { 358 | self.swap_chain_image_views = try self.allocator.alloc(vk.ImageView, self.swap_chain_images.?.len); 359 | 360 | for (self.swap_chain_images.?) |image, i| { 361 | self.swap_chain_image_views.?[i] = try self.vkd.createImageView(self.device, &.{ 362 | .flags = .{}, 363 | .image = image, 364 | .view_type = .@"2d", 365 | .format = self.swap_chain_image_format, 366 | .components = .{ .r = .identity, .g = .identity, .b = .identity, .a = .identity }, 367 | .subresource_range = .{ 368 | .aspect_mask = .{ .color_bit = true }, 369 | .base_mip_level = 0, 370 | .level_count = 1, 371 | .base_array_layer = 0, 372 | .layer_count = 1, 373 | }, 374 | }, null); 375 | } 376 | } 377 | 378 | fn createGraphicsPipeline(self: *Self) !void { 379 | const vert_shader_module: vk.ShaderModule = try self.createShaderModule(resources.vert_09); 380 | defer self.vkd.destroyShaderModule(self.device, vert_shader_module, null); 381 | const frag_shader_module: vk.ShaderModule = try self.createShaderModule(resources.frag_09); 382 | defer self.vkd.destroyShaderModule(self.device, frag_shader_module, null); 383 | 384 | const shader_stages = [_]vk.PipelineShaderStageCreateInfo{ 385 | .{ 386 | .flags = .{}, 387 | .stage = .{ .vertex_bit = true }, 388 | .module = vert_shader_module, 389 | .p_name = "main", 390 | .p_specialization_info = null, 391 | }, 392 | .{ 393 | .flags = .{}, 394 | .stage = .{ .fragment_bit = true }, 395 | .module = frag_shader_module, 396 | .p_name = "main", 397 | .p_specialization_info = null, 398 | }, 399 | }; 400 | 401 | const vertex_input_info = vk.PipelineVertexInputStateCreateInfo{ 402 | .flags = .{}, 403 | .vertex_binding_description_count = 0, 404 | .p_vertex_binding_descriptions = undefined, 405 | .vertex_attribute_description_count = 0, 406 | .p_vertex_attribute_descriptions = undefined, 407 | }; 408 | 409 | const input_assembly = vk.PipelineInputAssemblyStateCreateInfo{ 410 | .flags = .{}, 411 | .topology = .triangle_list, 412 | .primitive_restart_enable = vk.FALSE, 413 | }; 414 | 415 | const viewport_state = vk.PipelineViewportStateCreateInfo{ 416 | .flags = .{}, 417 | .viewport_count = 1, 418 | .p_viewports = undefined, 419 | .scissor_count = 1, 420 | .p_scissors = undefined, 421 | }; 422 | 423 | const rasterizer = vk.PipelineRasterizationStateCreateInfo{ 424 | .flags = .{}, 425 | .depth_clamp_enable = vk.FALSE, 426 | .rasterizer_discard_enable = vk.FALSE, 427 | .polygon_mode = .fill, 428 | .cull_mode = .{ .back_bit = true }, 429 | .front_face = .clockwise, 430 | .depth_bias_enable = vk.FALSE, 431 | .depth_bias_constant_factor = 0, 432 | .depth_bias_clamp = 0, 433 | .depth_bias_slope_factor = 0, 434 | .line_width = 1, 435 | }; 436 | 437 | const multisampling = vk.PipelineMultisampleStateCreateInfo{ 438 | .flags = .{}, 439 | .rasterization_samples = .{ .@"1_bit" = true }, 440 | .sample_shading_enable = vk.FALSE, 441 | .min_sample_shading = 1, 442 | .p_sample_mask = null, 443 | .alpha_to_coverage_enable = vk.FALSE, 444 | .alpha_to_one_enable = vk.FALSE, 445 | }; 446 | 447 | const color_blend_attachment = [_]vk.PipelineColorBlendAttachmentState{.{ 448 | .blend_enable = vk.FALSE, 449 | .src_color_blend_factor = .one, 450 | .dst_color_blend_factor = .zero, 451 | .color_blend_op = .add, 452 | .src_alpha_blend_factor = .one, 453 | .dst_alpha_blend_factor = .zero, 454 | .alpha_blend_op = .add, 455 | .color_write_mask = .{ .r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true }, 456 | }}; 457 | 458 | const color_blending = vk.PipelineColorBlendStateCreateInfo{ 459 | .flags = .{}, 460 | .logic_op_enable = vk.FALSE, 461 | .logic_op = .copy, 462 | .attachment_count = color_blend_attachment.len, 463 | .p_attachments = &color_blend_attachment, 464 | .blend_constants = [_]f32{ 0, 0, 0, 0 }, 465 | }; 466 | const dynamic_states = [_]vk.DynamicState{ .viewport, .scissor }; 467 | 468 | const dynamic_state = vk.PipelineDynamicStateCreateInfo{ 469 | .flags = .{}, 470 | .dynamic_state_count = dynamic_states.len, 471 | .p_dynamic_states = &dynamic_states, 472 | }; 473 | 474 | self.pipeline_layout = try self.vkd.createPipelineLayout(self.device, &.{ 475 | .flags = .{}, 476 | .set_layout_count = 0, 477 | .p_set_layouts = undefined, 478 | .push_constant_range_count = 0, 479 | .p_push_constant_ranges = undefined, 480 | }, null); 481 | 482 | std.log.debug("temp hold for compiler errors: {any} {any} {any} {any} {any} {any} {any} {any}", .{ dynamic_state, color_blending, multisampling, rasterizer, viewport_state, vertex_input_info, shader_stages, input_assembly }); 483 | } 484 | 485 | fn createShaderModule(self: *Self, code: []const u8) !vk.ShaderModule { 486 | return try self.vkd.createShaderModule(self.device, &.{ 487 | .flags = .{}, 488 | .code_size = code.len, 489 | .p_code = @ptrCast([*]const u32, code), 490 | }, null); 491 | } 492 | 493 | fn chooseSwapSurfaceFormat(available_formats: []vk.SurfaceFormatKHR) vk.SurfaceFormatKHR { 494 | for (available_formats) |available_format| { 495 | if (available_format.format == .b8g8r8a8_srgb and available_format.color_space == .srgb_nonlinear_khr) { 496 | return available_format; 497 | } 498 | } 499 | 500 | return available_formats[0]; 501 | } 502 | 503 | fn chooseSwapPresentMode(available_present_modes: []vk.PresentModeKHR) vk.PresentModeKHR { 504 | for (available_present_modes) |available_present_mode| { 505 | if (available_present_mode == .mailbox_khr) { 506 | return available_present_mode; 507 | } 508 | } 509 | 510 | return .fifo_khr; 511 | } 512 | 513 | fn chooseSwapExtent(self: *Self, capabilities: vk.SurfaceCapabilitiesKHR) !vk.Extent2D { 514 | if (capabilities.current_extent.width != 0xFFFF_FFFF) { 515 | return capabilities.current_extent; 516 | } else { 517 | const window_size = try self.window.?.getFramebufferSize(); 518 | 519 | return vk.Extent2D{ 520 | .width = std.math.clamp(window_size.width, capabilities.min_image_extent.width, capabilities.max_image_extent.width), 521 | .height = std.math.clamp(window_size.height, capabilities.min_image_extent.height, capabilities.max_image_extent.height), 522 | }; 523 | } 524 | } 525 | 526 | fn querySwapChainSupport(self: *Self, device: vk.PhysicalDevice) !SwapChainSupportDetails { 527 | var details = SwapChainSupportDetails.init(self.allocator); 528 | 529 | details.capabilities = try self.vki.getPhysicalDeviceSurfaceCapabilitiesKHR(device, self.surface); 530 | 531 | var format_count: u32 = undefined; 532 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, null); 533 | 534 | if (format_count != 0) { 535 | details.formats = try details.allocator.alloc(vk.SurfaceFormatKHR, format_count); 536 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, details.formats.?.ptr); 537 | } 538 | 539 | var present_mode_count: u32 = undefined; 540 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, null); 541 | 542 | if (present_mode_count != 0) { 543 | details.present_modes = try details.allocator.alloc(vk.PresentModeKHR, present_mode_count); 544 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, details.present_modes.?.ptr); 545 | } 546 | 547 | return details; 548 | } 549 | 550 | fn isDeviceSuitable(self: *Self, device: vk.PhysicalDevice) !bool { 551 | const indices = try self.findQueueFamilies(device); 552 | 553 | const extensions_supported = try self.checkDeviceExtensionSupport(device); 554 | 555 | var swap_chain_adequate = false; 556 | if (extensions_supported) { 557 | const swap_chain_support = try self.querySwapChainSupport(device); 558 | defer swap_chain_support.deinit(); 559 | 560 | swap_chain_adequate = swap_chain_support.formats != null and swap_chain_support.present_modes != null; 561 | } 562 | 563 | return indices.isComplete() and extensions_supported and swap_chain_adequate; 564 | } 565 | 566 | fn checkDeviceExtensionSupport(self: *Self, device: vk.PhysicalDevice) !bool { 567 | var extension_count: u32 = undefined; 568 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, null); 569 | 570 | const available_extensions = try self.allocator.alloc(vk.ExtensionProperties, extension_count); 571 | defer self.allocator.free(available_extensions); 572 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, available_extensions.ptr); 573 | 574 | const required_extensions = device_extensions[0..]; 575 | 576 | for (required_extensions) |required_extension| { 577 | for (available_extensions) |available_extension| { 578 | const len = std.mem.indexOfScalar(u8, &available_extension.extension_name, 0).?; 579 | const available_extension_name = available_extension.extension_name[0..len]; 580 | if (std.mem.eql(u8, std.mem.span(required_extension), available_extension_name)) { 581 | break; 582 | } 583 | } else { 584 | return false; 585 | } 586 | } 587 | 588 | return true; 589 | } 590 | 591 | fn findQueueFamilies(self: *Self, device: vk.PhysicalDevice) !QueueFamilyIndices { 592 | var indices: QueueFamilyIndices = .{}; 593 | 594 | var queue_family_count: u32 = 0; 595 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, null); 596 | 597 | const queue_families = try self.allocator.alloc(vk.QueueFamilyProperties, queue_family_count); 598 | defer self.allocator.free(queue_families); 599 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.ptr); 600 | 601 | for (queue_families) |queue_family, i| { 602 | if (indices.graphics_family == null and queue_family.queue_flags.graphics_bit) { 603 | indices.graphics_family = @intCast(u32, i); 604 | } else if (indices.present_family == null and (try self.vki.getPhysicalDeviceSurfaceSupportKHR(device, @intCast(u32, i), self.surface)) == vk.TRUE) { 605 | indices.present_family = @intCast(u32, i); 606 | } 607 | 608 | if (indices.isComplete()) { 609 | break; 610 | } 611 | } 612 | 613 | return indices; 614 | } 615 | 616 | fn getRequiredExtensions(allocator: Allocator) !std.ArrayListAligned([*:0]const u8, null) { 617 | var extensions = std.ArrayList([*:0]const u8).init(allocator); 618 | try extensions.appendSlice(try glfw.getRequiredInstanceExtensions()); 619 | 620 | if (enable_validation_layers) { 621 | try extensions.append(vk.extension_info.ext_debug_utils.name); 622 | } 623 | 624 | return extensions; 625 | } 626 | 627 | fn checkValidationLayerSupport(self: *Self) !bool { 628 | var layer_count: u32 = undefined; 629 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, null); 630 | 631 | var available_layers = try self.allocator.alloc(vk.LayerProperties, layer_count); 632 | defer self.allocator.free(available_layers); 633 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr); 634 | 635 | for (validation_layers) |layer_name| { 636 | var layer_found: bool = false; 637 | 638 | for (available_layers) |layer_properties| { 639 | const available_len = std.mem.indexOfScalar(u8, &layer_properties.layer_name, 0).?; 640 | const available_layer_name = layer_properties.layer_name[0..available_len]; 641 | if (std.mem.eql(u8, std.mem.span(layer_name), available_layer_name)) { 642 | layer_found = true; 643 | break; 644 | } 645 | } 646 | 647 | if (!layer_found) { 648 | return false; 649 | } 650 | } 651 | 652 | return true; 653 | } 654 | 655 | fn debugCallback(_: vk.DebugUtilsMessageSeverityFlagsEXT, _: vk.DebugUtilsMessageTypeFlagsEXT, p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque) callconv(vk.vulkan_call_conv) vk.Bool32 { 656 | if (p_callback_data != null) { 657 | std.log.debug("validation layer: {s}", .{p_callback_data.?.p_message}); 658 | } 659 | 660 | return vk.FALSE; 661 | } 662 | }; 663 | 664 | pub fn main() void { 665 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 666 | defer { 667 | const leaked = gpa.deinit(); 668 | if (leaked) std.log.err("MemLeak", .{}); 669 | } 670 | const allocator = gpa.allocator(); 671 | 672 | var app = HelloTriangleApplication.init(allocator); 673 | defer app.deinit(); 674 | app.run() catch |err| { 675 | std.log.err("application exited with error: {any}", .{err}); 676 | return; 677 | }; 678 | } 679 | -------------------------------------------------------------------------------- /src/11_render_passes.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const Allocator = std.mem.Allocator; 4 | 5 | const glfw = @import("glfw"); 6 | const vk = @import("vulkan"); 7 | const resources = @import("resources"); 8 | 9 | const WIDTH: u32 = 800; 10 | const HEIGHT: u32 = 600; 11 | 12 | const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; 13 | 14 | const device_extensions = [_][*:0]const u8{vk.extension_info.khr_swapchain.name}; 15 | 16 | const enable_validation_layers: bool = switch (builtin.mode) { 17 | .Debug, .ReleaseSafe => true, 18 | else => false, 19 | }; 20 | 21 | const BaseDispatch = vk.BaseWrapper(.{ 22 | .createInstance = true, 23 | .enumerateInstanceLayerProperties = true, 24 | }); 25 | 26 | const InstanceDispatch = vk.InstanceWrapper(.{ 27 | .createDebugUtilsMessengerEXT = enable_validation_layers, 28 | .createDevice = true, 29 | .destroyDebugUtilsMessengerEXT = enable_validation_layers, 30 | .destroyInstance = true, 31 | .destroySurfaceKHR = true, 32 | .enumerateDeviceExtensionProperties = true, 33 | .enumeratePhysicalDevices = true, 34 | .getDeviceProcAddr = true, 35 | .getPhysicalDeviceQueueFamilyProperties = true, 36 | .getPhysicalDeviceSurfaceCapabilitiesKHR = true, 37 | .getPhysicalDeviceSurfaceFormatsKHR = true, 38 | .getPhysicalDeviceSurfacePresentModesKHR = true, 39 | .getPhysicalDeviceSurfaceSupportKHR = true, 40 | }); 41 | 42 | const DeviceDispatch = vk.DeviceWrapper(.{ 43 | .createImageView = true, 44 | .createPipelineLayout = true, 45 | .createRenderPass = true, 46 | .createShaderModule = true, 47 | .createSwapchainKHR = true, 48 | .destroyDevice = true, 49 | .destroyImageView = true, 50 | .destroyPipelineLayout = true, 51 | .destroyRenderPass = true, 52 | .destroyShaderModule = true, 53 | .destroySwapchainKHR = true, 54 | .getDeviceQueue = true, 55 | .getSwapchainImagesKHR = true, 56 | }); 57 | 58 | const QueueFamilyIndices = struct { 59 | graphics_family: ?u32 = null, 60 | present_family: ?u32 = null, 61 | 62 | fn isComplete(self: *const QueueFamilyIndices) bool { 63 | return self.graphics_family != null and self.present_family != null; 64 | } 65 | }; 66 | 67 | pub const SwapChainSupportDetails = struct { 68 | allocator: Allocator, 69 | capabilities: vk.SurfaceCapabilitiesKHR = undefined, 70 | formats: ?[]vk.SurfaceFormatKHR = null, 71 | present_modes: ?[]vk.PresentModeKHR = null, 72 | 73 | pub fn init(allocator: Allocator) SwapChainSupportDetails { 74 | return .{ .allocator = allocator }; 75 | } 76 | 77 | pub fn deinit(self: SwapChainSupportDetails) void { 78 | if (self.formats != null) self.allocator.free(self.formats.?); 79 | if (self.present_modes != null) self.allocator.free(self.present_modes.?); 80 | } 81 | }; 82 | 83 | const HelloTriangleApplication = struct { 84 | const Self = @This(); 85 | allocator: Allocator, 86 | 87 | window: ?glfw.Window = null, 88 | 89 | vkb: BaseDispatch = undefined, 90 | vki: InstanceDispatch = undefined, 91 | vkd: DeviceDispatch = undefined, 92 | 93 | instance: vk.Instance = .null_handle, 94 | debug_messenger: vk.DebugUtilsMessengerEXT = .null_handle, 95 | surface: vk.SurfaceKHR = .null_handle, 96 | 97 | physical_device: vk.PhysicalDevice = .null_handle, 98 | device: vk.Device = .null_handle, 99 | 100 | graphics_queue: vk.Queue = .null_handle, 101 | present_queue: vk.Queue = .null_handle, 102 | 103 | swap_chain: vk.SwapchainKHR = .null_handle, 104 | swap_chain_images: ?[]vk.Image = null, 105 | swap_chain_image_format: vk.Format = .@"undefined", 106 | swap_chain_extent: vk.Extent2D = .{ .width = 0, .height = 0 }, 107 | swap_chain_image_views: ?[]vk.ImageView = null, 108 | 109 | render_pass: vk.RenderPass = .null_handle, 110 | pipeline_layout: vk.PipelineLayout = .null_handle, 111 | 112 | pub fn init(allocator: Allocator) Self { 113 | return Self{ .allocator = allocator }; 114 | } 115 | 116 | pub fn run(self: *Self) !void { 117 | try self.initWindow(); 118 | try self.initVulkan(); 119 | try self.mainLoop(); 120 | } 121 | 122 | fn initWindow(self: *Self) !void { 123 | try glfw.init(.{}); 124 | self.window = try glfw.Window.create(WIDTH, HEIGHT, "Vulkan", null, null, .{ 125 | .client_api = .no_api, 126 | .resizable = false, 127 | }); 128 | } 129 | 130 | fn initVulkan(self: *Self) !void { 131 | try self.createInstance(); 132 | try self.setupDebugMessenger(); 133 | try self.createSurface(); 134 | try self.pickPhysicalDevice(); 135 | try self.createLogicalDevice(); 136 | try self.createSwapChain(); 137 | try self.createImageViews(); 138 | try self.createRenderPass(); 139 | try self.createGraphicsPipeline(); 140 | } 141 | 142 | fn mainLoop(self: *Self) !void { 143 | while (!self.window.?.shouldClose()) { 144 | try glfw.pollEvents(); 145 | } 146 | } 147 | 148 | pub fn deinit(self: *Self) void { 149 | if (self.pipeline_layout != .null_handle) self.vkd.destroyPipelineLayout(self.device, self.pipeline_layout, null); 150 | if (self.render_pass != .null_handle) self.vkd.destroyRenderPass(self.device, self.render_pass, null); 151 | 152 | if (self.swap_chain_image_views != null) { 153 | for (self.swap_chain_image_views.?) |image_view| { 154 | self.vkd.destroyImageView(self.device, image_view, null); 155 | } 156 | self.allocator.free(self.swap_chain_image_views.?); 157 | } 158 | 159 | if (self.swap_chain_images != null) self.allocator.free(self.swap_chain_images.?); 160 | if (self.swap_chain != .null_handle) self.vkd.destroySwapchainKHR(self.device, self.swap_chain, null); 161 | if (self.device != .null_handle) self.vkd.destroyDevice(self.device, null); 162 | 163 | if (enable_validation_layers and self.debug_messenger != .null_handle) self.vki.destroyDebugUtilsMessengerEXT(self.instance, self.debug_messenger, null); 164 | 165 | if (self.surface != .null_handle) self.vki.destroySurfaceKHR(self.instance, self.surface, null); 166 | if (self.instance != .null_handle) self.vki.destroyInstance(self.instance, null); 167 | 168 | if (self.window != null) self.window.?.destroy(); 169 | 170 | glfw.terminate(); 171 | } 172 | 173 | fn createInstance(self: *Self) !void { 174 | const vk_proc = @ptrCast(*const fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, &glfw.getInstanceProcAddress); 175 | self.vkb = try BaseDispatch.load(vk_proc); 176 | 177 | if (enable_validation_layers and !try self.checkValidationLayerSupport()) { 178 | return error.MissingValidationLayer; 179 | } 180 | 181 | const app_info = vk.ApplicationInfo{ 182 | .p_application_name = "Hello Triangle", 183 | .application_version = vk.makeApiVersion(1, 0, 0, 0), 184 | .p_engine_name = "No Engine", 185 | .engine_version = vk.makeApiVersion(1, 0, 0, 0), 186 | .api_version = vk.API_VERSION_1_2, 187 | }; 188 | 189 | const extensions = try getRequiredExtensions(self.allocator); 190 | defer extensions.deinit(); 191 | 192 | var create_info = vk.InstanceCreateInfo{ 193 | .flags = .{}, 194 | .p_application_info = &app_info, 195 | .enabled_layer_count = 0, 196 | .pp_enabled_layer_names = undefined, 197 | .enabled_extension_count = @intCast(u32, extensions.items.len), 198 | .pp_enabled_extension_names = extensions.items.ptr, 199 | }; 200 | 201 | if (enable_validation_layers) { 202 | create_info.enabled_layer_count = validation_layers.len; 203 | create_info.pp_enabled_layer_names = &validation_layers; 204 | 205 | var debug_create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 206 | populateDebugMessengerCreateInfo(&debug_create_info); 207 | create_info.p_next = &debug_create_info; 208 | } 209 | 210 | self.instance = try self.vkb.createInstance(&create_info, null); 211 | 212 | self.vki = try InstanceDispatch.load(self.instance, vk_proc); 213 | } 214 | 215 | fn populateDebugMessengerCreateInfo(create_info: *vk.DebugUtilsMessengerCreateInfoEXT) void { 216 | create_info.* = .{ 217 | .flags = .{}, 218 | .message_severity = .{ 219 | .verbose_bit_ext = true, 220 | .warning_bit_ext = true, 221 | .error_bit_ext = true, 222 | }, 223 | .message_type = .{ 224 | .general_bit_ext = true, 225 | .validation_bit_ext = true, 226 | .performance_bit_ext = true, 227 | }, 228 | .pfn_user_callback = debugCallback, 229 | .p_user_data = null, 230 | }; 231 | } 232 | 233 | fn setupDebugMessenger(self: *Self) !void { 234 | if (!enable_validation_layers) return; 235 | 236 | var create_info: vk.DebugUtilsMessengerCreateInfoEXT = undefined; 237 | populateDebugMessengerCreateInfo(&create_info); 238 | 239 | self.debug_messenger = try self.vki.createDebugUtilsMessengerEXT(self.instance, &create_info, null); 240 | } 241 | 242 | fn createSurface(self: *Self) !void { 243 | if ((try glfw.createWindowSurface(self.instance, self.window.?, null, &self.surface)) != @enumToInt(vk.Result.success)) { 244 | return error.SurfaceInitFailed; 245 | } 246 | } 247 | 248 | fn pickPhysicalDevice(self: *Self) !void { 249 | var device_count: u32 = undefined; 250 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, null); 251 | 252 | if (device_count == 0) { 253 | return error.NoGPUsSupportVulkan; 254 | } 255 | 256 | const devices = try self.allocator.alloc(vk.PhysicalDevice, device_count); 257 | defer self.allocator.free(devices); 258 | _ = try self.vki.enumeratePhysicalDevices(self.instance, &device_count, devices.ptr); 259 | 260 | for (devices) |device| { 261 | if (try self.isDeviceSuitable(device)) { 262 | self.physical_device = device; 263 | break; 264 | } 265 | } 266 | 267 | if (self.physical_device == .null_handle) { 268 | return error.NoSuitableDevice; 269 | } 270 | } 271 | 272 | fn createLogicalDevice(self: *Self) !void { 273 | const indices = try self.findQueueFamilies(self.physical_device); 274 | const queue_priority = [_]f32{1}; 275 | 276 | var queue_create_info = [_]vk.DeviceQueueCreateInfo{ 277 | .{ 278 | .flags = .{}, 279 | .queue_family_index = indices.graphics_family.?, 280 | .queue_count = 1, 281 | .p_queue_priorities = &queue_priority, 282 | }, 283 | .{ 284 | .flags = .{}, 285 | .queue_family_index = indices.present_family.?, 286 | .queue_count = 1, 287 | .p_queue_priorities = &queue_priority, 288 | }, 289 | }; 290 | 291 | var create_info = vk.DeviceCreateInfo{ 292 | .flags = .{}, 293 | .queue_create_info_count = queue_create_info.len, 294 | .p_queue_create_infos = &queue_create_info, 295 | .enabled_layer_count = 0, 296 | .pp_enabled_layer_names = undefined, 297 | .enabled_extension_count = device_extensions.len, 298 | .pp_enabled_extension_names = &device_extensions, 299 | .p_enabled_features = null, 300 | }; 301 | 302 | if (enable_validation_layers) { 303 | create_info.enabled_layer_count = validation_layers.len; 304 | create_info.pp_enabled_layer_names = &validation_layers; 305 | } 306 | 307 | self.device = try self.vki.createDevice(self.physical_device, &create_info, null); 308 | 309 | self.vkd = try DeviceDispatch.load(self.device, self.vki.dispatch.vkGetDeviceProcAddr); 310 | 311 | self.graphics_queue = self.vkd.getDeviceQueue(self.device, indices.graphics_family.?, 0); 312 | self.present_queue = self.vkd.getDeviceQueue(self.device, indices.present_family.?, 0); 313 | } 314 | 315 | fn createSwapChain(self: *Self) !void { 316 | const swap_chain_support = try self.querySwapChainSupport(self.physical_device); 317 | defer swap_chain_support.deinit(); 318 | 319 | const surface_format: vk.SurfaceFormatKHR = chooseSwapSurfaceFormat(swap_chain_support.formats.?); 320 | const present_mode: vk.PresentModeKHR = chooseSwapPresentMode(swap_chain_support.present_modes.?); 321 | const extent: vk.Extent2D = try self.chooseSwapExtent(swap_chain_support.capabilities); 322 | 323 | var image_count = swap_chain_support.capabilities.min_image_count + 1; 324 | if (swap_chain_support.capabilities.max_image_count > 0) { 325 | image_count = std.math.min(image_count, swap_chain_support.capabilities.max_image_count); 326 | } 327 | 328 | const indices = try self.findQueueFamilies(self.physical_device); 329 | const queue_family_indices = [_]u32{ indices.graphics_family.?, indices.present_family.? }; 330 | const sharing_mode: vk.SharingMode = if (indices.graphics_family.? != indices.present_family.?) 331 | .concurrent 332 | else 333 | .exclusive; 334 | 335 | self.swap_chain = try self.vkd.createSwapchainKHR(self.device, &.{ 336 | .flags = .{}, 337 | .surface = self.surface, 338 | .min_image_count = image_count, 339 | .image_format = surface_format.format, 340 | .image_color_space = surface_format.color_space, 341 | .image_extent = extent, 342 | .image_array_layers = 1, 343 | .image_usage = .{ .color_attachment_bit = true }, 344 | .image_sharing_mode = sharing_mode, 345 | .queue_family_index_count = queue_family_indices.len, 346 | .p_queue_family_indices = &queue_family_indices, 347 | .pre_transform = swap_chain_support.capabilities.current_transform, 348 | .composite_alpha = .{ .opaque_bit_khr = true }, 349 | .present_mode = present_mode, 350 | .clipped = vk.TRUE, 351 | .old_swapchain = .null_handle, 352 | }, null); 353 | 354 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, null); 355 | self.swap_chain_images = try self.allocator.alloc(vk.Image, image_count); 356 | _ = try self.vkd.getSwapchainImagesKHR(self.device, self.swap_chain, &image_count, self.swap_chain_images.?.ptr); 357 | 358 | self.swap_chain_image_format = surface_format.format; 359 | self.swap_chain_extent = extent; 360 | } 361 | 362 | fn createImageViews(self: *Self) !void { 363 | self.swap_chain_image_views = try self.allocator.alloc(vk.ImageView, self.swap_chain_images.?.len); 364 | 365 | for (self.swap_chain_images.?) |image, i| { 366 | self.swap_chain_image_views.?[i] = try self.vkd.createImageView(self.device, &.{ 367 | .flags = .{}, 368 | .image = image, 369 | .view_type = .@"2d", 370 | .format = self.swap_chain_image_format, 371 | .components = .{ .r = .identity, .g = .identity, .b = .identity, .a = .identity }, 372 | .subresource_range = .{ 373 | .aspect_mask = .{ .color_bit = true }, 374 | .base_mip_level = 0, 375 | .level_count = 1, 376 | .base_array_layer = 0, 377 | .layer_count = 1, 378 | }, 379 | }, null); 380 | } 381 | } 382 | 383 | fn createRenderPass(self: *Self) !void { 384 | const color_attachment = [_]vk.AttachmentDescription{.{ 385 | .flags = .{}, 386 | .format = self.swap_chain_image_format, 387 | .samples = .{ .@"1_bit" = true }, 388 | .load_op = .clear, 389 | .store_op = .store, 390 | .stencil_load_op = .dont_care, 391 | .stencil_store_op = .dont_care, 392 | .initial_layout = .@"undefined", 393 | .final_layout = .present_src_khr, 394 | }}; 395 | 396 | const color_attachment_ref = [_]vk.AttachmentReference{.{ 397 | .attachment = 0, 398 | .layout = .color_attachment_optimal, 399 | }}; 400 | 401 | const subpass = [_]vk.SubpassDescription{.{ 402 | .flags = .{}, 403 | .pipeline_bind_point = .graphics, 404 | .input_attachment_count = 0, 405 | .p_input_attachments = undefined, 406 | .color_attachment_count = color_attachment_ref.len, 407 | .p_color_attachments = &color_attachment_ref, 408 | .p_resolve_attachments = null, 409 | .p_depth_stencil_attachment = null, 410 | .preserve_attachment_count = 0, 411 | .p_preserve_attachments = undefined, 412 | }}; 413 | 414 | self.render_pass = try self.vkd.createRenderPass(self.device, &.{ 415 | .flags = .{}, 416 | .attachment_count = color_attachment.len, 417 | .p_attachments = &color_attachment, 418 | .subpass_count = subpass.len, 419 | .p_subpasses = &subpass, 420 | .dependency_count = 0, 421 | .p_dependencies = undefined, 422 | }, null); 423 | } 424 | 425 | fn createGraphicsPipeline(self: *Self) !void { 426 | const vert_shader_module: vk.ShaderModule = try self.createShaderModule(resources.vert_09); 427 | defer self.vkd.destroyShaderModule(self.device, vert_shader_module, null); 428 | const frag_shader_module: vk.ShaderModule = try self.createShaderModule(resources.frag_09); 429 | defer self.vkd.destroyShaderModule(self.device, frag_shader_module, null); 430 | 431 | const shader_stages = [_]vk.PipelineShaderStageCreateInfo{ 432 | .{ 433 | .flags = .{}, 434 | .stage = .{ .vertex_bit = true }, 435 | .module = vert_shader_module, 436 | .p_name = "main", 437 | .p_specialization_info = null, 438 | }, 439 | .{ 440 | .flags = .{}, 441 | .stage = .{ .fragment_bit = true }, 442 | .module = frag_shader_module, 443 | .p_name = "main", 444 | .p_specialization_info = null, 445 | }, 446 | }; 447 | 448 | const vertex_input_info = vk.PipelineVertexInputStateCreateInfo{ 449 | .flags = .{}, 450 | .vertex_binding_description_count = 0, 451 | .p_vertex_binding_descriptions = undefined, 452 | .vertex_attribute_description_count = 0, 453 | .p_vertex_attribute_descriptions = undefined, 454 | }; 455 | 456 | const input_assembly = vk.PipelineInputAssemblyStateCreateInfo{ 457 | .flags = .{}, 458 | .topology = .triangle_list, 459 | .primitive_restart_enable = vk.FALSE, 460 | }; 461 | 462 | const viewport_state = vk.PipelineViewportStateCreateInfo{ 463 | .flags = .{}, 464 | .viewport_count = 1, 465 | .p_viewports = undefined, 466 | .scissor_count = 1, 467 | .p_scissors = undefined, 468 | }; 469 | 470 | const rasterizer = vk.PipelineRasterizationStateCreateInfo{ 471 | .flags = .{}, 472 | .depth_clamp_enable = vk.FALSE, 473 | .rasterizer_discard_enable = vk.FALSE, 474 | .polygon_mode = .fill, 475 | .cull_mode = .{ .back_bit = true }, 476 | .front_face = .clockwise, 477 | .depth_bias_enable = vk.FALSE, 478 | .depth_bias_constant_factor = 0, 479 | .depth_bias_clamp = 0, 480 | .depth_bias_slope_factor = 0, 481 | .line_width = 1, 482 | }; 483 | 484 | const multisampling = vk.PipelineMultisampleStateCreateInfo{ 485 | .flags = .{}, 486 | .rasterization_samples = .{ .@"1_bit" = true }, 487 | .sample_shading_enable = vk.FALSE, 488 | .min_sample_shading = 1, 489 | .p_sample_mask = null, 490 | .alpha_to_coverage_enable = vk.FALSE, 491 | .alpha_to_one_enable = vk.FALSE, 492 | }; 493 | 494 | const color_blend_attachment = [_]vk.PipelineColorBlendAttachmentState{.{ 495 | .blend_enable = vk.FALSE, 496 | .src_color_blend_factor = .one, 497 | .dst_color_blend_factor = .zero, 498 | .color_blend_op = .add, 499 | .src_alpha_blend_factor = .one, 500 | .dst_alpha_blend_factor = .zero, 501 | .alpha_blend_op = .add, 502 | .color_write_mask = .{ .r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true }, 503 | }}; 504 | 505 | const color_blending = vk.PipelineColorBlendStateCreateInfo{ 506 | .flags = .{}, 507 | .logic_op_enable = vk.FALSE, 508 | .logic_op = .copy, 509 | .attachment_count = color_blend_attachment.len, 510 | .p_attachments = &color_blend_attachment, 511 | .blend_constants = [_]f32{ 0, 0, 0, 0 }, 512 | }; 513 | const dynamic_states = [_]vk.DynamicState{ .viewport, .scissor }; 514 | 515 | const dynamic_state = vk.PipelineDynamicStateCreateInfo{ 516 | .flags = .{}, 517 | .dynamic_state_count = dynamic_states.len, 518 | .p_dynamic_states = &dynamic_states, 519 | }; 520 | 521 | self.pipeline_layout = try self.vkd.createPipelineLayout(self.device, &.{ 522 | .flags = .{}, 523 | .set_layout_count = 0, 524 | .p_set_layouts = undefined, 525 | .push_constant_range_count = 0, 526 | .p_push_constant_ranges = undefined, 527 | }, null); 528 | 529 | std.log.debug("temp hold for compiler errors: {any} {any} {any} {any} {any} {any} {any} {any}", .{ dynamic_state, color_blending, multisampling, rasterizer, viewport_state, vertex_input_info, shader_stages, input_assembly }); 530 | } 531 | 532 | fn createShaderModule(self: *Self, code: []const u8) !vk.ShaderModule { 533 | return try self.vkd.createShaderModule(self.device, &.{ 534 | .flags = .{}, 535 | .code_size = code.len, 536 | .p_code = @ptrCast([*]const u32, code), 537 | }, null); 538 | } 539 | fn chooseSwapSurfaceFormat(available_formats: []vk.SurfaceFormatKHR) vk.SurfaceFormatKHR { 540 | for (available_formats) |available_format| { 541 | if (available_format.format == .b8g8r8a8_srgb and available_format.color_space == .srgb_nonlinear_khr) { 542 | return available_format; 543 | } 544 | } 545 | 546 | return available_formats[0]; 547 | } 548 | 549 | fn chooseSwapPresentMode(available_present_modes: []vk.PresentModeKHR) vk.PresentModeKHR { 550 | for (available_present_modes) |available_present_mode| { 551 | if (available_present_mode == .mailbox_khr) { 552 | return available_present_mode; 553 | } 554 | } 555 | 556 | return .fifo_khr; 557 | } 558 | 559 | fn chooseSwapExtent(self: *Self, capabilities: vk.SurfaceCapabilitiesKHR) !vk.Extent2D { 560 | if (capabilities.current_extent.width != 0xFFFF_FFFF) { 561 | return capabilities.current_extent; 562 | } else { 563 | const window_size = try self.window.?.getFramebufferSize(); 564 | 565 | return vk.Extent2D{ 566 | .width = std.math.clamp(window_size.width, capabilities.min_image_extent.width, capabilities.max_image_extent.width), 567 | .height = std.math.clamp(window_size.height, capabilities.min_image_extent.height, capabilities.max_image_extent.height), 568 | }; 569 | } 570 | } 571 | 572 | fn querySwapChainSupport(self: *Self, device: vk.PhysicalDevice) !SwapChainSupportDetails { 573 | var details = SwapChainSupportDetails.init(self.allocator); 574 | 575 | details.capabilities = try self.vki.getPhysicalDeviceSurfaceCapabilitiesKHR(device, self.surface); 576 | 577 | var format_count: u32 = undefined; 578 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, null); 579 | 580 | if (format_count != 0) { 581 | details.formats = try details.allocator.alloc(vk.SurfaceFormatKHR, format_count); 582 | _ = try self.vki.getPhysicalDeviceSurfaceFormatsKHR(device, self.surface, &format_count, details.formats.?.ptr); 583 | } 584 | 585 | var present_mode_count: u32 = undefined; 586 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, null); 587 | 588 | if (present_mode_count != 0) { 589 | details.present_modes = try details.allocator.alloc(vk.PresentModeKHR, present_mode_count); 590 | _ = try self.vki.getPhysicalDeviceSurfacePresentModesKHR(device, self.surface, &present_mode_count, details.present_modes.?.ptr); 591 | } 592 | 593 | return details; 594 | } 595 | 596 | fn isDeviceSuitable(self: *Self, device: vk.PhysicalDevice) !bool { 597 | const indices = try self.findQueueFamilies(device); 598 | 599 | const extensions_supported = try self.checkDeviceExtensionSupport(device); 600 | 601 | var swap_chain_adequate = false; 602 | if (extensions_supported) { 603 | const swap_chain_support = try self.querySwapChainSupport(device); 604 | defer swap_chain_support.deinit(); 605 | 606 | swap_chain_adequate = swap_chain_support.formats != null and swap_chain_support.present_modes != null; 607 | } 608 | 609 | return indices.isComplete() and extensions_supported and swap_chain_adequate; 610 | } 611 | 612 | fn checkDeviceExtensionSupport(self: *Self, device: vk.PhysicalDevice) !bool { 613 | var extension_count: u32 = undefined; 614 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, null); 615 | 616 | const available_extensions = try self.allocator.alloc(vk.ExtensionProperties, extension_count); 617 | defer self.allocator.free(available_extensions); 618 | _ = try self.vki.enumerateDeviceExtensionProperties(device, null, &extension_count, available_extensions.ptr); 619 | 620 | const required_extensions = device_extensions[0..]; 621 | 622 | for (required_extensions) |required_extension| { 623 | for (available_extensions) |available_extension| { 624 | const len = std.mem.indexOfScalar(u8, &available_extension.extension_name, 0).?; 625 | const available_extension_name = available_extension.extension_name[0..len]; 626 | if (std.mem.eql(u8, std.mem.span(required_extension), available_extension_name)) { 627 | break; 628 | } 629 | } else { 630 | return false; 631 | } 632 | } 633 | 634 | return true; 635 | } 636 | 637 | fn findQueueFamilies(self: *Self, device: vk.PhysicalDevice) !QueueFamilyIndices { 638 | var indices: QueueFamilyIndices = .{}; 639 | 640 | var queue_family_count: u32 = 0; 641 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, null); 642 | 643 | const queue_families = try self.allocator.alloc(vk.QueueFamilyProperties, queue_family_count); 644 | defer self.allocator.free(queue_families); 645 | self.vki.getPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.ptr); 646 | 647 | for (queue_families) |queue_family, i| { 648 | if (indices.graphics_family == null and queue_family.queue_flags.graphics_bit) { 649 | indices.graphics_family = @intCast(u32, i); 650 | } else if (indices.present_family == null and (try self.vki.getPhysicalDeviceSurfaceSupportKHR(device, @intCast(u32, i), self.surface)) == vk.TRUE) { 651 | indices.present_family = @intCast(u32, i); 652 | } 653 | 654 | if (indices.isComplete()) { 655 | break; 656 | } 657 | } 658 | 659 | return indices; 660 | } 661 | 662 | fn getRequiredExtensions(allocator: Allocator) !std.ArrayListAligned([*:0]const u8, null) { 663 | var extensions = std.ArrayList([*:0]const u8).init(allocator); 664 | try extensions.appendSlice(try glfw.getRequiredInstanceExtensions()); 665 | 666 | if (enable_validation_layers) { 667 | try extensions.append(vk.extension_info.ext_debug_utils.name); 668 | } 669 | 670 | return extensions; 671 | } 672 | 673 | fn checkValidationLayerSupport(self: *Self) !bool { 674 | var layer_count: u32 = undefined; 675 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, null); 676 | 677 | var available_layers = try self.allocator.alloc(vk.LayerProperties, layer_count); 678 | defer self.allocator.free(available_layers); 679 | _ = try self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr); 680 | 681 | for (validation_layers) |layer_name| { 682 | var layer_found: bool = false; 683 | 684 | for (available_layers) |layer_properties| { 685 | const available_len = std.mem.indexOfScalar(u8, &layer_properties.layer_name, 0).?; 686 | const available_layer_name = layer_properties.layer_name[0..available_len]; 687 | if (std.mem.eql(u8, std.mem.span(layer_name), available_layer_name)) { 688 | layer_found = true; 689 | break; 690 | } 691 | } 692 | 693 | if (!layer_found) { 694 | return false; 695 | } 696 | } 697 | 698 | return true; 699 | } 700 | 701 | fn debugCallback(_: vk.DebugUtilsMessageSeverityFlagsEXT, _: vk.DebugUtilsMessageTypeFlagsEXT, p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque) callconv(vk.vulkan_call_conv) vk.Bool32 { 702 | if (p_callback_data != null) { 703 | std.log.debug("validation layer: {s}", .{p_callback_data.?.p_message}); 704 | } 705 | 706 | return vk.FALSE; 707 | } 708 | }; 709 | 710 | pub fn main() void { 711 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 712 | defer { 713 | const leaked = gpa.deinit(); 714 | if (leaked) std.log.err("MemLeak", .{}); 715 | } 716 | const allocator = gpa.allocator(); 717 | 718 | var app = HelloTriangleApplication.init(allocator); 719 | defer app.deinit(); 720 | app.run() catch |err| { 721 | std.log.err("application exited with error: {any}", .{err}); 722 | return; 723 | }; 724 | } 725 | --------------------------------------------------------------------------------