const std = @import("std"); const builtin = @import("builtin"); const vk = @import("volk"); pub const InitError = error{ InitializationFailed, Unknown, }; pub fn init() InitError!void { switch (vk.volkInitialize()) { vk.VK_ERROR_INITIALIZATION_FAILED => return error.InitializationFailed, vk.VK_SUCCESS => {}, else => return error.Unknown, } } pub fn deinit() void { vk.volkFinalize(); } pub const Version = packed struct(u32) { patch: u12, minor: u10, major: u7, variant: u3 = 0, pub const API_1_0: Version = .{ .variant = 0, .major = 1, .minor = 0, .patch = 0 }; pub const API_1_1: Version = .{ .variant = 0, .major = 1, .minor = 1, .patch = 0 }; pub const API_1_2: Version = .{ .variant = 0, .major = 1, .minor = 2, .patch = 0 }; pub const API_1_3: Version = .{ .variant = 0, .major = 1, .minor = 3, .patch = 0 }; pub const API_1_4: Version = .{ .variant = 0, .major = 1, .minor = 4, .patch = 0 }; }; pub const MessageTypes = struct { general: bool, performance: bool, validation: bool, }; pub const InstanceConfig = struct { api_version: Version, app_name: [*:0]const u8 = "", app_version: Version = .{ .major = 0, .minor = 0, .patch = 0 }, engine_name: [*:0]const u8 = "", engine_version: Version = .{ .major = 0, .minor = 0, .patch = 0 }, layer_names: []const [*:0]const u8 = &.{}, extension_names: []const [*:0]const u8 = &.{}, log_scope: ?@EnumLiteral() = .vulkan, log_message_types: MessageTypes = .{ .general = false, .performance = true, .validation = true, }, }; pub const PhysicalDevice = *const opaque {}; pub const Instance = struct { handle: vk.VkInstance, vtable: *const vk.VolkInstanceTable, pub fn enumerate_physical_devices(self: Instance, buf: []PhysicalDevice) ![]PhysicalDevice { var count: u32 = @intCast(buf.len); if (self.vtable.vkEnumeratePhysicalDevices.?(self.handle, &count, @ptrCast(buf.ptr)) != vk.VK_SUCCESS) return error.Unknown; return buf[0..count]; } pub fn get_physical_device_properties(self: Instance, pdev: PhysicalDevice) vk.VkPhysicalDeviceProperties { var props: vk.VkPhysicalDeviceProperties = undefined; self.vtable.vkGetPhysicalDeviceProperties.?(@ptrCast(@constCast(pdev)), &props); return props; } }; pub const InstanceContext = struct { handle: vk.VkInstance, vtable: vk.VolkInstanceTable, debug: ?vk.VkDebugUtilsMessengerEXT, pub const InstanceCreateError = error{ Unknown, }; pub fn init(config: InstanceConfig) InstanceCreateError!InstanceContext { const callback: vk.PFN_vkDebugUtilsMessengerCallbackEXT = if (config.log_scope) |scope| default_debug_callback(scope) else null; var message_severity: vk.VkDebugUtilsMessageSeverityFlagsEXT = 0; if (config.log_scope) |scope| { if (std.log.logEnabled(.debug, scope)) message_severity |= vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; if (std.log.logEnabled(.info, scope)) message_severity |= vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; if (std.log.logEnabled(.warn, scope)) message_severity |= vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; if (std.log.logEnabled(.err, scope)) message_severity |= vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; } var message_type: vk.VkDebugUtilsMessageTypeFlagsEXT = 0; if (config.log_message_types.general) message_type |= vk.VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT; if (config.log_message_types.performance) message_type |= vk.VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; if (config.log_message_types.validation) message_type |= vk.VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; var extension_names_buf: [256][*:0]const u8 = undefined; var extension_names: std.ArrayListUnmanaged([*:0]const u8) = .initBuffer(&extension_names_buf); extension_names.appendSliceAssumeCapacity(config.extension_names); var layer_names_buf: [256][*:0]const u8 = undefined; var layer_names: std.ArrayListUnmanaged([*:0]const u8) = .initBuffer(&layer_names_buf); layer_names.appendSliceAssumeCapacity(config.layer_names); var do_logging = message_severity != 0 and message_type != 0; if (do_logging) { for (extension_names.items) |name| { if (std.mem.eql(u8, std.mem.span(name), "VK_EXT_debug_utils")) break; } else { var extension_props_buf: [256]vk.VkExtensionProperties = undefined; var count: u32 = @intCast(extension_props_buf.len); if (vk.vkEnumerateInstanceExtensionProperties.?(null, &count, &extension_props_buf) != vk.VK_SUCCESS) { do_logging = false; } else { for (extension_props_buf[0..count]) |prop| { if (std.mem.eql(u8, std.mem.sliceTo(&prop.extensionName, 0), "VK_EXT_debug_utils")) { extension_names.appendAssumeCapacity("VK_EXT_debug_utils"); break; } } else { do_logging = false; } } } } if (do_logging and config.log_message_types.validation) { for (layer_names.items) |name| { if (std.mem.eql(u8, std.mem.span(name), "VK_LAYER_KHRONOS_validation")) break; } else { var layer_props_buf: [256]vk.VkLayerProperties = undefined; var count: u32 = @intCast(layer_props_buf.len); if (vk.vkEnumerateInstanceLayerProperties.?(&count, &layer_props_buf) != vk.VK_SUCCESS) { do_logging = false; } else { for (layer_props_buf[0..count]) |prop| { if (std.mem.eql(u8, std.mem.sliceTo(&prop.layerName, 0), "VK_LAYER_KHRONOS_validation")) { layer_names.appendAssumeCapacity("VK_LAYER_KHRONOS_validation"); break; } } } } } const debug_info: vk.VkDebugUtilsMessengerCreateInfoEXT = .{ .sType = vk.VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, .pNext = null, .flags = 0, .messageSeverity = message_severity, .messageType = message_type, .pfnUserCallback = callback, .pUserData = null, }; const app_info = vk.VkApplicationInfo{ .sType = vk.VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = null, .apiVersion = @bitCast(config.api_version), .applicationVersion = @bitCast(config.app_version), .engineVersion = @bitCast(config.engine_version), .pApplicationName = config.app_name, .pEngineName = config.engine_name, }; const info = vk.VkInstanceCreateInfo{ .sType = vk.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = if (do_logging) &debug_info else null, .flags = 0, .pApplicationInfo = &app_info, .enabledLayerCount = @intCast(layer_names.items.len), .ppEnabledLayerNames = layer_names.items.ptr, .enabledExtensionCount = @intCast(extension_names.items.len), .ppEnabledExtensionNames = extension_names.items.ptr, }; // std.log.debug("{s} ", .{}) var result: InstanceContext = undefined; switch (vk.vkCreateInstance.?(&info, null, &result.handle)) { vk.VK_SUCCESS => {}, else => return error.Unknown, } vk.volkLoadInstanceTable(&result.vtable, result.handle); result.debug = null; if (do_logging) { var debug_handle: vk.VkDebugUtilsMessengerEXT = undefined; if (result.vtable.vkCreateDebugUtilsMessengerEXT.?(result.handle, &debug_info, null, &debug_handle) != vk.VK_SUCCESS) { do_logging = false; } else { result.debug = debug_handle; } } return result; } pub fn interface(self: *const InstanceContext) Instance { return .{ .handle = self.handle, .vtable = &self.vtable }; } pub fn deinit(self: *const InstanceContext) void { if (self.debug) |debug_handle| { self.vtable.vkDestroyDebugUtilsMessengerEXT.?(self.handle, debug_handle, null); } self.vtable.vkDestroyInstance.?(self.handle, null); } }; fn default_debug_callback(scope: @EnumLiteral()) vk.PFN_vkDebugUtilsMessengerCallbackEXT { const impl = struct { fn impl( message_severity: vk.VkDebugUtilsMessageSeverityFlagBitsEXT, message_types: vk.VkDebugUtilsMessageTypeFlagsEXT, p_callback_data: [*c]const vk.VkDebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque, ) callconv(.c) vk.VkBool32 { var types_buf: [64]u8 = undefined; var types_str: std.ArrayListUnmanaged(u8) = .initBuffer(&types_buf); if (message_types & vk.VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT != 0) { if (types_str.items.len > 0) types_str.appendSliceAssumeCapacity(" "); types_str.appendSliceAssumeCapacity("general"); } if (message_types & vk.VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT != 0) { if (types_str.items.len > 0) types_str.appendSliceAssumeCapacity(" "); types_str.appendSliceAssumeCapacity("validation"); } if (message_types & vk.VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT != 0) { if (types_str.items.len > 0) types_str.appendSliceAssumeCapacity(" "); types_str.appendSliceAssumeCapacity("performance"); } if (types_str.items.len > 0) types_str.appendSliceAssumeCapacity(": "); const fmt = "{s}{s}"; const arg = .{ types_str.items, p_callback_data.*.pMessage, }; const log = std.log.scoped(scope); if (message_severity & vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT != 0) { log.err(fmt, arg); switch (builtin.mode) { .Debug, .ReleaseSafe => @breakpoint(), else => {}, } } else if (message_severity & vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT != 0) { log.warn(fmt, arg); } else if (message_severity & vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT != 0) { log.info(fmt, arg); } else if (message_severity & vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT != 0) { log.debug(fmt, arg); } else { log.debug(fmt, arg); } return vk.VK_FALSE; } }; return &impl.impl; }