From 1ebed09413bd41ecdfdfb8c968a89efb461e73a5 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 2 Jul 2020 04:01:59 +0200 Subject: [PATCH] Graphics context for example --- build.zig | 4 +- examples/c.zig | 13 ++ examples/graphics_context.zig | 281 ++++++++++++++++++++++++++++++++++ examples/main.zig | 50 +++++- generator/render.zig | 48 +++++- 5 files changed, 385 insertions(+), 11 deletions(-) create mode 100644 examples/c.zig create mode 100644 examples/graphics_context.zig diff --git a/build.zig b/build.zig index 6d1764d..c0ad177 100644 --- a/build.zig +++ b/build.zig @@ -27,9 +27,11 @@ pub fn build(b: *Builder) void { exe.setTarget(target); exe.setBuildMode(mode); exe.install(); + exe.linkSystemLibrary("c"); + exe.linkSystemLibrary("glfw"); const vk_path = generateVk(b); const fmt_step = b.addFmt(&[_][]const u8{vk_path}); exe.step.dependOn(&fmt_step.step); - exe.addPackagePath("vk", vk_path); + exe.addPackagePath("vulkan", vk_path); } diff --git a/examples/c.zig b/examples/c.zig new file mode 100644 index 0000000..1b472af --- /dev/null +++ b/examples/c.zig @@ -0,0 +1,13 @@ +pub usingnamespace @cImport({ + @cDefine("GLFW_INCLUDE_NONE", {}); + @cInclude("GLFW/glfw3.h"); +}); + +const vk = @import("vulkan"); + +// usually the GLFW vulkan functions are exported if Vulkan is included, +// but since thats not the case here, they are manually imported. + +pub extern "c" fn glfwGetInstanceProcAddress(instance: vk.Instance, procname: [*:0]const u8) vk.PfnVoidFunction; +pub extern "c" fn glfwGetPhysicalDevicePresentationSupport(instance: vk.Instance, pdev: vk.PhysicalDevice, queuefamily: u32) c_int; +pub extern "c" fn glfwCreateWindowSurface(instance: vk.Instance, window: *GLFWwindow, allocation_callbacks: ?*const vk.AllocationCallbacks, surface: *vk.SurfaceKHR) vk.Result; diff --git a/examples/graphics_context.zig b/examples/graphics_context.zig new file mode 100644 index 0000000..df802a7 --- /dev/null +++ b/examples/graphics_context.zig @@ -0,0 +1,281 @@ +const std = @import("std"); +const vk = @import("vulkan"); +const c = @import("c.zig"); +const Allocator = std.mem.Allocator; + +const required_device_extensions = [_][]const u8{ + vk.extension_info.khr_swapchain.name +}; + +const BaseDispatch = struct { + vkCreateInstance: vk.PfnCreateInstance, + usingnamespace vk.BaseWrapper(@This()); +}; + +const InstanceDispatch = struct { + vkDestroyInstance: vk.PfnDestroyInstance, + vkCreateDevice: vk.PfnCreateDevice, + vkDestroySurfaceKHR: vk.PfnDestroySurfaceKHR, + vkEnumeratePhysicalDevices: vk.PfnEnumeratePhysicalDevices, + vkGetPhysicalDeviceProperties: vk.PfnGetPhysicalDeviceProperties, + vkEnumerateDeviceExtensionProperties: vk.PfnEnumerateDeviceExtensionProperties, + vkGetPhysicalDeviceSurfaceFormatsKHR: vk.PfnGetPhysicalDeviceSurfaceFormatsKHR, + vkGetPhysicalDeviceSurfacePresentModesKHR: vk.PfnGetPhysicalDeviceSurfacePresentModesKHR, + vkGetPhysicalDeviceQueueFamilyProperties: vk.PfnGetPhysicalDeviceQueueFamilyProperties, + vkGetPhysicalDeviceSurfaceSupportKHR: vk.PfnGetPhysicalDeviceSurfaceSupportKHR, + vkGetDeviceProcAddr: vk.PfnGetDeviceProcAddr, + usingnamespace vk.InstanceWrapper(@This()); +}; + +const DeviceDispatch = struct { + vkDestroyDevice: vk.PfnDestroyDevice, + vkGetDeviceQueue: vk.PfnGetDeviceQueue, + usingnamespace vk.DeviceWrapper(@This()); +}; + +pub const GraphicsContext = struct { + vkb: BaseDispatch, + vki: InstanceDispatch, + vkd: DeviceDispatch, + + instance: vk.Instance, + surface: vk.SurfaceKHR, + pdev: vk.PhysicalDevice, + props: vk.PhysicalDeviceProperties, + dev: vk.Device, + graphics_queue: Queue, + present_queue: Queue, + + pub fn init(allocator: *Allocator, app_info: *const vk.ApplicationInfo, window: *c.GLFWwindow) !GraphicsContext { + var self: GraphicsContext = undefined; + self.vkb = try BaseDispatch.load(c.glfwGetInstanceProcAddress); + + var glfw_exts_count: u32 = 0; + const glfw_exts = c.glfwGetRequiredInstanceExtensions(&glfw_exts_count); + + self.instance = try self.vkb.createInstance(.{ + .flags = .{}, + .p_application_info = app_info, + .enabled_layer_count = 0, + .pp_enabled_layer_names = undefined, + .enabled_extension_count = glfw_exts_count, + .pp_enabled_extension_names = @ptrCast([*]const [*:0]const u8, glfw_exts), + }, null); + + self.vki = try InstanceDispatch.load(self.instance, c.glfwGetInstanceProcAddress); + errdefer self.vki.destroyInstance(self.instance, null); + + self.surface = try createSurface(self.vki, self.instance, window); + errdefer self.vki.destroySurfaceKHR(self.instance, self.surface, null); + + const candidate = try pickPhysicalDevice(self.vki, self.instance, allocator, self.surface); + self.pdev = candidate.pdev; + self.props = candidate.props; + self.dev = try initializeCandidate(self.vki, candidate); + self.vkd = try DeviceDispatch.load(self.dev, self.vki.vkGetDeviceProcAddr); + errdefer self.vkd.destroyDevice(self.dev, null); + + self.graphics_queue = Queue.init(self.vkd, self.dev, candidate.queues.graphics_family); + self.present_queue = Queue.init(self.vkd, self.dev, candidate.queues.graphics_family); + + return self; + } + + pub fn deinit(self: GraphicsContext) void { + self.vkd.destroyDevice(self.dev, null); + self.vki.destroySurfaceKHR(self.instance, self.surface, null); + self.vki.destroyInstance(self.instance, null); + } +}; + +pub const Queue = struct { + handle: vk.Queue, + family: u32, + + fn init(vkd: DeviceDispatch, dev: vk.Device, family: u32) Queue { + return .{ + .handle = vkd.getDeviceQueue(dev, family, 0), + .family = family, + }; + } +}; + +fn createSurface(vki: InstanceDispatch, instance: vk.Instance, window: *c.GLFWwindow) !vk.SurfaceKHR { + var surface: vk.SurfaceKHR = undefined; + if (c.glfwCreateWindowSurface(instance, window, null, &surface) != .success) { + return error.SurfaceInitFailed; + } + + return surface; +} + +fn initializeCandidate(vki: InstanceDispatch, candidate: DeviceCandidate) !vk.Device { + const priority = [_]f32{1}; + const qci = [_]vk.DeviceQueueCreateInfo{ + .{ + .flags = .{}, + .queue_family_index = candidate.queues.graphics_family, + .queue_count = 1, + .p_queue_priorities = &priority, + }, + .{ + .flags = .{}, + .queue_family_index = candidate.queues.present_family, + .queue_count = 1, + .p_queue_priorities = &priority, + } + }; + + const queue_count: u32 = if (candidate.queues.graphics_family == candidate.queues.present_family) + 1 + else + 2; + + return try vki.createDevice(candidate.pdev, .{ + .flags = .{}, + .queue_create_info_count = queue_count, + .p_queue_create_infos = &qci, + .enabled_layer_count = 0, + .pp_enabled_layer_names = undefined, + .enabled_extension_count = required_device_extensions.len, + .pp_enabled_extension_names = @ptrCast([*]const [*:0]const u8, &required_device_extensions), + .p_enabled_features = null, + }, null); +} + +const DeviceCandidate = struct { + pdev: vk.PhysicalDevice, + props: vk.PhysicalDeviceProperties, + queues: QueueAllocation, +}; + +const QueueAllocation = struct { + graphics_family: u32, + present_family: u32, +}; + +fn pickPhysicalDevice( + vki: InstanceDispatch, + instance: vk.Instance, + allocator: *Allocator, + surface: vk.SurfaceKHR, +) !DeviceCandidate { + var device_count: u32 = undefined; + _ = try vki.enumeratePhysicalDevices(instance, &device_count, null); + + const pdevs = try allocator.alloc(vk.PhysicalDevice, device_count); + defer allocator.free(pdevs); + + _ = try vki.enumeratePhysicalDevices(instance, &device_count, pdevs.ptr); + + for (pdevs) |pdev| { + if (try checkSuitable(vki, pdev, allocator, surface)) |candidate| { + return candidate; + } + } + + return error.NoSuitableDevice; +} + +fn checkSuitable( + vki: InstanceDispatch, + pdev: vk.PhysicalDevice, + allocator: *Allocator, + surface: vk.SurfaceKHR, +) !?DeviceCandidate { + const props = vki.getPhysicalDeviceProperties(pdev); + + if (!try checkExtensionSupport(vki, pdev, allocator)) { + return null; + } + + if (!try checkSurfaceSupport(vki, pdev, surface)) { + return null; + } + + if (try allocateQueues(vki, pdev, allocator, surface)) |allocation| { + return DeviceCandidate{ + .pdev = pdev, + .props = props, + .queues = allocation + }; + } + + return null; +} + +fn allocateQueues( + vki: InstanceDispatch, + pdev: vk.PhysicalDevice, + allocator: *Allocator, + surface: vk.SurfaceKHR +) !?QueueAllocation { + var family_count: u32 = undefined; + vki.getPhysicalDeviceQueueFamilyProperties(pdev, &family_count, null); + + const families = try allocator.alloc(vk.QueueFamilyProperties, family_count); + defer allocator.free(families); + vki.getPhysicalDeviceQueueFamilyProperties(pdev, &family_count, families.ptr); + + var graphics_family: ?u32 = null; + var present_family: ?u32 = null; + + for (families) |properties, i| { + const family = @intCast(u32, i); + + if (graphics_family == null and properties.queue_flags.contains(.{.graphics_bit = true})) { + graphics_family = family; + } + + if (present_family == null and (try vki.getPhysicalDeviceSurfaceSupportKHR(pdev, family, surface)) == vk.TRUE) { + present_family = family; + } + } + + if (graphics_family != null and present_family != null) { + return QueueAllocation{ + .graphics_family = graphics_family.?, + .present_family = present_family.? + }; + } + + return null; +} + +fn checkSurfaceSupport(vki: InstanceDispatch, pdev: vk.PhysicalDevice, surface: vk.SurfaceKHR) !bool { + var format_count: u32 = undefined; + _ = try vki.getPhysicalDeviceSurfaceFormatsKHR(pdev, surface, &format_count, null); + + var present_mode_count: u32 = undefined; + _ = try vki.getPhysicalDeviceSurfacePresentModesKHR(pdev, surface, &present_mode_count, null); + + return format_count > 0 and present_mode_count > 0; +} + +fn checkExtensionSupport( + vki: InstanceDispatch, + pdev: vk.PhysicalDevice, + allocator: *Allocator, +) !bool { + var count: u32 = undefined; + _ = try vki.enumerateDeviceExtensionProperties(pdev, null, &count, null); + + const propsv = try allocator.alloc(vk.ExtensionProperties, count); + defer allocator.free(propsv); + + _ = try vki.enumerateDeviceExtensionProperties(pdev, null, &count, propsv.ptr); + + for (required_device_extensions) |ext| { + for (propsv) |props| { + const len = std.mem.indexOfScalar(u8, &props.extension_name, 0).?; + const prop_ext_name = props.extension_name[0 .. len]; + if (std.mem.eql(u8, ext, prop_ext_name)) { + break; + } + } else { + return false; + } + } + + return true; +} diff --git a/examples/main.zig b/examples/main.zig index c6a70af..3ed2fe0 100644 --- a/examples/main.zig +++ b/examples/main.zig @@ -1,5 +1,51 @@ const std = @import("std"); +const vk = @import("vulkan"); +const c = @import("c.zig"); +const GraphicsContext = @import("graphics_context.zig").GraphicsContext; -pub fn main() anyerror!void { - std.debug.warn("All your codebase are belong to us.\n", .{}); +const BaseDispatch = struct { + vkCreateInstance: vk.PfnCreateInstance, + usingnamespace vk.BaseWrapper(@This()); +}; + +const InstanceDispatch = struct { + vkDestroyInstance: vk.PfnDestroyInstance, + vkCreateDevice: vk.PfnCreateDevice, + vkDestroySurfaceKHR: vk.PfnDestroySurfaceKHR, + usingnamespace vk.InstanceWrapper(@This()); +}; + +const DeviceDispatch = struct { + vkDestroyDevice: vk.PfnDestroyDevice, + usingnamespace vk.DeviceWrapper(@This()); +}; + +const app_name = "vulkan-zig example"; + +const app_info = vk.ApplicationInfo{ + .p_application_name = app_name, + .application_version = vk.makeVersion(0, 0, 0), + .p_engine_name = app_name, + .engine_version = vk.makeVersion(0, 0, 0), + .api_version = vk.API_VERSION_1_2, +}; + +pub fn main() !void { + if (c.glfwInit() != c.GLFW_TRUE) return error.GlfwInitFailed; + defer c.glfwTerminate(); + + const dim = vk.Extent2D{.width = 800, .height = 600}; + + c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API); + const window = c.glfwCreateWindow( + dim.width, + dim.height, + app_name, + null, + null + ) orelse return error.WindowInitFailed; + defer c.glfwDestroyWindow(window); + + const gc = try GraphicsContext.init(std.heap.page_allocator, &app_info, window); + defer gc.deinit(); } diff --git a/generator/render.zig b/generator/render.zig index e7a7eae..8b038a3 100644 --- a/generator/render.zig +++ b/generator/render.zig @@ -40,8 +40,8 @@ const preamble = \\ pub fn subtract(lhs: FlagsType, rhs: FlagsType) FlagsType { \\ return fromInt(toInt(lhs) & toInt(rhs.complement())); \\ } - \\ pub fn contains(lhs: FlagsType, rhs: FlagsType) FlagsType { - \\ return toInt(merge(lhs, rhs)) == toInt(rhs); + \\ pub fn contains(lhs: FlagsType, rhs: FlagsType) bool { + \\ return toInt(intersect(lhs, rhs)) == toInt(rhs); \\ } \\ }; \\} @@ -409,9 +409,9 @@ fn Renderer(comptime WriterType: type) type { try self.writeIdentifier(name[2..]); return; } else if (mem.startsWith(u8, name, "PFN_vk")) { - // Function pointer type, strip off the PFN_vk part. Note that this function - // is only called to render the typedeffed function pointers like vkVoidFunction - try self.writeIdentifier(name[6..]); + // Function pointer type, strip off the PFN_vk part and replace it with Pfn. Note that + // this function is only called to render the typedeffed function pointers like vkVoidFunction + try self.writeIdentifierFmt("Pfn{}", .{name[6..]}); return; } else if (mem.startsWith(u8, name, "VK_")) { // Constants @@ -706,7 +706,7 @@ fn Renderer(comptime WriterType: type) type { } fn renderCommandPtrName(self: *Self, name: []const u8) !void { - try self.writeIdentifierFmt("{}Fn", .{util.trimVkNamespace(name)}); + try self.writeIdentifierFmt("Pfn{}", .{util.trimVkNamespace(name)}); } fn renderCommandPtrs(self: *Self) !void { @@ -727,7 +727,7 @@ fn Renderer(comptime WriterType: type) type { try self.writer.writeAll( \\pub const extension_info = struct { \\ const Info = struct { - \\ name: []const u8, + \\ name: [:0]const u8, \\ version: u32, \\ }; ); @@ -755,6 +755,8 @@ fn Renderer(comptime WriterType: type) type { , .{name} ); + try self.renderWrapperLoader(dispatch_type); + for (self.registry.decls) |decl| { if (decl.decl_type == .command) { const command = decl.decl_type.command; @@ -767,6 +769,36 @@ fn Renderer(comptime WriterType: type) type { try self.writer.writeAll("};}\n"); } + fn renderWrapperLoader(self: *Self, dispatch_type: CommandDispatchType) !void { + const params = switch (dispatch_type) { + .base => "loader: PfnGetInstanceProcAddr", + .instance => "instance: Instance, loader: PfnGetInstanceProcAddr", + .device => "device: Device, loader: PfnGetDeviceProcAddr", + }; + + const loader_first_param = switch (dispatch_type) { + .base => ".null_handle, ", + .instance => "instance, ", + .device => "device, ", + }; + + @setEvalBranchQuota(2000); + + try self.writer.print( + \\pub fn load({}) !Self {{ + \\ var self: Self = undefined; + \\ inline for (std.meta.fields(Self)) |field| {{ + \\ const name = @ptrCast([*:0]const u8, field.name ++ "\x00"); + \\ const cmd_ptr = loader({}name) orelse return error.InvalidCommand; + \\ @field(self, field.name) = @ptrCast(field.field_type, cmd_ptr); + \\ }} + \\ return self; + \\}} + \\ + , .{params, loader_first_param} + ); + } + fn derefName(name: []const u8) []const u8 { var it = util.SegmentIterator.init(name); return if (mem.eql(u8, it.next().?, "p")) @@ -1012,7 +1044,7 @@ fn Renderer(comptime WriterType: type) type { try self.renderResultAsErrorName(name); try self.writer.writeAll(", "); } - try self.writer.writeAll("Unkown, }"); + try self.writer.writeAll("Unknown, }"); } fn renderResultAsErrorName(self: *Self, name: []const u8) !void {