diff --git a/shape/.gitignore b/shape/.gitignore new file mode 100644 index 0000000..6182617 --- /dev/null +++ b/shape/.gitignore @@ -0,0 +1,3 @@ +.zig-cache/ +zig-out/ +.idea/ diff --git a/shape/.tool-versions b/shape/.tool-versions new file mode 100644 index 0000000..2aa6915 --- /dev/null +++ b/shape/.tool-versions @@ -0,0 +1,3 @@ +zig 0.14.0-dev.3217+5b9b5e45c + +# zig 0.14.0-dev.2647+5322459a0 diff --git a/shape/README.md b/shape/README.md new file mode 100644 index 0000000..e469a5b --- /dev/null +++ b/shape/README.md @@ -0,0 +1,18 @@ +### Configuring CLion for debugging + +Use https://github.com/asdf-vm/asdf with https://github.com/allemangd/asdf-zig for the versioned zig master in `.tool-versions`. + +Build ZLS with that zig and point ZigBrains to it. + +Custom Build Application configurations: + +- `zig-shape` uses custom target `zig build -fincremental` and executes `zig-out/bin/zig-shape`. +- `exe-unit-tests` uses custom target `zig build -fincremental build-tests` and executes `zig-out/dev/exe-unit-tests` +- `lib-unit-tests` uses custom target `zig build -fincremental build-tests` and executes `zig-out/dev/lib-unit-tests` + +Then native "build", "run", and "debug" buttons will work. + +Optionally create a "clean" target that executes `rm -r .zig-cache zig-out` + +Switching between debug and release build is more difficult. Probably easier to run release builds +only via CLI, like `zig build --release=fast` or `zig build --release=fast test`. diff --git a/shape/build.zig b/shape/build.zig new file mode 100644 index 0000000..435045f --- /dev/null +++ b/shape/build.zig @@ -0,0 +1,83 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const vulkan = b.dependency("vulkan_zig", .{ + .registry = b.dependency("vulkan_headers", .{}).path("registry/vk.xml"), + .target = target, + .optimize = optimize, + }).module("vulkan-zig"); + + const glfw = b.dependency("glfw", .{ + .target = target, + .optimize = optimize, + }).artifact("glfw"); + + const lib_mod = b.createModule(.{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + lib_mod.addImport("vk", vulkan); + + const exe_mod = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + exe_mod.addImport("vk", vulkan); + exe_mod.addImport("zig-shape-lib", lib_mod); + exe_mod.linkLibrary(glfw); + + const lib = b.addStaticLibrary(.{ + .name = "zig-shape", + .root_module = lib_mod, + }); + + b.installArtifact(lib); + + const exe = b.addExecutable(.{ + .name = "zig-shape", + .root_module = exe_mod, + }); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + const lib_unit_tests = b.addTest(.{ + .root_module = lib_mod, + .name = "lib-unit-tests", + }); + + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const exe_unit_tests = b.addTest(.{ + .root_module = exe_mod, + .name = "exe-unit-tests", + }); + + const build_test_step = b.step("build-test", "Build unit tests"); + + const install_lib_test = b.addInstallArtifact(lib_unit_tests, .{ .dest_dir = .{ .override = .{ .custom = "dev" } } }); + build_test_step.dependOn(&install_lib_test.step); + + const install_exe_test = b.addInstallArtifact(exe_unit_tests, .{ .dest_dir = .{ .override = .{ .custom = "dev" } } }); + build_test_step.dependOn(&install_exe_test.step); + + const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_lib_unit_tests.step); + test_step.dependOn(&run_exe_unit_tests.step); +} diff --git a/shape/build.zig.zon b/shape/build.zig.zon new file mode 100644 index 0000000..ad91da8 --- /dev/null +++ b/shape/build.zig.zon @@ -0,0 +1,27 @@ +.{ + .name = "zig-shape", + .version = "0.0.0", + + .minimum_zig_version = "0.14.0", + + .dependencies = .{ + .glfw = .{ + .url = "https://github.com/emidoots/glfw/archive/ec656e10d3643a3a71149bb96ae6b0831b84b677.tar.gz", + .hash = "12207be469bad84baed2ede75cf89ede37e1ed5ee8be62a54d2b2112c5f56a44cc89", + }, + .vulkan_zig = .{ + .url = "https://github.com/Snektron/vulkan-zig/archive/604416bf44baf95568c428e1aa55499aa8e07607.tar.gz", + .hash = "1220129ea1652cc0f8578db3dcba254bc9fbc6fdd195b5e1021bf4da8592ea5dc9fb", + }, + .vulkan_headers = .{ + .url = "https://github.com/KhronosGroup/Vulkan-Headers/archive/v1.3.283.tar.gz", + .hash = "1220a7e73d72a0d56bc2a65f9d8999a7c019e42260a0744c408d1cded111bc205e10", + }, + }, + + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/shape/src/Core.zig b/shape/src/Core.zig new file mode 100644 index 0000000..cea9876 --- /dev/null +++ b/shape/src/Core.zig @@ -0,0 +1,308 @@ +const std = @import("std"); +const lib = @import("std"); +const builtin = @import("builtin"); + +const vk = @import("vk"); +const glfw = @cImport({ + @cDefine("GLFW_INCLUDE_NONE", {}); + @cInclude("GLFW/glfw3.h"); +}); + +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, + vk.extensions.khr_timeline_semaphore, +} ++ switch (builtin.mode) { + .Debug, .ReleaseSafe => .{ + vk.extensions.ext_debug_utils, + }, + else => .{}, +}); + +const Base = vk.BaseWrapper(apis); +const Instance = vk.InstanceProxy(apis); +const Device = vk.DeviceProxy(apis); +const Queue = vk.QueueProxy(apis); + +const Core = @This(); + +alloc: std.mem.Allocator, +b: *Base, +i: Instance, +d: Device, +q: Queue, +window: *glfw.GLFWwindow, +pdev: vk.PhysicalDevice, +surface: vk.SurfaceKHR, +msg: switch (builtin.mode) { + .Debug, .ReleaseSafe => vk.DebugUtilsMessengerEXT, + else => void, +}, + +pub fn init( + alloc: std.mem.Allocator, + options: struct { + width: u32 = 1280, + height: u32 = 720, + title: []const u8 = "Hello World", + }, +) !Core { + var self: Core = undefined; + self.alloc = alloc; + + var arena = std.heap.ArenaAllocator.init(alloc); + defer arena.deinit(); + const ialloc = arena.allocator(); + + const titlez = try ialloc.dupeZ(u8, options.title); + + if (glfw.glfwInit() != glfw.GLFW_TRUE) { + return error.GLFWInitFailed; + } + errdefer glfw.glfwTerminate(); + + glfw.glfwWindowHint(glfw.GLFW_CLIENT_API, glfw.GLFW_NO_API); + glfw.glfwWindowHintString(glfw.GLFW_X11_CLASS_NAME, "floating_window"); + glfw.glfwWindowHintString(glfw.GLFW_X11_INSTANCE_NAME, "floating_window"); + self.window = glfw.glfwCreateWindow( + @intCast(options.width), + @intCast(options.height), + titlez, + null, + null, + ) orelse + return error.GLFWWindowFailed; + + const bw = try alloc.create(vk.BaseWrapper(apis)); + errdefer alloc.destroy(bw); + bw.* = try vk.BaseWrapper(apis).load(glfwGetInstanceProcAddress); + self.b = bw; + + var iexts = std.ArrayList([*:0]const u8).init(ialloc); + var ilyrs = std.ArrayList([*:0]const u8).init(ialloc); + + switch (builtin.mode) { + .Debug, .ReleaseSafe => { + try iexts.appendSlice(&.{ + vk.extensions.ext_debug_utils.name, + }); + try ilyrs.appendSlice(&.{ + "VK_LAYER_KHRONOS_validation", + }); + }, + else => {}, + } + + { + var count: u32 = undefined; + const ptr = glfwGetRequiredInstanceExtensions(&count); + try iexts.appendSlice(ptr[0..count]); + } + + var dmci = switch (builtin.mode) { + .Debug, .ReleaseSafe => 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, + }, + else => {}, + }; + + var ici: vk.InstanceCreateInfo = .{ + .p_application_info = &vk.ApplicationInfo{ + .api_version = vk.API_VERSION_1_3, + .engine_version = 0, + .application_version = 0, + .p_application_name = titlez, + .p_engine_name = titlez, + }, + .enabled_extension_count = @intCast(iexts.items.len), + .pp_enabled_extension_names = iexts.items.ptr, + .enabled_layer_count = @intCast(ilyrs.items.len), + .pp_enabled_layer_names = ilyrs.items.ptr, + }; + + switch (builtin.mode) { + .Debug, .ReleaseSafe => { + ici.p_next = &dmci; + }, + else => {}, + } + + self.i.handle = try self.b.createInstance(&ici, null); + const iw = try alloc.create(vk.InstanceWrapper(apis)); + errdefer alloc.destroy(iw); + iw.* = try vk.InstanceWrapper(apis).load(self.i.handle, bw.dispatch.vkGetInstanceProcAddr); + self.i.wrapper = iw; + errdefer self.i.destroyInstance(null); + + self.msg = switch (builtin.mode) { + .Debug, .ReleaseSafe => try self.i.createDebugUtilsMessengerEXT(&dmci, null), + else => {}, + }; + errdefer switch (builtin.mode) { + .Debug, .ReleaseSafe => self.i.destroyDebugUtilsMessengerEXT(self.msg, null), + else => {}, + }; + + // Device selection. Just use the first one. + const pdevs: []vk.PhysicalDevice = try self.i.enumeratePhysicalDevicesAlloc(ialloc); + std.debug.assert(pdevs.len >= 1); + self.pdev = pdevs[0]; + + switch (glfwCreateWindowSurface( + self.i.handle, + self.window, + null, + &self.surface, + )) { + .success => {}, + else => return error.GLFWWindowSurfaceFailed, + } + errdefer self.i.destroySurfaceKHR(self.surface, null); + + // Queue selection. Just use the first one. + const qci: []const vk.DeviceQueueCreateInfo = &.{ + vk.DeviceQueueCreateInfo{ + .queue_family_index = 0, + .queue_count = 1, + .p_queue_priorities = &.{1}, + }, + }; + + var dexts = std.ArrayList([*:0]const u8).init(ialloc); + var dlyrs = std.ArrayList([*:0]const u8).init(ialloc); + + _ = &dlyrs; + + try dexts.appendSlice(&.{ + vk.extensions.khr_swapchain.name, + vk.extensions.khr_dynamic_rendering.name, + vk.extensions.khr_timeline_semaphore.name, + }); + + self.d.handle = try self.i.createDevice(self.pdev, &.{ + .enabled_extension_count = @intCast(dexts.items.len), + .pp_enabled_extension_names = dexts.items.ptr, + .enabled_layer_count = @intCast(dlyrs.items.len), + .pp_enabled_layer_names = dlyrs.items.ptr, + .queue_create_info_count = @intCast(qci.len), + .p_queue_create_infos = qci.ptr, + .p_next = &vk.PhysicalDeviceDynamicRenderingFeatures{ + .dynamic_rendering = vk.TRUE, + }, + }, null); + const dw = try alloc.create(vk.DeviceWrapper(apis)); + dw.* = try vk.DeviceWrapper(apis).load(self.d.handle, iw.dispatch.vkGetDeviceProcAddr); + self.d.wrapper = dw; + errdefer self.d.destroyDevice(null); + + self.q.handle = self.d.getDeviceQueue(0, 0); + self.q.wrapper = dw; + + return self; +} + +pub fn deinit(self: Core) void { + self.d.destroyDevice(null); + + self.i.destroySurfaceKHR(self.surface, null); + + switch (builtin.mode) { + .Debug, .ReleaseSafe => self.i.destroyDebugUtilsMessengerEXT(self.msg, null), + else => {}, + } + + self.i.destroyInstance(null); + + self.alloc.destroy(self.d.wrapper); + self.alloc.destroy(self.i.wrapper); + self.alloc.destroy(self.b); + + glfw.glfwTerminate(); +} + +extern fn glfwGetInstanceProcAddress( + instance: vk.Instance, + procname: [*:0]const u8, +) vk.PfnVoidFunction; + +extern fn glfwGetRequiredInstanceExtensions( + count: *u32, +) [*][*:0]const u8; + +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; +} + +extern fn glfwCreateWindowSurface( + instance: vk.Instance, + window: *glfw.GLFWwindow, + allocation_callbacks: ?*const vk.AllocationCallbacks, + surface: *vk.SurfaceKHR, +) vk.Result; + +extern fn glfwGetPhysicalDevicePresentationSupport( + instance: vk.Instance, + pdev: vk.PhysicalDevice, + queuefamily: u32, +) c_int; diff --git a/shape/src/main.zig b/shape/src/main.zig new file mode 100644 index 0000000..1f85a61 --- /dev/null +++ b/shape/src/main.zig @@ -0,0 +1,164 @@ +const std = @import("std"); +const lib = @import("zig-shape-lib"); +const builtin = @import("builtin"); + +const Core = @import("Core.zig"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const alloc = gpa.allocator(); + + const core = try Core.init(alloc, .{}); + defer core.deinit(); + + std.log.debug("Created instance: {any}", .{core.i.handle}); + std.log.debug("Created device: {any}", .{core.d.handle}); + std.log.debug("Created queue: {any}", .{core.q.handle}); + std.log.debug("Created surface: {any}", .{core.surface}); + + // var caps = try I.getPhysicalDeviceSurfaceCapabilitiesKHR(pdev, surface); + // const format = search: { + // const formats = try I.getPhysicalDeviceSurfaceFormatsAllocKHR(pdev, surface, alloc); + // defer alloc.free(formats); + // for (formats) |f| { + // if (f.color_space == .srgb_nonlinear_khr) break :search f; + // } else break :search formats[0]; + // }; + // const mode: vk.PresentModeKHR = .fifo_khr; + + // var scinfo: vk.SwapchainCreateInfoKHR = .{ + // .surface = surface, + // .min_image_count = std.math.clamp( + // @min(3, caps.min_image_count + 1), + // caps.min_image_count, + // if (caps.max_image_count > 0) caps.max_image_count else 127, + // ), + // .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, + // }; + + // var sc: vk.SwapchainKHR = .null_handle; + // defer D.destroySwapchainKHR(device, sc, null); + + // const FRAMES_IN_FLIGHT = 3; + // + // var pools = [FRAMES_IN_FLIGHT]vk.CommandPool; + // var fence = [FRAMES_IN_FLIGHT]vk.Fence; + + // const pool = D.createCommandPool(device, &.{ + // .queue_family_index = family, + // }, null); + // defer D.destroyCommandPool(device, pool, null); + + // var cmd: vk.CommandBuffer = undefined; + // try D.allocateCommandBuffers(device, &.{vk.CommandBufferAllocateInfo{ + // .command_buffer_count = 1, + // .command_pool = pool, + // .level = .primary, + // }}, @ptrCast(&cmd)); + // defer D.freeCommandBuffers(device, pool, 1, @ptrCast(&cmd)); + + // while (glfw.glfwWindowShouldClose(window) == glfw.GLFW_FALSE) { + // if (glfw.glfwGetWindowAttrib(window, glfw.GLFW_FOCUSED) == glfw.GLFW_TRUE) + // glfw.glfwPollEvents() + // else + // glfw.glfwWaitEventsTimeout(1.0 / 60.0); + // + // if (sc == .null_handle) { + // caps = try I.getPhysicalDeviceSurfaceCapabilitiesKHR(pdev, surface); + // scinfo.image_extent = caps.current_extent; + // sc = try D.createSwapchainKHR(device, &scinfo, null); + // D.destroySwapchainKHR(device, scinfo.old_swapchain, null); + // scinfo.old_swapchain = .null_handle; + // } + // + // const render_area: vk.Rect2D = .{ + // .offset = .{ .x = 0, .y = 0 }, + // .extent = scinfo.image_extent, + // }; + // + // try D.resetCommandPool(device, pool, .{}); + // try D.beginCommandBuffer(cmd, .{ .flags = .{ .one_time_submit_bit = true } }); + // { + // D.cmdPipelineBarrier( + // cmd, + // .{ .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, + // .dst_queue_family_index = 0, + // .image = image, + // .subresource_range = .{ + // .aspect_mask = .{ .color_bit = true }, + // .base_mip_level = 0, + // .level_count = 1, + // .base_array_layer = 0, + // .layer_count = 1, + // }, + // }, + // }, + // ); + // + // D.cmdPipelineBarrier( + // cmd, + // .{ .color_attachment_output_bit = true }, + // .{ .bottom_of_pipe_bit = true }, + // .{}, + // 0, + // null, + // 0, + // null, + // 1, + // &.{ + // vk.ImageMemoryBarrier{ + // // todo ! + // }, + // }, + // ); + // } + // try D.endCommandBuffer(cmd); + // } +} + +test "simple test" { + var list = std.ArrayList(i32).init(std.testing.allocator); + defer list.deinit(); // Try commenting this out and see if zig detects the memory leak! + try list.append(42); + try std.testing.expectEqual(@as(i32, 42), list.pop()); +} + +test "use other module" { + try std.testing.expectEqual(@as(i32, 150), lib.add(100, 50)); +} + +test "fuzz example" { + const Context = struct { + fn testOne(context: @This(), input: []const u8) anyerror!void { + _ = context; + // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case! + try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input)); + } + }; + try std.testing.fuzz(Context{}, Context.testOne, .{}); +} diff --git a/shape/src/root.zig b/shape/src/root.zig new file mode 100644 index 0000000..27d2be8 --- /dev/null +++ b/shape/src/root.zig @@ -0,0 +1,13 @@ +//! By convention, root.zig is the root source file when making a library. If +//! you are making an executable, the convention is to delete this file and +//! start with main.zig instead. +const std = @import("std"); +const testing = std.testing; + +pub export fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "basic add functionality" { + try testing.expect(add(3, 7) == 10); +} diff --git a/shape/src/swapchain.zig b/shape/src/swapchain.zig new file mode 100644 index 0000000..cf25ada --- /dev/null +++ b/shape/src/swapchain.zig @@ -0,0 +1,92 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const Core = @import("Core.zig"); + +const vk = @import("vk"); + +const I = &Core.I; +const D = &Core.D; +const pdev = &Core.pdev; +const surface = &Core.surface; + +pub fn SwapChain(Flight: type, N: u3) type { + return struct { + handle: vk.SwapchainKHR = .null_handle, + cinfo: vk.SwapchainCreateInfoKHR, + + // todo populate and use this. + flights: std.MultiArrayList(struct{ + fence: vk.Fence, + acquired: vk.Semaphore, + complete: vk.Semaphore, + }), + + pub fn init(alloc: std.mem.Allocator) !SwapChain { + var arena = std.heap.ArenaAllocator.init(alloc); + defer arena.deinit(); + const ialloc = arena.allocator(); + + const caps = try I.getPhysicalDeviceSurfaceCapabilitiesKHR( + pdev, + surface, + ); + + const formats = try I.getPhysicalDeviceSurfaceFormatsAllocKHR( + pdev, + surface, + ialloc, + ); + const format: vk.SurfaceFormatKHR = formats[0]; + const mode: vk.PresentModeKHR = .fifo_khr; + + return .{ + .cinfo = vk.SwapchainCreateInfoKHR{ + .surface = surface, + .min_image_count = std.math.clamp( + @min(3, caps.min_image_count + 1), + caps.min_image_count, + if (caps.max_image_count > 0) caps.max_image_count else 127, + ), + .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, + }, + }; + } + + fn build(self: *SwapChain) !void { + if (self.handle != .null_handle) return; + + const caps = try I.getPhysicalDeviceSurfaceCapabilitiesKHR( + pdev, + surface, + ); + self.cinfo.image_extent = caps.current_extent; + + self.handle = try D.createSwapchainKHR(&self.cinfo, null); + D.destroySwapchainKHR(self.cinfo.old_swapchain, null); + self.cinfo.old_swapchain = self.handle; + } + + pub fn acquire(self: *SwapChain) !void { + D.acquireNextImageKHR(self.handle, std.math.maxInt(u64), semaphore: Semaphore, fence: Fence) + } + + pub fn invalidate(self: *SwapChain) void { + self.handle = .null_handle; + } + + pub fn deinit(self: SwapChain) void { + D.destroySwapchainKHR(self.cinfo.old_swapchain, null); + } + }; +}