From e8121803898efe997d9ba10a38757d79125b910f Mon Sep 17 00:00:00 2001 From: David Allemang Date: Sun, 31 May 2026 16:25:04 -0400 Subject: [PATCH] hand-written interface wrapper --- build.zig | 44 ++++----- mise.toml | 4 +- src/main.zig | 168 ++++++++++++++++++++++---------- src/root.zig | 18 ---- src/volk.zig | 25 ----- vk/root.zig | 267 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 406 insertions(+), 120 deletions(-) delete mode 100644 src/root.zig delete mode 100644 src/volk.zig create mode 100644 vk/root.zig diff --git a/build.zig b/build.zig index 3e58c65..d4d6e34 100644 --- a/build.zig +++ b/build.zig @@ -7,38 +7,39 @@ pub fn build(b: *std.Build) void { const vk_dep = b.dependency("vk", .{}); const volk_dep = b.dependency("volk", .{}); - // const volk_c = b.addTranslateC(.{ - // .target = target, - // .optimize = optimize, - // .root_source_file = volk_dep.path("volk.c"), - // .link_libc = true, - // }); - // volk_c.addIncludePath(vk_dep.path("include")); - - const vk = b.addModule("vulkan", .{ + const volk_h = b.addTranslateC(.{ .target = target, .optimize = optimize, - .root_source_file = b.path("src/volk.zig"), + .root_source_file = volk_dep.path("volk.h"), .link_libc = true, }); - vk.addIncludePath(vk_dep.path("include")); - vk.addIncludePath(volk_dep.path(".")); - vk.addCSourceFile(.{ .file = volk_dep.path("volk.c"), .language = .c }); - const mod = b.addModule("zig_vk_api_test", .{ - .root_source_file = b.path("src/root.zig"), + const volk = b.addModule("volk", .{ .target = target, + .optimize = optimize, + .root_source_file = volk_h.getOutput(), + .link_libc = true, + }); + volk.addIncludePath(vk_dep.path("include")); + volk.addCSourceFile(.{ .file = volk_dep.path("volk.c"), .language = .c }); + + const vulkan = b.addModule("vulkan", .{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("vk/root.zig"), + .imports = &.{ + .{ .name = "volk", .module = volk }, + }, }); const exe = b.addExecutable(.{ - .name = "zig_vk_api_test", + .name = "main", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, .imports = &.{ - .{ .name = "zig_vk_api_test", .module = mod }, - .{ .name = "vk", .module = vk }, + .{ .name = "vk", .module = vulkan }, }, }), }); @@ -56,12 +57,6 @@ pub fn build(b: *std.Build) void { run_cmd.addArgs(args); } - const mod_tests = b.addTest(.{ - .root_module = mod, - }); - - const run_mod_tests = b.addRunArtifact(mod_tests); - const exe_tests = b.addTest(.{ .root_module = exe.root_module, }); @@ -69,6 +64,5 @@ pub fn build(b: *std.Build) void { const run_exe_tests = b.addRunArtifact(exe_tests); const test_step = b.step("test", "Run tests"); - test_step.dependOn(&run_mod_tests.step); test_step.dependOn(&run_exe_tests.step); } diff --git a/mise.toml b/mise.toml index abdd0f3..5f0e2a9 100644 --- a/mise.toml +++ b/mise.toml @@ -1,3 +1,3 @@ [tools] -zig = "latest" -zls = "latest" +zig = "0.16.0" +zls = "0.16.0" diff --git a/src/main.zig b/src/main.zig index 93a7a53..15a5394 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,67 +1,135 @@ const std = @import("std"); -const Io = std.Io; - +const builtin = @import("builtin"); const vk = @import("vk"); pub fn main() !void { - if (vk.c.volkInitialize() != vk.c.VK_SUCCESS) @panic("Failed to initialize vk"); - defer vk.c.volkFinalize(); + // const log = std.log.scoped(.main); - const app_info = vk.c.VkApplicationInfo{ - .sType = vk.c.VK_STRUCTURE_TYPE_APPLICATION_INFO, - .pNext = null, - .apiVersion = vk.c.VK_API_VERSION_1_4, - .pApplicationName = "WIP", - .applicationVersion = vk.c.VK_MAKE_VERSION(0, 0, 1), - .pEngineName = "WIP", - .engineVersion = vk.c.VK_MAKE_VERSION(0, 0, 1), - }; + try vk.init(); + defer vk.deinit(); - const ext_names: []const [*c]const u8 = &.{"VK_EXT_debug_utils"}; - const lyr_names: []const [*c]const u8 = &.{"VK_LAYER_KHRONOS_validation"}; + const ctx_ins: vk.InstanceContext = try .init(.{ + .api_version = .API_1_3, + .log_message_types = .{ + .general = false, + .performance = true, + .validation = true, + }, + }); + defer ctx_ins.deinit(); + const ins = ctx_ins.interface(); - const ins_info = vk.c.VkInstanceCreateInfo{ - .sType = vk.c.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .pNext = null, - .flags = 0, - .enabledExtensionCount = @intCast(ext_names.len), - .ppEnabledExtensionNames = ext_names.ptr, - .enabledLayerCount = @intCast(lyr_names.len), - .ppEnabledLayerNames = lyr_names.ptr, - .pApplicationInfo = &app_info, - }; - const deb_info = vk.c.VkDebugUtilsMessengerCreateInfoEXT{ - .sType = vk.c.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - .pNext = null, - .flags = 0, - }; + var pdevs_buf: [8]vk.PhysicalDevice = undefined; + for (try ins.enumerate_physical_devices(&pdevs_buf)) |pdev| { + std.log.debug("{s}", .{ins.get_physical_device_properties(pdev).deviceName}); + } - var ins: vk.c.VkInstance = undefined; - if (vk.c.vkCreateInstance.?(&ins_info, null, &ins) != vk.c.VK_SUCCESS) @panic("Failed to create VkInstance"); - vk.c.volkLoadInstanceOnly(ins); - defer vk.c.vkDestroyInstance.?(ins, null); + // defer ctx_ins.deinit(); + // ctx_ins.deinit(); - var pdevs: [3]vk.c.VkPhysicalDevice = undefined; - var pdev_count: u32 = 3; - if (vk.c.vkEnumeratePhysicalDevices.?( - ins, - &pdev_count, - &pdevs, - ) != vk.c.VK_SUCCESS) @panic("Failed to enumerate physical devices."); + // const ins = ctx_ins.interface(); - // pub const PFN_vkEnumeratePhysicalDevices = ?*const fn (instance: VkInstance, pPhysicalDeviceCount: [*c]u32, pPhysicalDevices: [*c]VkPhysicalDevice) callconv(.c) VkResult; - - // vk.c.vkEnumerateDeviceExtensionProperties(); - - // const info = vk.c.VkInstanceCreateInfo { - // .sType = vk.c.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + // const ext_names: []const [*c]const u8 = &.{"VK_EXT_debug_utils"}; + // const lyr_names: []const [*c]const u8 = &.{"VK_LAYER_KHRONOS_validation"}; + // + // const deb_info = vk.VkDebugUtilsMessengerCreateInfoEXT{ + // .sType = vk.VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, // .pNext = null, - // . + // .flags = 0, + // .messageSeverity = + // vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + // vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + // vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + // vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + // .messageType = + // vk.VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT | + // // vk.VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + // vk.VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + // .pfnUserCallback = &debug_messenger_callback, + // .pUserData = null, // }; - - // vk.c.vkCreateInstance(pCreateInfo: [*c]const VkInstanceCreateInfo, pAllocator: [*c]const VkAllocationCallbacks, pInstance: [*c]VkInstance); + // const ins_info = vk.VkInstanceCreateInfo{ + // .sType = vk.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + // .pNext = &deb_info, + // .flags = 0, + // .enabledExtensionCount = @intCast(ext_names.len), + // .ppEnabledExtensionNames = ext_names.ptr, + // .enabledLayerCount = @intCast(lyr_names.len), + // .ppEnabledLayerNames = lyr_names.ptr, + // .pApplicationInfo = &app_info, + // }; + // + // var ins: vk.VkInstance = undefined; + // if (vk.vkCreateInstance.?(&ins_info, null, &ins) != vk.VK_SUCCESS) @panic("Failed to create VkInstance"); + // vk.volkLoadInstanceOnly(ins); + // defer vk.vkDestroyInstance.?(ins, null); + // + // var deb: vk.VkDebugUtilsMessengerEXT = undefined; + // if (vk.vkCreateDebugUtilsMessengerEXT.?(ins, &deb_info, null, &deb) != vk.VK_SUCCESS) @panic("Failed to create VkDebugUtilsMessengerEXT"); + // defer vk.vkDestroyDebugUtilsMessengerEXT.?(ins, deb, null); + // + // var pdevs: [8]vk.VkPhysicalDevice = undefined; + // var pdev_count: u32 = 8; + // if (vk.vkEnumeratePhysicalDevices.?( + // ins, + // &pdev_count, + // &pdevs, + // ) != vk.VK_SUCCESS) @panic("Failed to enumerate physical devices."); + // const pdev = pdevs[0]; // assume the drivers will put the best device first + // var props : vk.VkPhysicalDeviceProperties2 = .{ .sType = vk.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; + // vk.vkGetPhysicalDeviceProperties2.?(pdev, &props); + // log.info("Selected device {s} (API {d}.{d}.{d}, Driver {d})", .{ + // props.properties.deviceName, + // vk.VK_API_VERSION_MAJOR(props.properties.apiVersion), + // vk.VK_API_VERSION_MINOR(props.properties.apiVersion), + // vk.VK_API_VERSION_PATCH(props.properties.apiVersion), + // vk.VK_VERSION_MAJOR(props.properties.driverVersion), + // }); } +// fn debug_messenger_callback( +// 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_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 log = std.log.scoped(.vulkan); +// +// const fmt = "{s}{s}"; +// const arg = .{ +// types_str.items, +// p_callback_data.*.pMessage, +// }; +// +// if (message_severity & vk.VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT != 0) { +// log.err(fmt, arg); +// if (builtin.mode == .Debug) @breakpoint(); +// } 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; +// } + // pub fn main(init: std.process.Init) !void { // std.debug.print("{any}\n", .{vk}); // diff --git a/src/root.zig b/src/root.zig deleted file mode 100644 index 5a71250..0000000 --- a/src/root.zig +++ /dev/null @@ -1,18 +0,0 @@ -//! By convention, root.zig is the root source file when making a package. -const std = @import("std"); -const Io = std.Io; - -/// This is a documentation comment to explain the `printAnotherMessage` function below. -/// -/// Accepting an `Io.Writer` instance is a handy way to write reusable code. -pub fn printAnotherMessage(writer: *Io.Writer) Io.Writer.Error!void { - try writer.print("Run `zig build test` to run the tests.\n", .{}); -} - -pub fn add(a: i32, b: i32) i32 { - return a + b; -} - -test "basic add functionality" { - try std.testing.expect(add(3, 7) == 10); -} diff --git a/src/volk.zig b/src/volk.zig deleted file mode 100644 index f372e55..0000000 --- a/src/volk.zig +++ /dev/null @@ -1,25 +0,0 @@ -const std = @import("std"); - -pub const c = @cImport({ - @cInclude("volk.h"); -}); - -pub const InstanceTable = blk: { - const info = @typeInfo(c.VolkInstanceTable).@"struct"; - - var field_names: [info.fields.len][]const u8 = undefined; - var field_types: [info.fields.len]type = undefined; - var field_attrs: [info.fields.len]std.builtin.Type.StructField.Attributes = undefined; - - for (info.fields, &field_names, &field_types, &field_attrs) |field, *field_name, *field_type, *field_attr| { - field_name.* = field.name; - field_type.* = std.meta.Child(field.type); - field_attr.* = .{}; - } - - break :blk @Struct(.auto, null, field_names, field_types, field_attrs); -}; - -// pub const InstanceTable: type = { -// -// }; diff --git a/vk/root.zig b/vk/root.zig new file mode 100644 index 0000000..69159ef --- /dev/null +++ b/vk/root.zig @@ -0,0 +1,267 @@ +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; +}