diff --git a/src/soft/SoftDevice.zig b/src/soft/SoftDevice.zig index e8193a1..aad962b 100644 --- a/src/soft/SoftDevice.zig +++ b/src/soft/SoftDevice.zig @@ -1,9 +1,13 @@ const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); +const builtin = @import("builtin"); + +const Debug = std.builtin.OptimizeMode.Debug; const SoftDeviceMemory = @import("SoftDeviceMemory.zig"); const SoftFence = @import("SoftFence.zig"); +const SoftQueue = @import("SoftQueue.zig"); const VkError = base.VkError; @@ -13,7 +17,7 @@ pub const Interface = base.Device; const SpawnError = std.Thread.SpawnError; interface: Interface, -device_allocator: std.heap.ThreadSafeAllocator, +device_allocator: if (builtin.mode == Debug) std.heap.DebugAllocator(.{}) else std.heap.ThreadSafeAllocator, workers: std.Thread.Pool, pub fn create(physical_device: *base.PhysicalDevice, allocator: std.mem.Allocator, info: *const vk.DeviceCreateInfo) VkError!*Self { @@ -22,6 +26,11 @@ pub fn create(physical_device: *base.PhysicalDevice, allocator: std.mem.Allocato var interface = try Interface.init(allocator, physical_device, info); + interface.vtable = &.{ + .createQueue = SoftQueue.create, + .destroyQueue = SoftQueue.destroy, + }; + interface.dispatch_table = &.{ .allocateMemory = allocateMemory, .createFence = createFence, @@ -35,20 +44,30 @@ pub fn create(physical_device: *base.PhysicalDevice, allocator: std.mem.Allocato self.* = .{ .interface = interface, - .device_allocator = .{ .child_allocator = std.heap.c_allocator }, // TODO: better device allocator + .device_allocator = if (builtin.mode == Debug) .init else .{ .child_allocator = std.heap.c_allocator }, // TODO: better device allocator .workers = undefined, }; - self.workers.init(.{ .allocator = self.interface.host_allocator.allocator() }) catch |err| return switch (err) { + self.workers.init(.{ .allocator = self.device_allocator.allocator() }) catch |err| return switch (err) { SpawnError.OutOfMemory, SpawnError.LockedMemoryLimitExceeded => VkError.OutOfDeviceMemory, else => VkError.Unknown, }; + + try self.interface.createQueues(allocator, info); return self; } pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); self.workers.deinit(); + + if (builtin.mode == Debug) { + // All device memory allocations should've been freed by now + if (!self.device_allocator.detectLeaks()) { + std.log.scoped(.vkDestroyDevice).debug("No device memory leaks detected", .{}); + } + } + allocator.destroy(self); } diff --git a/src/soft/SoftInstance.zig b/src/soft/SoftInstance.zig index 351947f..57280cc 100644 --- a/src/soft/SoftInstance.zig +++ b/src/soft/SoftInstance.zig @@ -18,9 +18,11 @@ pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) self.interface = try base.Instance.init(allocator, infos); self.interface.dispatch_table = &.{ + .destroyInstance = destroyInstance, + }; + self.interface.vtable = &.{ .requestPhysicalDevices = requestPhysicalDevices, .releasePhysicalDevices = releasePhysicalDevices, - .destroyInstance = destroyInstance, }; return &self.interface; } diff --git a/src/soft/SoftPhysicalDevice.zig b/src/soft/SoftPhysicalDevice.zig index ced8b6c..9bccd15 100644 --- a/src/soft/SoftPhysicalDevice.zig +++ b/src/soft/SoftPhysicalDevice.zig @@ -74,6 +74,7 @@ pub fn create(allocator: std.mem.Allocator, instance: *const base.Instance) VkEr .timestamp_valid_bits = 0, .min_image_transfer_granularity = .{ .width = 1, .height = 1, .depth = 1 }, }, + // TODO: maybe add a compute specialized queue }; interface.queue_family_props = std.ArrayList(vk.QueueFamilyProperties).fromOwnedSlice(queue_family_props[0..]); diff --git a/src/soft/SoftQueue.zig b/src/soft/SoftQueue.zig index f4a72c0..e45b11c 100644 --- a/src/soft/SoftQueue.zig +++ b/src/soft/SoftQueue.zig @@ -13,7 +13,7 @@ pub const Interface = base.Queue; interface: Interface, mutex: std.Thread.Mutex, -pub fn create(allocator: std.mem.Allocator, device: *const base.Device, index: u32, family_index: u32, flags: vk.DeviceQueueCreateFlags) VkError!*Self { +pub fn create(allocator: std.mem.Allocator, device: *const base.Device, index: u32, family_index: u32, flags: vk.DeviceQueueCreateFlags) VkError!*Interface { const self = allocator.create(Self) catch return VkError.OutOfHostMemory; errdefer allocator.destroy(self); @@ -29,7 +29,12 @@ pub fn create(allocator: std.mem.Allocator, device: *const base.Device, index: u .interface = interface, .mutex = .{}, }; - return self; + return &self.interface; +} + +pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + allocator.destroy(self); } pub fn bindSparse(interface: *Interface, info: []*const vk.BindSparseInfo, fence: ?*base.Fence) VkError!void { diff --git a/src/vulkan/Device.zig b/src/vulkan/Device.zig index c3c300e..a49ccf8 100644 --- a/src/vulkan/Device.zig +++ b/src/vulkan/Device.zig @@ -13,10 +13,16 @@ const Self = @This(); pub const ObjectType: vk.ObjectType = .device; physical_device: *const PhysicalDevice, -dispatch_table: *const DispatchTable, -host_allocator: VulkanAllocator, queues: std.AutoArrayHashMapUnmanaged(u32, std.ArrayListUnmanaged(*Dispatchable(Queue))), +dispatch_table: *const DispatchTable, +vtable: *const VTable, + +pub const VTable = struct { + createQueue: *const fn (std.mem.Allocator, *const Self, u32, u32, vk.DeviceQueueCreateFlags) VkError!*Queue, + destroyQueue: *const fn (*Queue, std.mem.Allocator) VkError!void, +}; + pub const DispatchTable = struct { allocateMemory: *const fn (*Self, std.mem.Allocator, *const vk.MemoryAllocateInfo) VkError!*DeviceMemory, createFence: *const fn (*Self, std.mem.Allocator, *const vk.FenceCreateInfo) VkError!*Fence, @@ -29,15 +35,14 @@ pub const DispatchTable = struct { }; pub fn init(allocator: std.mem.Allocator, physical_device: *const PhysicalDevice, info: *const vk.DeviceCreateInfo) VkError!Self { - const vulkan_allocator: *VulkanAllocator = @ptrCast(@alignCast(allocator.ptr)); - var self: Self = .{ + _ = allocator; + _ = info; + return .{ .physical_device = physical_device, - .dispatch_table = undefined, - .host_allocator = vulkan_allocator.*, .queues = .empty, + .dispatch_table = undefined, + .vtable = undefined, }; - try self.createQueues(allocator, info); - return self; } pub fn createQueues(self: *Self, allocator: std.mem.Allocator, info: *const vk.DeviceCreateInfo) VkError!void { @@ -46,22 +51,31 @@ pub fn createQueues(self: *Self, allocator: std.mem.Allocator, info: *const vk.D } else if (info.p_queue_create_infos == null) { return VkError.ValidationFailed; } - var family_sizes: std.AutoHashMap(u32, usize) = .empty; - defer family_sizes.deinit(allocator); for (0..info.queue_create_info_count) |i| { const queue_info = info.p_queue_create_infos.?[i]; - const value = family_sizes.getOrPut(allocator, queue_info.queue_family_index) catch return VkError.OutOfHostMemory; - value.value_ptr.* += @intCast(queue_info.queue_count); - } + const res = (self.queues.getOrPut(allocator, queue_info.queue_family_index) catch return VkError.OutOfHostMemory); + const family_ptr = res.value_ptr; + if (!res.found_existing) { + family_ptr.* = .empty; + } - var it = family_sizes.iterator(); - while (it.next()) |entry| { - self.queues.put(allocator, entry.key_ptr.*, std.ArrayListUnmanaged(*Dispatchable(Queue)).initCapacity(allocator, entry.value_ptr.*)) catch return VkError.OutOfHostMemory; + const queue = try self.vtable.createQueue(allocator, self, queue_info.queue_family_index, @intCast(family_ptr.items.len), queue_info.flags); + const dispatchable_queue = try Dispatchable(Queue).wrap(allocator, queue); + family_ptr.append(allocator, dispatchable_queue) catch return VkError.OutOfHostMemory; } } pub fn destroy(self: *Self, allocator: std.mem.Allocator) VkError!void { + var it = self.queues.iterator(); + while (it.next()) |entry| { + const family = entry.value_ptr; + for (family.items) |dispatchable_queue| { + try self.vtable.destroyQueue(dispatchable_queue.object, allocator); + dispatchable_queue.destroy(allocator); + } + family.deinit(allocator); + } self.queues.deinit(allocator); try self.dispatch_table.destroy(self, allocator); } diff --git a/src/vulkan/Instance.zig b/src/vulkan/Instance.zig index 6ac494a..7ff11a5 100644 --- a/src/vulkan/Instance.zig +++ b/src/vulkan/Instance.zig @@ -20,11 +20,15 @@ pub const ObjectType: vk.ObjectType = .instance; physical_devices: std.ArrayListUnmanaged(*Dispatchable(PhysicalDevice)), dispatch_table: *const DispatchTable, +vtable: *const VTable, + +pub const VTable = struct { + releasePhysicalDevices: *const fn (*Self, std.mem.Allocator) VkError!void, + requestPhysicalDevices: *const fn (*Self, std.mem.Allocator) VkError!void, +}; pub const DispatchTable = struct { destroyInstance: *const fn (*Self, std.mem.Allocator) VkError!void, - releasePhysicalDevices: *const fn (*Self, std.mem.Allocator) VkError!void, - requestPhysicalDevices: *const fn (*Self, std.mem.Allocator) VkError!void, }; pub fn init(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!Self { @@ -33,11 +37,12 @@ pub fn init(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) V return .{ .physical_devices = .empty, .dispatch_table = undefined, + .vtable = undefined, }; } pub fn deinit(self: *Self, allocator: std.mem.Allocator) VkError!void { - try self.dispatch_table.releasePhysicalDevices(self, allocator); + try self.releasePhysicalDevices(allocator); try self.dispatch_table.destroyInstance(self, allocator); } @@ -61,11 +66,11 @@ pub fn enumerateVersion(version: *u32) VkError!void { } pub fn releasePhysicalDevices(self: *Self, allocator: std.mem.Allocator) VkError!void { - try self.dispatch_table.releasePhysicalDevices(self, allocator); + try self.vtable.releasePhysicalDevices(self, allocator); } pub fn requestPhysicalDevices(self: *Self, allocator: std.mem.Allocator) VkError!void { - try self.dispatch_table.requestPhysicalDevices(self, allocator); + try self.vtable.requestPhysicalDevices(self, allocator); if (self.physical_devices.items.len == 0) { std.log.scoped(.vkCreateInstance).info("No VkPhysicalDevice found", .{}); return; diff --git a/src/vulkan/Queue.zig b/src/vulkan/Queue.zig index 4aeb1ac..06c886d 100644 --- a/src/vulkan/Queue.zig +++ b/src/vulkan/Queue.zig @@ -22,6 +22,7 @@ pub const DispatchTable = struct { }; pub fn init(allocator: std.mem.Allocator, device: *const Device, index: u32, family_index: u32, flags: vk.DeviceQueueCreateFlags) VkError!Self { + std.log.scoped(.vkCreateDevice).info("Creating device queue with family index {d} and index {d}", .{ family_index, index }); _ = allocator; return .{ .owner = device, diff --git a/src/vulkan/VulkanAllocator.zig b/src/vulkan/VulkanAllocator.zig index abbf000..b7018ce 100644 --- a/src/vulkan/VulkanAllocator.zig +++ b/src/vulkan/VulkanAllocator.zig @@ -3,12 +3,16 @@ const std = @import("std"); const vk = @import("vulkan"); +const builtin = @import("builtin"); +const DRIVER_DEBUG_ALLOCATOR_ENV_NAME = @import("lib.zig").DRIVER_DEBUG_ALLOCATOR_ENV_NAME; const Allocator = std.mem.Allocator; const Alignment = std.mem.Alignment; const Self = @This(); +pub var debug_allocator: std.heap.DebugAllocator(.{}) = .init; + callbacks: ?vk.AllocationCallbacks, scope: vk.SystemAllocationScope, @@ -21,22 +25,22 @@ pub fn init(callbacks: ?*const vk.AllocationCallbacks, scope: vk.SystemAllocatio } pub fn allocator(self: *const Self) Allocator { - if (self.callbacks == null) { + if (self.callbacks != null) { return .{ .ptr = @constCast(self), - .vtable = std.heap.c_allocator.vtable, + .vtable = &.{ + .alloc = alloc, + .resize = resize, + .remap = remap, + .free = free, + }, }; } - return .{ - .ptr = @constCast(self), - .vtable = &.{ - .alloc = alloc, - .resize = resize, - .remap = remap, - .free = free, - }, - }; + return if (std.process.hasEnvVarConstant(DRIVER_DEBUG_ALLOCATOR_ENV_NAME) or builtin.mode == std.builtin.OptimizeMode.Debug) + debug_allocator.allocator() + else + std.heap.c_allocator; } fn alloc(context: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 { diff --git a/src/vulkan/lib.zig b/src/vulkan/lib.zig index 19efd44..aed84bf 100644 --- a/src/vulkan/lib.zig +++ b/src/vulkan/lib.zig @@ -18,7 +18,9 @@ pub const DeviceMemory = @import("DeviceMemory.zig"); pub const Fence = @import("Fence.zig"); pub const VULKAN_VENDOR_ID = @typeInfo(vk.VendorId).@"enum".fields[@typeInfo(vk.VendorId).@"enum".fields.len - 1].value + 1; + pub const DRIVER_LOGS_ENV_NAME = "STROLL_LOGS_LEVEL"; +pub const DRIVER_DEBUG_ALLOCATOR_ENV_NAME = "STROLL_DEBUG_ALLOCATOR"; pub const std_options: std.Options = .{ .log_level = .debug, diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index ad7a8be..3a60bba 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -75,6 +75,7 @@ const device_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{ functionMapEntryPoint("vkDestroyDevice"), functionMapEntryPoint("vkCreateFence"), functionMapEntryPoint("vkFreeMemory"), + functionMapEntryPoint("vkGetDeviceQueue"), functionMapEntryPoint("vkGetFenceStatus"), functionMapEntryPoint("vkMapMemory"), functionMapEntryPoint("vkUnmapMemory"), @@ -173,6 +174,13 @@ pub export fn strollDestroyInstance(p_instance: vk.Instance, callbacks: ?*const const dispatchable = Dispatchable(Instance).fromHandle(p_instance) catch return; dispatchable.object.deinit(allocator) catch {}; dispatchable.destroy(allocator); + + if (std.process.hasEnvVarConstant(lib.DRIVER_DEBUG_ALLOCATOR_ENV_NAME) or builtin.mode == std.builtin.OptimizeMode.Debug) { + // All host memory allocations should've been freed by now + if (!VulkanAllocator.debug_allocator.detectLeaks()) { + std.log.scoped(.vkDestroyInstance).debug("No memory leaks detected", .{}); + } + } } pub export fn strollEnumeratePhysicalDevices(p_instance: vk.Instance, count: *u32, p_devices: ?[*]vk.PhysicalDevice) callconv(vk.vulkan_call_conv) vk.Result { @@ -330,6 +338,22 @@ pub export fn strollGetDeviceProcAddr(p_device: vk.Device, p_name: ?[*:0]const u return null; } +pub export fn strollGetDeviceQueue(p_device: vk.Device, queue_family_index: u32, queue_index: u32, p_queue: *vk.Queue) callconv(vk.vulkan_call_conv) void { + p_queue.* = .null_handle; + const device = Dispatchable(Device).fromHandleObject(p_device) catch return; + if (device.queues.get(queue_family_index)) |family| { + if (queue_index >= family.items.len) return; + + const dispatchable_queue = family.items[queue_index]; + const queue = dispatchable_queue.object; + + // https://docs.vulkan.org/refpages/latest/refpages/source/vkGetDeviceQueue.html#VUID-vkGetDeviceQueue-flags-01841 + if (queue.flags != @TypeOf(queue.flags){}) return; + + p_queue.* = dispatchable_queue.toVkHandle(vk.Queue); + } +} + pub export fn strollGetFenceStatus(p_device: vk.Device, p_fence: vk.Fence) callconv(vk.vulkan_call_conv) vk.Result { const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const fence = NonDispatchable(Fence).fromHandleObject(p_fence) catch |err| return toVkResult(err); diff --git a/src/vulkan/logger.zig b/src/vulkan/logger.zig index 7eb76c2..4cf652d 100644 --- a/src/vulkan/logger.zig +++ b/src/vulkan/logger.zig @@ -45,10 +45,9 @@ pub fn log(comptime level: std.log.Level, comptime scope: @Type(.enum_literal), const prefix = std.fmt.comptimePrint("{s: <8}", .{"[" ++ comptime level.asText() ++ "] "}); const level_color: std.Io.tty.Color = switch (level) { - .info => .blue, + .info, .debug => .blue, .warn => .yellow, .err => .red, - .debug => .blue, }; std.debug.lockStdErr(); diff --git a/test/c/main.c b/test/c/main.c index 6abe95d..40c8e6b 100644 --- a/test/c/main.c +++ b/test/c/main.c @@ -69,20 +69,29 @@ int main(void) vkGetPhysicalDeviceProperties(physical_devices[0], &props); printf("VkPhysicalDevice name %s\n", props.deviceName); + VkDeviceQueueCreateInfo queue_create_infos = {0}; + queue_create_infos.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_infos.queueFamilyIndex = 1; + queue_create_infos.queueCount = 1; + VkDeviceCreateInfo device_create_info = {0}; device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - + device_create_info.queueCreateInfoCount = 1; + device_create_info.pQueueCreateInfos = &queue_create_infos; + VkDevice device = VK_NULL_HANDLE; CheckVk(vkCreateDevice(physical_devices[0], &device_create_info, NULL, &device)); - volkLoadDevice(device); + VkQueue queue = VK_NULL_HANDLE; + vkGetDeviceQueue(device, 1, 0, &queue); + printf("VkQueue %p\n", queue); + VkFenceCreateInfo fence_info = {}; fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; VkFence fence = VK_NULL_HANDLE; CheckVk(vkCreateFence(device, &fence_info, NULL, &fence)); - printf("VkFence %p\n", fence); vkDestroyFence(device, fence, NULL);