diff --git a/src/au.zig b/src/au.zig new file mode 100644 index 0000000..8b6f2ae --- /dev/null +++ b/src/au.zig @@ -0,0 +1,432 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const vk = @import("vk"); +const c = @import("c.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 _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(); + errdefer deinit_window(); + + try init_device(alloc); + errdefer deinit_device(); +} + +pub fn deinit() void { + 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() !void { + _window = try Window.init( + 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; +} + +const Window = struct { + const Self = @This(); + + handle: *c.GLFWwindow, + surface: vk.SurfaceKHR, + + pub fn init(title: [*:0]const u8, extent: vk.Extent2D) !Self { + var self: Self = undefined; + + 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 wait_events(_: Self) void { + c.glfwWaitEvents(); + // todo events as values? push into a buffer and return here? + } + + pub fn poll_events(_: Self) void { + c.glfwPollEvents(); + } + + pub fn wait_events_timeout(seconds: f64) void { + c.glfwWaitEventsTimeout(seconds); + } +}; diff --git a/src/main.zig b/src/main.zig index 2162eca..b513378 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,8 @@ const Allocator = std.mem.Allocator; const gfx = @import("gfx.zig"); +const au = @import("au.zig"); + const app_name = "vulkan-zig triangle example"; const Vertex = extern struct { @@ -94,106 +96,108 @@ pub fn main() !void { defer _ = gpa.deinit(); const ally = gpa.allocator(); - const base = try gfx.Base.init(); - defer base.deinit(); + try au.init(ally); + defer au.deinit(); - const inst = try gfx.Instance.init(&base); - defer inst.deinit(); + // std.debug.print("Initialized!!\n", .{ }); - const win = try gfx.Window.init(&inst, "zig-glfw-vulkan", .{ .width = 800, .height = 600 }); - defer win.deinit(); - - const dev = try gfx.Device.init(ally, &inst, &win); - defer dev.deinit(); - - var sc = try gfx.Swapchain.create(ally, &dev); - defer sc.deinit(); - - const device_local = gfx.VkAllocator.init(dev.pdev, inst.vki); - - const pipeline_layout = try dev.vkd.createPipelineLayout(dev.dev, &.{ - .flags = .{}, - .set_layout_count = 0, - .p_set_layouts = undefined, - .push_constant_range_count = 0, - .p_push_constant_ranges = undefined, - }, null); - defer dev.vkd.destroyPipelineLayout(dev.dev, pipeline_layout, null); - - const pipeline = try createPipeline(dev.dev, pipeline_layout, dev.format, dev.vkd); - defer dev.vkd.destroyPipeline(dev.dev, pipeline, null); - - const vertex_buffer = try dev.vkd.createBuffer(dev.dev, &.{ - .size = @sizeOf(@TypeOf(vertices)), - .usage = .{ .transfer_dst_bit = true, .vertex_buffer_bit = true }, - .sharing_mode = .exclusive, - }, null); - defer dev.vkd.destroyBuffer(dev.dev, vertex_buffer, null); - const vertex_mem_reqs = dev.vkd.getBufferMemoryRequirements(dev.dev, vertex_buffer); - const vertex_memory = try device_local.alloc(dev.dev, dev.vkd, vertex_mem_reqs, .{ .device_local_bit = true }); - defer dev.vkd.freeMemory(dev.dev, vertex_memory, null); - try dev.vkd.bindBufferMemory(dev.dev, vertex_buffer, vertex_memory, 0); - - try gfx.uploadData(Vertex, dev.pdev, inst.vki, dev.dev, dev.vkd, dev.queue, dev.pool, vertex_buffer, &vertices); - - const index_buffer = try dev.vkd.createBuffer(dev.dev, &.{ - .size = @sizeOf(@TypeOf(indices)), - .usage = .{ .transfer_dst_bit = true, .index_buffer_bit = true }, - .sharing_mode = .exclusive, - }, null); - defer dev.vkd.destroyBuffer(dev.dev, index_buffer, null); - const index_mem_reqs = dev.vkd.getBufferMemoryRequirements(dev.dev, index_buffer); - const index_memory = try device_local.alloc(dev.dev, dev.vkd, index_mem_reqs, .{ .device_local_bit = true }); - defer dev.vkd.freeMemory(dev.dev, index_memory, null); - try dev.vkd.bindBufferMemory(dev.dev, index_buffer, index_memory, 0); - - try gfx.uploadData(Index, dev.pdev, inst.vki, dev.dev, dev.vkd, dev.queue, dev.pool, index_buffer, &indices); - - try sc.init(); - for (sc.chain.items(.image), sc.chain.items(.view), sc.chain.items(.cmdbuf)) |image, view, cmdbuf| { - try record_cmdbuf(cmdbuf, dev.vkd, image, view, sc.extent, pipeline, vertex_buffer, index_buffer); + while (!au.W.should_close()) { + au.W.wait_events(); + // std.debug.print("Event!!\n", .{ }); } - var index: u32 = 0; + try au.D.deviceWaitIdle(); - while (c.glfwWindowShouldClose(win.ref) == c.GLFW_FALSE) { - var w: c_int = undefined; - var h: c_int = undefined; - c.glfwGetFramebufferSize(win.ref, &w, &h); - - // Don't present or resize swapchain while the window is minimized - if (w == 0 or h == 0) { - c.glfwPollEvents(); - continue; - } - - const frame = sc.chain.get(index); - - render(dev.dev, dev.vkd, sc.ref, frame, dev.queue) catch |err| switch (err) { - error.OutOfDateKHR => { - // TODO: this is a hack to safely destroy sync primitives - // don't do this. be smart about sync primitive reuse or - // move them to "garbage" to be destroyed later. - try dev.vkd.deviceWaitIdle(dev.dev); - - try sc.init(); - for (sc.chain.items(.image), sc.chain.items(.view), sc.chain.items(.cmdbuf)) |image, view, cmdbuf| { - try record_cmdbuf(cmdbuf, dev.vkd, image, view, sc.extent, pipeline, vertex_buffer, index_buffer); - } - - index = 0; - - continue; - }, - else => |errx| return errx, - }; - - c.glfwPollEvents(); - - index = @intCast((index + 1) % sc.chain.len); - } - - try dev.vkd.deviceWaitIdle(dev.dev); + // // todo create command pool + // + // var sc = try gfx.Swapchain.create(ally, &dev); + // defer sc.deinit(); + // + // const device_local = gfx.VkAllocator.init(dev.pdev, inst.vki); + // + // const pipeline_layout = try dev.vkd.createPipelineLayout(dev.dev, &.{ + // .flags = .{}, + // .set_layout_count = 0, + // .p_set_layouts = undefined, + // .push_constant_range_count = 0, + // .p_push_constant_ranges = undefined, + // }, null); + // defer dev.vkd.destroyPipelineLayout(dev.dev, pipeline_layout, null); + // + // const pipeline = try createPipeline(dev.dev, pipeline_layout, dev.format, dev.vkd); + // defer dev.vkd.destroyPipeline(dev.dev, pipeline, null); + // + // const vertex_buffer = try dev.vkd.createBuffer(dev.dev, &.{ + // .size = @sizeOf(@TypeOf(vertices)), + // .usage = .{ .transfer_dst_bit = true, .vertex_buffer_bit = true }, + // .sharing_mode = .exclusive, + // }, null); + // defer dev.vkd.destroyBuffer(dev.dev, vertex_buffer, null); + // const vertex_mem_reqs = dev.vkd.getBufferMemoryRequirements(dev.dev, vertex_buffer); + // const vertex_memory = try device_local.alloc(dev.dev, dev.vkd, vertex_mem_reqs, .{ .device_local_bit = true }); + // defer dev.vkd.freeMemory(dev.dev, vertex_memory, null); + // try dev.vkd.bindBufferMemory(dev.dev, vertex_buffer, vertex_memory, 0); + // + // try gfx.uploadData(Vertex, dev.pdev, inst.vki, dev.dev, dev.vkd, dev.queue, dev.pool, vertex_buffer, &vertices); + // + // const index_buffer = try dev.vkd.createBuffer(dev.dev, &.{ + // .size = @sizeOf(@TypeOf(indices)), + // .usage = .{ .transfer_dst_bit = true, .index_buffer_bit = true }, + // .sharing_mode = .exclusive, + // }, null); + // defer dev.vkd.destroyBuffer(dev.dev, index_buffer, null); + // const index_mem_reqs = dev.vkd.getBufferMemoryRequirements(dev.dev, index_buffer); + // const index_memory = try device_local.alloc(dev.dev, dev.vkd, index_mem_reqs, .{ .device_local_bit = true }); + // defer dev.vkd.freeMemory(dev.dev, index_memory, null); + // try dev.vkd.bindBufferMemory(dev.dev, index_buffer, index_memory, 0); + // + // try gfx.uploadData(Index, dev.pdev, inst.vki, dev.dev, dev.vkd, dev.queue, dev.pool, index_buffer, &indices); + // + // try sc.init(); + // for (sc.chain.items(.image), sc.chain.items(.view), sc.chain.items(.cmdbuf)) |image, view, cmdbuf| { + // try record_cmdbuf(cmdbuf, dev.vkd, image, view, sc.extent, pipeline, vertex_buffer, index_buffer); + // } + // + // var index: u32 = 0; + // + // while (c.glfwWindowShouldClose(win.ref) == c.GLFW_FALSE) { + // var w: c_int = undefined; + // var h: c_int = undefined; + // c.glfwGetFramebufferSize(win.ref, &w, &h); + // + // // Don't present or resize swapchain while the window is minimized + // if (w == 0 or h == 0) { + // c.glfwPollEvents(); + // continue; + // } + // + // const frame = sc.chain.get(index); + // + // render(dev.dev, dev.vkd, sc.ref, frame, dev.queue) catch |err| switch (err) { + // error.OutOfDateKHR => { + // // TODO: this is a hack to safely destroy sync primitives + // // don't do this. be smart about sync primitive reuse or + // // move them to "garbage" to be destroyed later. + // try dev.vkd.deviceWaitIdle(dev.dev); + // + // try sc.init(); + // for (sc.chain.items(.image), sc.chain.items(.view), sc.chain.items(.cmdbuf)) |image, view, cmdbuf| { + // try record_cmdbuf(cmdbuf, dev.vkd, image, view, sc.extent, pipeline, vertex_buffer, index_buffer); + // } + // + // index = 0; + // + // continue; + // }, + // else => |errx| return errx, + // }; + // + // c.glfwPollEvents(); + // + // index = @intCast((index + 1) % sc.chain.len); + // } + // + // try dev.vkd.deviceWaitIdle(dev.dev); } fn record_cmdbuf(