From 7c6e0dc2a8ffef14e13bde0a2ecdaa51d1c9ae17 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Sun, 28 Jun 2026 13:26:15 +0200 Subject: [PATCH] improving push constants support, fixing image resolving, lots of rasterizer fixes --- build.zig.zon | 4 +- src/software/SoftCommandBuffer.zig | 24 ++-- src/software/SoftPhysicalDevice.zig | 4 +- src/software/SoftPipeline.zig | 81 +++++++---- src/software/SoftPipelineCache.zig | 1 + src/software/device/Device.zig | 2 +- src/software/device/Renderer.zig | 4 +- src/software/device/blitter.zig | 14 +- src/software/device/fragment.zig | 33 ++++- src/software/device/rasterizer.zig | 87 +++++++++--- src/software/device/rasterizer/bresenham.zig | 92 ++++++++++++- src/software/device/rasterizer/common.zig | 114 ++++++++++++++-- .../device/rasterizer/edge_function.zig | 127 +++++++++++++++++- src/software/device/vertex_dispatcher.zig | 45 +++++-- src/software/lib.zig | 2 +- src/vulkan/Pipeline.zig | 6 + src/vulkan/lib_vulkan.zig | 4 + 17 files changed, 547 insertions(+), 97 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 50b8a03..eaa02f7 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#cba8a4723d9b21c9c58494be8ce477124946f52a", - .hash = "SPIRV_Interpreter-0.0.1-ajmpnyQ4CABQ7B36QdzikdLDH1E5LMk-ib8O2gAHgwbE", + .url = "git+https://github.com/Kbz-8/SPIRV-Interpreter#b9b6087fef5a61a3d9e3187ff7c66dffc54603da", + .hash = "SPIRV_Interpreter-0.0.1-ajmpn9JxCADthSmyktTCV_jIA8iJU_aiM_kboXqfPDDD", .lazy = true, }, //.SPIRV_Interpreter = .{ diff --git a/src/software/SoftCommandBuffer.zig b/src/software/SoftCommandBuffer.zig index 88a30be..0585980 100644 --- a/src/software/SoftCommandBuffer.zig +++ b/src/software/SoftCommandBuffer.zig @@ -1152,16 +1152,22 @@ pub fn pushConstants(interface: *Interface, stages: vk.ShaderStageFlags, offset: pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { const impl: *Impl = @ptrCast(@alignCast(context)); - - const state = &device.pipeline_states[ - if (impl.stages.vertex_bit or impl.stages.fragment_bit) - ExecutionDevice.GRAPHICS_PIPELINE_STATE - else - ExecutionDevice.COMPUTE_PIPELINE_STATE - ]; - const size = @min(lib.PUSH_CONSTANT_SIZE - impl.offset, impl.blob.len); - @memcpy(state.push_constant_blob[impl.offset .. impl.offset + size], impl.blob[0..size]); + + if (impl.stages.vertex_bit or + impl.stages.tessellation_control_bit or + impl.stages.tessellation_evaluation_bit or + impl.stages.geometry_bit or + impl.stages.fragment_bit) + { + const state = &device.pipeline_states[ExecutionDevice.GRAPHICS_PIPELINE_STATE]; + @memcpy(state.push_constant_blob[impl.offset .. impl.offset + size], impl.blob[0..size]); + } + + if (impl.stages.compute_bit) { + const state = &device.pipeline_states[ExecutionDevice.COMPUTE_PIPELINE_STATE]; + @memcpy(state.push_constant_blob[impl.offset .. impl.offset + size], impl.blob[0..size]); + } } }; diff --git a/src/software/SoftPhysicalDevice.zig b/src/software/SoftPhysicalDevice.zig index 4b441a1..d352907 100644 --- a/src/software/SoftPhysicalDevice.zig +++ b/src/software/SoftPhysicalDevice.zig @@ -118,8 +118,8 @@ pub fn create(allocator: std.mem.Allocator, instance: *base.Instance) VkError!*S .max_compute_work_group_invocations = 128, .max_compute_work_group_size = .{ 128, 128, 64 }, .sub_pixel_precision_bits = 4, - .sub_texel_precision_bits = 4, - .mipmap_precision_bits = 4, + .sub_texel_precision_bits = 8, + .mipmap_precision_bits = 8, .max_draw_indexed_index_value = 4294967295, .max_draw_indirect_count = 65535, .max_sampler_lod_bias = 2.0, diff --git a/src/software/SoftPipeline.zig b/src/software/SoftPipeline.zig index c9a047d..93edb43 100644 --- a/src/software/SoftPipeline.zig +++ b/src/software/SoftPipeline.zig @@ -273,6 +273,7 @@ fn applySpecialization(runtime: *spv.Runtime, allocator: std.mem.Allocator, spec .size = @intCast(entry.size), }, data) catch return VkError.OutOfDeviceMemory; } + runtime.applySpecializationLayout(allocator) catch return VkError.OutOfDeviceMemory; } fn imageApi() spv.Runtime.ImageAPI { @@ -338,13 +339,48 @@ fn subpassDataCoord(x: i32, y: i32, z: i32) SpvRuntimeError!vk.Offset3D { return .{ .x = coord.x + x, .y = coord.y + y, .z = coord.z + z }; } +fn bufferViewRange(buffer_view: *const SoftBufferView) SpvRuntimeError!usize { + const offset: usize = @intCast(buffer_view.interface.offset); + if (offset > buffer_view.interface.buffer.size) + return SpvRuntimeError.Unknown; + + if (buffer_view.interface.range == vk.WHOLE_SIZE) + return @intCast(buffer_view.interface.buffer.size - offset); + + return @intCast(buffer_view.interface.range); +} + +fn mapBufferViewTexel(buffer_view: *const SoftBufferView, x: i32) SpvRuntimeError![]u8 { + if (x < 0) + return SpvRuntimeError.Unknown; + + const texel_size = base.format.texelSize(buffer_view.interface.format); + const texel_index: usize = @intCast(x); + const range = try bufferViewRange(buffer_view); + const texel_offset = std.math.mul(usize, texel_index, texel_size) catch return SpvRuntimeError.Unknown; + if (texel_offset > range or texel_size > range - texel_offset) + return SpvRuntimeError.Unknown; + + const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer)); + return buffer.mapAsSliceWithOffset( + u8, + @as(usize, @intCast(buffer_view.interface.offset)) + texel_offset, + texel_size, + ) catch return SpvRuntimeError.Unknown; +} + +fn bufferViewFromContext(context: *anyopaque) SpvRuntimeError!*SoftBufferView { + const addr = @intFromPtr(context); + if (!std.mem.isAligned(addr, @alignOf(SoftBufferView))) + return SpvRuntimeError.Unknown; + return @ptrCast(@alignCast(context)); +} + fn readImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, lod: ?i32) SpvRuntimeError!spv.Runtime.Vec4(f32) { var pixel = zm.f32x4s(0.0); if (dim == .Buffer) { - const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context)); - const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer)); - const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown; - pixel = blitter.readFloat4(map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format); + const buffer_view = try bufferViewFromContext(context); + pixel = blitter.readFloat4(try mapBufferViewTexel(buffer_view, x), buffer_view.interface.format); } else { const image_view: *SoftImageView = @ptrCast(@alignCast(context)); const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); @@ -387,10 +423,8 @@ fn readImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, fn readImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, lod: ?i32) SpvRuntimeError!spv.Runtime.Vec4(u32) { var pixel = @Vector(4, u32){ 0, 0, 0, 0 }; if (dim == .Buffer) { - const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context)); - const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer)); - const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown; - pixel = blitter.readInt4(map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format); + const buffer_view = try bufferViewFromContext(context); + pixel = blitter.readInt4(try mapBufferViewTexel(buffer_view, x), buffer_view.interface.format); } else { const image_view: *SoftImageView = @ptrCast(@alignCast(context)); const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); @@ -433,10 +467,8 @@ fn readImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, l fn writeImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(f32)) SpvRuntimeError!void { const vec_pixel = zm.f32x4(pixel.x, pixel.y, pixel.z, pixel.w); if (dim == .Buffer) { - const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context)); - const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer)); - const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown; - blitter.writeFloat4(vec_pixel, map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format); + const buffer_view = try bufferViewFromContext(context); + blitter.writeFloat4(vec_pixel, try mapBufferViewTexel(buffer_view, x), buffer_view.interface.format); } else { const image_view: *SoftImageView = @ptrCast(@alignCast(context)); const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); @@ -461,10 +493,8 @@ fn writeImageFloat4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32 fn writeImageInt4(context: *anyopaque, dim: spv.SpvDim, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(u32)) SpvRuntimeError!void { const vec_pixel = @Vector(4, u32){ pixel.x, pixel.y, pixel.z, pixel.w }; if (dim == .Buffer) { - const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context)); - const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer)); - const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown; - blitter.writeInt4(vec_pixel, map[(@as(usize, @intCast(x)) * base.format.texelSize(buffer_view.interface.format))..], buffer_view.interface.format); + const buffer_view = try bufferViewFromContext(context); + blitter.writeInt4(vec_pixel, try mapBufferViewTexel(buffer_view, x), buffer_view.interface.format); } else { const image_view: *SoftImageView = @ptrCast(@alignCast(context)); const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); @@ -490,10 +520,8 @@ fn sampleImageFloat4(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, var pixel = zm.f32x4s(0.0); if (dim == .Buffer) { - const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context)); - const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer)); - const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown; - _ = map; + const buffer_view = try bufferViewFromContext(context); + _ = try bufferViewRange(buffer_view); } else { const image_view: *SoftImageView = @ptrCast(@alignCast(context)); const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); @@ -514,10 +542,8 @@ fn sampleImageInt4(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, x var pixel = @Vector(4, u32){ 0, 0, 0, 0 }; if (dim == .Buffer) { - const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context)); - const buffer: *SoftBuffer = @alignCast(@fieldParentPtr("interface", buffer_view.interface.buffer)); - const map = buffer.mapAsSliceWithOffset(u8, buffer_view.interface.offset, buffer_view.interface.range) catch return SpvRuntimeError.Unknown; - _ = map; + const buffer_view = try bufferViewFromContext(context); + _ = try bufferViewRange(buffer_view); } else { const image_view: *SoftImageView = @ptrCast(@alignCast(context)); const image: *SoftImage = @alignCast(@fieldParentPtr("interface", image_view.interface.image)); @@ -546,11 +572,8 @@ fn sampleImageDref(context: *anyopaque, context2: *anyopaque, dim: spv.SpvDim, x fn queryImageSize(context: *anyopaque, dim: spv.SpvDim, arrayed: bool, lod: ?i32) SpvRuntimeError!spv.Runtime.Vec4(u32) { if (dim == .Buffer) { - const buffer_view: *SoftBufferView = @ptrCast(@alignCast(context)); - const range = if (buffer_view.interface.range == vk.WHOLE_SIZE) - buffer_view.interface.buffer.size - buffer_view.interface.offset - else - buffer_view.interface.range; + const buffer_view = try bufferViewFromContext(context); + const range = try bufferViewRange(buffer_view); return .{ .x = @intCast(@divTrunc(range, base.format.texelSize(buffer_view.interface.format))), .y = 0, diff --git a/src/software/SoftPipelineCache.zig b/src/software/SoftPipelineCache.zig index f12cdb1..982ba0c 100644 --- a/src/software/SoftPipelineCache.zig +++ b/src/software/SoftPipelineCache.zig @@ -163,6 +163,7 @@ fn applySpecialization(runtime: *spv.Runtime, allocator: std.mem.Allocator, spec data, ) catch return VkError.OutOfDeviceMemory; } + runtime.applySpecializationLayout(allocator) catch return VkError.OutOfDeviceMemory; } fn cloneSpecs(allocator: std.mem.Allocator, specialization: ?*const vk.SpecializationInfo) VkError![]SpecConstant { diff --git a/src/software/device/Device.zig b/src/software/device/Device.zig index 641e9f0..518cdf9 100644 --- a/src/software/device/Device.zig +++ b/src/software/device/Device.zig @@ -204,7 +204,7 @@ pub fn writeDescriptorSets(state: *PipelineState, rt: *spv.Runtime) !void { const addr: usize = @intFromPtr(buffer_view); writeDescriptorSet( rt, - .{ .raw = std.mem.asBytes(&addr) }, + .{ .sampled_image = .{ .image = addr, .sampler = addr } }, @as(u32, @intCast(set_index)), @as(u32, @intCast(binding_index)), @as(u32, @intCast(descriptor_index)), diff --git a/src/software/device/Renderer.zig b/src/software/device/Renderer.zig index 0848b5d..78a4fd1 100644 --- a/src/software/device/Renderer.zig +++ b/src/software/device/Renderer.zig @@ -166,12 +166,12 @@ pub fn init(device: *SoftDevice, state: *PipelineState, active_occlusion_queries } pub fn draw(self: *Self, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize) VkError!void { - var bounded_allocator: BoundedAllocator = .init(self.device.device_allocator.allocator(), @"1GiB"); + var bounded_allocator: BoundedAllocator = .init(self.device.device_allocator.allocator(), 4 * @"1GiB"); try self.drawCall(&bounded_allocator, vertex_count, instance_count, first_vertex, first_instance, null, null); } pub fn drawIndexed(self: *Self, index_count: usize, instance_count: usize, first_index: usize, first_instance: usize, vertex_offset: i32) VkError!void { - var bounded_allocator: BoundedAllocator = .init(self.device.device_allocator.allocator(), @"1GiB"); + var bounded_allocator: BoundedAllocator = .init(self.device.device_allocator.allocator(), 4 * @"1GiB"); const allocator = bounded_allocator.allocator(); const indexed_draw = try self.readIndexBuffer(allocator, index_count, first_index, vertex_offset); diff --git a/src/software/device/blitter.zig b/src/software/device/blitter.zig index 742ad06..daf7b80 100644 --- a/src/software/device/blitter.zig +++ b/src/software/device/blitter.zig @@ -186,9 +186,19 @@ fn sample(src: []const u8, pos: F32x4, dim: F32x4, slice_bytes: usize, pitch_byt z = std.math.clamp(z, 0, @as(usize, @intFromFloat(dim[2])) - 1); } - const src_map = src[computeOffset3D(x, y, z, slice_bytes, pitch_bytes, src_texel_size)..]; + const offset = computeOffset3D(x, y, z, slice_bytes, pitch_bytes, src_texel_size); + const src_map = src[offset..]; - color = readFloat4(src_map, state.src_format); + if (state.src_samples > 1 and state.dst_samples == 1 and !base.format.isUnnormalizedInteger(state.src_format)) { + const sample_stride = slice_bytes * @as(usize, @intFromFloat(dim[2])); + color = zm.f32x4s(0.0); + for (0..state.src_samples) |sample_index| { + color += readFloat4(src_map[sample_index * sample_stride ..], state.src_format); + } + color /= zm.f32x4s(@floatFromInt(state.src_samples)); + } else { + color = readFloat4(src_map, state.src_format); + } } else { var x: f32 = pos[0]; var y: f32 = pos[1]; diff --git a/src/software/device/fragment.zig b/src/software/device/fragment.zig index aba8f73..5e3c3ed 100644 --- a/src/software/device/fragment.zig +++ b/src/software/device/fragment.zig @@ -30,6 +30,7 @@ pub fn shaderInvocation( batch_id: usize, position: zm.F32x4, point_coord: ?@Vector(2, f32), + sample_id: ?u32, front_face: bool, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation, derivative_inputs: ?DerivativeInputs, @@ -67,6 +68,13 @@ pub fn shaderInvocation( else => return err, }; } + if (sample_id) |id| { + const sample_id_i32: i32 = @intCast(id); + rt.writeBuiltIn(allocator, std.mem.asBytes(&sample_id_i32), .SampleId) catch |err| switch (err) { + SpvRuntimeError.NotFound => {}, + else => return err, + }; + } rt.writeBuiltIn(allocator, std.mem.asBytes(&front_face), .FrontFacing) catch |err| switch (err) { SpvRuntimeError.NotFound => {}, else => return err, @@ -85,14 +93,28 @@ pub fn shaderInvocation( for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| { for (0..4) |component| { + var input = fragment_inputs[location][component]; const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .input) catch |err| switch (err) { - SpvRuntimeError.NotFound => continue, + SpvRuntimeError.NotFound => { + if (input.blob.len != 0) { + rt.writeInputLocation(input.blob, @intCast(location)) catch |write_err| switch (write_err) { + SpvRuntimeError.NotFound => {}, + else => return write_err, + }; + } + continue; + }, else => return err, }; - var input = fragment_inputs[location][component]; + const has_result_value = rt.results[result_word].variant != null; + const memory_size = if (has_result_value) + try rt.getResultMemorySize(result_word) + else if (input.blob.len == 0) + try rt.getInputLocationMemorySize(@intCast(location)) + else + input.blob.len; 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] = .{ @@ -104,7 +126,10 @@ pub fn shaderInvocation( } if (input.blob.len != 0) { - try rt.writeInput(allocator, input.blob, result_word); + if (!has_result_value or input.blob.len < memory_size) + try rt.writeInputLocation(input.blob, @intCast(location)) + else + try rt.writeInput(allocator, input.blob, result_word); if (derivatives) |derivative| { const dx = derivative.dx[location][component]; const dy = derivative.dy[location][component]; diff --git a/src/software/device/rasterizer.zig b/src/software/device/rasterizer.zig index e683de7..e7dcb94 100644 --- a/src/software/device/rasterizer.zig +++ b/src/software/device/rasterizer.zig @@ -171,6 +171,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato v0, v1, v2, + v0, color_attachment_access, if (depth_attachment_access) |*access| access else null, if (stencil_attachment_access) |*access| access else null, @@ -196,6 +197,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato v0, v1, v2, + v0, color_attachment_access, if (depth_attachment_access) |*access| access else null, if (stencil_attachment_access) |*access| access else null, @@ -227,6 +229,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato v0, v1, v2, + v0, color_attachment_access, if (depth_attachment_access) |*access| access else null, if (stencil_attachment_access) |*access| access else null, @@ -239,6 +242,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato v1, v0, v2, + v0, color_attachment_access, if (depth_attachment_access) |*access| access else null, if (stencil_attachment_access) |*access| access else null, @@ -266,6 +270,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato color_attachment_access, if (depth_attachment_access) |*access| access else null, if (stencil_attachment_access) |*access| access else null, + false, ); } }, @@ -288,6 +293,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato color_attachment_access, if (depth_attachment_access) |*access| access else null, if (stencil_attachment_access) |*access| access else null, + true, ); } } @@ -341,13 +347,31 @@ fn clipTransformAndRasterizePoint( var transformed = vertex.*; clip.viewportTransformVertex(draw_call.viewport, &transformed); - const point_size = transformed.point_size; - const min_x: i32 = @intFromFloat(@ceil(transformed.position[0] - (point_size / 2.0) - 0.5)); - const max_x: i32 = @intFromFloat(@ceil(transformed.position[0] + (point_size / 2.0) - 0.5) - 1.0); - const min_y: i32 = @intFromFloat(@ceil(transformed.position[1] - (point_size / 2.0) - 0.5)); - const max_y: i32 = @intFromFloat(@ceil(transformed.position[1] + (point_size / 2.0) - 0.5) - 1.0); - const point_min_x = transformed.position[0] - (point_size / 2.0); - const point_min_y = transformed.position[1] - (point_size / 2.0); + try rasterizeTransformedPoint( + allocator, + draw_call, + &transformed, + color_attachment_access, + depth_attachment_access, + stencil_attachment_access, + ); +} + +fn rasterizeTransformedPoint( + allocator: std.mem.Allocator, + draw_call: *DrawCall, + vertex: *Vertex, + color_attachment_access: []const ?common.RenderTargetAccess, + depth_attachment_access: ?*common.RenderTargetAccess, + stencil_attachment_access: ?*common.RenderTargetAccess, +) VkError!void { + const point_size = vertex.point_size; + const min_x: i32 = @intFromFloat(@ceil(vertex.position[0] - (point_size / 2.0) - 0.5)); + const max_x: i32 = @intFromFloat(@ceil(vertex.position[0] + (point_size / 2.0) - 0.5) - 1.0); + const min_y: i32 = @intFromFloat(@ceil(vertex.position[1] - (point_size / 2.0) - 0.5)); + const max_y: i32 = @intFromFloat(@ceil(vertex.position[1] + (point_size / 2.0) - 0.5) - 1.0); + const point_min_x = vertex.position[0] - (point_size / 2.0); + const point_min_y = vertex.position[1] - (point_size / 2.0); const pipeline = draw_call.renderer.state.pipeline orelse return; const has_fragment_shader = pipeline.stages.getPtr(.fragment) != null; @@ -375,10 +399,11 @@ fn clipTransformAndRasterizePoint( allocator, draw_call, 0, - zm.f32x4(frag_x, frag_y, transformed.position[2], 1.0 / transformed.position[3]), + zm.f32x4(frag_x, frag_y, vertex.position[2], 1.0 / vertex.position[3]), point_coord, + null, true, - try common.interpolateVertexOutputs(allocator, &transformed, &transformed, &transformed, 1.0, 0.0, 0.0), + try common.interpolateVertexOutputs(allocator, vertex, vertex, vertex, vertex, 1.0, 0.0, 0.0), null, ) catch |err| { if (err == SpvRuntimeError.Killed) @@ -403,7 +428,7 @@ fn clipTransformAndRasterizePoint( true, @intCast(px), @intCast(py), - fragment_result.depth orelse transformed.position[2], + fragment_result.depth orelse vertex.position[2], null, fragment_result.sample_mask, ); @@ -419,6 +444,7 @@ fn clipTransformAndRasterizeLine( color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, stencil_attachment_access: ?*common.RenderTargetAccess, + include_last_endpoint: bool, ) VkError!void { const clipped_line = (try clip.clipLine(allocator, v0, v1)) orelse return; @@ -428,15 +454,27 @@ fn clipTransformAndRasterizeLine( clip.viewportTransformVertex(draw_call.viewport, &tv0); clip.viewportTransformVertex(draw_call.viewport, &tv1); - try bresenham.drawLine( - allocator, - draw_call, - &tv0, - &tv1, - color_attachment_access, - depth_attachment_access, - stencil_attachment_access, - ); + if (include_last_endpoint) { + try bresenham.drawLineIncludingEndpoint( + allocator, + draw_call, + &tv0, + &tv1, + color_attachment_access, + depth_attachment_access, + stencil_attachment_access, + ); + } else { + try bresenham.drawLine( + allocator, + draw_call, + &tv0, + &tv1, + color_attachment_access, + depth_attachment_access, + stencil_attachment_access, + ); + } } fn clipTransformAndRasterizeTriangle( @@ -446,6 +484,7 @@ fn clipTransformAndRasterizeTriangle( v0: *Vertex, v1: *Vertex, v2: *Vertex, + provoking_vertex: *Vertex, color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, stencil_attachment_access: ?*common.RenderTargetAccess, @@ -471,6 +510,7 @@ fn clipTransformAndRasterizeTriangle( &tv0, &tv1, &tv2, + provoking_vertex, color_attachment_access, depth_attachment_access, stencil_attachment_access, @@ -485,6 +525,7 @@ fn rasterizeTriangle( v0: *Vertex, v1: *Vertex, v2: *Vertex, + provoking_vertex: *Vertex, color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, stencil_attachment_access: ?*common.RenderTargetAccess, @@ -499,13 +540,17 @@ fn rasterizeTriangle( 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, stencil_attachment_access, front_face), + .fill => try edge_function.drawTriangle(allocator, draw_call, v0, v1, v2, provoking_vertex, 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, 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 + .point => { + try rasterizeTransformedPoint(allocator, draw_call, v0, color_attachment_access, depth_attachment_access, stencil_attachment_access); + try rasterizeTransformedPoint(allocator, draw_call, v1, color_attachment_access, depth_attachment_access, stencil_attachment_access); + try rasterizeTransformedPoint(allocator, draw_call, v2, color_attachment_access, depth_attachment_access, stencil_attachment_access); + }, else => base.unsupported("polygon mode {any}", .{pipeline_data.rasterization.polygon_mode}), } } diff --git a/src/software/device/rasterizer/bresenham.zig b/src/software/device/rasterizer/bresenham.zig index bfa3e1b..0c2b2f2 100644 --- a/src/software/device/rasterizer/bresenham.zig +++ b/src/software/device/rasterizer/bresenham.zig @@ -1,6 +1,7 @@ const std = @import("std"); const base = @import("base"); const spv = @import("spv"); +const vk = @import("vulkan"); const zm = base.zm; const common = @import("common.zig"); @@ -41,6 +42,31 @@ pub fn drawLine( color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, stencil_attachment_access: ?*common.RenderTargetAccess, +) VkError!void { + try drawLineWithEndpointMode(allocator, draw_call, v0, v1, color_attachment_access, depth_attachment_access, stencil_attachment_access, false); +} + +pub fn drawLineIncludingEndpoint( + 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, +) VkError!void { + try drawLineWithEndpointMode(allocator, draw_call, v0, v1, color_attachment_access, depth_attachment_access, stencil_attachment_access, true); +} + +fn drawLineWithEndpointMode( + 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 io = draw_call.renderer.device.interface.io(); @@ -76,7 +102,7 @@ pub fn drawLine( if (runtimes_count == 0) return; - const step_count: usize = @intCast(@max(d_x, 0) + 1); + 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); @@ -125,6 +151,62 @@ fn bresenhamYAtStep(y0: i32, d_x: i32, d_err: i32, y_step: i32, step: usize) i32 return y0 + (y_step * y_offset); } +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 lineCoverageMask(data: RunData, pixel_x: i32, pixel_y: i32, sample_count: usize) vk.SampleMask { + if (sample_count <= 1) + return 1; + + const a = data.start_vertex.position; + const b = data.end_vertex.position; + 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 runWrapper(data: RunData) void { @call(.always_inline, run, .{data}) catch |err| { std.log.scoped(.@"Rasterization stage").err("line fill mode catched a '{s}'", .{@errorName(err)}); @@ -165,6 +247,7 @@ inline fn run(data: RunData) !void { data.batch_id, zm.f32x4(@as(f32, @floatFromInt(pixel_x)) + 0.5, @as(f32, @floatFromInt(pixel_y)) + 0.5, z, frag_w), null, + null, true, try common.interpolateLineOutputs(data.allocator, data.start_vertex, data.end_vertex, t), null, @@ -193,7 +276,12 @@ inline fn run(data: RunData) !void { @intCast(pixel_x), @intCast(pixel_y), fragment_result.depth orelse z, - null, + lineCoverageMask( + data, + pixel_x, + pixel_y, + data.draw_call.renderer.state.pipeline.?.interface.mode.graphics.multisample.rasterization_samples.toInt(), + ), fragment_result.sample_mask, ); } diff --git a/src/software/device/rasterizer/common.zig b/src/software/device/rasterizer/common.zig index 1a1e44a..722c16e 100644 --- a/src/software/device/rasterizer/common.zig +++ b/src/software/device/rasterizer/common.zig @@ -201,6 +201,7 @@ pub fn interpolateVertexOutputs( v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, v2: *const Renderer.Vertex, + provoking_vertex: *const Renderer.Vertex, b0: f32, b1: f32, b2: f32, @@ -218,7 +219,8 @@ pub fn interpolateVertexOutputs( const out2 = v2.outputs[location][component] orelse continue; if (out0.interpolation_type == .flat or out0.size == 0) { - inputs[location][component] = .{ .blob = out0.blob, .size = out0.size, .free_responsability = false }; + const flat_out = provoking_vertex.outputs[location][component] orelse out0; + inputs[location][component] = .{ .blob = flat_out.blob, .size = flat_out.size, .free_responsability = false }; continue; } @@ -236,14 +238,14 @@ pub fn interpolateVertexOutputs( 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)); + base.utils.writePacked(F32x4, input[byte_index..], interpolateF32x4(out0.interpolation_type, value0, value1, value2, v0, v1, v2, 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)); + base.utils.writePacked(f32, input[byte_index..], interpolateF32(out0.interpolation_type, value0, value1, value2, v0, v1, v2, b0, b1, b2)); } if (byte_index < len) @@ -262,7 +264,7 @@ pub fn interpolateLineOutputs( v1: *const Renderer.Vertex, t: f32, ) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation { - return interpolateVertexOutputs(allocator, v0, v1, v0, 1.0 - t, t, 0.0); + return interpolateVertexOutputs(allocator, v0, v1, v0, v0, 1.0 - t, t, 0.0); } pub fn interpolateVertexOutputDerivatives( @@ -270,6 +272,9 @@ pub fn interpolateVertexOutputDerivatives( v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, v2: *const Renderer.Vertex, + b0: f32, + b1: f32, + b2: f32, db0: f32, db1: f32, db2: f32, @@ -299,14 +304,14 @@ pub fn interpolateVertexOutputDerivatives( const value0 = std.mem.bytesToValue(F32x4, out0.blob[byte_index..]); const value1 = std.mem.bytesToValue(F32x4, out1.blob[byte_index..]); const value2 = std.mem.bytesToValue(F32x4, out2.blob[byte_index..]); - base.utils.writePacked(F32x4, input[byte_index..], interpolateF32x4(value0, value1, value2, db0, db1, db2)); + base.utils.writePacked(F32x4, input[byte_index..], interpolateDerivativeF32x4(out0.interpolation_type, value0, value1, value2, v0, v1, v2, b0, b1, b2, db0, db1, db2)); } while (byte_index + @sizeOf(f32) <= len) : (byte_index += @sizeOf(f32)) { const value0 = std.mem.bytesToValue(f32, out0.blob[byte_index..]); const value1 = std.mem.bytesToValue(f32, out1.blob[byte_index..]); const value2 = std.mem.bytesToValue(f32, out2.blob[byte_index..]); - base.utils.writePacked(f32, input[byte_index..], (value0 * db0) + (value1 * db1) + (value2 * db2)); + base.utils.writePacked(f32, input[byte_index..], interpolateDerivativeF32(out0.interpolation_type, value0, value1, value2, v0, v1, v2, b0, b1, b2, db0, db1, db2)); } } @@ -317,10 +322,68 @@ pub fn interpolateVertexOutputDerivatives( return inputs; } -inline fn interpolateF32x4(value0: F32x4, value1: F32x4, value2: F32x4, b0: f32, b1: f32, b2: f32) F32x4 { +fn perspectiveWeights(v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, v2: *const Renderer.Vertex, b0: f32, b1: f32, b2: f32) struct { w0: f32, w1: f32, w2: f32 } { + const iw0 = 1.0 / v0.position[3]; + const iw1 = 1.0 / v1.position[3]; + const iw2 = 1.0 / v2.position[3]; + const denominator = (b0 * iw0) + (b1 * iw1) + (b2 * iw2); + if (denominator == 0.0) + return .{ .w0 = b0, .w1 = b1, .w2 = b2 }; + return .{ + .w0 = (b0 * iw0) / denominator, + .w1 = (b1 * iw1) / denominator, + .w2 = (b2 * iw2) / denominator, + }; +} + +inline fn interpolateF32(interpolation_type: anytype, value0: f32, value1: f32, value2: f32, v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, v2: *const Renderer.Vertex, b0: f32, b1: f32, b2: f32) f32 { + if (interpolation_type == .smooth) { + const weights = perspectiveWeights(v0, v1, v2, b0, b1, b2); + return (value0 * weights.w0) + (value1 * weights.w1) + (value2 * weights.w2); + } + return (value0 * b0) + (value1 * b1) + (value2 * b2); +} + +inline fn interpolateF32x4(interpolation_type: anytype, value0: F32x4, value1: F32x4, value2: F32x4, v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, v2: *const Renderer.Vertex, b0: f32, b1: f32, b2: f32) F32x4 { + if (interpolation_type == .smooth) { + const weights = perspectiveWeights(v0, v1, v2, b0, b1, b2); + return (value0 * zm.f32x4s(weights.w0)) + (value1 * zm.f32x4s(weights.w1)) + (value2 * zm.f32x4s(weights.w2)); + } return (value0 * zm.f32x4s(b0)) + (value1 * zm.f32x4s(b1)) + (value2 * zm.f32x4s(b2)); } +inline fn interpolateDerivativeF32(interpolation_type: anytype, value0: f32, value1: f32, value2: f32, v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, v2: *const Renderer.Vertex, b0: f32, b1: f32, b2: f32, db0: f32, db1: f32, db2: f32) f32 { + if (interpolation_type != .smooth) + return (value0 * db0) + (value1 * db1) + (value2 * db2); + + const iw0 = 1.0 / v0.position[3]; + const iw1 = 1.0 / v1.position[3]; + const iw2 = 1.0 / v2.position[3]; + const n = (value0 * b0 * iw0) + (value1 * b1 * iw1) + (value2 * b2 * iw2); + const d = (b0 * iw0) + (b1 * iw1) + (b2 * iw2); + const dn = (value0 * db0 * iw0) + (value1 * db1 * iw1) + (value2 * db2 * iw2); + const dd = (db0 * iw0) + (db1 * iw1) + (db2 * iw2); + if (d == 0.0) + return 0.0; + return ((dn * d) - (n * dd)) / (d * d); +} + +inline fn interpolateDerivativeF32x4(interpolation_type: anytype, value0: F32x4, value1: F32x4, value2: F32x4, v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, v2: *const Renderer.Vertex, b0: f32, b1: f32, b2: f32, db0: f32, db1: f32, db2: f32) F32x4 { + if (interpolation_type != .smooth) + return (value0 * zm.f32x4s(db0)) + (value1 * zm.f32x4s(db1)) + (value2 * zm.f32x4s(db2)); + + const iw0 = 1.0 / v0.position[3]; + const iw1 = 1.0 / v1.position[3]; + const iw2 = 1.0 / v2.position[3]; + const n = (value0 * zm.f32x4s(b0 * iw0)) + (value1 * zm.f32x4s(b1 * iw1)) + (value2 * zm.f32x4s(b2 * iw2)); + const d = (b0 * iw0) + (b1 * iw1) + (b2 * iw2); + const dn = (value0 * zm.f32x4s(db0 * iw0)) + (value1 * zm.f32x4s(db1 * iw1)) + (value2 * zm.f32x4s(db2 * iw2)); + const dd = (db0 * iw0) + (db1 * iw1) + (db2 * iw2); + if (d == 0.0) + return zm.f32x4s(0.0); + return ((dn * zm.f32x4s(d)) - (n * zm.f32x4s(dd))) / zm.f32x4s(d * d); +} + inline fn fragmentOutputFloat4(output: [@sizeOf(F32x4)]u8, format: vk.Format) F32x4 { const color = std.mem.bytesToValue(F32x4, &output); _ = format; @@ -418,8 +481,13 @@ pub fn writeToTargets( 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 effective_fragment_sample_mask = alphaToCoverageMask( + pipeline_data.multisample, + outputs, + fragment_sample_mask, + ); - if (!sampleMaskEnablesAnySample(pipeline_data.multisample, coverage_sample_mask, fragment_sample_mask)) + if (!sampleMaskEnablesAnySample(pipeline_data.multisample, coverage_sample_mask, effective_fragment_sample_mask)) return; if (x >= draw_call.framebuffer.interface.width or y >= draw_call.framebuffer.interface.height) @@ -432,7 +500,7 @@ pub fn writeToTargets( const sample_count = pipeline_data.multisample.rasterization_samples.toInt(); for (0..sample_count) |sample_index| { - if (!sampleMaskEnablesSample(pipeline_data.multisample, coverage_sample_mask, fragment_sample_mask, sample_index)) + if (!sampleMaskEnablesSample(pipeline_data.multisample, coverage_sample_mask, effective_fragment_sample_mask, sample_index)) continue; var stencil_state: ?vk.StencilOpState = null; @@ -511,6 +579,34 @@ pub fn writeToTargets( } } +fn alphaToCoverageMask( + multisample: anytype, + outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8, + fragment_sample_mask: ?vk.SampleMask, +) ?vk.SampleMask { + if (multisample.alpha_to_coverage_enable == .false) + return fragment_sample_mask; + + const sample_count = multisample.rasterization_samples.toInt(); + if (sample_count <= 1) + return fragment_sample_mask; + + const color = std.mem.bytesToValue(F32x4, &outputs[0]); + const alpha = std.math.clamp(color[3], 0.0, 1.0); + const covered_samples: usize = @intFromFloat(@round(alpha * @as(f32, @floatFromInt(sample_count)))); + + var alpha_mask: vk.SampleMask = 0; + for (0..covered_samples) |sample_index| { + if (sample_index >= @bitSizeOf(vk.SampleMask)) + break; + + const bit_index: u5 = @intCast(sample_index); + alpha_mask |= @as(vk.SampleMask, 1) << bit_index; + } + + return if (fragment_sample_mask) |mask| mask & alpha_mask else alpha_mask; +} + 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 coverage_sample_mask == null and fragment_sample_mask == null) diff --git a/src/software/device/rasterizer/edge_function.zig b/src/software/device/rasterizer/edge_function.zig index 1898722..d2020f1 100644 --- a/src/software/device/rasterizer/edge_function.zig +++ b/src/software/device/rasterizer/edge_function.zig @@ -13,6 +13,11 @@ const VkError = base.VkError; const SpvRuntimeError = spv.Runtime.RuntimeError; const F32x4 = zm.F32x4; +const SamplePosition = struct { + x: f32, + y: f32, +}; + const RunData = struct { allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, @@ -25,12 +30,15 @@ const RunData = struct { v0: Renderer.Vertex, v1: Renderer.Vertex, v2: Renderer.Vertex, + provoking_vertex: 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, fragment_uses_derivatives: bool, + fragment_uses_sample_id: bool, + fragment_uses_centroid: bool, }; pub fn drawTriangle( @@ -39,6 +47,7 @@ pub fn drawTriangle( v0: *Renderer.Vertex, v1: *Renderer.Vertex, v2: *Renderer.Vertex, + provoking_vertex: *Renderer.Vertex, color_attachment_access: []const ?common.RenderTargetAccess, depth_attachment_access: ?*common.RenderTargetAccess, stencil_attachment_access: ?*common.RenderTargetAccess, @@ -61,6 +70,14 @@ pub fn drawTriangle( stage.module.module.reflection_infos.needs_derivatives else false; + const fragment_uses_sample_id = if (fragment_stage) |stage| + stage.module.module.builtins.get(.SampleId) != null + else + false; + const fragment_uses_centroid = if (fragment_stage) |stage| + fragmentStageUsesInputDecoration(stage, .Centroid) + else + false; const runtimes_count = if (fragment_stage) |stage| stage.runtimes.len else 1; if (runtimes_count == 0) @@ -102,6 +119,7 @@ pub fn drawTriangle( .v0 = v0.*, .v1 = v1.*, .v2 = v2.*, + .provoking_vertex = provoking_vertex.*, .area = area, .min_x = run_min_x, .max_x = run_max_x, @@ -113,6 +131,8 @@ pub fn drawTriangle( .front_face = front_face, .has_fragment_shader = fragment_stage != null, .fragment_uses_derivatives = fragment_uses_derivatives, + .fragment_uses_sample_id = fragment_uses_sample_id, + .fragment_uses_centroid = fragment_uses_centroid, }; draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data}); @@ -139,7 +159,7 @@ 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 } { +inline fn standardSamplePosition(sample_count: usize, sample_index: usize) SamplePosition { return switch (sample_count) { 1 => .{ .x = 0.5, .y = 0.5 }, 2 => switch (sample_index) { @@ -158,6 +178,32 @@ inline fn standardSamplePosition(sample_count: usize, sample_index: usize) struc }; } +fn fragmentStageUsesInputDecoration(stage: anytype, decoration: anytype) bool { + const rt = &stage.runtimes[0].rt; + for (rt.mod.input_locations) |location| { + for (location) |result_word| { + if (result_word == 0) + continue; + + if (rt.hasResultDecoration(result_word, decoration)) + return true; + } + } + return false; +} + +fn firstCoveredSamplePosition(sample_count: usize, coverage_sample_mask: vk.SampleMask) SamplePosition { + for (0..sample_count) |sample_index| { + if (sample_index >= @bitSizeOf(vk.SampleMask)) + break; + + const bit_index: u5 = @intCast(sample_index); + if ((coverage_sample_mask & (@as(vk.SampleMask, 1) << bit_index)) != 0) + return standardSamplePosition(sample_count, sample_index); + } + return .{ .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| { @@ -226,14 +272,84 @@ 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]); const frag_w = (b0 / data.v0.position[3]) + (b1 / data.v1.position[3]) + (b2 / data.v2.position[3]); + const interpolation_barycentrics = if (data.fragment_uses_centroid and sample_count > 1) blk: { + const sample_pos = firstCoveredSamplePosition(sample_count, coverage_sample_mask); + const centroid_p = zm.f32x4( + @as(f32, @floatFromInt(x)) + sample_pos.x, + @as(f32, @floatFromInt(y)) + sample_pos.y, + 0.0, + 1.0, + ); + const centroid_w0 = edgeFunction(data.v1.position, data.v2.position, centroid_p); + const centroid_w1 = edgeFunction(data.v2.position, data.v0.position, centroid_p); + const centroid_w2 = edgeFunction(data.v0.position, data.v1.position, centroid_p); + break :blk .{ + centroid_w0 / data.area, + centroid_w1 / data.area, + centroid_w2 / data.area, + }; + } else .{ b0, b1, b2 }; + const input_b0 = interpolation_barycentrics[0]; + const input_b1 = interpolation_barycentrics[1]; + const input_b2 = interpolation_barycentrics[2]; var fragment_result: fragment.InvocationResult = .{ .outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8), .depth = null, .sample_mask = null, }; + if (data.has_fragment_shader and data.fragment_uses_sample_id and sample_count > 1) { + for (0..sample_count) |sample_index| { + if (sample_index >= @bitSizeOf(vk.SampleMask)) + break; + + const bit_index: u5 = @intCast(sample_index); + const sample_coverage_mask = @as(vk.SampleMask, 1) << bit_index; + if ((coverage_sample_mask & sample_coverage_mask) == 0) + continue; + + const inputs = try common.interpolateVertexOutputs(data.allocator, &data.v0, &data.v1, &data.v2, &data.provoking_vertex, input_b0, input_b1, input_b2); + const sample_result = fragment.shaderInvocation( + data.allocator, + data.draw_call, + data.batch_id, + zm.f32x4(@as(f32, @floatFromInt(x)) + 0.5, @as(f32, @floatFromInt(y)) + 0.5, z, frag_w), + null, + @intCast(sample_index), + data.front_face, + inputs, + 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( + sample_result.outputs, + data.draw_call, + data.color_attachment_access, + data.depth_attachment_access, + data.stencil_attachment_access, + data.front_face, + @intCast(x), + @intCast(y), + sample_result.depth orelse z, + sample_coverage_mask, + sample_result.sample_mask, + ); + } + continue; + } if (data.has_fragment_shader) { - const inputs = try common.interpolateVertexOutputs(data.allocator, &data.v0, &data.v1, &data.v2, b0, b1, b2); + const inputs = try common.interpolateVertexOutputs(data.allocator, &data.v0, &data.v1, &data.v2, &data.provoking_vertex, input_b0, input_b1, input_b2); const derivative_inputs: ?fragment.DerivativeInputs = if (data.fragment_uses_derivatives) blk: { var derivatives: fragment.DerivativeInputs = undefined; @@ -246,6 +362,9 @@ inline fn run(data: RunData) !void { &data.v0, &data.v1, &data.v2, + b0, + b1, + b2, (dx_w0 / data.area) - b0, (dx_w1 / data.area) - b1, (dx_w2 / data.area) - b2, @@ -260,6 +379,9 @@ inline fn run(data: RunData) !void { &data.v0, &data.v1, &data.v2, + b0, + b1, + b2, (dy_w0 / data.area) - b0, (dy_w1 / data.area) - b1, (dy_w2 / data.area) - b2, @@ -273,6 +395,7 @@ inline fn run(data: RunData) !void { data.batch_id, zm.f32x4(@as(f32, @floatFromInt(x)) + 0.5, @as(f32, @floatFromInt(y)) + 0.5, z, frag_w), null, + null, data.front_face, inputs, derivative_inputs, diff --git a/src/software/device/vertex_dispatcher.zig b/src/software/device/vertex_dispatcher.zig index 24c415e..d59f39f 100644 --- a/src/software/device/vertex_dispatcher.zig +++ b/src/software/device/vertex_dispatcher.zig @@ -68,10 +68,7 @@ inline fn run(data: RunData) !void { const vertex_index: usize = vertex_index_u32; const instance_index = data.first_instance + data.instance_index; - setupBuiltins(rt, data.allocator, vertex_index_u32, instance_index) catch |err| switch (err) { - SpvRuntimeError.NotFound => {}, - else => return err, - }; + try setupBuiltins(rt, data.allocator, vertex_index_u32, instance_index); if (data.pipeline.interface.mode.graphics.input_assembly.attribute_description) |attributes| { for (attributes) |attribute| { @@ -118,10 +115,7 @@ inline fn run(data: RunData) !void { const memory_size = try rt.getResultMemorySize(result_word); - const result_is_integer = blk: { - const result_type = rt.getResultPrimitiveType(result_word) catch break :blk false; - break :blk result_type == .SInt or result_type == .UInt; - }; + const result_is_integer = resultIsInteger(rt, result_word); output.outputs[location][component] = .{ .interpolation_type = if (rt.hasResultDecoration(result_word, .Flat) or result_is_integer) .flat else .smooth, // TODO : handle noperspective @@ -149,7 +143,7 @@ fn readPosition(rt: *spv.Runtime, output: []u8) !void { if (rt.readBuiltIn(output, .Position)) { return; } else |err| switch (err) { - SpvRuntimeError.InvalidSpirV => {}, + SpvRuntimeError.InvalidSpirV, SpvRuntimeError.NotFound => {}, else => return err, } @@ -212,11 +206,40 @@ fn isConstantZero(rt: *spv.Runtime, result_word: spv.SpvWord) bool { } } +fn resultIsInteger(rt: *spv.Runtime, result_word: spv.SpvWord) bool { + const value = (rt.results[result_word].getConstValue() catch return false); + return valueIsInteger(value); +} + +fn valueIsInteger(value: anytype) bool { + return switch (value.*) { + .Int, + .Vector2i32, + .Vector3i32, + .Vector4i32, + .Vector2u32, + .Vector3u32, + .Vector4u32, + => true, + .Vector, + .Matrix, + => |values| values.len != 0 and valueIsInteger(&values[0]), + .Array => |array| array.values.len != 0 and valueIsInteger(&array.values[0]), + else => false, + }; +} + fn setupBuiltins(rt: *spv.Runtime, allocator: std.mem.Allocator, vertex_index_u32: u32, instance_index: usize) !void { const instance_index_u32: u32 = @intCast(instance_index); - try rt.writeBuiltIn(allocator, std.mem.asBytes(&vertex_index_u32), .VertexIndex); - try rt.writeBuiltIn(allocator, std.mem.asBytes(&instance_index_u32), .InstanceIndex); + rt.writeBuiltIn(allocator, std.mem.asBytes(&vertex_index_u32), .VertexIndex) catch |err| switch (err) { + SpvRuntimeError.NotFound => {}, + else => return err, + }; + rt.writeBuiltIn(allocator, std.mem.asBytes(&instance_index_u32), .InstanceIndex) catch |err| switch (err) { + SpvRuntimeError.NotFound => {}, + else => return err, + }; } fn writeVertexInput(rt: *spv.Runtime, allocator: std.mem.Allocator, raw_input: []const u8, format: vk.Format, location: u32) !void { diff --git a/src/software/lib.zig b/src/software/lib.zig index 730acaa..3ab8838 100644 --- a/src/software/lib.zig +++ b/src/software/lib.zig @@ -63,7 +63,7 @@ pub const MIN_UNIFORM_BUFFER_ALIGNMENT = 256; pub const MIN_STORAGE_BUFFER_ALIGNMENT = 256; pub const MAX_VERTEX_INPUT_BINDINGS = 16; -pub const MAX_VERTEX_INPUT_ATTRIBUTES = 32; +pub const MAX_VERTEX_INPUT_ATTRIBUTES = 16; pub const PUSH_CONSTANT_SIZE = 256; diff --git a/src/vulkan/Pipeline.zig b/src/vulkan/Pipeline.zig index 765c4ec..218fe47 100644 --- a/src/vulkan/Pipeline.zig +++ b/src/vulkan/Pipeline.zig @@ -53,6 +53,8 @@ mode: union(enum) { multisample: struct { rasterization_samples: vk.SampleCountFlags, sample_mask: ?[]vk.SampleMask, + alpha_to_coverage_enable: vk.Bool32, + alpha_to_one_enable: vk.Bool32, }, color_blend: struct { attachments: ?[]vk.PipelineColorBlendAttachmentState, @@ -198,6 +200,8 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe break :blk .{ .rasterization_samples = .{ .@"1_bit" = true }, .sample_mask = null, + .alpha_to_coverage_enable = .false, + .alpha_to_one_enable = .false, }; } @@ -209,6 +213,8 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe sample_mask = allocator.dupe(vk.SampleMask, mask[0..mask_word_count]) catch return VkError.OutOfHostMemory; break :blk_mask sample_mask; } else null, + .alpha_to_coverage_enable = state.alpha_to_coverage_enable, + .alpha_to_one_enable = state.alpha_to_one_enable, }; }, .color_blend = blk: { diff --git a/src/vulkan/lib_vulkan.zig b/src/vulkan/lib_vulkan.zig index a699f67..325646f 100644 --- a/src/vulkan/lib_vulkan.zig +++ b/src/vulkan/lib_vulkan.zig @@ -1591,6 +1591,10 @@ pub export fn apeGetPipelineCacheData(p_device: vk.Device, p_cache: vk.PipelineC const available = cache.availableDataSize(); const result = if (data) |ptr| blk: { + if (size.* < @sizeOf(PipelineCache.Header)) { + size.* = 0; + return .incomplete; + } const bytes = @as([*]u8, @ptrCast(ptr))[0..size.*]; break :blk cache.getData(bytes); } else cache.getData(null);