From 401f82c523bade934098aaaddce198c7c2a160ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Gasi=C5=84ski?= Date: Sun, 1 Sep 2024 23:09:24 +0200 Subject: [PATCH] WIP: Start refactoring --- build.zig | 2 +- src/Context.zig | 368 +++++++++++++++++++++++++ src/Mesh.zig | 4 +- src/Swapchain.zig | 189 +++++++++++++ src/main.zig | 4 +- src/utilities.zig | 22 -- src/validation_layers.zig | 61 +++++ src/vulkan_renderer.zig | 562 ++------------------------------------ 8 files changed, 640 insertions(+), 572 deletions(-) create mode 100644 src/Context.zig create mode 100644 src/Swapchain.zig create mode 100644 src/validation_layers.zig diff --git a/build.zig b/build.zig index 2090664..612b8ca 100644 --- a/build.zig +++ b/build.zig @@ -41,7 +41,7 @@ pub fn build(b: *std.Build) void { // SDL2 const sdl_sdk = sdl.init(b, .{}); sdl_sdk.link(exe, .dynamic, sdl.Library.SDL2); - exe.root_module.addImport("sdl2", sdl_sdk.getWrapperModuleVulkan(vkzig_bindings)); + exe.root_module.addImport("sdl", sdl_sdk.getWrapperModuleVulkan(vkzig_bindings)); // zmath const zmath = b.dependency("zmath", .{}); diff --git a/src/Context.zig b/src/Context.zig new file mode 100644 index 0000000..3da6512 --- /dev/null +++ b/src/Context.zig @@ -0,0 +1,368 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const vk = @import("vulkan"); +const sdl = @import("sdl"); +const img = @import("zstbi"); + +const validation = @import("./validation_layers.zig"); +const Swapchain = @import("Swapchain.zig"); + +const device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name}; + +pub const apis: []const vk.ApiInfo = &.{ + vk.features.version_1_0, + vk.features.version_1_1, + vk.features.version_1_2, + vk.features.version_1_3, + vk.extensions.khr_surface, + vk.extensions.khr_swapchain, + vk.extensions.ext_debug_utils, +}; + +const enable_validation_layers = builtin.mode == .Debug; +const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; + +const QueueFamilyIndices = struct { + graphics_family: ?u32 = null, + presentation_family: ?u32 = null, + + fn isValid(self: QueueFamilyIndices) bool { + return self.graphics_family != null and self.presentation_family != null; + } +}; + +const BaseDispatch = vk.BaseWrapper(apis); +const InstanceDispatch = vk.InstanceWrapper(apis); +const DeviceDispatch = vk.DeviceWrapper(apis); + +pub const Instance = vk.InstanceProxy(apis); +pub const Device = vk.DeviceProxy(apis); +pub const Queue = vk.QueueProxy(apis); + +// --- + +const Self = @This(); + +allocator: std.mem.Allocator, + +vkb: BaseDispatch, + +window: sdl.Window, + +instance: Instance, +physical_device: vk.PhysicalDevice, +device: Device, + +graphics_queue: Queue, +presentation_queue: Queue, +surface: vk.SurfaceKHR, + +debug_utils: ?vk.DebugUtilsMessengerEXT, + +pub fn init(allocator: std.mem.Allocator, window: sdl.Window) !Self { + var self: Self = undefined; + + self.window = window; + self.allocator = allocator; + self.vkb = try BaseDispatch.load(try sdl.vulkan.getVkGetInstanceProcAddr()); + + img.init(allocator); + + try self.createInstance(); + + if (enable_validation_layers) { + self.debug_utils = try validation.createDebugMessenger(self.instance); + } + + try self.createSurface(); + + try self.getPhysicalDevice(); + try self.createLogicalDevice(); + + return self; +} + +pub fn deinit(self: *Self) void { + if (enable_validation_layers) { + self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null); + } + + self.device.destroyDevice(null); + self.instance.destroySurfaceKHR(self.surface, null); + self.instance.destroyInstance(null); + + self.allocator.destroy(self.device.wrapper); + self.allocator.destroy(self.instance.wrapper); + + img.deinit(); +} + +fn createInstance(self: *Self) !void { + if (enable_validation_layers and !self.checkValidationLayersSupport()) { + // TODO Better error + return error.LayerNotPresent; + } + + const extensions = try self.getRequiredExtensions(); + defer self.allocator.free(extensions); + + std.debug.print("[Required instance extensions]\n", .{}); + for (extensions) |ext| { + std.debug.print("\t- {s}\n", .{ext}); + } + + if (!try self.checkInstanceExtensions(&extensions)) { + return error.ExtensionNotPresent; + } + + const app_info = vk.ApplicationInfo{ + .p_application_name = "Vulkan SDL Test", + .application_version = vk.makeApiVersion(0, 0, 1, 0), + .p_engine_name = "Vulkan SDL Test", + .engine_version = vk.makeApiVersion(0, 0, 1, 0), + .api_version = vk.API_VERSION_1_3, + }; + + var instance_create_info: vk.InstanceCreateInfo = .{ + .p_application_info = &app_info, + .enabled_extension_count = @intCast(extensions.len), + .pp_enabled_extension_names = @ptrCast(extensions), + }; + + if (enable_validation_layers) { + const debug_create_info = validation.getDebugUtilsCreateInfo(); + + instance_create_info.enabled_layer_count = @intCast(validation_layers.len); + instance_create_info.pp_enabled_layer_names = &validation_layers; + instance_create_info.p_next = &debug_create_info; + } + + const instance_handle = try self.vkb.createInstance(&instance_create_info, null); + const vki = try self.allocator.create(InstanceDispatch); + errdefer self.allocator.destroy(vki); + vki.* = try InstanceDispatch.load(instance_handle, self.vkb.dispatch.vkGetInstanceProcAddr); + + self.instance = Instance.init(instance_handle, vki); +} + +fn createSurface(self: *Self) !void { + self.surface = try sdl.vulkan.createSurface(self.window, self.instance.handle); +} + +fn getPhysicalDevice(self: *Self) !void { + var pdev_count: u32 = 0; + _ = try self.instance.enumeratePhysicalDevices(&pdev_count, null); + + const pdevs = try self.allocator.alloc(vk.PhysicalDevice, pdev_count); + defer self.allocator.free(pdevs); + + _ = try self.instance.enumeratePhysicalDevices(&pdev_count, pdevs.ptr); + + for (pdevs) |pdev| { + if (self.checkDeviceSuitable(pdev)) { + self.physical_device = pdev; + break; + } + } else { + // TODO Obviously needs to be something else + unreachable; + } +} + +fn createLogicalDevice(self: *Self) !void { + const indices = try self.getQueueFamilies(self.physical_device); + // 1 is the highest priority + const priority = [_]f32{1}; + + const qci = [_]vk.DeviceQueueCreateInfo{ + .{ + .queue_family_index = indices.graphics_family.?, + .queue_count = 1, + .p_queue_priorities = &priority, + }, + .{ + .queue_family_index = indices.presentation_family.?, + .queue_count = 1, + .p_queue_priorities = &priority, + }, + }; + + const queue_count: u32 = if (indices.graphics_family.? == indices.presentation_family.?) + 1 + else + 2; + + // Device features + const device_features: vk.PhysicalDeviceFeatures = .{ + .sampler_anisotropy = vk.TRUE, // Enable anisotropy + }; + + const device_create_info: vk.DeviceCreateInfo = .{ + .queue_create_info_count = queue_count, + .p_queue_create_infos = &qci, + .pp_enabled_extension_names = &device_extensions, + .enabled_extension_count = @intCast(device_extensions.len), + .p_enabled_features = &device_features, + }; + + const device_handle = try self.instance.createDevice(self.physical_device, &device_create_info, null); + + const vkd = try self.allocator.create(DeviceDispatch); + errdefer self.allocator.destroy(vkd); + vkd.* = try DeviceDispatch.load(device_handle, self.instance.wrapper.dispatch.vkGetDeviceProcAddr); + + self.device = Device.init(device_handle, vkd); + + const queues = try self.getDeviceQueues(); + + self.graphics_queue = Queue.init(queues[0], self.device.wrapper); + self.presentation_queue = Queue.init(queues[1], self.device.wrapper); +} + +fn getRequiredExtensions(self: Self) ![][*:0]const u8 { + var ext_count = sdl.vulkan.getInstanceExtensionsCount(self.window); + + if (enable_validation_layers) { + ext_count += 1; + } + + var extensions = try self.allocator.alloc([*:0]const u8, ext_count); + _ = try sdl.vulkan.getInstanceExtensions(self.window, extensions); + + if (enable_validation_layers) { + extensions[extensions.len - 1] = vk.extensions.ext_debug_utils.name; + } + + return extensions; +} + +fn getQueueFamilies(self: Self, pdev: vk.PhysicalDevice) !QueueFamilyIndices { + var indices: QueueFamilyIndices = .{ .graphics_family = null }; + + var queue_family_count: u32 = 0; + self.instance.getPhysicalDeviceQueueFamilyProperties(pdev, &queue_family_count, null); + + const queue_family_list = try self.allocator.alloc(vk.QueueFamilyProperties, queue_family_count); + defer self.allocator.free(queue_family_list); + + self.instance.getPhysicalDeviceQueueFamilyProperties(pdev, &queue_family_count, queue_family_list.ptr); + + for (queue_family_list, 0..) |queue_family, i| { + if (queue_family.queue_count > 0 and queue_family.queue_flags.graphics_bit) { + indices.graphics_family = @intCast(i); + } + + const presentation_support = try self.instance.getPhysicalDeviceSurfaceSupportKHR(pdev, @intCast(i), self.surface); + if (queue_family.queue_count > 0 and presentation_support == vk.TRUE) { + indices.presentation_family = @intCast(i); + } + + if (indices.isValid()) { + return indices; + } + } + + unreachable; +} + +fn getDeviceQueues(self: Self) ![2]vk.Queue { + const indices = try self.getQueueFamilies(self.physical_device); + + const graphics_queue = self.device.getDeviceQueue(indices.graphics_family.?, 0); + const presentation_queue = self.device.getDeviceQueue(indices.presentation_family.?, 0); + return .{ graphics_queue, presentation_queue }; +} + +fn checkInstanceExtensions(self: Self, required_extensions: *const [][*:0]const u8) !bool { + var prop_count: u32 = 0; + _ = try self.vkb.enumerateInstanceExtensionProperties(null, &prop_count, null); + + const props = try self.allocator.alloc(vk.ExtensionProperties, prop_count); + defer self.allocator.free(props); + + _ = try self.vkb.enumerateInstanceExtensionProperties(null, &prop_count, props.ptr); + + for (required_extensions.*) |required_extension| { + for (props) |prop| { + if (std.mem.eql(u8, std.mem.sliceTo(&prop.extension_name, 0), std.mem.span(required_extension))) { + break; + } + } else { + return false; + } + } + + return true; +} + +fn checkDeviceExtensions(self: Self, pdev: vk.PhysicalDevice) !bool { + var prop_count: u32 = 0; + _ = try self.instance.enumerateDeviceExtensionProperties(pdev, null, &prop_count, null); + + if (prop_count == 0) { + return false; + } + + const props = try self.allocator.alloc(vk.ExtensionProperties, prop_count); + defer self.allocator.free(props); + + _ = try self.instance.enumerateDeviceExtensionProperties(pdev, null, &prop_count, props.ptr); + + for (device_extensions) |device_extension| { + for (props) |prop| { + if (std.mem.eql(u8, std.mem.sliceTo(&prop.extension_name, 0), std.mem.span(device_extension))) { + break; + } + } else { + return false; + } + } + + return true; +} + +fn checkDeviceSuitable(self: Self, pdev: vk.PhysicalDevice) bool { + const pdev_properties = self.instance.getPhysicalDeviceProperties(pdev); + + if (pdev_properties.device_type == .cpu) { + return false; + } + + const pdev_features = self.instance.getPhysicalDeviceFeatures(pdev); + const queue_family_indices = self.getQueueFamilies(pdev) catch return false; + const extension_support = self.checkDeviceExtensions(pdev) catch return false; + + const swapchain_details = Swapchain.getSwapchainDetails( + self.allocator, + self.instance, + self.physical_device, + self.surface, + ) catch return false; + defer self.allocator.free(swapchain_details.formats); + defer self.allocator.free(swapchain_details.presentation_modes); + + const swapchain_valid = swapchain_details.formats.len != 0 and swapchain_details.formats.len != 0; + + return queue_family_indices.isValid() and extension_support and swapchain_valid and pdev_features.sampler_anisotropy == vk.TRUE; +} + +fn checkValidationLayersSupport(self: Self) bool { + var layer_count: u32 = undefined; + _ = self.vkb.enumerateInstanceLayerProperties(&layer_count, null) catch return false; + + const available_layers = self.allocator.alloc(vk.LayerProperties, layer_count) catch unreachable; + defer self.allocator.free(available_layers); + + _ = self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr) catch return false; + + for (validation_layers) |validation_layer| { + for (available_layers) |available_layer| { + if (std.mem.eql(u8, std.mem.span(validation_layer), std.mem.sliceTo(&available_layer.layer_name, 0))) { + return true; + } + } + } + + return false; +} diff --git a/src/Mesh.zig b/src/Mesh.zig index 74e52d4..1f577e6 100644 --- a/src/Mesh.zig +++ b/src/Mesh.zig @@ -4,8 +4,8 @@ const zm = @import("zmath"); const Utilities = @import("utilities.zig"); const Vertex = Utilities.Vertex; -const Device = @import("vulkan_renderer.zig").Device; -const Instance = @import("vulkan_renderer.zig").Instance; +const Device = @import("Context.zig").Device; +const Instance = @import("Context.zig").Instance; const Model = @import("vulkan_renderer.zig").Model; const Self = @This(); diff --git a/src/Swapchain.zig b/src/Swapchain.zig new file mode 100644 index 0000000..36b7668 --- /dev/null +++ b/src/Swapchain.zig @@ -0,0 +1,189 @@ +const std = @import("std"); +const vk = @import("vulkan"); +const sdl = @import("sdl"); + +const Context = @import("Context.zig"); +const Instance = Context.Instance; + +pub const SwapchainDetails = struct { + surface_capabilities: vk.SurfaceCapabilitiesKHR, + formats: []vk.SurfaceFormatKHR, + presentation_modes: []vk.PresentModeKHR, +}; + +pub const SwapchainImage = struct { + image: vk.Image, + image_view: vk.ImageView, +}; + +const Self = @This(); + +allocator: std.mem.Allocator, + +ctx: Context, + +swapchain: vk.SwapchainKHR, + +swapchain_images: []SwapchainImage, +swapchain_framebuffers: []vk.Framebuffer, + +pub fn create(allocator: std.mem.Allocator, context: Context) !Self { + var self: Self = undefined; + + self.allocator = allocator; + self.ctx = context; + + const swapchain_details = try getSwapchainDetails(allocator, context.instance, context.physical_device, context.surface); + defer self.allocator.free(swapchain_details.formats); + defer self.allocator.free(swapchain_details.presentation_modes); + + // 1. Choose best surface format + const surface_format = chooseBestSurfaceFormat(swapchain_details.formats); + // 2. Choose best presentation mode + const present_mode = chooseBestPresentationMode(swapchain_details.presentation_modes); + // 3. Choose swapchain image resolution + const extent = chooseSwapExtent(&context.window, swapchain_details.surface_capabilities); + + // How many images are in the swapchain? Get 1 more than the minimum to allow triple buffering + var image_count: u32 = swapchain_details.surface_capabilities.min_image_count + 1; + const max_image_count = swapchain_details.surface_capabilities.max_image_count; + + // Clamp down if higher + // If 0, it means it's limitless + if (max_image_count != 0 and image_count > max_image_count) { + image_count = max_image_count; + } + + var swapchain_create_info: vk.SwapchainCreateInfoKHR = .{ + .image_format = surface_format.format, + .image_color_space = surface_format.color_space, + .present_mode = present_mode, + .image_extent = extent, + .min_image_count = image_count, + .image_array_layers = 1, // Number of layers for each image + .image_usage = .{ .color_attachment_bit = true }, // What attachment will images be used as + .pre_transform = swapchain_details.surface_capabilities.current_transform, // Transform to perform on swapchain images + .composite_alpha = .{ .opaque_bit_khr = true }, // How to handle blending images with external graphics (e.g.: other windows) + .clipped = vk.TRUE, // Whether to clip parts of images not in view (e.g.: behind another window, off-screen, etc...) + .old_swapchain = .null_handle, // Links old one to quickly share responsibilities in case it's been destroyed and replaced + .surface = context.surface, + .image_sharing_mode = .exclusive, + }; + + // Get queue family indices + const family_indices = try self.getQueueFamilies(self.physical_device); + + // If graphic and presentation families are different, then swapchain must let images be shared between families + + if (family_indices.graphics_family.? != family_indices.presentation_family.?) { + const qfi = [_]u32{ + family_indices.graphics_family.?, + family_indices.presentation_family.?, + }; + + swapchain_create_info.image_sharing_mode = .concurrent; + swapchain_create_info.queue_family_index_count = @intCast(qfi.len); // Number of queues to share images between + swapchain_create_info.p_queue_family_indices = &qfi; + } + + self.swapchain = try self.device.createSwapchainKHR(&swapchain_create_info, null); + self.swapchain_image_format = surface_format.format; + self.extent = extent; + + // Swapchain images + var swapchain_image_count: u32 = 0; + _ = try self.device.getSwapchainImagesKHR(self.swapchain, &swapchain_image_count, null); + + const images = try self.allocator.alloc(vk.Image, swapchain_image_count); + defer self.allocator.free(images); + + _ = try self.device.getSwapchainImagesKHR(self.swapchain, &swapchain_image_count, images.ptr); + + self.swapchain_images = try self.allocator.alloc(SwapchainImage, swapchain_image_count); + + for (images, 0..) |image, i| { + self.swapchain_images[i] = .{ + .image = image, + .image_view = try self.createImageView(image, self.swapchain_image_format, .{ .color_bit = true }), + }; + } + + return self; +} + +pub fn getSwapchainDetails(allocator: std.mem.Allocator, instance: Instance, pdev: vk.PhysicalDevice, surface: vk.SurfaceKHR) !SwapchainDetails { + // Capabilities + const surface_capabilities = try instance.getPhysicalDeviceSurfaceCapabilitiesKHR(pdev, surface); + + // Formats + var format_count: u32 = 0; + _ = try instance.getPhysicalDeviceSurfaceFormatsKHR(pdev, surface, &format_count, null); + + const formats = try allocator.alloc(vk.SurfaceFormatKHR, format_count); + _ = try instance.getPhysicalDeviceSurfaceFormatsKHR(pdev, surface, &format_count, formats.ptr); + + // Presentation modes + var present_mode_count: u32 = 0; + _ = try instance.getPhysicalDeviceSurfacePresentModesKHR(pdev, surface, &present_mode_count, null); + + const presentation_modes = try allocator.alloc(vk.PresentModeKHR, format_count); + _ = try instance.getPhysicalDeviceSurfacePresentModesKHR(pdev, surface, &present_mode_count, presentation_modes.ptr); + + return .{ + .surface_capabilities = surface_capabilities, + .formats = formats, + .presentation_modes = presentation_modes, + }; +} + +// Format: VK_FORMAT_R8G8B8A8_UNORM (VK_FORMAT_B8G8R8A8_UNORM as backup) +// Color space: VK_COLOR_SPACE_SRGB_NONLINEAR_KHR +fn chooseBestSurfaceFormat(formats: []vk.SurfaceFormatKHR) vk.SurfaceFormatKHR { + // If only one format available and is undefined, then this means all formats are available + if (formats.len == 1 and formats[0].format == vk.Format.undefined) { + return .{ + .format = vk.Format.r8g8b8a8_srgb, + .color_space = vk.ColorSpaceKHR.srgb_nonlinear_khr, + }; + } + + for (formats) |format| { + if ((format.format == vk.Format.r8g8b8a8_srgb or format.format == vk.Format.b8g8r8a8_srgb) and format.color_space == vk.ColorSpaceKHR.srgb_nonlinear_khr) { + return format; + } + } + + return formats[0]; +} + +fn chooseBestPresentationMode(presentation_modes: []vk.PresentModeKHR) vk.PresentModeKHR { + for (presentation_modes) |presentation_mode| { + if (presentation_mode == vk.PresentModeKHR.mailbox_khr) { + return presentation_mode; + } + } + + // Use FIFO as Vulkan spec says it must be present + return vk.PresentModeKHR.fifo_khr; +} + +fn chooseSwapExtent(window: *sdl.Window, surface_capabilities: vk.SurfaceCapabilitiesKHR) vk.Extent2D { + // If the current extent is at max value, the extent can vary. Otherwise it's the size of the window + if (surface_capabilities.current_extent.width != std.math.maxInt(u32)) { + return surface_capabilities.current_extent; + } + + // If value can very, need to set the extent manually + const framebuffer_size = sdl.vulkan.getDrawableSize(window); + + var extent: vk.Extent2D = .{ + .width = @intCast(framebuffer_size.width), + .height = @intCast(framebuffer_size.height), + }; + + // Surface also defines max and min, so make sure it's within boundaries by clamping values + extent.width = @max(surface_capabilities.min_image_extent.width, @min(surface_capabilities.max_image_extent.width, extent.width)); + extent.height = @max(surface_capabilities.min_image_extent.height, @min(surface_capabilities.max_image_extent.height, extent.height)); + + return extent; +} diff --git a/src/main.zig b/src/main.zig index 9855338..5475116 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,6 @@ const std = @import("std"); const vk = @import("vulkan"); -const sdl = @import("sdl2"); +const sdl = @import("sdl"); const zm = @import("zmath"); const VulkanRenderer = @import("vulkan_renderer.zig").VulkanRenderer; @@ -62,7 +62,7 @@ pub fn main() !void { defer _ = gpa.deinit(); const allocator = gpa.allocator(); - var vulkan_renderer = try VulkanRenderer.init(window, allocator); + var vulkan_renderer = try VulkanRenderer.init(allocator, window); defer vulkan_renderer.deinit(); var delta = Delta.new(); diff --git a/src/utilities.zig b/src/utilities.zig index 94eed98..428b2e4 100644 --- a/src/utilities.zig +++ b/src/utilities.zig @@ -5,8 +5,6 @@ const Instance = @import("vulkan_renderer.zig").Instance; const Device = @import("vulkan_renderer.zig").Device; const CommandBuffer = @import("vulkan_renderer.zig").CommandBuffer; -pub const device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name}; - pub const Vector3 = @Vector(3, f32); pub const Vector2 = @Vector(2, f32); @@ -17,26 +15,6 @@ pub const Vertex = struct { tex: Vector2, // Texture coords (u, v) }; -pub const QueueFamilyIndices = struct { - graphics_family: ?u32 = null, - presentation_family: ?u32 = null, - - pub fn isValid(self: QueueFamilyIndices) bool { - return self.graphics_family != null and self.presentation_family != null; - } -}; - -pub const SwapchainDetails = struct { - surface_capabilities: vk.SurfaceCapabilitiesKHR, - formats: []vk.SurfaceFormatKHR, - presentation_modes: []vk.PresentModeKHR, -}; - -pub const SwapchainImage = struct { - image: vk.Image, - image_view: vk.ImageView, -}; - pub fn findMemoryTypeIndex(pdev: vk.PhysicalDevice, instance: Instance, allowed_types: u32, properties: vk.MemoryPropertyFlags) u32 { // Get properties of physical device memory const memory_properties = instance.getPhysicalDeviceMemoryProperties(pdev); diff --git a/src/validation_layers.zig b/src/validation_layers.zig new file mode 100644 index 0000000..69ee9c6 --- /dev/null +++ b/src/validation_layers.zig @@ -0,0 +1,61 @@ +const std = @import("std"); + +const vk = @import("vulkan"); +const Instance = @import("Context.zig").Instance; + +// Validation layers stuff +pub fn createDebugMessenger(instance: Instance) !vk.DebugUtilsMessengerEXT { + const debug_create_info = getDebugUtilsCreateInfo(); + + return try instance.createDebugUtilsMessengerEXT(&debug_create_info, null); +} + +pub fn getDebugUtilsCreateInfo() vk.DebugUtilsMessengerCreateInfoEXT { + return vk.DebugUtilsMessengerCreateInfoEXT{ + .message_severity = .{ .verbose_bit_ext = true, .warning_bit_ext = true, .error_bit_ext = true }, + .message_type = .{ .general_bit_ext = true, .validation_bit_ext = true, .performance_bit_ext = true }, + .pfn_user_callback = debugCallback, + }; +} + +fn debugCallback( + message_severity: vk.DebugUtilsMessageSeverityFlagsEXT, + message_types: vk.DebugUtilsMessageTypeFlagsEXT, + p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, + p_user_data: ?*anyopaque, +) callconv(vk.vulkan_call_conv) vk.Bool32 { + _ = p_user_data; + const severity = getMessageSeverityLabel(message_severity); + const message_type = getMessageTypeLabel(message_types); + + std.debug.print("[{s}] ({s}): {s}\n=====\n", .{ severity, message_type, p_callback_data.?.p_message.? }); + return vk.TRUE; +} + +inline fn getMessageSeverityLabel(message_severity: vk.DebugUtilsMessageSeverityFlagsEXT) []const u8 { + if (message_severity.verbose_bit_ext) { + return "VERBOSE"; + } else if (message_severity.info_bit_ext) { + return "INFO"; + } else if (message_severity.warning_bit_ext) { + return "WARNING"; + } else if (message_severity.error_bit_ext) { + return "ERROR"; + } else { + unreachable; + } +} + +inline fn getMessageTypeLabel(message_types: vk.DebugUtilsMessageTypeFlagsEXT) []const u8 { + if (message_types.general_bit_ext) { + return "general"; + } else if (message_types.validation_bit_ext) { + return "validation"; + } else if (message_types.performance_bit_ext) { + return "performance"; + } else if (message_types.device_address_binding_bit_ext) { + return "device_address_binding"; + } else { + return "unknown"; + } +} diff --git a/src/vulkan_renderer.zig b/src/vulkan_renderer.zig index 75eabd9..a528d90 100644 --- a/src/vulkan_renderer.zig +++ b/src/vulkan_renderer.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const sdl = @import("sdl2"); +const sdl = @import("sdl"); const vk = @import("vulkan"); const builtin = @import("builtin"); const shaders = @import("shaders"); @@ -9,39 +9,19 @@ const ai = @import("assimp.zig").c; const StringUtils = @import("string_utils.zig"); const Utilities = @import("utilities.zig"); -const QueueFamilyIndices = Utilities.QueueFamilyIndices; -const SwapchainDetails = Utilities.SwapchainDetails; -const SwapchainImage = Utilities.SwapchainImage; const Vertex = Utilities.Vertex; const Vector3 = Utilities.Vector3; +const Context = @import("Context.zig"); +const Instance = @import("Context.zig").Instance; +const Swapchain = @import("Swapchain.zig"); + const Mesh = @import("Mesh.zig"); const MeshModel = @import("MeshModel.zig"); -const enable_validation_layers = builtin.mode == .Debug; -const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; - const MAX_FRAME_DRAWS: u32 = 2; const MAX_OBJECTS: u32 = 20; - -const apis: []const vk.ApiInfo = &.{ - vk.features.version_1_0, - vk.features.version_1_1, - vk.features.version_1_2, - vk.features.version_1_3, - vk.extensions.khr_surface, - vk.extensions.khr_swapchain, - vk.extensions.ext_debug_utils, -}; - -const BaseDispatch = vk.BaseWrapper(apis); -const InstanceDispatch = vk.InstanceWrapper(apis); -const DeviceDispatch = vk.DeviceWrapper(apis); - -pub const Instance = vk.InstanceProxy(apis); -pub const Device = vk.DeviceProxy(apis); -pub const Queue = vk.QueueProxy(apis); -pub const CommandBuffer = vk.CommandBufferProxy(apis); +pub const CommandBuffer = vk.CommandBufferProxy(Context.apis); const UboViewProjection = struct { projection: zm.Mat align(16), @@ -57,31 +37,20 @@ pub const VulkanRenderer = struct { allocator: std.mem.Allocator, - vkb: BaseDispatch, - - window: sdl.Window, - current_frame: u32 = 0, + context: Context, + + swapchain: Swapchain, + // Scene settings ubo_view_projection: UboViewProjection, // Main - instance: Instance, - physical_device: vk.PhysicalDevice, - device: Device, - graphics_queue: Queue, - presentation_queue: Queue, - surface: vk.SurfaceKHR, - swapchain: vk.SwapchainKHR, viewport: vk.Viewport, scissor: vk.Rect2D, texture_sampler: vk.Sampler, - swapchain_images: []SwapchainImage, - swapchain_framebuffers: []vk.Framebuffer, - command_buffers: []CommandBuffer, - depth_buffer_image: []vk.Image, depth_buffer_image_memory: []vk.DeviceMemory, depth_buffer_image_view: []vk.ImageView, @@ -106,6 +75,9 @@ pub const VulkanRenderer = struct { vp_uniform_buffer: []vk.Buffer, vp_uniform_buffer_memory: []vk.DeviceMemory, + // TODO + command_buffers: []CommandBuffer, + // Assets image_files: std.ArrayList(img.Image), texture_images: std.ArrayList(vk.Image), @@ -135,28 +107,13 @@ pub const VulkanRenderer = struct { render_finished: [MAX_FRAME_DRAWS]vk.Semaphore, draw_fences: [MAX_FRAME_DRAWS]vk.Fence, - debug_utils: ?vk.DebugUtilsMessengerEXT, - - pub fn init(window: sdl.Window, allocator: std.mem.Allocator) !Self { + pub fn init(allocator: std.mem.Allocator, window: sdl.Window) !Self { var self: Self = undefined; - self.window = window; + self.context = try Context.init(allocator, window); self.current_frame = 0; - self.allocator = allocator; - self.vkb = try BaseDispatch.load(try sdl.vulkan.getVkGetInstanceProcAddr()); + self.swapchain = try Swapchain.create(allocator, self.context); - img.init(allocator); - - try self.createInstance(); - try self.createSurface(); - - if (enable_validation_layers) { - self.debug_utils = try createDebugMessenger(self.instance); - } - - try self.getPhysicalDevice(); - try self.createLogicalDevice(); - try self.createSwapchain(); try self.createColourBufferImage(); try self.createDepthBufferImage(); try self.createRenderPass(); @@ -272,10 +229,6 @@ pub const VulkanRenderer = struct { pub fn deinit(self: *Self) void { self.device.deviceWaitIdle() catch undefined; - if (enable_validation_layers) { - self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null); - } - for (0..self.model_list.items.len) |i| { self.model_list.items[i].destroy(); } @@ -366,193 +319,8 @@ pub const VulkanRenderer = struct { self.allocator.free(self.swapchain_images); self.device.destroySwapchainKHR(self.swapchain, null); - self.device.destroyDevice(null); - self.instance.destroySurfaceKHR(self.surface, null); - self.instance.destroyInstance(null); - self.allocator.destroy(self.device.wrapper); - self.allocator.destroy(self.instance.wrapper); - - img.deinit(); - } - - fn createInstance(self: *Self) !void { - if (enable_validation_layers and !self.checkValidationLayersSupport()) { - // TODO Better error - return error.LayerNotPresent; - } - - const extensions = try self.getRequiredExtensions(); - defer self.allocator.free(extensions); - - std.debug.print("[Required instance extensions]\n", .{}); - for (extensions) |ext| { - std.debug.print("\t- {s}\n", .{ext}); - } - - if (!try self.checkInstanceExtensions(&extensions)) { - return error.ExtensionNotPresent; - } - - const app_info = vk.ApplicationInfo{ - .p_application_name = "Vulkan SDL Test", - .application_version = vk.makeApiVersion(0, 0, 1, 0), - .p_engine_name = "Vulkan SDL Test", - .engine_version = vk.makeApiVersion(0, 0, 1, 0), - .api_version = vk.API_VERSION_1_3, - }; - - var instance_create_info: vk.InstanceCreateInfo = .{ - .p_application_info = &app_info, - .enabled_extension_count = @intCast(extensions.len), - .pp_enabled_extension_names = @ptrCast(extensions), - }; - - if (enable_validation_layers) { - const debug_create_info = getDebugUtilsCreateInfo(); - - instance_create_info.enabled_layer_count = @intCast(validation_layers.len); - instance_create_info.pp_enabled_layer_names = &validation_layers; - instance_create_info.p_next = &debug_create_info; - } - - const instance_handle = try self.vkb.createInstance(&instance_create_info, null); - const vki = try self.allocator.create(InstanceDispatch); - errdefer self.allocator.destroy(vki); - vki.* = try InstanceDispatch.load(instance_handle, self.vkb.dispatch.vkGetInstanceProcAddr); - - self.instance = Instance.init(instance_handle, vki); - } - - fn createSurface(self: *Self) !void { - self.surface = try sdl.vulkan.createSurface(self.window, self.instance.handle); - } - - fn createLogicalDevice(self: *Self) !void { - const indices = try self.getQueueFamilies(self.physical_device); - // 1 is the highest priority - const priority = [_]f32{1}; - - const qci = [_]vk.DeviceQueueCreateInfo{ - .{ - .queue_family_index = indices.graphics_family.?, - .queue_count = 1, - .p_queue_priorities = &priority, - }, - .{ - .queue_family_index = indices.presentation_family.?, - .queue_count = 1, - .p_queue_priorities = &priority, - }, - }; - - const queue_count: u32 = if (indices.graphics_family.? == indices.presentation_family.?) - 1 - else - 2; - - // Device features - const device_features: vk.PhysicalDeviceFeatures = .{ - .sampler_anisotropy = vk.TRUE, // Enable anisotropy - }; - - const device_create_info: vk.DeviceCreateInfo = .{ - .queue_create_info_count = queue_count, - .p_queue_create_infos = &qci, - .pp_enabled_extension_names = &Utilities.device_extensions, - .enabled_extension_count = @intCast(Utilities.device_extensions.len), - .p_enabled_features = &device_features, - }; - - const device_handle = try self.instance.createDevice(self.physical_device, &device_create_info, null); - - const vkd = try self.allocator.create(DeviceDispatch); - errdefer self.allocator.destroy(vkd); - vkd.* = try DeviceDispatch.load(device_handle, self.instance.wrapper.dispatch.vkGetDeviceProcAddr); - - self.device = Device.init(device_handle, vkd); - - const queues = try self.getDeviceQueues(); - - self.graphics_queue = Queue.init(queues[0], self.device.wrapper); - self.presentation_queue = Queue.init(queues[1], self.device.wrapper); - } - - fn createSwapchain(self: *Self) !void { - const swapchain_details = try self.getSwapchainDetails(self.physical_device); - defer self.allocator.free(swapchain_details.formats); - defer self.allocator.free(swapchain_details.presentation_modes); - - // 1. Choose best surface format - const surface_format = chooseBestSurfaceFormat(swapchain_details.formats); - // 2. Choose best presentation mode - const present_mode = chooseBestPresentationMode(swapchain_details.presentation_modes); - // 3. Choose swapchain image resolution - const extent = chooseSwapExtent(&self.window, swapchain_details.surface_capabilities); - - // How many images are in the swapchain? Get 1 more than the minimum to allow triple buffering - var image_count: u32 = swapchain_details.surface_capabilities.min_image_count + 1; - const max_image_count = swapchain_details.surface_capabilities.max_image_count; - - // Clamp down if higher - // If 0, it means it's limitless - if (max_image_count != 0 and image_count > max_image_count) { - image_count = max_image_count; - } - - var swapchain_create_info: vk.SwapchainCreateInfoKHR = .{ - .image_format = surface_format.format, - .image_color_space = surface_format.color_space, - .present_mode = present_mode, - .image_extent = extent, - .min_image_count = image_count, - .image_array_layers = 1, // Number of layers for each image - .image_usage = .{ .color_attachment_bit = true }, // What attachment will images be used as - .pre_transform = swapchain_details.surface_capabilities.current_transform, // Transform to perform on swapchain images - .composite_alpha = .{ .opaque_bit_khr = true }, // How to handle blending images with external graphics (e.g.: other windows) - .clipped = vk.TRUE, // Whether to clip parts of images not in view (e.g.: behind another window, off-screen, etc...) - .old_swapchain = .null_handle, // Links old one to quickly share responsibilities in case it's been destroyed and replaced - .surface = self.surface, - .image_sharing_mode = .exclusive, - }; - - // Get queue family indices - const family_indices = try self.getQueueFamilies(self.physical_device); - - // If graphic and presentation families are different, then swapchain must let images be shared between families - - if (family_indices.graphics_family.? != family_indices.presentation_family.?) { - const qfi = [_]u32{ - family_indices.graphics_family.?, - family_indices.presentation_family.?, - }; - - swapchain_create_info.image_sharing_mode = .concurrent; - swapchain_create_info.queue_family_index_count = @intCast(qfi.len); // Number of queues to share images between - swapchain_create_info.p_queue_family_indices = &qfi; - } - - self.swapchain = try self.device.createSwapchainKHR(&swapchain_create_info, null); - self.swapchain_image_format = surface_format.format; - self.extent = extent; - - // Swapchain images - var swapchain_image_count: u32 = 0; - _ = try self.device.getSwapchainImagesKHR(self.swapchain, &swapchain_image_count, null); - - const images = try self.allocator.alloc(vk.Image, swapchain_image_count); - defer self.allocator.free(images); - - _ = try self.device.getSwapchainImagesKHR(self.swapchain, &swapchain_image_count, images.ptr); - - self.swapchain_images = try self.allocator.alloc(SwapchainImage, swapchain_image_count); - - for (images, 0..) |image, i| { - self.swapchain_images[i] = .{ - .image = image, - .image_view = try self.createImageView(image, self.swapchain_image_format, .{ .color_bit = true }), - }; - } + self.context.deinit(); } fn createRenderPass(self: *Self) !void { @@ -1539,193 +1307,6 @@ pub const VulkanRenderer = struct { try command_buffer.endCommandBuffer(); } - fn getPhysicalDevice(self: *Self) !void { - var pdev_count: u32 = 0; - _ = try self.instance.enumeratePhysicalDevices(&pdev_count, null); - - const pdevs = try self.allocator.alloc(vk.PhysicalDevice, pdev_count); - defer self.allocator.free(pdevs); - - _ = try self.instance.enumeratePhysicalDevices(&pdev_count, pdevs.ptr); - - for (pdevs) |pdev| { - if (self.checkDeviceSuitable(pdev)) { - self.physical_device = pdev; - break; - } - } else { - // TODO Obviously needs to be something else - unreachable; - } - } - - fn getRequiredExtensions(self: Self) ![][*:0]const u8 { - var ext_count = sdl.vulkan.getInstanceExtensionsCount(self.window); - - if (enable_validation_layers) { - ext_count += 1; - } - - var extensions = try self.allocator.alloc([*:0]const u8, ext_count); - _ = try sdl.vulkan.getInstanceExtensions(self.window, extensions); - - if (enable_validation_layers) { - extensions[extensions.len - 1] = vk.extensions.ext_debug_utils.name; - } - - return extensions; - } - - fn getQueueFamilies(self: Self, pdev: vk.PhysicalDevice) !QueueFamilyIndices { - var indices: QueueFamilyIndices = .{ .graphics_family = null }; - - var queue_family_count: u32 = 0; - self.instance.getPhysicalDeviceQueueFamilyProperties(pdev, &queue_family_count, null); - - const queue_family_list = try self.allocator.alloc(vk.QueueFamilyProperties, queue_family_count); - defer self.allocator.free(queue_family_list); - - self.instance.getPhysicalDeviceQueueFamilyProperties(pdev, &queue_family_count, queue_family_list.ptr); - - for (queue_family_list, 0..) |queue_family, i| { - if (queue_family.queue_count > 0 and queue_family.queue_flags.graphics_bit) { - indices.graphics_family = @intCast(i); - } - - const presentation_support = try self.instance.getPhysicalDeviceSurfaceSupportKHR(pdev, @intCast(i), self.surface); - if (queue_family.queue_count > 0 and presentation_support == vk.TRUE) { - indices.presentation_family = @intCast(i); - } - - if (indices.isValid()) { - return indices; - } - } - - unreachable; - } - - fn getDeviceQueues(self: Self) ![2]vk.Queue { - const indices = try self.getQueueFamilies(self.physical_device); - - const graphics_queue = self.device.getDeviceQueue(indices.graphics_family.?, 0); - const presentation_queue = self.device.getDeviceQueue(indices.presentation_family.?, 0); - return .{ graphics_queue, presentation_queue }; - } - - fn checkInstanceExtensions(self: Self, required_extensions: *const [][*:0]const u8) !bool { - var prop_count: u32 = 0; - _ = try self.vkb.enumerateInstanceExtensionProperties(null, &prop_count, null); - - const props = try self.allocator.alloc(vk.ExtensionProperties, prop_count); - defer self.allocator.free(props); - - _ = try self.vkb.enumerateInstanceExtensionProperties(null, &prop_count, props.ptr); - - for (required_extensions.*) |required_extension| { - for (props) |prop| { - if (std.mem.eql(u8, std.mem.sliceTo(&prop.extension_name, 0), std.mem.span(required_extension))) { - break; - } - } else { - return false; - } - } - - return true; - } - - fn checkDeviceExtensions(self: Self, pdev: vk.PhysicalDevice) !bool { - var prop_count: u32 = 0; - _ = try self.instance.enumerateDeviceExtensionProperties(pdev, null, &prop_count, null); - - if (prop_count == 0) { - return false; - } - - const props = try self.allocator.alloc(vk.ExtensionProperties, prop_count); - defer self.allocator.free(props); - - _ = try self.instance.enumerateDeviceExtensionProperties(pdev, null, &prop_count, props.ptr); - - for (Utilities.device_extensions) |device_extension| { - for (props) |prop| { - if (std.mem.eql(u8, std.mem.sliceTo(&prop.extension_name, 0), std.mem.span(device_extension))) { - break; - } - } else { - return false; - } - } - - return true; - } - - fn checkDeviceSuitable(self: Self, pdev: vk.PhysicalDevice) bool { - const pdev_properties = self.instance.getPhysicalDeviceProperties(pdev); - - if (pdev_properties.device_type == .cpu) { - return false; - } - - const pdev_features = self.instance.getPhysicalDeviceFeatures(pdev); - const queue_family_indices = self.getQueueFamilies(pdev) catch return false; - const extension_support = self.checkDeviceExtensions(pdev) catch return false; - - const swapchain_details = self.getSwapchainDetails(pdev) catch return false; - defer self.allocator.free(swapchain_details.formats); - defer self.allocator.free(swapchain_details.presentation_modes); - - const swapchain_valid = swapchain_details.formats.len != 0 and swapchain_details.formats.len != 0; - - return queue_family_indices.isValid() and extension_support and swapchain_valid and pdev_features.sampler_anisotropy == vk.TRUE; - } - - fn checkValidationLayersSupport(self: Self) bool { - var layer_count: u32 = undefined; - _ = self.vkb.enumerateInstanceLayerProperties(&layer_count, null) catch return false; - - const available_layers = self.allocator.alloc(vk.LayerProperties, layer_count) catch unreachable; - defer self.allocator.free(available_layers); - - _ = self.vkb.enumerateInstanceLayerProperties(&layer_count, available_layers.ptr) catch return false; - - for (validation_layers) |validation_layer| { - for (available_layers) |available_layer| { - if (std.mem.eql(u8, std.mem.span(validation_layer), std.mem.sliceTo(&available_layer.layer_name, 0))) { - return true; - } - } - } - - return false; - } - - fn getSwapchainDetails(self: Self, pdev: vk.PhysicalDevice) !SwapchainDetails { - // Capabilities - const surface_capabilities = try self.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(pdev, self.surface); - - // Formats - var format_count: u32 = 0; - _ = try self.instance.getPhysicalDeviceSurfaceFormatsKHR(pdev, self.surface, &format_count, null); - - const formats = try self.allocator.alloc(vk.SurfaceFormatKHR, format_count); - _ = try self.instance.getPhysicalDeviceSurfaceFormatsKHR(pdev, self.surface, &format_count, formats.ptr); - - // Presentation modes - var present_mode_count: u32 = 0; - _ = try self.instance.getPhysicalDeviceSurfacePresentModesKHR(pdev, self.surface, &present_mode_count, null); - - const presentation_modes = try self.allocator.alloc(vk.PresentModeKHR, format_count); - _ = try self.instance.getPhysicalDeviceSurfacePresentModesKHR(pdev, self.surface, &present_mode_count, presentation_modes.ptr); - - return .{ - .surface_capabilities = surface_capabilities, - .formats = formats, - .presentation_modes = presentation_modes, - }; - } - fn createImage( self: *Self, width: u32, @@ -2016,58 +1597,6 @@ pub const VulkanRenderer = struct { } }; -// Format: VK_FORMAT_R8G8B8A8_UNORM (VK_FORMAT_B8G8R8A8_UNORM as backup) -// Color space: VK_COLOR_SPACE_SRGB_NONLINEAR_KHR -fn chooseBestSurfaceFormat(formats: []vk.SurfaceFormatKHR) vk.SurfaceFormatKHR { - // If only one format available and is undefined, then this means all formats are available - if (formats.len == 1 and formats[0].format == vk.Format.undefined) { - return .{ - .format = vk.Format.r8g8b8a8_srgb, - .color_space = vk.ColorSpaceKHR.srgb_nonlinear_khr, - }; - } - - for (formats) |format| { - if ((format.format == vk.Format.r8g8b8a8_srgb or format.format == vk.Format.b8g8r8a8_srgb) and format.color_space == vk.ColorSpaceKHR.srgb_nonlinear_khr) { - return format; - } - } - - return formats[0]; -} - -fn chooseBestPresentationMode(presentation_modes: []vk.PresentModeKHR) vk.PresentModeKHR { - for (presentation_modes) |presentation_mode| { - if (presentation_mode == vk.PresentModeKHR.mailbox_khr) { - return presentation_mode; - } - } - - // Use FIFO as Vulkan spec says it must be present - return vk.PresentModeKHR.fifo_khr; -} - -fn chooseSwapExtent(window: *sdl.Window, surface_capabilities: vk.SurfaceCapabilitiesKHR) vk.Extent2D { - // If the current extent is at max value, the extent can vary. Otherwise it's the size of the window - if (surface_capabilities.current_extent.width != std.math.maxInt(u32)) { - return surface_capabilities.current_extent; - } - - // If value can very, need to set the extent manually - const framebuffer_size = sdl.vulkan.getDrawableSize(window); - - var extent: vk.Extent2D = .{ - .width = @intCast(framebuffer_size.width), - .height = @intCast(framebuffer_size.height), - }; - - // Surface also defines max and min, so make sure it's within boundaries by clamping values - extent.width = @max(surface_capabilities.min_image_extent.width, @min(surface_capabilities.max_image_extent.width, extent.width)); - extent.height = @max(surface_capabilities.min_image_extent.height, @min(surface_capabilities.max_image_extent.height, extent.height)); - - return extent; -} - fn chooseSupportedFormat( pdev: vk.PhysicalDevice, instance: Instance, @@ -2091,60 +1620,3 @@ fn chooseSupportedFormat( return null; } - -// Validation layers stuff -fn createDebugMessenger(instance: Instance) !vk.DebugUtilsMessengerEXT { - const debug_create_info = getDebugUtilsCreateInfo(); - - return try instance.createDebugUtilsMessengerEXT(&debug_create_info, null); -} - -fn getDebugUtilsCreateInfo() vk.DebugUtilsMessengerCreateInfoEXT { - return vk.DebugUtilsMessengerCreateInfoEXT{ - .message_severity = .{ .verbose_bit_ext = true, .warning_bit_ext = true, .error_bit_ext = true }, - .message_type = .{ .general_bit_ext = true, .validation_bit_ext = true, .performance_bit_ext = true }, - .pfn_user_callback = debugCallback, - }; -} - -fn debugCallback( - message_severity: vk.DebugUtilsMessageSeverityFlagsEXT, - message_types: vk.DebugUtilsMessageTypeFlagsEXT, - p_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, - p_user_data: ?*anyopaque, -) callconv(vk.vulkan_call_conv) vk.Bool32 { - _ = p_user_data; - const severity = getMessageSeverityLabel(message_severity); - const message_type = getMessageTypeLabel(message_types); - - std.debug.print("[{s}] ({s}): {s}\n=====\n", .{ severity, message_type, p_callback_data.?.p_message.? }); - return vk.TRUE; -} - -inline fn getMessageSeverityLabel(message_severity: vk.DebugUtilsMessageSeverityFlagsEXT) []const u8 { - if (message_severity.verbose_bit_ext) { - return "VERBOSE"; - } else if (message_severity.info_bit_ext) { - return "INFO"; - } else if (message_severity.warning_bit_ext) { - return "WARNING"; - } else if (message_severity.error_bit_ext) { - return "ERROR"; - } else { - unreachable; - } -} - -inline fn getMessageTypeLabel(message_types: vk.DebugUtilsMessageTypeFlagsEXT) []const u8 { - if (message_types.general_bit_ext) { - return "general"; - } else if (message_types.validation_bit_ext) { - return "validation"; - } else if (message_types.performance_bit_ext) { - return "performance"; - } else if (message_types.device_address_binding_bit_ext) { - return "device_address_binding"; - } else { - return "unknown"; - } -}