const std = @import("std"); const builtin = @import("builtin"); const vk = @import("vk"); const c = @import("c.zig"); pub const Bus = @import("au/Bus.zig"); pub const SwapChain = @import("au/SwapChain.zig"); pub const use_debug_messenger = switch (builtin.mode) { .Debug, .ReleaseSafe => true, .ReleaseSmall, .ReleaseFast => false, }; 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.khr_dynamic_rendering, if (use_debug_messenger) vk.extensions.ext_debug_utils else .{}, }; pub const device_extensions: []const [*:0]const u8 = &.{ // todo somehow sync this with APIs above? vk.extensions.khr_swapchain.name, vk.extensions.khr_dynamic_rendering.name, }; pub const app_info: vk.ApplicationInfo = .{ .p_application_name = "zig-glfw-vulkan", .application_version = vk.makeApiVersion(0, 0, 0, 0), .p_engine_name = "zig-glfw-vulkan", .engine_version = vk.makeApiVersion(0, 0, 0, 0), .api_version = vk.API_VERSION_1_3, }; pub const BaseWrapper = vk.BaseWrapper(apis); pub const InstanceWrapper = vk.InstanceWrapper(apis); pub const DeviceWrapper = vk.DeviceWrapper(apis); pub const InstanceProxy = vk.InstanceProxy(apis); pub const DeviceProxy = vk.DeviceProxy(apis); pub const QueueProxy = vk.QueueProxy(apis); pub const CommandBufferProxy = vk.CommandBufferProxy(apis); pub const B: *const BaseWrapper = &_bw; pub const I: *const InstanceProxy = &_ip; pub const D: *const DeviceProxy = &_dp; pub const W: *const Window = &_window; pub const Q: *const QueueProxy = &_qp; pub const device_config: *const CandidateDeviceInfo = &_dconfig; var _bw: BaseWrapper = undefined; var _iw: InstanceWrapper = undefined; var _dw: DeviceWrapper = undefined; var _ip: InstanceProxy = undefined; var _dp: DeviceProxy = undefined; var _qp: QueueProxy = undefined; var _instance: vk.Instance = undefined; var _window: Window = undefined; var _bus: Bus = undefined; var _device: vk.Device = undefined; var _dconfig: CandidateDeviceInfo = undefined; var _queue: vk.Queue = undefined; pub fn init(alloc: std.mem.Allocator) !void { try init_glfw(); errdefer deinit_glfw(); try init_base(); errdefer deinit_base(); try init_instance(alloc); errdefer deinit_instance(); try init_window(alloc); errdefer deinit_window(); try init_device(alloc); errdefer deinit_device(); try init_event_bus(alloc); errdefer deinit_event_bus(); } pub fn deinit() void { deinit_event_bus(); deinit_device(); deinit_window(); deinit_instance(); deinit_base(); deinit_glfw(); } fn init_glfw() !void { if (c.glfwInit() != c.GLFW_TRUE) return error.glfwInitFailed; errdefer c.glfwTerminate(); if (c.glfwVulkanSupported() != c.GLFW_TRUE) return error.glfwNoVulkan; } fn deinit_glfw() void { c.glfwTerminate(); } fn init_base() !void { if (use_debug_messenger) { _bw = try BaseWrapper.load(c.glfwGetInstanceProcAddress); } else { _bw = BaseWrapper.loadNoFail(c.glfwGetInstanceProcAddress); } } fn deinit_base() void {} fn init_instance(alloc: std.mem.Allocator) !void { var extensions = std.ArrayList([*:0]const u8).init(alloc); defer extensions.deinit(); var layers = std.ArrayList([*:0]const u8).init(alloc); defer layers.deinit(); if (use_debug_messenger) { try extensions.appendSlice(&.{ vk.extensions.ext_debug_utils.name, }); try layers.appendSlice(&.{ "VK_LAYER_KHRONOS_validation", }); } var glfw_exts_count: u32 = 0; const glfw_exts: [*]const [*:0]const u8 = @ptrCast(c.glfwGetRequiredInstanceExtensions(&glfw_exts_count)); try extensions.appendSlice(glfw_exts[0..glfw_exts_count]); const mci: vk.DebugUtilsMessengerCreateInfoEXT = .{ .message_severity = .{ .error_bit_ext = true, .info_bit_ext = true, .verbose_bit_ext = true, .warning_bit_ext = true, }, .message_type = .{ .device_address_binding_bit_ext = true, .general_bit_ext = false, .performance_bit_ext = true, .validation_bit_ext = true, }, .pfn_user_callback = &debug_callback, .p_user_data = null, }; _instance = try B.createInstance(&.{ .p_application_info = &app_info, .enabled_extension_count = @intCast(extensions.items.len), .pp_enabled_extension_names = extensions.items.ptr, .enabled_layer_count = @intCast(layers.items.len), .pp_enabled_layer_names = layers.items.ptr, .p_next = if (use_debug_messenger) &mci else null, }, null); if (use_debug_messenger) { _iw = try InstanceWrapper.load(_instance, _bw.dispatch.vkGetInstanceProcAddr); } else { _iw = InstanceWrapper.loadNoFail(_instance, _bw.dispatch.vkGetInstanceProcAddr); } _ip = InstanceProxy.init(_instance, &_iw); } fn deinit_instance() void { _ip.destroyInstance(null); } fn init_window(alloc: std.mem.Allocator) !void { _window = try Window.init( alloc, app_info.p_application_name orelse "Au Window", .{ .height = 720, .width = 1280 }, ); errdefer _window.deinit(); } fn deinit_window() void { _window.deinit(); } const CandidateDeviceInfo = struct { pdev: vk.PhysicalDevice, format: vk.SurfaceFormatKHR, mode: vk.PresentModeKHR, family: u32, // must support graphics and present for now fn init(alloc: std.mem.Allocator, pdev: vk.PhysicalDevice) !struct { i32, CandidateDeviceInfo } { var score: i32 = 0; var res: CandidateDeviceInfo = undefined; res.pdev = pdev; var format_count: u32 = undefined; _ = try I.getPhysicalDeviceSurfaceFormatsKHR(pdev, W.surface, &format_count, null); if (format_count == 0) return error.NoSurfaceFormats; const formats = try alloc.alloc(vk.SurfaceFormatKHR, format_count); defer alloc.free(formats); _ = try I.getPhysicalDeviceSurfaceFormatsKHR(pdev, W.surface, &format_count, formats.ptr); for (formats) |fmt| { if (fmt.color_space == .srgb_nonlinear_khr) { res.format = fmt; break; } } else { res.format = formats[0]; score -= 100; } var mode_count: u32 = undefined; _ = try I.getPhysicalDeviceSurfacePresentModesKHR(pdev, W.surface, &mode_count, null); if (mode_count == 0) return error.NoSurfacePresentModes; const modes = try alloc.alloc(vk.PresentModeKHR, mode_count); defer alloc.free(modes); _ = try I.getPhysicalDeviceSurfacePresentModesKHR(pdev, W.surface, &mode_count, modes.ptr); if (std.mem.indexOfAny(vk.PresentModeKHR, modes, &.{ vk.PresentModeKHR.mailbox_khr, })) |idx| { res.mode = modes[idx]; } else { score -= 50; res.mode = .fifo_khr; // this is guaranteed } var ext_count: u32 = undefined; _ = try I.enumerateDeviceExtensionProperties(pdev, null, &ext_count, null); const exts = try alloc.alloc(vk.ExtensionProperties, ext_count); defer alloc.free(exts); _ = try I.enumerateDeviceExtensionProperties(pdev, null, &ext_count, exts.ptr); for (device_extensions) |needle| { for (exts) |ext| { if (std.mem.eql( u8, std.mem.span(needle), std.mem.sliceTo(&ext.extension_name, 0), )) break; } else { return error.MissingDeviceExtension; } } var family_count: u32 = undefined; I.getPhysicalDeviceQueueFamilyProperties(pdev, &family_count, null); const families = try alloc.alloc(vk.QueueFamilyProperties, family_count); defer alloc.free(families); I.getPhysicalDeviceQueueFamilyProperties(pdev, &family_count, families.ptr); for (families, 0..) |prop, idx| { const graphics_support = prop.queue_flags.graphics_bit; const present_support = try I.getPhysicalDeviceSurfaceSupportKHR(pdev, @intCast(idx), W.surface) == vk.TRUE; if (graphics_support and present_support) { res.family = @intCast(idx); break; } } else { return error.NoSuitableFamily; } return .{ score, res }; } }; fn init_device(alloc: std.mem.Allocator) !void { var pdev_count: u32 = undefined; _ = try I.enumeratePhysicalDevices(&pdev_count, null); if (pdev_count == 0) return error.NoDevice; const pdevs = try alloc.alloc(vk.PhysicalDevice, pdev_count); defer alloc.free(pdevs); _ = try I.enumeratePhysicalDevices(&pdev_count, pdevs.ptr); // const scores = std.ArrayList(i32). var scores: std.MultiArrayList(struct { score: i32, ci: CandidateDeviceInfo }) = .{}; defer scores.deinit(alloc); for (pdevs) |pdev| { const score, const ci = CandidateDeviceInfo.init(alloc, pdev) catch continue; try scores.append(alloc, .{ .score = score, .ci = ci }); } const idx = std.sort.argMax(i32, scores.items(.score), {}, std.sort.asc(i32)) orelse return error.NoSuitableDevice; _dconfig = scores.get(idx).ci; const qci: []const vk.DeviceQueueCreateInfo = &.{ vk.DeviceQueueCreateInfo{ .queue_family_index = _dconfig.family, .queue_count = 1, .p_queue_priorities = &[_]f32{1.0}, }, }; _device = try I.createDevice(_dconfig.pdev, &.{ .queue_create_info_count = @intCast(qci.len), .p_queue_create_infos = qci.ptr, .enabled_extension_count = @intCast(device_extensions.len), .pp_enabled_extension_names = device_extensions.ptr, .p_next = &vk.PhysicalDeviceDynamicRenderingFeaturesKHR{ .dynamic_rendering = vk.TRUE, }, }, null); if (use_debug_messenger) { _dw = try DeviceWrapper.load(_device, _iw.dispatch.vkGetDeviceProcAddr); } else { _dw = DeviceWrapper.loadNoFail(_device, _iw.dispatch.vkGetDeviceProcAddr); } _dp = DeviceProxy.init(_device, &_dw); errdefer D.destroyDevice(null); _queue = D.getDeviceQueue(_dconfig.family, 0); _qp = QueueProxy.init(_queue, &_dw); // todo i'm thinking this needs to be a more complex pointer structure... i'm making assumptions here about how the // command pools are meant to work. probably I am cooking too much. } fn deinit_device() void { D.destroyDevice(null); } pub fn debug_callback( msg_severity: vk.DebugUtilsMessageSeverityFlagsEXT, msg_type: vk.DebugUtilsMessageTypeFlagsEXT, p_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque, ) callconv(vk.vulkan_call_conv) vk.Bool32 { // ripped from std.log.defaultLog const data = p_data orelse return vk.FALSE; const message = data.p_message orelse return vk.FALSE; const severity_prefix = if (msg_severity.verbose_bit_ext) "verbose:" else if (msg_severity.info_bit_ext) "info:" else if (msg_severity.warning_bit_ext) "warning:" else if (msg_severity.error_bit_ext) "error:" else "?:"; const type_prefix = if (msg_type.general_bit_ext) "" else if (msg_type.validation_bit_ext) "validation:" else if (msg_type.performance_bit_ext) "performance:" else if (msg_type.device_address_binding_bit_ext) "device_address_binding:" else "?:"; const stderr = std.io.getStdErr().writer(); var bw = std.io.bufferedWriter(stderr); const writer = bw.writer(); std.debug.lockStdErr(); defer std.debug.unlockStdErr(); nosuspend { writer.print("vk-{s}{s} {s}\n", .{ severity_prefix, type_prefix, message }) catch return vk.FALSE; bw.flush() catch return vk.FALSE; } return vk.FALSE; } pub const Window = struct { const Self = @This(); alloc: std.mem.Allocator, handle: *c.GLFWwindow, surface: vk.SurfaceKHR, pub fn init(alloc: std.mem.Allocator, title: [*:0]const u8, extent: vk.Extent2D) !Self { var self: Self = undefined; self.alloc = alloc; c.glfwWindowHintString(c.GLFW_X11_CLASS_NAME, "floating_window"); c.glfwWindowHintString(c.GLFW_X11_INSTANCE_NAME, "floating_window"); c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API); self.handle = c.glfwCreateWindow( @intCast(extent.width), @intCast(extent.height), title, null, null, ) orelse return error.glfwWindowFailed; errdefer c.glfwDestroyWindow(self.handle); if (c.glfwCreateWindowSurface(_instance, self.handle, null, &self.surface) != .success) { return error.glfwSurfaceFailed; } errdefer I.destroySurfaceKHR(self.surface, null); return self; } pub fn deinit(self: Self) void { I.destroySurfaceKHR(self.surface, null); c.glfwDestroyWindow(self.handle); } pub fn should_close(self: Self) bool { return c.glfwWindowShouldClose(self.handle) == c.GLFW_TRUE; } pub fn focused(self: Self) bool { return c.glfwGetWindowAttrib(self.handle, c.GLFW_FOCUSED) == c.GLFW_TRUE; } }; pub fn wait_events() []const Bus.Event { _bus.clear(); c.glfwWaitEvents(); return _bus.events.items; } pub fn poll_events() []const Bus.Event { _bus.clear(); c.glfwPollEvents(); return _bus.events.items; } pub fn wait_events_timeout(seconds: f64) []const Bus.Event { _bus.clear(); c.glfwWaitEventsTimeout(seconds); return _bus.events.items; } fn init_event_bus(alloc: std.mem.Allocator) !void { _bus = Bus.init(alloc); errdefer _bus.deinit(); try _bus.connect(&_window); } fn deinit_event_bus() void { try _bus.disconnect(&_window); _bus.deinit(); }