adding queues and debug allocators

This commit is contained in:
2025-11-11 23:55:44 +01:00
parent c6db045bbc
commit c1ed06945e
12 changed files with 128 additions and 43 deletions

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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..]);

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);