From 2b3734a087ac6e3b682adb486e1fb45045448aff Mon Sep 17 00:00:00 2001 From: David Allemang Date: Wed, 15 Jan 2025 16:47:59 -0500 Subject: [PATCH 1/4] Initial Commit --- .gitignore | 3 +++ .tool-versions | 1 + README.md | 18 +++++++++++++ build.zig | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ build.zig.zon | 16 ++++++++++++ src/main.zig | 35 +++++++++++++++++++++++++ src/root.zig | 14 ++++++++++ 7 files changed, 157 insertions(+) create mode 100644 .gitignore create mode 100644 .tool-versions create mode 100644 README.md create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/main.zig create mode 100644 src/root.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6182617 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.zig-cache/ +zig-out/ +.idea/ diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..fe42a60 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +zig 0.14.0-dev.2647+5322459a0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e469a5b --- /dev/null +++ b/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/build.zig b/build.zig new file mode 100644 index 0000000..acc7311 --- /dev/null +++ b/build.zig @@ -0,0 +1,70 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const lib_mod = b.createModule(.{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const exe_mod = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + exe_mod.addImport("zig-shape-lib", lib_mod); + + 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/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..9101812 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,16 @@ +.{ + .name = "zig-shape", + .version = "0.0.0", + + .minimum_zig_version = "0.14.0", + + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + }, + + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..deacd15 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,35 @@ +const std = @import("std"); +const lib = @import("zig-shape-lib"); + +pub fn main() !void { + std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + + const stdout_file = std.io.getStdOut().writer(); + var bw = std.io.bufferedWriter(stdout_file); + const stdout = bw.writer(); + + try stdout.print("Run `zig build test` to run the tests.\n", .{}); + + try bw.flush(); +} + +test "simple test" { + var list = std.ArrayList(i32).init(std.testing.allocator); + defer list.deinit(); + 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 global = struct { + fn testOne(input: []const u8) anyerror!void { + // 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(global.testOne, .{}); +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..0c5c75e --- /dev/null +++ b/src/root.zig @@ -0,0 +1,14 @@ +//! 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); +} From f325f65f4f53911d9bba18d49a35813209d3d356 Mon Sep 17 00:00:00 2001 From: David Allemang Date: Sat, 15 Feb 2025 12:31:04 -0500 Subject: [PATCH 2/4] WIP: Vulkan Setup --- .tool-versions | 4 +- build.zig | 15 +- build.zig.zon | 13 +- src/main.zig | 361 +++++++++++++++++++++++++++++++++++++++++++++++-- src/root.zig | 1 - 5 files changed, 380 insertions(+), 14 deletions(-) diff --git a/.tool-versions b/.tool-versions index fe42a60..2aa6915 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1,3 @@ -zig 0.14.0-dev.2647+5322459a0 +zig 0.14.0-dev.3217+5b9b5e45c + +# zig 0.14.0-dev.2647+5322459a0 diff --git a/build.zig b/build.zig index acc7311..435045f 100644 --- a/build.zig +++ b/build.zig @@ -4,19 +4,32 @@ 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", diff --git a/build.zig.zon b/build.zig.zon index 9101812..ad91da8 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,7 +5,18 @@ .minimum_zig_version = "0.14.0", .dependencies = .{ - // See `zig fetch --save ` for a command-line interface for adding 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 = .{ diff --git a/src/main.zig b/src/main.zig index deacd15..faf3e0a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,21 +1,339 @@ const std = @import("std"); const lib = @import("zig-shape-lib"); +const builtin = @import("builtin"); + +const glfw = @cImport({ + @cDefine("GLFW_INCLUDE_NONE", {}); + @cInclude("GLFW/glfw3.h"); +}); + +const vk = @import("vk"); + +pub const features: []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 = &.{ + vk.extensions.khr_surface, +}; + +pub const device_exts: []const vk.ApiInfo = &.{ + vk.extensions.khr_swapchain, + vk.extensions.khr_dynamic_rendering, + vk.extensions.khr_timeline_semaphore, +}; + +pub const apis: []const vk.ApiInfo = + features ++ instance_exts ++ device_exts ++ + switch (builtin.mode) { + .Debug, .ReleaseSafe => Debug.instance_exts, + else => &.{}, +}; + +pub const layers: []const [*:0]const u8 = switch (builtin.mode) { + .Debug, .ReleaseSafe => Debug.layers, + else => &.{}, +}; + +pub const BaseWrapper = vk.BaseWrapper(apis); +pub const InstanceWrapper = vk.InstanceWrapper(apis); +pub const DeviceWrapper = vk.DeviceWrapper(apis); + +pub const Debug = struct { + pub const instance_exts: []const vk.ApiInfo = &.{ + vk.extensions.ext_debug_utils, + }; + + pub const layers: []const [*:0]const u8 = &.{ + "VK_LAYER_KHRONOS_validation", + }; + + pub const create_info: 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; + } +}; pub fn main() !void { - std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const alloc = gpa.allocator(); - const stdout_file = std.io.getStdOut().writer(); - var bw = std.io.bufferedWriter(stdout_file); - const stdout = bw.writer(); + if (glfw.glfwInit() != glfw.GLFW_TRUE) std.debug.panic("GLFW Init Failed", .{}); + defer glfw.glfwTerminate(); - try stdout.print("Run `zig build test` to run the tests.\n", .{}); + const B = try BaseWrapper.load(glfwGetInstanceProcAddress); - try bw.flush(); + var extnames = std.ArrayList([*:0]const u8).init(alloc); + defer extnames.deinit(); + + for (instance_exts) |ext| extnames.append(ext.name) catch std.debug.panic("OOM", .{}); + var glfw_exts_count: u32 = undefined; + const glfw_exts: [*]const [*:0]const u8 = + glfwGetRequiredInstanceExtensions(&glfw_exts_count); + extnames.appendSlice(glfw_exts[0..glfw_exts_count]) catch std.debug.panic("OOM", .{}); + + var ci: vk.InstanceCreateInfo = .{ + .p_application_info = &vk.ApplicationInfo{ + .p_application_name = "Hello World", + .application_version = vk.makeApiVersion(0, 0, 0, 0), + .p_engine_name = "Hello World", + .engine_version = vk.makeApiVersion(0, 0, 0, 0), + .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, + .p_next = switch (builtin.mode) { + .Debug, .ReleaseSafe => &Debug.create_info, + else => null, + }, + }; + const instance = try B.createInstance(&ci, null); + const I = try InstanceWrapper.load(instance, B.dispatch.vkGetInstanceProcAddr); + defer I.destroyInstance(instance, null); + + const messenger = try I.createDebugUtilsMessengerEXT(instance, &ci, null); + defer I.destroyDebugUtilsMessengerEXT(instance, messenger, null); + + 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"); + + const window = glfw.glfwCreateWindow(1280, 720, "Hello World", null, null) orelse + return error.glfwWindowCreateFailed; + defer glfw.glfwDestroyWindow(window); + + const pdevs = try I.enumeratePhysicalDevicesAlloc(instance, alloc); + defer alloc.free(pdevs); + + // just pick the first physical device + const pdev = pdevs[0]; + + const family: u32 = 0; + + // just pick the first queue family + const qcis: []const vk.DeviceQueueCreateInfo = &.{ + vk.DeviceQueueCreateInfo{ + .queue_family_index = family, + .queue_count = 1, + .p_queue_priorities = &.{1}, + }, + }; + + extnames.clearRetainingCapacity(); + for (device_exts) |ext| extnames.append(ext.name) catch std.debug.panic("OOM", .{}); + + const device = try I.createDevice(pdev, &.{ + .queue_create_info_count = @intCast(qcis.len), + .p_queue_create_infos = qcis.ptr, + .enabled_extension_count = @intCast(extnames.items.len), + .pp_enabled_extension_names = extnames.items.ptr, + .p_next = &vk.PhysicalDeviceDynamicRenderingFeatures{ + .dynamic_rendering = vk.TRUE, + }, + }, null); + const D = try DeviceWrapper.load(device, I.dispatch.vkGetDeviceProcAddr); + defer D.destroyDevice(device, null); + + const queue = D.getDeviceQueue(device, family, 0); + _ = queue; + + var surface: vk.SurfaceKHR = undefined; + if (glfwCreateWindowSurface(instance, window, null, &surface) != vk.Result.success) { + return error.SurfaceCreateFailed; + } + + 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(); + 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()); } @@ -25,11 +343,34 @@ test "use other module" { } test "fuzz example" { - const global = struct { - fn testOne(input: []const u8) anyerror!void { + 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(global.testOne, .{}); + try std.testing.fuzz(Context{}, Context.testOne, .{}); } + +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: *glfw.GLFWwindow, + allocation_callbacks: ?*const vk.AllocationCallbacks, + surface: *vk.SurfaceKHR, +) vk.Result; + +pub extern fn glfwGetRequiredInstanceExtensions( + count: *u32, +) [*][*:0]const u8; diff --git a/src/root.zig b/src/root.zig index 0c5c75e..27d2be8 100644 --- a/src/root.zig +++ b/src/root.zig @@ -1,7 +1,6 @@ //! 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; From a55775e21d1787b51891906664024a41a8076a8e Mon Sep 17 00:00:00 2001 From: David Allemang Date: Wed, 19 Feb 2025 20:50:40 -0500 Subject: [PATCH 3/4] WIP: Move base, instance, device, queue setup into core.zig --- src/core.zig | 296 +++++++++++++++++++++++++++++++++ src/main.zig | 452 ++++++++++++++------------------------------------- 2 files changed, 418 insertions(+), 330 deletions(-) create mode 100644 src/core.zig diff --git a/src/core.zig b/src/core.zig new file mode 100644 index 0000000..adf99d1 --- /dev/null +++ b/src/core.zig @@ -0,0 +1,296 @@ +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 Self = @This(); + +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); + +alloc: std.mem.Allocator, +win: *glfw.GLFWwindow, +bw: *const Base, +i: Instance, +d: Device, +q: Queue, +dm: 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", + }, +) !Self { + 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"); + const win = glfw.glfwCreateWindow( + @intCast(options.width), + @intCast(options.height), + titlez, + null, + null, + ) orelse + return error.GLFWWindowFailed; + + var bw = try alloc.create(vk.BaseWrapper(apis)); + errdefer alloc.destroy(bw); + bw.* = try vk.BaseWrapper(apis).load(glfwGetInstanceProcAddress); + + 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 => {}, + } + + const instance = try bw.createInstance( + &ici, + null, + ); + var iw = try alloc.create(vk.InstanceWrapper(apis)); + errdefer alloc.destroy(iw); + iw.* = try vk.InstanceWrapper(apis).load(instance, bw.dispatch.vkGetInstanceProcAddr); + errdefer iw.destroyInstance(instance, null); + + const dm = switch (builtin.mode) { + .Debug, .ReleaseSafe => try iw.createDebugUtilsMessengerEXT(instance, &dmci, null), + else => {}, + }; + errdefer switch (builtin.mode) { + .Debug, .ReleaseSafe => iw.destroyDebugUtilsMessengerEXT(instance, dm, null), + else => {}, + }; + + // Device selection. Just use the first one. + const pdevs: []vk.PhysicalDevice = try iw.enumeratePhysicalDevicesAlloc(instance, ialloc); + std.debug.assert(pdevs.len >= 1); + const pdev = pdevs[0]; + + // 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, + }); + + const device = try iw.createDevice(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); + var dw = try alloc.create(vk.DeviceWrapper(apis)); + errdefer alloc.destroy(dw); + dw.* = try vk.DeviceWrapper(apis).load(device, iw.dispatch.vkGetDeviceProcAddr); + errdefer dw.destroyDevice(device, null); + + const queue = dw.getDeviceQueue(device, 0, 0); + + return Self{ + .alloc = alloc, + .win = win, + .bw = bw, + .i = .{ .handle = instance, .wrapper = iw }, + .d = .{ .handle = device, .wrapper = dw }, + .q = .{ .handle = queue, .wrapper = dw }, + .dm = dm, + }; +} + +pub fn deinit(self: Self) void { + self.d.destroyDevice(null); + self.alloc.destroy(self.d.wrapper); + + switch (builtin.mode) { + .Debug, .ReleaseSafe => self.i.destroyDebugUtilsMessengerEXT(self.dm, null), + else => {}, + } + self.i.destroyInstance(null); + + self.alloc.destroy(self.i.wrapper); + self.alloc.destroy(self.bw); + glfw.glfwTerminate(); +} + +extern fn glfwGetInstanceProcAddress( + instance: vk.Instance, + procname: [*:0]const u8, +) vk.PfnVoidFunction; + +extern fn glfwGetPhysicalDevicePresentationSupport( + instance: vk.Instance, + pdev: vk.PhysicalDevice, + queuefamily: u32, +) c_int; + +extern fn glfwCreateWindowSurface( + instance: vk.Instance, + window: *glfw.GLFWwindow, + allocation_callbacks: ?*const vk.AllocationCallbacks, + surface: *vk.SurfaceKHR, +) vk.Result; + +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; +} diff --git a/src/main.zig b/src/main.zig index faf3e0a..8263ec6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,333 +2,147 @@ const std = @import("std"); const lib = @import("zig-shape-lib"); const builtin = @import("builtin"); -const glfw = @cImport({ - @cDefine("GLFW_INCLUDE_NONE", {}); - @cInclude("GLFW/glfw3.h"); -}); - -const vk = @import("vk"); - -pub const features: []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 = &.{ - vk.extensions.khr_surface, -}; - -pub const device_exts: []const vk.ApiInfo = &.{ - vk.extensions.khr_swapchain, - vk.extensions.khr_dynamic_rendering, - vk.extensions.khr_timeline_semaphore, -}; - -pub const apis: []const vk.ApiInfo = - features ++ instance_exts ++ device_exts ++ - switch (builtin.mode) { - .Debug, .ReleaseSafe => Debug.instance_exts, - else => &.{}, -}; - -pub const layers: []const [*:0]const u8 = switch (builtin.mode) { - .Debug, .ReleaseSafe => Debug.layers, - else => &.{}, -}; - -pub const BaseWrapper = vk.BaseWrapper(apis); -pub const InstanceWrapper = vk.InstanceWrapper(apis); -pub const DeviceWrapper = vk.DeviceWrapper(apis); - -pub const Debug = struct { - pub const instance_exts: []const vk.ApiInfo = &.{ - vk.extensions.ext_debug_utils, - }; - - pub const layers: []const [*:0]const u8 = &.{ - "VK_LAYER_KHRONOS_validation", - }; - - pub const create_info: 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; - } -}; +const Core = @import("core.zig"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const alloc = gpa.allocator(); - if (glfw.glfwInit() != glfw.GLFW_TRUE) std.debug.panic("GLFW Init Failed", .{}); - defer glfw.glfwTerminate(); + const core = try Core.init(alloc, .{}); + defer core.deinit(); - const B = try BaseWrapper.load(glfwGetInstanceProcAddress); + 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}); - var extnames = std.ArrayList([*:0]const u8).init(alloc); - defer extnames.deinit(); + // var surface: vk.SurfaceKHR = undefined; + // if (glfwCreateWindowSurface(instance, window, null, &surface) != vk.Result.success) { + // return error.SurfaceCreateFailed; + // } - for (instance_exts) |ext| extnames.append(ext.name) catch std.debug.panic("OOM", .{}); - var glfw_exts_count: u32 = undefined; - const glfw_exts: [*]const [*:0]const u8 = - glfwGetRequiredInstanceExtensions(&glfw_exts_count); - extnames.appendSlice(glfw_exts[0..glfw_exts_count]) catch std.debug.panic("OOM", .{}); + // 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 ci: vk.InstanceCreateInfo = .{ - .p_application_info = &vk.ApplicationInfo{ - .p_application_name = "Hello World", - .application_version = vk.makeApiVersion(0, 0, 0, 0), - .p_engine_name = "Hello World", - .engine_version = vk.makeApiVersion(0, 0, 0, 0), - .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, - .p_next = switch (builtin.mode) { - .Debug, .ReleaseSafe => &Debug.create_info, - else => null, - }, - }; - const instance = try B.createInstance(&ci, null); - const I = try InstanceWrapper.load(instance, B.dispatch.vkGetInstanceProcAddr); - defer I.destroyInstance(instance, null); + // 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, + // }; - const messenger = try I.createDebugUtilsMessengerEXT(instance, &ci, null); - defer I.destroyDebugUtilsMessengerEXT(instance, messenger, null); - - 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"); - - const window = glfw.glfwCreateWindow(1280, 720, "Hello World", null, null) orelse - return error.glfwWindowCreateFailed; - defer glfw.glfwDestroyWindow(window); - - const pdevs = try I.enumeratePhysicalDevicesAlloc(instance, alloc); - defer alloc.free(pdevs); - - // just pick the first physical device - const pdev = pdevs[0]; - - const family: u32 = 0; - - // just pick the first queue family - const qcis: []const vk.DeviceQueueCreateInfo = &.{ - vk.DeviceQueueCreateInfo{ - .queue_family_index = family, - .queue_count = 1, - .p_queue_priorities = &.{1}, - }, - }; - - extnames.clearRetainingCapacity(); - for (device_exts) |ext| extnames.append(ext.name) catch std.debug.panic("OOM", .{}); - - const device = try I.createDevice(pdev, &.{ - .queue_create_info_count = @intCast(qcis.len), - .p_queue_create_infos = qcis.ptr, - .enabled_extension_count = @intCast(extnames.items.len), - .pp_enabled_extension_names = extnames.items.ptr, - .p_next = &vk.PhysicalDeviceDynamicRenderingFeatures{ - .dynamic_rendering = vk.TRUE, - }, - }, null); - const D = try DeviceWrapper.load(device, I.dispatch.vkGetDeviceProcAddr); - defer D.destroyDevice(device, null); - - const queue = D.getDeviceQueue(device, family, 0); - _ = queue; - - var surface: vk.SurfaceKHR = undefined; - if (glfwCreateWindowSurface(instance, window, null, &surface) != vk.Result.success) { - return error.SurfaceCreateFailed; - } - - 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); + // 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); + // 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)); + // 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); - } + // 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" { @@ -352,25 +166,3 @@ test "fuzz example" { }; try std.testing.fuzz(Context{}, Context.testOne, .{}); } - -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: *glfw.GLFWwindow, - allocation_callbacks: ?*const vk.AllocationCallbacks, - surface: *vk.SurfaceKHR, -) vk.Result; - -pub extern fn glfwGetRequiredInstanceExtensions( - count: *u32, -) [*][*:0]const u8; From 8c82294c9190463a2fe19de485ff706ae21ef758 Mon Sep 17 00:00:00 2001 From: David Allemang Date: Wed, 26 Feb 2025 17:24:06 -0500 Subject: [PATCH 4/4] restructure --- src/{core.zig => Core.zig} | 116 ++++++++++++++++++++----------------- src/main.zig | 8 +-- src/swapchain.zig | 92 +++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 58 deletions(-) rename src/{core.zig => Core.zig} (81%) create mode 100644 src/swapchain.zig diff --git a/src/core.zig b/src/Core.zig similarity index 81% rename from src/core.zig rename to src/Core.zig index adf99d1..cea9876 100644 --- a/src/core.zig +++ b/src/Core.zig @@ -8,8 +8,6 @@ const glfw = @cImport({ @cInclude("GLFW/glfw3.h"); }); -const Self = @This(); - const apis: []const vk.ApiInfo = &(.{ vk.features.version_1_0, vk.features.version_1_1, @@ -31,13 +29,17 @@ const Instance = vk.InstanceProxy(apis); const Device = vk.DeviceProxy(apis); const Queue = vk.QueueProxy(apis); +const Core = @This(); + alloc: std.mem.Allocator, -win: *glfw.GLFWwindow, -bw: *const Base, +b: *Base, i: Instance, d: Device, q: Queue, -dm: switch (builtin.mode) { +window: *glfw.GLFWwindow, +pdev: vk.PhysicalDevice, +surface: vk.SurfaceKHR, +msg: switch (builtin.mode) { .Debug, .ReleaseSafe => vk.DebugUtilsMessengerEXT, else => void, }, @@ -49,7 +51,10 @@ pub fn init( height: u32 = 720, title: []const u8 = "Hello World", }, -) !Self { +) !Core { + var self: Core = undefined; + self.alloc = alloc; + var arena = std.heap.ArenaAllocator.init(alloc); defer arena.deinit(); const ialloc = arena.allocator(); @@ -64,7 +69,7 @@ pub fn init( 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"); - const win = glfw.glfwCreateWindow( + self.window = glfw.glfwCreateWindow( @intCast(options.width), @intCast(options.height), titlez, @@ -73,9 +78,10 @@ pub fn init( ) orelse return error.GLFWWindowFailed; - var bw = try alloc.create(vk.BaseWrapper(apis)); + 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); @@ -139,28 +145,37 @@ pub fn init( else => {}, } - const instance = try bw.createInstance( - &ici, - null, - ); - var iw = try alloc.create(vk.InstanceWrapper(apis)); + 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(instance, bw.dispatch.vkGetInstanceProcAddr); - errdefer iw.destroyInstance(instance, null); + iw.* = try vk.InstanceWrapper(apis).load(self.i.handle, bw.dispatch.vkGetInstanceProcAddr); + self.i.wrapper = iw; + errdefer self.i.destroyInstance(null); - const dm = switch (builtin.mode) { - .Debug, .ReleaseSafe => try iw.createDebugUtilsMessengerEXT(instance, &dmci, null), + self.msg = switch (builtin.mode) { + .Debug, .ReleaseSafe => try self.i.createDebugUtilsMessengerEXT(&dmci, null), else => {}, }; errdefer switch (builtin.mode) { - .Debug, .ReleaseSafe => iw.destroyDebugUtilsMessengerEXT(instance, dm, null), + .Debug, .ReleaseSafe => self.i.destroyDebugUtilsMessengerEXT(self.msg, null), else => {}, }; // Device selection. Just use the first one. - const pdevs: []vk.PhysicalDevice = try iw.enumeratePhysicalDevicesAlloc(instance, ialloc); + const pdevs: []vk.PhysicalDevice = try self.i.enumeratePhysicalDevicesAlloc(ialloc); std.debug.assert(pdevs.len >= 1); - const pdev = pdevs[0]; + 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 = &.{ @@ -182,7 +197,7 @@ pub fn init( vk.extensions.khr_timeline_semaphore.name, }); - const device = try iw.createDevice(pdev, &.{ + 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), @@ -193,36 +208,33 @@ pub fn init( .dynamic_rendering = vk.TRUE, }, }, null); - var dw = try alloc.create(vk.DeviceWrapper(apis)); - errdefer alloc.destroy(dw); - dw.* = try vk.DeviceWrapper(apis).load(device, iw.dispatch.vkGetDeviceProcAddr); - errdefer dw.destroyDevice(device, 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); - const queue = dw.getDeviceQueue(device, 0, 0); + self.q.handle = self.d.getDeviceQueue(0, 0); + self.q.wrapper = dw; - return Self{ - .alloc = alloc, - .win = win, - .bw = bw, - .i = .{ .handle = instance, .wrapper = iw }, - .d = .{ .handle = device, .wrapper = dw }, - .q = .{ .handle = queue, .wrapper = dw }, - .dm = dm, - }; + return self; } -pub fn deinit(self: Self) void { +pub fn deinit(self: Core) void { self.d.destroyDevice(null); - self.alloc.destroy(self.d.wrapper); + + self.i.destroySurfaceKHR(self.surface, null); switch (builtin.mode) { - .Debug, .ReleaseSafe => self.i.destroyDebugUtilsMessengerEXT(self.dm, null), + .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.bw); + self.alloc.destroy(self.b); + glfw.glfwTerminate(); } @@ -231,19 +243,6 @@ extern fn glfwGetInstanceProcAddress( procname: [*:0]const u8, ) vk.PfnVoidFunction; -extern fn glfwGetPhysicalDevicePresentationSupport( - instance: vk.Instance, - pdev: vk.PhysicalDevice, - queuefamily: u32, -) c_int; - -extern fn glfwCreateWindowSurface( - instance: vk.Instance, - window: *glfw.GLFWwindow, - allocation_callbacks: ?*const vk.AllocationCallbacks, - surface: *vk.SurfaceKHR, -) vk.Result; - extern fn glfwGetRequiredInstanceExtensions( count: *u32, ) [*][*:0]const u8; @@ -294,3 +293,16 @@ fn debug_callback( 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/src/main.zig b/src/main.zig index 8263ec6..1f85a61 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,7 +2,7 @@ const std = @import("std"); const lib = @import("zig-shape-lib"); const builtin = @import("builtin"); -const Core = @import("core.zig"); +const Core = @import("Core.zig"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; @@ -15,11 +15,7 @@ pub fn main() !void { 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}); - - // var surface: vk.SurfaceKHR = undefined; - // if (glfwCreateWindowSurface(instance, window, null, &surface) != vk.Result.success) { - // return error.SurfaceCreateFailed; - // } + std.log.debug("Created surface: {any}", .{core.surface}); // var caps = try I.getPhysicalDeviceSurfaceCapabilitiesKHR(pdev, surface); // const format = search: { diff --git a/src/swapchain.zig b/src/swapchain.zig new file mode 100644 index 0000000..cf25ada --- /dev/null +++ b/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); + } + }; +}