1477 lines
59 KiB
Zig
1477 lines
59 KiB
Zig
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);
|
|
}
|