From 19c8d84e05120c59db05b8f2d60bfef561083ca1 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Sat, 30 May 2026 13:10:34 +0200 Subject: [PATCH] improving draw line --- src/soft/device/blitter.zig | 2 +- src/soft/device/clip.zig | 45 +++++++++++++ src/soft/device/rasterizer.zig | 71 ++++++++++++++++---- src/soft/device/rasterizer/bresenham.zig | 55 ++++++++------- src/soft/device/rasterizer/common.zig | 42 ++++++++++++ src/soft/device/rasterizer/edge_function.zig | 28 +------- 6 files changed, 177 insertions(+), 66 deletions(-) diff --git a/src/soft/device/blitter.zig b/src/soft/device/blitter.zig index 45b6cb4..4b28bab 100644 --- a/src/soft/device/blitter.zig +++ b/src/soft/device/blitter.zig @@ -1016,7 +1016,7 @@ pub fn writeFloat4(c: F32x4, map: []u8, dst_format: vk.Format) void { (@as(u32, a) << 30); }, - .r32g32b32a32_uint => std.mem.bytesAsValue(U32x4, map).* = @intFromFloat(@round(@as(@Vector(4, f64), color) * @as(@Vector(4, f64), @splat(std.math.maxInt(u32))))), + .r32g32b32a32_uint => std.mem.bytesAsValue(U32x4, map).* = @intFromFloat(@round(@as(@Vector(4, f64), color))), .r32g32b32a32_sfloat => std.mem.bytesAsValue(F32x4, map).* = color, diff --git a/src/soft/device/clip.zig b/src/soft/device/clip.zig index 7457611..217fd80 100644 --- a/src/soft/device/clip.zig +++ b/src/soft/device/clip.zig @@ -22,6 +22,11 @@ const ClipPlane = enum { const MAX_CLIPPED_POLYGON_VERTICES = 16; +pub const ClippedLine = struct { + v0: Vertex, + v1: Vertex, +}; + const ClippedPolygon = struct { vertices: [MAX_CLIPPED_POLYGON_VERTICES]Vertex = undefined, len: usize = 0, @@ -59,6 +64,46 @@ pub fn clipTriangle(allocator: std.mem.Allocator, v0: *const Vertex, v1: *const return polygon; } +pub fn clipLine(allocator: std.mem.Allocator, v0: *const Vertex, v1: *const Vertex) VkError!?ClippedLine { + var line: ClippedLine = .{ + .v0 = v0.*, + .v1 = v1.*, + }; + + const planes = [_]ClipPlane{ + .Left, + .Right, + .Bottom, + .Top, + .Near, + .Far, + }; + + for (planes) |plane| { + const v0_distance = clipDistance(line.v0.position, plane); + const v1_distance = clipDistance(line.v1.position, plane); + const v0_inside = v0_distance >= 0.0; + const v1_inside = v1_distance >= 0.0; + + if (!v0_inside and !v1_inside) + return null; + + if (v0_inside and v1_inside) + continue; + + const t = v0_distance / (v0_distance - v1_distance); + const clipped_vertex = try interpolateVertexForClipping(allocator, &line.v0, &line.v1, t); + + if (v0_inside) { + line.v1 = clipped_vertex; + } else { + line.v0 = clipped_vertex; + } + } + + return line; +} + pub fn viewportTransformVertex(viewport: vk.Viewport, vertex: *Vertex) void { const x, const y, const z, const w = vertex.position; diff --git a/src/soft/device/rasterizer.zig b/src/soft/device/rasterizer.zig index d93c9d6..45ee636 100644 --- a/src/soft/device/rasterizer.zig +++ b/src/soft/device/rasterizer.zig @@ -133,12 +133,67 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato } } }, + .line_list => for (0..@divTrunc(draw_call.vertices.len, 2)) |line_index| { + const first_vertex = line_index * 2; + const v0 = &draw_call.vertices[first_vertex + 0]; + const v1 = &draw_call.vertices[first_vertex + 1]; + + try clipTransformAndRasterizeLine( + allocator, + draw_call, + v0, + v1, + &color_attachment_access, + if (depth_attachment_access) |*access| access else null, + ); + }, + .line_strip => if (draw_call.vertices.len >= 2) { + for (0..(draw_call.vertices.len - 1)) |vertex_index| { + const v0 = &draw_call.vertices[vertex_index + 0]; + const v1 = &draw_call.vertices[vertex_index + 1]; + + try clipTransformAndRasterizeLine( + allocator, + draw_call, + v0, + v1, + &color_attachment_access, + if (depth_attachment_access) |*access| access else null, + ); + } + }, else => base.unsupported("primitive topology {any}", .{topology}), } draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost; } +fn clipTransformAndRasterizeLine( + allocator: std.mem.Allocator, + draw_call: *DrawCall, + v0: *Vertex, + v1: *Vertex, + color_attachment_access: *const common.RenderTargetAccess, + depth_attachment_access: ?*common.RenderTargetAccess, +) VkError!void { + const clipped_line = (try clip.clipLine(allocator, v0, v1)) orelse return; + + var tv0 = clipped_line.v0; + var tv1 = clipped_line.v1; + + 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, + ); +} + fn clipTransformAndRasterizeTriangle( renderer: *Renderer, allocator: std.mem.Allocator, @@ -193,19 +248,11 @@ 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, - ), + .fill => try edge_function.drawTriangle(allocator, draw_call, v0, v1, v2, color_attachment_access, depth_attachment_access), .line => { - try bresenham.drawLine(allocator, draw_call, v0, v1); - try bresenham.drawLine(allocator, draw_call, v1, v2); - try bresenham.drawLine(allocator, draw_call, v2, v0); + 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); }, .point => {}, // TODO else => base.unsupported("polygon mode {any}", .{pipeline_data.rasterization.polygon_mode}), diff --git a/src/soft/device/rasterizer/bresenham.zig b/src/soft/device/rasterizer/bresenham.zig index 82bdee8..31ce1f2 100644 --- a/src/soft/device/rasterizer/bresenham.zig +++ b/src/soft/device/rasterizer/bresenham.zig @@ -3,6 +3,7 @@ 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"); @@ -27,9 +28,18 @@ const RunData = struct { end_vertex: *Renderer.Vertex, start_step: usize, end_step: usize, + color_attachment_access: *const common.RenderTargetAccess, + depth_attachment_access: ?*common.RenderTargetAccess, }; -pub fn drawLine(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, v0: *Renderer.Vertex, v1: *Renderer.Vertex) VkError!void { +pub fn drawLine( + 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, +) VkError!void { const io = draw_call.renderer.device.interface.io(); var x0: i32 = @intFromFloat(v0.position[0]); @@ -60,7 +70,6 @@ pub fn drawLine(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, v0: const pipeline = draw_call.renderer.state.pipeline orelse return; - var wg: std.Io.Group = .init; const runtimes_count = (pipeline.stages.getPtr(.fragment) orelse return).runtimes.len; if (runtimes_count == 0) return; @@ -93,11 +102,17 @@ pub fn drawLine(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, v0: .end_vertex = end_vertex, .start_step = start_step, .end_step = end_step, + .color_attachment_access = color_attachment_access, + .depth_attachment_access = depth_attachment_access, }; - wg.async(io, runWrapper, .{run_data}); + draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data}); } - wg.await(io) catch return VkError.DeviceLost; + + // 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; } fn bresenhamYAtStep(y0: i32, d_x: i32, d_err: i32, y_step: i32, step: usize) i32 { @@ -121,10 +136,6 @@ fn runWrapper(data: RunData) void { } inline fn run(data: RunData) !void { - const color_attachment = if (data.draw_call.render_pass.interface.subpasses[data.draw_call.renderer.subpass_index].color_attachments) |attachments| attachments[0].attachment else return VkError.InvalidAttachmentDrv; - const render_target_view: *base.ImageView = data.draw_call.color_attachments[color_attachment]; - const render_target: *SoftImage = @alignCast(@fieldParentPtr("interface", render_target_view.image)); - var step = data.start_step; while (step <= data.end_step) : (step += 1) { const x = data.x0 + @as(i32, @intCast(step)); @@ -140,7 +151,15 @@ 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]); - const pixel = fragment.shaderInvocation( + // 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; + } + + const outputs = fragment.shaderInvocation( data.allocator, data.draw_call, data.batch_id, @@ -157,22 +176,6 @@ inline fn run(data: RunData) !void { return; }; - _ = pixel; - _ = render_target; - - //try render_target.writeFloat4( - // .{ - // .x = pixel_x, - // .y = pixel_y, - // .z = 0, // FIXME - // }, - // .{ - // .aspect_mask = render_target_view.subresource_range.aspect_mask, - // .mip_level = render_target_view.subresource_range.base_mip_level, - // .array_layer = render_target_view.subresource_range.base_array_layer, - // }, - // render_target_view.format, - // pixel, - //); + try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, @intCast(pixel_x), @intCast(pixel_y), z); } } diff --git a/src/soft/device/rasterizer/common.zig b/src/soft/device/rasterizer/common.zig index 72a8195..f573340 100644 --- a/src/soft/device/rasterizer/common.zig +++ b/src/soft/device/rasterizer/common.zig @@ -4,10 +4,12 @@ const base = @import("base"); const zm = base.zm; const spv = @import("spv"); +const blitter = @import("../blitter.zig"); const Renderer = @import("../Renderer.zig"); const VkError = base.VkError; const F32x4 = zm.F32x4; +const U32x4 = @Vector(4, u32); pub const RenderTargetAccess = struct { mutex: std.Io.Mutex, @@ -98,3 +100,43 @@ pub fn interpolateLineOutputs( inline fn interpolateF32x4(value0: F32x4, value1: F32x4, value2: F32x4, b0: f32, b1: f32, b2: f32) F32x4 { return (value0 * zm.f32x4s(b0)) + (value1 * zm.f32x4s(b1)) + (value2 * zm.f32x4s(b2)); } + +pub fn writeToTargets( + outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8, + draw_call: *Renderer.DrawCall, + color_attachment_access: *const RenderTargetAccess, + depth_attachment_access: ?*RenderTargetAccess, + x: usize, + y: usize, + z: f32, +) VkError!void { + const io = draw_call.renderer.device.interface.io(); + + const color_offset = @as(usize, @intCast(x)) * color_attachment_access.texel_size + @as(usize, @intCast(y)) * color_attachment_access.row_pitch; + + // After work depth test to avoid overwritten depth pixels during fragment invocations + if (depth_attachment_access) |depth| { + const depth_offset = @as(usize, @intCast(x)) * depth.texel_size + @as(usize, @intCast(y)) * depth.row_pitch; + + 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); + + // Doubled line to stay inside the critical section + if (base.format.isUnnormalizedInteger(color_attachment_access.format)) { + blitter.writeInt4(std.mem.bytesToValue(U32x4, &outputs[0]), color_attachment_access.base[color_offset..], color_attachment_access.format); + } else { + blitter.writeFloat4(std.mem.bytesToValue(F32x4, &outputs[0]), color_attachment_access.base[color_offset..], color_attachment_access.format); + } + } else { + if (base.format.isUnnormalizedInteger(color_attachment_access.format)) { + blitter.writeInt4(std.mem.bytesToValue(U32x4, &outputs[0]), color_attachment_access.base[color_offset..], color_attachment_access.format); + } else { + blitter.writeFloat4(std.mem.bytesToValue(F32x4, &outputs[0]), color_attachment_access.base[color_offset..], color_attachment_access.format); + } + } +} diff --git a/src/soft/device/rasterizer/edge_function.zig b/src/soft/device/rasterizer/edge_function.zig index 1ff9243..549a1e2 100644 --- a/src/soft/device/rasterizer/edge_function.zig +++ b/src/soft/device/rasterizer/edge_function.zig @@ -125,8 +125,6 @@ fn runWrapper(data: RunData) void { } inline fn run(data: RunData) !void { - const io = data.draw_call.renderer.device.interface.io(); - var y = data.min_y; while (y <= data.max_y) : (y += 1) { var x = data.min_x; @@ -178,31 +176,7 @@ inline fn run(data: RunData) !void { return; }; - const color_offset = @as(usize, @intCast(x)) * data.color_attachment_access.texel_size + @as(usize, @intCast(y)) * data.color_attachment_access.row_pitch; - - // After work depth test to avoid overwritten depth pixels during fragment invocations - if (data.depth_attachment_access) |depth| { - const depth_offset = @as(usize, @intCast(x)) * depth.texel_size + @as(usize, @intCast(y)) * depth.row_pitch; - - 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]) - continue; - blitter.writeFloat4(zm.f32x4s(z), depth.base[depth_offset..], depth.format); - - // Doubled line to stay inside the critical section - if (base.format.isUnnormalizedInteger(data.color_attachment_access.format)) - blitter.writeInt4(std.mem.bytesToValue(@Vector(4, u32), &outputs[0]), data.color_attachment_access.base[color_offset..], data.color_attachment_access.format) - else - blitter.writeFloat4(std.mem.bytesToValue(@Vector(4, f32), &outputs[0]), data.color_attachment_access.base[color_offset..], data.color_attachment_access.format); - } else { - if (base.format.isUnnormalizedInteger(data.color_attachment_access.format)) - blitter.writeInt4(std.mem.bytesToValue(@Vector(4, u32), &outputs[0]), data.color_attachment_access.base[color_offset..], data.color_attachment_access.format) - else - blitter.writeFloat4(std.mem.bytesToValue(@Vector(4, f32), &outputs[0]), data.color_attachment_access.base[color_offset..], data.color_attachment_access.format); - } + try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, @intCast(x), @intCast(y), z); } } }