From 7933bea68aa2d412cfb05efac4d12e31490acf52 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Tue, 30 Jun 2026 14:36:20 +0200 Subject: [PATCH] fixing synchronization issues, implementing missing entrypoints --- README.md | 10 +- src/intel/FlintCommandBuffer.zig | 28 +++ src/software/SoftCommandBuffer.zig | 103 ++++++++ src/software/SoftFence.zig | 50 +++- src/software/device/ComputeDispatcher.zig | 14 +- src/software/device/Renderer.zig | 15 ++ src/software/device/rasterizer.zig | 2 +- src/software/device/rasterizer/bresenham.zig | 232 +++++++++++++----- src/software/device/rasterizer/common.zig | 46 +++- .../device/rasterizer/edge_function.zig | 47 +++- src/vulkan/CommandBuffer.zig | 20 ++ src/vulkan/Fence.zig | 38 +++ src/vulkan/Pipeline.zig | 8 + src/vulkan/QueryPool.zig | 10 + src/vulkan/lib_vulkan.zig | 54 ++-- 15 files changed, 547 insertions(+), 130 deletions(-) diff --git a/README.md b/README.md index 7b669d9..3f242e3 100644 --- a/README.md +++ b/README.md @@ -89,11 +89,11 @@ vkCmdResetEvent | ✅ Implemented vkCmdResetQueryPool | ✅ Implemented vkCmdResolveImage | ✅ Implemented vkCmdSetBlendConstants | ✅ Implemented -vkCmdSetDepthBias | ⚙️ WIP -vkCmdSetDepthBounds | ⚙️ WIP +vkCmdSetDepthBias | ✅ Implemented +vkCmdSetDepthBounds | ✅ Implemented vkCmdSetDeviceMaskKHR | ✅ Implemented vkCmdSetEvent | ✅ Implemented -vkCmdSetLineWidth | ⚙️ WIP +vkCmdSetLineWidth | ✅ Implemented vkCmdSetScissor | ✅ Implemented vkCmdSetStencilCompareMask | ✅ Implemented vkCmdSetStencilReference | ✅ Implemented @@ -101,7 +101,7 @@ vkCmdSetStencilWriteMask | ✅ Implemented vkCmdSetViewport | ✅ Implemented vkCmdUpdateBuffer | ✅ Implemented vkCmdWaitEvents | ✅ Implemented -vkCmdWriteTimestamp | ⚙️ WIP +vkCmdWriteTimestamp | ✅ Implemented vkCreateBuffer | ✅ Implemented vkCreateBufferView | ✅ Implemented vkCreateCommandPool | ✅ Implemented @@ -169,7 +169,7 @@ vkGetBufferMemoryRequirements | ✅ Implemented vkGetDeviceGroupPeerMemoryFeaturesKHR | ✅ Implemented vkGetDeviceGroupPresentCapabilitiesKHR | ✅ Implemented vkGetDeviceGroupSurfacePresentModesKHR | ✅ Implemented -vkGetDeviceMemoryCommitment | ⚙️ WIP +vkGetDeviceMemoryCommitment | ✅ Implemented vkGetDeviceProcAddr | ✅ Implemented vkGetDeviceQueue | ✅ Implemented vkGetEventStatus | ✅ Implemented diff --git a/src/intel/FlintCommandBuffer.zig b/src/intel/FlintCommandBuffer.zig index 9f919d1..91c5b4f 100644 --- a/src/intel/FlintCommandBuffer.zig +++ b/src/intel/FlintCommandBuffer.zig @@ -53,7 +53,10 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v .resolveImage = resolveImage, .setEvent = setEvent, .setBlendConstants = setBlendConstants, + .setDepthBias = setDepthBias, + .setDepthBounds = setDepthBounds, .setDeviceMask = setDeviceMask, + .setLineWidth = setLineWidth, .setScissor = setScissor, .setStencilCompareMask = setStencilCompareMask, .setStencilReference = setStencilReference, @@ -61,6 +64,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v .setViewport = setViewport, .updateBuffer = updateBuffer, .waitEvent = waitEvent, + .writeTimestamp = writeTimestamp, }; self.* = .{ .interface = interface }; @@ -363,6 +367,24 @@ pub fn setBlendConstants(interface: *Interface, constants: [4]f32) VkError!void _ = constants; } +pub fn setDepthBias(interface: *Interface, constant_factor: f32, clamp: f32, slope_factor: f32) VkError!void { + _ = interface; + _ = constant_factor; + _ = clamp; + _ = slope_factor; +} + +pub fn setDepthBounds(interface: *Interface, min: f32, max: f32) VkError!void { + _ = interface; + _ = min; + _ = max; +} + +pub fn setLineWidth(interface: *Interface, width: f32) VkError!void { + _ = interface; + _ = width; +} + pub fn setStencilCompareMask(interface: *Interface, face_mask: vk.StencilFaceFlags, compare_mask: u32) VkError!void { _ = interface; _ = face_mask; @@ -390,3 +412,9 @@ pub fn waitEvent(interface: *Interface, event: *base.Event, src_stage: vk.Pipeli _ = buffer_barriers; _ = image_barriers; } + +pub fn writeTimestamp(interface: *Interface, stage: vk.PipelineStageFlags, pool: *base.QueryPool, query: u32) VkError!void { + _ = interface; + _ = stage; + try pool.writeTimestamp(query, 0); +} diff --git a/src/software/SoftCommandBuffer.zig b/src/software/SoftCommandBuffer.zig index 1bfdb88..4bd8d05 100644 --- a/src/software/SoftCommandBuffer.zig +++ b/src/software/SoftCommandBuffer.zig @@ -113,7 +113,10 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v .resolveImage = resolveImage, .setEvent = setEvent, .setBlendConstants = setBlendConstants, + .setDepthBias = setDepthBias, + .setDepthBounds = setDepthBounds, .setDeviceMask = setDeviceMask, + .setLineWidth = setLineWidth, .setScissor = setScissor, .setStencilCompareMask = setStencilCompareMask, .setStencilReference = setStencilReference, @@ -121,6 +124,7 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v .setViewport = setViewport, .updateBuffer = updateBuffer, .waitEvent = waitEvent, + .writeTimestamp = writeTimestamp, }; self.* = .{ @@ -283,6 +287,36 @@ pub fn resetQueryPool(interface: *Interface, pool: *base.QueryPool, first: u32, self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; } +pub fn writeTimestamp(interface: *Interface, stage: vk.PipelineStageFlags, pool: *base.QueryPool, query: u32) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const allocator = self.command_allocator.allocator(); + + const CommandImpl = struct { + const Impl = @This(); + + stage: vk.PipelineStageFlags, + pool: *base.QueryPool, + query: u32, + + pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { + const impl: *Impl = @ptrCast(@alignCast(context)); + _ = impl.stage; + const io = device.renderer.device.interface.io(); + const now = std.Io.Timestamp.now(io, .real).toNanoseconds(); + try impl.pool.writeTimestamp(impl.query, if (now > 0) @intCast(now) else 0); + } + }; + + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(cmd); + cmd.* = .{ + .stage = stage, + .pool = pool, + .query = query, + }; + self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; +} + pub fn beginRenderPass(interface: *Interface, render_pass: *base.RenderPass, framebuffer: *base.Framebuffer, render_area: vk.Rect2D, clear_values: ?[]const vk.ClearValue) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const allocator = self.command_allocator.allocator(); @@ -1330,6 +1364,75 @@ pub fn setBlendConstants(interface: *Interface, constants: [4]f32) VkError!void self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; } +pub fn setDepthBias(interface: *Interface, constant_factor: f32, clamp: f32, slope_factor: f32) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const allocator = self.command_allocator.allocator(); + + const CommandImpl = struct { + const Impl = @This(); + + depth_bias: @import("device/Renderer.zig").DepthBias, + + pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { + const impl: *Impl = @ptrCast(@alignCast(context)); + device.renderer.dynamic_state.depth_bias = impl.depth_bias; + } + }; + + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(cmd); + cmd.* = .{ + .depth_bias = .{ + .constant_factor = constant_factor, + .clamp = clamp, + .slope_factor = slope_factor, + }, + }; + self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; +} + +pub fn setDepthBounds(interface: *Interface, min: f32, max: f32) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const allocator = self.command_allocator.allocator(); + + const CommandImpl = struct { + const Impl = @This(); + + depth_bounds: @import("device/Renderer.zig").DepthBounds, + + pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { + const impl: *Impl = @ptrCast(@alignCast(context)); + device.renderer.dynamic_state.depth_bounds = impl.depth_bounds; + } + }; + + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(cmd); + cmd.* = .{ .depth_bounds = .{ .min = min, .max = max } }; + self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; +} + +pub fn setLineWidth(interface: *Interface, width: f32) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const allocator = self.command_allocator.allocator(); + + const CommandImpl = struct { + const Impl = @This(); + + width: f32, + + pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { + const impl: *Impl = @ptrCast(@alignCast(context)); + device.renderer.dynamic_state.line_width = impl.width; + } + }; + + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(cmd); + cmd.* = .{ .width = width }; + self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; +} + pub fn setStencilCompareMask(interface: *Interface, face_mask: vk.StencilFaceFlags, compare_mask: u32) VkError!void { try setStencilDynamicState(interface, face_mask, compare_mask, .compare_mask); } diff --git a/src/software/SoftFence.zig b/src/software/SoftFence.zig index c8820cc..b302b7a 100644 --- a/src/software/SoftFence.zig +++ b/src/software/SoftFence.zig @@ -43,6 +43,11 @@ pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { pub fn getStatus(interface: *Interface) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const io = interface.owner.io(); + + self.mutex.lock(io) catch return VkError.DeviceLost; + defer self.mutex.unlock(io); + if (!self.is_signaled) { return VkError.NotReady; } @@ -50,6 +55,11 @@ pub fn getStatus(interface: *Interface) VkError!void { pub fn reset(interface: *Interface) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const io = interface.owner.io(); + + self.mutex.lock(io) catch return VkError.DeviceLost; + defer self.mutex.unlock(io); + self.is_signaled = false; } @@ -68,18 +78,34 @@ pub fn wait(interface: *Interface, timeout: u64) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const io = interface.owner.io(); - self.mutex.lock(io) catch return VkError.DeviceLost; - defer self.mutex.unlock(io); + if (timeout == std.math.maxInt(@TypeOf(timeout))) { + self.mutex.lock(io) catch return VkError.DeviceLost; + defer self.mutex.unlock(io); - if (self.is_signaled) return; - if (timeout == 0) return VkError.Timeout; - - if (timeout != std.math.maxInt(@TypeOf(timeout))) { - const duration: std.Io.Clock.Duration = .{ - .raw = .fromNanoseconds(@intCast(timeout)), - .clock = .cpu_process, - }; - duration.sleep(io) catch return VkError.DeviceLost; + while (!self.is_signaled) { + self.condition.wait(io, &self.mutex) catch return VkError.DeviceLost; + } + return; + } + + const deadline = std.Io.Clock.Timestamp.fromNow(io, .{ + .raw = .fromNanoseconds(@intCast(timeout)), + .clock = .awake, + }); + while (true) { + { + self.mutex.lock(io) catch return VkError.DeviceLost; + defer self.mutex.unlock(io); + + if (self.is_signaled) return; + } + + const remaining = deadline.durationFromNow(io); + if (remaining.raw.nanoseconds <= 0) return VkError.Timeout; + + (std.Io.Clock.Duration{ + .raw = .fromNanoseconds(@min(remaining.raw.nanoseconds, std.time.ns_per_ms)), + .clock = .awake, + }).sleep(io) catch return VkError.DeviceLost; } - self.condition.wait(io, &self.mutex) catch return VkError.DeviceLost; } diff --git a/src/software/device/ComputeDispatcher.zig b/src/software/device/ComputeDispatcher.zig index afb69de..8794a70 100644 --- a/src/software/device/ComputeDispatcher.zig +++ b/src/software/device/ComputeDispatcher.zig @@ -184,14 +184,16 @@ inline fn run(data: RunData) !void { const workgroup_memory = try rt.createWorkgroupMemory(allocator); defer rt.destroyWorkgroupMemory(allocator, workgroup_memory); + rt.resetInvocation(allocator); + if (rt.specialization_constants.count() != 0) + try rt.applySpecializationInvocationLayout(allocator); + try ExecutionDevice.writeDescriptorSets(data.self.state, rt); + try rt.populatePushConstants(data.self.state.push_constant_blob[0..]); + try rt.bindWorkgroupMemory(workgroup_memory); + try setupWorkgroupBuiltins(data.self, rt, data.local_size, group_count_vec, group_id_vec); + for (0..data.invocations_per_workgroup) |i| { rt.resetInvocation(allocator); - if (rt.specialization_constants.count() != 0) - try rt.applySpecializationInvocationLayout(allocator); - try ExecutionDevice.writeDescriptorSets(data.self.state, rt); - try rt.populatePushConstants(data.self.state.push_constant_blob[0..]); - try rt.bindWorkgroupMemory(workgroup_memory); - try setupWorkgroupBuiltins(data.self, rt, data.local_size, group_count_vec, group_id_vec); const invocation_index = data.self.invocation_index.fetchAdd(1, .monotonic); diff --git a/src/software/device/Renderer.zig b/src/software/device/Renderer.zig index fdbffd3..3b3c477 100644 --- a/src/software/device/Renderer.zig +++ b/src/software/device/Renderer.zig @@ -45,6 +45,8 @@ pub const DynamicState = struct { viewports: ?[]const vk.Viewport, scissor: ?[]const vk.Rect2D, line_width: ?f32, + depth_bias: ?DepthBias, + depth_bounds: ?DepthBounds, blend_constants: ?[4]f32, stencil_front_compare_mask: ?u32, stencil_back_compare_mask: ?u32, @@ -54,6 +56,17 @@ pub const DynamicState = struct { stencil_back_reference: ?u32, }; +pub const DepthBias = struct { + constant_factor: f32, + clamp: f32, + slope_factor: f32, +}; + +pub const DepthBounds = struct { + min: f32, + max: f32, +}; + pub const Vertex = struct { primitive_restart: bool, position: F32x4, @@ -158,6 +171,8 @@ pub fn init(device: *SoftDevice, state: *PipelineState, active_occlusion_queries .viewports = null, .scissor = null, .line_width = null, + .depth_bias = null, + .depth_bounds = null, .blend_constants = null, .stencil_front_compare_mask = null, .stencil_back_compare_mask = null, diff --git a/src/software/device/rasterizer.zig b/src/software/device/rasterizer.zig index 45edb9d..959473e 100644 --- a/src/software/device/rasterizer.zig +++ b/src/software/device/rasterizer.zig @@ -271,7 +271,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato v0, v1, v2, - v0, + v1, color_attachment_access, if (depth_attachment_access) |*access| access else null, if (stencil_attachment_access) |*access| access else null, diff --git a/src/software/device/rasterizer/bresenham.zig b/src/software/device/rasterizer/bresenham.zig index bad5334..4ad72b9 100644 --- a/src/software/device/rasterizer/bresenham.zig +++ b/src/software/device/rasterizer/bresenham.zig @@ -26,6 +26,7 @@ const RunData = struct { steep: bool, start_vertex: *Renderer.Vertex, end_vertex: *Renderer.Vertex, + provoking_vertex: *Renderer.Vertex, start_step: usize, end_step: usize, color_attachment_access: []const ?common.RenderTargetAccess, @@ -68,78 +69,183 @@ fn drawLineWithEndpointMode( stencil_attachment_access: ?*common.RenderTargetAccess, include_last_endpoint: bool, ) VkError!void { - const io = draw_call.renderer.device.interface.io(); - - var x0: i32 = @intFromFloat(v0.position[0]); - var y0: i32 = @intFromFloat(@floor(v0.position[1] - 0.5)); - var x1: i32 = @intFromFloat(v1.position[0]); - var y1: i32 = @intFromFloat(@floor(v1.position[1] - 0.5)); - - const steep = blk: { - if (@abs(y1 - y0) > @abs(x1 - x0)) { - std.mem.swap(i32, &x0, &y0); - std.mem.swap(i32, &x1, &y1); - break :blk true; - } - break :blk false; - }; - - var start_vertex = v0; - var end_vertex = v1; - if (x0 > x1) { - std.mem.swap(i32, &x0, &x1); - std.mem.swap(i32, &y0, &y1); - std.mem.swap(*Renderer.Vertex, &start_vertex, &end_vertex); - } - - const d_err: i32 = @intCast(@abs(y1 - y0)); - const d_x = x1 - x0; - const y_step: i32 = if (y0 > y1) -1 else 1; + try drawLineDiamond( + allocator, + draw_call, + v0, + v1, + color_attachment_access, + depth_attachment_access, + stencil_attachment_access, + include_last_endpoint, + ); +} +fn drawLineDiamond( + allocator: std.mem.Allocator, + draw_call: *Renderer.DrawCall, + v0: *Renderer.Vertex, + v1: *Renderer.Vertex, + color_attachment_access: []const ?common.RenderTargetAccess, + depth_attachment_access: ?*common.RenderTargetAccess, + stencil_attachment_access: ?*common.RenderTargetAccess, + include_last_endpoint: bool, +) VkError!void { const pipeline = draw_call.renderer.state.pipeline orelse return; const fragment_stage = pipeline.stages.getPtr(.fragment); - const runtimes_count = if (fragment_stage) |stage| stage.runtimes.len else 1; - if (runtimes_count == 0) - return; + const has_fragment_shader = fragment_stage != null; + const batch_id: usize = 0; - const step_count: usize = @intCast(if (include_last_endpoint) @max(d_x, 0) + 1 else @max(d_x, 1)); - const runs_count = @min(runtimes_count, step_count); - const steps_per_run = @divTrunc(step_count + runs_count - 1, runs_count); + const min_x: i32 = @intFromFloat(@floor(@min(v0.position[0], v1.position[0]) - 1.0)); + const max_x: i32 = @intFromFloat(@ceil(@max(v0.position[0], v1.position[0]) + 1.0)); + const min_y: i32 = @intFromFloat(@floor(@min(v0.position[1], v1.position[1]) - 1.0)); + const max_y: i32 = @intFromFloat(@ceil(@max(v0.position[1], v1.position[1]) + 1.0)); - var batch_id: usize = 0; - for (0..runs_count) |run_index| { - defer batch_id = @mod(batch_id + 1, runtimes_count); + var y = min_y; + while (y <= max_y) : (y += 1) { + var x = min_x; + while (x <= max_x) : (x += 1) { + if (!common.scissorContainsPixel(draw_call.scissor, x, y)) + continue; - const start_step = run_index * steps_per_run; - if (start_step >= step_count) + const coverage = diamondCoverage(v0.position, v1.position, x, y, include_last_endpoint); + const t = coverage orelse continue; + const z = ((1.0 - t) * v0.position[2]) + (t * v1.position[2]); + const frag_w = ((1.0 - t) / v0.position[3]) + (t / v1.position[3]); + + var fragment_result: fragment.InvocationResult = .{ + .outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8), + .depth = null, + .sample_mask = null, + }; + if (has_fragment_shader) { + fragment_result = fragment.shaderInvocation( + allocator, + draw_call, + batch_id, + zm.f32x4(@as(f32, @floatFromInt(x)) + 0.5, @as(f32, @floatFromInt(y)) + 0.5, z, frag_w), + null, + null, + true, + try common.interpolateLineOutputs(allocator, v0, v1, v0, t), + null, + ) catch |err| { + if (err == SpvRuntimeError.Killed) + continue; + + std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)}); + if (comptime base.config.logs == .verbose) { + if (@errorReturnTrace()) |trace| { + std.debug.dumpErrorReturnTrace(trace); + } + } + return; + }; + } + + try common.writeToTargets( + fragment_result.outputs, + draw_call, + color_attachment_access, + depth_attachment_access, + stencil_attachment_access, + true, + @intCast(x), + @intCast(y), + fragment_result.depth orelse z, + lineCoverageMaskFloat( + v0.position, + v1.position, + x, + y, + pipeline.interface.mode.graphics.multisample.rasterization_samples.toInt(), + ), + fragment_result.sample_mask, + false, + ); + } + } +} + +fn diamondCoverage(a: F32x4, b: F32x4, pixel_x: i32, pixel_y: i32, include_last_endpoint: bool) ?f32 { + const cx = @as(f32, @floatFromInt(pixel_x)) + 0.5; + const cy = @as(f32, @floatFromInt(pixel_y)) + 0.5; + const clipped = clipSegmentToDiamond(a[0], a[1], b[0], b[1], cx, cy) orelse return null; + if (!include_last_endpoint and a[3] == 1.0 and b[3] == 1.0 and clipped.min_t <= 0.0) + return null; + if (!include_last_endpoint and clipped.min_t >= 1.0) + return null; + return std.math.clamp((clipped.min_t + clipped.max_t) * 0.5, 0.0, 1.0); +} + +fn clipSegmentToDiamond(ax: f32, ay: f32, bx: f32, by: f32, cx: f32, cy: f32) ?struct { min_t: f32, max_t: f32 } { + var min_t: f32 = 0.0; + var max_t: f32 = 1.0; + const dx = bx - ax; + const dy = by - ay; + + const normals = [_]struct { nx: f32, ny: f32 }{ + .{ .nx = 1.0, .ny = 1.0 }, + .{ .nx = 1.0, .ny = -1.0 }, + .{ .nx = -1.0, .ny = 1.0 }, + .{ .nx = -1.0, .ny = -1.0 }, + }; + + for (normals) |normal| { + const origin_distance = normal.nx * (ax - cx) + normal.ny * (ay - cy) - 0.5; + const delta_distance = normal.nx * dx + normal.ny * dy; + if (delta_distance == 0.0) { + if (origin_distance > 0.0) + return null; continue; + } - const end_step = @min(start_step + steps_per_run - 1, step_count - 1); - - const run_data: RunData = .{ - .allocator = allocator, - .draw_call = draw_call, - .batch_id = batch_id, - .x0 = x0, - .y0 = y0, - .d_x = d_x, - .d_err = d_err, - .y_step = y_step, - .steep = steep, - .start_vertex = start_vertex, - .end_vertex = end_vertex, - .start_step = start_step, - .end_step = end_step, - .color_attachment_access = color_attachment_access, - .depth_attachment_access = depth_attachment_access, - .stencil_attachment_access = stencil_attachment_access, - .has_fragment_shader = fragment_stage != null, - }; - - draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data}); + const t = -origin_distance / delta_distance; + if (delta_distance > 0.0) { + max_t = @min(max_t, t); + } else { + min_t = @max(min_t, t); + } + if (min_t > max_t) + return null; } - draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost; + return .{ .min_t = min_t, .max_t = max_t }; +} + +fn lineCoverageMaskFloat(a: F32x4, b: F32x4, pixel_x: i32, pixel_y: i32, sample_count: usize) vk.SampleMask { + if (sample_count <= 1) + return 1; + + const ab_x = b[0] - a[0]; + const ab_y = b[1] - a[1]; + const ab_len2 = ab_x * ab_x + ab_y * ab_y; + if (ab_len2 == 0.0) + return 1; + + var mask: vk.SampleMask = 0; + for (0..sample_count) |sample_index| { + if (sample_index >= @bitSizeOf(vk.SampleMask)) + break; + + const sample_pos = standardSamplePosition(sample_count, sample_index); + const sample_x = @as(f32, @floatFromInt(pixel_x)) + sample_pos.x; + const sample_y = @as(f32, @floatFromInt(pixel_y)) + sample_pos.y; + const ap_x = sample_x - a[0]; + const ap_y = sample_y - a[1]; + const t = std.math.clamp(((ap_x * ab_x) + (ap_y * ab_y)) / ab_len2, 0.0, 1.0); + const closest_x = a[0] + ab_x * t; + const closest_y = a[1] + ab_y * t; + const dx = sample_x - closest_x; + const dy = sample_y - closest_y; + + if (dx * dx + dy * dy <= 0.25) { + const bit_index: u5 = @intCast(sample_index); + mask |= @as(vk.SampleMask, 1) << bit_index; + } + } + + return if (mask == 0) 1 else mask; } fn bresenhamYAtStep(y0: i32, d_x: i32, d_err: i32, y_step: i32, step: usize) i32 { @@ -249,7 +355,7 @@ inline fn run(data: RunData) !void { null, null, true, - try common.interpolateLineOutputs(data.allocator, data.start_vertex, data.end_vertex, t), + try common.interpolateLineOutputs(data.allocator, data.start_vertex, data.end_vertex, data.provoking_vertex, t), null, ) catch |err| { if (err == SpvRuntimeError.Killed) diff --git a/src/software/device/rasterizer/common.zig b/src/software/device/rasterizer/common.zig index c908ef8..a66c50f 100644 --- a/src/software/device/rasterizer/common.zig +++ b/src/software/device/rasterizer/common.zig @@ -31,6 +31,27 @@ pub const VertexInterpolation = struct { pub const VertexInterpolationLocation = [4]VertexInterpolation; +pub fn depthBiasConstantUnit(format: vk.Format, z: f32) f32 { + return switch (format) { + .d16_unorm => 1.0 / @as(f32, @floatFromInt(std.math.maxInt(u16))), + .x8_d24_unorm_pack32, + .d24_unorm_s8_uint, + => 1.0 / @as(f32, @floatFromInt(0x00ff_ffff)), + .d32_sfloat, + .d32_sfloat_s8_uint, + => if (z > 0.0) std.math.pow(f32, 2.0, @floor(@log2(z)) - 23.0) else 0.0, + else => 0.0, + }; +} + +pub fn clampDepthBias(bias: f32, clamp: f32) f32 { + if (clamp > 0.0) + return @min(bias, clamp); + if (clamp < 0.0) + return @max(bias, clamp); + return bias; +} + pub fn scissorContainsPixel(scissor: vk.Rect2D, x: i32, y: i32) bool { const min_x: i64 = @as(i64, scissor.offset.x); const min_y: i64 = @as(i64, scissor.offset.y); @@ -172,6 +193,20 @@ pub fn depthTestAndUpdate(depth: *RenderTargetAccess, x: usize, y: usize, z: f32 return depthTestAndUpdateAtOffset(depth, offset, z, state); } +pub fn resolveDepthStencilState(draw_call: *Renderer.DrawCall, state: vk.PipelineDepthStencilStateCreateInfo) vk.PipelineDepthStencilStateCreateInfo { + var resolved = state; + const pipeline_data = draw_call.renderer.state.pipeline.?.interface.mode.graphics; + if (pipeline_data.dynamic_state.depth_bounds) { + const bounds = draw_call.renderer.dynamic_state.depth_bounds orelse Renderer.DepthBounds{ + .min = 0.0, + .max = 1.0, + }; + resolved.min_depth_bounds = bounds.min; + resolved.max_depth_bounds = bounds.max; + } + return resolved; +} + pub fn depthTestSampleAndUpdate( io: std.Io, depth: *RenderTargetAccess, @@ -198,10 +233,14 @@ pub fn depthTestSampleAndUpdate( } fn depthTestAndUpdateAtOffset(depth: *RenderTargetAccess, offset: usize, z: f32, state: vk.PipelineDepthStencilStateCreateInfo) bool { + const reference = quantizeDepthForFormat(depth.format, z); + if (state.depth_bounds_test_enable == .true and + (reference < state.min_depth_bounds or reference > state.max_depth_bounds)) + return false; + if (state.depth_test_enable == .false) return true; - const reference = quantizeDepthForFormat(depth.format, z); const depth_value = blitter.readFloat4(depth.base[offset..], depth.format); const passed = compare(f32, state.depth_compare_op, reference, depth_value[0]); if (passed and state.depth_write_enable == .true) @@ -287,9 +326,10 @@ pub fn interpolateLineOutputs( allocator: std.mem.Allocator, v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, + provoking_vertex: *const Renderer.Vertex, t: f32, ) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation { - return interpolateVertexOutputs(allocator, v0, v1, v0, v0, 1.0 - t, t, 0.0); + return interpolateVertexOutputs(allocator, v0, v1, v0, provoking_vertex, 1.0 - t, t, 0.0); } pub fn interpolateVertexOutputDerivatives( @@ -506,7 +546,7 @@ pub fn writeToTargets( ) VkError!void { const io = draw_call.renderer.device.interface.io(); const pipeline_data = draw_call.renderer.state.pipeline.?.interface.mode.graphics; - const depth_stencil_state = pipeline_data.depth_stencil; + const depth_stencil_state = if (pipeline_data.depth_stencil) |state| resolveDepthStencilState(draw_call, state) else null; const effective_fragment_sample_mask = alphaToCoverageMask( pipeline_data.multisample, outputs, diff --git a/src/software/device/rasterizer/edge_function.zig b/src/software/device/rasterizer/edge_function.zig index b2a6a41..ec0778e 100644 --- a/src/software/device/rasterizer/edge_function.zig +++ b/src/software/device/rasterizer/edge_function.zig @@ -40,6 +40,7 @@ const RunData = struct { fragment_uses_derivatives: bool, fragment_uses_sample_id: bool, fragment_uses_centroid: bool, + depth_bias_slope: f32, }; pub fn drawTriangle( @@ -64,6 +65,16 @@ pub fn drawTriangle( const area = edgeFunction(v0.position, v1.position, v2.position); if (area == 0.0) return; + const inv_area = 1.0 / area; + const dz_dx = + (v0.position[2] * ((v1.position[1] - v2.position[1]) * inv_area)) + + (v1.position[2] * ((v2.position[1] - v0.position[1]) * inv_area)) + + (v2.position[2] * ((v0.position[1] - v1.position[1]) * inv_area)); + const dz_dy = + (v0.position[2] * ((v2.position[0] - v1.position[0]) * inv_area)) + + (v1.position[2] * ((v0.position[0] - v2.position[0]) * inv_area)) + + (v2.position[2] * ((v1.position[0] - v0.position[0]) * inv_area)); + const depth_bias_slope = @max(@abs(dz_dx), @abs(dz_dy)); const pipeline = draw_call.renderer.state.pipeline orelse return; const fragment_stage = pipeline.stages.getPtr(.fragment); @@ -139,6 +150,7 @@ pub fn drawTriangle( .fragment_uses_derivatives = fragment_uses_derivatives, .fragment_uses_sample_id = fragment_uses_sample_id, .fragment_uses_centroid = fragment_uses_centroid, + .depth_bias_slope = depth_bias_slope, }; draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data}); @@ -250,6 +262,7 @@ fn applyEarlyDepth(data: RunData, coverage_sample_mask: vk.SampleMask, x: i32, y const depth = data.depth_attachment_access orelse return .{ .mask = coverage_sample_mask, .applied = false }; const pipeline_data = data.draw_call.renderer.state.pipeline.?.interface.mode.graphics; + const depth_stencil_state = if (pipeline_data.depth_stencil) |state| common.resolveDepthStencilState(data.draw_call, state) else null; const io = data.draw_call.renderer.device.interface.io(); var passed_mask: vk.SampleMask = 0; @@ -261,13 +274,38 @@ fn applyEarlyDepth(data: RunData, coverage_sample_mask: vk.SampleMask, x: i32, y if ((coverage_sample_mask & bit) == 0) continue; - if (try common.depthTestSampleAndUpdate(io, depth, @intCast(x), @intCast(y), sample_index, z, pipeline_data.depth_stencil)) + if (try common.depthTestSampleAndUpdate(io, depth, @intCast(x), @intCast(y), sample_index, z, depth_stencil_state)) passed_mask |= bit; } return .{ .mask = passed_mask, .applied = true }; } +fn biasedDepth(data: RunData, z: f32) f32 { + const pipeline_data = data.draw_call.renderer.state.pipeline.?.interface.mode.graphics; + if (pipeline_data.rasterization.depth_bias_enable == .false) + return z; + + const depth = data.depth_attachment_access orelse return z; + const bias_state: Renderer.DepthBias = if (pipeline_data.dynamic_state.depth_bias) + data.draw_call.renderer.dynamic_state.depth_bias orelse Renderer.DepthBias{ + .constant_factor = 0.0, + .clamp = 0.0, + .slope_factor = 0.0, + } + else + Renderer.DepthBias{ + .constant_factor = pipeline_data.rasterization.depth_bias_constant_factor, + .clamp = pipeline_data.rasterization.depth_bias_clamp, + .slope_factor = pipeline_data.rasterization.depth_bias_slope_factor, + }; + + const bias = + bias_state.constant_factor * common.depthBiasConstantUnit(depth.format, z) + + bias_state.slope_factor * data.depth_bias_slope; + return z + common.clampDepthBias(bias, bias_state.clamp); +} + fn runWrapper(data: RunData) void { @call(.always_inline, run, .{data}) catch |err| { std.log.scoped(.@"Rasterization stage").err("triangle fill mode catched a '{s}'", .{@errorName(err)}); @@ -304,8 +342,9 @@ inline fn run(data: RunData) !void { const b1 = w1 / data.area; const b2 = w2 / data.area; const z = (b0 * data.v0.position[2]) + (b1 * data.v1.position[2]) + (b2 * data.v2.position[2]); + const depth_z = biasedDepth(data, z); const frag_w = (b0 / data.v0.position[3]) + (b1 / data.v1.position[3]) + (b2 / data.v2.position[3]); - const early_depth = try applyEarlyDepth(data, coverage_sample_mask, x, y, z, sample_count); + const early_depth = try applyEarlyDepth(data, coverage_sample_mask, x, y, depth_z, sample_count); if (early_depth.mask == 0) continue; @@ -378,7 +417,7 @@ inline fn run(data: RunData) !void { data.front_face, @intCast(x), @intCast(y), - sample_result.depth orelse z, + sample_result.depth orelse depth_z, sample_coverage_mask, sample_result.sample_mask, early_depth.applied, @@ -460,7 +499,7 @@ inline fn run(data: RunData) !void { data.front_face, @intCast(x), @intCast(y), - fragment_result.depth orelse z, + fragment_result.depth orelse depth_z, early_depth.mask, fragment_result.sample_mask, early_depth.applied, diff --git a/src/vulkan/CommandBuffer.zig b/src/vulkan/CommandBuffer.zig index 3664b04..29aab4c 100644 --- a/src/vulkan/CommandBuffer.zig +++ b/src/vulkan/CommandBuffer.zig @@ -78,7 +78,10 @@ pub const DispatchTable = struct { resolveImage: *const fn (*Self, *Image, vk.ImageLayout, *Image, vk.ImageLayout, vk.ImageResolve) VkError!void, setEvent: *const fn (*Self, *Event, vk.PipelineStageFlags) VkError!void, setBlendConstants: *const fn (*Self, [4]f32) VkError!void, + setDepthBias: *const fn (*Self, f32, f32, f32) VkError!void, + setDepthBounds: *const fn (*Self, f32, f32) VkError!void, setDeviceMask: *const fn (*Self, u32) VkError!void, + setLineWidth: *const fn (*Self, f32) VkError!void, setScissor: *const fn (*Self, u32, []const vk.Rect2D) VkError!void, setStencilCompareMask: *const fn (*Self, vk.StencilFaceFlags, u32) VkError!void, setStencilReference: *const fn (*Self, vk.StencilFaceFlags, u32) VkError!void, @@ -86,6 +89,7 @@ pub const DispatchTable = struct { setViewport: *const fn (*Self, u32, []const vk.Viewport) VkError!void, updateBuffer: *const fn (*Self, *Buffer, vk.DeviceSize, []const u8) VkError!void, waitEvent: *const fn (*Self, *Event, vk.PipelineStageFlags, vk.PipelineStageFlags, []const vk.MemoryBarrier, []const vk.BufferMemoryBarrier, []const vk.ImageMemoryBarrier) VkError!void, + writeTimestamp: *const fn (*Self, vk.PipelineStageFlags, *QueryPool, u32) VkError!void, }; pub const VTable = struct { @@ -338,6 +342,14 @@ pub inline fn setBlendConstants(self: *Self, constants: [4]f32) VkError!void { try self.dispatch_table.setBlendConstants(self, constants); } +pub inline fn setDepthBias(self: *Self, constant_factor: f32, clamp: f32, slope_factor: f32) VkError!void { + try self.dispatch_table.setDepthBias(self, constant_factor, clamp, slope_factor); +} + +pub inline fn setDepthBounds(self: *Self, min: f32, max: f32) VkError!void { + try self.dispatch_table.setDepthBounds(self, min, max); +} + pub inline fn setDeviceMask(self: *Self, device_mask: u32) VkError!void { if (device_mask != 1) return VkError.ValidationFailed; @@ -345,6 +357,10 @@ pub inline fn setDeviceMask(self: *Self, device_mask: u32) VkError!void { try self.dispatch_table.setDeviceMask(self, device_mask); } +pub inline fn setLineWidth(self: *Self, width: f32) VkError!void { + try self.dispatch_table.setLineWidth(self, width); +} + pub inline fn setScissor(self: *Self, first: u32, scissor: []const vk.Rect2D) VkError!void { try self.dispatch_table.setScissor(self, first, scissor); } @@ -376,3 +392,7 @@ pub inline fn waitEvent( ) VkError!void { try self.dispatch_table.waitEvent(self, event, src_stage, dst_stage, memory_barriers, buffer_barriers, image_barriers); } + +pub inline fn writeTimestamp(self: *Self, stage: vk.PipelineStageFlags, pool: *QueryPool, query: u32) VkError!void { + try self.dispatch_table.writeTimestamp(self, stage, pool, query); +} diff --git a/src/vulkan/Fence.zig b/src/vulkan/Fence.zig index c30e3fa..55c5f58 100644 --- a/src/vulkan/Fence.zig +++ b/src/vulkan/Fence.zig @@ -48,3 +48,41 @@ pub inline fn signal(self: *Self) VkError!void { pub inline fn wait(self: *Self, timeout: u64) VkError!void { try self.vtable.wait(self, timeout); } + +pub fn waitMany(device: *Device, fences: []const *Self, wait_for_all: vk.Bool32, timeout: u64) VkError!void { + const forever = timeout == std.math.maxInt(@TypeOf(timeout)); + const io = device.io(); + const deadline = if (forever) null else std.Io.Clock.Timestamp.fromNow(io, .{ + .raw = .fromNanoseconds(@intCast(timeout)), + .clock = .awake, + }); + + while (true) { + var signaled_count: usize = 0; + for (fences) |fence| { + if (fence.owner != device) return VkError.InvalidHandleDrv; + + fence.getStatus() catch |err| switch (err) { + VkError.NotReady => continue, + else => return err, + }; + + if (wait_for_all == .false) return; + signaled_count += 1; + } + + if (signaled_count == fences.len) return; + if (timeout == 0) return VkError.Timeout; + + const sleep_ns = if (deadline) |value| blk: { + const remaining = value.durationFromNow(io); + if (remaining.raw.nanoseconds <= 0) return VkError.Timeout; + break :blk @min(remaining.raw.nanoseconds, std.time.ns_per_ms); + } else std.time.ns_per_ms; + + (std.Io.Clock.Duration{ + .raw = .fromNanoseconds(sleep_ns), + .clock = .awake, + }).sleep(io) catch return VkError.DeviceLost; + } +} diff --git a/src/vulkan/Pipeline.zig b/src/vulkan/Pipeline.zig index 218fe47..7fbc3f6 100644 --- a/src/vulkan/Pipeline.zig +++ b/src/vulkan/Pipeline.zig @@ -49,6 +49,10 @@ mode: union(enum) { cull_mode: vk.CullModeFlags, front_face: vk.FrontFace, line_width: f32, + depth_bias_enable: vk.Bool32, + depth_bias_constant_factor: f32, + depth_bias_clamp: f32, + depth_bias_slope_factor: f32, }, multisample: struct { rasterization_samples: vk.SampleCountFlags, @@ -194,6 +198,10 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe .cull_mode = if (info.p_rasterization_state) |state| state.cull_mode else return VkError.ValidationFailed, .front_face = if (info.p_rasterization_state) |state| state.front_face else return VkError.ValidationFailed, .line_width = if (info.p_rasterization_state) |state| state.line_width else return VkError.ValidationFailed, + .depth_bias_enable = if (info.p_rasterization_state) |state| state.depth_bias_enable else return VkError.ValidationFailed, + .depth_bias_constant_factor = if (info.p_rasterization_state) |state| state.depth_bias_constant_factor else return VkError.ValidationFailed, + .depth_bias_clamp = if (info.p_rasterization_state) |state| state.depth_bias_clamp else return VkError.ValidationFailed, + .depth_bias_slope_factor = if (info.p_rasterization_state) |state| state.depth_bias_slope_factor else return VkError.ValidationFailed, }, .multisample = blk: { if (rasterizer_discard_enable) { diff --git a/src/vulkan/QueryPool.zig b/src/vulkan/QueryPool.zig index 5c958f9..952b04a 100644 --- a/src/vulkan/QueryPool.zig +++ b/src/vulkan/QueryPool.zig @@ -69,6 +69,16 @@ pub fn end(self: *Self, query: u32) VkError!void { q.available = true; } +pub fn writeTimestamp(self: *Self, query: u32, value: u64) VkError!void { + if (self.query_type != .timestamp) + return VkError.ValidationFailed; + + const q = try self.queryAt(query); + q.value.store(value, .seq_cst); + q.available = true; + q.active = false; +} + pub fn addSamples(self: *Self, query: u32, samples: u64) VkError!void { const q = try self.queryAt(query); if (q.active) diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index 325646f..7ba1e73 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -1434,13 +1434,11 @@ pub export fn apeGetDeviceMemoryCommitment(p_device: vk.Device, p_memory: vk.Dev defer entryPointEndLogTrace(); const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return errorLogger(err); - const memory = Dispatchable(DeviceMemory).fromHandleObject(p_memory) catch |err| return errorLogger(err); + const memory = NonDispatchable(DeviceMemory).fromHandleObject(p_memory) catch |err| return errorLogger(err); + if (memory.owner != device) + return errorLogger(VkError.InvalidHandleDrv); - notImplementedWarning(); - - _ = device; - _ = memory; - _ = committed_memory; + committed_memory.* = memory.size; } pub export fn apeGetDeviceGroupPeerMemoryFeatures( @@ -1759,13 +1757,17 @@ pub export fn apeWaitForFences(p_device: vk.Device, count: u32, p_fences: [*]con entryPointBeginLogTrace(.vkWaitForFences); defer entryPointEndLogTrace(); - Dispatchable(Device).checkHandleValidity(p_device) catch |err| return toVkResult(err); + const device = Dispatchable(Device).fromHandleObject(p_device) catch |err| return toVkResult(err); - for (p_fences, 0..count) |p_fence, _| { - const fence = NonDispatchable(Fence).fromHandleObject(p_fence) catch |err| return toVkResult(err); - fence.wait(timeout) catch |err| return toVkResult(err); - if (waitForAll == .false) break; + const allocator = VulkanAllocator.init(null, .command).allocator(); + const fences = allocator.alloc(*Fence, count) catch return toVkResult(VkError.OutOfHostMemory); + defer allocator.free(fences); + + for (p_fences[0..count], fences) |p_fence, *fence| { + fence.* = NonDispatchable(Fence).fromHandleObject(p_fence) catch |err| return toVkResult(err); } + + Fence.waitMany(device, fences, waitForAll, timeout) catch |err| return toVkResult(err); return .success; } @@ -2152,13 +2154,7 @@ pub export fn apeCmdSetDepthBias(p_cmd: vk.CommandBuffer, constant_factor: f32, defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = constant_factor; - _ = clamp; - _ = slope_factor; + cmd.setDepthBias(constant_factor, clamp, slope_factor) catch |err| return errorLogger(err); } pub export fn apeCmdSetDepthBounds(p_cmd: vk.CommandBuffer, min: f32, max: f32) callconv(vk.vulkan_call_conv) void { @@ -2166,12 +2162,7 @@ pub export fn apeCmdSetDepthBounds(p_cmd: vk.CommandBuffer, min: f32, max: f32) defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = min; - _ = max; + cmd.setDepthBounds(min, max) catch |err| return errorLogger(err); } pub export fn apeCmdSetDeviceMask(p_cmd: vk.CommandBuffer, device_mask: u32) callconv(vk.vulkan_call_conv) void { @@ -2200,11 +2191,7 @@ pub export fn apeCmdSetLineWidth(p_cmd: vk.CommandBuffer, width: f32) callconv(v defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = width; + cmd.setLineWidth(width) catch |err| return errorLogger(err); } pub export fn apeCmdSetScissor(p_cmd: vk.CommandBuffer, first: u32, count: u32, scissors: [*]const vk.Rect2D) callconv(vk.vulkan_call_conv) void { @@ -2292,13 +2279,8 @@ pub export fn apeCmdWriteTimestamp(p_cmd: vk.CommandBuffer, stage: vk.PipelineSt defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = stage; - _ = p_pool; - _ = query; + const pool = NonDispatchable(QueryPool).fromHandleObject(p_pool) catch |err| return errorLogger(err); + cmd.writeTimestamp(stage, pool, query) catch |err| return errorLogger(err); } pub export fn apeEndCommandBuffer(p_cmd: vk.CommandBuffer) callconv(vk.vulkan_call_conv) vk.Result {