387 lines
15 KiB
Zig
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);
|
|
}
|
|
}
|
|
}
|