Port rewrite
This commit is contained in:
463
src/engine/Engine.zig
Normal file
463
src/engine/Engine.zig
Normal file
@@ -0,0 +1,463 @@
|
||||
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 VkAllocator = @import("VkAllocator.zig");
|
||||
|
||||
pub const Mode = union(enum) {
|
||||
headless: void,
|
||||
surface: struct {
|
||||
window: *glfw.Window,
|
||||
surface: vk.SurfaceKHR,
|
||||
presentation_queue: Queue,
|
||||
},
|
||||
|
||||
pub fn deinit(self: *Mode, instance: vk.InstanceProxy, vk_allocator: *VkAllocator) void {
|
||||
switch (self.*) {
|
||||
.headless => {},
|
||||
.surface => |x| {
|
||||
instance.destroySurfaceKHR(x.surface, &vk_allocator.interface);
|
||||
},
|
||||
}
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
|
||||
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 = 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);
|
||||
}
|
||||
|
||||
break :blk try instance.createDevice(physical_device, &.{
|
||||
.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,
|
||||
}, &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);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
return .{
|
||||
.mode = if (maybe_window) |window| .{ .surface = .{
|
||||
.window = window,
|
||||
.surface = surface,
|
||||
.presentation_queue = .initAllocation(device, queue_allocations.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 = .initAllocation(device, queue_allocations.graphics_queue),
|
||||
.compute_queue = .initAllocation(device, queue_allocations.compute_queue),
|
||||
.transfer_queue = .initAllocation(device, queue_allocations.transfer_queue),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Engine) void {
|
||||
const allocator = self.vk_allocator.allocator;
|
||||
|
||||
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),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return error.NoSuitableMemoryType;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user