WIP: Vulkan Setup

This commit is contained in:
2025-02-15 12:31:04 -05:00
parent 2b3734a087
commit f325f65f4f
5 changed files with 380 additions and 14 deletions

View File

@@ -1 +1,3 @@
zig 0.14.0-dev.2647+5322459a0
zig 0.14.0-dev.3217+5b9b5e45c
# zig 0.14.0-dev.2647+5322459a0

View File

@@ -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",

View File

@@ -5,7 +5,18 @@
.minimum_zig_version = "0.14.0",
.dependencies = .{
// See `zig fetch --save <url>` 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 = .{

View File

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

View File

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