diff --git a/README.md b/README.md index b358406..0c81804 100644 --- a/README.md +++ b/README.md @@ -81,15 +81,15 @@ vkCmdPushConstants | ✅ Implemented vkCmdResetEvent | ✅ Implemented vkCmdResetQueryPool | ⚙️ WIP vkCmdResolveImage | ✅ Implemented -vkCmdSetBlendConstants | ⚙️ WIP +vkCmdSetBlendConstants | ✅ Implemented vkCmdSetDepthBias | ⚙️ WIP vkCmdSetDepthBounds | ⚙️ WIP vkCmdSetEvent | ✅ Implemented vkCmdSetLineWidth | ⚙️ WIP vkCmdSetScissor | ✅ Implemented -vkCmdSetStencilCompareMask | ⚙️ WIP -vkCmdSetStencilReference | ⚙️ WIP -vkCmdSetStencilWriteMask | ⚙️ WIP +vkCmdSetStencilCompareMask | ✅ Implemented +vkCmdSetStencilReference | ✅ Implemented +vkCmdSetStencilWriteMask | ✅ Implemented vkCmdSetViewport | ✅ Implemented vkCmdUpdateBuffer | ⚙️ WIP vkCmdWaitEvents | ✅ Implemented diff --git a/build.zig.zon b/build.zig.zon index e6cc9fb..5eea2db 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -25,16 +25,16 @@ .url = "git+https://git.kbz8.me/kbz_8/Vulkan-CTS-bin.git#a5f787d80f14f136e3cb3e1185c35e298846c1d7", .hash = "N-V-__8AAMpOQxkHCKTw9i-NwmmQ3ks1ndFDXcVLlic4KjK3", }, - .SPIRV_Interpreter = .{ - .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#8677e8a6833d39e9169460fd75de2d7c70fab4b8", - .hash = "SPIRV_Interpreter-0.0.1-ajmpn_HtBQBR82M2KlR_yuUvdHUVdXrYVGRh5YNqVFqQ", - .lazy = true, - }, //.SPIRV_Interpreter = .{ - // // For development - // .path = "../SPIRV-Interpreter", + // .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#8677e8a6833d39e9169460fd75de2d7c70fab4b8", + // .hash = "SPIRV_Interpreter-0.0.1-ajmpn_HtBQBR82M2KlR_yuUvdHUVdXrYVGRh5YNqVFqQ", // .lazy = true, //}, + .SPIRV_Interpreter = .{ + // For development + .path = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#ee5a400010b3a5852606b2f8593bd5c745bf0a26", + .lazy = true, + }, }, .paths = .{ diff --git a/src/soft/SoftCommandBuffer.zig b/src/soft/SoftCommandBuffer.zig index 544a3b5..832474a 100644 --- a/src/soft/SoftCommandBuffer.zig +++ b/src/soft/SoftCommandBuffer.zig @@ -75,7 +75,11 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v .resetEvent = resetEvent, .resolveImage = resolveImage, .setEvent = setEvent, + .setBlendConstants = setBlendConstants, .setScissor = setScissor, + .setStencilCompareMask = setStencilCompareMask, + .setStencilReference = setStencilReference, + .setStencilWriteMask = setStencilWriteMask, .setViewport = setViewport, .waitEvent = waitEvent, }; @@ -1059,6 +1063,85 @@ pub fn setViewport(interface: *Interface, first: u32, viewports: []const vk.View self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; } +pub fn setBlendConstants(interface: *Interface, constants: [4]f32) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const allocator = self.command_allocator.allocator(); + + const CommandImpl = struct { + const Impl = @This(); + + constants: [4]f32, + + pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { + const impl: *Impl = @ptrCast(@alignCast(context)); + device.renderer.dynamic_state.blend_constants = impl.constants; + } + }; + + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(cmd); + cmd.* = .{ .constants = constants }; + 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); +} + +pub fn setStencilReference(interface: *Interface, face_mask: vk.StencilFaceFlags, reference: u32) VkError!void { + try setStencilDynamicState(interface, face_mask, reference, .reference); +} + +pub fn setStencilWriteMask(interface: *Interface, face_mask: vk.StencilFaceFlags, write_mask: u32) VkError!void { + try setStencilDynamicState(interface, face_mask, write_mask, .write_mask); +} + +fn setStencilDynamicState(interface: *Interface, face_mask: vk.StencilFaceFlags, value: u32, comptime kind: enum { compare_mask, reference, write_mask }) VkError!void { + const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); + const allocator = self.command_allocator.allocator(); + + const CommandImpl = struct { + const Impl = @This(); + + face_mask: vk.StencilFaceFlags, + value: u32, + + pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { + const impl: *Impl = @ptrCast(@alignCast(context)); + if (!impl.face_mask.front_bit and !impl.face_mask.back_bit) + return; + switch (kind) { + .compare_mask => { + if (impl.face_mask.front_bit) + device.renderer.dynamic_state.stencil_front_compare_mask = impl.value; + if (impl.face_mask.back_bit) + device.renderer.dynamic_state.stencil_back_compare_mask = impl.value; + }, + .reference => { + if (impl.face_mask.front_bit) + device.renderer.dynamic_state.stencil_front_reference = impl.value; + if (impl.face_mask.back_bit) + device.renderer.dynamic_state.stencil_back_reference = impl.value; + }, + .write_mask => { + if (impl.face_mask.front_bit) + device.renderer.dynamic_state.stencil_front_write_mask = impl.value; + if (impl.face_mask.back_bit) + device.renderer.dynamic_state.stencil_back_write_mask = impl.value; + }, + } + } + }; + + const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory; + errdefer allocator.destroy(cmd); + cmd.* = .{ + .face_mask = face_mask, + .value = value, + }; + self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory; +} + pub fn waitEvent(interface: *Interface, event: *base.Event, src_stage: vk.PipelineStageFlags, dst_stage: vk.PipelineStageFlags, memory_barriers: []const vk.MemoryBarrier, buffer_barriers: []const vk.BufferMemoryBarrier, image_barriers: []const vk.ImageMemoryBarrier) VkError!void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const allocator = self.command_allocator.allocator(); diff --git a/src/soft/device/ComputeDispatcher.zig b/src/soft/device/ComputeDispatcher.zig index f686c6b..9c46e9b 100644 --- a/src/soft/device/ComputeDispatcher.zig +++ b/src/soft/device/ComputeDispatcher.zig @@ -129,6 +129,8 @@ inline fn run(data: RunData) !void { if (!uses_control_barrier) try ExecutionDevice.writeDescriptorSets(data.self.state, rt); + try rt.populatePushConstants(data.self.state.push_constant_blob[0..]); + var group_index: usize = data.batch_id; while (group_index < data.group_count) : (group_index += data.self.batch_size) { var modulo: usize = group_index; @@ -205,6 +207,7 @@ fn runBarrierWorkgroup( for (runtimes, 0..) |*rt, i| { rt.resetInvocation(allocator); try ExecutionDevice.writeDescriptorSets(data.self.state, rt); + try rt.populatePushConstants(data.self.state.push_constant_blob[0..]); try setupWorkgroupBuiltins(data.self, rt, group_count, group_id); try setupSubgroupBuiltins(data.self, rt, group_id, i); statuses[i] = try rt.beginEntryPoint(allocator, entry); diff --git a/src/soft/device/Renderer.zig b/src/soft/device/Renderer.zig index be5d604..c357b7e 100644 --- a/src/soft/device/Renderer.zig +++ b/src/soft/device/Renderer.zig @@ -43,11 +43,18 @@ pub const DynamicState = struct { viewports: ?[]const vk.Viewport, scissor: ?[]const vk.Rect2D, line_width: ?f32, + blend_constants: ?[4]f32, + stencil_front_compare_mask: ?u32, + stencil_back_compare_mask: ?u32, + stencil_front_write_mask: ?u32, + stencil_back_write_mask: ?u32, + stencil_front_reference: ?u32, + stencil_back_reference: ?u32, }; pub const Vertex = struct { position: F32x4, - outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]?struct { + outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][4]?struct { interpolation_type: enum { smooth, flat, noperspective }, blob: []u8, size: usize, @@ -67,6 +74,7 @@ pub const DrawCall = struct { render_pass: *SoftRenderPass, framebuffer: *SoftFramebuffer, + allocator_mutex: std.Io.Mutex, rasterizer_wait_group: std.Io.Group, stats: struct { @@ -86,6 +94,7 @@ pub const DrawCall = struct { .depth_attachment = if (render_pass.interface.subpasses[renderer.subpass_index].depth_stencil_attachments) |desc| framebuffer.interface.attachments[desc.attachment] else null, .render_pass = render_pass, .framebuffer = framebuffer, + .allocator_mutex = .init, .rasterizer_wait_group = .init, .stats = .{ .polygons_drawn = 0, @@ -93,7 +102,9 @@ pub const DrawCall = struct { }; for (self.vertices) |*vertex| { - @memset(vertex.outputs[0..], null); + for (&vertex.outputs) |*location| { + @memset(location, null); + } } return self; @@ -102,8 +113,10 @@ pub const DrawCall = struct { fn deinit(self: *@This(), allocator: std.mem.Allocator) void { for (self.vertices) |*vertex| { for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { - if (vertex.outputs[location]) |output| { - allocator.free(output.blob); + for (0..4) |component| { + if (vertex.outputs[location][component]) |output| { + allocator.free(output.blob); + } } } } @@ -130,6 +143,13 @@ pub fn init(device: *SoftDevice, state: *PipelineState) Self { .viewports = null, .scissor = null, .line_width = null, + .blend_constants = null, + .stencil_front_compare_mask = null, + .stencil_back_compare_mask = null, + .stencil_front_write_mask = null, + .stencil_back_write_mask = null, + .stencil_front_reference = null, + .stencil_back_reference = null, }, .subpass_index = 0, }; @@ -189,9 +209,10 @@ fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usi for (vertex_shader.runtimes[0..]) |*runtime| { ExecutionDevice.writeDescriptorSets(self.state, &runtime.rt) catch return VkError.Unknown; } - const fragment_shader = pipeline.stages.getPtrAssertContains(.fragment); - for (fragment_shader.runtimes[0..]) |*runtime| { - ExecutionDevice.writeDescriptorSets(self.state, &runtime.rt) catch return VkError.Unknown; + if (pipeline.stages.getPtr(.fragment)) |fragment_shader| { + for (fragment_shader.runtimes[0..]) |*runtime| { + ExecutionDevice.writeDescriptorSets(self.state, &runtime.rt) catch return VkError.Unknown; + } } self.vertexShaderStage(allocator, &draw_call, vertex_count, instance_count, first_vertex, first_instance, indices) catch |err| { diff --git a/src/soft/device/blitter.zig b/src/soft/device/blitter.zig index df47eab..dd1cdc4 100644 --- a/src/soft/device/blitter.zig +++ b/src/soft/device/blitter.zig @@ -1096,7 +1096,7 @@ pub fn writeFloat4(c: F32x4, map: []u8, dst_format: vk.Format) void { const r: u5 = @intFromFloat(@round(color[0] * std.math.maxInt(u5))); const g: u5 = @intFromFloat(@round(color[1] * std.math.maxInt(u5))); const b: u5 = @intFromFloat(@round(color[2] * std.math.maxInt(u5))); - const a: u1 = @intFromFloat(color[3]); + const a: u1 = @intFromFloat(@round(color[3])); std.mem.bytesAsValue(u16, map).* = (@as(u16, b) << 0) | (@as(u16, g) << 5) | diff --git a/src/soft/device/clip.zig b/src/soft/device/clip.zig index 7799bed..5702f7f 100644 --- a/src/soft/device/clip.zig +++ b/src/soft/device/clip.zig @@ -173,20 +173,24 @@ fn interpolateVertexForClipping(allocator: std.mem.Allocator, a: *const Vertex, .outputs = undefined, }; - @memset(result.outputs[0..], null); + for (&result.outputs) |*location| { + @memset(location, null); + } for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { - const out_a = a.outputs[location] orelse continue; - const out_b = b.outputs[location] orelse continue; + for (0..4) |component| { + const out_a = a.outputs[location][component] orelse continue; + const out_b = b.outputs[location][component] orelse continue; - result.outputs[location] = .{ - .interpolation_type = out_a.interpolation_type, - .blob = if (out_a.interpolation_type == .flat) - allocator.dupe(u8, out_a.blob) catch return VkError.OutOfDeviceMemory - else - try interpolateBlob(allocator, out_a.blob, out_b.blob, @min(out_a.size, out_b.size), t), - .size = @min(out_a.size, out_b.size), - }; + result.outputs[location][component] = .{ + .interpolation_type = out_a.interpolation_type, + .blob = if (out_a.interpolation_type == .flat) + allocator.dupe(u8, out_a.blob) catch return VkError.OutOfDeviceMemory + else + try interpolateBlob(allocator, out_a.blob, out_b.blob, @min(out_a.size, out_b.size), t), + .size = @min(out_a.size, out_b.size), + }; + } } return result; diff --git a/src/soft/device/fragment.zig b/src/soft/device/fragment.zig index 8f3713f..33a9724 100644 --- a/src/soft/device/fragment.zig +++ b/src/soft/device/fragment.zig @@ -4,7 +4,7 @@ const base = @import("base"); const zm = base.zm; const spv = @import("spv"); -const VertexInterpolation = @import("rasterizer/common.zig").VertexInterpolation; +const VertexInterpolationLocation = @import("rasterizer/common.zig").VertexInterpolationLocation; const Renderer = @import("Renderer.zig"); const SoftImage = @import("../SoftImage.zig"); @@ -18,7 +18,7 @@ pub fn shaderInvocation( draw_call: *Renderer.DrawCall, batch_id: usize, position: zm.F32x4, - inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation, + inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation, ) SpvRuntimeError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 { var fragment_inputs = inputs; errdefer freeOwnedInputs(allocator, fragment_inputs); @@ -45,26 +45,28 @@ pub fn shaderInvocation( const entry = try rt.getEntryPointByName(shader.entry); for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { - const result_word = rt.getResultByLocation(@intCast(location), .input) catch |err| switch (err) { - SpvRuntimeError.NotFound => continue, - else => return err, - }; - - var input = fragment_inputs[location]; - if (input.blob.len == 0) { - const memory_size = try rt.getResultMemorySize(result_word); - const zeroes = allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return SpvRuntimeError.OutOfMemory; - @memset(zeroes, 0); - fragment_inputs[location] = .{ - .blob = zeroes, - .size = memory_size, - .free_responsability = true, + for (0..4) |component| { + const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .input) catch |err| switch (err) { + SpvRuntimeError.NotFound => continue, + else => return err, }; - input = fragment_inputs[location]; - } - if (input.blob.len != 0) { - try rt.writeInput(input.blob, result_word); + var input = fragment_inputs[location][component]; + if (input.blob.len == 0) { + const memory_size = try rt.getResultMemorySize(result_word); + const zeroes = allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return SpvRuntimeError.OutOfMemory; + @memset(zeroes, 0); + fragment_inputs[location][component] = .{ + .blob = zeroes, + .size = memory_size, + .free_responsability = true, + }; + input = fragment_inputs[location][component]; + } + + if (input.blob.len != 0) { + try rt.writeInput(input.blob, result_word); + } } } @@ -80,6 +82,29 @@ pub fn shaderInvocation( @memset(std.mem.asBytes(&outputs), 0); for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { + var has_split_components = false; + for (1..4) |component| { + _ = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .output) catch |err| switch (err) { + SpvRuntimeError.NotFound => continue, + else => return err, + }; + has_split_components = true; + break; + } + + if (has_split_components) { + for (0..4) |component| { + const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .output) catch |err| switch (err) { + SpvRuntimeError.NotFound => continue, + else => return err, + }; + const memory_size = try rt.getResultMemorySize(result_word); + const offset = component * @sizeOf(f32); + try rt.readOutput(outputs[location][offset .. offset + memory_size], result_word); + } + continue; + } + const result_word = rt.getResultByLocation(@intCast(location), .output) catch |err| switch (err) { SpvRuntimeError.NotFound => continue, else => return err, @@ -93,9 +118,11 @@ pub fn shaderInvocation( return outputs; } -fn freeOwnedInputs(allocator: std.mem.Allocator, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation) void { - for (inputs) |input| { - if (input.free_responsability) - allocator.free(input.blob); +fn freeOwnedInputs(allocator: std.mem.Allocator, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation) void { + for (inputs) |location| { + for (location) |input| { + if (input.free_responsability) + allocator.free(input.blob); + } } } diff --git a/src/soft/device/rasterizer.zig b/src/soft/device/rasterizer.zig index d23676d..29a0123 100644 --- a/src/soft/device/rasterizer.zig +++ b/src/soft/device/rasterizer.zig @@ -1,6 +1,7 @@ const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); +const spv = @import("spv"); const zm = base.zm; const clip = @import("clip.zig"); @@ -9,7 +10,6 @@ const bresenham = @import("rasterizer/bresenham.zig"); const edge_function = @import("rasterizer/edge_function.zig"); const common = @import("rasterizer/common.zig"); const fragment = @import("fragment.zig"); -const blitter = @import("blitter.zig"); const Renderer = @import("Renderer.zig"); const Vertex = Renderer.Vertex; @@ -37,6 +37,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato const color_range = render_target_view.subresource_range; const color_format = render_target_view.format; + const color_extent = render_target.getMipLevelExtent(color_range.base_mip_level); const color_attachment_subresource_offset = try render_target.getSubresourceOffset( color_range.aspect_mask, @@ -49,6 +50,8 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato .base = try render_target.mapAsSliceWithAddedOffset(u8, color_attachment_subresource_offset, color_attachment_subresource_size), .row_pitch = render_target.getRowPitchMemSizeForMipLevelWithFormat(color_range.aspect_mask, color_range.base_mip_level, color_format), .texel_size = base.format.texelSize(color_format), + .width = color_extent.width, + .height = color_extent.height, .format = color_format, }; } @@ -61,20 +64,58 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato break :blk null; const depth_range = depth_attachment_view.?.subresource_range; + if (!depth_range.aspect_mask.depth_bit) + break :blk null; + const depth_format = depth_attachment_view.?.format; + const depth_aspect: vk.ImageAspectFlags = .{ .depth_bit = true }; + const depth_aspect_format = base.format.fromAspect(depth_format, depth_aspect); + const depth_extent = depth_attachment.?.getMipLevelExtent(depth_range.base_mip_level); const attachment_subresource_offset = try depth_attachment.?.getSubresourceOffset( - depth_range.aspect_mask, + depth_aspect, depth_range.base_mip_level, depth_range.base_array_layer, ); - const attachment_subresource_size = depth_attachment.?.getLayerSize(depth_range.aspect_mask); + const attachment_subresource_size = depth_attachment.?.getLayerSize(depth_aspect); break :blk .{ .mutex = .init, .base = try depth_attachment.?.mapAsSliceWithAddedOffset(u8, attachment_subresource_offset, attachment_subresource_size), - .row_pitch = depth_attachment.?.getRowPitchMemSizeForMipLevelWithFormat(depth_range.aspect_mask, depth_range.base_mip_level, depth_format), - .texel_size = base.format.texelSize(depth_format), - .format = depth_format, + .row_pitch = depth_attachment.?.getRowPitchMemSizeForMipLevelWithFormat(depth_aspect, depth_range.base_mip_level, depth_format), + .texel_size = base.format.texelSize(depth_aspect_format), + .width = depth_extent.width, + .height = depth_extent.height, + .format = depth_aspect_format, + }; + }; + + var stencil_attachment_access: ?common.RenderTargetAccess = blk: { + if (depth_attachment == null) + break :blk null; + + const stencil_range = depth_attachment_view.?.subresource_range; + if (!stencil_range.aspect_mask.stencil_bit) + break :blk null; + + const stencil_format = depth_attachment_view.?.format; + const stencil_aspect: vk.ImageAspectFlags = .{ .stencil_bit = true }; + const stencil_aspect_format = base.format.fromAspect(stencil_format, stencil_aspect); + const stencil_extent = depth_attachment.?.getMipLevelExtent(stencil_range.base_mip_level); + + const attachment_subresource_offset = try depth_attachment.?.getSubresourceOffset( + stencil_aspect, + stencil_range.base_mip_level, + stencil_range.base_array_layer, + ); + const attachment_subresource_size = depth_attachment.?.getLayerSize(stencil_aspect); + break :blk .{ + .mutex = .init, + .base = try depth_attachment.?.mapAsSliceWithAddedOffset(u8, attachment_subresource_offset, attachment_subresource_size), + .row_pitch = depth_attachment.?.getRowPitchMemSizeForMipLevelWithFormat(stencil_aspect, stencil_range.base_mip_level, stencil_format), + .texel_size = base.format.texelSize(stencil_aspect_format), + .width = stencil_extent.width, + .height = stencil_extent.height, + .format = stencil_aspect_format, }; }; @@ -86,6 +127,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato vertex, color_attachment_access, if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, ); }, .triangle_list => for (0..@divTrunc(draw_call.vertices.len, 3)) |triangle_index| { @@ -103,6 +145,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato v2, color_attachment_access, if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, ); }, .triangle_fan => if (draw_call.vertices.len >= 3) { @@ -120,6 +163,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato v2, color_attachment_access, if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, ); } }, @@ -139,6 +183,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato v2, color_attachment_access, if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, ); } else { try clipTransformAndRasterizeTriangle( @@ -150,6 +195,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato v2, color_attachment_access, if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, ); } } @@ -166,6 +212,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato v1, color_attachment_access, if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, ); }, .line_strip => if (draw_call.vertices.len >= 2) { @@ -180,6 +227,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato v1, color_attachment_access, if (depth_attachment_access) |*access| access else null, + if (stencil_attachment_access) |*access| access else null, ); } }, @@ -195,6 +243,7 @@ fn clipTransformAndRasterizePoint( vertex: *Vertex, color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, + stencil_attachment_access: ?*common.RenderTargetAccess, ) VkError!void { const x, const y, const z, const w = vertex.position; if (w == 0.0 or x < -w or x > w or y < -w or y > w or z < 0.0 or z > w) @@ -208,6 +257,8 @@ fn clipTransformAndRasterizePoint( const max_x: i32 = @intFromFloat(@ceil(transformed.position[0] + (point_size / 2.0)) - 1.0); const min_y: i32 = @intFromFloat(@floor(transformed.position[1] - (point_size / 2.0))); const max_y: i32 = @intFromFloat(@ceil(transformed.position[1] + (point_size / 2.0)) - 1.0); + const pipeline = draw_call.renderer.state.pipeline orelse return; + const has_fragment_shader = pipeline.stages.getPtr(.fragment) != null; var py = min_y; while (py <= max_y) : (py += 1) { @@ -216,30 +267,27 @@ fn clipTransformAndRasterizePoint( if (!common.scissorContainsPixel(draw_call.scissor, px, py)) continue; - if (depth_attachment_access) |depth| { - const offset = @as(usize, @intCast(px)) * depth.texel_size + @as(usize, @intCast(py)) * depth.row_pitch; - const depth_value = blitter.readFloat4(depth.base[offset..], depth.format); - if (transformed.position[2] >= depth_value[0]) - continue; + var outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 = undefined; + @memset(std.mem.asBytes(&outputs), 0); + if (has_fragment_shader) { + outputs = fragment.shaderInvocation( + allocator, + draw_call, + 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), + ) catch |err| { + 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; + }; } - const outputs = fragment.shaderInvocation( - allocator, - draw_call, - 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), - ) catch |err| { - 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(outputs, draw_call, color_attachment_access, depth_attachment_access, @intCast(px), @intCast(py), transformed.position[2]); + try common.writeToTargets(outputs, draw_call, color_attachment_access, depth_attachment_access, stencil_attachment_access, true, @intCast(px), @intCast(py), transformed.position[2]); } } } @@ -251,6 +299,7 @@ fn clipTransformAndRasterizeLine( v1: *Vertex, color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, + stencil_attachment_access: ?*common.RenderTargetAccess, ) VkError!void { const clipped_line = (try clip.clipLine(allocator, v0, v1)) orelse return; @@ -267,6 +316,7 @@ fn clipTransformAndRasterizeLine( &tv1, color_attachment_access, depth_attachment_access, + stencil_attachment_access, ); } @@ -279,6 +329,7 @@ fn clipTransformAndRasterizeTriangle( v2: *Vertex, color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, + stencil_attachment_access: ?*common.RenderTargetAccess, ) VkError!void { const clipped_polygon = try clip.clipTriangle(allocator, v0, v1, v2); @@ -303,6 +354,7 @@ fn clipTransformAndRasterizeTriangle( &tv2, color_attachment_access, depth_attachment_access, + stencil_attachment_access, ); } } @@ -316,29 +368,32 @@ fn rasterizeTriangle( v2: *Vertex, color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, + stencil_attachment_access: ?*common.RenderTargetAccess, ) VkError!void { - if (try triangleIsCulled(renderer, v0, v1, v2)) + const maybe_front_face = try triangleFrontFace(renderer, v0, v1, v2); + const front_face = maybe_front_face orelse return; + + if (try triangleIsCulled(renderer, front_face)) return; draw_call.stats.polygons_drawn += 1; const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics; switch (pipeline_data.rasterization.polygon_mode) { - .fill => try edge_function.drawTriangle(allocator, draw_call, v0, v1, v2, color_attachment_access, depth_attachment_access), + .fill => try edge_function.drawTriangle(allocator, draw_call, v0, v1, v2, color_attachment_access, depth_attachment_access, stencil_attachment_access, front_face), .line => { - try bresenham.drawLine(allocator, draw_call, v0, v1, color_attachment_access, depth_attachment_access); - try bresenham.drawLine(allocator, draw_call, v1, v2, color_attachment_access, depth_attachment_access); - try bresenham.drawLine(allocator, draw_call, v2, v0, color_attachment_access, depth_attachment_access); + try bresenham.drawLine(allocator, draw_call, v0, v1, color_attachment_access, depth_attachment_access, stencil_attachment_access); + try bresenham.drawLine(allocator, draw_call, v1, v2, color_attachment_access, depth_attachment_access, stencil_attachment_access); + try bresenham.drawLine(allocator, draw_call, v2, v0, color_attachment_access, depth_attachment_access, stencil_attachment_access); }, .point => {}, // TODO else => base.unsupported("polygon mode {any}", .{pipeline_data.rasterization.polygon_mode}), } } -fn triangleIsCulled(renderer: *Renderer, v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) VkError!bool { +fn triangleIsCulled(renderer: *Renderer, front_face: bool) VkError!bool { const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics; - const rasterization = pipeline_data.rasterization; - const cull_mode = rasterization.cull_mode; + const cull_mode = pipeline_data.rasterization.cull_mode; if (!cull_mode.front_bit and !cull_mode.back_bit) return false; @@ -346,17 +401,21 @@ fn triangleIsCulled(renderer: *Renderer, v0: *const Vertex, v1: *const Vertex, v if (cull_mode.front_bit and cull_mode.back_bit) return true; + return (cull_mode.front_bit and front_face) or (cull_mode.back_bit and !front_face); +} + +fn triangleFrontFace(renderer: *Renderer, v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) VkError!?bool { + const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics; + const rasterization = pipeline_data.rasterization; const area = triangleArea(v0, v1, v2); if (area == 0.0) - return true; + return null; - const front_face = switch (rasterization.front_face) { + return switch (rasterization.front_face) { .counter_clockwise => area < 0.0, .clockwise => area > 0.0, - else => return false, + else => false, }; - - return (cull_mode.front_bit and front_face) or (cull_mode.back_bit and !front_face); } inline fn triangleArea(v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) f32 { diff --git a/src/soft/device/rasterizer/bresenham.zig b/src/soft/device/rasterizer/bresenham.zig index 550060f..229d7dd 100644 --- a/src/soft/device/rasterizer/bresenham.zig +++ b/src/soft/device/rasterizer/bresenham.zig @@ -3,7 +3,6 @@ const base = @import("base"); const spv = @import("spv"); const zm = base.zm; -const blitter = @import("../blitter.zig"); const common = @import("common.zig"); const fragment = @import("../fragment.zig"); @@ -30,6 +29,8 @@ const RunData = struct { end_step: usize, color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, + stencil_attachment_access: ?*common.RenderTargetAccess, + has_fragment_shader: bool, }; pub fn drawLine( @@ -39,6 +40,7 @@ pub fn drawLine( v1: *Renderer.Vertex, color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, + stencil_attachment_access: ?*common.RenderTargetAccess, ) VkError!void { const io = draw_call.renderer.device.interface.io(); @@ -69,8 +71,8 @@ pub fn drawLine( const y_step: i32 = if (y0 > y1) -1 else 1; const pipeline = draw_call.renderer.state.pipeline orelse return; - - const runtimes_count = (pipeline.stages.getPtr(.fragment) orelse return).runtimes.len; + const fragment_stage = pipeline.stages.getPtr(.fragment); + const runtimes_count = if (fragment_stage) |stage| stage.runtimes.len else 1; if (runtimes_count == 0) return; @@ -104,15 +106,14 @@ pub fn drawLine( .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}); } - // Not syncing workers between triangles when rendering without depth buffer - // will lead to pixel rendering order issues between triangles. - if (depth_attachment_access == null) - draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost; + draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost; } fn bresenhamYAtStep(y0: i32, d_x: i32, d_err: i32, y_step: i32, step: usize) i32 { @@ -151,31 +152,27 @@ 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]); - // Early depth test to avoid unnecesary computations - if (data.depth_attachment_access) |depth| { - const offset = @as(usize, @intCast(pixel_x)) * depth.texel_size + @as(usize, @intCast(pixel_y)) * depth.row_pitch; - const depth_value = blitter.readFloat4(depth.base[offset..], depth.format); - if (z >= depth_value[0]) - continue; + var outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8 = undefined; + @memset(std.mem.asBytes(&outputs), 0); + if (data.has_fragment_shader) { + outputs = fragment.shaderInvocation( + data.allocator, + data.draw_call, + 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), + ) catch |err| { + 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; + }; } - const outputs = fragment.shaderInvocation( - data.allocator, - data.draw_call, - 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), - ) catch |err| { - 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(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, @intCast(pixel_x), @intCast(pixel_y), z); + try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, data.stencil_attachment_access, true, @intCast(pixel_x), @intCast(pixel_y), z); } } diff --git a/src/soft/device/rasterizer/common.zig b/src/soft/device/rasterizer/common.zig index 07e9809..b9ae163 100644 --- a/src/soft/device/rasterizer/common.zig +++ b/src/soft/device/rasterizer/common.zig @@ -16,6 +16,8 @@ pub const RenderTargetAccess = struct { base: []u8, row_pitch: usize, texel_size: usize, + width: u32, + height: u32, format: vk.Format, }; @@ -25,6 +27,8 @@ pub const VertexInterpolation = struct { free_responsability: bool, }; +pub const VertexInterpolationLocation = [4]VertexInterpolation; + 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); @@ -41,6 +45,117 @@ pub fn scissorContainsPixel(scissor: vk.Rect2D, x: i32, y: i32) bool { pixel_y < max_y; } +pub fn targetContainsPixel(target: RenderTargetAccess, x: i32, y: i32) bool { + if (x < 0 or y < 0) + return false; + const pixel_x: u32 = @intCast(x); + const pixel_y: u32 = @intCast(y); + return pixel_x < target.width and pixel_y < target.height; +} + +pub fn targetOffset(target: RenderTargetAccess, x: usize, y: usize) ?usize { + if (x >= target.width or y >= target.height) + return null; + const offset = x * target.texel_size + y * target.row_pitch; + if (offset > target.base.len or target.texel_size > target.base.len - offset) + return null; + return offset; +} + +pub fn compare(comptime T: type, op: vk.CompareOp, reference: T, value: T) bool { + return switch (op) { + .never => false, + .less => reference < value, + .equal => reference == value, + .less_or_equal => reference <= value, + .greater => reference > value, + .not_equal => reference != value, + .greater_or_equal => reference >= value, + .always => true, + else => false, + }; +} + +fn applyStencilOp(op: vk.StencilOp, current: u32, reference: u32) u32 { + return switch (op) { + .keep => current, + .zero => 0, + .replace => reference, + .increment_and_clamp => @min(current +| 1, std.math.maxInt(u8)), + .decrement_and_clamp => if (current == 0) 0 else current - 1, + .invert => ~current, + .increment_and_wrap => current +% 1, + .decrement_and_wrap => current -% 1, + else => current, + } & std.math.maxInt(u8); +} + +fn updateStencilValue(stencil: *RenderTargetAccess, offset: usize, state: vk.StencilOpState, op: vk.StencilOp) void { + const current = blitter.readInt4(stencil.base[offset..], stencil.format)[0] & std.math.maxInt(u8); + const op_value = applyStencilOp(op, current, state.reference & std.math.maxInt(u8)); + const write_mask = state.write_mask & std.math.maxInt(u8); + const new_value = (current & ~write_mask) | (op_value & write_mask); + blitter.writeInt4(@splat(new_value), stencil.base[offset..], stencil.format); +} + +pub fn stencilTestAndUpdate( + stencil: *RenderTargetAccess, + x: usize, + y: usize, + state: vk.StencilOpState, + depth_passed: ?bool, +) bool { + const offset = targetOffset(stencil.*, x, y) orelse return false; + const current = blitter.readInt4(stencil.base[offset..], stencil.format)[0] & std.math.maxInt(u8); + const reference = state.reference & std.math.maxInt(u8); + const compare_mask = state.compare_mask & std.math.maxInt(u8); + const stencil_passed = compare(u32, state.compare_op, reference & compare_mask, current & compare_mask); + + if (!stencil_passed) { + updateStencilValue(stencil, offset, state, state.fail_op); + return false; + } + + if (depth_passed != null and !depth_passed.?) { + updateStencilValue(stencil, offset, state, state.depth_fail_op); + return false; + } + + updateStencilValue(stencil, offset, state, state.pass_op); + return true; +} + +fn stencilTest(stencil: *RenderTargetAccess, offset: usize, state: vk.StencilOpState) bool { + const current = blitter.readInt4(stencil.base[offset..], stencil.format)[0] & std.math.maxInt(u8); + const reference = state.reference & std.math.maxInt(u8); + const compare_mask = state.compare_mask & std.math.maxInt(u8); + return compare(u32, state.compare_op, reference & compare_mask, current & compare_mask); +} + +pub fn depthTestAndUpdate(depth: *RenderTargetAccess, x: usize, y: usize, z: f32, state: vk.PipelineDepthStencilStateCreateInfo) bool { + if (state.depth_test_enable == .false) + return true; + + const offset = targetOffset(depth.*, x, y) orelse return false; + const depth_value = blitter.readFloat4(depth.base[offset..], depth.format); + const passed = compare(f32, state.depth_compare_op, z, depth_value[0]); + if (passed and state.depth_write_enable == .true) + blitter.writeFloat4(zm.f32x4s(z), depth.base[offset..], depth.format); + return passed; +} + +fn resolveStencilState(draw_call: *Renderer.DrawCall, state: vk.StencilOpState, front: bool) vk.StencilOpState { + var resolved = state; + const dynamic = draw_call.renderer.dynamic_state; + if ((if (front) dynamic.stencil_front_compare_mask else dynamic.stencil_back_compare_mask)) |mask| + resolved.compare_mask = mask; + if ((if (front) dynamic.stencil_front_write_mask else dynamic.stencil_back_write_mask)) |mask| + resolved.write_mask = mask; + if ((if (front) dynamic.stencil_front_reference else dynamic.stencil_back_reference)) |reference| + resolved.reference = reference; + return resolved; +} + pub fn interpolateVertexOutputs( allocator: std.mem.Allocator, v0: *const Renderer.Vertex, @@ -49,46 +164,48 @@ pub fn interpolateVertexOutputs( b0: f32, b1: f32, b2: f32, -) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation { - var inputs = [_]VertexInterpolation{.{ +) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation { + var inputs = [_]VertexInterpolationLocation{[_]VertexInterpolation{.{ .blob = &.{}, .size = 0, .free_responsability = false, - }} ** spv.SPIRV_MAX_OUTPUT_LOCATIONS; + }} ** 4} ** spv.SPIRV_MAX_OUTPUT_LOCATIONS; for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { - const out0 = v0.outputs[location] orelse continue; - const out1 = v1.outputs[location] orelse continue; - const out2 = v2.outputs[location] orelse continue; + 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; - if (out0.interpolation_type == .flat or out0.size == 0) { - inputs[location] = .{ .blob = out0.blob, .size = out0.size, .free_responsability = false }; - continue; + if (out0.interpolation_type == .flat or out0.size == 0) { + inputs[location][component] = .{ .blob = out0.blob, .size = out0.size, .free_responsability = false }; + continue; + } + + const len = @min(out0.size, out1.size, out2.size); + const input = allocator.alloc(u8, len + @sizeOf(F32x4)) catch return VkError.OutOfDeviceMemory; + @memset(input, 0); + + 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, b0, b1, b2)); + } + + 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 * b0) + (value1 * b1) + (value2 * b2)); + } + + if (byte_index < len) + @memcpy(input[byte_index..len], out0.blob[byte_index..len]); + + inputs[location][component] = .{ .blob = input, .size = len, .free_responsability = true }; } - - const len = @min(out0.size, out1.size, out2.size); - const input = allocator.alloc(u8, len + @sizeOf(F32x4)) catch return VkError.OutOfDeviceMemory; - @memset(input, 0); - - 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, b0, b1, b2)); - } - - 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 * b0) + (value1 * b1) + (value2 * b2)); - } - - if (byte_index < len) - @memcpy(input[byte_index..len], out0.blob[byte_index..len]); - - inputs[location] = .{ .blob = input, .size = len, .free_responsability = true }; } return inputs; @@ -99,7 +216,7 @@ pub fn interpolateLineOutputs( v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, t: f32, -) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation { +) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation { return interpolateVertexOutputs(allocator, v0, v1, v0, 1.0 - t, t, 0.0); } @@ -109,7 +226,76 @@ inline fn interpolateF32x4(value0: F32x4, value1: F32x4, value2: F32x4, b0: f32, inline fn fragmentOutputFloat4(output: [@sizeOf(F32x4)]u8, format: vk.Format) F32x4 { const color = std.mem.bytesToValue(F32x4, &output); - return if (base.format.isSrgb(format)) zm.rgbToSrgb(color) else color; + _ = format; + return color; +} + +inline fn blendFactor(factor: vk.BlendFactor, src: F32x4, dst: F32x4, constant: F32x4) F32x4 { + return switch (factor) { + .zero => zm.f32x4s(0.0), + .one => zm.f32x4s(1.0), + .src_color => src, + .one_minus_src_color => zm.f32x4s(1.0) - src, + .dst_color => dst, + .one_minus_dst_color => zm.f32x4s(1.0) - dst, + .src_alpha => zm.f32x4s(src[3]), + .one_minus_src_alpha => zm.f32x4s(1.0 - src[3]), + .dst_alpha => zm.f32x4s(dst[3]), + .one_minus_dst_alpha => zm.f32x4s(1.0 - dst[3]), + .constant_color => constant, + .one_minus_constant_color => zm.f32x4s(1.0) - constant, + .constant_alpha => zm.f32x4s(constant[3]), + .one_minus_constant_alpha => zm.f32x4s(1.0 - constant[3]), + .src_alpha_saturate => .{ @min(src[3], 1.0 - dst[3]), @min(src[3], 1.0 - dst[3]), @min(src[3], 1.0 - dst[3]), 1.0 }, + else => zm.f32x4s(0.0), + }; +} + +inline fn blendOp(op: vk.BlendOp, src: F32x4, dst: F32x4) F32x4 { + return switch (op) { + .add => src + dst, + .subtract => src - dst, + .reverse_subtract => dst - src, + .min => @min(src, dst), + .max => @max(src, dst), + else => src, + }; +} + +inline fn blendColor(src: F32x4, dst: F32x4, state: vk.PipelineColorBlendAttachmentState, constants: [4]f32) F32x4 { + if (state.blend_enable == .false) + return src; + + const constant = F32x4{ constants[0], constants[1], constants[2], constants[3] }; + const color_src = if (state.color_blend_op == .min or state.color_blend_op == .max) + src + else + src * blendFactor(state.src_color_blend_factor, src, dst, constant); + const color_dst = if (state.color_blend_op == .min or state.color_blend_op == .max) + dst + else + dst * blendFactor(state.dst_color_blend_factor, src, dst, constant); + const alpha_src = if (state.alpha_blend_op == .min or state.alpha_blend_op == .max) + src + else + src * blendFactor(state.src_alpha_blend_factor, src, dst, constant); + const alpha_dst = if (state.alpha_blend_op == .min or state.alpha_blend_op == .max) + dst + else + dst * blendFactor(state.dst_alpha_blend_factor, src, dst, constant); + + var blended = blendOp(state.color_blend_op, color_src, color_dst); + blended[3] = blendOp(state.alpha_blend_op, alpha_src, alpha_dst)[3]; + return blended; +} + +inline fn applyColorWriteMask(blended: F32x4, dst: F32x4, mask: vk.ColorComponentFlags) F32x4 { + return .{ + if (mask.r_bit) blended[0] else dst[0], + if (mask.g_bit) blended[1] else dst[1], + if (mask.b_bit) blended[2] else dst[2], + if (mask.a_bit) blended[3] else dst[3], + }; } pub fn writeToTargets( @@ -117,33 +303,84 @@ pub fn writeToTargets( draw_call: *Renderer.DrawCall, color_attachment_access: []const ?RenderTargetAccess, depth_attachment_access: ?*RenderTargetAccess, + stencil_attachment_access: ?*RenderTargetAccess, + front_face: bool, x: usize, y: usize, z: f32, ) VkError!void { const io = draw_call.renderer.device.interface.io(); + const depth_stencil_state = draw_call.renderer.state.pipeline.?.interface.mode.graphics.depth_stencil; - // After work depth test to avoid overwritten depth pixels during fragment invocations + var stencil_state: ?vk.StencilOpState = null; + var stencil_offset: ?usize = null; + if (stencil_attachment_access) |stencil| { + if (depth_stencil_state) |state| { + if (state.stencil_test_enable == .true) { + stencil_state = if (front_face) + resolveStencilState(draw_call, state.front, true) + else + resolveStencilState(draw_call, state.back, false); + stencil_offset = targetOffset(stencil.*, x, y) orelse return; + if (!stencilTest(stencil, stencil_offset.?, stencil_state.?)) { + updateStencilValue(stencil, stencil_offset.?, stencil_state.?, stencil_state.?.fail_op); + return; + } + } + } + } + + // After work depth test to avoid overwritten depth pixels during fragment invocations. + var depth_passed: ?bool = null; if (depth_attachment_access) |depth| { - const depth_offset = @as(usize, @intCast(x)) * depth.texel_size + @as(usize, @intCast(y)) * depth.row_pitch; + const depth_offset = targetOffset(depth.*, x, y) orelse return; depth.mutex.lock(io) catch return VkError.DeviceLost; defer depth.mutex.unlock(io); - const depth_value = blitter.readFloat4(depth.base[depth_offset..], depth.format); - if (z >= depth_value[0]) - return; - blitter.writeFloat4(zm.f32x4s(z), depth.base[depth_offset..], depth.format); + if (depth_stencil_state) |state| { + depth_passed = depthTestAndUpdate(depth, x, y, z, state); + if (!depth_passed.? and stencil_state == null) + return; + } else { + const depth_value = blitter.readFloat4(depth.base[depth_offset..], depth.format); + if (z >= depth_value[0]) + return; + blitter.writeFloat4(zm.f32x4s(z), depth.base[depth_offset..], depth.format); + depth_passed = true; + } + } + + if (stencil_attachment_access) |stencil| { + if (stencil_state) |state| { + if (depth_passed != null and !depth_passed.?) { + updateStencilValue(stencil, stencil_offset.?, state, state.depth_fail_op); + return; + } + updateStencilValue(stencil, stencil_offset.?, state, state.pass_op); + } } for (color_attachment_access, 0..) |maybe_color, location| { const color = maybe_color orelse continue; - const color_offset = @as(usize, @intCast(x)) * color.texel_size + @as(usize, @intCast(y)) * color.row_pitch; + const color_offset = targetOffset(color, x, y) orelse continue; if (base.format.isUnnormalizedInteger(color.format)) { blitter.writeInt4(std.mem.bytesToValue(U32x4, &outputs[location]), color.base[color_offset..], color.format); } else { - blitter.writeFloat4(fragmentOutputFloat4(outputs[location], color.format), color.base[color_offset..], color.format); + const pipeline_data = draw_call.renderer.state.pipeline.?.interface.mode.graphics; + const src = fragmentOutputFloat4(outputs[location], color.format); + const encoded_dst = blitter.readFloat4(color.base[color_offset..], color.format); + const dst = if (base.format.isSrgb(color.format)) zm.srgbToRgb(encoded_dst) else encoded_dst; + const final_color = if (pipeline_data.color_blend.attachments) |attachments| blk: { + if (location >= attachments.len) + break :blk src; + const constants = draw_call.renderer.dynamic_state.blend_constants orelse pipeline_data.color_blend.constants; + const blended = blendColor(src, dst, attachments[location], constants); + break :blk applyColorWriteMask(blended, dst, attachments[location].color_write_mask); + } else src; + const encoded_color = if (base.format.isSrgb(color.format)) zm.rgbToSrgb(final_color) else final_color; + blitter.writeFloat4(encoded_color, color.base[color_offset..], color.format); } } } diff --git a/src/soft/device/rasterizer/edge_function.zig b/src/soft/device/rasterizer/edge_function.zig index e4faf50..4d96f5d 100644 --- a/src/soft/device/rasterizer/edge_function.zig +++ b/src/soft/device/rasterizer/edge_function.zig @@ -6,7 +6,6 @@ const zm = base.zm; const common = @import("common.zig"); const fragment = @import("../fragment.zig"); -const blitter = @import("../blitter.zig"); const Renderer = @import("../Renderer.zig"); @@ -28,6 +27,9 @@ const RunData = struct { v2: Renderer.Vertex, color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, + stencil_attachment_access: ?*common.RenderTargetAccess, + front_face: bool, + has_fragment_shader: bool, }; pub fn drawTriangle( @@ -38,6 +40,8 @@ pub fn drawTriangle( v2: *Renderer.Vertex, color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, + stencil_attachment_access: ?*common.RenderTargetAccess, + front_face: bool, ) VkError!void { const io = draw_call.renderer.device.interface.io(); @@ -51,8 +55,10 @@ pub fn drawTriangle( return; const pipeline = draw_call.renderer.state.pipeline orelse return; - - const runtimes_count = (pipeline.stages.getPtr(.fragment) orelse return).runtimes.len; + 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 grid_size: usize = @intFromFloat(@ceil(@sqrt(@as(f32, @floatFromInt(runtimes_count))))); const width: usize = @intCast(max_x - min_x + 1); @@ -97,22 +103,35 @@ pub fn drawTriangle( .max_y = run_max_y, .color_attachment_access = color_attachment_access, .depth_attachment_access = depth_attachment_access, + .stencil_attachment_access = stencil_attachment_access, + .front_face = front_face, + .has_fragment_shader = fragment_stage != null, }; draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data}); } } - // Not syncing workers between triangles when rendering without depth buffer - // will lead to pixel rendering order issues between triangles. - if (depth_attachment_access == null) - draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost; + draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost; } inline fn edgeFunction(a: F32x4, b: F32x4, p: F32x4) f32 { return ((p[0] - a[0]) * (b[1] - a[1])) - ((p[1] - a[1]) * (b[0] - a[0])); } +inline fn isInclusiveEdge(a: F32x4, b: F32x4) bool { + const dx = b[0] - a[0]; + const dy = b[1] - a[1]; + return dy < 0.0 or (dy == 0.0 and dx > 0.0); +} + +inline fn edgeContainsPixel(a: F32x4, b: F32x4, edge_value: f32, area: f32) bool { + return if (area > 0.0) + edge_value > 0.0 or (edge_value == 0.0 and isInclusiveEdge(a, b)) + else + edge_value < 0.0 or (edge_value == 0.0 and isInclusiveEdge(b, a)); +} + 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)}); @@ -139,10 +158,10 @@ inline fn run(data: RunData) !void { const w1 = edgeFunction(data.v2.position, data.v0.position, p); const w2 = edgeFunction(data.v0.position, data.v1.position, p); - const inside = if (data.area > 0.0) - w0 >= 0.0 and w1 >= 0.0 and w2 >= 0.0 - else - w0 <= 0.0 and w1 <= 0.0 and w2 <= 0.0; + const inside = + edgeContainsPixel(data.v1.position, data.v2.position, w0, data.area) and + edgeContainsPixel(data.v2.position, data.v0.position, w1, data.area) and + edgeContainsPixel(data.v0.position, data.v1.position, w2, data.area); if (!inside) continue; @@ -152,31 +171,27 @@ 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]); - // Early depth test to avoid unnecesary computations - if (data.depth_attachment_access) |depth| { - const offset = @as(usize, @intCast(x)) * depth.texel_size + @as(usize, @intCast(y)) * depth.row_pitch; - const depth_value = blitter.readFloat4(depth.base[offset..], depth.format); - if (z >= depth_value[0]) - continue; + var outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8 = undefined; + @memset(std.mem.asBytes(&outputs), 0); + if (data.has_fragment_shader) { + 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), + ) catch |err| { + 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; + }; } - const 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), - ) catch |err| { - 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(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, @intCast(x), @intCast(y), z); + try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, data.stencil_attachment_access, data.front_face, @intCast(x), @intCast(y), z); } } } diff --git a/src/soft/device/vertex_dispatcher.zig b/src/soft/device/vertex_dispatcher.zig index e70600f..d43ad3f 100644 --- a/src/soft/device/vertex_dispatcher.zig +++ b/src/soft/device/vertex_dispatcher.zig @@ -45,6 +45,10 @@ inline fn run(data: RunData) !void { var invocation_index: usize = data.batch_id; while (invocation_index < data.vertex_count) : (invocation_index += data.batch_size) { + const io = data.draw_call.renderer.device.interface.io(); + data.draw_call.allocator_mutex.lock(io) catch return VkError.DeviceLost; + defer data.draw_call.allocator_mutex.unlock(io); + rt.resetInvocation(data.allocator); try rt.populatePushConstants(data.draw_call.renderer.state.push_constant_blob[0..]); @@ -84,18 +88,20 @@ inline fn run(data: RunData) !void { try rt.readBuiltIn(std.mem.asBytes(&output.position), .Position); for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { - const result_word = rt.getResultByLocation(@intCast(location), .output) catch |err| switch (err) { - SpvRuntimeError.NotFound => continue, - else => return err, - }; - const memory_size = try rt.getResultMemorySize(result_word); - output.outputs[location] = .{ - .interpolation_type = if (rt.hasResultDecoration(result_word, .Flat)) .flat else .smooth, // TODO : handle noperspective - .blob = data.allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return VkError.OutOfDeviceMemory, - .size = memory_size, - }; - @memset(output.outputs[location].?.blob, 0); - try rt.readOutput(output.outputs[location].?.blob, result_word); + for (0..4) |component| { + const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .output) catch |err| switch (err) { + SpvRuntimeError.NotFound => continue, + else => return err, + }; + const memory_size = try rt.getResultMemorySize(result_word); + output.outputs[location][component] = .{ + .interpolation_type = if (rt.hasResultDecoration(result_word, .Flat) or resultIsInteger(rt, result_word)) .flat else .smooth, // TODO : handle noperspective + .blob = data.allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return VkError.OutOfDeviceMemory, + .size = memory_size, + }; + @memset(output.outputs[location][component].?.blob, 0); + try rt.readOutput(output.outputs[location][component].?.blob, result_word); + } } try rt.flushDescriptorSets(data.allocator); @@ -110,6 +116,25 @@ fn setupBuiltins(rt: *spv.Runtime, vertex_index: usize, instance_index: usize) ! try rt.writeBuiltIn(std.mem.asBytes(&instance_index_u32), .InstanceIndex); } +fn resultIsInteger(rt: *spv.Runtime, result_word: spv.SpvWord) bool { + const value = rt.results[result_word].getConstValue() catch return false; + return switch (value.*) { + .Int, + .Vector2i32, + .Vector3i32, + .Vector4i32, + .Vector2u32, + .Vector3u32, + .Vector4u32, + => true, + .Vector => |lanes| lanes.len != 0 and switch (lanes[0]) { + .Int => true, + else => false, + }, + else => false, + }; +} + fn writeVertexInput( rt: *spv.Runtime, allocator: std.mem.Allocator, @@ -117,6 +142,54 @@ fn writeVertexInput( format: vk.Format, location: u32, ) !void { + var has_split_components = false; + for (1..4) |component| { + _ = rt.getResultByLocationComponent(location, @intCast(component), .input) catch |err| switch (err) { + SpvRuntimeError.NotFound => continue, + else => return err, + }; + has_split_components = true; + break; + } + + if (has_split_components) { + for (0..4) |component| { + const result_word = rt.getResultByLocationComponent(location, @intCast(component), .input) catch |err| switch (err) { + SpvRuntimeError.NotFound => continue, + else => return err, + }; + 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); + continue; + } + + const input = allocator.alloc(u8, input_memory_size) catch return VkError.OutOfDeviceMemory; + defer allocator.free(input); + + @memset(input, 0); + if (raw_offset < raw_input.len) { + const copy_size = @min(input_memory_size, raw_input.len - raw_offset); + @memcpy(input[0..copy_size], raw_input[raw_offset .. raw_offset + copy_size]); + } + + if (component == 3 and input_memory_size >= @sizeOf(f32)) { + if (base.format.isUnnormalizedInteger(format)) { + const one: u32 = 1; + @memcpy(input[0..@sizeOf(u32)], std.mem.asBytes(&one)); + } else { + const one: f32 = 1.0; + @memcpy(input[0..@sizeOf(f32)], std.mem.asBytes(&one)); + } + } + + try rt.writeInput(input, result_word); + } + return; + } + const input_memory_size = try rt.getInputLocationMemorySize(location); if (raw_input.len >= input_memory_size) { diff --git a/src/vulkan/CommandBuffer.zig b/src/vulkan/CommandBuffer.zig index 194ea05..dfdc930 100644 --- a/src/vulkan/CommandBuffer.zig +++ b/src/vulkan/CommandBuffer.zig @@ -69,7 +69,11 @@ pub const DispatchTable = struct { resetEvent: *const fn (*Self, *Event, vk.PipelineStageFlags) VkError!void, 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, 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, + setStencilWriteMask: *const fn (*Self, vk.StencilFaceFlags, u32) VkError!void, setViewport: *const fn (*Self, u32, []const vk.Viewport) VkError!void, waitEvent: *const fn (*Self, *Event, vk.PipelineStageFlags, vk.PipelineStageFlags, []const vk.MemoryBarrier, []const vk.BufferMemoryBarrier, []const vk.ImageMemoryBarrier) VkError!void, }; @@ -162,8 +166,10 @@ pub inline fn beginRenderPass(self: *Self, render_pass: *RenderPass, framebuffer } pub fn bindDescriptorSets(self: *Self, bind_point: vk.PipelineBindPoint, first_set: u32, sets: []const vk.DescriptorSet, dynamic_offsets: []const u32) VkError!void { - std.debug.assert(sets.len < lib.VULKAN_MAX_DESCRIPTOR_SETS); - var inner_sets = [_]?*DescriptorSet{null} ** lib.VULKAN_MAX_DESCRIPTOR_SETS; + if (sets.len > lib.VULKAN_MAX_DESCRIPTOR_SETS or first_set > lib.VULKAN_MAX_DESCRIPTOR_SETS or first_set + sets.len > lib.VULKAN_MAX_DESCRIPTOR_SETS) + return VkError.ValidationFailed; + + var inner_sets: [lib.VULKAN_MAX_DESCRIPTOR_SETS]?*DescriptorSet = @splat(null); for (sets, inner_sets[0..sets.len]) |set, *inner_set| { inner_set.* = try NonDispatchable(DescriptorSet).fromHandleObject(set); } @@ -288,10 +294,26 @@ pub inline fn setEvent(self: *Self, event: *Event, stage: vk.PipelineStageFlags) try self.dispatch_table.setEvent(self, event, stage); } +pub inline fn setBlendConstants(self: *Self, constants: [4]f32) VkError!void { + try self.dispatch_table.setBlendConstants(self, constants); +} + pub inline fn setScissor(self: *Self, first: u32, scissor: []const vk.Rect2D) VkError!void { try self.dispatch_table.setScissor(self, first, scissor); } +pub inline fn setStencilCompareMask(self: *Self, face_mask: vk.StencilFaceFlags, compare_mask: u32) VkError!void { + try self.dispatch_table.setStencilCompareMask(self, face_mask, compare_mask); +} + +pub inline fn setStencilReference(self: *Self, face_mask: vk.StencilFaceFlags, reference: u32) VkError!void { + try self.dispatch_table.setStencilReference(self, face_mask, reference); +} + +pub inline fn setStencilWriteMask(self: *Self, face_mask: vk.StencilFaceFlags, write_mask: u32) VkError!void { + try self.dispatch_table.setStencilWriteMask(self, face_mask, write_mask); +} + pub inline fn setViewport(self: *Self, first: u32, viewports: []const vk.Viewport) VkError!void { try self.dispatch_table.setViewport(self, first, viewports); } diff --git a/src/vulkan/Pipeline.zig b/src/vulkan/Pipeline.zig index a718be6..950c7a3 100644 --- a/src/vulkan/Pipeline.zig +++ b/src/vulkan/Pipeline.zig @@ -48,6 +48,11 @@ mode: union(enum) { front_face: vk.FrontFace, line_width: f32, }, + color_blend: struct { + attachments: ?[]vk.PipelineColorBlendAttachmentState, + constants: [4]f32, + }, + depth_stencil: ?vk.PipelineDepthStencilStateCreateInfo, dynamic_state: DynamicState, }, }, @@ -142,6 +147,23 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe .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, }, + .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, + .constants = state.blend_constants, + }; + } + + break :blk .{ + .attachments = null, + .constants = .{ 0.0, 0.0, 0.0, 0.0 }, + }; + }, + .depth_stencil = if (info.p_depth_stencil_state) |state| state.* else null, .dynamic_state = blk: { var state: DynamicState = .{}; @@ -181,6 +203,9 @@ 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.color_blend.attachments) |attachments| { + allocator.free(attachments); + } }, } self.layout.unref(allocator); diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index 8659d3a..e9b2a82 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -1997,10 +1997,7 @@ pub export fn apeCmdSetBlendConstants(p_cmd: vk.CommandBuffer, p_constants: [*]f const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); const constants = [4]f32{ p_constants[0], p_constants[1], p_constants[2], p_constants[3] }; - notImplementedWarning(); - - _ = cmd; - _ = constants; + cmd.setBlendConstants(constants) catch |err| return errorLogger(err); } pub export fn apeCmdSetDepthBias(p_cmd: vk.CommandBuffer, constant_factor: f32, clamp: f32, slope_factor: f32) callconv(vk.vulkan_call_conv) void { @@ -2064,12 +2061,7 @@ pub export fn apeCmdSetStencilCompareMask(p_cmd: vk.CommandBuffer, face_mask: vk defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = face_mask; - _ = compare_mask; + cmd.setStencilCompareMask(face_mask, compare_mask) catch |err| return errorLogger(err); } pub export fn apeCmdSetStencilReference(p_cmd: vk.CommandBuffer, face_mask: vk.StencilFaceFlags, reference: u32) callconv(vk.vulkan_call_conv) void { @@ -2077,12 +2069,7 @@ pub export fn apeCmdSetStencilReference(p_cmd: vk.CommandBuffer, face_mask: vk.S defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = face_mask; - _ = reference; + cmd.setStencilReference(face_mask, reference) catch |err| return errorLogger(err); } pub export fn apeCmdSetStencilWriteMask(p_cmd: vk.CommandBuffer, face_mask: vk.StencilFaceFlags, write_mask: u32) callconv(vk.vulkan_call_conv) void { @@ -2090,12 +2077,7 @@ pub export fn apeCmdSetStencilWriteMask(p_cmd: vk.CommandBuffer, face_mask: vk.S defer entryPointEndLogTrace(); const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err); - - notImplementedWarning(); - - _ = cmd; - _ = face_mask; - _ = write_mask; + cmd.setStencilWriteMask(face_mask, write_mask) catch |err| return errorLogger(err); } pub export fn apeCmdSetViewport(p_cmd: vk.CommandBuffer, first: u32, count: u32, viewports: [*]const vk.Viewport) callconv(vk.vulkan_call_conv) void {