//! This file contains all exported Vulkan entrypoints. const std = @import("std"); const vk = @import("vulkan"); const root = @import("root"); const lib = @import("lib.zig"); const builtin = @import("builtin"); const logger = @import("logger.zig"); const error_set = @import("error_set.zig"); const VkError = error_set.VkError; const toVkResult = error_set.toVkResult; const Dispatchable = @import("Dispatchable.zig").Dispatchable; const NonDispatchable = @import("NonDispatchable.zig").NonDispatchable; const VulkanAllocator = @import("VulkanAllocator.zig"); const Instance = @import("Instance.zig"); const Device = @import("Device.zig"); const PhysicalDevice = @import("PhysicalDevice.zig"); const Queue = @import("Queue.zig"); const CommandBuffer = @import("CommandBuffer.zig"); const CommandPool = @import("CommandPool.zig"); const DeviceMemory = @import("DeviceMemory.zig"); const Fence = @import("Fence.zig"); fn entryPointNotFoundErrorLog(comptime scope: @Type(.enum_literal), name: []const u8) void { if (lib.getLogVerboseLevel() != .High) return; std.log.scoped(scope).err("Could not find function {s}", .{name}); } fn functionMapEntryPoint(comptime name: []const u8) struct { []const u8, vk.PfnVoidFunction } { const stroll_name = std.fmt.comptimePrint("stroll{s}", .{name[2..]}); return if (std.meta.hasFn(@This(), name)) .{ name, @as(vk.PfnVoidFunction, @ptrCast(&@field(@This(), name))) } else if (std.meta.hasFn(@This(), stroll_name)) .{ name, @as(vk.PfnVoidFunction, @ptrCast(&@field(@This(), stroll_name))) } else @compileError("Invalid entry point name"); } const icd_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{ functionMapEntryPoint("vk_icdGetInstanceProcAddr"), functionMapEntryPoint("vk_icdGetPhysicalDeviceProcAddr"), functionMapEntryPoint("vk_icdNegotiateLoaderICDInterfaceVersion"), }); const global_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{ functionMapEntryPoint("vkCreateInstance"), functionMapEntryPoint("vkGetInstanceProcAddr"), functionMapEntryPoint("vkEnumerateInstanceExtensionProperties"), functionMapEntryPoint("vkEnumerateInstanceVersion"), }); const instance_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{ functionMapEntryPoint("vkDestroyInstance"), functionMapEntryPoint("vkEnumeratePhysicalDevices"), functionMapEntryPoint("vkGetDeviceProcAddr"), }); const physical_device_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{ functionMapEntryPoint("vkCreateDevice"), functionMapEntryPoint("vkEnumerateDeviceExtensionProperties"), functionMapEntryPoint("vkGetPhysicalDeviceFormatProperties"), functionMapEntryPoint("vkGetPhysicalDeviceFeatures"), functionMapEntryPoint("vkGetPhysicalDeviceImageFormatProperties"), functionMapEntryPoint("vkGetPhysicalDeviceProperties"), functionMapEntryPoint("vkGetPhysicalDeviceMemoryProperties"), functionMapEntryPoint("vkGetPhysicalDeviceQueueFamilyProperties"), functionMapEntryPoint("vkGetPhysicalDeviceSparseImageFormatProperties"), }); const device_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{ functionMapEntryPoint("vkAllocateCommandBuffers"), functionMapEntryPoint("vkAllocateMemory"), functionMapEntryPoint("vkCreateCommandPool"), functionMapEntryPoint("vkCreateFence"), functionMapEntryPoint("vkDestroyCommandPool"), functionMapEntryPoint("vkDestroyFence"), functionMapEntryPoint("vkDestroyDevice"), functionMapEntryPoint("vkFreeMemory"), functionMapEntryPoint("vkGetDeviceQueue"), functionMapEntryPoint("vkGetFenceStatus"), functionMapEntryPoint("vkMapMemory"), functionMapEntryPoint("vkUnmapMemory"), functionMapEntryPoint("vkResetFences"), functionMapEntryPoint("vkQueueBindSparse"), functionMapEntryPoint("vkQueueSubmit"), functionMapEntryPoint("vkQueueWaitIdle"), functionMapEntryPoint("vkWaitForFences"), }); // ICD Interface ============================================================================================================================================= pub export fn stroll_icdNegotiateLoaderICDInterfaceVersion(p_version: *u32) callconv(vk.vulkan_call_conv) vk.Result { p_version.* = 7; return .success; } pub export fn vk_icdGetInstanceProcAddr(p_instance: vk.Instance, p_name: ?[*:0]const u8) callconv(vk.vulkan_call_conv) vk.PfnVoidFunction { if (p_name == null) return null; const name = std.mem.span(p_name.?); if (icd_pfn_map.get(name)) |pfn| return pfn; return vkGetInstanceProcAddr(p_instance, p_name); } pub export fn stroll_icdGetPhysicalDeviceProcAddr(_: vk.Instance, p_name: ?[*:0]const u8) callconv(vk.vulkan_call_conv) vk.PfnVoidFunction { if (p_name == null) return null; const name = std.mem.span(p_name.?); if (physical_device_pfn_map.get(name)) |pfn| return pfn; entryPointNotFoundErrorLog(.vk_icdGetPhysicalDeviceProcAddr, name); return null; } // Global functions ========================================================================================================================================== pub export fn vkGetInstanceProcAddr(p_instance: vk.Instance, p_name: ?[*:0]const u8) callconv(vk.vulkan_call_conv) vk.PfnVoidFunction { if (p_name == null) return null; const name = std.mem.span(p_name.?); if (global_pfn_map.get(name)) |pfn| return pfn; if (p_instance == .null_handle) { entryPointNotFoundErrorLog(.vkGetInstanceProcAddr, name); return null; } if (instance_pfn_map.get(name)) |pfn| return pfn; if (physical_device_pfn_map.get(name)) |pfn| return pfn; if (device_pfn_map.get(name)) |pfn| return pfn; entryPointNotFoundErrorLog(.vkGetInstanceProcAddr, name); return null; } pub export fn strollCreateInstance(p_info: ?*const vk.InstanceCreateInfo, callbacks: ?*const vk.AllocationCallbacks, p_instance: *vk.Instance) callconv(vk.vulkan_call_conv) vk.Result { const info = p_info orelse return .error_validation_failed; if (info.s_type != .instance_create_info) { return .error_validation_failed; } std.log.scoped(.vkCreateInstance).info("Creating VkInstance", .{}); logger.indent(); defer logger.unindent(); const allocator = VulkanAllocator.init(callbacks, .instance).allocator(); var instance: *lib.Instance = undefined; if (!builtin.is_test) { // Will call impl instead of interface as root refs the impl module instance = root.Instance.create(allocator, info) catch |err| return toVkResult(err); } instance.requestPhysicalDevices(allocator) catch |err| return toVkResult(err); p_instance.* = (Dispatchable(Instance).wrap(allocator, instance) catch |err| return toVkResult(err)).toVkHandle(vk.Instance); return .success; } pub export fn strollEnumerateInstanceExtensionProperties(p_layer_name: ?[*:0]const u8, property_count: *u32, properties: ?*vk.ExtensionProperties) callconv(vk.vulkan_call_conv) vk.Result { var name: ?[]const u8 = null; if (p_layer_name) |layer_name| { name = std.mem.span(layer_name); } Instance.enumerateExtensionProperties(name, property_count, properties) catch |err| return toVkResult(err); return .success; } pub export fn strollEnumerateInstanceVersion(version: *u32) callconv(vk.vulkan_call_conv) vk.Result { Instance.enumerateVersion(version) catch |err| return toVkResult(err); return .success; } // Instance functions ======================================================================================================================================== pub export fn strollDestroyInstance(p_instance: vk.Instance, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void { std.log.scoped(.vkDestroyInstance).info("Destroying VkInstance", .{}); logger.indent(); defer logger.unindent(); const allocator = VulkanAllocator.init(callbacks, .instance).allocator(); 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 { const self = Dispatchable(Instance).fromHandleObject(p_instance) catch |err| return toVkResult(err); count.* = @intCast(self.physical_devices.items.len); if (p_devices) |devices| { for (0..count.*) |i| { devices[i] = self.physical_devices.items[i].toVkHandle(vk.PhysicalDevice); } } return .success; } // Physical Device functions ================================================================================================================================= pub export fn strollCreateDevice(p_physical_device: vk.PhysicalDevice, p_info: ?*const vk.DeviceCreateInfo, callbacks: ?*const vk.AllocationCallbacks, p_device: *vk.Device) callconv(vk.vulkan_call_conv) vk.Result { const info = p_info orelse return .error_validation_failed; if (info.s_type != .device_create_info) { return .error_validation_failed; } const allocator = VulkanAllocator.init(callbacks, .device).allocator(); const physical_device = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch |err| return toVkResult(err); std.log.scoped(.vkCreateDevice).info("Creating VkDevice from {s}", .{physical_device.props.device_name}); logger.indent(); defer logger.unindent(); const device = physical_device.createDevice(allocator, info) catch |err| return toVkResult(err); p_device.* = (Dispatchable(Device).wrap(allocator, device) catch |err| return toVkResult(err)).toVkHandle(vk.Device); return .success; } pub export fn strollEnumerateDeviceExtensionProperties(p_physical_device: vk.PhysicalDevice, p_layer_name: ?[*:0]const u8, property_count: *u32, properties: ?*vk.ExtensionProperties) callconv(vk.vulkan_call_conv) vk.Result { var name: ?[]const u8 = null; if (p_layer_name) |layer_name| { name = std.mem.span(layer_name); } _ = p_physical_device; property_count.* = 0; _ = properties; return .success; } pub export fn strollGetPhysicalDeviceFormatProperties(p_physical_device: vk.PhysicalDevice, format: vk.Format, properties: *vk.FormatProperties) callconv(vk.vulkan_call_conv) void { const self = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch return; properties.* = self.getFormatProperties(format) catch return; } pub export fn strollGetPhysicalDeviceFeatures(p_physical_device: vk.PhysicalDevice, features: *vk.PhysicalDeviceFeatures) callconv(vk.vulkan_call_conv) void { const self = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch return; features.* = self.features; } pub export fn strollGetPhysicalDeviceImageFormatProperties(p_physical_device: vk.PhysicalDevice, format: vk.Format, image_type: vk.ImageType, tiling: vk.ImageTiling, usage: vk.ImageUsageFlags, flags: vk.ImageCreateFlags, properties: *vk.ImageFormatProperties) callconv(vk.vulkan_call_conv) vk.Result { const self = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch |err| return toVkResult(err); properties.* = self.getImageFormatProperties(format, image_type, tiling, usage, flags) catch |err| return toVkResult(err); return .success; } pub export fn strollGetPhysicalDeviceProperties(p_physical_device: vk.PhysicalDevice, properties: *vk.PhysicalDeviceProperties) callconv(vk.vulkan_call_conv) void { const self = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch return; properties.* = self.props; } pub export fn strollGetPhysicalDeviceMemoryProperties(p_physical_device: vk.PhysicalDevice, properties: *vk.PhysicalDeviceMemoryProperties) callconv(vk.vulkan_call_conv) void { const self = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch return; properties.* = self.mem_props; } pub export fn strollGetPhysicalDeviceQueueFamilyProperties(p_physical_device: vk.PhysicalDevice, count: *u32, properties: ?[*]vk.QueueFamilyProperties) callconv(vk.vulkan_call_conv) void { const self = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch return; count.* = @intCast(self.queue_family_props.items.len); if (properties) |props| { @memcpy(props[0..count.*], self.queue_family_props.items[0..count.*]); } } pub export fn strollGetPhysicalDeviceSparseImageFormatProperties( p_physical_device: vk.PhysicalDevice, format: vk.Format, image_type: vk.ImageType, samples: vk.SampleCountFlags, tiling: vk.ImageTiling, usage: vk.ImageUsageFlags, flags: vk.ImageCreateFlags, properties: *vk.SparseImageFormatProperties, ) callconv(vk.vulkan_call_conv) vk.Result { const self = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch |err| return toVkResult(err); properties.* = self.getSparseImageFormatProperties(format, image_type, samples, tiling, usage, flags) catch |err| return toVkResult(err); return .success; } // Device functions ========================================================================================================================================== pub export fn strollAllocateCommandBuffers(p_device: vk.Device, p_info: ?*const vk.CommandBufferAllocateInfo, p_cmds: [*]vk.CommandBuffer) callconv(vk.vulkan_call_conv) vk.Result { const info = p_info orelse return .error_validation_failed; if (info.s_type != .command_buffer_allocate_info) { return .error_validation_failed; } const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const pool = NonDispatchable(CommandPool).fromHandleObject(info.command_pool) catch |err| return toVkResult(err); const allocator = pool.host_allocator.allocator(); const cmds = device.allocateCommandBuffers(info) catch |err| return toVkResult(err); for (cmds[0..info.command_buffer_count], 0..) |cmd, i| { p_cmds[i] = (NonDispatchable(CommandBuffer).wrap(allocator, cmd) catch |err| return toVkResult(err)).toVkHandle(vk.CommandBuffer); } return .success; } pub export fn strollAllocateMemory(p_device: vk.Device, p_info: ?*const vk.MemoryAllocateInfo, callbacks: ?*const vk.AllocationCallbacks, p_memory: *vk.DeviceMemory) callconv(vk.vulkan_call_conv) vk.Result { const info = p_info orelse return .error_validation_failed; if (info.s_type != .memory_allocate_info) { return .error_validation_failed; } const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const device_memory = device.allocateMemory(allocator, info) catch |err| return toVkResult(err); p_memory.* = (NonDispatchable(DeviceMemory).wrap(allocator, device_memory) catch |err| return toVkResult(err)).toVkHandle(vk.DeviceMemory); return .success; } pub export fn strollCreateCommandPool(p_device: vk.Device, p_info: ?*const vk.CommandPoolCreateInfo, callbacks: ?*const vk.AllocationCallbacks, p_pool: *vk.CommandPool) callconv(vk.vulkan_call_conv) vk.Result { const info = p_info orelse return .error_validation_failed; if (info.s_type != .command_pool_create_info) { return .error_validation_failed; } const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const pool = device.createCommandPool(allocator, info) catch |err| return toVkResult(err); p_pool.* = (NonDispatchable(CommandPool).wrap(allocator, pool) catch |err| return toVkResult(err)).toVkHandle(vk.CommandPool); return .success; } pub export fn strollCreateFence(p_device: vk.Device, p_info: ?*const vk.FenceCreateInfo, callbacks: ?*const vk.AllocationCallbacks, p_fence: *vk.Fence) callconv(vk.vulkan_call_conv) vk.Result { const info = p_info orelse return .error_validation_failed; if (info.s_type != .fence_create_info) { return .error_validation_failed; } const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const fence = device.createFence(allocator, info) catch |err| return toVkResult(err); p_fence.* = (NonDispatchable(Fence).wrap(allocator, fence) catch |err| return toVkResult(err)).toVkHandle(vk.Fence); return .success; } pub export fn strollDestroyDevice(p_device: vk.Device, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void { const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const dispatchable = Dispatchable(Device).fromHandle(p_device) catch return; std.log.scoped(.vkDestroyDevice).info("Destroying VkDevice created from {s}", .{dispatchable.object.physical_device.props.device_name}); logger.indent(); defer logger.unindent(); dispatchable.object.destroy(allocator) catch return; dispatchable.destroy(allocator); } pub export fn strollDestroyCommandPool(p_device: vk.Device, p_pool: vk.CommandPool, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void { const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch return; const non_dispatchable_pool = NonDispatchable(CommandPool).fromHandle(p_pool) catch return; device.destroyCommandPool(allocator, non_dispatchable_pool.object) catch return; non_dispatchable_pool.destroy(allocator); } pub export fn strollDestroyFence(p_device: vk.Device, p_fence: vk.Fence, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void { const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch return; const non_dispatchable_fence = NonDispatchable(Fence).fromHandle(p_fence) catch return; device.destroyFence(allocator, non_dispatchable_fence.object) catch return; non_dispatchable_fence.destroy(allocator); } pub export fn strollFreeMemory(p_device: vk.Device, p_memory: vk.DeviceMemory, callbacks: ?*const vk.AllocationCallbacks) callconv(vk.vulkan_call_conv) void { const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch return; const non_dispatchable_device_memory = NonDispatchable(DeviceMemory).fromHandle(p_memory) catch return; device.freeMemory(allocator, non_dispatchable_device_memory.object) catch return; non_dispatchable_device_memory.destroy(allocator); } pub export fn strollGetDeviceProcAddr(p_device: vk.Device, p_name: ?[*:0]const u8) callconv(vk.vulkan_call_conv) vk.PfnVoidFunction { if (p_name == null) return null; const name = std.mem.span(p_name.?); if (p_device == .null_handle) return null; if (device_pfn_map.get(name)) |pfn| return pfn; entryPointNotFoundErrorLog(.vkGetDeviceProcAddr, name); 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); device.getFenceStatus(fence) catch |err| return toVkResult(err); return .success; } pub export fn strollMapMemory(p_device: vk.Device, p_memory: vk.DeviceMemory, offset: vk.DeviceSize, size: vk.DeviceSize, _: vk.MemoryMapFlags, pp_data: *?*anyopaque) callconv(vk.vulkan_call_conv) vk.Result { const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const device_memory = NonDispatchable(DeviceMemory).fromHandleObject(p_memory) catch |err| return toVkResult(err); pp_data.* = device.mapMemory(device_memory, offset, size) catch |err| return toVkResult(err); return .success; } pub export fn strollUnmapMemory(p_device: vk.Device, p_memory: vk.DeviceMemory) callconv(vk.vulkan_call_conv) void { const device = Dispatchable(Device).fromHandleObject(p_device) catch return; const device_memory = NonDispatchable(DeviceMemory).fromHandleObject(p_memory) catch return; device.unmapMemory(device_memory); } pub export fn strollResetFences(p_device: vk.Device, count: u32, p_fences: [*]const vk.Fence) callconv(vk.vulkan_call_conv) vk.Result { const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const allocator = std.heap.c_allocator; const fences: []*Fence = allocator.alloc(*Fence, count) catch return .error_unknown; defer allocator.free(fences); for (p_fences, 0..count) |fence, i| { fences[i] = NonDispatchable(Fence).fromHandleObject(fence) catch |err| return toVkResult(err); } device.resetFences(fences) catch |err| return toVkResult(err); return .success; } pub export fn strollQueueBindSparse(p_queue: vk.Queue, count: u32, info: [*]vk.BindSparseInfo, p_fence: vk.Fence) callconv(vk.vulkan_call_conv) vk.Result { const queue = Dispatchable(Queue).fromHandleObject(p_queue) catch |err| return toVkResult(err); const fence = if (p_fence != .null_handle) NonDispatchable(Fence).fromHandleObject(p_fence) catch |err| return toVkResult(err) else null; queue.bindSparse(info[0..count], fence) catch |err| return toVkResult(err); return .success; } pub export fn strollQueueSubmit(p_queue: vk.Queue, count: u32, info: [*]const vk.SubmitInfo, p_fence: vk.Fence) callconv(vk.vulkan_call_conv) vk.Result { const queue = Dispatchable(Queue).fromHandleObject(p_queue) catch |err| return toVkResult(err); const fence = if (p_fence != .null_handle) NonDispatchable(Fence).fromHandleObject(p_fence) catch |err| return toVkResult(err) else null; queue.submit(info[0..count], fence) catch |err| return toVkResult(err); return .success; } pub export fn strollQueueWaitIdle(p_queue: vk.Queue) callconv(vk.vulkan_call_conv) vk.Result { const queue = Dispatchable(Queue).fromHandleObject(p_queue) catch |err| return toVkResult(err); queue.waitIdle() catch |err| return toVkResult(err); return .success; } pub export fn strollWaitForFences(p_device: vk.Device, count: u32, p_fences: [*]const vk.Fence, waitForAll: vk.Bool32, timeout: u64) callconv(vk.vulkan_call_conv) vk.Result { const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const allocator = std.heap.c_allocator; const fences: []*Fence = allocator.alloc(*Fence, count) catch return .error_unknown; defer allocator.free(fences); for (p_fences, 0..count) |fence, i| { fences[i] = NonDispatchable(Fence).fromHandleObject(fence) catch |err| return toVkResult(err); } device.waitForFences(fences, (waitForAll == .true), timeout) catch |err| return toVkResult(err); return .success; }