diff --git a/build.zig.zon b/build.zig.zon index 6942b2e..4873873 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -8,7 +8,7 @@ .hash = "1220cf0972c6fe05437c1a8689b955084385eb7ca1f8c14010d49ca5a89570a5d90d", }, .cimgui = .{ - .path="cimgui", + .path = "cimgui", }, }, diff --git a/src/main.zig b/src/main.zig index 887ab29..17627fb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,7 +3,7 @@ const nu = @import("nu.zig"); pub const nu_modules = .{ App, - UI, + // UI, }; pub const main = nu.main; @@ -15,62 +15,62 @@ pub const nu_options: nu.Options = .{ }, }; -pub const UI = struct { - const im = nu.ImGui; - - pub const depends = .{im}; - - var color: @Vector(4, f32) = @splat(1); - - pub fn setup(_: std.mem.Allocator) !void { - const io: *nu.ImGui.ImGuiIO = @ptrCast(nu.ImGui.igGetIO()); - io.ConfigFlags |= nu.ImGui.ImGuiConfigFlags_DockingEnable; - } - - pub fn frame() !void { - nu.ImGui.igShowMetricsWindow(null); - - { - const viewport = im.igGetMainViewport(); - im.igSetNextWindowPos(viewport.*.WorkPos, 0, .{ .x = 0, .y = 0 }); - im.igSetNextWindowSize(viewport.*.WorkSize, 0); - im.igSetNextWindowViewport(viewport.*.ID); - im.igPushStyleVar_Float(im.ImGuiStyleVar_WindowRounding, 0); - im.igPushStyleVar_Float(im.ImGuiStyleVar_WindowBorderSize, 0); - im.igPushStyleVar_Vec2(im.ImGuiStyleVar_WindowPadding, .{ .x = 0, .y = 0 }); - defer im.igPopStyleVar(3); - - const window_flags = - im.ImGuiWindowFlags_MenuBar | - im.ImGuiWindowFlags_NoDocking | - im.ImGuiWindowFlags_NoTitleBar | - im.ImGuiWindowFlags_NoCollapse | - im.ImGuiWindowFlags_NoResize | - im.ImGuiWindowFlags_NoMove | - im.ImGuiWindowFlags_NoBringToFrontOnFocus | - im.ImGuiWindowFlags_NoNavFocus | - im.ImGuiWindowFlags_NoBackground; - - const dock_flags = - im.ImGuiDockNodeFlags_PassthruCentralNode | - im.ImGuiDockNodeFlags_NoDockingOverCentralNode; - - _ = im.igBegin("Main Dockspace", null, window_flags); - const id = im.igGetID_Str("maindockspace"); - _ = im.igDockSpace(id, .{ .x = 0, .y = 0 }, dock_flags, null); - im.igEnd(); - } - - if (nu.ImGui.igBegin("Color", null, nu.ImGui.ImGuiWindowFlags_None)) { - if (nu.ImGui.igColorEdit4("color", @ptrCast(&color), nu.ImGui.ImGuiColorEditFlags_AlphaPreviewHalf)) {} - } - nu.ImGui.igEnd(); - } -}; +// pub const UI = struct { +// const im = nu.ImGui; +// +// pub const depends = .{im}; +// +// var color: @Vector(4, f32) = @splat(1); +// +// pub fn setup(_: std.mem.Allocator) !void { +// const io: *nu.ImGui.ImGuiIO = @ptrCast(nu.ImGui.igGetIO()); +// io.ConfigFlags |= nu.ImGui.ImGuiConfigFlags_DockingEnable; +// } +// +// pub fn frame() !void { +// nu.ImGui.igShowMetricsWindow(null); +// +// { +// const viewport = im.igGetMainViewport(); +// im.igSetNextWindowPos(viewport.*.WorkPos, 0, .{ .x = 0, .y = 0 }); +// im.igSetNextWindowSize(viewport.*.WorkSize, 0); +// im.igSetNextWindowViewport(viewport.*.ID); +// im.igPushStyleVar_Float(im.ImGuiStyleVar_WindowRounding, 0); +// im.igPushStyleVar_Float(im.ImGuiStyleVar_WindowBorderSize, 0); +// im.igPushStyleVar_Vec2(im.ImGuiStyleVar_WindowPadding, .{ .x = 0, .y = 0 }); +// defer im.igPopStyleVar(3); +// +// const window_flags = +// im.ImGuiWindowFlags_MenuBar | +// im.ImGuiWindowFlags_NoDocking | +// im.ImGuiWindowFlags_NoTitleBar | +// im.ImGuiWindowFlags_NoCollapse | +// im.ImGuiWindowFlags_NoResize | +// im.ImGuiWindowFlags_NoMove | +// im.ImGuiWindowFlags_NoBringToFrontOnFocus | +// im.ImGuiWindowFlags_NoNavFocus | +// im.ImGuiWindowFlags_NoBackground; +// +// const dock_flags = +// im.ImGuiDockNodeFlags_PassthruCentralNode | +// im.ImGuiDockNodeFlags_NoDockingOverCentralNode; +// +// _ = im.igBegin("Main Dockspace", null, window_flags); +// const id = im.igGetID_Str("maindockspace"); +// _ = im.igDockSpace(id, .{ .x = 0, .y = 0 }, dock_flags, null); +// im.igEnd(); +// } +// +// if (nu.ImGui.igBegin("Color", null, nu.ImGui.ImGuiWindowFlags_None)) { +// if (nu.ImGui.igColorEdit4("color", @ptrCast(&color), nu.ImGui.ImGuiColorEditFlags_AlphaPreviewHalf)) {} +// } +// nu.ImGui.igEnd(); +// } +// }; const App = struct { const vk = @import("vk"); - const au = @import("nu/Render/au.zig"); + // const au = @import("nu/Render/au.zig"); pub const depends = .{nu.Render}; @@ -82,5 +82,5 @@ const App = struct { pub fn frame() !void {} - pub fn present(_: au.CommandBufferProxy) void {} + // pub fn present(_: au.CommandBufferProxy) void {} }; diff --git a/src/nu.zig b/src/nu.zig index 2786121..aaacbb2 100644 --- a/src/nu.zig +++ b/src/nu.zig @@ -3,7 +3,7 @@ const root = @import("root"); pub const Window = @import("nu/Window.zig"); pub const Render = @import("nu/Render.zig"); -pub const ImGui = @import("nu/ImGui.zig"); +// pub const ImGui = @import("nu/ImGui.zig"); pub const Bus = @import("nu/Bus.zig"); @@ -109,5 +109,4 @@ const Graph = struct { } }; -test { -} \ No newline at end of file +test {} diff --git a/src/nu/ImGui.zig b/src/nu/ImGui.zig index b891211..38e1caf 100644 --- a/src/nu/ImGui.zig +++ b/src/nu/ImGui.zig @@ -4,7 +4,6 @@ const std = @import("std"); const vk = @import("vk"); const nu = @import("../nu.zig"); -const au = @import("Render/au.zig"); const Render = @import("Render.zig"); const Window = @import("Window.zig"); @@ -18,7 +17,7 @@ const config = nu.config.imgui; pub const depends = .{ Render, Window }; pub fn loader_wrapper(procname: [*c]const u8, _: ?*anyopaque) callconv(.C) vk.PfnVoidFunction { - return au.glfwGetInstanceProcAddress(au.I.handle, procname); + return nu.glfwGetInstanceProcAddress(nu.I.handle, procname); } var ctx: *im.ImGuiContext = undefined; @@ -39,23 +38,27 @@ pub fn setup(_: std.mem.Allocator) !void { } errdefer im.impl.ImGui_ImplGlfw_Shutdown(); - descriptor_pool = try au.D.createDescriptorPool(&vk.DescriptorPoolCreateInfo{ - .flags = .{ .free_descriptor_set_bit = true }, - .pool_size_count = 1, - .p_pool_sizes = &.{vk.DescriptorPoolSize{ - .descriptor_count = 32, - .type = .combined_image_sampler, - }}, - .max_sets = 32, - }, null); - errdefer au.D.destroyDescriptorPool(descriptor_pool, null); + descriptor_pool = try Render.ctx.dw.createDescriptorPool( + Render.ctx.device, + &vk.DescriptorPoolCreateInfo{ + .flags = .{ .free_descriptor_set_bit = true }, + .pool_size_count = 1, + .p_pool_sizes = &.{vk.DescriptorPoolSize{ + .descriptor_count = 32, + .type = .combined_image_sampler, + }}, + .max_sets = 32, + }, + null, + ); + errdefer Render.ctx.dw.destroyDescriptorPool(Render.ctx.device, descriptor_pool, null); if (im.impl.ImGui_ImplVulkan_Init(@constCast(&im.impl.ImGui_ImplVulkan_InitInfo{ - .Instance = @ptrFromInt(@intFromEnum(au.I.handle)), - .PhysicalDevice = @ptrFromInt(@intFromEnum(au.device_config.pdev)), - .Device = @ptrFromInt(@intFromEnum(au.D.handle)), - .QueueFamily = au.device_config.family, - .Queue = @ptrFromInt(@intFromEnum(au.Q.handle)), + .Instance = @ptrFromInt(@intFromEnum(Render.ctx.instance)), + .PhysicalDevice = @ptrFromInt(@intFromEnum(Render.ctx.pdevice)), + .Device = @ptrFromInt(@intFromEnum(Render.ctx.device)), + .QueueFamily = au.device_config.family, // todo + .Queue = @ptrFromInt(@intFromEnum(au.Q.handle)), // todo .DescriptorPool = @ptrFromInt(@intFromEnum(descriptor_pool)), .RenderPass = null, .MinImageCount = 2, @@ -65,7 +68,7 @@ pub fn setup(_: std.mem.Allocator) !void { .depth_attachment_format = .undefined, .stencil_attachment_format = .undefined, .color_attachment_count = 1, - .p_color_attachment_formats = &.{au.device_config.format.format}, + .p_color_attachment_formats = &.{au.device_config.format.format}, // todo }), .MSAASamples = 0, .PipelineCache = null, @@ -83,9 +86,9 @@ pub fn setup(_: std.mem.Allocator) !void { } pub fn teardown() void { - au.D.deviceWaitIdle() catch |err| std.debug.panic("Device wait failed: {!}", .{err}); + Render.ctx.dw.deviceWaitIdle(Render.ctx.device) catch |err| std.debug.panic("Device wait failed: {!}", .{err}); im.impl.ImGui_ImplVulkan_Shutdown(); - au.D.destroyDescriptorPool(descriptor_pool, null); + Render.ctx.dw.destroyDescriptorPool(Render.ctx.device, descriptor_pool, null); im.impl.ImGui_ImplGlfw_Shutdown(); im.igDestroyContext(ctx); } @@ -96,7 +99,7 @@ pub fn frame() !void { im.igNewFrame(); } -pub fn present(cmd: au.CommandBufferProxy) void { +pub fn present(cmd: au.CommandBufferProxy) void { // todo im.igEndFrame(); im.igRender(); diff --git a/src/nu/Render.zig b/src/nu/Render.zig index ec49598..ed243a4 100644 --- a/src/nu/Render.zig +++ b/src/nu/Render.zig @@ -7,7 +7,10 @@ const builtin = @import("builtin"); const vk = @import("vk"); const nu = @import("../nu.zig"); -const au = @import("Render/au.zig"); +// const au = @import("Render/au.zig"); + +const ctx = @import("Render/ctx.zig"); +const swap_chain = @import("Render/swap_chain.zig"); pub const Config = struct { app_name: [*:0]const u8 = "nu-au-app", @@ -24,7 +27,6 @@ pub const Config = struct { minor: u10 = 0, patch: u12 = 0, } = .{}, - frames_in_flight: u8 = 3, use_debug_messenger: bool = switch (builtin.mode) { .Debug, .ReleaseSafe => true, .ReleaseSmall, .ReleaseFast => false, @@ -34,84 +36,178 @@ const config = nu.config.render; pub const depends = .{nu.Window}; -var sc: au.SwapChain = undefined; -var flights: au.Flights = undefined; +const SwapChain = swap_chain.SwapChain(Flight); +const Flight = struct { + pool: vk.CommandPool = .null_handle, + cmd: vk.CommandBuffer = .null_handle, + + pub fn init() !Flight { + const pool = try ctx.D.createCommandPool( + &.{ .queue_family_index = ctx.family.* }, + null, + ); + errdefer ctx.D.destroyCommandPool(pool, null); + + var cmds: [1]vk.CommandBuffer = undefined; + try ctx.D.allocateCommandBuffers( + &vk.CommandBufferAllocateInfo{ + .command_buffer_count = 1, + .command_pool = pool, + .level = .primary, + }, + &cmds, + ); + errdefer ctx.D.freeCommandBuffers(pool, 1, &cmds); + + return .{ + .pool = pool, + .cmd = cmds[0], + }; + } + + pub fn deinit(self: Flight) void { + const cmds: [1]vk.CommandBuffer = .{self.cmd}; + ctx.D.freeCommandBuffers(self.pool, 1, &cmds); + ctx.D.destroyCommandPool(self.pool, null); + } +}; + +var _sc: SwapChain = undefined; +var _flights: []Flight = undefined; pub fn setup(alloc: std.mem.Allocator) !void { - // todo pick apart au into helpers; not a sub-module filled with its own globals. - try au.init(alloc); - errdefer au.deinit(); + try ctx.init(alloc); + errdefer ctx.deinit(); - sc = try au.SwapChain.init(alloc); - errdefer sc.deinit(); + _flights = try alloc.alloc(Flight, 3); + errdefer alloc.free(_flights); + errdefer for (_flights) |flight| flight.deinit(); + for (_flights) |*flight| flight.* = try Flight.init(); - flights = try au.Flights.init(alloc, config.frames_in_flight); - errdefer flights.deinit(); + _sc = try SwapChain.init(alloc, _flights); + errdefer _sc.deinit(); } pub fn teardown() void { - au.D.deviceWaitIdle() catch |err| std.debug.panic("Device wait failed: {!}", .{err}); - flights.deinit(); - sc.deinit(); - au.deinit(); + _sc.deinit(); + for (_flights) |flight| flight.deinit(); + ctx.deinit(); } pub fn render() !void { - const flight: au.Flights.Flight = flights.next(); - try flight.wait(); + const target = try _sc.acquire(); - while (true) { - _ = try sc.rebuild(); + const render_area: vk.Rect2D = .{ + .offset = .{ .x = 0, .y = 0 }, + .extent = _sc.cinfo.image_extent, + }; - const target = sc.acquire(flight.acquire, .null_handle) catch |err| switch (err) { - error.OutOfDateKHR => { - sc.mark(); - continue; + try ctx.D.resetCommandPool(target.flight.pool, .{}); + var cmd = ctx.CommandBufferProxy.init(target.flight.cmd, ctx.dw); + + try cmd.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); + { + cmd.pipelineBarrier( + .{ .top_of_pipe_bit = true }, + .{ .color_attachment_output_bit = true }, + .{}, + 0, + null, + 0, + null, + 1, + &.{ + vk.ImageMemoryBarrier{ + .src_access_mask = .{}, + .dst_access_mask = .{ .color_attachment_write_bit = true }, + .old_layout = .undefined, + .new_layout = .color_attachment_optimal, + .src_queue_family_index = 0, // values are the same; no transfer occurs + .dst_queue_family_index = 0, + .image = target.image, + .subresource_range = .{ + .aspect_mask = .{ .color_bit = true }, + .base_mip_level = 0, + .level_count = 1, + .base_array_layer = 0, + .layer_count = 1, + }, + }, }, - else => return err, - }; + ); - const render_area: vk.Rect2D = .{ - .offset = .{ .x = 0, .y = 0 }, - .extent = sc.cinfo.image_extent, - }; + cmd.beginRendering(&vk.RenderingInfo{ + .render_area = render_area, + .layer_count = 1, + .view_mask = 0, + .color_attachment_count = 1, + .p_color_attachments = &.{ + vk.RenderingAttachmentInfo{ + .image_view = target.view, + .image_layout = .color_attachment_optimal, + .resolve_mode = .{}, + .resolve_image_view = .null_handle, + .resolve_image_layout = .undefined, + .load_op = .clear, + .store_op = .store, + .clear_value = .{ .color = .{ .float_32 = .{ 1, 0, 0, 1 } } }, + }, + }, + }); - try au.D.resetCommandPool(flight.pool, .{}); - var cmd = au.CommandBufferProxy.init(flight.cmd, au.D.wrapper); + cmd.endRendering(); - try cmd.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); - target.begin_rendering(cmd, render_area); + cmd.pipelineBarrier( + .{ .color_attachment_output_bit = true }, + .{ .bottom_of_pipe_bit = true }, + .{}, + 0, + null, + 0, + null, + 1, + &.{ + vk.ImageMemoryBarrier{ + .src_access_mask = .{ .color_attachment_write_bit = true }, + .dst_access_mask = .{}, + .old_layout = .color_attachment_optimal, + .new_layout = .present_src_khr, + .src_queue_family_index = 0, // values are the same; no transfer occurs. + .dst_queue_family_index = 0, + .image = target.image, + .subresource_range = .{ + .aspect_mask = .{ .color_bit = true }, + .base_mip_level = 0, + .level_count = 1, + .base_array_layer = 0, + .layer_count = 1, + }, + }, + }, + ); + } + try cmd.endCommandBuffer(); - // todo manage frame in flight state for each hook; pass the current flight in as context. - - nu.engine.invoke("present", .{cmd}); - - target.end_rendering(cmd); - try cmd.endCommandBuffer(); - - try au.Q.submit(1, &.{ + try ctx.Q.submit( + 1, + &.{ vk.SubmitInfo{ .wait_semaphore_count = 1, - .p_wait_semaphores = &.{flight.acquire}, + // don't start writing to color attachment until the swapchain image has been acquired. + .p_wait_semaphores = &.{ + target.acquired, + }, .p_wait_dst_stage_mask = &.{ vk.PipelineStageFlags{ .color_attachment_output_bit = true }, }, .command_buffer_count = 1, - .p_command_buffers = &.{cmd.handle}, + .p_command_buffers = &.{target.flight.cmd}, .signal_semaphore_count = 1, - .p_signal_semaphores = &.{flight.complete}, + .p_signal_semaphores = &.{target.complete}, }, - }, flight.fence); + }, + target.available, // target will become available again once these finish + ); - if (sc.present(&.{flight.complete}, target)) |_| { - return; - } else |err| switch (err) { - error.OutOfDateKHR => { - try flight.wait(); - sc.mark(); - continue; - }, - else => return err, - } - } + try _sc.present(target); } diff --git a/src/nu/Render/Debug.zig b/src/nu/Render/Debug.zig new file mode 100644 index 0000000..1cbb4b1 --- /dev/null +++ b/src/nu/Render/Debug.zig @@ -0,0 +1,66 @@ +const std = @import("std"); +const vk = @import("vk"); + +pub const ci: 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 = true, + .performance_bit_ext = true, + .validation_bit_ext = true, + }, + .pfn_user_callback = &debug_callback, + .p_user_data = 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; +} diff --git a/src/nu/Render/au/SwapChain.zig b/src/nu/Render/au/SwapChain.zig index 204316d..17c2bd8 100644 --- a/src/nu/Render/au/SwapChain.zig +++ b/src/nu/Render/au/SwapChain.zig @@ -99,7 +99,12 @@ pub fn rebuild(self: *Self) !bool { } pub fn acquire(self: Self, semaphore: vk.Semaphore, fence: vk.Fence) !Target { - const acq = try au.D.acquireNextImageKHR(self.handle, std.math.maxInt(u64), semaphore, fence); + const acq = try au.D.acquireNextImageKHR( + self.handle, + std.math.maxInt(u64), + semaphore, + fence, + ); return .{ .idx = acq.image_index, .image = self.images.items[acq.image_index], diff --git a/src/nu/Render/ctx.zig b/src/nu/Render/ctx.zig new file mode 100644 index 0000000..170331d --- /dev/null +++ b/src/nu/Render/ctx.zig @@ -0,0 +1,298 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const vk = @import("vk"); +const nu = @import("../../nu.zig"); + +const Debug = @import("Debug.zig"); + +const config = nu.config.render; + +pub const versions: []const vk.ApiInfo = &.{ + vk.features.version_1_0, + vk.features.version_1_1, + vk.features.version_1_2, + vk.features.version_1_3, +}; + +pub const instance_exts: []const vk.ApiInfo = if (config.use_debug_messenger) &.{ + vk.extensions.ext_debug_utils, + vk.extensions.khr_surface, +} else &.{}; + +pub const device_exts: []const vk.ApiInfo = &.{ + vk.extensions.khr_swapchain, + vk.extensions.khr_dynamic_rendering, + vk.extensions.khr_timeline_semaphore, +}; + +pub const apis = versions ++ instance_exts ++ device_exts; + +pub const layers: []const [*:0]const u8 = + if (config.use_debug_messenger) &.{ + "VK_LAYER_KHRONOS_validation", +} else &.{}; + +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); + +var _iw: InstanceWrapper = undefined; +pub const iw: *const InstanceWrapper = &_iw; +var _dw: DeviceWrapper = undefined; +pub const dw: *const DeviceWrapper = &_dw; + +var _B: BaseWrapper = undefined; +pub const B: *const BaseWrapper = &_B; +var _I: InstanceProxy = undefined; +pub const I: *const InstanceProxy = &_I; +var _D: DeviceProxy = undefined; +pub const D: *const DeviceProxy = &_D; +var _Q: QueueProxy = undefined; +pub const Q: *const QueueProxy = &_Q; + +var _instance: vk.Instance = undefined; +pub const instance: *const vk.Instance = &_instance; +var _messenger: if (config.use_debug_messenger) vk.DebugUtilsMessengerEXT else void = undefined; +pub const messenger: *const if (config.use_debug_messenger) vk.DebugUtilsMessengerEXT else void = &_messenger; +var _surface: vk.SurfaceKHR = undefined; +pub const surface: *const vk.SurfaceKHR = &_surface; +var _pdevice: vk.PhysicalDevice = undefined; +pub const pdevice: *const vk.PhysicalDevice = &_pdevice; +var _device: vk.Device = undefined; +pub const device: *const vk.Device = &_device; +var _family: u32 = undefined; +pub const family: *const u32 = &_family; +var _queue: vk.Queue = undefined; +pub const queue: *const vk.Queue = &_queue; + +pub fn init(alloc: std.mem.Allocator) !void { + _B = try BaseWrapper.load(glfwGetInstanceProcAddress); + + _instance = try _create_instance(alloc); + _iw = try InstanceWrapper.load(_instance, glfwGetInstanceProcAddress); + errdefer _destroy_instance(); + _I = InstanceProxy.init(_instance, iw); + + if (config.use_debug_messenger) _messenger = try _create_messenger(); + errdefer if (config.use_debug_messenger) _destroy_messenger(); + + _surface = try _create_surface(); + errdefer _destroy_surface(); + + _pdevice = try _select_pdevice(alloc); + _family = try _select_queue_family_index(alloc); // only one queue supported + _device = try _create_device(alloc); + _dw = try DeviceWrapper.load(_device, iw.dispatch.vkGetDeviceProcAddr); + errdefer _destroy_device(); + _D = DeviceProxy.init(_device, dw); + _queue = D.getDeviceQueue(_family, 0); // only one queue supported + _Q = QueueProxy.init(_queue, dw); +} + +pub fn deinit() void { + _destroy_device(); + _destroy_surface(); + if (config.use_debug_messenger) _destroy_messenger(); + _destroy_instance(); +} + +fn _create_instance(alloc: std.mem.Allocator) !vk.Instance { + var extnames = std.ArrayList([*:0]const u8).init(alloc); + defer extnames.deinit(); + + for (instance_exts) |ext| + try extnames.append(ext.name); + + var glfw_exts_count: u32 = 0; + const glfw_exts: [*]const [*:0]const u8 = + glfwGetRequiredInstanceExtensions(&glfw_exts_count); + try extnames.appendSlice(glfw_exts[0..glfw_exts_count]); + + var ci: vk.InstanceCreateInfo = .{ + .p_application_info = &vk.ApplicationInfo{ + .p_application_name = config.app_name, + .application_version = vk.makeApiVersion( + config.app_version.variant, + config.app_version.major, + config.app_version.minor, + config.app_version.patch, + ), + .p_engine_name = config.engine_name, + .engine_version = vk.makeApiVersion( + config.engine_version.variant, + config.engine_version.major, + config.engine_version.minor, + config.engine_version.patch, + ), + .api_version = vk.features.version_1_3.version, + }, + .enabled_extension_count = @intCast(extnames.items.len), + .pp_enabled_extension_names = extnames.items.ptr, + .enabled_layer_count = @intCast(layers.len), + .pp_enabled_layer_names = layers.ptr, + }; + + if (config.use_debug_messenger) ci.p_next = &Debug.ci; + + return try B.createInstance(&ci, null); +} + +fn _destroy_instance() void { + I.destroyInstance(null); +} + +fn _create_messenger() !vk.DebugUtilsMessengerEXT { + return try I.createDebugUtilsMessengerEXT(&Debug.ci, null); +} + +fn _destroy_messenger() void { + I.destroyDebugUtilsMessengerEXT(_messenger, null); +} + +fn _create_surface() !vk.SurfaceKHR { + var res: vk.SurfaceKHR = undefined; + if (glfwCreateWindowSurface( + _instance, + nu.Window.handle, + null, + &res, + ) != .success) { + return error.CreateWindowSurfaceFailed; + } + return res; +} + +fn _destroy_surface() void { + I.destroySurfaceKHR(_surface, null); +} + +fn _select_pdevice(alloc: std.mem.Allocator) !vk.PhysicalDevice { + var count: u32 = undefined; + _ = try I.enumeratePhysicalDevices( + &count, + null, + ); + const pdevs = try alloc.alloc(vk.PhysicalDevice, count); + defer alloc.free(pdevs); + _ = try I.enumeratePhysicalDevices( + &count, + pdevs.ptr, + ); + + const scores = try alloc.alloc(i32, count); + @memset(scores, 0); + defer alloc.free(scores); + + for (pdevs, scores) |pdev, *score| { + const props = I.getPhysicalDeviceProperties(pdev); + score.* += switch (props.device_type) { + .discrete_gpu => 1000, + .integrated_gpu => 500, + else => 0, + }; + } + + const idx = std.mem.indexOfMax(i32, scores); + + return pdevs[idx]; +} + +fn _select_queue_family_index(alloc: std.mem.Allocator) !u32 { + var count: u32 = undefined; + I.getPhysicalDeviceQueueFamilyProperties( + _pdevice, + &count, + null, + ); + const families = try alloc.alloc(vk.QueueFamilyProperties, count); + defer alloc.free(families); + I.getPhysicalDeviceQueueFamilyProperties( + _pdevice, + &count, + families.ptr, + ); + + for (families, 0..) |prop, idx| { + if (!prop.queue_flags.graphics_bit) continue; + + if (!prop.queue_flags.transfer_bit) continue; + + if (try I.getPhysicalDeviceSurfaceSupportKHR( + _pdevice, + @intCast(idx), + _surface, + ) != vk.TRUE) continue; + + return @intCast(idx); + } + + return error.NoSuitableQueueFamily; +} + +fn _create_device(alloc: std.mem.Allocator) !vk.Device { + const qci: []const vk.DeviceQueueCreateInfo = &.{ + vk.DeviceQueueCreateInfo{ + .queue_count = 1, + .queue_family_index = @intCast(_family), + .p_queue_priorities = &[_]f32{1.0}, + }, + }; + + var extnames = std.ArrayList([*:0]const u8).init(alloc); + defer extnames.deinit(); + + for (device_exts) |ext| + try extnames.append(ext.name); + + const ci: vk.DeviceCreateInfo = .{ + .queue_create_info_count = @intCast(qci.len), + .p_queue_create_infos = qci.ptr, + .enabled_extension_count = @intCast(extnames.items.len), + .pp_enabled_extension_names = extnames.items.ptr, + .p_next = &vk.PhysicalDeviceDynamicRenderingFeaturesKHR{ + .dynamic_rendering = vk.TRUE, + }, + }; + + return try I.createDevice(_pdevice, &ci, null); +} + +fn _destroy_device() void { + D.deviceWaitIdle() catch |err| switch (err) { + error.OutOfHostMemory, + error.OutOfDeviceMemory, + error.DeviceLost, + => { + // In these cases we would destroy the device anyway, so just fall through. Note any child objects must + // already be destroyed. This assumes normal cleanup has been done before _destroy_device was called. + }, + else => unreachable, + }; + D.destroyDevice(null); +} + +pub extern fn glfwGetInstanceProcAddress( + instance: vk.Instance, + procname: [*:0]const u8, +) vk.PfnVoidFunction; + +pub extern fn glfwGetPhysicalDevicePresentationSupport( + instance: vk.Instance, + pdev: vk.PhysicalDevice, + queuefamily: u32, +) c_int; + +pub extern fn glfwCreateWindowSurface( + instance: vk.Instance, + window: *nu.Window.c.GLFWwindow, + allocation_callbacks: ?*const vk.AllocationCallbacks, + surface: *vk.SurfaceKHR, +) vk.Result; + +pub extern fn glfwGetRequiredInstanceExtensions( + count: *u32, +) [*][*:0]const u8; diff --git a/src/nu/Render/swap_chain.zig b/src/nu/Render/swap_chain.zig new file mode 100644 index 0000000..c87f665 --- /dev/null +++ b/src/nu/Render/swap_chain.zig @@ -0,0 +1,283 @@ +const std = @import("std"); +const vk = @import("vk"); +const ctx = @import("ctx.zig"); + +fn _choose_format(alloc: std.mem.Allocator) !vk.SurfaceFormatKHR { + var count: u32 = undefined; + std.debug.assert(.success == try ctx.I.getPhysicalDeviceSurfaceFormatsKHR( + ctx.pdevice.*, + ctx.surface.*, + &count, + null, + )); + const formats = try alloc.alloc(vk.SurfaceFormatKHR, count); + defer alloc.free(formats); + std.debug.assert(.success == try ctx.I.getPhysicalDeviceSurfaceFormatsKHR( + ctx.pdevice.*, + ctx.surface.*, + &count, + formats.ptr, + )); + + for (formats) |format| { + if (format.color_space == .srgb_nonlinear_khr) return format; + } else { + return formats[0]; + } +} + +fn _choose_mode(alloc: std.mem.Allocator) !vk.PresentModeKHR { + _ = ctx; + _ = alloc; + + return .fifo_khr; +} + +pub fn SwapChain(F: type) type { + return struct { + const Self = @This(); + + pub const Target = struct { + image_index: u32, + flight_index: u32, + image: vk.Image, + view: vk.ImageView, + flight: *F, + acquired: vk.Semaphore, // this semaphore will be signaled when the target is acquired + complete: vk.Semaphore, // this semaphore should be signaled when the render is complete + available: vk.Fence, // this fence should be signaled when the target flight is available + }; + + alloc: std.mem.Allocator, + + cur: u8 = 0, + flights: []F, + acquired_sems: []vk.Semaphore, + complete_sems: []vk.Semaphore, + available_fncs: []vk.Fence, + + cinfo: vk.SwapchainCreateInfoKHR, + handle: vk.SwapchainKHR, + + images: std.ArrayListUnmanaged(vk.Image), + views: std.ArrayListUnmanaged(vk.ImageView), + + pub fn init(alloc: std.mem.Allocator, flights: []F) !Self { + const acquired_sems = try alloc.alloc(vk.Semaphore, flights.len); + errdefer alloc.free(acquired_sems); + @memset(acquired_sems, .null_handle); + errdefer for (acquired_sems) |semaphore| ctx.D.destroySemaphore(semaphore, null); + for (acquired_sems) |*sem| { + sem.* = try ctx.D.createSemaphore(&vk.SemaphoreCreateInfo{}, null); + } + + const complete_sems = try alloc.alloc(vk.Semaphore, flights.len); + errdefer alloc.free(complete_sems); + @memset(complete_sems, .null_handle); + errdefer for (complete_sems) |semaphore| ctx.D.destroySemaphore(semaphore, null); + for (complete_sems) |*sem| { + sem.* = try ctx.D.createSemaphore(&vk.SemaphoreCreateInfo{}, null); + } + + const available_fncs = try alloc.alloc(vk.Fence, flights.len); + errdefer alloc.free(available_fncs); + @memset(available_fncs, .null_handle); + errdefer for (available_fncs) |fence| ctx.D.destroyFence(fence, null); + for (available_fncs) |*fnc| { + fnc.* = try ctx.D.createFence(&vk.FenceCreateInfo{ .flags = .{ .signaled_bit = true } }, null); + } + + const capabilities = try ctx.I.getPhysicalDeviceSurfaceCapabilitiesKHR(ctx.pdevice.*, ctx.surface.*); + const format = try _choose_format(alloc); + const mode = try _choose_mode(alloc); + + var min_image_count = @min(3, capabilities.min_image_count + 1); + if (capabilities.max_image_count > 0) { + min_image_count = @min(min_image_count, capabilities.max_image_count); + } + + const cinfo: vk.SwapchainCreateInfoKHR = .{ + .surface = ctx.surface.*, + .min_image_count = min_image_count, + .image_format = format.format, + .image_color_space = format.color_space, + .image_extent = undefined, // set in rebuild + .image_array_layers = 1, + .image_usage = .{ .color_attachment_bit = true }, + .image_sharing_mode = .exclusive, + .pre_transform = .{ .identity_bit_khr = true }, + .composite_alpha = .{ .opaque_bit_khr = true }, + .present_mode = mode, + .clipped = vk.TRUE, + .old_swapchain = .null_handle, + }; + + return .{ + .alloc = alloc, + .flights = flights, + .acquired_sems = acquired_sems, + .complete_sems = complete_sems, + .available_fncs = available_fncs, + .cinfo = cinfo, + .handle = .null_handle, + .images = .{}, + .views = .{}, + }; + } + + pub fn deinit(self: *Self) void { + for (self.views.items) |view| ctx.D.destroyImageView(view, null); + self.views.deinit(self.alloc); + + // images are owned by swapchain and not explicitly destroyed + self.images.deinit(self.alloc); + + ctx.D.destroySwapchainKHR(self.handle, null); + + // The easiest way to ensure fences and semaphores are not in use for deletion. + ctx.D.deviceWaitIdle() catch |err| switch (err) { + error.OutOfHostMemory, + error.OutOfDeviceMemory, + => {}, + error.DeviceLost, + => return, // If the devices is lost there isn't much I know to do. I guess deinit is not needed? + else => unreachable, + }; + + for (self.available_fncs) |fnc| ctx.D.destroyFence(fnc, null); + self.alloc.free(self.available_fncs); + + for (self.complete_sems) |sem| ctx.D.destroySemaphore(sem, null); + self.alloc.free(self.complete_sems); + + for (self.acquired_sems) |sem| ctx.D.destroySemaphore(sem, null); + self.alloc.free(self.acquired_sems); + } + + pub fn acquire(self: *Self) !Target { + const flight_index = self.cur; + const acquired = self.acquired_sems[flight_index]; + const complete = self.complete_sems[flight_index]; + const available = self.available_fncs[flight_index]; + + const timeout = std.math.maxInt(u64); + + for (0..5) |_| { + if (self.handle == .null_handle) { + try self.rebuild(); + std.debug.assert(self.handle != .null_handle); + } + + const fences: [1]vk.Fence = .{available}; + std.debug.assert(.success == try ctx.D.waitForFences( + 1, + &fences, + vk.TRUE, + std.math.maxInt(u64), + )); + + if (ctx.D.acquireNextImageKHR( + self.handle, + timeout, + acquired, + .null_handle, + )) |res| { + switch (res.result) { + .success, .suboptimal_khr => {}, + else => unreachable, + } + + try ctx.D.resetFences(1, &.{available}); + self.cur = @intCast(@mod(self.cur + 1, self.flights.len)); + + return Target{ + .image_index = res.image_index, + .flight_index = flight_index, + .image = self.images.items[res.image_index], + .view = self.views.items[res.image_index], + .flight = &self.flights[flight_index], + .acquired = acquired, + .complete = complete, + .available = available, + }; + } else |err| switch (err) { + error.OutOfDateKHR => { + self.handle = .null_handle; + continue; + }, + else => return err, + } + } else { + return error.CannotRecreateSwapchain; + } + } + + pub fn present(self: *Self, target: Target) !void { + if (ctx.Q.presentKHR(&vk.PresentInfoKHR{ + .wait_semaphore_count = 1, // todo extra semaphores? + .p_wait_semaphores = &.{target.complete}, + .swapchain_count = 1, + .p_swapchains = &.{self.handle}, + .p_image_indices = &.{target.image_index}, + .p_results = null, + })) |res| { + switch (res) { + .success => {}, + .suboptimal_khr => { + self.handle = .null_handle; + return; + }, + else => unreachable, + } + } else |err| switch (err) { + error.OutOfDateKHR => { + self.handle = .null_handle; + std.log.debug("Dropped frame", .{}); + return; + }, + else => return err, + } + } + + fn rebuild(self: *Self) !void { + std.debug.assert(self.handle == .null_handle); + + const capabilities = try ctx.I.getPhysicalDeviceSurfaceCapabilitiesKHR( + ctx.pdevice.*, + ctx.surface.*, + ); + self.cinfo.image_extent = capabilities.current_extent; + self.handle = try ctx.D.createSwapchainKHR(&self.cinfo, null); + ctx.D.destroySwapchainKHR(self.cinfo.old_swapchain, null); + errdefer ctx.D.destroySwapchainKHR(self.handle, null); + self.cinfo.old_swapchain = self.handle; + + for (self.views.items) |view| ctx.D.destroyImageView(view, null); + + var count: u32 = undefined; + std.debug.assert(.success == try ctx.D.getSwapchainImagesKHR(self.handle, &count, null)); + try self.images.resize(self.alloc, count); + try self.views.resize(self.alloc, count); + std.debug.assert(.success == try ctx.D.getSwapchainImagesKHR(self.handle, &count, self.images.items.ptr)); + + @memset(self.views.items, .null_handle); + errdefer for (self.views.items) |view| ctx.D.destroyImageView(view, null); + + for (self.images.items, self.views.items) |image, *view| { + view.* = try ctx.D.createImageView(&vk.ImageViewCreateInfo{ + .image = image, + .view_type = .@"2d", + .format = self.cinfo.image_format, + .components = .{ .r = .identity, .g = .identity, .b = .identity, .a = .identity }, + .subresource_range = .{ + .aspect_mask = .{ .color_bit = true }, + .base_mip_level = 0, + .level_count = 1, + .base_array_layer = 0, + .layer_count = 1, + }, + }, null); + } + } + }; +} diff --git a/src/nu/Window.zig b/src/nu/Window.zig index bc13dc9..3d10aed 100644 --- a/src/nu/Window.zig +++ b/src/nu/Window.zig @@ -40,6 +40,9 @@ pub var handle: *c.GLFWwindow = undefined; pub fn setup(_: std.mem.Allocator) !void { if (c.glfwInit() != c.GLFW_TRUE) std.debug.panic("GLFW Init Failed", .{}); + if (c.glfwVulkanSupported() != c.GLFW_TRUE) + std.debug.panic("GLFW Vulkan not supported", .{}); + c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API); c.glfwWindowHintString(c.GLFW_X11_CLASS_NAME, config.x11_class_name); c.glfwWindowHintString(c.GLFW_X11_INSTANCE_NAME, config.x11_instance_name);