From a8d9f3b3337529d757db350bc35694ef61408cbb Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Mon, 8 Jun 2026 14:55:41 +0200 Subject: [PATCH] adding fragment derivatives, base pipeline cache, missing KHR function to avoid CTS crash + lots of minor bugfixes --- README.md | 6 +- build.zig.zon | 4 +- src/soft/SoftCommandBuffer.zig | 8 +- src/soft/SoftDescriptorPool.zig | 8 +- src/soft/SoftDevice.zig | 10 +- src/soft/SoftInstance.zig | 12 +- src/soft/SoftPhysicalDevice.zig | 14 +- src/soft/SoftPipeline.zig | 10 +- src/soft/SoftQueue.zig | 23 ++- src/soft/SoftShaderModule.zig | 15 +- src/soft/device/blitter.zig | 14 ++ src/soft/device/fragment.zig | 50 +++++- src/soft/device/rasterizer.zig | 4 +- src/soft/device/rasterizer/bresenham.zig | 4 +- src/soft/device/rasterizer/common.zig | 52 ++++++ src/soft/device/rasterizer/edge_function.zig | 46 ++++- src/soft/device/vertex_dispatcher.zig | 28 +++- src/soft/lib.zig | 1 + src/vulkan/CommandBuffer.zig | 28 ++-- src/vulkan/CommandPool.zig | 25 +-- src/vulkan/Device.zig | 16 +- src/vulkan/Image.zig | 2 +- src/vulkan/Instance.zig | 54 +++++- src/vulkan/PhysicalDevice.zig | 38 +++++ src/vulkan/Pipeline.zig | 47 ++++-- src/vulkan/PipelineCache.zig | 48 +++++- src/vulkan/lib.zig | 4 +- src/vulkan/lib_vulkan.zig | 168 ++++++++++++++----- src/vulkan/utils.zig | 26 +++ src/vulkan/wsi/SwapchainKHR.zig | 4 +- 30 files changed, 625 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index 9d8ba4e..8346e3f 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ vkCreatePipelineLayout | ✅ Implemented vkCreateQueryPool | ✅ Implemented vkCreateRenderPass | ✅ Implemented vkCreateSampler | ✅ Implemented -vkCreateSemaphore | ⚙️ WIP +vkCreateSemaphore | ✅ Implemented vkCreateShaderModule | ✅ Implemented vkCreateSwapchainKHR | ✅ Implemented vkCreateWaylandSurfaceKHR | ✅ Implemented @@ -149,6 +149,8 @@ vkEnumerateDeviceLayerProperties | ✅ Implemented vkEnumerateInstanceExtensionProperties | ✅ Implemented vkEnumerateInstanceLayerProperties | ✅ Implemented vkEnumeratePhysicalDevices | ✅ Implemented +vkEnumeratePhysicalDeviceGroups | ✅ Implemented +vkEnumeratePhysicalDeviceGroupsKHR | ✅ Implemented vkFlushMappedMemoryRanges | ✅ Implemented vkFreeCommandBuffers | ✅ Implemented vkFreeDescriptorSets | ✅ Implemented @@ -179,7 +181,7 @@ vkGetPhysicalDeviceWin32PresentationSupportKHR | ⚙️ WIP vkGetPhysicalDeviceXcbPresentationSupportKHR | ⚙️ WIP vkGetPhysicalDeviceXlibPresentationSupportKHR | ⚙️ WIP vkGetPipelineCacheData | ⚙️ WIP -vkGetQueryPoolResults | ⚙️ WIP +vkGetQueryPoolResults | ✅ Implemented vkGetRenderAreaGranularity | ✅ Implemented vkGetSwapchainImagesKHR | ✅ Implemented vkInvalidateMappedMemoryRanges | ✅ Implemented diff --git a/build.zig.zon b/build.zig.zon index 1a97d42..5a9449f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -26,8 +26,8 @@ .hash = "N-V-__8AAF9uOh0I4P_99za7N822J3JwsDaqONrFVrcEQo59", }, .SPIRV_Interpreter = .{ - .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#7c6da62e3cc6e58260527abef6e2e161e45d7f84", - .hash = "SPIRV_Interpreter-0.0.1-ajmpnzALBgCxqYfFMsc_moezDHsaeKyImzHXuJ7dYffX", + .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#9270d83464afc32c8ea3dd89931056421046df5b", + .hash = "SPIRV_Interpreter-0.0.1-ajmpn_V1BgDlmfIb9PHyP2cij9m63vE-Nx0o4mFsEd9L", .lazy = true, }, //.SPIRV_Interpreter = .{ diff --git a/src/soft/SoftCommandBuffer.zig b/src/soft/SoftCommandBuffer.zig index 934f392..556b81c 100644 --- a/src/soft/SoftCommandBuffer.zig +++ b/src/soft/SoftCommandBuffer.zig @@ -104,8 +104,8 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { allocator.destroy(self); } -pub fn execute(self: *Self, device: *ExecutionDevice) void { - self.interface.submit() catch return; +pub fn execute(self: *Self, device: *ExecutionDevice) VkError!void { + try self.interface.submit(); defer self.interface.finish() catch {}; for (self.commands.items) |command| { @@ -116,7 +116,7 @@ pub fn execute(self: *Self, device: *ExecutionDevice) void { std.debug.dumpErrorReturnTrace(trace); } } - return; // Should we return or continue ? Maybe device lost ? + return VkError.DeviceLost; }; } } @@ -988,7 +988,7 @@ pub fn executeCommands(interface: *Interface, commands: *Interface) VkError!void pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { const impl: *Impl = @ptrCast(@alignCast(context)); - impl.cmd.execute(device); + try impl.cmd.execute(device); } }; diff --git a/src/soft/SoftDescriptorPool.zig b/src/soft/SoftDescriptorPool.zig index 013a516..7eec0de 100644 --- a/src/soft/SoftDescriptorPool.zig +++ b/src/soft/SoftDescriptorPool.zig @@ -46,6 +46,10 @@ pub fn allocateDescriptorSet(interface: *Interface, layout: *base.DescriptorSetL pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const set_allocator = VulkanAllocator.init(null, .object).allocator(); + for (self.list.items) |set| { + set.interface.destroy(set_allocator); + } self.list.deinit(allocator); allocator.destroy(self); } @@ -59,7 +63,7 @@ pub fn freeDescriptorSet(interface: *Interface, set: *base.DescriptorSet) VkErro } const allocator = VulkanAllocator.init(null, .object).allocator(); - allocator.destroy(soft_set); + set.destroy(allocator); } pub fn reset(interface: *Interface, _: vk.DescriptorPoolResetFlags) VkError!void { @@ -67,7 +71,7 @@ pub fn reset(interface: *Interface, _: vk.DescriptorPoolResetFlags) VkError!void const allocator = VulkanAllocator.init(null, .object).allocator(); for (self.list.items) |set| { - allocator.destroy(set); + set.interface.destroy(allocator); } self.list.clearRetainingCapacity(); } diff --git a/src/soft/SoftDevice.zig b/src/soft/SoftDevice.zig index cce585b..aa55f8a 100644 --- a/src/soft/SoftDevice.zig +++ b/src/soft/SoftDevice.zig @@ -46,7 +46,14 @@ device_allocator: if (config.debug_allocator) std.heap.DebugAllocator(.{}) else pub fn create(instance: *base.Instance, physical_device: *base.PhysicalDevice, allocator: std.mem.Allocator, info: *const vk.DeviceCreateInfo) VkError!*Self { const self = allocator.create(Self) catch return VkError.OutOfHostMemory; - errdefer allocator.destroy(self); + var initialized = false; + errdefer { + if (initialized) { + self.interface.destroy(allocator) catch {}; + } else { + allocator.destroy(self); + } + } var interface = try Interface.init(allocator, instance, physical_device, info); @@ -83,6 +90,7 @@ pub fn create(instance: *base.Instance, physical_device: *base.PhysicalDevice, a .interface = interface, .device_allocator = if (config.debug_allocator) .init else .{}, }; + initialized = true; try self.interface.createQueues(allocator, info); return self; diff --git a/src/soft/SoftInstance.zig b/src/soft/SoftInstance.zig index 3bd62d0..887455d 100644 --- a/src/soft/SoftInstance.zig +++ b/src/soft/SoftInstance.zig @@ -25,6 +25,7 @@ fn castExtension(comptime ext: vk.ApiInfo) vk.ExtensionProperties { } pub const EXTENSIONS = [_]vk.ExtensionProperties{ + castExtension(vk.extensions.khr_device_group_creation), castExtension(vk.extensions.khr_get_physical_device_properties_2), castExtension(vk.extensions.khr_surface), castExtension(vk.extensions.khr_wayland_surface), @@ -60,13 +61,16 @@ fn requestPhysicalDevices(interface: *Interface, allocator: std.mem.Allocator) V // Software driver has only one physical device (the CPU) const physical_device = try SoftPhysicalDevice.create(allocator, interface); errdefer physical_device.interface.releasePhysicalDevice(allocator) catch {}; - interface.physical_devices.append(allocator, try Dispatchable(base.PhysicalDevice).wrap(allocator, &physical_device.interface)) catch return VkError.OutOfHostMemory; + const dispatchable = try Dispatchable(base.PhysicalDevice).wrap(allocator, &physical_device.interface); + errdefer dispatchable.destroy(allocator); + interface.physical_devices.append(allocator, dispatchable) catch return VkError.OutOfHostMemory; } fn releasePhysicalDevices(interface: *Interface, allocator: std.mem.Allocator) VkError!void { - const physical_device = interface.physical_devices.getLast(); - try physical_device.object.releasePhysicalDevice(allocator); - physical_device.destroy(allocator); + for (interface.physical_devices.items) |physical_device| { + try physical_device.object.releasePhysicalDevice(allocator); + physical_device.destroy(allocator); + } interface.physical_devices.deinit(allocator); interface.physical_devices = .empty; diff --git a/src/soft/SoftPhysicalDevice.zig b/src/soft/SoftPhysicalDevice.zig index 919433a..b9d130d 100644 --- a/src/soft/SoftPhysicalDevice.zig +++ b/src/soft/SoftPhysicalDevice.zig @@ -22,7 +22,7 @@ fn castExtension(comptime ext: vk.ApiInfo) vk.ExtensionProperties { return props; } -const EXTENSIONS = [_]vk.ExtensionProperties{ +pub const EXTENSIONS = [_]vk.ExtensionProperties{ castExtension(vk.extensions.khr_swapchain), }; @@ -59,6 +59,7 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance) VkError!*S interface.props.driver_version = @bitCast(lib.DRIVER_VERSION); interface.props.device_id = lib.DEVICE_ID; interface.props.device_type = .cpu; + interface.props.pipeline_cache_uuid = lib.PIPELINE_CACHE_UUID; interface.props.limits = .{ .max_image_dimension_1d = 4096, .max_image_dimension_2d = 4096, @@ -203,7 +204,6 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance) VkError!*S if (device_name[0] == 0) { const name = blk: { - // If arch is x86 we try to get precise CPU name through CPUID // and fallback to vendor name if not available if (comptime builtin.cpu.arch.isX86()) { @@ -251,6 +251,7 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance) VkError!*S self.* = .{ .interface = interface, }; + return self; } @@ -274,11 +275,16 @@ pub fn enumerateExtensionProperties(_: *const Interface, layer_name: ?[]const u8 return VkError.LayerNotPresent; } - count.* = EXTENSIONS.len; + const available = EXTENSIONS.len; if (p_properties) |properties| { - for (EXTENSIONS, properties[0..]) |ext, *prop| { + const write_count = @min(count.*, available); + for (EXTENSIONS[0..write_count], properties[0..write_count]) |ext, *prop| { prop.* = ext; } + count.* = @intCast(write_count); + if (write_count < available) return VkError.Incomplete; + } else { + count.* = @intCast(available); } } diff --git a/src/soft/SoftPipeline.zig b/src/soft/SoftPipeline.zig index 311997b..c362e79 100644 --- a/src/soft/SoftPipeline.zig +++ b/src/soft/SoftPipeline.zig @@ -52,7 +52,8 @@ stages: std.EnumMap(Stages, Shader), pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.ComputePipelineCreateInfo) VkError!*Self { const self = allocator.create(Self) catch return VkError.OutOfHostMemory; - errdefer allocator.destroy(self); + var initialized = false; + errdefer if (initialized) self.interface.destroy(allocator) else allocator.destroy(self); var interface = try Interface.initCompute(device, allocator, cache, info); @@ -71,7 +72,7 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: .runtimes_allocator = .init(device_allocator), .stages = std.EnumMap(Stages, Shader).init(.{}), }; - errdefer self.runtimes_allocator.deinit(); + initialized = true; const runtimes_allocator = self.runtimes_allocator.allocator(); const instance: *SoftInstance = @alignCast(@fieldParentPtr("interface", device.instance)); @@ -138,7 +139,8 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.GraphicsPipelineCreateInfo) VkError!*Self { const self = allocator.create(Self) catch return VkError.OutOfHostMemory; - errdefer allocator.destroy(self); + var initialized = false; + errdefer if (initialized) self.interface.destroy(allocator) else allocator.destroy(self); var interface = try Interface.initGraphics(device, allocator, cache, info); @@ -154,7 +156,7 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache: .runtimes_allocator = .init(device_allocator), .stages = std.EnumMap(Stages, Shader).init(.{}), }; - errdefer self.runtimes_allocator.deinit(); + initialized = true; const runtimes_allocator = self.runtimes_allocator.allocator(); const instance: *SoftInstance = @alignCast(@fieldParentPtr("interface", device.instance)); diff --git a/src/soft/SoftQueue.zig b/src/soft/SoftQueue.zig index 1a60687..152cc6f 100644 --- a/src/soft/SoftQueue.zig +++ b/src/soft/SoftQueue.zig @@ -68,11 +68,11 @@ pub fn bindSparse(interface: *Interface, info: []const vk.BindSparseInfo, fence: pub fn submit(interface: *Interface, infos: []Interface.SubmitInfo, p_fence: ?*base.Fence) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", interface.owner)); - const allocator = soft_device.device_allocator.allocator(); const io = interface.owner.io(); + const allocator = soft_device.device_allocator.allocator(); - const task_data = allocator.create(TaskData) catch return VkError.OutOfDeviceMemory; - errdefer allocator.destroy(task_data); + const data = allocator.create(TaskData) catch return VkError.OutOfDeviceMemory; + errdefer allocator.destroy(data); var cloned_infos = try cloneSubmitInfos(allocator, infos); errdefer deinitSubmitInfos(allocator, &cloned_infos); @@ -87,7 +87,7 @@ pub fn submit(interface: *Interface, infos: []Interface.SubmitInfo, p_fence: ?*b break :blk seq; }; - task_data.* = .{ + data.* = .{ .queue = self, .soft_device = soft_device, .sequence = sequence, @@ -95,7 +95,7 @@ pub fn submit(interface: *Interface, infos: []Interface.SubmitInfo, p_fence: ?*b .fence = p_fence, }; - self.group.async(io, Self.taskRunner, .{task_data}); + self.group.async(io, taskRunner, .{data}); } pub fn waitIdle(interface: *Interface) VkError!void { @@ -115,7 +115,7 @@ fn executeSubmitInfo(soft_device: *SoftDevice, info: Interface.SubmitInfo) VkErr for (info.command_buffers.items) |command_buffer| { const soft_command_buffer: *SoftCommandBuffer = @alignCast(@fieldParentPtr("interface", command_buffer)); - soft_command_buffer.execute(&execution_device); + try soft_command_buffer.execute(&execution_device); } for (info.signal_semaphores.items) |semaphore| { @@ -126,6 +126,7 @@ fn executeSubmitInfo(soft_device: *SoftDevice, info: Interface.SubmitInfo) VkErr fn taskRunner(data: *TaskData) void { const io = data.queue.interface.owner.io(); const allocator = data.soft_device.device_allocator.allocator(); + defer { deinitSubmitInfos(allocator, &data.infos); allocator.destroy(data); @@ -140,18 +141,13 @@ fn taskRunner(data: *TaskData) void { } } - var submit_err: ?VkError = null; for (data.infos.items) |info| { executeSubmitInfo(data.soft_device, info) catch |err| { - submit_err = err; + std.log.scoped(.SoftQueue).err("Command buffer execution failed with '{s}'", .{@errorName(err)}); break; }; } - if (submit_err) |err| { - std.log.scoped(.SoftQueue).err("Queue submit failed with '{s}'", .{@errorName(err)}); - } - if (data.fence) |fence| { fence.signal() catch |err| { std.log.scoped(.SoftQueue).err("Queue submit fence signal failed with '{s}'", .{@errorName(err)}); @@ -159,9 +155,10 @@ fn taskRunner(data: *TaskData) void { } data.queue.mutex.lock(io) catch return; + defer data.queue.mutex.unlock(io); + data.queue.executing_sequence += 1; data.queue.condition.broadcast(io); - data.queue.mutex.unlock(io); } fn cloneSubmitInfos(allocator: std.mem.Allocator, infos: []Interface.SubmitInfo) VkError!std.ArrayList(Interface.SubmitInfo) { diff --git a/src/soft/SoftShaderModule.zig b/src/soft/SoftShaderModule.zig index c93e466..2df9fe2 100644 --- a/src/soft/SoftShaderModule.zig +++ b/src/soft/SoftShaderModule.zig @@ -14,7 +14,6 @@ pub const Interface = base.ShaderModule; interface: Interface, module: spv.Module, -module_allocator: std.heap.ArenaAllocator, /// Pipelines need SPIR-V module reference so shader module may not /// be destroy on call to `vkDestroyShaderModule` @@ -29,10 +28,6 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", device)); const device_allocator = soft_device.device_allocator.allocator(); - var module_allocator_arena: std.heap.ArenaAllocator = .init(device_allocator); - errdefer module_allocator_arena.deinit(); - const module_allocator = module_allocator_arena.allocator(); - interface.vtable = &.{ .destroy = destroy, }; @@ -41,7 +36,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v self.* = .{ .interface = interface, - .module = spv.Module.init(module_allocator, code, .{ + .module = spv.Module.init(device_allocator, code, .{ .use_simd_vectors_specializations = base.config.shaders_simd, }) catch |err| switch (err) { spv.Module.ModuleError.OutOfMemory => return VkError.OutOfHostMemory, @@ -55,9 +50,9 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v return VkError.ValidationFailed; }, }, - .module_allocator = module_allocator_arena, .ref_count = std.atomic.Value(usize).init(1), }; + return self; } @@ -67,7 +62,11 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { } pub fn drop(self: *Self, allocator: std.mem.Allocator) void { - self.module_allocator.deinit(); + const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", self.interface.owner)); + const device_allocator = soft_device.device_allocator.allocator(); + + self.module.deinit(device_allocator); + allocator.destroy(self); } diff --git a/src/soft/device/blitter.zig b/src/soft/device/blitter.zig index f0a6194..fd40ef1 100644 --- a/src/soft/device/blitter.zig +++ b/src/soft/device/blitter.zig @@ -683,6 +683,12 @@ pub fn readFloat4(map: []const u8, src_format: vk.Format) F32x4 { c[1] = std.mem.bytesToValue(f32, map[4..]); }, + .r32g32b32_sfloat => { + c[0] = std.mem.bytesToValue(f32, map[0..]); + c[1] = std.mem.bytesToValue(f32, map[4..]); + c[2] = std.mem.bytesToValue(f32, map[8..]); + }, + .d32_sfloat, .r32_sfloat, => c[0] = std.mem.bytesToValue(f32, map), @@ -1190,6 +1196,14 @@ pub fn readInt4(map: []const u8, src_format: vk.Format) U32x4 { c[1] = std.mem.bytesToValue(u32, map[4..]); }, + .r32g32b32_sint, + .r32g32b32_uint, + => { + c[0] = std.mem.bytesToValue(u32, map[0..]); + c[1] = std.mem.bytesToValue(u32, map[4..]); + c[2] = std.mem.bytesToValue(u32, map[8..]); + }, + .r8g8b8a8_uint, => { c[0] = map[0]; diff --git a/src/soft/device/fragment.zig b/src/soft/device/fragment.zig index 3ac84e7..3da7fc8 100644 --- a/src/soft/device/fragment.zig +++ b/src/soft/device/fragment.zig @@ -13,15 +13,51 @@ const VkError = base.VkError; const SpvRuntimeError = spv.Runtime.RuntimeError; const INTERFACE_BLOB_PADDING = @sizeOf(zm.F32x4); +pub const DerivativeInputs = struct { + dx: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation, + dy: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation, +}; + +pub fn shaderUsesDerivatives(code: []const spv.SpvWord) bool { + var i: usize = 5; + while (i < code.len) { + const opcode_data = code[i]; + const word_count = (opcode_data & (~spv.spv.SpvOpCodeMask)) >> spv.spv.SpvWordCountShift; + if (word_count == 0) + return false; + + const opcode = opcode_data & spv.spv.SpvOpCodeMask; + switch (opcode) { + @intFromEnum(spv.spv.SpvOp.DPdx), + @intFromEnum(spv.spv.SpvOp.DPdy), + @intFromEnum(spv.spv.SpvOp.DPdxFine), + @intFromEnum(spv.spv.SpvOp.DPdyFine), + @intFromEnum(spv.spv.SpvOp.DPdxCoarse), + @intFromEnum(spv.spv.SpvOp.DPdyCoarse), + => return true, + else => {}, + } + + i += word_count; + } + return false; +} + pub fn shaderInvocation( allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, batch_id: usize, position: zm.F32x4, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation, + derivative_inputs: ?DerivativeInputs, ) SpvRuntimeError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 { var fragment_inputs = inputs; errdefer freeOwnedInputs(allocator, fragment_inputs); + const derivatives = derivative_inputs; + errdefer if (derivatives) |owned_derivatives| { + freeOwnedInputs(allocator, owned_derivatives.dx); + freeOwnedInputs(allocator, owned_derivatives.dy); + }; const io = draw_call.renderer.device.interface.io(); @@ -66,6 +102,13 @@ pub fn shaderInvocation( if (input.blob.len != 0) { try rt.writeInput(input.blob, result_word); + if (derivatives) |derivative| { + const dx = derivative.dx[location][component]; + const dy = derivative.dy[location][component]; + if (dx.blob.len != 0 and dy.blob.len != 0) { + try rt.setDerivativeFromMemory(allocator, result_word, dx.blob, dy.blob); + } + } } } } @@ -81,8 +124,7 @@ pub fn shaderInvocation( else => return err, }; - var outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 = undefined; - @memset(std.mem.asBytes(&outputs), 0); + var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8); for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { var has_split_components = false; @@ -117,6 +159,10 @@ pub fn shaderInvocation( try rt.flushDescriptorSets(allocator); freeOwnedInputs(allocator, fragment_inputs); + if (derivatives) |owned_derivatives| { + freeOwnedInputs(allocator, owned_derivatives.dx); + freeOwnedInputs(allocator, owned_derivatives.dy); + } return outputs; } diff --git a/src/soft/device/rasterizer.zig b/src/soft/device/rasterizer.zig index 7041782..27fcc6f 100644 --- a/src/soft/device/rasterizer.zig +++ b/src/soft/device/rasterizer.zig @@ -304,8 +304,7 @@ fn clipTransformAndRasterizePoint( if (!common.scissorContainsPixel(draw_call.scissor, px, py)) continue; - var outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 = undefined; - @memset(std.mem.asBytes(&outputs), 0); + var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8); if (has_fragment_shader) { outputs = fragment.shaderInvocation( allocator, @@ -313,6 +312,7 @@ fn clipTransformAndRasterizePoint( 0, zm.f32x4(@floatFromInt(px), @floatFromInt(py), transformed.position[2], 1.0), try common.interpolateVertexOutputs(allocator, &transformed, &transformed, &transformed, 1.0, 0.0, 0.0), + null, ) catch |err| { std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)}); if (comptime base.config.logs == .verbose) { diff --git a/src/soft/device/rasterizer/bresenham.zig b/src/soft/device/rasterizer/bresenham.zig index 5b9e5a4..1c18d83 100644 --- a/src/soft/device/rasterizer/bresenham.zig +++ b/src/soft/device/rasterizer/bresenham.zig @@ -152,8 +152,7 @@ inline fn run(data: RunData) !void { const t = @as(f32, @floatFromInt(step)) / @as(f32, @floatFromInt(@max(data.d_x, 1))); const z = ((1.0 - t) * data.start_vertex.position[2]) + (t * data.end_vertex.position[2]); - var outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8 = undefined; - @memset(std.mem.asBytes(&outputs), 0); + var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8); if (data.has_fragment_shader) { outputs = fragment.shaderInvocation( data.allocator, @@ -161,6 +160,7 @@ inline fn run(data: RunData) !void { data.batch_id, zm.f32x4(@floatFromInt(pixel_x), @floatFromInt(pixel_y), z, 1.0), try common.interpolateLineOutputs(data.allocator, data.start_vertex, data.end_vertex, t), + null, ) catch |err| { std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)}); if (comptime base.config.logs == .verbose) { diff --git a/src/soft/device/rasterizer/common.zig b/src/soft/device/rasterizer/common.zig index 812ee31..404619b 100644 --- a/src/soft/device/rasterizer/common.zig +++ b/src/soft/device/rasterizer/common.zig @@ -226,6 +226,58 @@ pub fn interpolateLineOutputs( return interpolateVertexOutputs(allocator, v0, v1, v0, 1.0 - t, t, 0.0); } +pub fn interpolateVertexOutputDerivatives( + allocator: std.mem.Allocator, + v0: *const Renderer.Vertex, + v1: *const Renderer.Vertex, + v2: *const Renderer.Vertex, + db0: f32, + db1: f32, + db2: f32, +) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation { + var inputs = [_]VertexInterpolationLocation{[_]VertexInterpolation{.{ + .blob = &.{}, + .size = 0, + .free_responsability = false, + }} ** 4} ** spv.SPIRV_MAX_OUTPUT_LOCATIONS; + + for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { + for (0..4) |component| { + const out0 = v0.outputs[location][component] orelse continue; + const out1 = v1.outputs[location][component] orelse continue; + const out2 = v2.outputs[location][component] orelse continue; + + const len = @min(out0.size, out1.size, out2.size); + if (len == 0) + continue; + + const input = allocator.alloc(u8, len + @sizeOf(F32x4)) catch return VkError.OutOfDeviceMemory; + @memset(input, 0); + + if (out0.interpolation_type != .flat) { + var byte_index: usize = 0; + while (byte_index + @sizeOf(F32x4) <= len) : (byte_index += @sizeOf(F32x4)) { + const value0 = std.mem.bytesToValue(F32x4, out0.blob[byte_index..]); + const value1 = std.mem.bytesToValue(F32x4, out1.blob[byte_index..]); + const value2 = std.mem.bytesToValue(F32x4, out2.blob[byte_index..]); + base.utils.writePacked(F32x4, input[byte_index..], interpolateF32x4(value0, value1, value2, db0, db1, db2)); + } + + while (byte_index + @sizeOf(f32) <= len) : (byte_index += @sizeOf(f32)) { + const value0 = std.mem.bytesToValue(f32, out0.blob[byte_index..]); + const value1 = std.mem.bytesToValue(f32, out1.blob[byte_index..]); + const value2 = std.mem.bytesToValue(f32, out2.blob[byte_index..]); + base.utils.writePacked(f32, input[byte_index..], (value0 * db0) + (value1 * db1) + (value2 * db2)); + } + } + + inputs[location][component] = .{ .blob = input, .size = len, .free_responsability = true }; + } + } + + return inputs; +} + inline fn interpolateF32x4(value0: F32x4, value1: F32x4, value2: F32x4, b0: f32, b1: f32, b2: f32) F32x4 { return (value0 * zm.f32x4s(b0)) + (value1 * zm.f32x4s(b1)) + (value2 * zm.f32x4s(b2)); } diff --git a/src/soft/device/rasterizer/edge_function.zig b/src/soft/device/rasterizer/edge_function.zig index cb8b672..fc23e40 100644 --- a/src/soft/device/rasterizer/edge_function.zig +++ b/src/soft/device/rasterizer/edge_function.zig @@ -30,6 +30,7 @@ const RunData = struct { stencil_attachment_access: ?*common.RenderTargetAccess, front_face: bool, has_fragment_shader: bool, + fragment_uses_derivatives: bool, }; pub fn drawTriangle( @@ -56,6 +57,10 @@ pub fn drawTriangle( const pipeline = draw_call.renderer.state.pipeline orelse return; const fragment_stage = pipeline.stages.getPtr(.fragment); + const fragment_uses_derivatives = if (fragment_stage) |stage| + fragment.shaderUsesDerivatives(stage.module.module.code) + else + false; const runtimes_count = if (fragment_stage) |stage| stage.runtimes.len else 1; if (runtimes_count == 0) return; @@ -106,6 +111,7 @@ pub fn drawTriangle( .stencil_attachment_access = stencil_attachment_access, .front_face = front_face, .has_fragment_shader = fragment_stage != null, + .fragment_uses_derivatives = fragment_uses_derivatives, }; draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data}); @@ -171,15 +177,49 @@ inline fn run(data: RunData) !void { const b2 = w2 / data.area; const z = (b0 * data.v0.position[2]) + (b1 * data.v1.position[2]) + (b2 * data.v2.position[2]); - var outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8 = undefined; - @memset(std.mem.asBytes(&outputs), 0); + var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8); if (data.has_fragment_shader) { + const inputs = try common.interpolateVertexOutputs(data.allocator, &data.v0, &data.v1, &data.v2, b0, b1, b2); + const derivative_inputs: ?fragment.DerivativeInputs = if (data.fragment_uses_derivatives) blk: { + var derivatives: fragment.DerivativeInputs = undefined; + + const p_dx = zm.f32x4(@as(f32, @floatFromInt(x)) + 1.5, @as(f32, @floatFromInt(y)) + 0.5, 0.0, 1.0); + const dx_w0 = edgeFunction(data.v1.position, data.v2.position, p_dx); + const dx_w1 = edgeFunction(data.v2.position, data.v0.position, p_dx); + const dx_w2 = edgeFunction(data.v0.position, data.v1.position, p_dx); + derivatives.dx = try common.interpolateVertexOutputDerivatives( + data.allocator, + &data.v0, + &data.v1, + &data.v2, + (dx_w0 / data.area) - b0, + (dx_w1 / data.area) - b1, + (dx_w2 / data.area) - b2, + ); + + const p_dy = zm.f32x4(@as(f32, @floatFromInt(x)) + 0.5, @as(f32, @floatFromInt(y)) + 1.5, 0.0, 1.0); + const dy_w0 = edgeFunction(data.v1.position, data.v2.position, p_dy); + const dy_w1 = edgeFunction(data.v2.position, data.v0.position, p_dy); + const dy_w2 = edgeFunction(data.v0.position, data.v1.position, p_dy); + derivatives.dy = try common.interpolateVertexOutputDerivatives( + data.allocator, + &data.v0, + &data.v1, + &data.v2, + (dy_w0 / data.area) - b0, + (dy_w1 / data.area) - b1, + (dy_w2 / data.area) - b2, + ); + break :blk derivatives; + } else null; + outputs = fragment.shaderInvocation( data.allocator, data.draw_call, data.batch_id, zm.f32x4(@floatFromInt(x), @floatFromInt(y), z, 1.0), - try common.interpolateVertexOutputs(data.allocator, &data.v0, &data.v1, &data.v2, b0, b1, b2), + inputs, + derivative_inputs, ) catch |err| { std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)}); if (comptime base.config.logs == .verbose) { diff --git a/src/soft/device/vertex_dispatcher.zig b/src/soft/device/vertex_dispatcher.zig index 02685ae..76ed575 100644 --- a/src/soft/device/vertex_dispatcher.zig +++ b/src/soft/device/vertex_dispatcher.zig @@ -9,6 +9,7 @@ const SpvRuntimeError = spv.Runtime.RuntimeError; const Renderer = @import("Renderer.zig"); const SoftPipeline = @import("../SoftPipeline.zig"); +const blitter = @import("blitter.zig"); const VkError = base.VkError; const INTERFACE_BLOB_PADDING = @sizeOf(F32x4); @@ -129,6 +130,9 @@ fn setupBuiltins(rt: *spv.Runtime, vertex_index_u32: u32, instance_index: usize) } fn writeVertexInput(rt: *spv.Runtime, allocator: std.mem.Allocator, raw_input: []const u8, format: vk.Format, location: u32) !void { + var expanded_input: [@sizeOf(F32x4)]u8 = @splat(0); + const expanded_slice = expandedVertexInput(raw_input, format, &expanded_input); + var has_split_components = false; for (1..4) |component| { _ = rt.getResultByLocationComponent(location, @intCast(component), .input) catch |err| switch (err) { @@ -148,8 +152,8 @@ fn writeVertexInput(rt: *spv.Runtime, allocator: std.mem.Allocator, raw_input: [ const input_memory_size = try rt.getResultMemorySize(result_word); const raw_offset = component * @sizeOf(f32); - if (raw_offset + input_memory_size <= raw_input.len) { - try rt.writeInput(raw_input[raw_offset .. raw_offset + input_memory_size], result_word); + if (raw_offset + input_memory_size <= expanded_slice.len) { + try rt.writeInput(expanded_slice[raw_offset .. raw_offset + input_memory_size], result_word); continue; } @@ -179,8 +183,8 @@ fn writeVertexInput(rt: *spv.Runtime, allocator: std.mem.Allocator, raw_input: [ const input_memory_size = try rt.getInputLocationMemorySize(location); - if (raw_input.len >= input_memory_size) { - try rt.writeInputLocation(raw_input[0..input_memory_size], location); + if (expanded_slice.len >= input_memory_size) { + try rt.writeInputLocation(expanded_slice[0..input_memory_size], location); return; } @@ -188,12 +192,24 @@ fn writeVertexInput(rt: *spv.Runtime, allocator: std.mem.Allocator, raw_input: [ defer allocator.free(input); @memset(input, 0); - @memcpy(input[0..raw_input.len], raw_input); + @memcpy(input[0..expanded_slice.len], expanded_slice); - fillMissingVertexComponents(input, raw_input.len, format); + fillMissingVertexComponents(input, expanded_slice.len, format); try rt.writeInputLocation(input, location); } +fn expandedVertexInput(raw_input: []const u8, format: vk.Format, expanded: *[@sizeOf(F32x4)]u8) []const u8 { + if (base.format.isUnnormalizedInteger(format)) { + const value = blitter.readInt4(raw_input, format); + @memcpy(expanded, std.mem.asBytes(&value)); + return expanded; + } + + const value = blitter.readFloat4(raw_input, format); + @memcpy(expanded, std.mem.asBytes(&value)); + return expanded; +} + fn fillMissingVertexComponents(input: []u8, raw_input_size: usize, format: vk.Format) void { if (input.len < @sizeOf(F32x4) or raw_input_size > 3 * @sizeOf(f32)) return; diff --git a/src/soft/lib.zig b/src/soft/lib.zig index 55d8a48..e1b4f91 100644 --- a/src/soft/lib.zig +++ b/src/soft/lib.zig @@ -41,6 +41,7 @@ pub const DRIVER_NAME = "Soft"; pub const VULKAN_VERSION = vk.makeApiVersion(0, 1, 0, 0); pub const DRIVER_VERSION = vk.makeApiVersion(0, 0, 0, 1); pub const DEVICE_ID = 0x600DCAFE; +pub const PIPELINE_CACHE_UUID: [vk.UUID_SIZE]u8 = "ApeSoftCacheUUID".*; /// Generic system memory. pub const MEMORY_TYPE_GENERIC_BIT = 0; diff --git a/src/vulkan/CommandBuffer.zig b/src/vulkan/CommandBuffer.zig index d9b1449..3dd5d00 100644 --- a/src/vulkan/CommandBuffer.zig +++ b/src/vulkan/CommandBuffer.zig @@ -32,6 +32,7 @@ owner: *Device, pool: *CommandPool, state: State, begin_info: ?vk.CommandBufferBeginInfo, +usage_flags: vk.CommandBufferUsageFlags, host_allocator: VulkanAllocator, state_mutex: std.Io.Mutex, @@ -94,6 +95,7 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Comma .pool = try NonDispatchable(CommandPool).fromHandleObject(info.command_pool), .state = .Initial, .begin_info = null, + .usage_flags = .{}, .host_allocator = VulkanAllocator.from(allocator).cloneWithScope(.object), .state_mutex = .init, .vtable = undefined, @@ -120,7 +122,7 @@ pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { pub fn begin(self: *Self, info: *const vk.CommandBufferBeginInfo) VkError!void { const implicitly_reset = self.state == .Executable; - self.transitionState(.Recording, &.{ .Initial, .Executable }) catch return VkError.ValidationFailed; + self.transitionState(.Recording, &.{ .Initial, .Executable, .Invalid }) catch return VkError.ValidationFailed; if (implicitly_reset) { try self.dispatch_table.reset(self, .{}); self.begin_info = null; @@ -128,12 +130,12 @@ pub fn begin(self: *Self, info: *const vk.CommandBufferBeginInfo) VkError!void { try self.dispatch_table.begin(self, info); self.begin_info = info.*; + self.usage_flags = info.flags; } pub fn end(self: *Self) VkError!void { self.transitionState(.Executable, &.{.Recording}) catch return VkError.ValidationFailed; try self.dispatch_table.end(self); - self.begin_info = null; } pub fn reset(self: *Self, flags: vk.CommandBufferResetFlags) VkError!void { @@ -141,26 +143,28 @@ pub fn reset(self: *Self, flags: vk.CommandBufferResetFlags) VkError!void { return VkError.ValidationFailed; } + try self.resetFromPool(flags); +} + +pub fn resetFromPool(self: *Self, flags: vk.CommandBufferResetFlags) VkError!void { self.transitionState(.Initial, &.{ .Initial, .Recording, .Executable, .Invalid }) catch return VkError.ValidationFailed; try self.dispatch_table.reset(self, flags); + self.begin_info = null; + self.usage_flags = .{}; } pub fn submit(self: *Self) VkError!void { - if (self.begin_info) |begin_info| { - if (!begin_info.flags.simultaneous_use_bit) { - self.transitionState(.Pending, &.{.Executable}) catch return VkError.ValidationFailed; - return; - } + if (!self.usage_flags.simultaneous_use_bit) { + self.transitionState(.Pending, &.{.Executable}) catch return VkError.ValidationFailed; + return; } self.transitionState(.Pending, &.{ .Pending, .Executable }) catch return VkError.ValidationFailed; } pub fn finish(self: *Self) VkError!void { - if (self.begin_info) |begin_info| { - if (!begin_info.flags.one_time_submit_bit) { - self.transitionState(.Invalid, &.{.Pending}) catch return VkError.ValidationFailed; - return; - } + if (self.usage_flags.one_time_submit_bit) { + self.transitionState(.Invalid, &.{.Pending}) catch return VkError.ValidationFailed; + return; } self.transitionState(.Executable, &.{.Pending}) catch return VkError.ValidationFailed; } diff --git a/src/vulkan/CommandPool.zig b/src/vulkan/CommandPool.zig index 781ae48..d2d6d27 100644 --- a/src/vulkan/CommandPool.zig +++ b/src/vulkan/CommandPool.zig @@ -55,10 +55,23 @@ pub fn allocateCommandBuffers(self: *Self, info: *const vk.CommandBufferAllocate while (self.buffers.capacity < self.buffers.items.len + info.command_buffer_count) { self.buffers.ensureUnusedCapacity(allocator, BUFFER_POOL_BASE_CAPACITY) catch return VkError.OutOfHostMemory; } + const original_len = self.buffers.items.len; + errdefer { + for (self.buffers.items[original_len..]) |dis_cmd| { + dis_cmd.intrusiveDestroy(allocator); + } + self.buffers.shrinkRetainingCapacity(original_len); + } for (0..info.command_buffer_count) |_| { const cmd = try self.vtable.createCommandBuffer(self, allocator, info); + var cmd_owned = true; + errdefer if (cmd_owned) cmd.destroy(allocator); const dis_cmd = try Dispatchable(CommandBuffer).wrap(allocator, cmd); + cmd_owned = false; + var dis_cmd_owned = true; + errdefer if (dis_cmd_owned) dis_cmd.intrusiveDestroy(allocator); self.buffers.appendAssumeCapacity(dis_cmd); + dis_cmd_owned = false; } } @@ -97,15 +110,7 @@ pub fn reset(self: *Self, flags: vk.CommandPoolResetFlags) VkError!void { self.first_free_buffer_index = 0; - if (flags.release_resources_bit) { - const allocator = self.host_allocator.allocator(); - for (self.buffers.items) |dis_cmd| { - dis_cmd.intrusiveDestroy(allocator); - } - self.buffers.clearRetainingCapacity(); - } else { - for (self.buffers.items) |dis_cmd| { - _ = dis_cmd.object.reset(.{ .release_resources_bit = true }) catch {}; - } + for (self.buffers.items) |dis_cmd| { + _ = dis_cmd.object.resetFromPool(.{ .release_resources_bit = flags.release_resources_bit }) catch {}; } } diff --git a/src/vulkan/Device.zig b/src/vulkan/Device.zig index 7dc86cc..d43cdc4 100644 --- a/src/vulkan/Device.zig +++ b/src/vulkan/Device.zig @@ -41,6 +41,7 @@ instance: *Instance, physical_device: *const PhysicalDevice, queues: std.AutoArrayHashMapUnmanaged(u32, std.ArrayList(*Dispatchable(Queue))), host_allocator: VulkanAllocator, +khr_swapchain_enabled: bool, dispatch_table: *const DispatchTable, vtable: *const VTable, @@ -75,17 +76,28 @@ pub const DispatchTable = struct { }; pub fn init(allocator: std.mem.Allocator, instance: *Instance, physical_device: *const PhysicalDevice, info: *const vk.DeviceCreateInfo) VkError!Self { - _ = info; return .{ .instance = instance, .physical_device = physical_device, .queues = .empty, .host_allocator = VulkanAllocator.from(allocator).cloneWithScope(.object), + .khr_swapchain_enabled = isExtensionEnabled(info, vk.extensions.khr_swapchain.name), .dispatch_table = undefined, .vtable = undefined, }; } +fn isExtensionEnabled(info: *const vk.DeviceCreateInfo, extension_name: []const u8) bool { + if (info.enabled_extension_count == 0) return false; + const names = info.pp_enabled_extension_names orelse return false; + for (0..info.enabled_extension_count) |i| { + if (std.mem.eql(u8, std.mem.span(names[i]), extension_name)) { + return true; + } + } + return false; +} + pub fn createQueues(self: *Self, allocator: std.mem.Allocator, info: *const vk.DeviceCreateInfo) VkError!void { if (info.queue_create_info_count == 0) { return; @@ -102,8 +114,10 @@ pub fn createQueues(self: *Self, allocator: std.mem.Allocator, info: *const vk.D } const queue = try self.vtable.createQueue(allocator, self, queue_info.queue_family_index, @intCast(family_ptr.items.len), queue_info.flags); + errdefer self.vtable.destroyQueue(queue, allocator) catch {}; const dispatchable_queue = try Dispatchable(Queue).wrap(allocator, queue); + errdefer dispatchable_queue.destroy(allocator); family_ptr.append(allocator, dispatchable_queue) catch return VkError.OutOfHostMemory; } } diff --git a/src/vulkan/Image.zig b/src/vulkan/Image.zig index 05d57b5..5c71770 100644 --- a/src/vulkan/Image.zig +++ b/src/vulkan/Image.zig @@ -63,7 +63,7 @@ pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { pub fn bindMemory(self: *Self, memory: *DeviceMemory, offset: vk.DeviceSize) VkError!void { const image_size = try self.getTotalSize(); - if (offset >= image_size or !self.allowed_memory_types.isSet(memory.memory_type_index)) { + if (offset + image_size > memory.size or !self.allowed_memory_types.isSet(memory.memory_type_index)) { return VkError.ValidationFailed; } self.memory = memory; diff --git a/src/vulkan/Instance.zig b/src/vulkan/Instance.zig index 192c3a4..a5ab81c 100644 --- a/src/vulkan/Instance.zig +++ b/src/vulkan/Instance.zig @@ -2,6 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const vk = @import("vulkan"); const config = @import("lib.zig").config; +const utils = @import("utils.zig"); const VkError = @import("error_set.zig").VkError; const Dispatchable = @import("Dispatchable.zig").Dispatchable; @@ -50,6 +51,41 @@ pub fn init(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) V }; } +pub fn validateCreateInfo(info: *const vk.InstanceCreateInfo) VkError!void { + if (info.p_application_info) |application_info| { + const requested: vk.Version = @bitCast(application_info.api_version); + const supported: vk.Version = if (comptime builtin.is_test) + vk.API_VERSION_1_0 + else + @bitCast(root.VULKAN_VERSION); + if (requested.variant != 0 or requested.major > supported.major or (requested.major == supported.major and requested.minor > supported.minor)) { + return VkError.IncompatibleDriver; + } + } + + if (info.enabled_layer_count != 0) { + const names = info.pp_enabled_layer_names orelse return VkError.LayerNotPresent; + for (0..info.enabled_layer_count) |i| { + _ = utils.boundedName(names[i], vk.MAX_EXTENSION_NAME_SIZE) orelse return VkError.LayerNotPresent; + return VkError.LayerNotPresent; + } + } + + if (info.enabled_extension_count != 0) { + const names = info.pp_enabled_extension_names orelse return VkError.ExtensionNotPresent; + const supported_extensions = if (comptime builtin.is_test) + &[_]vk.ExtensionProperties{} + else + root.Instance.EXTENSIONS[0..]; + for (0..info.enabled_extension_count) |i| { + const name = utils.boundedName(names[i], vk.MAX_EXTENSION_NAME_SIZE) orelse return VkError.ExtensionNotPresent; + if (!utils.isSupportedExtension(name, supported_extensions)) { + return VkError.ExtensionNotPresent; + } + } + } +} + /// Dummy to avoid compile error in tests and doc generation pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) VkError!*Self { _ = allocator; @@ -64,11 +100,16 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) VkError!void { pub fn enumerateLayerProperties(count: *u32, p_properties: ?[*]vk.LayerProperties) VkError!void { if (comptime !builtin.is_test and @hasDecl(root.Instance, "LAYERS")) { - count.* = root.Instance.LAYERS.len; + const available = root.Instance.LAYERS.len; if (p_properties) |properties| { - for (root.Instance.LAYERS, properties[0..]) |layer, *prop| { + const write_count = @min(count.*, available); + for (root.Instance.LAYERS[0..write_count], properties[0..write_count]) |layer, *prop| { prop.* = layer; } + count.* = @intCast(write_count); + if (write_count < available) return VkError.Incomplete; + } else { + count.* = @intCast(available); } } else { count.* = 0; @@ -81,11 +122,16 @@ pub fn enumerateExtensionProperties(layer_name: ?[]const u8, count: *u32, p_prop } if (comptime !builtin.is_test and @hasDecl(root.Instance, "EXTENSIONS")) { - count.* = root.Instance.EXTENSIONS.len; + const available = root.Instance.EXTENSIONS.len; if (p_properties) |properties| { - for (root.Instance.EXTENSIONS, properties[0..]) |ext, *prop| { + const write_count = @min(count.*, available); + for (root.Instance.EXTENSIONS[0..write_count], properties[0..write_count]) |ext, *prop| { prop.* = ext; } + count.* = @intCast(write_count); + if (write_count < available) return VkError.Incomplete; + } else { + count.* = @intCast(available); } } else { count.* = 0; diff --git a/src/vulkan/PhysicalDevice.zig b/src/vulkan/PhysicalDevice.zig index 21aeab3..3b0c4bf 100644 --- a/src/vulkan/PhysicalDevice.zig +++ b/src/vulkan/PhysicalDevice.zig @@ -1,6 +1,7 @@ const std = @import("std"); const vk = @import("vulkan"); const root = @import("lib.zig"); +const utils = @import("utils.zig"); const Instance = @import("Instance.zig"); const VkError = @import("error_set.zig").VkError; @@ -64,6 +65,43 @@ pub inline fn createDevice(self: *Self, allocator: std.mem.Allocator, infos: *co return try self.dispatch_table.createDevice(self, allocator, infos); } +pub fn validateCreateInfo(self: *const Self, allocator: std.mem.Allocator, info: *const vk.DeviceCreateInfo) VkError!void { + if (info.enabled_layer_count != 0) { + const names = info.pp_enabled_layer_names orelse return VkError.LayerNotPresent; + for (0..info.enabled_layer_count) |i| { + _ = utils.boundedName(names[i], vk.MAX_EXTENSION_NAME_SIZE) orelse return VkError.LayerNotPresent; + return VkError.LayerNotPresent; + } + } + + if (info.enabled_extension_count != 0) { + const names = info.pp_enabled_extension_names orelse return VkError.ExtensionNotPresent; + + var available_count: u32 = 0; + try self.enumerateExtensionProperties(null, &available_count, null); + const supported_extensions = allocator.alloc(vk.ExtensionProperties, available_count) catch return VkError.OutOfHostMemory; + defer allocator.free(supported_extensions); + + var written_count = available_count; + try self.enumerateExtensionProperties(null, &written_count, supported_extensions.ptr); + + for (0..info.enabled_extension_count) |i| { + const name = utils.boundedName(names[i], vk.MAX_EXTENSION_NAME_SIZE) orelse return VkError.ExtensionNotPresent; + if (!utils.isSupportedExtension(name, supported_extensions[0..written_count])) { + return VkError.ExtensionNotPresent; + } + } + } + + if (info.p_enabled_features) |requested_features| { + inline for (std.meta.fields(vk.PhysicalDeviceFeatures)) |field| { + if (@field(requested_features, field.name) == .true and @field(self.features, field.name) == .false) { + return VkError.FeatureNotPresent; + } + } + } +} + pub inline fn getFormatProperties(self: *Self, format: vk.Format) VkError!vk.FormatProperties { return try self.dispatch_table.getFormatProperties(self, format); } diff --git a/src/vulkan/Pipeline.zig b/src/vulkan/Pipeline.zig index b8ab448..eb8cfff 100644 --- a/src/vulkan/Pipeline.zig +++ b/src/vulkan/Pipeline.zig @@ -93,6 +93,21 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe } } + var binding_description: ?[]vk.VertexInputBindingDescription = null; + errdefer if (binding_description) |value| allocator.free(value); + + var attribute_description: ?[]vk.VertexInputAttributeDescription = null; + errdefer if (attribute_description) |value| allocator.free(value); + + var viewports: ?[]vk.Viewport = null; + errdefer if (viewports) |value| allocator.free(value); + + var scissors: ?[]vk.Rect2D = null; + errdefer if (scissors) |value| allocator.free(value); + + var color_attachments: ?[]vk.PipelineColorBlendAttachmentState = null; + errdefer if (color_attachments) |value| allocator.free(value); + return .{ .owner = device, .vtable = undefined, @@ -105,7 +120,8 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe .binding_description = blk: { if (info.p_vertex_input_state) |vertex_input_state| { if (vertex_input_state.p_vertex_binding_descriptions) |vertex_binding_descriptions| { - break :blk allocator.dupe(vk.VertexInputBindingDescription, vertex_binding_descriptions[0..vertex_input_state.vertex_binding_description_count]) catch return VkError.OutOfHostMemory; + binding_description = allocator.dupe(vk.VertexInputBindingDescription, vertex_binding_descriptions[0..vertex_input_state.vertex_binding_description_count]) catch return VkError.OutOfHostMemory; + break :blk binding_description; } } else { return VkError.ValidationFailed; @@ -115,7 +131,8 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe .attribute_description = blk: { if (info.p_vertex_input_state) |vertex_input_state| { if (vertex_input_state.p_vertex_attribute_descriptions) |vertex_attribute_descriptions| { - break :blk allocator.dupe(vk.VertexInputAttributeDescription, vertex_attribute_descriptions[0..vertex_input_state.vertex_attribute_description_count]) catch return VkError.OutOfHostMemory; + attribute_description = allocator.dupe(vk.VertexInputAttributeDescription, vertex_attribute_descriptions[0..vertex_input_state.vertex_attribute_description_count]) catch return VkError.OutOfHostMemory; + break :blk attribute_description; } } else { return VkError.ValidationFailed; @@ -128,16 +145,20 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe .viewport_state = .{ .viewports = blk: { if (info.p_viewport_state) |viewport_state| { - if (viewport_state.p_viewports) |viewports| { - break :blk allocator.dupe(vk.Viewport, viewports[0..viewport_state.viewport_count]) catch return VkError.OutOfHostMemory; + if (viewport_state.p_viewports) |p_viewports| { + const copy = allocator.dupe(vk.Viewport, p_viewports[0..viewport_state.viewport_count]) catch return VkError.OutOfHostMemory; + viewports = copy; + break :blk viewports; } } break :blk null; }, .scissor = blk: { if (info.p_viewport_state) |viewport_state| { - if (viewport_state.p_scissors) |scissors| { - break :blk allocator.dupe(vk.Rect2D, scissors[0..viewport_state.scissor_count]) catch return VkError.OutOfHostMemory; + if (viewport_state.p_scissors) |p_scissors| { + const copy = allocator.dupe(vk.Rect2D, p_scissors[0..viewport_state.scissor_count]) catch return VkError.OutOfHostMemory; + scissors = copy; + break :blk scissors; } } break :blk null; @@ -152,10 +173,10 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe .color_blend = blk: { if (info.p_color_blend_state) |state| { break :blk .{ - .attachments = if (state.p_attachments) |attachments| - allocator.dupe(vk.PipelineColorBlendAttachmentState, attachments[0..state.attachment_count]) catch return VkError.OutOfHostMemory - else - null, + .attachments = if (state.p_attachments) |attachments| blk_attachments: { + color_attachments = allocator.dupe(vk.PipelineColorBlendAttachmentState, attachments[0..state.attachment_count]) catch return VkError.OutOfHostMemory; + break :blk_attachments color_attachments; + } else null, .constants = state.blend_constants, }; } @@ -205,6 +226,12 @@ pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { if (graphics.input_assembly.attribute_description) |attribute_description| { allocator.free(attribute_description); } + if (graphics.viewport_state.viewports) |viewports| { + allocator.free(viewports); + } + if (graphics.viewport_state.scissor) |scissor| { + allocator.free(scissor); + } if (graphics.color_blend.attachments) |attachments| { allocator.free(attachments); } diff --git a/src/vulkan/PipelineCache.zig b/src/vulkan/PipelineCache.zig index 33bbf09..5239f85 100644 --- a/src/vulkan/PipelineCache.zig +++ b/src/vulkan/PipelineCache.zig @@ -4,6 +4,7 @@ const vk = @import("vulkan"); const NonDispatchable = @import("NonDispatchable.zig"); const VkError = @import("error_set.zig").VkError; +const root = @import("lib.zig"); const Device = @import("Device.zig"); @@ -11,6 +12,7 @@ const Self = @This(); pub const ObjectType: vk.ObjectType = .pipeline_cache; owner: *Device, +data_available: bool, vtable: *const VTable, @@ -18,11 +20,14 @@ pub const VTable = struct { destroy: *const fn (*Self, std.mem.Allocator) void, }; +pub const Header = vk.PipelineCacheHeaderVersionOne; + pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.PipelineCacheCreateInfo) VkError!Self { _ = allocator; - _ = info; + const has_initial_data = info.initial_data_size != 0 or info.p_initial_data != null; return .{ .owner = device, + .data_available = !has_initial_data or info.initial_data_size >= dataSize(), .vtable = undefined, }; } @@ -30,3 +35,44 @@ pub fn init(device: *Device, allocator: std.mem.Allocator, info: *const vk.Pipel pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void { self.vtable.destroy(self, allocator); } + +pub fn merge(self: *Self) VkError!void { + _ = self; +} + +pub fn getData(self: *Self, data: ?[]u8) vk.Result { + if (!self.data_available) { + return .success; + } + + const cache_header = self.header(); + const bytes = std.mem.asBytes(&cache_header); + + if (data) |dst| { + if (dst.len < bytes.len) { + return .incomplete; + } + + @memcpy(dst[0..bytes.len], bytes); + } + + return .success; +} + +pub inline fn dataSize() usize { + return @sizeOf(Header); +} + +pub inline fn availableDataSize(self: *const Self) usize { + return if (self.data_available) dataSize() else 0; +} + +fn header(self: *const Self) Header { + return .{ + .header_size = @sizeOf(Header), + .header_version = .one, + .vendor_id = @intCast(root.VULKAN_VENDOR_ID), + .device_id = self.owner.physical_device.props.device_id, + .pipeline_cache_uuid = self.owner.physical_device.props.pipeline_cache_uuid, + }; +} diff --git a/src/vulkan/lib.zig b/src/vulkan/lib.zig index 384188a..bd6e510 100644 --- a/src/vulkan/lib.zig +++ b/src/vulkan/lib.zig @@ -52,7 +52,9 @@ pub const ShaderModule = @import("ShaderModule.zig"); pub const SurfaceKHR = @import("wsi/SurfaceKHR.zig"); pub const SwapchainKHR = @import("wsi/SwapchainKHR.zig"); -pub const VULKAN_VENDOR_ID = 0x10008; +// To be commented out when spec 1.4.354 is released +// pub const VULKAN_VENDOR_ID: i32 = @intFromEnum(vk.VendorId.ape); +pub const VULKAN_VENDOR_ID: i32 = 0x10008; /// Default driver name pub const DRIVER_NAME = "Unnamed Ape Driver"; diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index 795134a..8bcbfce 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -64,6 +64,22 @@ inline fn notImplementedWarning() void { logger.fixme("function not yet implemented", .{}); } +fn isSwapchainDeviceFunction(name: []const u8) bool { + return std.mem.eql(u8, name, "vkAcquireNextImageKHR") or + std.mem.eql(u8, name, "vkCreateSwapchainKHR") or + std.mem.eql(u8, name, "vkDestroySwapchainKHR") or + std.mem.eql(u8, name, "vkGetSwapchainImagesKHR") or + std.mem.eql(u8, name, "vkQueuePresentKHR"); +} + +fn wrapNonDispatchable(comptime T: type, allocator: std.mem.Allocator, object: *T, comptime VkT: type) VkError!VkT { + const handle = NonDispatchable(T).wrap(allocator, object) catch |err| { + object.destroy(allocator); + return err; + }; + return handle.toVkHandle(VkT); +} + fn functionMapEntryPoint(comptime name: []const u8) struct { []const u8, vk.PfnVoidFunction } { // Mapping 'vkFnName' to 'apeFnName' const ape_name = std.fmt.comptimePrint("ape{s}", .{name[2..]}); @@ -94,6 +110,8 @@ const instance_pfn_map = std.StaticStringMap(vk.PfnVoidFunction).initComptime(.{ functionMapEntryPoint("vkCreateWaylandSurfaceKHR"), functionMapEntryPoint("vkDestroyInstance"), functionMapEntryPoint("vkDestroySurfaceKHR"), + functionMapEntryPoint("vkEnumeratePhysicalDeviceGroups"), + functionMapEntryPoint("vkEnumeratePhysicalDeviceGroupsKHR"), functionMapEntryPoint("vkEnumeratePhysicalDevices"), functionMapEntryPoint("vkGetDeviceProcAddr"), }); @@ -312,15 +330,23 @@ pub export fn apeCreateInstance(info: *const vk.InstanceCreateInfo, callbacks: ? return .error_validation_failed; } const allocator = VulkanAllocator.init(callbacks, .instance).allocator(); + Instance.validateCreateInfo(info) catch |err| return toVkResult(err); 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); + instance.requestPhysicalDevices(allocator) catch |err| { + if (!builtin.is_test) instance.deinit(allocator) catch {}; + return toVkResult(err); + }; - p_instance.* = (Dispatchable(Instance).wrap(allocator, instance) catch |err| return toVkResult(err)).toVkHandle(vk.Instance); + const dispatchable = Dispatchable(Instance).wrap(allocator, instance) catch |err| { + if (!builtin.is_test) instance.deinit(allocator) catch {}; + return toVkResult(err); + }; + p_instance.* = dispatchable.toVkHandle(vk.Instance); return .success; } @@ -370,15 +396,63 @@ pub export fn apeEnumeratePhysicalDevices(p_instance: vk.Instance, count: *u32, defer entryPointEndLogTrace(); const instance = Dispatchable(Instance).fromHandleObject(p_instance) catch |err| return toVkResult(err); - count.* = @intCast(instance.physical_devices.items.len); + const available = instance.physical_devices.items.len; if (p_devices) |devices| { - for (0..count.*) |i| { + const write_count = @min(count.*, available); + for (0..write_count) |i| { devices[i] = instance.physical_devices.items[i].toVkHandle(vk.PhysicalDevice); } + count.* = @intCast(write_count); + if (write_count < available) return .incomplete; + } else { + count.* = @intCast(available); } return .success; } +pub export fn apeEnumeratePhysicalDeviceGroups( + p_instance: vk.Instance, + count: *u32, + p_groups: ?[*]vk.PhysicalDeviceGroupProperties, +) callconv(vk.vulkan_call_conv) vk.Result { + entryPointBeginLogTrace(.vkEnumeratePhysicalDeviceGroups); + defer entryPointEndLogTrace(); + + const instance = Dispatchable(Instance).fromHandleObject(p_instance) catch |err| return toVkResult(err); + const available: u32 = 1; + + if (p_groups) |groups| { + const write_count = @min(count.*, available); + if (write_count == 0) { + count.* = 0; + return .incomplete; + } + + var group = groups[0]; + group.physical_device_count = @intCast(instance.physical_devices.items.len); + group.physical_devices = @splat(.null_handle); + for (instance.physical_devices.items, 0..) |physical_device, i| { + group.physical_devices[i] = physical_device.toVkHandle(vk.PhysicalDevice); + } + group.subset_allocation = .false; + groups[0] = group; + + count.* = write_count; + return .success; + } + + count.* = available; + return .success; +} + +pub export fn apeEnumeratePhysicalDeviceGroupsKHR( + p_instance: vk.Instance, + count: *u32, + p_groups: ?[*]vk.PhysicalDeviceGroupProperties, +) callconv(vk.vulkan_call_conv) vk.Result { + return apeEnumeratePhysicalDeviceGroups(p_instance, count, p_groups); +} + // Physical Device functions ================================================================================================================================= pub export fn apeCreateDevice(p_physical_device: vk.PhysicalDevice, info: *const vk.DeviceCreateInfo, callbacks: ?*const vk.AllocationCallbacks, p_device: *vk.Device) callconv(vk.vulkan_call_conv) vk.Result { @@ -391,11 +465,16 @@ pub export fn apeCreateDevice(p_physical_device: vk.PhysicalDevice, info: *const const allocator = VulkanAllocator.init(callbacks, .device).allocator(); const physical_device = Dispatchable(PhysicalDevice).fromHandleObject(p_physical_device) catch |err| return toVkResult(err); + physical_device.validateCreateInfo(allocator, info) catch |err| return toVkResult(err); std.log.scoped(.vkCreateDevice).debug("Using VkPhysicalDevice named {s}", .{physical_device.props.device_name}); 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); + const dispatchable = Dispatchable(Device).wrap(allocator, device) catch |err| { + device.destroy(allocator) catch {}; + return toVkResult(err); + }; + p_device.* = dispatchable.toVkHandle(vk.Device); return .success; } @@ -679,7 +758,7 @@ pub export fn apeAllocateMemory(p_device: vk.Device, info: *const vk.MemoryAlloc 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); + p_memory.* = wrapNonDispatchable(DeviceMemory, allocator, device_memory, vk.DeviceMemory) catch |err| return toVkResult(err); return .success; } @@ -723,7 +802,7 @@ pub export fn apeCreateBuffer(p_device: vk.Device, info: *const vk.BufferCreateI const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const buffer = device.createBuffer(allocator, info) catch |err| return toVkResult(err); - p_buffer.* = (NonDispatchable(Buffer).wrap(allocator, buffer) catch |err| return toVkResult(err)).toVkHandle(vk.Buffer); + p_buffer.* = wrapNonDispatchable(Buffer, allocator, buffer, vk.Buffer) catch |err| return toVkResult(err); return .success; } @@ -737,7 +816,7 @@ pub export fn apeCreateBufferView(p_device: vk.Device, info: *const vk.BufferVie const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const view = device.createBufferView(allocator, info) catch |err| return toVkResult(err); - p_view.* = (NonDispatchable(BufferView).wrap(allocator, view) catch |err| return toVkResult(err)).toVkHandle(vk.BufferView); + p_view.* = wrapNonDispatchable(BufferView, allocator, view, vk.BufferView) catch |err| return toVkResult(err); return .success; } @@ -752,7 +831,7 @@ pub export fn apeCreateCommandPool(p_device: vk.Device, info: *const vk.CommandP 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); + p_pool.* = wrapNonDispatchable(CommandPool, allocator, pool, vk.CommandPool) catch |err| return toVkResult(err); return .success; } @@ -808,7 +887,7 @@ pub export fn apeCreateDescriptorPool(p_device: vk.Device, info: *const vk.Descr const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const pool = device.createDescriptorPool(allocator, info) catch |err| return toVkResult(err); - p_pool.* = (NonDispatchable(DescriptorPool).wrap(allocator, pool) catch |err| return toVkResult(err)).toVkHandle(vk.DescriptorPool); + p_pool.* = wrapNonDispatchable(DescriptorPool, allocator, pool, vk.DescriptorPool) catch |err| return toVkResult(err); return .success; } @@ -824,7 +903,7 @@ pub export fn apeCreateDescriptorSetLayout(p_device: vk.Device, info: *const vk. const allocator = VulkanAllocator.init(callbacks, .device).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const layout = device.createDescriptorSetLayout(allocator, info) catch |err| return toVkResult(err); - p_layout.* = (NonDispatchable(DescriptorSetLayout).wrap(allocator, layout) catch |err| return toVkResult(err)).toVkHandle(vk.DescriptorSetLayout); + p_layout.* = wrapNonDispatchable(DescriptorSetLayout, allocator, layout, vk.DescriptorSetLayout) catch |err| return toVkResult(err); return .success; } @@ -839,7 +918,7 @@ pub export fn apeCreateEvent(p_device: vk.Device, info: *const vk.EventCreateInf const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const event = device.createEvent(allocator, info) catch |err| return toVkResult(err); - p_event.* = (NonDispatchable(Event).wrap(allocator, event) catch |err| return toVkResult(err)).toVkHandle(vk.Event); + p_event.* = wrapNonDispatchable(Event, allocator, event, vk.Event) catch |err| return toVkResult(err); return .success; } @@ -854,7 +933,7 @@ pub export fn apeCreateFence(p_device: vk.Device, info: *const vk.FenceCreateInf 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); + p_fence.* = wrapNonDispatchable(Fence, allocator, fence, vk.Fence) catch |err| return toVkResult(err); return .success; } @@ -869,7 +948,7 @@ pub export fn apeCreateFramebuffer(p_device: vk.Device, info: *const vk.Framebuf const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const framebuffer = device.createFramebuffer(allocator, info) catch |err| return toVkResult(err); - p_framebuffer.* = (NonDispatchable(Framebuffer).wrap(allocator, framebuffer) catch |err| return toVkResult(err)).toVkHandle(vk.Framebuffer); + p_framebuffer.* = wrapNonDispatchable(Framebuffer, allocator, framebuffer, vk.Framebuffer) catch |err| return toVkResult(err); return .success; } @@ -922,7 +1001,7 @@ pub export fn apeCreateImage(p_device: vk.Device, info: *const vk.ImageCreateInf const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const image = device.createImage(allocator, info) catch |err| return toVkResult(err); - p_image.* = (NonDispatchable(Image).wrap(allocator, image) catch |err| return toVkResult(err)).toVkHandle(vk.Image); + p_image.* = wrapNonDispatchable(Image, allocator, image, vk.Image) catch |err| return toVkResult(err); return .success; } @@ -936,7 +1015,7 @@ pub export fn apeCreateImageView(p_device: vk.Device, info: *const vk.ImageViewC const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const image_view = device.createImageView(allocator, info) catch |err| return toVkResult(err); - p_image_view.* = (NonDispatchable(ImageView).wrap(allocator, image_view) catch |err| return toVkResult(err)).toVkHandle(vk.ImageView); + p_image_view.* = wrapNonDispatchable(ImageView, allocator, image_view, vk.ImageView) catch |err| return toVkResult(err); return .success; } @@ -951,7 +1030,7 @@ pub export fn apeCreatePipelineCache(p_device: vk.Device, info: *const vk.Pipeli const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const cache = device.createPipelineCache(allocator, info) catch |err| return toVkResult(err); - p_cache.* = (NonDispatchable(PipelineCache).wrap(allocator, cache) catch |err| return toVkResult(err)).toVkHandle(vk.PipelineCache); + p_cache.* = wrapNonDispatchable(PipelineCache, allocator, cache, vk.PipelineCache) catch |err| return toVkResult(err); return .success; } @@ -967,7 +1046,7 @@ pub export fn apeCreatePipelineLayout(p_device: vk.Device, info: *const vk.Pipel const allocator = VulkanAllocator.init(callbacks, .device).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const layout = device.createPipelineLayout(allocator, info) catch |err| return toVkResult(err); - p_layout.* = (NonDispatchable(PipelineLayout).wrap(allocator, layout) catch |err| return toVkResult(err)).toVkHandle(vk.PipelineLayout); + p_layout.* = wrapNonDispatchable(PipelineLayout, allocator, layout, vk.PipelineLayout) catch |err| return toVkResult(err); return .success; } @@ -982,7 +1061,7 @@ pub export fn apeCreateQueryPool(p_device: vk.Device, info: *const vk.QueryPoolC const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const pool = device.createQueryPool(allocator, info) catch |err| return toVkResult(err); - p_pool.* = (NonDispatchable(QueryPool).wrap(allocator, pool) catch |err| return toVkResult(err)).toVkHandle(vk.QueryPool); + p_pool.* = wrapNonDispatchable(QueryPool, allocator, pool, vk.QueryPool) catch |err| return toVkResult(err); return .success; } @@ -997,7 +1076,7 @@ pub export fn apeCreateRenderPass(p_device: vk.Device, info: *const vk.RenderPas const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const pass = device.createRenderPass(allocator, info) catch |err| return toVkResult(err); - p_pass.* = (NonDispatchable(RenderPass).wrap(allocator, pass) catch |err| return toVkResult(err)).toVkHandle(vk.RenderPass); + p_pass.* = wrapNonDispatchable(RenderPass, allocator, pass, vk.RenderPass) catch |err| return toVkResult(err); return .success; } @@ -1012,7 +1091,7 @@ pub export fn apeCreateSampler(p_device: vk.Device, info: *const vk.SamplerCreat const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const sampler = device.createSampler(allocator, info) catch |err| return toVkResult(err); - p_sampler.* = (NonDispatchable(Sampler).wrap(allocator, sampler) catch |err| return toVkResult(err)).toVkHandle(vk.Sampler); + p_sampler.* = wrapNonDispatchable(Sampler, allocator, sampler, vk.Sampler) catch |err| return toVkResult(err); return .success; } @@ -1027,7 +1106,7 @@ pub export fn apeCreateSemaphore(p_device: vk.Device, info: *const vk.SemaphoreC const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const semaphore = device.createSemaphore(allocator, info) catch |err| return toVkResult(err); - p_semaphore.* = (NonDispatchable(BinarySemaphore).wrap(allocator, semaphore) catch |err| return toVkResult(err)).toVkHandle(vk.Semaphore); + p_semaphore.* = wrapNonDispatchable(BinarySemaphore, allocator, semaphore, vk.Semaphore) catch |err| return toVkResult(err); return .success; } @@ -1042,7 +1121,7 @@ pub export fn apeCreateShaderModule(p_device: vk.Device, info: *const vk.ShaderM const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const module = device.createShaderModule(allocator, info) catch |err| return toVkResult(err); - p_module.* = (NonDispatchable(ShaderModule).wrap(allocator, module) catch |err| return toVkResult(err)).toVkHandle(vk.ShaderModule); + p_module.* = wrapNonDispatchable(ShaderModule, allocator, module, vk.ShaderModule) catch |err| return toVkResult(err); return .success; } @@ -1348,6 +1427,8 @@ pub export fn apeGetDeviceProcAddr(p_device: vk.Device, p_name: ?[*:0]const u8) const name = std.mem.span(p_name.?); if (p_device == .null_handle) return null; + const device = Dispatchable(Device).fromHandleObject(p_device) catch return null; + if (isSwapchainDeviceFunction(name) and !device.khr_swapchain_enabled) return null; if (device_pfn_map.get(name)) |pfn| return pfn; return null; @@ -1380,7 +1461,7 @@ pub export fn apeGetEventStatus(p_device: vk.Device, p_event: vk.Event) callconv const event = NonDispatchable(Event).fromHandleObject(p_event) catch |err| return toVkResult(err); event.getStatus() catch |err| return toVkResult(err); - return .success; + return .event_set; } pub export fn apeGetFenceStatus(p_device: vk.Device, p_fence: vk.Fence) callconv(vk.vulkan_call_conv) vk.Result { @@ -1391,7 +1472,7 @@ pub export fn apeGetFenceStatus(p_device: vk.Device, p_fence: vk.Fence) callconv const fence = NonDispatchable(Fence).fromHandleObject(p_fence) catch |err| return toVkResult(err); fence.getStatus() catch |err| return toVkResult(err); - return .event_set; + return .success; } pub export fn apeGetImageMemoryRequirements(p_device: vk.Device, p_image: vk.Image, requirements: *vk.MemoryRequirements) callconv(vk.vulkan_call_conv) void { @@ -1428,19 +1509,20 @@ pub export fn apeGetImageSubresourceLayout(p_device: vk.Device, p_image: vk.Imag layout.* = image.getSubresourceLayout(subresource.*) catch |err| return errorLogger(err); } -pub export fn apeGetPipelineCacheData(p_device: vk.Device, p_cache: vk.PipelineCache, size: *usize, data: *anyopaque) callconv(vk.vulkan_call_conv) vk.Result { +pub export fn apeGetPipelineCacheData(p_device: vk.Device, p_cache: vk.PipelineCache, size: *usize, data: ?*anyopaque) callconv(vk.vulkan_call_conv) vk.Result { entryPointBeginLogTrace(.vkGetPipelineCacheData); defer entryPointEndLogTrace(); Dispatchable(Device).checkHandleValidity(p_device) catch |err| return toVkResult(err); + const cache = NonDispatchable(PipelineCache).fromHandleObject(p_cache) catch |err| return toVkResult(err); - notImplementedWarning(); - - _ = p_cache; - _ = size; - _ = data; - - return .success; + const available = cache.availableDataSize(); + const result = if (data) |ptr| blk: { + const bytes = @as([*]u8, @ptrCast(ptr))[0..size.*]; + break :blk cache.getData(bytes); + } else cache.getData(null); + size.* = if (result == .incomplete) 0 else available; + return result; } pub export fn apeGetQueryPoolResults( @@ -1503,14 +1585,14 @@ pub export fn apeMergePipelineCaches(p_device: vk.Device, p_dst: vk.PipelineCach defer entryPointEndLogTrace(); Dispatchable(Device).checkHandleValidity(p_device) catch |err| return toVkResult(err); + const dst = NonDispatchable(PipelineCache).fromHandleObject(p_dst) catch |err| return toVkResult(err); - notImplementedWarning(); + for (0..count) |i| { + _ = NonDispatchable(PipelineCache).fromHandleObject(p_srcs[i]) catch |err| return toVkResult(err); + } - _ = p_dst; - _ = count; - _ = p_srcs; - - return .error_unknown; + dst.merge() catch |err| return toVkResult(err); + return .success; } pub export fn apeResetCommandPool(p_device: vk.Device, p_pool: vk.CommandPool, flags: vk.CommandPoolResetFlags) callconv(vk.vulkan_call_conv) vk.Result { @@ -1533,7 +1615,7 @@ pub export fn apeResetDescriptorPool(p_device: vk.Device, p_pool: vk.DescriptorP return .success; } -pub export fn apeResetEvent(p_device: vk.Device, p_event: vk.Fence) callconv(vk.vulkan_call_conv) vk.Result { +pub export fn apeResetEvent(p_device: vk.Device, p_event: vk.Event) callconv(vk.vulkan_call_conv) vk.Result { entryPointBeginLogTrace(.vkResetEvent); defer entryPointEndLogTrace(); @@ -1557,7 +1639,7 @@ pub export fn apeResetFences(p_device: vk.Device, count: u32, p_fences: [*]const return .success; } -pub export fn apeSetEvent(p_device: vk.Device, p_event: vk.Fence) callconv(vk.vulkan_call_conv) vk.Result { +pub export fn apeSetEvent(p_device: vk.Device, p_event: vk.Event) callconv(vk.vulkan_call_conv) vk.Result { entryPointBeginLogTrace(.vkSetEvent); defer entryPointEndLogTrace(); @@ -2168,7 +2250,7 @@ pub export fn apeCreateSwapchainKHR(p_device: vk.Device, info: *const vk.Swapcha const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); const surface = NonDispatchable(SurfaceKHR).fromHandleObject(info.surface) catch |err| return toVkResult(err); const swapchain = SwapchainKHR.create(device, allocator, surface, info) catch |err| return toVkResult(err); - p_swapchain.* = (NonDispatchable(SwapchainKHR).wrap(allocator, swapchain) catch |err| return toVkResult(err)).toVkHandle(vk.SwapchainKHR); + p_swapchain.* = wrapNonDispatchable(SwapchainKHR, allocator, swapchain, vk.SwapchainKHR) catch |err| return toVkResult(err); return .success; } @@ -2183,7 +2265,7 @@ pub export fn apeCreateWaylandSurfaceKHR(p_instance: vk.Instance, info: *const v const allocator = VulkanAllocator.init(callbacks, .object).allocator(); const instance = Dispatchable(Instance).fromHandleObject(p_instance) catch |err| return toVkResult(err); const surface = WaylandSurfaceKHR.create(instance, allocator, info) catch |err| return toVkResult(err); - p_surface.* = (NonDispatchable(SurfaceKHR).wrap(allocator, surface) catch |err| return toVkResult(err)).toVkHandle(vk.SurfaceKHR); + p_surface.* = wrapNonDispatchable(SurfaceKHR, allocator, surface, vk.SurfaceKHR) catch |err| return toVkResult(err); return .success; } else { return .error_unknown; diff --git a/src/vulkan/utils.zig b/src/vulkan/utils.zig index 88413e8..1f51ad7 100644 --- a/src/vulkan/utils.zig +++ b/src/vulkan/utils.zig @@ -1,3 +1,29 @@ +const std = @import("std"); +const vk = @import("vulkan"); + +pub fn boundedName(name: [*:0]const u8, max_len: usize) ?[]const u8 { + const bytes = name[0..max_len]; + const len = std.mem.indexOfScalar(u8, bytes, 0) orelse return null; + return bytes[0..len]; +} + +pub fn propertyName(comptime max_len: usize, name: *const [max_len]u8) []const u8 { + return std.mem.sliceTo(name[0..], 0); +} + +pub fn extensionName(name: *const [vk.MAX_EXTENSION_NAME_SIZE]u8) []const u8 { + return propertyName(vk.MAX_EXTENSION_NAME_SIZE, name); +} + +pub fn isSupportedExtension(name: []const u8, extensions: []const vk.ExtensionProperties) bool { + for (extensions) |extension| { + if (std.mem.eql(u8, name, extensionName(&extension.extension_name))) { + return true; + } + } + return false; +} + pub fn writePacked(comptime T: type, bytes: []u8, value: T) void { const raw: [@sizeOf(T)]u8 = @bitCast(value); @memcpy(bytes[0..@sizeOf(T)], raw[0..]); diff --git a/src/vulkan/wsi/SwapchainKHR.zig b/src/vulkan/wsi/SwapchainKHR.zig index 3d40aae..8f588e9 100644 --- a/src/vulkan/wsi/SwapchainKHR.zig +++ b/src/vulkan/wsi/SwapchainKHR.zig @@ -68,8 +68,8 @@ pub fn getNextImage(self: *const Self, timeout: u64, semaphore: ?*BinarySemaphor if (image.state == .Available) { image.state = .Drawing; index.* = @intCast(i); - // TODO: signal semaphore - _ = semaphore; + if (semaphore) |s| + try s.signal(); if (fence) |f| try f.signal(); return;