nu simplified; working swapchain, queue, and validators.

it compiles, but initialization is not done

create debug messenger

Use proxies

wip swapchain

wip swapchain - stub usage

get device queue

wip swapchain - scaffolded with segfault

wip swapchain - fix segfault

wip swapchain - working, but resize broken. semaphore issue with naive handling

satisfy validation
This commit is contained in:
2024-11-21 23:03:08 -05:00
parent 9150224734
commit ccee3733b0
10 changed files with 890 additions and 137 deletions

View File

@@ -8,7 +8,7 @@
.hash = "1220cf0972c6fe05437c1a8689b955084385eb7ca1f8c14010d49ca5a89570a5d90d", .hash = "1220cf0972c6fe05437c1a8689b955084385eb7ca1f8c14010d49ca5a89570a5d90d",
}, },
.cimgui = .{ .cimgui = .{
.path="cimgui", .path = "cimgui",
}, },
}, },

View File

@@ -3,7 +3,7 @@ const nu = @import("nu.zig");
pub const nu_modules = .{ pub const nu_modules = .{
App, App,
UI, // UI,
}; };
pub const main = nu.main; pub const main = nu.main;
@@ -15,62 +15,62 @@ pub const nu_options: nu.Options = .{
}, },
}; };
pub const UI = struct { // pub const UI = struct {
const im = nu.ImGui; // const im = nu.ImGui;
//
pub const depends = .{im}; // pub const depends = .{im};
//
var color: @Vector(4, f32) = @splat(1); // var color: @Vector(4, f32) = @splat(1);
//
pub fn setup(_: std.mem.Allocator) !void { // pub fn setup(_: std.mem.Allocator) !void {
const io: *nu.ImGui.ImGuiIO = @ptrCast(nu.ImGui.igGetIO()); // const io: *nu.ImGui.ImGuiIO = @ptrCast(nu.ImGui.igGetIO());
io.ConfigFlags |= nu.ImGui.ImGuiConfigFlags_DockingEnable; // io.ConfigFlags |= nu.ImGui.ImGuiConfigFlags_DockingEnable;
} // }
//
pub fn frame() !void { // pub fn frame() !void {
nu.ImGui.igShowMetricsWindow(null); // nu.ImGui.igShowMetricsWindow(null);
//
{ // {
const viewport = im.igGetMainViewport(); // const viewport = im.igGetMainViewport();
im.igSetNextWindowPos(viewport.*.WorkPos, 0, .{ .x = 0, .y = 0 }); // im.igSetNextWindowPos(viewport.*.WorkPos, 0, .{ .x = 0, .y = 0 });
im.igSetNextWindowSize(viewport.*.WorkSize, 0); // im.igSetNextWindowSize(viewport.*.WorkSize, 0);
im.igSetNextWindowViewport(viewport.*.ID); // im.igSetNextWindowViewport(viewport.*.ID);
im.igPushStyleVar_Float(im.ImGuiStyleVar_WindowRounding, 0); // im.igPushStyleVar_Float(im.ImGuiStyleVar_WindowRounding, 0);
im.igPushStyleVar_Float(im.ImGuiStyleVar_WindowBorderSize, 0); // im.igPushStyleVar_Float(im.ImGuiStyleVar_WindowBorderSize, 0);
im.igPushStyleVar_Vec2(im.ImGuiStyleVar_WindowPadding, .{ .x = 0, .y = 0 }); // im.igPushStyleVar_Vec2(im.ImGuiStyleVar_WindowPadding, .{ .x = 0, .y = 0 });
defer im.igPopStyleVar(3); // defer im.igPopStyleVar(3);
//
const window_flags = // const window_flags =
im.ImGuiWindowFlags_MenuBar | // im.ImGuiWindowFlags_MenuBar |
im.ImGuiWindowFlags_NoDocking | // im.ImGuiWindowFlags_NoDocking |
im.ImGuiWindowFlags_NoTitleBar | // im.ImGuiWindowFlags_NoTitleBar |
im.ImGuiWindowFlags_NoCollapse | // im.ImGuiWindowFlags_NoCollapse |
im.ImGuiWindowFlags_NoResize | // im.ImGuiWindowFlags_NoResize |
im.ImGuiWindowFlags_NoMove | // im.ImGuiWindowFlags_NoMove |
im.ImGuiWindowFlags_NoBringToFrontOnFocus | // im.ImGuiWindowFlags_NoBringToFrontOnFocus |
im.ImGuiWindowFlags_NoNavFocus | // im.ImGuiWindowFlags_NoNavFocus |
im.ImGuiWindowFlags_NoBackground; // im.ImGuiWindowFlags_NoBackground;
//
const dock_flags = // const dock_flags =
im.ImGuiDockNodeFlags_PassthruCentralNode | // im.ImGuiDockNodeFlags_PassthruCentralNode |
im.ImGuiDockNodeFlags_NoDockingOverCentralNode; // im.ImGuiDockNodeFlags_NoDockingOverCentralNode;
//
_ = im.igBegin("Main Dockspace", null, window_flags); // _ = im.igBegin("Main Dockspace", null, window_flags);
const id = im.igGetID_Str("maindockspace"); // const id = im.igGetID_Str("maindockspace");
_ = im.igDockSpace(id, .{ .x = 0, .y = 0 }, dock_flags, null); // _ = im.igDockSpace(id, .{ .x = 0, .y = 0 }, dock_flags, null);
im.igEnd(); // im.igEnd();
} // }
//
if (nu.ImGui.igBegin("Color", null, nu.ImGui.ImGuiWindowFlags_None)) { // if (nu.ImGui.igBegin("Color", null, nu.ImGui.ImGuiWindowFlags_None)) {
if (nu.ImGui.igColorEdit4("color", @ptrCast(&color), nu.ImGui.ImGuiColorEditFlags_AlphaPreviewHalf)) {} // if (nu.ImGui.igColorEdit4("color", @ptrCast(&color), nu.ImGui.ImGuiColorEditFlags_AlphaPreviewHalf)) {}
} // }
nu.ImGui.igEnd(); // nu.ImGui.igEnd();
} // }
}; // };
const App = struct { const App = struct {
const vk = @import("vk"); const vk = @import("vk");
const au = @import("nu/Render/au.zig"); // const au = @import("nu/Render/au.zig");
pub const depends = .{nu.Render}; pub const depends = .{nu.Render};
@@ -82,5 +82,5 @@ const App = struct {
pub fn frame() !void {} pub fn frame() !void {}
pub fn present(_: au.CommandBufferProxy) void {} // pub fn present(_: au.CommandBufferProxy) void {}
}; };

View File

@@ -3,7 +3,7 @@ const root = @import("root");
pub const Window = @import("nu/Window.zig"); pub const Window = @import("nu/Window.zig");
pub const Render = @import("nu/Render.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"); pub const Bus = @import("nu/Bus.zig");
@@ -109,5 +109,4 @@ const Graph = struct {
} }
}; };
test { test {}
}

