Port rewrite
This commit is contained in:
250
src/engine.zig
250
src/engine.zig
@@ -1,250 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const glfw = @import("zglfw");
|
|
||||||
const vk = @import("vulkan");
|
|
||||||
|
|
||||||
const c = @import("const.zig");
|
|
||||||
const main = @import("main.zig");
|
|
||||||
|
|
||||||
pub const required_instance_layers = [_][*:0]const u8{
|
|
||||||
"VK_LAYER_KHRONOS_validation",
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const required_device_extensions = [_][*:0]const u8{
|
|
||||||
vk.extensions.khr_swapchain.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const vk_log_scope = .vulkan;
|
|
||||||
pub const log = std.log.scoped(vk_log_scope);
|
|
||||||
|
|
||||||
pub var vkb: vk.BaseWrapper = undefined;
|
|
||||||
pub var vki: vk.InstanceProxy = undefined;
|
|
||||||
pub var vkd: vk.DeviceProxy = undefined;
|
|
||||||
|
|
||||||
pub var dum: vk.DebugUtilsMessengerEXT = undefined;
|
|
||||||
pub var surface: vk.SurfaceKHR = undefined;
|
|
||||||
pub var graphics_queue: vk.Queue = undefined;
|
|
||||||
pub var present_queue: vk.Queue = undefined;
|
|
||||||
pub var memory_properties: vk.PhysicalDeviceMemoryProperties = undefined;
|
|
||||||
|
|
||||||
pub fn init() !void {
|
|
||||||
vkb = vk.BaseWrapper.load(glfw.getInstanceProcAddress);
|
|
||||||
|
|
||||||
var vk_instance_extensions = blk: {
|
|
||||||
var ret: std.ArrayList([*:0]const u8) = .empty;
|
|
||||||
try ret.append(main.allocator, vk.extensions.ext_debug_utils.name);
|
|
||||||
try ret.append(main.allocator, vk.extensions.khr_portability_enumeration.name);
|
|
||||||
try ret.append(main.allocator, vk.extensions.khr_get_physical_device_properties_2.name);
|
|
||||||
|
|
||||||
const glfw_instance_extensions = try glfw.getRequiredInstanceExtensions();
|
|
||||||
try ret.appendSlice(main.allocator, glfw_instance_extensions);
|
|
||||||
|
|
||||||
break :blk ret;
|
|
||||||
};
|
|
||||||
defer vk_instance_extensions.deinit(main.allocator);
|
|
||||||
|
|
||||||
const dum_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 = &dumCallback,
|
|
||||||
.p_user_data = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const version = vk.makeApiVersion(
|
|
||||||
0,
|
|
||||||
c.app_version.major,
|
|
||||||
c.app_version.minor,
|
|
||||||
c.app_version.patch,
|
|
||||||
);
|
|
||||||
|
|
||||||
const instance = vkb.createInstance(&.{
|
|
||||||
.p_application_info = &.{
|
|
||||||
.p_application_name = c.app_name,
|
|
||||||
.application_version = @bitCast(version),
|
|
||||||
.p_engine_name = c.app_name,
|
|
||||||
.engine_version = @bitCast(version),
|
|
||||||
.api_version = @bitCast(vk.API_VERSION_1_2),
|
|
||||||
},
|
|
||||||
.enabled_layer_count = required_instance_layers.len,
|
|
||||||
.pp_enabled_layer_names = &required_instance_layers,
|
|
||||||
.enabled_extension_count = @intCast(vk_instance_extensions.items.len),
|
|
||||||
.pp_enabled_extension_names = vk_instance_extensions.items.ptr,
|
|
||||||
.flags = .{ .enumerate_portability_bit_khr = true },
|
|
||||||
.p_next = &dum_create_info,
|
|
||||||
}, null) catch |err| {
|
|
||||||
std.log.err("Could not create Vulkan Instance", .{});
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
|
|
||||||
const instance_wrapper_ptr = try main.allocator.create(vk.InstanceWrapper);
|
|
||||||
errdefer main.allocator.destroy(instance_wrapper_ptr);
|
|
||||||
|
|
||||||
instance_wrapper_ptr.* = vk.InstanceWrapper.load(instance, vkb.dispatch.vkGetInstanceProcAddr.?);
|
|
||||||
vki = vk.InstanceProxy.init(instance, instance_wrapper_ptr);
|
|
||||||
errdefer vki.destroyInstance(null);
|
|
||||||
|
|
||||||
dum = try vki.createDebugUtilsMessengerEXT(&dum_create_info, null);
|
|
||||||
errdefer vki.destroyDebugUtilsMessengerEXT(dum, null);
|
|
||||||
|
|
||||||
try glfw.createWindowSurface(instance, main.window, null, &surface);
|
|
||||||
errdefer vki.destroySurfaceKHR(surface, null);
|
|
||||||
|
|
||||||
const physical_devices = try vki.enumeratePhysicalDevicesAlloc(main.allocator);
|
|
||||||
defer main.allocator.free(physical_devices);
|
|
||||||
|
|
||||||
const physical_device_data = physical_device_blk: {
|
|
||||||
loop: for (physical_devices) |physical_device_candidate| {
|
|
||||||
const device_properties = vki.getPhysicalDeviceProperties(physical_device_candidate);
|
|
||||||
const device_name = std.mem.sliceTo(&device_properties.device_name, 0);
|
|
||||||
|
|
||||||
const device_extensions = try vki.enumerateDeviceExtensionPropertiesAlloc(physical_device_candidate, null, main.allocator);
|
|
||||||
defer main.allocator.free(device_extensions);
|
|
||||||
|
|
||||||
for (required_device_extensions) |a| {
|
|
||||||
const a_name = std.mem.span(a);
|
|
||||||
for (device_extensions) |b| {
|
|
||||||
const b_name = std.mem.sliceTo(&b.extension_name, 0);
|
|
||||||
if (std.mem.eql(u8, a_name, b_name)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std.log.debug("Device \"{s}\" does not support Vulkan Device Extension \"{s}\" and is not suitable for this application", .{ device_name, a_name });
|
|
||||||
continue :loop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var format_count: u32 = undefined;
|
|
||||||
_ = try vki.getPhysicalDeviceSurfaceFormatsKHR(physical_device_candidate, surface, &format_count, null);
|
|
||||||
if (format_count == 0) {
|
|
||||||
std.log.debug("Device \"{s}\" has zero Vulkan Surface formats and is not suitable for this application", .{device_name});
|
|
||||||
continue :loop;
|
|
||||||
}
|
|
||||||
|
|
||||||
var present_mode_count: u32 = undefined;
|
|
||||||
_ = try vki.getPhysicalDeviceSurfacePresentModesKHR(physical_device_candidate, surface, &present_mode_count, null);
|
|
||||||
if (present_mode_count == 0) {
|
|
||||||
std.log.debug("Device \"{s}\" has zero Vulkan Present Modes and is not suitable for this application", .{device_name});
|
|
||||||
continue :loop;
|
|
||||||
}
|
|
||||||
|
|
||||||
const device_queue_families = try vki.getPhysicalDeviceQueueFamilyPropertiesAlloc(physical_device_candidate, main.allocator);
|
|
||||||
defer main.allocator.free(device_queue_families);
|
|
||||||
|
|
||||||
const graphics_family_id = blk: {
|
|
||||||
var family_id: u32 = 0;
|
|
||||||
while (family_id < device_queue_families.len) : (family_id += 1) {
|
|
||||||
const family = device_queue_families[family_id];
|
|
||||||
if (family.queue_flags.graphics_bit) {
|
|
||||||
break :blk family_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std.log.debug("Device \"{s}\" has no Vulkan Queue Family with graphics flag and is not suitable for this application", .{device_name});
|
|
||||||
continue :loop;
|
|
||||||
};
|
|
||||||
|
|
||||||
const present_family_id = blk: {
|
|
||||||
var family_id: u32 = 0;
|
|
||||||
while (family_id < device_queue_families.len) : (family_id += 1) {
|
|
||||||
if (try vki.getPhysicalDeviceSurfaceSupportKHR(physical_device_candidate, family_id, surface) == .true) {
|
|
||||||
break :blk family_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std.log.debug("Device \"{s}\" has no Vulkan Queue Family with Vulkan Surface Support and is not suitable for this application", .{device_name});
|
|
||||||
continue :loop;
|
|
||||||
};
|
|
||||||
|
|
||||||
break :physical_device_blk .{
|
|
||||||
.handle = physical_device_candidate,
|
|
||||||
.name = device_name,
|
|
||||||
.properties = device_properties,
|
|
||||||
.graphics_family_id = graphics_family_id,
|
|
||||||
.present_family_id = present_family_id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
std.log.err("Could not find suitable Vulkan Physical Device", .{});
|
|
||||||
return error.NoSuitableDevice;
|
|
||||||
};
|
|
||||||
|
|
||||||
std.log.debug("Picked Vulkan Physical Device \"{s}\"", .{physical_device_data.name});
|
|
||||||
|
|
||||||
const device = try vki.createDevice(physical_device_data.handle, &.{
|
|
||||||
.queue_create_info_count = if (physical_device_data.graphics_family_id == physical_device_data.present_family_id) 1 else 2,
|
|
||||||
.p_queue_create_infos = &.{
|
|
||||||
.{ .queue_family_index = physical_device_data.graphics_family_id, .queue_count = 1, .p_queue_priorities = &.{1.0} },
|
|
||||||
.{ .queue_family_index = physical_device_data.present_family_id, .queue_count = 1, .p_queue_priorities = &.{1.0} },
|
|
||||||
},
|
|
||||||
.enabled_extension_count = required_device_extensions.len,
|
|
||||||
.pp_enabled_extension_names = &required_device_extensions,
|
|
||||||
}, null);
|
|
||||||
|
|
||||||
const device_wrapper_ptr = try main.allocator.create(vk.DeviceWrapper);
|
|
||||||
errdefer main.allocator.destroy(device_wrapper_ptr);
|
|
||||||
|
|
||||||
device_wrapper_ptr.* = vk.DeviceWrapper.load(device, vki.wrapper.dispatch.vkGetDeviceProcAddr.?);
|
|
||||||
vkd = vk.DeviceProxy.init(device, device_wrapper_ptr);
|
|
||||||
errdefer vkd.destroyDevice(null);
|
|
||||||
|
|
||||||
graphics_queue = vkd.getDeviceQueue(physical_device_data.graphics_family_id, 0);
|
|
||||||
present_queue = vkd.getDeviceQueue(physical_device_data.present_family_id, 0);
|
|
||||||
|
|
||||||
memory_properties = vki.getPhysicalDeviceMemoryProperties(physical_device_data.handle);
|
|
||||||
|
|
||||||
const framebuffer_width, const framebuffer_height = blk: {
|
|
||||||
const w, const h = main.window.getFramebufferSize();
|
|
||||||
break :blk [_]u32{ @intCast(w), @intCast(h) };
|
|
||||||
};
|
|
||||||
|
|
||||||
const vk_extent: vk.Extent2D = .{
|
|
||||||
.width = framebuffer_width,
|
|
||||||
.height = framebuffer_height,
|
|
||||||
};
|
|
||||||
|
|
||||||
_ = vk_extent;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit() void {
|
|
||||||
vkd.destroyDevice(null);
|
|
||||||
vki.destroySurfaceKHR(surface, null);
|
|
||||||
vki.destroyDebugUtilsMessengerEXT(dum, null);
|
|
||||||
vki.destroyInstance(null);
|
|
||||||
|
|
||||||
main.allocator.destroy(vki.wrapper);
|
|
||||||
main.allocator.destroy(vkd.wrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dumCallback(
|
|
||||||
severity: vk.DebugUtilsMessageSeverityFlagsEXT,
|
|
||||||
message_type: vk.DebugUtilsMessageTypeFlagsEXT,
|
|
||||||
maybe_callback_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT,
|
|
||||||
_: ?*anyopaque,
|
|
||||||
) callconv(.c) vk.Bool32 {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
||||||
|
}
|
||||||
19
src/engine/Queue.zig
Normal file
19
src/engine/Queue.zig
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const Queue = @This();
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
|
||||||
|
pub const Allocation = struct {
|
||||||
|
family: u32,
|
||||||
|
index: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
handle: vk.Queue,
|
||||||
|
allocation: Allocation,
|
||||||
|
|
||||||
|
pub fn initAllocation(device: vk.DeviceProxy, allocation: Allocation) Queue {
|
||||||
|
return .{
|
||||||
|
.handle = device.getDeviceQueue(allocation.family, allocation.index),
|
||||||
|
.allocation = allocation,
|
||||||
|
};
|
||||||
|
}
|
||||||
319
src/engine/Swapchain.zig
Normal file
319
src/engine/Swapchain.zig
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
const Swapchain = @This();
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
|
||||||
|
const Engine = @import("Engine.zig");
|
||||||
|
|
||||||
|
engine: *Engine,
|
||||||
|
params: Params,
|
||||||
|
render_pass: vk.RenderPass,
|
||||||
|
swapchain: vk.SwapchainKHR = .null_handle,
|
||||||
|
swapchain_images: []SwapchainImage = &.{},
|
||||||
|
|
||||||
|
pub fn init(engine: *Engine) !Swapchain {
|
||||||
|
const params: Params = try .init(engine);
|
||||||
|
|
||||||
|
const render_pass = blk: {
|
||||||
|
const attachments = [_]vk.AttachmentDescription{
|
||||||
|
.{
|
||||||
|
.format = params.surface_format.format,
|
||||||
|
.samples = .{ .@"1_bit" = true },
|
||||||
|
.load_op = .dont_care,
|
||||||
|
.store_op = .store,
|
||||||
|
.stencil_load_op = .dont_care,
|
||||||
|
.stencil_store_op = .dont_care,
|
||||||
|
.initial_layout = .undefined,
|
||||||
|
.final_layout = .present_src_khr,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const color_attachments = [_]vk.AttachmentReference{
|
||||||
|
.{
|
||||||
|
.attachment = 0,
|
||||||
|
.layout = .color_attachment_optimal,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const subpasses = [_]vk.SubpassDescription{
|
||||||
|
.{
|
||||||
|
.pipeline_bind_point = .graphics,
|
||||||
|
.color_attachment_count = color_attachments.len,
|
||||||
|
.p_color_attachments = &color_attachments,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
break :blk try engine.device.createRenderPass(&.{
|
||||||
|
.attachment_count = attachments.len,
|
||||||
|
.p_attachments = &attachments,
|
||||||
|
.subpass_count = subpasses.len,
|
||||||
|
.p_subpasses = &subpasses,
|
||||||
|
}, &engine.vk_allocator.interface);
|
||||||
|
};
|
||||||
|
errdefer engine.device.destroyRenderPass(render_pass, &engine.vk_allocator.interface);
|
||||||
|
|
||||||
|
var swapchain: Swapchain = .{
|
||||||
|
.engine = engine,
|
||||||
|
.params = params,
|
||||||
|
.render_pass = render_pass,
|
||||||
|
};
|
||||||
|
|
||||||
|
try recreate(&swapchain);
|
||||||
|
return swapchain;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Swapchain) void {
|
||||||
|
const allocator = self.engine.vk_allocator.allocator;
|
||||||
|
|
||||||
|
for (self.swapchain_images) |swapchain_image| {
|
||||||
|
swapchain_image.deinit(self.engine);
|
||||||
|
}
|
||||||
|
allocator.free(self.swapchain_images);
|
||||||
|
|
||||||
|
if (self.swapchain != .null_handle) {
|
||||||
|
self.engine.device.destroySwapchainKHR(self.swapchain, &self.engine.vk_allocator.interface);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.engine.device.destroyRenderPass(self.render_pass, &self.engine.vk_allocator.interface);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recreate(self: *Swapchain) !void {
|
||||||
|
const mode = &self.engine.mode.surface;
|
||||||
|
const allocator = self.engine.vk_allocator.allocator;
|
||||||
|
|
||||||
|
const old_swapchain = self.swapchain;
|
||||||
|
const old_swapchain_images = self.swapchain_images;
|
||||||
|
|
||||||
|
const extent = try getCurrentExtent(self.engine);
|
||||||
|
|
||||||
|
// --- CREATE NEW SWAPCHAIN ------------------------------------------------
|
||||||
|
|
||||||
|
const surface_capabilities = try self.engine.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.engine.physical_device, mode.surface);
|
||||||
|
|
||||||
|
const graphics_queue_family = self.engine.graphics_queue.allocation.family;
|
||||||
|
const presentation_queue_family = mode.presentation_queue.allocation.family;
|
||||||
|
|
||||||
|
const single_queue_family = graphics_queue_family == presentation_queue_family;
|
||||||
|
const queue_family_indices: []const u32 = if (single_queue_family) &.{} else &.{ graphics_queue_family, presentation_queue_family };
|
||||||
|
|
||||||
|
const new_swapchain = try self.engine.device.createSwapchainKHR(&.{
|
||||||
|
.surface = mode.surface,
|
||||||
|
.min_image_count = self.params.image_count,
|
||||||
|
.image_format = self.params.surface_format.format,
|
||||||
|
.image_color_space = self.params.surface_format.color_space,
|
||||||
|
.image_extent = extent,
|
||||||
|
.image_array_layers = 1,
|
||||||
|
.image_usage = .{ .color_attachment_bit = true },
|
||||||
|
.image_sharing_mode = if (single_queue_family) .exclusive else .concurrent,
|
||||||
|
.queue_family_index_count = @intCast(queue_family_indices.len),
|
||||||
|
.p_queue_family_indices = queue_family_indices.ptr,
|
||||||
|
.pre_transform = surface_capabilities.current_transform,
|
||||||
|
.composite_alpha = .{ .opaque_bit_khr = true },
|
||||||
|
.present_mode = .fifo_khr,
|
||||||
|
.clipped = .true,
|
||||||
|
.old_swapchain = old_swapchain,
|
||||||
|
}, &self.engine.vk_allocator.interface);
|
||||||
|
errdefer self.engine.device.destroySwapchainKHR(new_swapchain, &self.engine.vk_allocator.interface);
|
||||||
|
|
||||||
|
// --- DESTROY OLD SWAPCHAIN AND ITS IMAGES --------------------------------
|
||||||
|
|
||||||
|
// NOTE At this point, the old swapchain has been recycled. There is no way
|
||||||
|
// to revert on error. Instead, we set the current swapchain and images to
|
||||||
|
// null and deinit the old swapchain and images.
|
||||||
|
|
||||||
|
for (old_swapchain_images) |swapchain_image| {
|
||||||
|
swapchain_image.deinit(self.engine);
|
||||||
|
}
|
||||||
|
allocator.free(self.swapchain_images);
|
||||||
|
self.swapchain_images = &.{};
|
||||||
|
|
||||||
|
if (old_swapchain != .null_handle) {
|
||||||
|
self.engine.device.destroySwapchainKHR(old_swapchain, &self.engine.vk_allocator.interface);
|
||||||
|
self.swapchain = .null_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CREATE NEW SWAPCHAIN IMAGES -----------------------------------------
|
||||||
|
|
||||||
|
const new_swapchain_images = blk: {
|
||||||
|
const images = try self.engine.device.getSwapchainImagesAllocKHR(new_swapchain, allocator);
|
||||||
|
defer allocator.free(images);
|
||||||
|
|
||||||
|
var swapchain_images: std.ArrayList(SwapchainImage) = try .initCapacity(allocator, images.len);
|
||||||
|
errdefer swapchain_images.deinit(allocator);
|
||||||
|
|
||||||
|
errdefer for (swapchain_images.items) |x| x.deinit(self.engine);
|
||||||
|
|
||||||
|
for (images) |image| {
|
||||||
|
swapchain_images.appendAssumeCapacity(try SwapchainImage.init(self.engine, .{
|
||||||
|
.image = image,
|
||||||
|
.format = self.params.surface_format.format,
|
||||||
|
.render_pass = self.render_pass,
|
||||||
|
.extent = extent,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
break :blk try swapchain_images.toOwnedSlice(allocator);
|
||||||
|
};
|
||||||
|
errdefer {
|
||||||
|
for (new_swapchain_images) |swapchain_image| {
|
||||||
|
swapchain_image.deinit(self.engine);
|
||||||
|
}
|
||||||
|
allocator.free(new_swapchain_images);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- COMMIT --------------------------------------------------------------
|
||||||
|
|
||||||
|
self.swapchain = new_swapchain;
|
||||||
|
self.swapchain_images = new_swapchain_images;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getCurrentExtent(self: *Engine) !vk.Extent2D {
|
||||||
|
const mode = &self.mode.surface;
|
||||||
|
const surface_capabilities = try self.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(self.physical_device, mode.surface);
|
||||||
|
|
||||||
|
if (surface_capabilities.current_extent.width != std.math.maxInt(u32) and surface_capabilities.current_extent.height != std.math.maxInt(u32)) {
|
||||||
|
return surface_capabilities.current_extent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const framebuffer_width, const framebuffer_height = mode.window.getFramebufferSize();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.width = std.math.clamp(
|
||||||
|
@as(u32, @intCast(framebuffer_width)),
|
||||||
|
surface_capabilities.min_image_extent.width,
|
||||||
|
surface_capabilities.max_image_extent.width,
|
||||||
|
),
|
||||||
|
.height = std.math.clamp(
|
||||||
|
@as(u32, @intCast(framebuffer_height)),
|
||||||
|
surface_capabilities.min_image_extent.height,
|
||||||
|
surface_capabilities.max_image_extent.height,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Params = struct {
|
||||||
|
surface_format: vk.SurfaceFormatKHR,
|
||||||
|
image_count: u32,
|
||||||
|
|
||||||
|
pub fn init(engine: *Engine) !Params {
|
||||||
|
const mode = &engine.mode.surface;
|
||||||
|
const allocator = engine.vk_allocator.allocator;
|
||||||
|
|
||||||
|
const surface_capabilities = try engine.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(engine.physical_device, mode.surface);
|
||||||
|
|
||||||
|
const surface_format = blk: {
|
||||||
|
const surface_formats = try engine.instance.getPhysicalDeviceSurfaceFormatsAllocKHR(engine.physical_device, mode.surface, allocator);
|
||||||
|
defer allocator.free(surface_formats);
|
||||||
|
|
||||||
|
// Look for 8-bit BGRA sRGB surface format.
|
||||||
|
|
||||||
|
for (surface_formats) |surface_format| {
|
||||||
|
if (surface_format.format == .b8g8r8a8_srgb and surface_format.color_space == .srgb_nonlinear_khr) {
|
||||||
|
break :blk surface_format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surface_formats.len == 0) {
|
||||||
|
return error.NoSurfaceFormats;
|
||||||
|
}
|
||||||
|
|
||||||
|
break :blk surface_formats[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
const image_count = @min(
|
||||||
|
surface_capabilities.min_image_count + 1,
|
||||||
|
if (surface_capabilities.max_image_count > 0) surface_capabilities.max_image_count else std.math.maxInt(u32),
|
||||||
|
);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.surface_format = surface_format,
|
||||||
|
.image_count = image_count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SwapchainImage = struct {
|
||||||
|
image: vk.Image,
|
||||||
|
image_view: vk.ImageView,
|
||||||
|
semaphore_image_acquired: vk.Semaphore,
|
||||||
|
semaphore_render_finished: vk.Semaphore,
|
||||||
|
fence: vk.Fence,
|
||||||
|
framebuffer: vk.Framebuffer,
|
||||||
|
|
||||||
|
const InitProps = struct {
|
||||||
|
image: vk.Image,
|
||||||
|
format: vk.Format,
|
||||||
|
render_pass: vk.RenderPass,
|
||||||
|
extent: vk.Extent2D,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn init(engine: *Engine, props: InitProps) !SwapchainImage {
|
||||||
|
const image_view = try engine.device.createImageView(&.{
|
||||||
|
.image = props.image,
|
||||||
|
.view_type = .@"2d",
|
||||||
|
.format = props.format,
|
||||||
|
.components = .{
|
||||||
|
.r = .identity,
|
||||||
|
.g = .identity,
|
||||||
|
.b = .identity,
|
||||||
|
.a = .identity,
|
||||||
|
},
|
||||||
|
.subresource_range = .{
|
||||||
|
.aspect_mask = .{ .color_bit = true },
|
||||||
|
.base_mip_level = 0,
|
||||||
|
.level_count = 1,
|
||||||
|
.base_array_layer = 0,
|
||||||
|
.layer_count = 1,
|
||||||
|
},
|
||||||
|
}, &engine.vk_allocator.interface);
|
||||||
|
errdefer engine.device.destroyImageView(image_view, &engine.vk_allocator.interface);
|
||||||
|
|
||||||
|
const semaphore_image_acquired = try engine.device.createSemaphore(&.{}, &engine.vk_allocator.interface);
|
||||||
|
errdefer engine.device.destroySemaphore(semaphore_image_acquired, &engine.vk_allocator.interface);
|
||||||
|
|
||||||
|
const semaphore_render_finished = try engine.device.createSemaphore(&.{}, &engine.vk_allocator.interface);
|
||||||
|
errdefer engine.device.destroySemaphore(semaphore_render_finished, &engine.vk_allocator.interface);
|
||||||
|
|
||||||
|
const fence = try engine.device.createFence(&.{
|
||||||
|
.flags = .{ .signaled_bit = true },
|
||||||
|
}, &engine.vk_allocator.interface);
|
||||||
|
errdefer engine.device.destroyFence(fence, &engine.vk_allocator.interface);
|
||||||
|
|
||||||
|
const attachments = [_]vk.ImageView{image_view};
|
||||||
|
|
||||||
|
const framebuffer = try engine.device.createFramebuffer(&.{
|
||||||
|
.render_pass = props.render_pass,
|
||||||
|
.attachment_count = attachments.len,
|
||||||
|
.p_attachments = &attachments,
|
||||||
|
.width = props.extent.width,
|
||||||
|
.height = props.extent.height,
|
||||||
|
.layers = 1,
|
||||||
|
}, &engine.vk_allocator.interface);
|
||||||
|
errdefer engine.device.destroyFramebuffer(framebuffer, &engine.vk_allocator.interface);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.image = props.image,
|
||||||
|
.image_view = image_view,
|
||||||
|
.semaphore_image_acquired = semaphore_image_acquired,
|
||||||
|
.semaphore_render_finished = semaphore_render_finished,
|
||||||
|
.fence = fence,
|
||||||
|
.framebuffer = framebuffer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: SwapchainImage, engine: *Engine) void {
|
||||||
|
self.waitForFence(engine) catch return;
|
||||||
|
engine.device.destroyFramebuffer(self.framebuffer, &engine.vk_allocator.interface);
|
||||||
|
engine.device.destroyFence(self.fence, &engine.vk_allocator.interface);
|
||||||
|
engine.device.destroySemaphore(self.semaphore_render_finished, &engine.vk_allocator.interface);
|
||||||
|
engine.device.destroySemaphore(self.semaphore_image_acquired, &engine.vk_allocator.interface);
|
||||||
|
engine.device.destroyImageView(self.image_view, &engine.vk_allocator.interface);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn waitForFence(self: SwapchainImage, engine: *Engine) !void {
|
||||||
|
const fences = [_]vk.Fence{self.fence};
|
||||||
|
_ = try engine.device.waitForFences(fences.len, &fences, .true, std.math.maxInt(u64));
|
||||||
|
}
|
||||||
|
};
|
||||||
119
src/engine/VkAllocator.zig
Normal file
119
src/engine/VkAllocator.zig
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
const VkAllocator = @This();
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const vk = @import("vulkan");
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
allocations: std.AutoHashMapUnmanaged(?*anyopaque, usize) = .empty,
|
||||||
|
mutex: std.Thread.Mutex = .{},
|
||||||
|
|
||||||
|
interface: vk.AllocationCallbacks,
|
||||||
|
|
||||||
|
// Allocation in Zig's standard library relies heavily on the alignment being
|
||||||
|
// comptime-known, but we only know the alignment at runtime. We could work
|
||||||
|
// around it, but it would add much complexity. Instead, we allocate everything
|
||||||
|
// with the same, relatively big alignment and hope for the best.
|
||||||
|
const actual_alignment: std.mem.Alignment = .@"16";
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) !*VkAllocator {
|
||||||
|
// NOTE We allocate the structure to pin its address.
|
||||||
|
const self = try allocator.create(VkAllocator);
|
||||||
|
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.interface = .{
|
||||||
|
.p_user_data = self,
|
||||||
|
.pfn_allocation = &allocationFunction,
|
||||||
|
.pfn_reallocation = &reallocationFunction,
|
||||||
|
.pfn_free = &freeFunction,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *VkAllocator) void {
|
||||||
|
const allocator = self.allocator;
|
||||||
|
|
||||||
|
self.allocations.deinit(allocator);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocationFunction(
|
||||||
|
p_user_data: ?*anyopaque,
|
||||||
|
size: usize,
|
||||||
|
alignment: usize,
|
||||||
|
_: vk.SystemAllocationScope,
|
||||||
|
) callconv(vk.vulkan_call_conv) ?*anyopaque {
|
||||||
|
const self: *VkAllocator = @ptrCast(@alignCast(p_user_data.?));
|
||||||
|
|
||||||
|
const desired_alignment: std.mem.Alignment = .fromByteUnits(alignment);
|
||||||
|
std.debug.assert(std.mem.Alignment.compare(actual_alignment, .gte, desired_alignment));
|
||||||
|
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
|
||||||
|
self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null;
|
||||||
|
const memory = self.allocator.alignedAlloc(u8, actual_alignment, size) catch return null;
|
||||||
|
|
||||||
|
self.allocations.putAssumeCapacity(memory.ptr, size);
|
||||||
|
|
||||||
|
return memory.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reallocationFunction(
|
||||||
|
p_user_data: ?*anyopaque,
|
||||||
|
maybe_p_original: ?*anyopaque,
|
||||||
|
size: usize,
|
||||||
|
alignment: usize,
|
||||||
|
_: vk.SystemAllocationScope,
|
||||||
|
) callconv(vk.vulkan_call_conv) ?*anyopaque {
|
||||||
|
const self: *VkAllocator = @ptrCast(@alignCast(p_user_data.?));
|
||||||
|
|
||||||
|
const desired_alignment: std.mem.Alignment = .fromByteUnits(alignment);
|
||||||
|
std.debug.assert(std.mem.Alignment.compare(actual_alignment, .gte, desired_alignment));
|
||||||
|
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
|
||||||
|
// NOTE If we were pedantic, we would consider the fact that we might not
|
||||||
|
// need unused capacity if the memory doesn't get relocated.
|
||||||
|
self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null;
|
||||||
|
|
||||||
|
const old_memory = if (maybe_p_original) |p_original| blk_then: {
|
||||||
|
const old_size = self.allocations.get(p_original).?;
|
||||||
|
break :blk_then @as([*]align(actual_alignment.toByteUnits()) u8, @ptrCast(@alignCast(p_original)))[0..old_size];
|
||||||
|
} else blk_else: {
|
||||||
|
break :blk_else @as([]align(actual_alignment.toByteUnits()) u8, &.{});
|
||||||
|
};
|
||||||
|
|
||||||
|
const memory = self.allocator.realloc(old_memory, size) catch return null;
|
||||||
|
std.debug.assert(std.mem.isAligned(@intFromPtr(memory.ptr), alignment));
|
||||||
|
|
||||||
|
if (maybe_p_original) |p_original| {
|
||||||
|
const removed = self.allocations.remove(p_original);
|
||||||
|
std.debug.assert(removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.allocations.putAssumeCapacityNoClobber(memory.ptr, size);
|
||||||
|
|
||||||
|
return memory.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn freeFunction(
|
||||||
|
p_user_data: ?*anyopaque,
|
||||||
|
maybe_p_memory: ?*anyopaque,
|
||||||
|
) callconv(vk.vulkan_call_conv) void {
|
||||||
|
const self: *VkAllocator = @ptrCast(@alignCast(p_user_data.?));
|
||||||
|
|
||||||
|
if (maybe_p_memory) |p_memory| {
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
|
||||||
|
const size = self.allocations.fetchRemove(p_memory).?.value;
|
||||||
|
const memory = @as([*]align(actual_alignment.toByteUnits()) u8, @ptrCast(@alignCast(p_memory)))[0..size];
|
||||||
|
|
||||||
|
self.allocator.free(memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/main.zig
14
src/main.zig
@@ -5,9 +5,11 @@ const stbi = @import("zstbi");
|
|||||||
const vk = @import("vulkan");
|
const vk = @import("vulkan");
|
||||||
|
|
||||||
const c = @import("const.zig");
|
const c = @import("const.zig");
|
||||||
const engine = @import("engine.zig");
|
|
||||||
const game = @import("game.zig");
|
const game = @import("game.zig");
|
||||||
|
|
||||||
|
const Engine = @import("engine/Engine.zig");
|
||||||
|
const Swapchain = @import("engine/Swapchain.zig");
|
||||||
|
|
||||||
pub var allocator: std.mem.Allocator = undefined;
|
pub var allocator: std.mem.Allocator = undefined;
|
||||||
pub var temp_allocator: std.mem.Allocator = undefined;
|
pub var temp_allocator: std.mem.Allocator = undefined;
|
||||||
pub var window: *glfw.Window = undefined;
|
pub var window: *glfw.Window = undefined;
|
||||||
@@ -46,18 +48,18 @@ pub fn main() !void {
|
|||||||
|
|
||||||
window.setSizeLimits(c.min_window_width, c.min_window_height, -1, -1);
|
window.setSizeLimits(c.min_window_width, c.min_window_height, -1, -1);
|
||||||
|
|
||||||
engine.init() catch |err| {
|
var engine = try Engine.init(allocator, window);
|
||||||
std.log.err("Could not initialize engine", .{});
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
defer engine.deinit();
|
defer engine.deinit();
|
||||||
|
|
||||||
|
var swapchain = try Swapchain.init(&engine);
|
||||||
|
defer swapchain.deinit();
|
||||||
|
|
||||||
//game.init();
|
//game.init();
|
||||||
//defer game.deinit();
|
//defer game.deinit();
|
||||||
|
|
||||||
while (!window.shouldClose()) {
|
while (!window.shouldClose()) {
|
||||||
glfw.pollEvents();
|
glfw.pollEvents();
|
||||||
//game.update(dt);
|
//game.update(dt);
|
||||||
window.swapBuffers();
|
std.Thread.sleep(1 * std.time.ns_per_ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user