Files
VulkanDriver/src/soft/device/rasterizer.zig
T
2026-06-08 14:55:41 +02:00

464 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 clip = @import("clip.zig");
const bresenham = @import("rasterizer/bresenham.zig");
const edge_function = @import("rasterizer/edge_function.zig");
const common = @import("rasterizer/common.zig");
const fragment = @import("fragment.zig");
const Renderer = @import("Renderer.zig");
const Vertex = Renderer.Vertex;
const DrawCall = Renderer.DrawCall;
const SoftImage = @import("../SoftImage.zig");
const VkError = base.VkError;
pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocator, draw_call: *DrawCall) VkError!void {
const io = draw_call.renderer.device.interface.io();
const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics;
const topology = pipeline_data.input_assembly.topology;
const color_attachments = draw_call.render_pass.interface.subpasses[renderer.subpass_index].color_attachments orelse &.{};
const color_attachment_access = allocator.alloc(?common.RenderTargetAccess, color_attachments.len) catch return VkError.OutOfDeviceMemory;
@memset(color_attachment_access, null);
for (color_attachments, color_attachment_access) |attachment_ref, *access| {
if (attachment_ref.attachment == vk.ATTACHMENT_UNUSED)
continue;
const render_target_view: *base.ImageView = draw_call.color_attachments[attachment_ref.attachment];
const render_target: *SoftImage = @alignCast(@fieldParentPtr("interface", render_target_view.image));
const color_range = render_target_view.subresource_range;
const color_format = render_target_view.format;
const color_extent = render_target.getMipLevelExtent(color_range.base_mip_level);
const color_attachment_subresource_offset = try render_target.getSubresourceOffset(
color_range.aspect_mask,
color_range.base_mip_level,
color_range.base_array_layer,
);
const color_attachment_subresource_size = render_target.getLayerSize(color_range.aspect_mask);
access.* = .{
.mutex = undefined,
.base = try render_target.mapAsSliceWithAddedOffset(u8, color_attachment_subresource_offset, color_attachment_subresource_size),
.row_pitch = render_target.getRowPitchMemSizeForMipLevelWithFormat(color_range.aspect_mask, color_range.base_mip_level, color_format),
.texel_size = base.format.texelSize(color_format),
.width = color_extent.width,
.height = color_extent.height,
.format = color_format,
};
}
const depth_attachment_view: ?*base.ImageView = if (draw_call.depth_attachment) |view| view else null;
const depth_attachment: ?*SoftImage = if (depth_attachment_view) |view| @alignCast(@fieldParentPtr("interface", view.image)) else null;
var depth_attachment_access: ?common.RenderTargetAccess = blk: {
if (depth_attachment == null)
break :blk null;
const depth_range = depth_attachment_view.?.subresource_range;
if (!depth_range.aspect_mask.depth_bit)
break :blk null;
const depth_format = depth_attachment_view.?.format;
const depth_aspect: vk.ImageAspectFlags = .{ .depth_bit = true };
const depth_aspect_format = base.format.fromAspect(depth_format, depth_aspect);
const depth_extent = depth_attachment.?.getMipLevelExtent(depth_range.base_mip_level);
const attachment_subresource_offset = try depth_attachment.?.getSubresourceOffset(
depth_aspect,
depth_range.base_mip_level,
depth_range.base_array_layer,
);
const attachment_subresource_size = depth_attachment.?.getLayerSize(depth_aspect);
break :blk .{
.mutex = .init,
.base = try depth_attachment.?.mapAsSliceWithAddedOffset(u8, attachment_subresource_offset, attachment_subresource_size),
.row_pitch = depth_attachment.?.getRowPitchMemSizeForMipLevelWithFormat(depth_aspect, depth_range.base_mip_level, depth_format),
.texel_size = base.format.texelSize(depth_aspect_format),
.width = depth_extent.width,
.height = depth_extent.height,
.format = depth_aspect_format,
};
};
var stencil_attachment_access: ?common.RenderTargetAccess = blk: {
if (depth_attachment == null)
break :blk null;
const stencil_range = depth_attachment_view.?.subresource_range;
if (!stencil_range.aspect_mask.stencil_bit)
break :blk null;
const stencil_format = depth_attachment_view.?.format;
const stencil_aspect: vk.ImageAspectFlags = .{ .stencil_bit = true };
const stencil_aspect_format = base.format.fromAspect(stencil_format, stencil_aspect);
const stencil_extent = depth_attachment.?.getMipLevelExtent(stencil_range.base_mip_level);
const attachment_subresource_offset = try depth_attachment.?.getSubresourceOffset(
stencil_aspect,
stencil_range.base_mip_level,
stencil_range.base_array_layer,
);
const attachment_subresource_size = depth_attachment.?.getLayerSize(stencil_aspect);
break :blk .{
.mutex = .init,
.base = try depth_attachment.?.mapAsSliceWithAddedOffset(u8, attachment_subresource_offset, attachment_subresource_size),
.row_pitch = depth_attachment.?.getRowPitchMemSizeForMipLevelWithFormat(stencil_aspect, stencil_range.base_mip_level, stencil_format),
.texel_size = base.format.texelSize(stencil_aspect_format),
.width = stencil_extent.width,
.height = stencil_extent.height,
.format = stencil_aspect_format,
};
};
switch (topology) {
.point_list => for (draw_call.vertices) |*vertex| {
if (vertex.primitive_restart)
continue;
try clipTransformAndRasterizePoint(
allocator,
draw_call,
vertex,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
if (stencil_attachment_access) |*access| access else null,
);
},
.triangle_list => for (0..@divTrunc(draw_call.vertices.len, 3)) |triangle_index| {
const first_vertex = triangle_index * 3;
const v0 = &draw_call.vertices[first_vertex + 0];
const v1 = &draw_call.vertices[first_vertex + 1];
const v2 = &draw_call.vertices[first_vertex + 2];
try clipTransformAndRasterizeTriangle(
renderer,
allocator,
draw_call,
v0,
v1,
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
if (stencil_attachment_access) |*access| access else null,
);
},
.triangle_fan => {
var segment_start = firstNonRestart(draw_call, 0);
while (segment_start < draw_call.vertices.len) {
const segment_end = nextRestart(draw_call, segment_start);
if (segment_end - segment_start >= 3) {
const v0 = &draw_call.vertices[segment_start];
for ((segment_start + 1)..(segment_end - 1)) |vertex_index| {
const v1 = &draw_call.vertices[vertex_index];
const v2 = &draw_call.vertices[vertex_index + 1];
try clipTransformAndRasterizeTriangle(
renderer,
allocator,
draw_call,
v0,
v1,
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
if (stencil_attachment_access) |*access| access else null,
);
}
}
segment_start = firstNonRestart(draw_call, segment_end + 1);
}
},
.triangle_strip => {
var segment_start = firstNonRestart(draw_call, 0);
while (segment_start < draw_call.vertices.len) {
const segment_end = nextRestart(draw_call, segment_start);
if (segment_end - segment_start >= 3) {
for (segment_start..(segment_end - 2)) |vertex_index| {
const local_index = vertex_index - segment_start;
const v0 = &draw_call.vertices[vertex_index + 0];
const v1 = &draw_call.vertices[vertex_index + 1];
const v2 = &draw_call.vertices[vertex_index + 2];
if ((local_index & 1) == 0) {
try clipTransformAndRasterizeTriangle(
renderer,
allocator,
draw_call,
v0,
v1,
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
if (stencil_attachment_access) |*access| access else null,
);
} else {
try clipTransformAndRasterizeTriangle(
renderer,
allocator,
draw_call,
v1,
v0,
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
if (stencil_attachment_access) |*access| access else null,
);
}
}
}
segment_start = firstNonRestart(draw_call, segment_end + 1);
}
},
.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,
if (stencil_attachment_access) |*access| access else null,
);
},
.line_strip => {
var segment_start = firstNonRestart(draw_call, 0);
while (segment_start < draw_call.vertices.len) {
const segment_end = nextRestart(draw_call, segment_start);
if (segment_end - segment_start >= 2) {
for (segment_start..(segment_end - 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,
if (stencil_attachment_access) |*access| access else null,
);
}
}
segment_start = firstNonRestart(draw_call, segment_end + 1);
}
},
else => base.unsupported("primitive topology {any}", .{topology}),
}
draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost;
}
fn firstNonRestart(draw_call: *const DrawCall, start: usize) usize {
var index = start;
while (index < draw_call.vertices.len and draw_call.vertices[index].primitive_restart) : (index += 1) {}
return index;
}
fn nextRestart(draw_call: *const DrawCall, start: usize) usize {
var index = start;
while (index < draw_call.vertices.len and !draw_call.vertices[index].primitive_restart) : (index += 1) {}
return index;
}
fn clipTransformAndRasterizePoint(
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 x, const y, const z, const w = vertex.position;
if (w == 0.0 or x < -w or x > w or y < -w or y > w or z < 0.0 or z > w)
return;
var transformed = vertex.*;
clip.viewportTransformVertex(draw_call.viewport, &transformed);
const point_size = 1.0;
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 pipeline = draw_call.renderer.state.pipeline orelse return;
const has_fragment_shader = pipeline.stages.getPtr(.fragment) != null;
var py = min_y;
while (py <= max_y) : (py += 1) {
var px = min_x;
while (px <= max_x) : (px += 1) {
if (!common.scissorContainsPixel(draw_call.scissor, px, py))
continue;
var outputs = std.mem.zeroes([spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8);
if (has_fragment_shader) {
outputs = fragment.shaderInvocation(
allocator,
draw_call,
0,
zm.f32x4(@floatFromInt(px), @floatFromInt(py), transformed.position[2], 1.0),
try common.interpolateVertexOutputs(allocator, &transformed, &transformed, &transformed, 1.0, 0.0, 0.0),
null,
) catch |err| {
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(outputs, draw_call, color_attachment_access, depth_attachment_access, stencil_attachment_access, true, @intCast(px), @intCast(py), transformed.position[2]);
}
}
}
fn clipTransformAndRasterizeLine(
allocator: std.mem.Allocator,
draw_call: *DrawCall,
v0: *Vertex,
v1: *Vertex,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
stencil_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,
stencil_attachment_access,
);
}
fn clipTransformAndRasterizeTriangle(
renderer: *Renderer,
allocator: std.mem.Allocator,
draw_call: *DrawCall,
v0: *Vertex,
v1: *Vertex,
v2: *Vertex,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
stencil_attachment_access: ?*common.RenderTargetAccess,
) VkError!void {
const clipped_polygon = try clip.clipTriangle(allocator, v0, v1, v2);
if (clipped_polygon.len < 3)
return;
for (1..(clipped_polygon.len - 1)) |vertex_index| {
var tv0 = clipped_polygon.vertices[0];
var tv1 = clipped_polygon.vertices[vertex_index];
var tv2 = clipped_polygon.vertices[vertex_index + 1];
clip.viewportTransformVertex(draw_call.viewport, &tv0);
clip.viewportTransformVertex(draw_call.viewport, &tv1);
clip.viewportTransformVertex(draw_call.viewport, &tv2);
try rasterizeTriangle(
renderer,
allocator,
draw_call,
&tv0,
&tv1,
&tv2,
color_attachment_access,
depth_attachment_access,
stencil_attachment_access,
);
}
}
fn rasterizeTriangle(
renderer: *Renderer,
allocator: std.mem.Allocator,
draw_call: *DrawCall,
v0: *Vertex,
v1: *Vertex,
v2: *Vertex,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
stencil_attachment_access: ?*common.RenderTargetAccess,
) VkError!void {
const maybe_front_face = try triangleFrontFace(renderer, v0, v1, v2);
const front_face = maybe_front_face orelse return;
if (try triangleIsCulled(renderer, front_face))
return;
draw_call.stats.polygons_drawn += 1;
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),
.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
else => base.unsupported("polygon mode {any}", .{pipeline_data.rasterization.polygon_mode}),
}
}
fn triangleIsCulled(renderer: *Renderer, front_face: bool) VkError!bool {
const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics;
const cull_mode = pipeline_data.rasterization.cull_mode;
if (!cull_mode.front_bit and !cull_mode.back_bit)
return false;
if (cull_mode.front_bit and cull_mode.back_bit)
return true;
return (cull_mode.front_bit and front_face) or (cull_mode.back_bit and !front_face);
}
fn triangleFrontFace(renderer: *Renderer, v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) VkError!?bool {
const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics;
const rasterization = pipeline_data.rasterization;
const area = triangleArea(v0, v1, v2);
if (area == 0.0)
return null;
return switch (rasterization.front_face) {
.counter_clockwise => area < 0.0,
.clockwise => area > 0.0,
else => false,
};
}
inline fn triangleArea(v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) f32 {
const x0, const y0, _, _ = v0.position;
const x1, const y1, _, _ = v1.position;
const x2, const y2, _, _ = v2.position;
return ((x1 - x0) * (y2 - y0)) - ((y1 - y0) * (x2 - x0));
}