refactoring renderer
Test / build_and_test (push) Successful in 35s
Build / build (push) Successful in 1m20s

This commit is contained in:
2026-05-13 22:05:25 +02:00
parent faae8e86e0
commit b5b05776d8
15 changed files with 915 additions and 507 deletions
+169
View File
@@ -0,0 +1,169 @@
const std = @import("std");
const base = @import("base");
const spv = @import("spv");
const zm = base.zm;
const common = @import("common.zig");
const fragment = @import("../fragment.zig");
const Renderer = @import("../Renderer.zig");
const SoftImage = @import("../../SoftImage.zig");
const VkError = base.VkError;
const SpvRuntimeError = spv.Runtime.RuntimeError;
const F32x4 = zm.F32x4;
const RunData = struct {
allocator: std.mem.Allocator,
draw_call: *Renderer.DrawCall,
batch_id: usize,
x0: i32,
y0: i32,
d_x: i32,
d_err: i32,
y_step: i32,
steep: bool,
start_vertex: *Renderer.Vertex,
end_vertex: *Renderer.Vertex,
start_step: usize,
end_step: usize,
};
pub fn drawLine(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, v0: *Renderer.Vertex, v1: *Renderer.Vertex) VkError!void {
const io = draw_call.renderer.device.interface.io();
var x0: i32 = @intFromFloat(v0.position[0]);
var y0: i32 = @intFromFloat(v0.position[1]);
var x1: i32 = @intFromFloat(v1.position[0]);
var y1: i32 = @intFromFloat(v1.position[1]);
const steep = blk: {
if (@abs(y1 - y0) > @abs(x1 - x0)) {
std.mem.swap(i32, &x0, &y0);
std.mem.swap(i32, &x1, &y1);
break :blk true;
}
break :blk false;
};
var start_vertex = v0;
var end_vertex = v1;
if (x0 > x1) {
std.mem.swap(i32, &x0, &x1);
std.mem.swap(i32, &y0, &y1);
std.mem.swap(*Renderer.Vertex, &start_vertex, &end_vertex);
}
const d_err: i32 = @intCast(@abs(y1 - y0));
const d_x = x1 - x0;
const y_step: i32 = if (y0 > y1) -1 else 1;
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;
const step_count: usize = @as(usize, @intCast(d_x)) + 1;
const runs_count = @min(runtimes_count, step_count);
const steps_per_run = @divTrunc(step_count + runs_count - 1, runs_count);
var batch_id: usize = 0;
for (0..runs_count) |run_index| {
defer batch_id = @mod(batch_id + 1, runtimes_count);
const start_step = run_index * steps_per_run;
if (start_step >= step_count)
continue;
const end_step = @min(start_step + steps_per_run - 1, step_count - 1);
const run_data: RunData = .{
.allocator = allocator,
.draw_call = draw_call,
.batch_id = batch_id,
.x0 = x0,
.y0 = y0,
.d_x = d_x,
.d_err = d_err,
.y_step = y_step,
.steep = steep,
.start_vertex = start_vertex,
.end_vertex = end_vertex,
.start_step = start_step,
.end_step = end_step,
};
wg.async(io, runWrapper, .{run_data});
}
wg.await(io) catch return VkError.DeviceLost;
}
inline fn bresenhamYAtStep(y0: i32, d_x: i32, d_err: i32, y_step: i32, step: usize) i32 {
if (d_x == 0)
return y0;
const numerator = (@as(i64, @intCast(step)) * @as(i64, d_err)) + @as(i64, @divTrunc(d_x - 1, 2));
const y_offset: i32 = @intCast(@divTrunc(numerator, @as(i64, d_x)));
return y0 + (y_step * y_offset);
}
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)});
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
};
}
inline fn run(data: RunData) !void {
const render_target_view: *base.ImageView = (data.draw_call.renderer.framebuffer orelse return).interface.attachments[0];
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));
const y = bresenhamYAtStep(data.y0, data.d_x, data.d_err, data.y_step, step);
const pixel_x = if (data.steep) y else x;
const pixel_y = if (data.steep) x else y;
if (!common.scissorContainsPixel(data.draw_call.scissor, pixel_x, pixel_y)) {
continue;
}
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(
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 (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
return;
};
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,
);
}
}
+87
View File
@@ -0,0 +1,87 @@
const std = @import("std");
const vk = @import("vulkan");
const base = @import("base");
const zm = base.zm;
const spv = @import("spv");
const Renderer = @import("../Renderer.zig");
const VkError = base.VkError;
const F32x4 = zm.F32x4;
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);
const max_x: i64 = min_x + @as(i64, @intCast(scissor.extent.width));
const max_y: i64 = min_y + @as(i64, @intCast(scissor.extent.height));
const pixel_x: i64 = @as(i64, x);
const pixel_y: i64 = @as(i64, y);
return pixel_x >= min_x and
pixel_x < max_x and
pixel_y >= min_y and
pixel_y < max_y;
}
fn writePacked(comptime T: type, bytes: []u8, value: T) void {
const raw: [@sizeOf(T)]u8 = @bitCast(value);
@memcpy(bytes[0..@sizeOf(T)], raw[0..]);
}
fn interpolateF32x4(value0: F32x4, value1: F32x4, value2: F32x4, b0: f32, b1: f32, b2: f32) F32x4 {
return (value0 * @as(F32x4, @splat(b0))) + (value1 * @as(F32x4, @splat(b1))) + (value2 * @as(F32x4, @splat(b2)));
}
pub fn interpolateVertexOutputs(
allocator: std.mem.Allocator,
v0: *const Renderer.Vertex,
v1: *const Renderer.Vertex,
v2: *const Renderer.Vertex,
b0: f32,
b1: f32,
b2: f32,
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][]u8 {
var inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][]u8 = undefined;
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;
if (out0.interpolation_type == .flat or out0.blob.len == 0) {
inputs[location] = out0.blob;
continue;
}
const len = @min(out0.blob.len, out1.blob.len, out2.blob.len);
const input = allocator.alloc(u8, len) catch return VkError.OutOfDeviceMemory;
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..]);
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..]);
writePacked(f32, input[byte_index..], (value0 * b0) + (value1 * b1) + (value2 * b2));
}
if (byte_index < len)
@memcpy(input[byte_index..], out0.blob[byte_index..len]);
inputs[location] = input;
}
return inputs;
}
pub fn interpolateLineOutputs(allocator: std.mem.Allocator, v0: *const Renderer.Vertex, v1: *const Renderer.Vertex, t: f32) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][]u8 {
return interpolateVertexOutputs(allocator, v0, v1, v0, 1.0 - t, t, 0.0);
}
@@ -0,0 +1,170 @@
const std = @import("std");
const vk = @import("vulkan");
const base = @import("base");
const spv = @import("spv");
const zm = base.zm;
const common = @import("common.zig");
const fragment = @import("../fragment.zig");
const Renderer = @import("../Renderer.zig");
const SoftImage = @import("../../SoftImage.zig");
const VkError = base.VkError;
const SpvRuntimeError = spv.Runtime.RuntimeError;
const F32x4 = zm.F32x4;
const RunData = struct {
allocator: std.mem.Allocator,
draw_call: *Renderer.DrawCall,
batch_id: usize,
min_x: i32,
max_x: i32,
min_y: i32,
max_y: i32,
area: f32,
v0: *Renderer.Vertex,
v1: *Renderer.Vertex,
v2: *Renderer.Vertex,
};
pub fn drawTriangle(allocator: std.mem.Allocator, draw_call: *Renderer.DrawCall, v0: *Renderer.Vertex, v1: *Renderer.Vertex, v2: *Renderer.Vertex) VkError!void {
const io = draw_call.renderer.device.interface.io();
const min_x: i32 = @intFromFloat(@floor(@min(v0.position[0], v1.position[0], v2.position[0])));
const max_x: i32 = @intFromFloat(@ceil(@max(v0.position[0], v1.position[0], v2.position[0])));
const min_y: i32 = @intFromFloat(@floor(@min(v0.position[1], v1.position[1], v2.position[1])));
const max_y: i32 = @intFromFloat(@ceil(@max(v0.position[1], v1.position[1], v2.position[1])));
const area = edgeFunction(v0.position, v1.position, v2.position);
if (area == 0.0)
return;
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;
const grid_size: usize = @intFromFloat(@floor(@sqrt(@as(f32, @floatFromInt(runtimes_count)))));
const width: usize = @intCast(max_x - min_x + 1);
const height: usize = @intCast(max_y - min_y + 1);
const cols_per_run = @divTrunc(width + grid_size - 1, grid_size);
const rows_per_run = @divTrunc(height + grid_size - 1, grid_size);
var batch_id: usize = 0;
for (0..grid_size) |gy| {
for (0..grid_size) |gx| {
defer batch_id = @mod(batch_id + 1, runtimes_count);
const run_min_x = min_x + @as(i32, @intCast(gx * cols_per_run));
const run_min_y = min_y + @as(i32, @intCast(gy * rows_per_run));
if (run_min_x > max_x or run_min_y > max_y)
continue;
const run_max_x = @min(
run_min_x + @as(i32, @intCast(cols_per_run)) - 1,
max_x,
);
const run_max_y = @min(
run_min_y + @as(i32, @intCast(rows_per_run)) - 1,
max_y,
);
const run_data: RunData = .{
.allocator = allocator,
.draw_call = draw_call,
.batch_id = batch_id,
.v0 = v0,
.v1 = v1,
.v2 = v2,
.area = area,
.min_x = run_min_x,
.max_x = run_max_x,
.min_y = run_min_y,
.max_y = run_max_y,
};
wg.async(io, runWrapper, .{run_data});
}
}
wg.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]));
}
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)});
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
};
}
inline fn run(data: RunData) !void {
const render_target_view: *base.ImageView = (data.draw_call.renderer.framebuffer orelse return).interface.attachments[0];
const render_target: *SoftImage = @alignCast(@fieldParentPtr("interface", render_target_view.image));
var y = data.min_y;
while (y <= data.max_y) : (y += 1) {
var x = data.min_x;
while (x <= data.max_x) : (x += 1) {
if (!common.scissorContainsPixel(data.draw_call.scissor, x, y)) {
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 = 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;
if (!inside)
continue;
const b0 = w0 / data.area;
const b1 = w1 / data.area;
const b2 = w2 / data.area;
const z = (b0 * data.v0.position[2]) + (b1 * data.v1.position[2]) + (b2 * data.v2.position[2]);
const pixel = 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 (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
return;
};
try render_target.writeFloat4(
.{
.x = x,
.y = 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,
);
}
}
}