Files
VulkanDriver/src/soft/device/rasterizer/common.zig
T
kbz_8 e75b8d17d3
Build / build (push) Failing after 20s
Test / build_and_test (push) Failing after 18s
yes
2026-06-05 01:22:07 +02:00

387 lines
15 KiB
Zig

const std = @import("std");
const vk = @import("vulkan");
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,
base: []u8,
row_pitch: usize,
texel_size: usize,
width: u32,
height: u32,
format: vk.Format,
};
pub const VertexInterpolation = struct {
blob: []const u8,
size: usize,
free_responsability: bool,
};
pub const VertexInterpolationLocation = [4]VertexInterpolation;
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;
}
pub fn targetContainsPixel(target: RenderTargetAccess, x: i32, y: i32) bool {
if (x < 0 or y < 0)
return false;
const pixel_x: u32 = @intCast(x);
const pixel_y: u32 = @intCast(y);
return pixel_x < target.width and pixel_y < target.height;
}
pub fn targetOffset(target: RenderTargetAccess, x: usize, y: usize) ?usize {
if (x >= target.width or y >= target.height)
return null;
const offset = x * target.texel_size + y * target.row_pitch;
if (offset > target.base.len or target.texel_size > target.base.len - offset)
return null;
return offset;
}
pub fn compare(comptime T: type, op: vk.CompareOp, reference: T, value: T) bool {
return switch (op) {
.never => false,
.less => reference < value,
.equal => reference == value,
.less_or_equal => reference <= value,
.greater => reference > value,
.not_equal => reference != value,
.greater_or_equal => reference >= value,
.always => true,
else => false,
};
}
fn applyStencilOp(op: vk.StencilOp, current: u32, reference: u32) u32 {
return switch (op) {
.keep => current,
.zero => 0,
.replace => reference,
.increment_and_clamp => @min(current +| 1, std.math.maxInt(u8)),
.decrement_and_clamp => if (current == 0) 0 else current - 1,
.invert => ~current,
.increment_and_wrap => current +% 1,
.decrement_and_wrap => current -% 1,
else => current,
} & std.math.maxInt(u8);
}
fn updateStencilValue(stencil: *RenderTargetAccess, offset: usize, state: vk.StencilOpState, op: vk.StencilOp) void {
const current = blitter.readInt4(stencil.base[offset..], stencil.format)[0] & std.math.maxInt(u8);
const op_value = applyStencilOp(op, current, state.reference & std.math.maxInt(u8));
const write_mask = state.write_mask & std.math.maxInt(u8);
const new_value = (current & ~write_mask) | (op_value & write_mask);
blitter.writeInt4(@splat(new_value), stencil.base[offset..], stencil.format);
}
pub fn stencilTestAndUpdate(
stencil: *RenderTargetAccess,
x: usize,
y: usize,
state: vk.StencilOpState,
depth_passed: ?bool,
) bool {
const offset = targetOffset(stencil.*, x, y) orelse return false;
const current = blitter.readInt4(stencil.base[offset..], stencil.format)[0] & std.math.maxInt(u8);
const reference = state.reference & std.math.maxInt(u8);
const compare_mask = state.compare_mask & std.math.maxInt(u8);
const stencil_passed = compare(u32, state.compare_op, reference & compare_mask, current & compare_mask);
if (!stencil_passed) {
updateStencilValue(stencil, offset, state, state.fail_op);
return false;
}
if (depth_passed != null and !depth_passed.?) {
updateStencilValue(stencil, offset, state, state.depth_fail_op);
return false;
}
updateStencilValue(stencil, offset, state, state.pass_op);
return true;
}
fn stencilTest(stencil: *RenderTargetAccess, offset: usize, state: vk.StencilOpState) bool {
const current = blitter.readInt4(stencil.base[offset..], stencil.format)[0] & std.math.maxInt(u8);
const reference = state.reference & std.math.maxInt(u8);
const compare_mask = state.compare_mask & std.math.maxInt(u8);
return compare(u32, state.compare_op, reference & compare_mask, current & compare_mask);
}
pub fn depthTestAndUpdate(depth: *RenderTargetAccess, x: usize, y: usize, z: f32, state: vk.PipelineDepthStencilStateCreateInfo) bool {
if (state.depth_test_enable == .false)
return true;
const offset = targetOffset(depth.*, x, y) orelse return false;
const depth_value = blitter.readFloat4(depth.base[offset..], depth.format);
const passed = compare(f32, state.depth_compare_op, z, depth_value[0]);
if (passed and state.depth_write_enable == .true)
blitter.writeFloat4(zm.f32x4s(z), depth.base[offset..], depth.format);
return passed;
}
fn resolveStencilState(draw_call: *Renderer.DrawCall, state: vk.StencilOpState, front: bool) vk.StencilOpState {
var resolved = state;
const dynamic = draw_call.renderer.dynamic_state;
if ((if (front) dynamic.stencil_front_compare_mask else dynamic.stencil_back_compare_mask)) |mask|
resolved.compare_mask = mask;
if ((if (front) dynamic.stencil_front_write_mask else dynamic.stencil_back_write_mask)) |mask|
resolved.write_mask = mask;
if ((if (front) dynamic.stencil_front_reference else dynamic.stencil_back_reference)) |reference|
resolved.reference = reference;
return resolved;
}
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]VertexInterpolationLocation {
var inputs = [_]VertexInterpolationLocation{[_]VertexInterpolation{.{
.blob = &.{},
.size = 0,
.free_responsability = false,
}} ** 4} ** spv.SPIRV_MAX_OUTPUT_LOCATIONS;
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
for (0..4) |component| {
const out0 = v0.outputs[location][component] orelse continue;
const out1 = v1.outputs[location][component] orelse continue;
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 };
continue;
}
const len = @min(out0.size, out1.size, out2.size);
const input = allocator.alloc(u8, len + @sizeOf(F32x4)) catch return VkError.OutOfDeviceMemory;
@memset(input, 0);
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..]);
base.utils.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..]);
base.utils.writePacked(f32, input[byte_index..], (value0 * b0) + (value1 * b1) + (value2 * b2));
}
if (byte_index < len)
@memcpy(input[byte_index..len], out0.blob[byte_index..len]);
inputs[location][component] = .{ .blob = input, .size = len, .free_responsability = true };
}
}
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]VertexInterpolationLocation {
return interpolateVertexOutputs(allocator, v0, v1, v0, 1.0 - t, t, 0.0);
}
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));
}
inline fn fragmentOutputFloat4(output: [@sizeOf(F32x4)]u8, format: vk.Format) F32x4 {
const color = std.mem.bytesToValue(F32x4, &output);
_ = format;
return color;
}
inline fn blendFactor(factor: vk.BlendFactor, src: F32x4, dst: F32x4, constant: F32x4) F32x4 {
return switch (factor) {
.zero => zm.f32x4s(0.0),
.one => zm.f32x4s(1.0),
.src_color => src,
.one_minus_src_color => zm.f32x4s(1.0) - src,
.dst_color => dst,
.one_minus_dst_color => zm.f32x4s(1.0) - dst,
.src_alpha => zm.f32x4s(src[3]),
.one_minus_src_alpha => zm.f32x4s(1.0 - src[3]),
.dst_alpha => zm.f32x4s(dst[3]),
.one_minus_dst_alpha => zm.f32x4s(1.0 - dst[3]),
.constant_color => constant,
.one_minus_constant_color => zm.f32x4s(1.0) - constant,
.constant_alpha => zm.f32x4s(constant[3]),
.one_minus_constant_alpha => zm.f32x4s(1.0 - constant[3]),
.src_alpha_saturate => .{ @min(src[3], 1.0 - dst[3]), @min(src[3], 1.0 - dst[3]), @min(src[3], 1.0 - dst[3]), 1.0 },
else => zm.f32x4s(0.0),
};
}
inline fn blendOp(op: vk.BlendOp, src: F32x4, dst: F32x4) F32x4 {
return switch (op) {
.add => src + dst,
.subtract => src - dst,
.reverse_subtract => dst - src,
.min => @min(src, dst),
.max => @max(src, dst),
else => src,
};
}
inline fn blendColor(src: F32x4, dst: F32x4, state: vk.PipelineColorBlendAttachmentState, constants: [4]f32) F32x4 {
if (state.blend_enable == .false)
return src;
const constant = F32x4{ constants[0], constants[1], constants[2], constants[3] };
const color_src = if (state.color_blend_op == .min or state.color_blend_op == .max)
src
else
src * blendFactor(state.src_color_blend_factor, src, dst, constant);
const color_dst = if (state.color_blend_op == .min or state.color_blend_op == .max)
dst
else
dst * blendFactor(state.dst_color_blend_factor, src, dst, constant);
const alpha_src = if (state.alpha_blend_op == .min or state.alpha_blend_op == .max)
src
else
src * blendFactor(state.src_alpha_blend_factor, src, dst, constant);
const alpha_dst = if (state.alpha_blend_op == .min or state.alpha_blend_op == .max)
dst
else
dst * blendFactor(state.dst_alpha_blend_factor, src, dst, constant);
var blended = blendOp(state.color_blend_op, color_src, color_dst);
blended[3] = blendOp(state.alpha_blend_op, alpha_src, alpha_dst)[3];
return blended;
}
inline fn applyColorWriteMask(blended: F32x4, dst: F32x4, mask: vk.ColorComponentFlags) F32x4 {
return .{
if (mask.r_bit) blended[0] else dst[0],
if (mask.g_bit) blended[1] else dst[1],
if (mask.b_bit) blended[2] else dst[2],
if (mask.a_bit) blended[3] else dst[3],
};
}
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,
stencil_attachment_access: ?*RenderTargetAccess,
front_face: bool,
x: usize,
y: usize,
z: f32,
) VkError!void {
const io = draw_call.renderer.device.interface.io();
const depth_stencil_state = draw_call.renderer.state.pipeline.?.interface.mode.graphics.depth_stencil;
var stencil_state: ?vk.StencilOpState = null;
var stencil_offset: ?usize = null;
if (stencil_attachment_access) |stencil| {
if (depth_stencil_state) |state| {
if (state.stencil_test_enable == .true) {
stencil_state = if (front_face)
resolveStencilState(draw_call, state.front, true)
else
resolveStencilState(draw_call, state.back, false);
stencil_offset = targetOffset(stencil.*, x, y) orelse return;
if (!stencilTest(stencil, stencil_offset.?, stencil_state.?)) {
updateStencilValue(stencil, stencil_offset.?, stencil_state.?, stencil_state.?.fail_op);
return;
}
}
}
}
// After work depth test to avoid overwritten depth pixels during fragment invocations.
var depth_passed: ?bool = null;
if (depth_attachment_access) |depth| {
const depth_offset = targetOffset(depth.*, x, y) orelse return;
depth.mutex.lock(io) catch return VkError.DeviceLost;
defer depth.mutex.unlock(io);
if (depth_stencil_state) |state| {
depth_passed = depthTestAndUpdate(depth, x, y, z, state);
if (!depth_passed.? and stencil_state == null)
return;
} else {
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);
depth_passed = true;
}
}
if (stencil_attachment_access) |stencil| {
if (stencil_state) |state| {
if (depth_passed != null and !depth_passed.?) {
updateStencilValue(stencil, stencil_offset.?, state, state.depth_fail_op);
return;
}
updateStencilValue(stencil, stencil_offset.?, state, state.pass_op);
}
}
for (color_attachment_access, 0..) |maybe_color, location| {
const color = maybe_color orelse continue;
const color_offset = targetOffset(color, x, y) orelse continue;
if (base.format.isUnnormalizedInteger(color.format)) {
blitter.writeInt4(std.mem.bytesToValue(U32x4, &outputs[location]), color.base[color_offset..], color.format);
} else {
const pipeline_data = draw_call.renderer.state.pipeline.?.interface.mode.graphics;
const src = fragmentOutputFloat4(outputs[location], color.format);
const encoded_dst = blitter.readFloat4(color.base[color_offset..], color.format);
const dst = if (base.format.isSrgb(color.format)) zm.srgbToRgb(encoded_dst) else encoded_dst;
const final_color = if (pipeline_data.color_blend.attachments) |attachments| blk: {
if (location >= attachments.len)
break :blk src;
const constants = draw_call.renderer.dynamic_state.blend_constants orelse pipeline_data.color_blend.constants;
const blended = blendColor(src, dst, attachments[location], constants);
break :blk applyColorWriteMask(blended, dst, attachments[location].color_write_mask);
} else src;
const encoded_color = if (base.format.isSrgb(color.format)) zm.rgbToSrgb(final_color) else final_color;
blitter.writeFloat4(encoded_color, color.base[color_offset..], color.format);
}
}
}