const std = @import("std"); const vk = @import("vulkan"); const glfw = @import("glfw"); const Allocator = std.mem.Allocator; const required_device_extensions = [_][*:0]const u8{vk.extension_info.khr_swapchain.name}; const BaseDispatch = vk.BaseWrapper(.{ .createInstance = true, }); const InstanceDispatch = vk.InstanceWrapper(.{ .destroyInstance = true, .createDevice = true, .destroySurfaceKHR = true, .enumeratePhysicalDevices = true, .getPhysicalDeviceProperties = true, .enumerateDeviceExtensionProperties = true, .getPhysicalDeviceSurfaceFormatsKHR = true, .getPhysicalDeviceSurfacePresentModesKHR = true, .getPhysicalDeviceSurfaceCapabilitiesKHR = true, .getPhysicalDeviceQueueFamilyProperties = true, .getPhysicalDeviceSurfaceSupportKHR = true, .getPhysicalDeviceMemoryProperties = true, .getDeviceProcAddr = true, }); const DeviceDispatch = vk.DeviceWrapper(.{ .destroyDevice = true, .getDeviceQueue = true, .createSemaphore = true, .createFence = true, .createImageView = true, .destroyImageView = true, .destroySemaphore = true, .destroyFence = true, .getSwapchainImagesKHR = true, .createSwapchainKHR = true, .destroySwapchainKHR = true, .acquireNextImageKHR = true, .deviceWaitIdle = true, .waitForFences = true, .resetFences = true, .queueSubmit = true, .queuePresentKHR = true, .createCommandPool = true, .destroyCommandPool = true, .allocateCommandBuffers = true, .freeCommandBuffers = true, .queueWaitIdle = true, .createShaderModule = true, .destroyShaderModule = true, .createPipelineLayout = true, .destroyPipelineLayout = true, .createRenderPass = true, .destroyRenderPass = true, .createGraphicsPipelines = true, .destroyPipeline = true, .createFramebuffer = true, .destroyFramebuffer = true, .beginCommandBuffer = true, .endCommandBuffer = true, .allocateMemory = true, .freeMemory = true, .createBuffer = true, .destroyBuffer = true, .getBufferMemoryRequirements = true, .mapMemory = true, .unmapMemory = true, .bindBufferMemory = true, .cmdBeginRenderPass = true, .cmdEndRenderPass = true, .cmdBindPipeline = true, .cmdDraw = true, .cmdSetViewport = true, .cmdSetScissor = true, .cmdBindVertexBuffers = true, .cmdCopyBuffer = true, }); pub const GraphicsContext = struct { vkb: BaseDispatch, vki: InstanceDispatch, vkd: DeviceDispatch, instance: vk.Instance, surface: vk.SurfaceKHR, pdev: vk.PhysicalDevice, props: vk.PhysicalDeviceProperties, mem_props: vk.PhysicalDeviceMemoryProperties, dev: vk.Device, graphics_queue: Queue, present_queue: Queue, pub fn init(allocator: Allocator, app_name: [*:0]const u8, window: *glfw.Window) !GraphicsContext { var self: GraphicsContext = undefined; const vk_proc = @ptrCast(fn (instance: vk.Instance, procname: [*:0]const u8) callconv(.C) vk.PfnVoidFunction, glfw.getInstanceProcAddress); self.vkb = try BaseDispatch.load(vk_proc); const glfw_exts = try glfw.getRequiredInstanceExtensions(); const app_info = vk.ApplicationInfo{ .p_application_name = app_name, .application_version = vk.makeApiVersion(0, 0, 0, 0), .p_engine_name = app_name, .engine_version = vk.makeApiVersion(0, 0, 0, 0), .api_version = vk.API_VERSION_1_2, }; self.instance = try self.vkb.createInstance(&.{ .flags = .{}, .p_application_info = &app_info, .enabled_layer_count = 0, .pp_enabled_layer_names = undefined, .enabled_extension_count = @intCast(u32, glfw_exts.len), .pp_enabled_extension_names = glfw_exts.ptr, }, null); self.vki = try InstanceDispatch.load(self.instance, vk_proc); errdefer self.vki.destroyInstance(self.instance, null); self.surface = try createSurface(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.dispatch.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.present_family); self.mem_props = self.vki.getPhysicalDeviceMemoryProperties(self.pdev); 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 fn deviceName(self: GraphicsContext) []const u8 { const len = std.mem.indexOfScalar(u8, &self.props.device_name, 0).?; return self.props.device_name[0..len]; } pub fn findMemoryTypeIndex(self: GraphicsContext, memory_type_bits: u32, flags: vk.MemoryPropertyFlags) !u32 { // wow wtf is this stuff for (self.mem_props.memory_types[0..self.mem_props.memory_type_count]) |mem_type, i| { if (memory_type_bits & (@as(u32, 1) << @truncate(u5, i)) != 0 and mem_type.property_flags.contains(flags)) { return @truncate(u32, i); } } return error.NoSuitableMemoryType; } pub fn allocate(self: GraphicsContext, requirements: vk.MemoryRequirements, flags: vk.MemoryPropertyFlags) !vk.DeviceMemory { return try self.vkd.allocateMemory(self.dev, &.{ .allocation_size = requirements.size, .memory_type_index = try self.findMemoryTypeIndex(requirements.memory_type_bits, flags), }, 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(instance: vk.Instance, window: *glfw.Window) !vk.SurfaceKHR { var surface: vk.SurfaceKHR = undefined; if (glfw.createWindowSurface(instance, window.*, null, &surface)) { return surface; } else |_| return error.SurfaceInitFailed; } 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.graphics_bit) { 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, std.mem.span(ext), prop_ext_name)) { break; } } else { return false; } } return true; }