From 47c63705e2206d6f1b2c2298ba69bc88ac2da9f3 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Sun, 21 Jun 2026 14:26:32 +0200 Subject: [PATCH] fixing spirv interpreter image API functions --- build.zig.zon | 4 +- src/software/SoftPipeline.zig | 66 +++++++++++++----- src/software/SoftSampler.zig | 22 +++--- src/software/device/ComputeDispatcher.zig | 3 + src/software/device/rasterizer.zig | 14 +++- src/software/device/rasterizer/bresenham.zig | 3 +- src/software/device/rasterizer/common.zig | 28 +++++--- .../device/rasterizer/edge_function.zig | 67 ++++++++++++++++--- 8 files changed, 158 insertions(+), 49 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 8503997..17355f3 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -32,8 +32,8 @@ // Soft dependencies .SPIRV_Interpreter = .{ - .url = "git+https://github.com/Kbz-8/SPIRV-Interpreter#25ef7751c6c0610179e65c7a0d1b8eff2165acbe", - .hash = "SPIRV_Interpreter-0.0.1-ajmpn6HuBwDtrbf_UHTbmbSCTgPBuNimCMt-l5NlU6tR", + .url = "git+https://github.com/Kbz-8/SPIRV-Interpreter#ae0914647bbc48683dd10108b5cae9acc54fe759", + .hash = "SPIRV_Interpreter-0.0.1-ajmpn9_vBwCUxGCNVFXUgZXaDv0OyNz8sqhzuyFD7hTU", .lazy = true, }, //.SPIRV_Interpreter = .{ diff --git a/src/software/SoftPipeline.zig b/src/software/SoftPipeline.zig index 230bcbc..c9a047d 100644 --- a/src/software/SoftPipeline.zig +++ b/src/software/SoftPipeline.zig @@ -251,12 +251,7 @@ fn createShader( }; } -fn initRuntime( - allocator: std.mem.Allocator, - module: *SoftShaderModule, - stage: *const vk.PipelineShaderStageCreateInfo, - image_api: spv.Runtime.ImageAPI, -) VkError!spv.Runtime { +fn initRuntime(allocator: std.mem.Allocator, module: *SoftShaderModule, stage: *const vk.PipelineShaderStageCreateInfo, image_api: spv.Runtime.ImageAPI) VkError!spv.Runtime { var runtime = spv.Runtime.init(allocator, &module.module, image_api) catch |err| { std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to initialize, {s}", .{@errorName(err)}); return VkError.Unknown; @@ -311,6 +306,33 @@ fn imageReadAspect(image_view: *SoftImageView, comptime int_read: bool) vk.Image return aspect; } +fn sampledTexelOffset(image: *SoftImage, offset: vk.Offset3D, subresource: vk.ImageSubresource, sample_index: u32) VkError!usize { + const sample_count = image.interface.samples.toInt(); + if (sample_index >= sample_count) + return VkError.ValidationFailed; + + return try image.getTexelMemoryOffset(offset, subresource) + + @as(usize, sample_index) * image.getMipLevelSize(subresource.aspect_mask, subresource.mip_level); +} + +fn readImageFloat4Sample(image: *SoftImage, offset: vk.Offset3D, subresource: vk.ImageSubresource, format: vk.Format, sample_index: u32) VkError!zm.F32x4 { + if (image.interface.samples.toInt() == 1) + return image.readFloat4(offset, subresource, format); + + const texel_size = base.format.texelSize(format); + const texel_offset = try sampledTexelOffset(image, offset, subresource, sample_index); + return blitter.readFloat4(try image.mapAsSliceWithAddedOffset(u8, texel_offset, texel_size), format); +} + +fn readImageInt4Sample(image: *SoftImage, offset: vk.Offset3D, subresource: vk.ImageSubresource, format: vk.Format, sample_index: u32) VkError!@Vector(4, u32) { + if (image.interface.samples.toInt() == 1) + return image.readInt4(offset, subresource, format); + + const texel_size = base.format.texelSize(format); + const texel_offset = try sampledTexelOffset(image, offset, subresource, sample_index); + return blitter.readInt4(try image.mapAsSliceWithAddedOffset(u8, texel_offset, texel_size), format); +} + fn subpassDataCoord(x: i32, y: i32, z: i32) SpvRuntimeError!vk.Offset3D { const coord = current_fragment_coord orelse return SpvRuntimeError.Unknown; return .{ .x = coord.x + x, .y = coord.y + y, .z = coord.z + z }; @@ -340,14 +362,18 @@ fn readImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, else => 0, }; const aspect_mask = imageReadAspect(image_view, false); - pixel = SoftSampler.swizzleFloat4(image.readFloat4( + const subresource = vk.ImageSubresource{ + .aspect_mask = aspect_mask, + .mip_level = mip_level, + .array_layer = array_layer, + }; + const sample_index: u32 = if (image.interface.samples.toInt() > 1) @intCast(z) else 0; + pixel = SoftSampler.swizzleFloat4(readImageFloat4Sample( + image, image_coord, - .{ - .aspect_mask = aspect_mask, - .mip_level = mip_level, - .array_layer = array_layer, - }, + subresource, base.format.fromAspect(image_view.interface.format, aspect_mask), + sample_index, ) catch return SpvRuntimeError.Unknown, image_view.interface.components); } return .{ @@ -382,14 +408,18 @@ fn readImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, l else => 0, }; const aspect_mask = imageReadAspect(image_view, true); - pixel = SoftSampler.swizzleInt4(image.readInt4( + const subresource = vk.ImageSubresource{ + .aspect_mask = aspect_mask, + .mip_level = mip_level, + .array_layer = array_layer, + }; + const sample_index: u32 = if (image.interface.samples.toInt() > 1) @intCast(z) else 0; + pixel = SoftSampler.swizzleInt4(readImageInt4Sample( + image, image_coord, - .{ - .aspect_mask = aspect_mask, - .mip_level = mip_level, - .array_layer = array_layer, - }, + subresource, base.format.fromAspect(image_view.interface.format, aspect_mask), + sample_index, ) catch return SpvRuntimeError.Unknown, image_view.interface.components); } return .{ diff --git a/src/software/SoftSampler.zig b/src/software/SoftSampler.zig index 5b0003a..db5670e 100644 --- a/src/software/SoftSampler.zig +++ b/src/software/SoftSampler.zig @@ -383,10 +383,13 @@ fn readSampledFloat4( std.math.clamp(@as(i32, @intFromFloat(texel.u * width_f)), 0, @as(i32, @intCast(extent.width)) - 1) else sampleAddressOrBorder(ix, extent.width, sampler.interface.address_mode_u) orelse return samplerBorderColor(sampler, format); - const sy = if (dim == .Cube) - std.math.clamp(@as(i32, @intFromFloat(texel.v * height_f)), 0, @as(i32, @intCast(extent.height)) - 1) - else - sampleAddressOrBorder(iy, extent.height, sampler.interface.address_mode_v) orelse return samplerBorderColor(sampler, format); + const sy = switch (image_view.interface.view_type) { + .@"1d", .@"1d_array" => 0, + else => if (dim == .Cube) + std.math.clamp(@as(i32, @intFromFloat(texel.v * height_f)), 0, @as(i32, @intCast(extent.height)) - 1) + else + sampleAddressOrBorder(iy, extent.height, sampler.interface.address_mode_v) orelse return samplerBorderColor(sampler, format), + }; const color = try image.readFloat4( .{ @@ -458,10 +461,13 @@ fn readSampledInt4( std.math.clamp(@as(i32, @intFromFloat(texel.u * width_f)), 0, @as(i32, @intCast(extent.width)) - 1) else sampleAddressOrBorder(ix, extent.width, sampler.interface.address_mode_u) orelse return samplerBorderColorInt(sampler, format); - const sy = if (dim == .Cube) - std.math.clamp(@as(i32, @intFromFloat(texel.v * height_f)), 0, @as(i32, @intCast(extent.height)) - 1) - else - sampleAddressOrBorder(iy, extent.height, sampler.interface.address_mode_v) orelse return samplerBorderColorInt(sampler, format); + const sy = switch (image_view.interface.view_type) { + .@"1d", .@"1d_array" => 0, + else => if (dim == .Cube) + std.math.clamp(@as(i32, @intFromFloat(texel.v * height_f)), 0, @as(i32, @intCast(extent.height)) - 1) + else + sampleAddressOrBorder(iy, extent.height, sampler.interface.address_mode_v) orelse return samplerBorderColorInt(sampler, format), + }; return image.readInt4( .{ diff --git a/src/software/device/ComputeDispatcher.zig b/src/software/device/ComputeDispatcher.zig index ab4e95e..2656531 100644 --- a/src/software/device/ComputeDispatcher.zig +++ b/src/software/device/ComputeDispatcher.zig @@ -286,6 +286,9 @@ fn setupSubgroupBuiltins(self: *Self, rt: *spv.Runtime, group_id: @Vector(3, u32 local_invocation[0] = idx; const global_invocation_index = local_base + local_invocation; + const local_invocation_index_u32: u32 = @intCast(local_invocation_index); + rt.writeBuiltIn(std.mem.asBytes(&local_invocation), .LocalInvocationId) catch {}; + rt.writeBuiltIn(std.mem.asBytes(&local_invocation_index_u32), .LocalInvocationIndex) catch {}; rt.writeBuiltIn(std.mem.asBytes(&global_invocation_index), .GlobalInvocationId) catch {}; } diff --git a/src/software/device/rasterizer.zig b/src/software/device/rasterizer.zig index 482fc80..e683de7 100644 --- a/src/software/device/rasterizer.zig +++ b/src/software/device/rasterizer.zig @@ -394,7 +394,19 @@ fn clipTransformAndRasterizePoint( }; } - try common.writeToTargets(fragment_result.outputs, draw_call, color_attachment_access, depth_attachment_access, stencil_attachment_access, true, @intCast(px), @intCast(py), fragment_result.depth orelse transformed.position[2], fragment_result.sample_mask); + try common.writeToTargets( + fragment_result.outputs, + draw_call, + color_attachment_access, + depth_attachment_access, + stencil_attachment_access, + true, + @intCast(px), + @intCast(py), + fragment_result.depth orelse transformed.position[2], + null, + fragment_result.sample_mask, + ); } } } diff --git a/src/software/device/rasterizer/bresenham.zig b/src/software/device/rasterizer/bresenham.zig index 908612b..bfa3e1b 100644 --- a/src/software/device/rasterizer/bresenham.zig +++ b/src/software/device/rasterizer/bresenham.zig @@ -76,7 +76,7 @@ pub fn drawLine( if (runtimes_count == 0) return; - const step_count: usize = if (d_x == 0) 1 else @intCast(d_x); + const step_count: usize = @intCast(@max(d_x, 0) + 1); const runs_count = @min(runtimes_count, step_count); const steps_per_run = @divTrunc(step_count + runs_count - 1, runs_count); @@ -193,6 +193,7 @@ inline fn run(data: RunData) !void { @intCast(pixel_x), @intCast(pixel_y), fragment_result.depth orelse z, + null, fragment_result.sample_mask, ); } diff --git a/src/software/device/rasterizer/common.zig b/src/software/device/rasterizer/common.zig index 422383c..1a1e44a 100644 --- a/src/software/device/rasterizer/common.zig +++ b/src/software/device/rasterizer/common.zig @@ -223,6 +223,11 @@ pub fn interpolateVertexOutputs( } const len = @min(out0.size, out1.size, out2.size); + if (std.mem.eql(u8, out0.blob[0..len], out1.blob[0..len]) and std.mem.eql(u8, out0.blob[0..len], out2.blob[0..len])) { + inputs[location][component] = .{ .blob = out0.blob, .size = len, .free_responsability = false }; + continue; + } + const input = allocator.alloc(u8, len + @sizeOf(F32x4)) catch return VkError.OutOfDeviceMemory; @memset(input, 0); @@ -407,13 +412,14 @@ pub fn writeToTargets( x: usize, y: usize, z: f32, + coverage_sample_mask: ?vk.SampleMask, fragment_sample_mask: ?vk.SampleMask, ) 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; - if (!sampleMaskEnablesAnySample(pipeline_data.multisample, fragment_sample_mask)) + if (!sampleMaskEnablesAnySample(pipeline_data.multisample, coverage_sample_mask, fragment_sample_mask)) return; if (x >= draw_call.framebuffer.interface.width or y >= draw_call.framebuffer.interface.height) @@ -426,7 +432,7 @@ pub fn writeToTargets( const sample_count = pipeline_data.multisample.rasterization_samples.toInt(); for (0..sample_count) |sample_index| { - if (!sampleMaskEnablesSample(pipeline_data.multisample, fragment_sample_mask, sample_index)) + if (!sampleMaskEnablesSample(pipeline_data.multisample, coverage_sample_mask, fragment_sample_mask, sample_index)) continue; var stencil_state: ?vk.StencilOpState = null; @@ -505,23 +511,23 @@ pub fn writeToTargets( } } -fn sampleMaskEnablesAnySample(multisample: anytype, fragment_sample_mask: ?vk.SampleMask) bool { +fn sampleMaskEnablesAnySample(multisample: anytype, coverage_sample_mask: ?vk.SampleMask, fragment_sample_mask: ?vk.SampleMask) bool { const sample_count = multisample.rasterization_samples.toInt(); - if (multisample.sample_mask == null and fragment_sample_mask == null) + if (multisample.sample_mask == null and coverage_sample_mask == null and fragment_sample_mask == null) return true; if (multisample.sample_mask) |pipeline_sample_mask| { for (pipeline_sample_mask, 0..) |word, word_index| { - if (sampleMaskWordEnablesAnySample(sample_count, word_index, word, fragment_sample_mask)) + if (sampleMaskWordEnablesAnySample(sample_count, word_index, word, coverage_sample_mask, fragment_sample_mask)) return true; } return false; } - return sampleMaskWordEnablesAnySample(sample_count, 0, std.math.maxInt(vk.SampleMask), fragment_sample_mask); + return sampleMaskWordEnablesAnySample(sample_count, 0, std.math.maxInt(vk.SampleMask), coverage_sample_mask, fragment_sample_mask); } -fn sampleMaskWordEnablesAnySample(sample_count: usize, word_index: usize, pipeline_word: vk.SampleMask, fragment_sample_mask: ?vk.SampleMask) bool { +fn sampleMaskWordEnablesAnySample(sample_count: usize, word_index: usize, pipeline_word: vk.SampleMask, coverage_sample_mask: ?vk.SampleMask, fragment_sample_mask: ?vk.SampleMask) bool { const remaining_samples = sample_count -| (word_index * 32); const active_bits = @min(remaining_samples, 32); if (active_bits == 0) @@ -532,11 +538,12 @@ fn sampleMaskWordEnablesAnySample(sample_count: usize, word_index: usize, pipeli else (@as(vk.SampleMask, 1) << @intCast(active_bits)) - 1; + const coverage_word = if (word_index == 0) coverage_sample_mask orelse std.math.maxInt(vk.SampleMask) else std.math.maxInt(vk.SampleMask); const fragment_word = if (word_index == 0) fragment_sample_mask orelse std.math.maxInt(vk.SampleMask) else std.math.maxInt(vk.SampleMask); - return ((pipeline_word & fragment_word) & active_mask) != 0; + return ((pipeline_word & coverage_word & fragment_word) & active_mask) != 0; } -fn sampleMaskEnablesSample(multisample: anytype, fragment_sample_mask: ?vk.SampleMask, sample_index: usize) bool { +fn sampleMaskEnablesSample(multisample: anytype, coverage_sample_mask: ?vk.SampleMask, fragment_sample_mask: ?vk.SampleMask, sample_index: usize) bool { if (sample_index >= multisample.rasterization_samples.toInt()) return false; @@ -549,6 +556,7 @@ fn sampleMaskEnablesSample(multisample: anytype, fragment_sample_mask: ?vk.Sampl else std.math.maxInt(vk.SampleMask); + const coverage_word = if (word_index == 0) coverage_sample_mask orelse std.math.maxInt(vk.SampleMask) else std.math.maxInt(vk.SampleMask); const fragment_word = if (word_index == 0) fragment_sample_mask orelse std.math.maxInt(vk.SampleMask) else std.math.maxInt(vk.SampleMask); - return (pipeline_word & fragment_word & bit) != 0; + return (pipeline_word & coverage_word & fragment_word & bit) != 0; } diff --git a/src/software/device/rasterizer/edge_function.zig b/src/software/device/rasterizer/edge_function.zig index 4d276ad..1898722 100644 --- a/src/software/device/rasterizer/edge_function.zig +++ b/src/software/device/rasterizer/edge_function.zig @@ -129,7 +129,7 @@ inline fn edgeFunction(a: F32x4, b: F32x4, p: F32x4) f32 { 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); + 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 { @@ -139,6 +139,56 @@ inline fn edgeContainsPixel(a: F32x4, b: F32x4, edge_value: f32, area: f32) bool edge_value < 0.0 or (edge_value == 0.0 and isInclusiveEdge(b, a)); } +inline fn standardSamplePosition(sample_count: usize, sample_index: usize) struct { x: f32, y: f32 } { + return switch (sample_count) { + 1 => .{ .x = 0.5, .y = 0.5 }, + 2 => switch (sample_index) { + 0 => .{ .x = 0.75, .y = 0.75 }, + 1 => .{ .x = 0.25, .y = 0.25 }, + else => .{ .x = 0.5, .y = 0.5 }, + }, + 4 => switch (sample_index) { + 0 => .{ .x = 0.375, .y = 0.125 }, + 1 => .{ .x = 0.875, .y = 0.375 }, + 2 => .{ .x = 0.125, .y = 0.625 }, + 3 => .{ .x = 0.625, .y = 0.875 }, + else => .{ .x = 0.5, .y = 0.5 }, + }, + else => .{ .x = 0.5, .y = 0.5 }, + }; +} + +fn triangleCoverageMask(data: RunData, x: i32, y: i32, sample_count: usize) vk.SampleMask { + 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 p = zm.f32x4( + @as(f32, @floatFromInt(x)) + sample_pos.x, + @as(f32, @floatFromInt(y)) + sample_pos.y, + 0.0, + 1.0, + ); + + const w0 = edgeFunction(data.v1.position, data.v2.position, p); + const w1 = edgeFunction(data.v2.position, data.v0.position, p); + const w2 = edgeFunction(data.v0.position, data.v1.position, p); + + 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) { + const bit_index: u5 = @intCast(sample_index); + mask |= @as(vk.SampleMask, 1) << bit_index; + } + } + return mask; +} + 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)}); @@ -159,20 +209,18 @@ inline fn run(data: RunData) !void { continue; } + const pipeline_data = data.draw_call.renderer.state.pipeline.?.interface.mode.graphics; + const sample_count = pipeline_data.multisample.rasterization_samples.toInt(); + const coverage_sample_mask = triangleCoverageMask(data, x, y, sample_count); + if (coverage_sample_mask == 0) + continue; + const p = zm.f32x4(@as(f32, @floatFromInt(x)) + 0.5, @as(f32, @floatFromInt(y)) + 0.5, 0.0, 1.0); const w0 = edgeFunction(data.v1.position, data.v2.position, p); const w1 = edgeFunction(data.v2.position, data.v0.position, p); const w2 = edgeFunction(data.v0.position, data.v1.position, p); - 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; - const b0 = w0 / data.area; const b1 = w1 / data.area; const b2 = w2 / data.area; @@ -252,6 +300,7 @@ inline fn run(data: RunData) !void { @intCast(x), @intCast(y), fragment_result.depth orelse z, + coverage_sample_mask, fragment_result.sample_mask, ); }