510 lines
20 KiB
Zig
510 lines
20 KiB
Zig
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 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,
|
|
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,
|
|
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,
|
|
early_fragment_tests: bool,
|
|
fragment_uses_derivatives: bool,
|
|
fragment_uses_sample_id: bool,
|
|
fragment_uses_centroid: bool,
|
|
depth_bias_slope: f32,
|
|
};
|
|
|
|
pub fn drawTriangle(
|
|
allocator: std.mem.Allocator,
|
|
draw_call: *Renderer.DrawCall,
|
|
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,
|
|
) 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 inv_area = 1.0 / area;
|
|
const dz_dx =
|
|
(v0.position[2] * ((v1.position[1] - v2.position[1]) * inv_area)) +
|
|
(v1.position[2] * ((v2.position[1] - v0.position[1]) * inv_area)) +
|
|
(v2.position[2] * ((v0.position[1] - v1.position[1]) * inv_area));
|
|
const dz_dy =
|
|
(v0.position[2] * ((v2.position[0] - v1.position[0]) * inv_area)) +
|
|
(v1.position[2] * ((v0.position[0] - v2.position[0]) * inv_area)) +
|
|
(v2.position[2] * ((v1.position[0] - v0.position[0]) * inv_area));
|
|
const depth_bias_slope = @max(@abs(dz_dx), @abs(dz_dy));
|
|
|
|
const pipeline = draw_call.renderer.state.pipeline orelse return;
|
|
const fragment_stage = pipeline.stages.getPtr(.fragment);
|
|
const fragment_uses_derivatives = if (fragment_stage) |stage|
|
|
stage.module.module.reflection_infos.needs_derivatives
|
|
else
|
|
false;
|
|
const early_fragment_tests = if (fragment_stage) |stage|
|
|
stage.module.module.reflection_infos.early_fragment_tests
|
|
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)
|
|
return;
|
|
const grid_size: usize = @intFromFloat(@ceil(@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.*,
|
|
.provoking_vertex = provoking_vertex.*,
|
|
.area = area,
|
|
.min_x = run_min_x,
|
|
.max_x = run_max_x,
|
|
.min_y = run_min_y,
|
|
.max_y = run_max_y,
|
|
.color_attachment_access = color_attachment_access,
|
|
.depth_attachment_access = depth_attachment_access,
|
|
.stencil_attachment_access = stencil_attachment_access,
|
|
.front_face = front_face,
|
|
.has_fragment_shader = fragment_stage != null,
|
|
.early_fragment_tests = early_fragment_tests,
|
|
.fragment_uses_derivatives = fragment_uses_derivatives,
|
|
.fragment_uses_sample_id = fragment_uses_sample_id,
|
|
.fragment_uses_centroid = fragment_uses_centroid,
|
|
.depth_bias_slope = depth_bias_slope,
|
|
};
|
|
|
|
draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data});
|
|
}
|
|
}
|
|
|
|
draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost;
|
|
}
|
|
|
|
inline fn edgeFunction(a: F32x4, b: F32x4, p: F32x4) f32 {
|
|
return ((p[0] - a[0]) * (b[1] - a[1])) - ((p[1] - a[1]) * (b[0] - a[0]));
|
|
}
|
|
|
|
inline fn isInclusiveEdge(a: F32x4, b: F32x4) bool {
|
|
const dx = b[0] - a[0];
|
|
const dy = b[1] - a[1];
|
|
return dy > 0.0 or (dy == 0.0 and dx < 0.0);
|
|
}
|
|
|
|
inline fn edgeContainsPixel(a: F32x4, b: F32x4, edge_value: f32, area: f32) bool {
|
|
return if (area > 0.0)
|
|
edge_value > 0.0 or (edge_value == 0.0 and isInclusiveEdge(a, b))
|
|
else
|
|
edge_value < 0.0 or (edge_value == 0.0 and isInclusiveEdge(b, a));
|
|
}
|
|
|
|
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) {
|
|
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 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| {
|
|
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 applyEarlyDepth(data: RunData, coverage_sample_mask: vk.SampleMask, x: i32, y: i32, z: f32, sample_count: usize) VkError!struct {
|
|
mask: vk.SampleMask,
|
|
applied: bool,
|
|
} {
|
|
if (!data.early_fragment_tests)
|
|
return .{ .mask = coverage_sample_mask, .applied = false };
|
|
|
|
const depth = data.depth_attachment_access orelse return .{ .mask = coverage_sample_mask, .applied = false };
|
|
const pipeline_data = data.draw_call.renderer.state.pipeline.?.interface.mode.graphics;
|
|
const depth_stencil_state = if (pipeline_data.depth_stencil) |state| common.resolveDepthStencilState(data.draw_call, state) else null;
|
|
const io = data.draw_call.renderer.device.interface.io();
|
|
|
|
var passed_mask: vk.SampleMask = 0;
|
|
for (0..sample_count) |sample_index| {
|
|
if (sample_index >= @bitSizeOf(vk.SampleMask))
|
|
break;
|
|
|
|
const bit = @as(vk.SampleMask, 1) << @as(u5, @intCast(sample_index));
|
|
if ((coverage_sample_mask & bit) == 0)
|
|
continue;
|
|
|
|
if (try common.depthTestSampleAndUpdate(io, depth, @intCast(x), @intCast(y), sample_index, z, depth_stencil_state))
|
|
passed_mask |= bit;
|
|
}
|
|
|
|
return .{ .mask = passed_mask, .applied = true };
|
|
}
|
|
|
|
fn biasedDepth(data: RunData, z: f32) f32 {
|
|
const pipeline_data = data.draw_call.renderer.state.pipeline.?.interface.mode.graphics;
|
|
if (pipeline_data.rasterization.depth_bias_enable == .false)
|
|
return z;
|
|
|
|
const depth = data.depth_attachment_access orelse return z;
|
|
const bias_state: Renderer.DepthBias = if (pipeline_data.dynamic_state.depth_bias)
|
|
data.draw_call.renderer.dynamic_state.depth_bias orelse Renderer.DepthBias{
|
|
.constant_factor = 0.0,
|
|
.clamp = 0.0,
|
|
.slope_factor = 0.0,
|
|
}
|
|
else
|
|
Renderer.DepthBias{
|
|
.constant_factor = pipeline_data.rasterization.depth_bias_constant_factor,
|
|
.clamp = pipeline_data.rasterization.depth_bias_clamp,
|
|
.slope_factor = pipeline_data.rasterization.depth_bias_slope_factor,
|
|
};
|
|
|
|
const bias =
|
|
bias_state.constant_factor * common.depthBiasConstantUnit(depth.format, z) +
|
|
bias_state.slope_factor * data.depth_bias_slope;
|
|
return z + common.clampDepthBias(bias, bias_state.clamp);
|
|
}
|
|
|
|
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 (comptime base.config.logs == .verbose) {
|
|
if (@errorReturnTrace()) |trace| {
|
|
std.debug.dumpErrorReturnTrace(trace);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
inline fn run(data: RunData) !void {
|
|
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 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 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 depth_z = biasedDepth(data, z);
|
|
const frag_w = (b0 / data.v0.position[3]) + (b1 / data.v1.position[3]) + (b2 / data.v2.position[3]);
|
|
const early_depth = try applyEarlyDepth(data, coverage_sample_mask, x, y, depth_z, sample_count);
|
|
if (early_depth.mask == 0)
|
|
continue;
|
|
|
|
const interpolation_barycentrics = if (sample_count > 1) blk: {
|
|
const sample_pos = firstCoveredSamplePosition(sample_count, early_depth.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 ((early_depth.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 depth_z,
|
|
sample_coverage_mask,
|
|
sample_result.sample_mask,
|
|
early_depth.applied,
|
|
);
|
|
}
|
|
continue;
|
|
}
|
|
if (data.has_fragment_shader) {
|
|
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;
|
|
|
|
const p_dx = zm.f32x4(@as(f32, @floatFromInt(x)) + 1.5, @as(f32, @floatFromInt(y)) + 0.5, 0.0, 1.0);
|
|
const dx_w0 = edgeFunction(data.v1.position, data.v2.position, p_dx);
|
|
const dx_w1 = edgeFunction(data.v2.position, data.v0.position, p_dx);
|
|
const dx_w2 = edgeFunction(data.v0.position, data.v1.position, p_dx);
|
|
derivatives.dx = try common.interpolateVertexOutputDerivatives(
|
|
data.allocator,
|
|
&data.v0,
|
|
&data.v1,
|
|
&data.v2,
|
|
b0,
|
|
b1,
|
|
b2,
|
|
(dx_w0 / data.area) - b0,
|
|
(dx_w1 / data.area) - b1,
|
|
(dx_w2 / data.area) - b2,
|
|
);
|
|
|
|
const p_dy = zm.f32x4(@as(f32, @floatFromInt(x)) + 0.5, @as(f32, @floatFromInt(y)) + 1.5, 0.0, 1.0);
|
|
const dy_w0 = edgeFunction(data.v1.position, data.v2.position, p_dy);
|
|
const dy_w1 = edgeFunction(data.v2.position, data.v0.position, p_dy);
|
|
const dy_w2 = edgeFunction(data.v0.position, data.v1.position, p_dy);
|
|
derivatives.dy = try common.interpolateVertexOutputDerivatives(
|
|
data.allocator,
|
|
&data.v0,
|
|
&data.v1,
|
|
&data.v2,
|
|
b0,
|
|
b1,
|
|
b2,
|
|
(dy_w0 / data.area) - b0,
|
|
(dy_w1 / data.area) - b1,
|
|
(dy_w2 / data.area) - b2,
|
|
);
|
|
break :blk derivatives;
|
|
} else null;
|
|
|
|
fragment_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,
|
|
null,
|
|
data.front_face,
|
|
inputs,
|
|
derivative_inputs,
|
|
) 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(
|
|
fragment_result.outputs,
|
|
data.draw_call,
|
|
data.color_attachment_access,
|
|
data.depth_attachment_access,
|
|
data.stencil_attachment_access,
|
|
data.front_face,
|
|
@intCast(x),
|
|
@intCast(y),
|
|
fragment_result.depth orelse depth_z,
|
|
early_depth.mask,
|
|
fragment_result.sample_mask,
|
|
early_depth.applied,
|
|
);
|
|
}
|
|
}
|
|
}
|