View File

@@ -4,7 +4,6 @@ const std = @import("std");
const vk = @import("vk"); const vk = @import("vk");
const nu = @import("../nu.zig"); const nu = @import("../nu.zig");
const au = @import("Render/au.zig");
const Render = @import("Render.zig"); const Render = @import("Render.zig");
const Window = @import("Window.zig"); const Window = @import("Window.zig");
@@ -18,7 +17,7 @@ const config = nu.config.imgui;
pub const depends = .{ Render, Window }; pub const depends = .{ Render, Window };
pub fn loader_wrapper(procname: [*c]const u8, _: ?*anyopaque) callconv(.C) vk.PfnVoidFunction { 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; var ctx: *im.ImGuiContext = undefined;
@@ -39,23 +38,27 @@ pub fn setup(_: std.mem.Allocator) !void {
} }
errdefer im.impl.ImGui_ImplGlfw_Shutdown(); errdefer im.impl.ImGui_ImplGlfw_Shutdown();
descriptor_pool = try au.D.createDescriptorPool(&vk.DescriptorPoolCreateInfo{ descriptor_pool = try Render.ctx.dw.createDescriptorPool(
.flags = .{ .free_descriptor_set_bit = true }, Render.ctx.device,
.pool_size_count = 1, &vk.DescriptorPoolCreateInfo{
.p_pool_sizes = &.{vk.DescriptorPoolSize{ .flags = .{ .free_descriptor_set_bit = true },
.descriptor_count = 32, .pool_size_count = 1,
.type = .combined_image_sampler, .p_pool_sizes = &.{vk.DescriptorPoolSize{
}}, .descriptor_count = 32,
.max_sets = 32, .type = .combined_image_sampler,
}, null); }},
errdefer au.D.destroyDescriptorPool(descriptor_pool, null); .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{ if (im.impl.ImGui_ImplVulkan_Init(@constCast(&im.impl.ImGui_ImplVulkan_InitInfo{
.Instance = @ptrFromInt(@intFromEnum(au.I.handle)), .Instance = @ptrFromInt(@intFromEnum(Render.ctx.instance)),
.PhysicalDevice = @ptrFromInt(@intFromEnum(au.device_config.pdev)), .PhysicalDevice = @ptrFromInt(@intFromEnum(Render.ctx.pdevice)),
.Device = @ptrFromInt(@intFromEnum(au.D.handle)), .Device = @ptrFromInt(@intFromEnum(Render.ctx.device)),
.QueueFamily = au.device_config.family, .QueueFamily = au.device_config.family, // todo
.Queue = @ptrFromInt(@intFromEnum(au.Q.handle)), .Queue = @ptrFromInt(@intFromEnum(au.Q.handle)), // todo
.DescriptorPool = @ptrFromInt(@intFromEnum(descriptor_pool)), .DescriptorPool = @ptrFromInt(@intFromEnum(descriptor_pool)),
.RenderPass = null, .RenderPass = null,
.MinImageCount = 2, .MinImageCount = 2,
@@ -65,7 +68,7 @@ pub fn setup(_: std.mem.Allocator) !void {
.depth_attachment_format = .undefined, .depth_attachment_format = .undefined,
.stencil_attachment_format = .undefined, .stencil_attachment_format = .undefined,
.color_attachment_count = 1, .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, .MSAASamples = 0,
.PipelineCache = null, .PipelineCache = null,
@@ -83,9 +86,9 @@ pub fn setup(_: std.mem.Allocator) !void {
} }
pub fn teardown() 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(); 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.impl.ImGui_ImplGlfw_Shutdown();
im.igDestroyContext(ctx); im.igDestroyContext(ctx);
} }
@@ -96,7 +99,7 @@ pub fn frame() !void {
im.igNewFrame(); im.igNewFrame();
} }
pub fn present(cmd: au.CommandBufferProxy) void { pub fn present(cmd: au.CommandBufferProxy) void { // todo
im.igEndFrame(); im.igEndFrame();
im.igRender(); im.igRender();

View File

@@ -7,7 +7,10 @@ const builtin = @import("builtin");
const vk = @import("vk"); const vk = @import("vk");
const nu = @import("../nu.zig"); 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 { pub const Config = struct {
app_name: [*:0]const u8 = "nu-au-app", app_name: [*:0]const u8 = "nu-au-app",
@@ -24,7 +27,6 @@ pub const Config = struct {
minor: u10 = 0, minor: u10 = 0,
patch: u12 = 0, patch: u12 = 0,
} = .{}, } = .{},
frames_in_flight: u8 = 3,
use_debug_messenger: bool = switch (builtin.mode) { use_debug_messenger: bool = switch (builtin.mode) {
.Debug, .ReleaseSafe => true, .Debug, .ReleaseSafe => true,
.ReleaseSmall, .ReleaseFast => false, .ReleaseSmall, .ReleaseFast => false,
@@ -34,84 +36,178 @@ const config = nu.config.render;
pub const depends = .{nu.Window}; pub const depends = .{nu.Window};
var sc: au.SwapChain = undefined; const SwapChain = swap_chain.SwapChain(Flight);
var flights: au.Flights = undefined; 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 { pub fn setup(alloc: std.mem.Allocator) !void {
// todo pick apart au into helpers; not a sub-module filled with its own globals. try ctx.init(alloc);
try au.init(alloc); errdefer ctx.deinit();
errdefer au.deinit();
sc = try au.SwapChain.init(alloc); _flights = try alloc.alloc(Flight, 3);
errdefer sc.deinit(); 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); _sc = try SwapChain.init(alloc, _flights);
errdefer flights.deinit(); errdefer _sc.deinit();
} }
pub fn teardown() void { pub fn teardown() void {
au.D.deviceWaitIdle() catch |err| std.debug.panic("Device wait failed: {!}", .{err}); _sc.deinit();
flights.deinit(); for (_flights) |flight| flight.deinit();
sc.deinit(); ctx.deinit();
au.deinit();
} }
pub fn render() !void { pub fn render() !void {
const flight: au.Flights.Flight = flights.next(); const target = try _sc.acquire();
try flight.wait();
while (true) { const render_area: vk.Rect2D = .{
_ = try sc.rebuild(); .offset = .{ .x = 0, .y = 0 },
.extent = _sc.cinfo.image_extent,
};
const target = sc.acquire(flight.acquire, .null_handle) catch |err| switch (err) { try ctx.D.resetCommandPool(target.flight.pool, .{});
error.OutOfDateKHR => { var cmd = ctx.CommandBufferProxy.init(target.flight.cmd, ctx.dw);
sc.mark();
continue; 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 = .{ cmd.beginRendering(&vk.RenderingInfo{
.offset = .{ .x = 0, .y = 0 }, .render_area = render_area,
.extent = sc.cinfo.image_extent, .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, .{}); cmd.endRendering();
var cmd = au.CommandBufferProxy.init(flight.cmd, au.D.wrapper);
try cmd.beginCommandBuffer(&.{ .flags = .{ .one_time_submit_bit = true } }); cmd.pipelineBarrier(
target.begin_rendering(cmd, render_area); .{ .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. try ctx.Q.submit(
1,
nu.engine.invoke("present", .{cmd}); &.{
target.end_rendering(cmd);
try cmd.endCommandBuffer();
try au.Q.submit(1, &.{
vk.SubmitInfo{ vk.SubmitInfo{
.wait_semaphore_count = 1, .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 = &.{ .p_wait_dst_stage_mask = &.{
vk.PipelineStageFlags{ .color_attachment_output_bit = true }, vk.PipelineStageFlags{ .color_attachment_output_bit = true },
}, },
.command_buffer_count = 1, .command_buffer_count = 1,
.p_command_buffers = &.{cmd.handle}, .p_command_buffers = &.{target.flight.cmd},
.signal_semaphore_count = 1, .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)) |_| { try _sc.present(target);
return;
} else |err| switch (err) {
error.OutOfDateKHR => {
try flight.wait();
sc.mark();
continue;
},
else => return err,
}
}
} }

66
src/nu/Render/Debug.zig Normal file
View File

@@ -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;
}

View File

@@ -99,7 +99,12 @@ pub fn rebuild(self: *Self) !bool {
} }
pub fn acquire(self: Self, semaphore: vk.Semaphore, fence: vk.Fence) !Target { 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 .{ return .{
.idx = acq.image_index, .idx = acq.image_index,
.image = self.images.items[acq.image_index], .image = self.images.items[acq.image_index],

298
src/nu/Render/ctx.zig Normal file
View File

@@ -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;

View File

@@ -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);
}
}
};
}

View File

@@ -40,6 +40,9 @@ pub var handle: *c.GLFWwindow = undefined;
pub fn setup(_: std.mem.Allocator) !void { pub fn setup(_: std.mem.Allocator) !void {
if (c.glfwInit() != c.GLFW_TRUE) std.debug.panic("GLFW Init Failed", .{}); 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.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_CLASS_NAME, config.x11_class_name);
c.glfwWindowHintString(c.GLFW_X11_INSTANCE_NAME, config.x11_instance_name); c.glfwWindowHintString(c.GLFW_X11_INSTANCE_NAME, config.x11_instance_name);