From f325f65f4f53911d9bba18d49a35813209d3d356 Mon Sep 17 00:00:00 2001 From: David Allemang Date: Sat, 15 Feb 2025 12:31:04 -0500 Subject: [PATCH] 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;