const Engine = @This(); const std = @import("std"); const c = @import("../const.zig"); const glfw = @import("zglfw"); const vk = @import("vulkan"); const Queue = @import("Queue.zig"); const QueueType = @import("QueueType.zig").QueueType; const Transient = @import("Transient.zig").Transient; const VkAllocator = @import("VkAllocator.zig"); const WaitSemaphore = @import("WaitSemaphore.zig"); pub const Mode = union(enum) { headless: void, surface: struct { window: *glfw.Window, surface: vk.SurfaceKHR, presentation_queue: Queue, }, }; pub const ModeTag = std.meta.Tag(Mode); const QueueAllocator = struct { queue_families_properties_buffer: [max_queue_families]vk.QueueFamilyProperties, queue_families_in_use: [max_queue_families]u32, queue_families_count: u32, const max_queue_families = 16; pub fn init(instance: vk.InstanceProxy, physical_device: vk.PhysicalDevice) QueueAllocator { var queue_families_properties_buffer: [max_queue_families]vk.QueueFamilyProperties = undefined; var queue_families_count: u32 = max_queue_families; instance.getPhysicalDeviceQueueFamilyProperties(physical_device, &queue_families_count, &queue_families_properties_buffer); var queue_families_in_use: [max_queue_families]u32 = undefined; @memset(queue_families_in_use[0..queue_families_count], 0); return .{ .queue_families_properties_buffer = queue_families_properties_buffer, .queue_families_in_use = queue_families_in_use, .queue_families_count = queue_families_count, }; } pub fn allocateQueueIndex(self: *QueueAllocator, queue_family: u32) u32 { std.debug.assert(queue_family < self.queue_families_count); if (self.queue_families_in_use[queue_family] < self.queue_families_properties_buffer[queue_family].queue_count) { const queue_index = self.queue_families_in_use[queue_family]; self.queue_families_in_use[queue_family] += 1; return queue_index; } else { return 0; } } pub fn queueFamiliesProperties(self: *const QueueAllocator) []const vk.QueueFamilyProperties { return self.queue_families_properties_buffer[0..self.queue_families_count]; } pub fn queueInUse(self: *const QueueAllocator) []const u32 { return self.queue_families_in_use[0..self.queue_families_count]; } }; const QueueAllocations = struct { graphics_queue: Queue.Allocation, compute_queue: Queue.Allocation, transfer_queue: Queue.Allocation, /// Defined only in `surface` mode, `undefined` otherwise. presentation_queue: Queue.Allocation, }; const DebugUtilsMessenger = if (debug) vk.DebugUtilsMessengerEXT else void; mode: Mode, vk_allocator: *VkAllocator, base: vk.BaseWrapper, instance: vk.InstanceProxy, device: vk.DeviceProxy, debug_utils_messenger: DebugUtilsMessenger, physical_device: vk.PhysicalDevice, memory_types: std.ArrayList(vk.MemoryType), memory_heaps: std.ArrayList(vk.MemoryHeap), graphics_queue: Queue, compute_queue: Queue, transfer_queue: Queue, graphics_command_pool: vk.CommandPool, compute_command_pool: vk.CommandPool, transfer_command_pool: vk.CommandPool, transient_graphics_command_pool: vk.CommandPool, transient_compute_command_pool: vk.CommandPool, transient_transfer_command_pool: vk.CommandPool, prng_ptr: *std.Random.Pcg, random: std.Random, const debug = @import("builtin").mode == .Debug; const vk_application_info: vk.ApplicationInfo = .{ .p_application_name = c.app_name, .application_version = @bitCast(vk_version), .p_engine_name = c.app_name, .engine_version = @bitCast(vk_version), .api_version = @bitCast(vk.API_VERSION_1_2), }; const vk_debug_utils_messenger_create_info: vk.DebugUtilsMessengerCreateInfoEXT = .{ .message_severity = .{ .verbose_bit_ext = false, .info_bit_ext = false, .warning_bit_ext = true, .error_bit_ext = true, }, .message_type = .{ .general_bit_ext = true, .validation_bit_ext = true, .performance_bit_ext = true, }, .pfn_user_callback = &debugUtilsMessengerCallback, .p_user_data = null, }; const vk_version: vk.Version = vk.makeApiVersion( 0, c.app_version.major, c.app_version.minor, c.app_version.patch, ); pub fn init(allocator: std.mem.Allocator, maybe_window: ?*glfw.Window) !Engine { const vk_allocator = try VkAllocator.init(allocator); errdefer vk_allocator.deinit(); // --- LOAD BASE DISPATCH -------------------------------------------------- const base = vk.BaseWrapper.load(glfw.getInstanceProcAddress); // --- CREATE INSTANCE, ITS WRAPPER AND PROXY ------------------------------ const instance_handle = blk: { var enabled_extensions_buffer: [16][*:0]const u8 = undefined; var enabled_extensions: std.ArrayList([*:0]const u8) = .initBuffer(&enabled_extensions_buffer); if (debug) { try enabled_extensions.appendBounded(vk.extensions.ext_debug_utils.name); } if (maybe_window != null) { const glfw_instance_extensions = try glfw.getRequiredInstanceExtensions(); try enabled_extensions.appendSliceBounded(glfw_instance_extensions); } const enabled_layers: []const [*:0]const u8 = if (debug) &.{"VK_LAYER_KHRONOS_validation"} else &.{}; break :blk try base.createInstance(&.{ .p_application_info = &vk_application_info, .enabled_layer_count = 0, //enabled_layers.len, .pp_enabled_layer_names = enabled_layers.ptr, .enabled_extension_count = @intCast(enabled_extensions.items.len), .pp_enabled_extension_names = enabled_extensions.items.ptr, .p_next = if (debug) &vk_debug_utils_messenger_create_info else null, }, &vk_allocator.interface); }; const instance_wrapper_ptr = try allocator.create(vk.InstanceWrapper); errdefer allocator.destroy(instance_wrapper_ptr); instance_wrapper_ptr.* = .load(instance_handle, base.dispatch.vkGetInstanceProcAddr.?); const instance = vk.InstanceProxy.init(instance_handle, instance_wrapper_ptr); errdefer instance.destroyInstance(&vk_allocator.interface); // --- CREATE DEBUG UTILS MESSENGER ---------------------------------------- const debug_utils_messenger: DebugUtilsMessenger = if (debug) try instance.createDebugUtilsMessengerEXT(&vk_debug_utils_messenger_create_info, &vk_allocator.interface) else {}; errdefer { if (debug) instance.destroyDebugUtilsMessengerEXT(debug_utils_messenger, &vk_allocator.interface); } // --- CHOOSE PHYSICAL DEVICE, GET ITS MEMORY PROPERTIES ------------------- const physical_device = blk: { var physical_devices_buffer: [16]vk.PhysicalDevice = undefined; var physical_devices_count: u32 = physical_devices_buffer.len; _ = try instance.enumeratePhysicalDevices(&physical_devices_count, &physical_devices_buffer); // Look for the first GPU with its `device_type` equal to // `.discrete_gpu`. for (physical_devices_buffer[0..physical_devices_count]) |physical_device_candidate| { const physical_device_properties = instance.getPhysicalDeviceProperties(physical_device_candidate); if (physical_device_properties.device_type == .discrete_gpu) { break :blk physical_device_candidate; } } // Look for the first GPU. if (physical_devices_count > 0) { break :blk physical_devices_buffer[0]; } return error.NoDevice; }; const physical_device_memory_properties = instance.getPhysicalDeviceMemoryProperties(physical_device); var memory_types = try std.ArrayList(vk.MemoryType).initCapacity(allocator, vk.MAX_MEMORY_TYPES); errdefer memory_types.deinit(allocator); memory_types.appendSliceAssumeCapacity(physical_device_memory_properties.memory_types[0..physical_device_memory_properties.memory_type_count]); var memory_heaps = try std.ArrayList(vk.MemoryHeap).initCapacity(allocator, vk.MAX_MEMORY_HEAPS); errdefer memory_heaps.deinit(allocator); memory_heaps.appendSliceAssumeCapacity(physical_device_memory_properties.memory_heaps[0..physical_device_memory_properties.memory_heap_count]); // --- CREATE SURFACE ------------------------------------------------------ var surface: vk.SurfaceKHR = undefined; if (maybe_window) |window| try glfw.createWindowSurface(instance_handle, window, &vk_allocator.interface, &surface); errdefer { if (maybe_window != null) instance.destroySurfaceKHR(surface, &vk_allocator.interface); } // --- ALLOCATE QUEUES ----------------------------------------------------- const max_queues = 4; var queue_create_info_buffer: [max_queues]vk.DeviceQueueCreateInfo = undefined; var queue_create_info: std.ArrayList(vk.DeviceQueueCreateInfo) = .initBuffer(&queue_create_info_buffer); const queue_allocations: QueueAllocations = blk: { var queue_allocator: QueueAllocator = .init(instance, physical_device); const queue_families_properties = queue_allocator.queueFamiliesProperties(); const graphics_queue_family = findGraphicsQueueFamily(queue_families_properties) orelse return error.NoGraphicsQueue; const compute_queue_family = findComputeQueueFamily(queue_families_properties) orelse return error.NoComputeQueue; const transfer_queue_family = findTransferQueueFamily(queue_families_properties) orelse return error.NoTransferQueue; const presentation_queue_family = if (maybe_window != null) (try findPresentationQueueFamily(queue_families_properties, instance, physical_device, surface) orelse return error.NoPresentationQueue) else undefined; const graphics_queue_index = queue_allocator.allocateQueueIndex(graphics_queue_family); const compute_queue_index = queue_allocator.allocateQueueIndex(compute_queue_family); const transfer_queue_index = queue_allocator.allocateQueueIndex(transfer_queue_family); const presentation_queue_index = if (maybe_window != null) queue_allocator.allocateQueueIndex(presentation_queue_family) else undefined; const queue_priorities = [_]f32{1.0} ** max_queues; for (queue_allocator.queueInUse(), 0..) |count, queue_family| { if (count == 0) continue; queue_create_info.appendBounded(.{ .queue_family_index = @intCast(queue_family), .queue_count = count, .p_queue_priorities = &queue_priorities, }) catch unreachable; } break :blk .{ .graphics_queue = .{ .family = graphics_queue_family, .index = graphics_queue_index }, .compute_queue = .{ .family = compute_queue_family, .index = compute_queue_index }, .transfer_queue = .{ .family = transfer_queue_family, .index = transfer_queue_index }, .presentation_queue = if (maybe_window != null) .{ .family = presentation_queue_family, .index = presentation_queue_index } else undefined, }; }; // --- CREATE (LOGICAL) DEVICE, ITS WRAPPER AND PROXY ---------------------- const device_handle = blk: { var enabled_extensions_buffer: [16][*:0]const u8 = undefined; var enabled_extensions: std.ArrayList([*:0]const u8) = .initBuffer(&enabled_extensions_buffer); if (maybe_window != null) { try enabled_extensions.appendBounded(vk.extensions.khr_swapchain.name); } const enabled_features_vulkan10: vk.PhysicalDeviceFeatures = .{ .shader_int_16 = .true, }; var enabled_features_vulkan11: vk.PhysicalDeviceVulkan11Features = .{ .storage_buffer_16_bit_access = .true, .uniform_and_storage_buffer_16_bit_access = .true, }; var enabled_features_vulkan12: vk.PhysicalDeviceVulkan12Features = .{ .p_next = &enabled_features_vulkan11, .descriptor_binding_partially_bound = .true, .descriptor_binding_variable_descriptor_count = .true, .runtime_descriptor_array = .true, .scalar_block_layout = .true, }; break :blk try instance.createDevice(physical_device, &.{ .p_next = &enabled_features_vulkan12, .queue_create_info_count = @intCast(queue_create_info.items.len), .p_queue_create_infos = queue_create_info.items.ptr, .enabled_extension_count = @intCast(enabled_extensions.items.len), .pp_enabled_extension_names = enabled_extensions.items.ptr, .p_enabled_features = &enabled_features_vulkan10, }, &vk_allocator.interface); }; const device_wrapper_ptr = try allocator.create(vk.DeviceWrapper); errdefer allocator.destroy(device_wrapper_ptr); device_wrapper_ptr.* = .load(device_handle, instance.wrapper.dispatch.vkGetDeviceProcAddr.?); const device = vk.DeviceProxy.init(device_handle, device_wrapper_ptr); errdefer device.destroyDevice(&vk_allocator.interface); // --- CREATE COMMAND POOLS, GET QUEUES ------------------------------------ const graphics_command_pool = try device.createCommandPool(&.{ .queue_family_index = queue_allocations.graphics_queue.family, }, &vk_allocator.interface); errdefer device.destroyCommandPool(graphics_command_pool, &vk_allocator.interface); const compute_command_pool = try device.createCommandPool(&.{ .queue_family_index = queue_allocations.compute_queue.family, }, &vk_allocator.interface); errdefer device.destroyCommandPool(compute_command_pool, &vk_allocator.interface); const transfer_command_pool = try device.createCommandPool(&.{ .queue_family_index = queue_allocations.transfer_queue.family, }, &vk_allocator.interface); errdefer device.destroyCommandPool(transfer_command_pool, &vk_allocator.interface); const transient_graphics_command_pool = try device.createCommandPool(&.{ .flags = .{ .transient_bit = true }, .queue_family_index = queue_allocations.graphics_queue.family, }, &vk_allocator.interface); errdefer device.destroyCommandPool(transient_graphics_command_pool, &vk_allocator.interface); const transient_compute_command_pool = try device.createCommandPool(&.{ .flags = .{ .transient_bit = true }, .queue_family_index = queue_allocations.compute_queue.family, }, &vk_allocator.interface); errdefer device.destroyCommandPool(transient_compute_command_pool, &vk_allocator.interface); const transient_transfer_command_pool = try device.createCommandPool(&.{ .flags = .{ .transient_bit = true }, .queue_family_index = queue_allocations.transfer_queue.family, }, &vk_allocator.interface); errdefer device.destroyCommandPool(transient_transfer_command_pool, &vk_allocator.interface); const graphics_queue: Queue = .init( device.getDeviceQueue( queue_allocations.graphics_queue.family, queue_allocations.graphics_queue.index, ), queue_allocations.graphics_queue, ); const compute_queue: Queue = .init( device.getDeviceQueue( queue_allocations.compute_queue.family, queue_allocations.compute_queue.index, ), queue_allocations.compute_queue, ); const transfer_queue: Queue = .init( device.getDeviceQueue( queue_allocations.transfer_queue.family, queue_allocations.transfer_queue.index, ), queue_allocations.transfer_queue, ); const presentation_queue: Queue = if (maybe_window != null) .init( device.getDeviceQueue( queue_allocations.presentation_queue.family, queue_allocations.presentation_queue.index, ), queue_allocations.presentation_queue, ) else undefined; // --- CREATE AND SEED RNG ------------------------------------------------- const prng_ptr = try allocator.create(std.Random.Pcg); errdefer allocator.destroy(prng_ptr); const timestamp: u128 = @bitCast(std.time.nanoTimestamp()); prng_ptr.* = .init(@truncate(timestamp)); const random = prng_ptr.random(); // ------------------------------------------------------------------------- return .{ .mode = if (maybe_window) |window| .{ .surface = .{ .window = window, .surface = surface, .presentation_queue = presentation_queue, } } else .{ .headless = {} }, .vk_allocator = vk_allocator, .base = base, .instance = instance, .device = device, .debug_utils_messenger = debug_utils_messenger, .physical_device = physical_device, .memory_types = memory_types, .memory_heaps = memory_heaps, .graphics_queue = graphics_queue, .compute_queue = compute_queue, .transfer_queue = transfer_queue, .graphics_command_pool = graphics_command_pool, .compute_command_pool = compute_command_pool, .transfer_command_pool = transfer_command_pool, .transient_graphics_command_pool = transient_graphics_command_pool, .transient_compute_command_pool = transient_compute_command_pool, .transient_transfer_command_pool = transient_transfer_command_pool, .prng_ptr = prng_ptr, .random = random, }; } pub fn deinit(self: *Engine) void { std.log.scoped(.deinit).debug("Deinitializing {*}", .{self}); const allocator = self.vk_allocator.allocator; allocator.destroy(self.prng_ptr); self.device.destroyCommandPool(self.graphics_command_pool, &self.vk_allocator.interface); self.device.destroyCommandPool(self.compute_command_pool, &self.vk_allocator.interface); self.device.destroyCommandPool(self.transfer_command_pool, &self.vk_allocator.interface); self.device.destroyCommandPool(self.transient_graphics_command_pool, &self.vk_allocator.interface); self.device.destroyCommandPool(self.transient_compute_command_pool, &self.vk_allocator.interface); self.device.destroyCommandPool(self.transient_transfer_command_pool, &self.vk_allocator.interface); self.device.destroyDevice(&self.vk_allocator.interface); allocator.destroy(self.device.wrapper); if (std.meta.activeTag(self.mode) == .surface) { self.instance.destroySurfaceKHR(self.mode.surface.surface, &self.vk_allocator.interface); } self.memory_heaps.deinit(allocator); self.memory_types.deinit(allocator); if (debug) self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils_messenger, &self.vk_allocator.interface); self.instance.destroyInstance(&self.vk_allocator.interface); allocator.destroy(self.instance.wrapper); self.vk_allocator.deinit(); self.* = undefined; } pub fn allocate(self: *const Engine, memory_requirements: vk.MemoryRequirements, memory_property_flags: vk.MemoryPropertyFlags) !vk.DeviceMemory { for (self.memory_types.items, 0..) |memory_type, i| { const is_type = memory_requirements.memory_type_bits & (@as(u32, 1) << @truncate(i)) != 0; const has_flags = memory_type.property_flags.contains(memory_property_flags); if (is_type and has_flags) { return try self.device.allocateMemory(&.{ .allocation_size = memory_requirements.size, .memory_type_index = @truncate(i), }, &self.vk_allocator.interface); } } return error.NoSuitableMemoryType; } pub fn setObjectName(self: *const Engine, handle: anytype, comptime fmt: []const u8, args: anytype) void { if (debug) { const HandleType = @TypeOf(handle); const type_name = @typeName(HandleType); if (@as(std.builtin.TypeId, @typeInfo(HandleType)) != .@"enum") { @compileError("Cannot set object name for a handle of type " ++ type_name ++ ", which is not an enum."); } const object_type: vk.ObjectType = switch (HandleType) { vk.Instance => .instance, vk.PhysicalDevice => .physical_device, vk.Device => .device, vk.Queue => .queue, vk.Semaphore => .semaphore, vk.CommandBuffer => .command_buffer, vk.Fence => .fence, vk.DeviceMemory => .device_memory, vk.Buffer => .buffer, vk.Image => .image, vk.Event => .event, vk.QueryPool => .query_pool, vk.BufferView => .buffer_view, vk.ImageView => .image_view, vk.ShaderModule => .shader_module, vk.PipelineCache => .pipeline_cache, vk.PipelineLayout => .pipeline_layout, vk.RenderPass => .render_pass, vk.Pipeline => .pipeline, vk.DescriptorSetLayout => .descriptor_set_layout, vk.Sampler => .sampler, vk.DescriptorPool => .descriptor_pool, vk.DescriptorSet => .descriptor_set, vk.Framebuffer => .framebuffer, vk.CommandPool => .command_pool, vk.SurfaceKHR => .surface_khr, vk.SwapchainKHR => .swapchain_khr, else => @compileError("Unsupported type " ++ type_name ++ " for setting and object name"), }; const handle_value: u64 = @intFromEnum(handle); var buf: [256]u8 = undefined; const name = std.fmt.bufPrintZ(&buf, fmt, args) catch |err| { std.log.debug("Failed to set object name for {s}#{X}: {s}", .{ type_name, handle_value, @errorName(err) }); return; }; self.device.setDebugUtilsObjectNameEXT(&.{ .object_type = object_type, .object_handle = handle_value, .p_object_name = name.ptr, }) catch |err| { std.log.debug("Failed to set object name for {s}#{X}: {s}", .{ type_name, handle_value, @errorName(err) }); return; }; } } pub const SubmitInfo = struct { wait_semaphores: []const WaitSemaphore = &.{}, signal_semaphores: []const vk.Semaphore = &.{}, fence: vk.Fence = .null_handle, }; pub fn queueSubmit( self: *const Engine, queue_type: QueueType, command_buffer: vk.CommandBuffer, submit_info: SubmitInfo, ) !void { const queue = switch (queue_type) { .graphics => self.graphics_queue.handle, .compute => self.compute_queue.handle, .transfer => self.transfer_queue.handle, }; const command_buffers = [_]vk.CommandBuffer{command_buffer}; var wait_semaphores = std.MultiArrayList(WaitSemaphore){}; defer wait_semaphores.deinit(self.vk_allocator.allocator); try wait_semaphores.ensureTotalCapacity(self.vk_allocator.allocator, submit_info.wait_semaphores.len); for (submit_info.wait_semaphores) |wait_semaphore| { wait_semaphores.appendAssumeCapacity(wait_semaphore); } const submits = [_]vk.SubmitInfo{ .{ .wait_semaphore_count = @intCast(wait_semaphores.len), .p_wait_semaphores = wait_semaphores.items(.semaphore).ptr, .p_wait_dst_stage_mask = wait_semaphores.items(.stage_flags).ptr, .command_buffer_count = command_buffers.len, .p_command_buffers = &command_buffers, .signal_semaphore_count = @intCast(submit_info.signal_semaphores.len), .p_signal_semaphores = submit_info.signal_semaphores.ptr, }, }; try self.device.queueSubmit(queue, submits.len, &submits, submit_info.fence); } pub const PresentInfo = struct { wait_semaphores: []const vk.Semaphore = &.{}, image_index: u32, swapchain: vk.SwapchainKHR, }; pub fn queuePresent(self: *Engine, present_info: PresentInfo) !vk.Result { const swapchains = [_]vk.SwapchainKHR{present_info.swapchain}; const image_indices = [_]u32{present_info.image_index}; const res = try self.device.queuePresentKHR(self.mode.surface.presentation_queue.handle, &.{ .wait_semaphore_count = @intCast(present_info.wait_semaphores.len), .p_wait_semaphores = present_info.wait_semaphores.ptr, .swapchain_count = swapchains.len, .p_swapchains = &swapchains, .p_image_indices = &image_indices, }); return res; } fn debugUtilsMessengerCallback( severity: vk.DebugUtilsMessageSeverityFlagsEXT, message_type: vk.DebugUtilsMessageTypeFlagsEXT, maybe_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, _: ?*anyopaque, ) callconv(vk.vulkan_call_conv) vk.Bool32 { const log = std.log.scoped(.vulkan); const message = if (maybe_callback_data) |callback_data| callback_data.p_message orelse return .false else return .false; const type_name = if (message_type.general_bit_ext) "general" else if (message_type.validation_bit_ext) "validation" else if (message_type.performance_bit_ext) "performance" else if (message_type.device_address_binding_bit_ext) "device_address_binding" else "unknown"; const format = "[{s}] {s}"; const args = .{ type_name, message }; if (severity.error_bit_ext) { log.err(format, args); } else if (severity.warning_bit_ext) { log.warn(format, args); } else if (severity.info_bit_ext) { log.info(format, args); } else if (severity.verbose_bit_ext) { log.debug(format, args); } return .false; } fn findGraphicsQueueFamily(queue_families_properties: []const vk.QueueFamilyProperties) ?u32 { // Look for the first family that has the `graphics_bit` set. for (queue_families_properties, 0..) |queue_family_properties, i| { const graphics_bit = queue_family_properties.queue_flags.graphics_bit; if (graphics_bit) { return @intCast(i); } } return null; } fn findComputeQueueFamily(queue_families_properties: []const vk.QueueFamilyProperties) ?u32 { // Look for the first family that has the `compute_bit` set, but not // `graphics_bit`. for (queue_families_properties, 0..) |queue_family_properties, i| { const compute_bit = queue_family_properties.queue_flags.compute_bit; const graphics_bit = queue_family_properties.queue_flags.graphics_bit; if (compute_bit and !graphics_bit) { return @intCast(i); } } // Look for the first family that has the `compute_bit` set. for (queue_families_properties, 0..) |queue_family_properties, i| { const compute_bit = queue_family_properties.queue_flags.compute_bit; if (compute_bit) { return @intCast(i); } } return null; } fn findTransferQueueFamily(queue_families_properties: []const vk.QueueFamilyProperties) ?u32 { // Look for the first family that has the `transfer_bit` set, but neither // `graphics_bit` or `compute_bit`. for (queue_families_properties, 0..) |queue_family_properties, i| { const transfer_bit = queue_family_properties.queue_flags.transfer_bit; const graphics_bit = queue_family_properties.queue_flags.graphics_bit; const compute_bit = queue_family_properties.queue_flags.compute_bit; if (transfer_bit and !graphics_bit and !compute_bit) { return @intCast(i); } } // Look for the first family that has either `transfer_bit`, `graphics_bit` // or `compute_bit` set. // NOTE `graphics_bit` or `compute_bit` imply `transfer_bit`, even if not // set explicitly. for (queue_families_properties, 0..) |queue_family_properties, i| { const transfer_bit = queue_family_properties.queue_flags.transfer_bit; const graphics_bit = queue_family_properties.queue_flags.graphics_bit; const compute_bit = queue_family_properties.queue_flags.compute_bit; if (transfer_bit or graphics_bit or compute_bit) { return @intCast(i); } } return null; } fn findPresentationQueueFamily(queue_families_properties: []const vk.QueueFamilyProperties, instance: vk.InstanceProxy, physical_device: vk.PhysicalDevice, surface: vk.SurfaceKHR) !?u32 { // Look for the first family that supports presentation to a given surface. for (queue_families_properties, 0..) |_, i| { if (try instance.getPhysicalDeviceSurfaceSupportKHR(physical_device, @intCast(i), surface) == .true) { return @intCast(i); } } return null; } fn resolveCommandPool(self: *const Engine, queue_type: QueueType, transient: Transient) vk.CommandPool { return switch (queue_type) { .graphics => switch (transient) { .persistent => self.graphics_command_pool, .transient => self.transient_graphics_command_pool, }, .compute => switch (transient) { .persistent => self.compute_command_pool, .transient => self.transient_compute_command_pool, }, .transfer => switch (transient) { .persistent => self.transfer_command_pool, .transient => self.transient_transfer_command_pool, }, }; } // --- VULKAN WRAPPERS --------------------------------------------------------- pub const AcquireInfo = struct { timeout: u64 = std.math.maxInt(u64), semaphore: vk.Semaphore = .null_handle, fence: vk.Fence = .null_handle, }; pub const BufferCreateInfo = struct { flags: vk.BufferCreateFlags = .{}, size: vk.DeviceSize, usage: vk.BufferUsageFlags, queue_family_indices: []const u32 = &.{}, }; pub const CommandBufferAllocateInfo = struct { queue_type: QueueType, transient: Transient, level: vk.CommandBufferLevel, }; pub const CommandBufferFreeInfo = struct { queue_type: QueueType, transient: Transient, command_buffer: vk.CommandBuffer, }; pub const DescriptorPoolCreateInfo = struct { flags: vk.DescriptorPoolCreateFlags = .{}, max_sets: u32, pool_sizes: []const vk.DescriptorPoolSize = &.{}, }; pub const DescriptorSetAllocateInfo = struct { descriptor_pool: vk.DescriptorPool, set_layout: vk.DescriptorSetLayout, variable_descriptor_count: ?u32 = null, }; pub const DescriptorSetsAllocateInfo = struct { descriptor_pool: vk.DescriptorPool, set_layouts: []const vk.DescriptorSetLayout, variable_descriptor_counts: []const u32 = &.{}, }; pub const DescriptorSetLayoutBinding = struct { binding: u32, descriptor_type: vk.DescriptorType, descriptor_count: u32, stage_flags: vk.ShaderStageFlags, immutable_samplers: []const vk.Sampler = &.{}, flags: vk.DescriptorBindingFlags = .{}, }; pub const DescriptorSetLayoutCreateInfo = struct { flags: vk.DescriptorSetLayoutCreateFlags = .{}, bindings: []const DescriptorSetLayoutBinding = &.{}, }; pub const DescriptorSetsUpdateInfo = struct { writes: []const WriteDescriptorSet = &.{}, copies: []const vk.CopyDescriptorSet = &.{}, }; pub const FramebufferCreateInfo = struct { flags: vk.FramebufferCreateFlags = .{}, render_pass: vk.RenderPass, attachments: []const vk.ImageView = &.{}, width: u32, height: u32, layers: u32, }; pub const GraphicsPipelineCreateInfo = struct { flags: vk.PipelineCreateFlags = .{}, stages: []const PipelineShaderStageCreateInfo = &.{}, vertex_input_state: ?PipelineVertexInputStateCreateInfo = null, input_assembly_state: ?vk.PipelineInputAssemblyStateCreateInfo = null, tessellation_state: ?vk.PipelineTessellationStateCreateInfo = null, viewport_state: ?PipelineViewportStateCreateInfo = null, rasterization_state: ?vk.PipelineRasterizationStateCreateInfo = null, multisample_state: ?vk.PipelineMultisampleStateCreateInfo = null, depth_stencil_state: ?vk.PipelineDepthStencilStateCreateInfo = null, color_blend_state: ?PipelineColorBlendStateCreateInfo = null, dynamic_state: ?PipelineDynamicStateCreateInfo = null, layout: vk.PipelineLayout = .null_handle, render_pass: vk.RenderPass = .null_handle, subpass: u32, base_pipeline_handle: vk.Pipeline = .null_handle, }; pub const ImageCreateInfo = struct { flags: vk.ImageCreateFlags = .{}, image_type: vk.ImageType, format: vk.Format, extent: vk.Extent3D, mip_levels: u32, array_layers: u32, samples: vk.SampleCountFlags, tiling: vk.ImageTiling, usage: vk.ImageUsageFlags, queue_family_indices: []const u32 = &.{}, initial_layout: vk.ImageLayout, }; pub const ImageViewCreateInfo = struct { flags: vk.ImageViewCreateFlags = .{}, image: vk.Image, view_type: vk.ImageViewType, format: vk.Format, components: vk.ComponentMapping = .{ .r = .identity, .g = .identity, .b = .identity, .a = .identity, }, subresource_range: vk.ImageSubresourceRange, }; pub const PipelineColorBlendStateCreateInfo = struct { flags: vk.PipelineColorBlendStateCreateFlags = .{}, logic_op_enable: vk.Bool32, logic_op: vk.LogicOp, attachments: []const vk.PipelineColorBlendAttachmentState = &.{}, blend_constants: [4]f32, }; pub const PipelineDynamicStateCreateInfo = struct { flags: vk.PipelineDynamicStateCreateFlags = .{}, dynamic_states: []const vk.DynamicState = &.{}, }; pub const PipelineLayoutCreateInfo = struct { flags: vk.PipelineLayoutCreateFlags = .{}, set_layouts: []const vk.DescriptorSetLayout = &.{}, push_constant_ranges: []const vk.PushConstantRange = &.{}, }; pub const PipelineShaderStageCreateInfo = struct { flags: vk.PipelineShaderStageCreateFlags = .{}, stage: vk.ShaderStageFlags, module: vk.ShaderModule = .null_handle, name: [*:0]const u8, specialization_info: ?SpecializationInfo = null, }; pub const PipelineVertexInputStateCreateInfo = struct { flags: vk.PipelineVertexInputStateCreateFlags = .{}, vertex_binding_descriptions: []const vk.VertexInputBindingDescription = &.{}, vertex_attribute_descriptions: []const vk.VertexInputAttributeDescription = &.{}, }; pub const PipelineViewportStateCreateInfo = struct { flags: vk.PipelineViewportStateCreateFlags = .{}, viewports: []const vk.Viewport = &.{}, scissors: []const vk.Rect2D = &.{}, }; pub const RenderPassCreateInfo = struct { flags: vk.RenderPassCreateFlags = .{}, attachments: []const vk.AttachmentDescription = &.{}, subpasses: []const SubpassDescription, dependencies: []const vk.SubpassDependency = &.{}, }; pub const ShaderModuleCreateInfo = struct { flags: vk.ShaderModuleCreateFlags = .{}, code: []align(@alignOf(u32)) const u8, }; pub const SpecializationInfo = struct { map_entries: []vk.SpecializationMapEntry = &.{}, data: []const u8 = &.{}, }; pub const SubpassDescription = struct { flags: vk.SubpassDescriptionFlags = .{}, pipeline_bind_point: vk.PipelineBindPoint, input_attachments: []const vk.AttachmentReference = &.{}, color_attachments: []const vk.AttachmentReference = &.{}, resolve_attachments: ?[]const vk.AttachmentReference = null, depth_stencil_attachment: ?vk.AttachmentReference = null, preserve_attachments: []const u32 = &.{}, }; pub const SwapchainCreateInfo = struct { flags: vk.SwapchainCreateFlagsKHR = .{}, surface: vk.SurfaceKHR, min_image_count: u32, image_format: vk.Format, image_color_space: vk.ColorSpaceKHR, image_extent: vk.Extent2D, image_array_layers: u32, image_usage: vk.ImageUsageFlags, queue_family_indices: []const u32 = &.{}, pre_transform: vk.SurfaceTransformFlagsKHR, composite_alpha: vk.CompositeAlphaFlagsKHR, present_mode: vk.PresentModeKHR, clipped: vk.Bool32, old_swapchain: vk.SwapchainKHR = .null_handle, }; pub const WriteDescriptorSet = struct { dst_set: vk.DescriptorSet, dst_binding: u32, dst_array_element: u32, descriptor_type: vk.DescriptorType, descriptor_infos: union(enum) { image: []const vk.DescriptorImageInfo, buffer: []const vk.DescriptorBufferInfo, texel_buffer_view: []const vk.BufferView, }, }; pub fn acquireNextImage(self: *Engine, swapchain: vk.SwapchainKHR, acquire_info: AcquireInfo) !vk.DeviceProxy.AcquireNextImageKHRResult { const res = try self.device.acquireNextImageKHR( swapchain, acquire_info.timeout, acquire_info.semaphore, acquire_info.fence, ); return res; } pub fn allocateCommandBuffer(self: *Engine, allocate_info: CommandBufferAllocateInfo) !vk.CommandBuffer { const command_pool = self.resolveCommandPool(allocate_info.queue_type, allocate_info.transient); var command_buffers: [1]vk.CommandBuffer = undefined; try self.device.allocateCommandBuffers(&.{ .command_pool = command_pool, .level = allocate_info.level, .command_buffer_count = command_buffers.len, }, &command_buffers); return command_buffers[0]; } pub fn allocateDescriptorSet(self: *Engine, allocate_info: DescriptorSetAllocateInfo) !vk.DescriptorSet { var descriptor_sets: [1]vk.DescriptorSet = undefined; try self.allocateDescriptorSets(.{ .descriptor_pool = allocate_info.descriptor_pool, .set_layouts = &.{allocate_info.set_layout}, .variable_descriptor_counts = if (allocate_info.variable_descriptor_count) |x| &.{x} else &.{}, }, &descriptor_sets); return descriptor_sets[0]; } pub fn allocateDescriptorSets(self: *Engine, allocate_info: DescriptorSetsAllocateInfo, descriptor_sets: []vk.DescriptorSet) !void { std.debug.assert(descriptor_sets.len >= allocate_info.set_layouts.len); const has_variable_descriptor_counts = allocate_info.variable_descriptor_counts.len > 0; var p_next: ?*const anyopaque = null; if (has_variable_descriptor_counts) { p_next = &vk.DescriptorSetVariableDescriptorCountAllocateInfo{ .p_next = p_next, .descriptor_set_count = @intCast(allocate_info.variable_descriptor_counts.len), .p_descriptor_counts = allocate_info.variable_descriptor_counts.ptr, }; } try self.device.allocateDescriptorSets(&.{ .p_next = p_next, .descriptor_pool = allocate_info.descriptor_pool, .descriptor_set_count = @intCast(allocate_info.set_layouts.len), .p_set_layouts = allocate_info.set_layouts.ptr, }, descriptor_sets.ptr); } pub fn bindBufferMemory(self: *Engine, buffer: vk.Buffer, memory: vk.DeviceMemory, memory_offset: vk.DeviceSize) !void { try self.device.bindBufferMemory(buffer, memory, memory_offset); } pub fn bindImageMemory(self: *Engine, image: vk.Image, memory: vk.DeviceMemory, memory_offset: vk.DeviceSize) !void { try self.device.bindImageMemory(image, memory, memory_offset); } pub fn createBuffer(self: *Engine, create_info: BufferCreateInfo) !vk.Buffer { var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator); defer arena.deinit(); const allocator = arena.allocator(); var queue_family_indices_set: std.AutoArrayHashMapUnmanaged(u32, void) = .{}; for (create_info.queue_family_indices) |queue_family_index| { try queue_family_indices_set.put(allocator, queue_family_index, {}); } const queue_family_indices = queue_family_indices_set.keys(); const buffer = self.device.createBuffer(&.{ .flags = create_info.flags, .size = create_info.size, .usage = create_info.usage, .sharing_mode = if (queue_family_indices.len > 1) .concurrent else .exclusive, .queue_family_index_count = if (queue_family_indices.len > 1) @intCast(queue_family_indices.len) else 0, .p_queue_family_indices = if (queue_family_indices.len > 1) queue_family_indices.ptr else null, }, &self.vk_allocator.interface); return buffer; } pub fn createDescriptorSetLayout(self: *Engine, create_info: DescriptorSetLayoutCreateInfo) !vk.DescriptorSetLayout { var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator); defer arena.deinit(); const allocator = arena.allocator(); const has_binding_flags = blk: { for (create_info.bindings) |binding| { if (@as(vk.Flags, @bitCast(binding.flags)) != 0) { break :blk true; } } break :blk false; }; var p_next: ?*const anyopaque = null; if (has_binding_flags) { const descriptor_set_layout_bindings_flags = try allocator.alloc(vk.DescriptorBindingFlags, create_info.bindings.len); for (descriptor_set_layout_bindings_flags, create_info.bindings) |*x, binding| { x.* = binding.flags; } p_next = &vk.DescriptorSetLayoutBindingFlagsCreateInfo{ .p_next = p_next, .binding_count = @intCast(descriptor_set_layout_bindings_flags.len), .p_binding_flags = descriptor_set_layout_bindings_flags.ptr, }; } const bindings = try allocator.alloc(vk.DescriptorSetLayoutBinding, create_info.bindings.len); for (bindings, create_info.bindings) |*x, binding| { x.* = .{ .binding = binding.binding, .descriptor_type = binding.descriptor_type, .descriptor_count = binding.descriptor_count, .stage_flags = binding.stage_flags, .p_immutable_samplers = binding.immutable_samplers.ptr, }; } const descriptor_set_layout = try self.device.createDescriptorSetLayout(&.{ .p_next = p_next, .flags = create_info.flags, .binding_count = @intCast(bindings.len), .p_bindings = bindings.ptr, }, &self.vk_allocator.interface); return descriptor_set_layout; } pub fn createDescriptorPool(self: *Engine, create_info: DescriptorPoolCreateInfo) !vk.DescriptorPool { const descriptor_pool = try self.device.createDescriptorPool(&.{ .flags = create_info.flags, .max_sets = create_info.max_sets, .pool_size_count = @intCast(create_info.pool_sizes.len), .p_pool_sizes = create_info.pool_sizes.ptr, }, &self.vk_allocator.interface); return descriptor_pool; } pub fn createFence(self: *Engine, create_info: vk.FenceCreateInfo) !vk.Fence { const fence = try self.device.createFence(&create_info, &self.vk_allocator.interface); return fence; } pub fn createFramebuffer(self: *Engine, create_info: FramebufferCreateInfo) !vk.Framebuffer { const framebuffer = try self.device.createFramebuffer(&.{ .flags = create_info.flags, .render_pass = create_info.render_pass, .attachment_count = @intCast(create_info.attachments.len), .p_attachments = create_info.attachments.ptr, .width = create_info.width, .height = create_info.height, .layers = create_info.layers, }, &self.vk_allocator.interface); return framebuffer; } pub fn createGraphicsPipeline(self: *Engine, create_info: GraphicsPipelineCreateInfo) !vk.Pipeline { var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator); defer arena.deinit(); const allocator = arena.allocator(); const stages = try allocator.alloc(vk.PipelineShaderStageCreateInfo, create_info.stages.len); for (stages, create_info.stages) |*x, stage| { x.* = .{ .flags = stage.flags, .stage = stage.stage, .p_name = stage.name, .module = stage.module, .p_specialization_info = blk: { if (stage.specialization_info) |y| { const specialization_info = try allocator.create(vk.SpecializationInfo); specialization_info.* = .{ .map_entry_count = @intCast(y.map_entries.len), .p_map_entries = y.map_entries.ptr, .data_size = y.data.len, .p_data = y.data.ptr, }; break :blk specialization_info; } else { break :blk null; } }, }; } const graphics_pipeline_create_infos = [_]vk.GraphicsPipelineCreateInfo{ .{ .flags = create_info.flags, .stage_count = @intCast(stages.len), .p_stages = stages.ptr, .p_vertex_input_state = if (create_info.vertex_input_state) |vertex_input_state| &.{ .flags = vertex_input_state.flags, .vertex_binding_description_count = @intCast(vertex_input_state.vertex_binding_descriptions.len), .p_vertex_binding_descriptions = vertex_input_state.vertex_binding_descriptions.ptr, .vertex_attribute_description_count = @intCast(vertex_input_state.vertex_attribute_descriptions.len), .p_vertex_attribute_descriptions = vertex_input_state.vertex_attribute_descriptions.ptr, } else null, .p_input_assembly_state = if (create_info.input_assembly_state) |*x| x else null, .p_tessellation_state = if (create_info.tessellation_state) |*x| x else null, .p_viewport_state = if (create_info.viewport_state) |viewport_state| &.{ .flags = viewport_state.flags, .viewport_count = @intCast(viewport_state.viewports.len), .p_viewports = viewport_state.viewports.ptr, .scissor_count = @intCast(viewport_state.scissors.len), .p_scissors = viewport_state.scissors.ptr, } else null, .p_rasterization_state = if (create_info.rasterization_state) |*x| x else null, .p_multisample_state = if (create_info.multisample_state) |*x| x else null, .p_depth_stencil_state = if (create_info.depth_stencil_state) |*x| x else null, .p_color_blend_state = if (create_info.color_blend_state) |color_blend_state| &.{ .flags = color_blend_state.flags, .logic_op_enable = color_blend_state.logic_op_enable, .logic_op = color_blend_state.logic_op, .attachment_count = @intCast(color_blend_state.attachments.len), .p_attachments = color_blend_state.attachments.ptr, .blend_constants = color_blend_state.blend_constants, } else null, .p_dynamic_state = if (create_info.dynamic_state) |dynamic_state| &.{ .flags = dynamic_state.flags, .dynamic_state_count = @intCast(dynamic_state.dynamic_states.len), .p_dynamic_states = dynamic_state.dynamic_states.ptr, } else null, .layout = create_info.layout, .render_pass = create_info.render_pass, .subpass = create_info.subpass, .base_pipeline_handle = create_info.base_pipeline_handle, .base_pipeline_index = -1, }, }; var pipelines: [1]vk.Pipeline = undefined; _ = try self.device.createGraphicsPipelines(.null_handle, graphics_pipeline_create_infos.len, &graphics_pipeline_create_infos, &self.vk_allocator.interface, &pipelines); return pipelines[0]; } pub fn createImage(self: *Engine, create_info: ImageCreateInfo) !vk.Image { var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator); defer arena.deinit(); const allocator = arena.allocator(); var queue_family_indices_set: std.AutoArrayHashMapUnmanaged(u32, void) = .{}; for (create_info.queue_family_indices) |queue_family_index| { try queue_family_indices_set.put(allocator, queue_family_index, {}); } const queue_family_indices = queue_family_indices_set.keys(); const image = self.device.createImage(&.{ .flags = create_info.flags, .image_type = create_info.image_type, .format = create_info.format, .extent = create_info.extent, .mip_levels = create_info.mip_levels, .array_layers = create_info.array_layers, .samples = create_info.samples, .tiling = create_info.tiling, .usage = create_info.usage, .sharing_mode = if (queue_family_indices.len > 1) .concurrent else .exclusive, .queue_family_index_count = if (queue_family_indices.len > 1) @intCast(queue_family_indices.len) else 0, .p_queue_family_indices = if (queue_family_indices.len > 1) queue_family_indices.ptr else null, .initial_layout = create_info.initial_layout, }, &self.vk_allocator.interface); return image; } pub fn createImageView(self: *Engine, create_info: ImageViewCreateInfo) !vk.ImageView { const image_view = try self.device.createImageView(&.{ .flags = create_info.flags, .image = create_info.image, .view_type = create_info.view_type, .format = create_info.format, .components = create_info.components, .subresource_range = create_info.subresource_range, }, &self.vk_allocator.interface); return image_view; } pub fn createPipelineLayout(self: *Engine, create_info: PipelineLayoutCreateInfo) !vk.PipelineLayout { const pipeline_layout = try self.device.createPipelineLayout(&.{ .flags = create_info.flags, .set_layout_count = @intCast(create_info.set_layouts.len), .p_set_layouts = create_info.set_layouts.ptr, .push_constant_range_count = @intCast(create_info.push_constant_ranges.len), .p_push_constant_ranges = create_info.push_constant_ranges.ptr, }, &self.vk_allocator.interface); return pipeline_layout; } pub fn createRenderPass(self: *Engine, create_info: RenderPassCreateInfo) !vk.RenderPass { var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator); defer arena.deinit(); const allocator = arena.allocator(); const subpasses = try allocator.alloc(vk.SubpassDescription, create_info.subpasses.len); for (subpasses, create_info.subpasses) |*x, *subpass| { x.* = .{ .flags = subpass.flags, .pipeline_bind_point = subpass.pipeline_bind_point, .input_attachment_count = @intCast(subpass.input_attachments.len), .p_input_attachments = subpass.input_attachments.ptr, .color_attachment_count = @intCast(subpass.color_attachments.len), .p_color_attachments = subpass.color_attachments.ptr, .p_resolve_attachments = if (subpass.resolve_attachments) |resolve_attachments| resolve_attachments.ptr else null, .p_depth_stencil_attachment = if (subpass.depth_stencil_attachment) |*y| y else null, .preserve_attachment_count = @intCast(subpass.preserve_attachments.len), .p_preserve_attachments = subpass.preserve_attachments.ptr, }; } const render_pass = try self.device.createRenderPass(&.{ .flags = create_info.flags, .attachment_count = @intCast(create_info.attachments.len), .p_attachments = create_info.attachments.ptr, .subpass_count = @intCast(subpasses.len), .p_subpasses = subpasses.ptr, .dependency_count = @intCast(create_info.dependencies.len), .p_dependencies = create_info.dependencies.ptr, }, &self.vk_allocator.interface); return render_pass; } pub fn createSampler(self: *Engine, create_info: vk.SamplerCreateInfo) !vk.Sampler { const sampler = try self.device.createSampler(&create_info, &self.vk_allocator.interface); return sampler; } pub fn createSemaphore(self: *Engine) !vk.Semaphore { const semaphore = try self.device.createSemaphore(&.{}, &self.vk_allocator.interface); return semaphore; } pub fn createShaderModule(self: *Engine, create_info: ShaderModuleCreateInfo) !vk.ShaderModule { const shader_module = try self.device.createShaderModule(&.{ .flags = create_info.flags, .code_size = create_info.code.len, .p_code = @ptrCast(create_info.code.ptr), }, &self.vk_allocator.interface); return shader_module; } pub fn createSwapchain(self: *Engine, create_info: SwapchainCreateInfo) !vk.SwapchainKHR { var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator); defer arena.deinit(); const allocator = arena.allocator(); var queue_family_indices_set: std.AutoArrayHashMapUnmanaged(u32, void) = .{}; for (create_info.queue_family_indices) |queue_family_index| { try queue_family_indices_set.put(allocator, queue_family_index, {}); } const queue_family_indices = queue_family_indices_set.keys(); const swapchain = try self.device.createSwapchainKHR(&.{ .flags = create_info.flags, .surface = create_info.surface, .min_image_count = create_info.min_image_count, .image_format = create_info.image_format, .image_color_space = create_info.image_color_space, .image_extent = create_info.image_extent, .image_array_layers = create_info.image_array_layers, .image_usage = create_info.image_usage, .image_sharing_mode = if (queue_family_indices.len > 1) .concurrent else .exclusive, .queue_family_index_count = if (queue_family_indices.len > 1) @intCast(queue_family_indices.len) else 0, .p_queue_family_indices = if (queue_family_indices.len > 1) queue_family_indices.ptr else null, .pre_transform = create_info.pre_transform, .composite_alpha = create_info.composite_alpha, .present_mode = create_info.present_mode, .clipped = create_info.clipped, .old_swapchain = create_info.old_swapchain, }, &self.vk_allocator.interface); return swapchain; } pub fn destroyBuffer(self: *Engine, buffer: vk.Buffer) void { self.device.destroyBuffer(buffer, &self.vk_allocator.interface); } pub fn destroyDescriptorPool(self: *Engine, descriptor_pool: vk.DescriptorPool) void { self.device.destroyDescriptorPool(descriptor_pool, &self.vk_allocator.interface); } pub fn destroyDescriptorSetLayout(self: *Engine, descriptor_set_layout: vk.DescriptorSetLayout) void { self.device.destroyDescriptorSetLayout(descriptor_set_layout, &self.vk_allocator.interface); } pub fn destroyFence(self: *Engine, fence: vk.Fence) void { self.device.destroyFence(fence, &self.vk_allocator.interface); } pub fn destroyFramebuffer(self: *Engine, framebuffer: vk.Framebuffer) void { self.device.destroyFramebuffer(framebuffer, &self.vk_allocator.interface); } pub fn destroyImage(self: *Engine, image: vk.Image) void { self.device.destroyImage(image, &self.vk_allocator.interface); } pub fn destroyImageView(self: *Engine, image_view: vk.ImageView) void { self.device.destroyImageView(image_view, &self.vk_allocator.interface); } pub fn destroyPipeline(self: *Engine, pipeline: vk.Pipeline) void { self.device.destroyPipeline(pipeline, &self.vk_allocator.interface); } pub fn destroyPipelineLayout(self: *Engine, pipeline_layout: vk.PipelineLayout) void { self.device.destroyPipelineLayout(pipeline_layout, &self.vk_allocator.interface); } pub fn destroyRenderPass(self: *Engine, render_pass: vk.RenderPass) void { self.device.destroyRenderPass(render_pass, &self.vk_allocator.interface); } pub fn destroySampler(self: *Engine, sampler: vk.Sampler) void { self.device.destroySampler(sampler, &self.vk_allocator.interface); } pub fn destroySemaphore(self: *Engine, semaphore: vk.Semaphore) void { self.device.destroySemaphore(semaphore, &self.vk_allocator.interface); } pub fn destroyShaderModule(self: *Engine, shader_module: vk.ShaderModule) void { self.device.destroyShaderModule(shader_module, &self.vk_allocator.interface); } pub fn destroySwapchain(self: *Engine, swapchain: vk.SwapchainKHR) void { self.device.destroySwapchainKHR(swapchain, &self.vk_allocator.interface); } pub fn deviceWaitIdle(self: *Engine) !void { try self.device.deviceWaitIdle(); } pub fn freeCommandBuffer(self: *Engine, free_info: CommandBufferFreeInfo) void { const command_pool = self.resolveCommandPool(free_info.queue_type, free_info.transient); const command_buffers = [_]vk.CommandBuffer{free_info.command_buffer}; self.device.freeCommandBuffers(command_pool, command_buffers.len, &command_buffers); } pub fn freeDescriptorSet(self: *Engine, descriptor_pool: vk.DescriptorPool, descriptor_set: vk.DescriptorSet) void { const descriptor_sets = [_]vk.DescriptorSet{descriptor_set}; self.device.freeDescriptorSets(descriptor_pool, descriptor_sets.len, &descriptor_sets) catch unreachable; } pub fn freeMemory(self: *Engine, device_memory: vk.DeviceMemory) void { self.device.freeMemory(device_memory, &self.vk_allocator.interface); } pub fn getBufferMemoryRequirements(self: *Engine, buffer: vk.Buffer) vk.MemoryRequirements { return self.device.getBufferMemoryRequirements(buffer); } pub fn getImageMemoryRequirements(self: *Engine, image: vk.Image) vk.MemoryRequirements { return self.device.getImageMemoryRequirements(image); } pub fn getPhysicalDeviceSurfaceCapabilities(self: *Engine) !vk.SurfaceCapabilitiesKHR { const surface_capabilities = try self.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.physical_device, self.mode.surface.surface); return surface_capabilities; } pub fn getPhysicalDeviceSurfaceFormatsAlloc(self: *Engine, allocator: std.mem.Allocator) ![]vk.SurfaceFormatKHR { const surface_formats = try self.instance.getPhysicalDeviceSurfaceFormatsAllocKHR(self.physical_device, self.mode.surface.surface, allocator); return surface_formats; } pub fn getSwapchainImagesAlloc(self: *Engine, swapchain: vk.SwapchainKHR, allocator: std.mem.Allocator) ![]vk.Image { const images = try self.device.getSwapchainImagesAllocKHR(swapchain, allocator); return images; } pub fn mapMemory(self: *Engine, memory: vk.DeviceMemory, offset: vk.DeviceSize, size: vk.DeviceSize, flags: vk.MemoryMapFlags) ![]u8 { const mapped_memory = try self.device.mapMemory(memory, offset, size, flags); return @as([*]u8, @ptrCast(mapped_memory))[0..size]; } pub fn resetFence(self: *Engine, fence: vk.Fence) !void { try self.device.resetFences(1, @ptrCast(&fence)); } pub fn unmapMemory(self: *Engine, memory: vk.DeviceMemory) void { self.device.unmapMemory(memory); } pub fn updateDescriptorSets(self: *Engine, update_info: DescriptorSetsUpdateInfo) !void { var arena: std.heap.ArenaAllocator = .init(self.vk_allocator.allocator); defer arena.deinit(); const allocator = arena.allocator(); const descriptor_writes = try allocator.alloc(vk.WriteDescriptorSet, update_info.writes.len); for (descriptor_writes, update_info.writes) |*x, write| { x.* = switch (write.descriptor_infos) { .image => |y| .{ .dst_set = write.dst_set, .dst_binding = write.dst_binding, .dst_array_element = write.dst_array_element, .descriptor_count = @intCast(y.len), .descriptor_type = write.descriptor_type, .p_image_info = y.ptr, .p_buffer_info = &.{}, .p_texel_buffer_view = &.{}, }, .buffer => |y| .{ .dst_set = write.dst_set, .dst_binding = write.dst_binding, .dst_array_element = write.dst_array_element, .descriptor_count = @intCast(y.len), .descriptor_type = write.descriptor_type, .p_image_info = &.{}, .p_buffer_info = y.ptr, .p_texel_buffer_view = &.{}, }, .texel_buffer_view => |y| .{ .dst_set = write.dst_set, .dst_binding = write.dst_binding, .dst_array_element = write.dst_array_element, .descriptor_count = @intCast(y.len), .descriptor_type = write.descriptor_type, .p_image_info = &.{}, .p_buffer_info = &.{}, .p_texel_buffer_view = y.ptr, }, }; } self.device.updateDescriptorSets( @intCast(descriptor_writes.len), descriptor_writes.ptr, @intCast(update_info.copies.len), update_info.copies.ptr, ); } pub fn waitForFence(self: *Engine, fence: vk.Fence) !void { _ = try self.device.waitForFences(1, @ptrCast(&fence), .true, 1 * std.time.ns_per_s); }