yes
Build / build (push) Failing after 20s
Test / build_and_test (push) Failing after 18s

This commit is contained in:
2026-06-05 01:22:07 +02:00
parent decdc79ae7
commit e75b8d17d3
16 changed files with 787 additions and 239 deletions
+4 -4
View File
@@ -81,15 +81,15 @@ vkCmdPushConstants | ✅ Implemented
vkCmdResetEvent | ✅ Implemented
vkCmdResetQueryPool | ⚙️ WIP
vkCmdResolveImage | ✅ Implemented
vkCmdSetBlendConstants | ⚙️ WIP
vkCmdSetBlendConstants | ✅ Implemented
vkCmdSetDepthBias | ⚙️ WIP
vkCmdSetDepthBounds | ⚙️ WIP
vkCmdSetEvent | ✅ Implemented
vkCmdSetLineWidth | ⚙️ WIP
vkCmdSetScissor | ✅ Implemented
vkCmdSetStencilCompareMask | ⚙️ WIP
vkCmdSetStencilReference | ⚙️ WIP
vkCmdSetStencilWriteMask | ⚙️ WIP
vkCmdSetStencilCompareMask | ✅ Implemented
vkCmdSetStencilReference | ✅ Implemented
vkCmdSetStencilWriteMask | ✅ Implemented
vkCmdSetViewport | ✅ Implemented
vkCmdUpdateBuffer | ⚙️ WIP
vkCmdWaitEvents | ✅ Implemented
+7 -7
View File
@@ -25,16 +25,16 @@
.url = "git+https://git.kbz8.me/kbz_8/Vulkan-CTS-bin.git#a5f787d80f14f136e3cb3e1185c35e298846c1d7",
.hash = "N-V-__8AAMpOQxkHCKTw9i-NwmmQ3ks1ndFDXcVLlic4KjK3",
},
.SPIRV_Interpreter = .{
.url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#8677e8a6833d39e9169460fd75de2d7c70fab4b8",
.hash = "SPIRV_Interpreter-0.0.1-ajmpn_HtBQBR82M2KlR_yuUvdHUVdXrYVGRh5YNqVFqQ",
.lazy = true,
},
//.SPIRV_Interpreter = .{
// // For development
// .path = "../SPIRV-Interpreter",
// .url = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#8677e8a6833d39e9169460fd75de2d7c70fab4b8",
// .hash = "SPIRV_Interpreter-0.0.1-ajmpn_HtBQBR82M2KlR_yuUvdHUVdXrYVGRh5YNqVFqQ",
// .lazy = true,
//},
.SPIRV_Interpreter = .{
// For development
.path = "git+https://git.kbz8.me/kbz_8/SPIRV-Interpreter#ee5a400010b3a5852606b2f8593bd5c745bf0a26",
.lazy = true,
},
},
.paths = .{
+83
View File
@@ -75,7 +75,11 @@ pub fn create(device: *base.Device, allocator: std.mem.Allocator, info: *const v
.resetEvent = resetEvent,
.resolveImage = resolveImage,
.setEvent = setEvent,
.setBlendConstants = setBlendConstants,
.setScissor = setScissor,
.setStencilCompareMask = setStencilCompareMask,
.setStencilReference = setStencilReference,
.setStencilWriteMask = setStencilWriteMask,
.setViewport = setViewport,
.waitEvent = waitEvent,
};
@@ -1059,6 +1063,85 @@ pub fn setViewport(interface: *Interface, first: u32, viewports: []const vk.View
self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory;
}
pub fn setBlendConstants(interface: *Interface, constants: [4]f32) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const allocator = self.command_allocator.allocator();
const CommandImpl = struct {
const Impl = @This();
constants: [4]f32,
pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void {
const impl: *Impl = @ptrCast(@alignCast(context));
device.renderer.dynamic_state.blend_constants = impl.constants;
}
};
const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory;
errdefer allocator.destroy(cmd);
cmd.* = .{ .constants = constants };
self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory;
}
pub fn setStencilCompareMask(interface: *Interface, face_mask: vk.StencilFaceFlags, compare_mask: u32) VkError!void {
try setStencilDynamicState(interface, face_mask, compare_mask, .compare_mask);
}
pub fn setStencilReference(interface: *Interface, face_mask: vk.StencilFaceFlags, reference: u32) VkError!void {
try setStencilDynamicState(interface, face_mask, reference, .reference);
}
pub fn setStencilWriteMask(interface: *Interface, face_mask: vk.StencilFaceFlags, write_mask: u32) VkError!void {
try setStencilDynamicState(interface, face_mask, write_mask, .write_mask);
}
fn setStencilDynamicState(interface: *Interface, face_mask: vk.StencilFaceFlags, value: u32, comptime kind: enum { compare_mask, reference, write_mask }) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const allocator = self.command_allocator.allocator();
const CommandImpl = struct {
const Impl = @This();
face_mask: vk.StencilFaceFlags,
value: u32,
pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void {
const impl: *Impl = @ptrCast(@alignCast(context));
if (!impl.face_mask.front_bit and !impl.face_mask.back_bit)
return;
switch (kind) {
.compare_mask => {
if (impl.face_mask.front_bit)
device.renderer.dynamic_state.stencil_front_compare_mask = impl.value;
if (impl.face_mask.back_bit)
device.renderer.dynamic_state.stencil_back_compare_mask = impl.value;
},
.reference => {
if (impl.face_mask.front_bit)
device.renderer.dynamic_state.stencil_front_reference = impl.value;
if (impl.face_mask.back_bit)
device.renderer.dynamic_state.stencil_back_reference = impl.value;
},
.write_mask => {
if (impl.face_mask.front_bit)
device.renderer.dynamic_state.stencil_front_write_mask = impl.value;
if (impl.face_mask.back_bit)
device.renderer.dynamic_state.stencil_back_write_mask = impl.value;
},
}
}
};
const cmd = allocator.create(CommandImpl) catch return VkError.OutOfHostMemory;
errdefer allocator.destroy(cmd);
cmd.* = .{
.face_mask = face_mask,
.value = value,
};
self.commands.append(allocator, .{ .ptr = cmd, .vtable = &.{ .execute = CommandImpl.execute } }) catch return VkError.OutOfHostMemory;
}
pub fn waitEvent(interface: *Interface, event: *base.Event, src_stage: vk.PipelineStageFlags, dst_stage: vk.PipelineStageFlags, memory_barriers: []const vk.MemoryBarrier, buffer_barriers: []const vk.BufferMemoryBarrier, image_barriers: []const vk.ImageMemoryBarrier) VkError!void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const allocator = self.command_allocator.allocator();
+3
View File
@@ -129,6 +129,8 @@ inline fn run(data: RunData) !void {
if (!uses_control_barrier)
try ExecutionDevice.writeDescriptorSets(data.self.state, rt);
try rt.populatePushConstants(data.self.state.push_constant_blob[0..]);
var group_index: usize = data.batch_id;
while (group_index < data.group_count) : (group_index += data.self.batch_size) {
var modulo: usize = group_index;
@@ -205,6 +207,7 @@ fn runBarrierWorkgroup(
for (runtimes, 0..) |*rt, i| {
rt.resetInvocation(allocator);
try ExecutionDevice.writeDescriptorSets(data.self.state, rt);
try rt.populatePushConstants(data.self.state.push_constant_blob[0..]);
try setupWorkgroupBuiltins(data.self, rt, group_count, group_id);
try setupSubgroupBuiltins(data.self, rt, group_id, i);
statuses[i] = try rt.beginEntryPoint(allocator, entry);
+25 -4
View File
@@ -43,11 +43,18 @@ pub const DynamicState = struct {
viewports: ?[]const vk.Viewport,
scissor: ?[]const vk.Rect2D,
line_width: ?f32,
blend_constants: ?[4]f32,
stencil_front_compare_mask: ?u32,
stencil_back_compare_mask: ?u32,
stencil_front_write_mask: ?u32,
stencil_back_write_mask: ?u32,
stencil_front_reference: ?u32,
stencil_back_reference: ?u32,
};
pub const Vertex = struct {
position: F32x4,
outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]?struct {
outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][4]?struct {
interpolation_type: enum { smooth, flat, noperspective },
blob: []u8,
size: usize,
@@ -67,6 +74,7 @@ pub const DrawCall = struct {
render_pass: *SoftRenderPass,
framebuffer: *SoftFramebuffer,
allocator_mutex: std.Io.Mutex,
rasterizer_wait_group: std.Io.Group,
stats: struct {
@@ -86,6 +94,7 @@ pub const DrawCall = struct {
.depth_attachment = if (render_pass.interface.subpasses[renderer.subpass_index].depth_stencil_attachments) |desc| framebuffer.interface.attachments[desc.attachment] else null,
.render_pass = render_pass,
.framebuffer = framebuffer,
.allocator_mutex = .init,
.rasterizer_wait_group = .init,
.stats = .{
.polygons_drawn = 0,
@@ -93,7 +102,9 @@ pub const DrawCall = struct {
};
for (self.vertices) |*vertex| {
@memset(vertex.outputs[0..], null);
for (&vertex.outputs) |*location| {
@memset(location, null);
}
}
return self;
@@ -102,11 +113,13 @@ pub const DrawCall = struct {
fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
for (self.vertices) |*vertex| {
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
if (vertex.outputs[location]) |output| {
for (0..4) |component| {
if (vertex.outputs[location][component]) |output| {
allocator.free(output.blob);
}
}
}
}
allocator.free(self.vertices);
}
};
@@ -130,6 +143,13 @@ pub fn init(device: *SoftDevice, state: *PipelineState) Self {
.viewports = null,
.scissor = null,
.line_width = null,
.blend_constants = null,
.stencil_front_compare_mask = null,
.stencil_back_compare_mask = null,
.stencil_front_write_mask = null,
.stencil_back_write_mask = null,
.stencil_front_reference = null,
.stencil_back_reference = null,
},
.subpass_index = 0,
};
@@ -189,10 +209,11 @@ fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usi
for (vertex_shader.runtimes[0..]) |*runtime| {
ExecutionDevice.writeDescriptorSets(self.state, &runtime.rt) catch return VkError.Unknown;
}
const fragment_shader = pipeline.stages.getPtrAssertContains(.fragment);
if (pipeline.stages.getPtr(.fragment)) |fragment_shader| {
for (fragment_shader.runtimes[0..]) |*runtime| {
ExecutionDevice.writeDescriptorSets(self.state, &runtime.rt) catch return VkError.Unknown;
}
}
self.vertexShaderStage(allocator, &draw_call, vertex_count, instance_count, first_vertex, first_instance, indices) catch |err| {
std.log.scoped(.@"Vertex stage").err("catched a '{s}'", .{@errorName(err)});
+1 -1
View File
@@ -1096,7 +1096,7 @@ pub fn writeFloat4(c: F32x4, map: []u8, dst_format: vk.Format) void {
const r: u5 = @intFromFloat(@round(color[0] * std.math.maxInt(u5)));
const g: u5 = @intFromFloat(@round(color[1] * std.math.maxInt(u5)));
const b: u5 = @intFromFloat(@round(color[2] * std.math.maxInt(u5)));
const a: u1 = @intFromFloat(color[3]);
const a: u1 = @intFromFloat(@round(color[3]));
std.mem.bytesAsValue(u16, map).* =
(@as(u16, b) << 0) |
(@as(u16, g) << 5) |
+8 -4
View File
@@ -173,13 +173,16 @@ fn interpolateVertexForClipping(allocator: std.mem.Allocator, a: *const Vertex,
.outputs = undefined,
};
@memset(result.outputs[0..], null);
for (&result.outputs) |*location| {
@memset(location, null);
}
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
const out_a = a.outputs[location] orelse continue;
const out_b = b.outputs[location] orelse continue;
for (0..4) |component| {
const out_a = a.outputs[location][component] orelse continue;
const out_b = b.outputs[location][component] orelse continue;
result.outputs[location] = .{
result.outputs[location][component] = .{
.interpolation_type = out_a.interpolation_type,
.blob = if (out_a.interpolation_type == .flat)
allocator.dupe(u8, out_a.blob) catch return VkError.OutOfDeviceMemory
@@ -188,6 +191,7 @@ fn interpolateVertexForClipping(allocator: std.mem.Allocator, a: *const Vertex,
.size = @min(out_a.size, out_b.size),
};
}
}
return result;
}
+35 -8
View File
@@ -4,7 +4,7 @@ const base = @import("base");
const zm = base.zm;
const spv = @import("spv");
const VertexInterpolation = @import("rasterizer/common.zig").VertexInterpolation;
const VertexInterpolationLocation = @import("rasterizer/common.zig").VertexInterpolationLocation;
const Renderer = @import("Renderer.zig");
const SoftImage = @import("../SoftImage.zig");
@@ -18,7 +18,7 @@ pub fn shaderInvocation(
draw_call: *Renderer.DrawCall,
batch_id: usize,
position: zm.F32x4,
inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation,
inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation,
) SpvRuntimeError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 {
var fragment_inputs = inputs;
errdefer freeOwnedInputs(allocator, fragment_inputs);
@@ -45,28 +45,30 @@ pub fn shaderInvocation(
const entry = try rt.getEntryPointByName(shader.entry);
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
const result_word = rt.getResultByLocation(@intCast(location), .input) catch |err| switch (err) {
for (0..4) |component| {
const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .input) catch |err| switch (err) {
SpvRuntimeError.NotFound => continue,
else => return err,
};
var input = fragment_inputs[location];
var input = fragment_inputs[location][component];
if (input.blob.len == 0) {
const memory_size = try rt.getResultMemorySize(result_word);
const zeroes = allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return SpvRuntimeError.OutOfMemory;
@memset(zeroes, 0);
fragment_inputs[location] = .{
fragment_inputs[location][component] = .{
.blob = zeroes,
.size = memory_size,
.free_responsability = true,
};
input = fragment_inputs[location];
input = fragment_inputs[location][component];
}
if (input.blob.len != 0) {
try rt.writeInput(input.blob, result_word);
}
}
}
rt.callEntryPoint(allocator, entry) catch |err| switch (err) {
// Some errors can be safely ignored
@@ -80,6 +82,29 @@ pub fn shaderInvocation(
@memset(std.mem.asBytes(&outputs), 0);
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
var has_split_components = false;
for (1..4) |component| {
_ = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .output) catch |err| switch (err) {
SpvRuntimeError.NotFound => continue,
else => return err,
};
has_split_components = true;
break;
}
if (has_split_components) {
for (0..4) |component| {
const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .output) catch |err| switch (err) {
SpvRuntimeError.NotFound => continue,
else => return err,
};
const memory_size = try rt.getResultMemorySize(result_word);
const offset = component * @sizeOf(f32);
try rt.readOutput(outputs[location][offset .. offset + memory_size], result_word);
}
continue;
}
const result_word = rt.getResultByLocation(@intCast(location), .output) catch |err| switch (err) {
SpvRuntimeError.NotFound => continue,
else => return err,
@@ -93,9 +118,11 @@ pub fn shaderInvocation(
return outputs;
}
fn freeOwnedInputs(allocator: std.mem.Allocator, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation) void {
for (inputs) |input| {
fn freeOwnedInputs(allocator: std.mem.Allocator, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation) void {
for (inputs) |location| {
for (location) |input| {
if (input.free_responsability)
allocator.free(input.blob);
}
}
}
+87 -28
View File
@@ -1,6 +1,7 @@
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");
@@ -9,7 +10,6 @@ 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 blitter = @import("blitter.zig");
const Renderer = @import("Renderer.zig");
const Vertex = Renderer.Vertex;
@@ -37,6 +37,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
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,
@@ -49,6 +50,8 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
.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,
};
}
@@ -61,20 +64,58 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
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_range.aspect_mask,
depth_aspect,
depth_range.base_mip_level,
depth_range.base_array_layer,
);
const attachment_subresource_size = depth_attachment.?.getLayerSize(depth_range.aspect_mask);
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_range.aspect_mask, depth_range.base_mip_level, depth_format),
.texel_size = base.format.texelSize(depth_format),
.format = depth_format,
.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,
};
};
@@ -86,6 +127,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
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| {
@@ -103,6 +145,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
if (stencil_attachment_access) |*access| access else null,
);
},
.triangle_fan => if (draw_call.vertices.len >= 3) {
@@ -120,6 +163,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
if (stencil_attachment_access) |*access| access else null,
);
}
},
@@ -139,6 +183,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
if (stencil_attachment_access) |*access| access else null,
);
} else {
try clipTransformAndRasterizeTriangle(
@@ -150,6 +195,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
v2,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
if (stencil_attachment_access) |*access| access else null,
);
}
}
@@ -166,6 +212,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
v1,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
if (stencil_attachment_access) |*access| access else null,
);
},
.line_strip => if (draw_call.vertices.len >= 2) {
@@ -180,6 +227,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
v1,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
if (stencil_attachment_access) |*access| access else null,
);
}
},
@@ -195,6 +243,7 @@ fn clipTransformAndRasterizePoint(
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)
@@ -208,6 +257,8 @@ fn clipTransformAndRasterizePoint(
const max_x: i32 = @intFromFloat(@ceil(transformed.position[0] + (point_size / 2.0)) - 1.0);
const min_y: i32 = @intFromFloat(@floor(transformed.position[1] - (point_size / 2.0)));
const max_y: i32 = @intFromFloat(@ceil(transformed.position[1] + (point_size / 2.0)) - 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) {
@@ -216,14 +267,10 @@ fn clipTransformAndRasterizePoint(
if (!common.scissorContainsPixel(draw_call.scissor, px, py))
continue;
if (depth_attachment_access) |depth| {
const offset = @as(usize, @intCast(px)) * depth.texel_size + @as(usize, @intCast(py)) * depth.row_pitch;
const depth_value = blitter.readFloat4(depth.base[offset..], depth.format);
if (transformed.position[2] >= depth_value[0])
continue;
}
const outputs = fragment.shaderInvocation(
var outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 = undefined;
@memset(std.mem.asBytes(&outputs), 0);
if (has_fragment_shader) {
outputs = fragment.shaderInvocation(
allocator,
draw_call,
0,
@@ -238,8 +285,9 @@ fn clipTransformAndRasterizePoint(
}
return;
};
}
try common.writeToTargets(outputs, draw_call, color_attachment_access, depth_attachment_access, @intCast(px), @intCast(py), transformed.position[2]);
try common.writeToTargets(outputs, draw_call, color_attachment_access, depth_attachment_access, stencil_attachment_access, true, @intCast(px), @intCast(py), transformed.position[2]);
}
}
}
@@ -251,6 +299,7 @@ fn clipTransformAndRasterizeLine(
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;
@@ -267,6 +316,7 @@ fn clipTransformAndRasterizeLine(
&tv1,
color_attachment_access,
depth_attachment_access,
stencil_attachment_access,
);
}
@@ -279,6 +329,7 @@ fn clipTransformAndRasterizeTriangle(
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);
@@ -303,6 +354,7 @@ fn clipTransformAndRasterizeTriangle(
&tv2,
color_attachment_access,
depth_attachment_access,
stencil_attachment_access,
);
}
}
@@ -316,29 +368,32 @@ fn rasterizeTriangle(
v2: *Vertex,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
stencil_attachment_access: ?*common.RenderTargetAccess,
) VkError!void {
if (try triangleIsCulled(renderer, v0, v1, v2))
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),
.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);
try bresenham.drawLine(allocator, draw_call, v1, v2, color_attachment_access, depth_attachment_access);
try bresenham.drawLine(allocator, draw_call, v2, v0, color_attachment_access, depth_attachment_access);
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, v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) VkError!bool {
fn triangleIsCulled(renderer: *Renderer, front_face: bool) VkError!bool {
const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics;
const rasterization = pipeline_data.rasterization;
const cull_mode = rasterization.cull_mode;
const cull_mode = pipeline_data.rasterization.cull_mode;
if (!cull_mode.front_bit and !cull_mode.back_bit)
return false;
@@ -346,17 +401,21 @@ fn triangleIsCulled(renderer: *Renderer, v0: *const Vertex, v1: *const Vertex, v
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 true;
return null;
const front_face = switch (rasterization.front_face) {
return switch (rasterization.front_face) {
.counter_clockwise => area < 0.0,
.clockwise => area > 0.0,
else => return false,
else => false,
};
return (cull_mode.front_bit and front_face) or (cull_mode.back_bit and !front_face);
}
inline fn triangleArea(v0: *const Vertex, v1: *const Vertex, v2: *const Vertex) f32 {
+13 -16
View File
@@ -3,7 +3,6 @@ const base = @import("base");
const spv = @import("spv");
const zm = base.zm;
const blitter = @import("../blitter.zig");
const common = @import("common.zig");
const fragment = @import("../fragment.zig");
@@ -30,6 +29,8 @@ const RunData = struct {
end_step: usize,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
stencil_attachment_access: ?*common.RenderTargetAccess,
has_fragment_shader: bool,
};
pub fn drawLine(
@@ -39,6 +40,7 @@ pub fn drawLine(
v1: *Renderer.Vertex,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
stencil_attachment_access: ?*common.RenderTargetAccess,
) VkError!void {
const io = draw_call.renderer.device.interface.io();
@@ -69,8 +71,8 @@ pub fn drawLine(
const y_step: i32 = if (y0 > y1) -1 else 1;
const pipeline = draw_call.renderer.state.pipeline orelse return;
const runtimes_count = (pipeline.stages.getPtr(.fragment) orelse return).runtimes.len;
const fragment_stage = pipeline.stages.getPtr(.fragment);
const runtimes_count = if (fragment_stage) |stage| stage.runtimes.len else 1;
if (runtimes_count == 0)
return;
@@ -104,14 +106,13 @@ pub fn drawLine(
.end_step = end_step,
.color_attachment_access = color_attachment_access,
.depth_attachment_access = depth_attachment_access,
.stencil_attachment_access = stencil_attachment_access,
.has_fragment_shader = fragment_stage != null,
};
draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data});
}
// Not syncing workers between triangles when rendering without depth buffer
// will lead to pixel rendering order issues between triangles.
if (depth_attachment_access == null)
draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost;
}
@@ -151,15 +152,10 @@ inline fn run(data: RunData) !void {
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]);
// Early depth test to avoid unnecesary computations
if (data.depth_attachment_access) |depth| {
const offset = @as(usize, @intCast(pixel_x)) * depth.texel_size + @as(usize, @intCast(pixel_y)) * depth.row_pitch;
const depth_value = blitter.readFloat4(depth.base[offset..], depth.format);
if (z >= depth_value[0])
continue;
}
const outputs = fragment.shaderInvocation(
var outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8 = undefined;
@memset(std.mem.asBytes(&outputs), 0);
if (data.has_fragment_shader) {
outputs = fragment.shaderInvocation(
data.allocator,
data.draw_call,
data.batch_id,
@@ -175,7 +171,8 @@ inline fn run(data: RunData) !void {
return;
};
}
try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, @intCast(pixel_x), @intCast(pixel_y), z);
try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, data.stencil_attachment_access, true, @intCast(pixel_x), @intCast(pixel_y), z);
}
}
+251 -14
View File
@@ -16,6 +16,8 @@ pub const RenderTargetAccess = struct {
base: []u8,
row_pitch: usize,
texel_size: usize,
width: u32,
height: u32,
format: vk.Format,
};
@@ -25,6 +27,8 @@ pub const VertexInterpolation = struct {
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);
@@ -41,6 +45,117 @@ pub fn scissorContainsPixel(scissor: vk.Rect2D, x: i32, y: i32) bool {
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,
@@ -49,20 +164,21 @@ pub fn interpolateVertexOutputs(
b0: f32,
b1: f32,
b2: f32,
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation {
var inputs = [_]VertexInterpolation{.{
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation {
var inputs = [_]VertexInterpolationLocation{[_]VertexInterpolation{.{
.blob = &.{},
.size = 0,
.free_responsability = false,
}} ** spv.SPIRV_MAX_OUTPUT_LOCATIONS;
}} ** 4} ** spv.SPIRV_MAX_OUTPUT_LOCATIONS;
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;
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] = .{ .blob = out0.blob, .size = out0.size, .free_responsability = false };
inputs[location][component] = .{ .blob = out0.blob, .size = out0.size, .free_responsability = false };
continue;
}
@@ -88,7 +204,8 @@ pub fn interpolateVertexOutputs(
if (byte_index < len)
@memcpy(input[byte_index..len], out0.blob[byte_index..len]);
inputs[location] = .{ .blob = input, .size = len, .free_responsability = true };
inputs[location][component] = .{ .blob = input, .size = len, .free_responsability = true };
}
}
return inputs;
@@ -99,7 +216,7 @@ pub fn interpolateLineOutputs(
v0: *const Renderer.Vertex,
v1: *const Renderer.Vertex,
t: f32,
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation {
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolationLocation {
return interpolateVertexOutputs(allocator, v0, v1, v0, 1.0 - t, t, 0.0);
}
@@ -109,7 +226,76 @@ inline fn interpolateF32x4(value0: F32x4, value1: F32x4, value2: F32x4, b0: f32,
inline fn fragmentOutputFloat4(output: [@sizeOf(F32x4)]u8, format: vk.Format) F32x4 {
const color = std.mem.bytesToValue(F32x4, &output);
return if (base.format.isSrgb(format)) zm.rgbToSrgb(color) else color;
_ = 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(
@@ -117,33 +303,84 @@ pub fn writeToTargets(
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;
// After work depth test to avoid overwritten depth pixels during fragment invocations
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 = @as(usize, @intCast(x)) * depth.texel_size + @as(usize, @intCast(y)) * depth.row_pitch;
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 = @as(usize, @intCast(x)) * color.texel_size + @as(usize, @intCast(y)) * color.row_pitch;
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 {
blitter.writeFloat4(fragmentOutputFloat4(outputs[location], color.format), color.base[color_offset..], color.format);
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);
}
}
}
+35 -20
View File
@@ -6,7 +6,6 @@ const zm = base.zm;
const common = @import("common.zig");
const fragment = @import("../fragment.zig");
const blitter = @import("../blitter.zig");
const Renderer = @import("../Renderer.zig");
@@ -28,6 +27,9 @@ const RunData = struct {
v2: 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,
};
pub fn drawTriangle(
@@ -38,6 +40,8 @@ pub fn drawTriangle(
v2: *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();
@@ -51,8 +55,10 @@ pub fn drawTriangle(
return;
const pipeline = draw_call.renderer.state.pipeline orelse return;
const runtimes_count = (pipeline.stages.getPtr(.fragment) orelse return).runtimes.len;
const fragment_stage = pipeline.stages.getPtr(.fragment);
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);
@@ -97,15 +103,15 @@ pub fn drawTriangle(
.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,
};
draw_call.rasterizer_wait_group.async(io, runWrapper, .{run_data});
}
}
// Not syncing workers between triangles when rendering without depth buffer
// will lead to pixel rendering order issues between triangles.
if (depth_attachment_access == null)
draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost;
}
@@ -113,6 +119,19 @@ 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));
}
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)});
@@ -139,10 +158,10 @@ inline fn run(data: RunData) !void {
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;
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)
continue;
@@ -152,15 +171,10 @@ inline fn run(data: RunData) !void {
const b2 = w2 / data.area;
const z = (b0 * data.v0.position[2]) + (b1 * data.v1.position[2]) + (b2 * data.v2.position[2]);
// Early depth test to avoid unnecesary computations
if (data.depth_attachment_access) |depth| {
const offset = @as(usize, @intCast(x)) * depth.texel_size + @as(usize, @intCast(y)) * depth.row_pitch;
const depth_value = blitter.readFloat4(depth.base[offset..], depth.format);
if (z >= depth_value[0])
continue;
}
const outputs = fragment.shaderInvocation(
var outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(F32x4)]u8 = undefined;
@memset(std.mem.asBytes(&outputs), 0);
if (data.has_fragment_shader) {
outputs = fragment.shaderInvocation(
data.allocator,
data.draw_call,
data.batch_id,
@@ -175,8 +189,9 @@ inline fn run(data: RunData) !void {
}
return;
};
}
try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, @intCast(x), @intCast(y), z);
try common.writeToTargets(outputs, data.draw_call, data.color_attachment_access, data.depth_attachment_access, data.stencil_attachment_access, data.front_face, @intCast(x), @intCast(y), z);
}
}
}
+78 -5
View File
@@ -45,6 +45,10 @@ inline fn run(data: RunData) !void {
var invocation_index: usize = data.batch_id;
while (invocation_index < data.vertex_count) : (invocation_index += data.batch_size) {
const io = data.draw_call.renderer.device.interface.io();
data.draw_call.allocator_mutex.lock(io) catch return VkError.DeviceLost;
defer data.draw_call.allocator_mutex.unlock(io);
rt.resetInvocation(data.allocator);
try rt.populatePushConstants(data.draw_call.renderer.state.push_constant_blob[0..]);
@@ -84,18 +88,20 @@ inline fn run(data: RunData) !void {
try rt.readBuiltIn(std.mem.asBytes(&output.position), .Position);
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
const result_word = rt.getResultByLocation(@intCast(location), .output) catch |err| switch (err) {
for (0..4) |component| {
const result_word = rt.getResultByLocationComponent(@intCast(location), @intCast(component), .output) catch |err| switch (err) {
SpvRuntimeError.NotFound => continue,
else => return err,
};
const memory_size = try rt.getResultMemorySize(result_word);
output.outputs[location] = .{
.interpolation_type = if (rt.hasResultDecoration(result_word, .Flat)) .flat else .smooth, // TODO : handle noperspective
output.outputs[location][component] = .{
.interpolation_type = if (rt.hasResultDecoration(result_word, .Flat) or resultIsInteger(rt, result_word)) .flat else .smooth, // TODO : handle noperspective
.blob = data.allocator.alloc(u8, memory_size + INTERFACE_BLOB_PADDING) catch return VkError.OutOfDeviceMemory,
.size = memory_size,
};
@memset(output.outputs[location].?.blob, 0);
try rt.readOutput(output.outputs[location].?.blob, result_word);
@memset(output.outputs[location][component].?.blob, 0);
try rt.readOutput(output.outputs[location][component].?.blob, result_word);
}
}
try rt.flushDescriptorSets(data.allocator);
@@ -110,6 +116,25 @@ fn setupBuiltins(rt: *spv.Runtime, vertex_index: usize, instance_index: usize) !
try rt.writeBuiltIn(std.mem.asBytes(&instance_index_u32), .InstanceIndex);
}
fn resultIsInteger(rt: *spv.Runtime, result_word: spv.SpvWord) bool {
const value = rt.results[result_word].getConstValue() catch return false;
return switch (value.*) {
.Int,
.Vector2i32,
.Vector3i32,
.Vector4i32,
.Vector2u32,
.Vector3u32,
.Vector4u32,
=> true,
.Vector => |lanes| lanes.len != 0 and switch (lanes[0]) {
.Int => true,
else => false,
},
else => false,
};
}
fn writeVertexInput(
rt: *spv.Runtime,
allocator: std.mem.Allocator,
@@ -117,6 +142,54 @@ fn writeVertexInput(
format: vk.Format,
location: u32,
) !void {
var has_split_components = false;
for (1..4) |component| {
_ = rt.getResultByLocationComponent(location, @intCast(component), .input) catch |err| switch (err) {
SpvRuntimeError.NotFound => continue,
else => return err,
};
has_split_components = true;
break;
}
if (has_split_components) {
for (0..4) |component| {
const result_word = rt.getResultByLocationComponent(location, @intCast(component), .input) catch |err| switch (err) {
SpvRuntimeError.NotFound => continue,
else => return err,
};
const input_memory_size = try rt.getResultMemorySize(result_word);
const raw_offset = component * @sizeOf(f32);
if (raw_offset + input_memory_size <= raw_input.len) {
try rt.writeInput(raw_input[raw_offset .. raw_offset + input_memory_size], result_word);
continue;
}
const input = allocator.alloc(u8, input_memory_size) catch return VkError.OutOfDeviceMemory;
defer allocator.free(input);
@memset(input, 0);
if (raw_offset < raw_input.len) {
const copy_size = @min(input_memory_size, raw_input.len - raw_offset);
@memcpy(input[0..copy_size], raw_input[raw_offset .. raw_offset + copy_size]);
}
if (component == 3 and input_memory_size >= @sizeOf(f32)) {
if (base.format.isUnnormalizedInteger(format)) {
const one: u32 = 1;
@memcpy(input[0..@sizeOf(u32)], std.mem.asBytes(&one));
} else {
const one: f32 = 1.0;
@memcpy(input[0..@sizeOf(f32)], std.mem.asBytes(&one));
}
}
try rt.writeInput(input, result_word);
}
return;
}
const input_memory_size = try rt.getInputLocationMemorySize(location);
if (raw_input.len >= input_memory_size) {
+24 -2
View File
@@ -69,7 +69,11 @@ pub const DispatchTable = struct {
resetEvent: *const fn (*Self, *Event, vk.PipelineStageFlags) VkError!void,
resolveImage: *const fn (*Self, *Image, vk.ImageLayout, *Image, vk.ImageLayout, vk.ImageResolve) VkError!void,
setEvent: *const fn (*Self, *Event, vk.PipelineStageFlags) VkError!void,
setBlendConstants: *const fn (*Self, [4]f32) VkError!void,
setScissor: *const fn (*Self, u32, []const vk.Rect2D) VkError!void,
setStencilCompareMask: *const fn (*Self, vk.StencilFaceFlags, u32) VkError!void,
setStencilReference: *const fn (*Self, vk.StencilFaceFlags, u32) VkError!void,
setStencilWriteMask: *const fn (*Self, vk.StencilFaceFlags, u32) VkError!void,
setViewport: *const fn (*Self, u32, []const vk.Viewport) VkError!void,
waitEvent: *const fn (*Self, *Event, vk.PipelineStageFlags, vk.PipelineStageFlags, []const vk.MemoryBarrier, []const vk.BufferMemoryBarrier, []const vk.ImageMemoryBarrier) VkError!void,
};
@@ -162,8 +166,10 @@ pub inline fn beginRenderPass(self: *Self, render_pass: *RenderPass, framebuffer
}
pub fn bindDescriptorSets(self: *Self, bind_point: vk.PipelineBindPoint, first_set: u32, sets: []const vk.DescriptorSet, dynamic_offsets: []const u32) VkError!void {
std.debug.assert(sets.len < lib.VULKAN_MAX_DESCRIPTOR_SETS);
var inner_sets = [_]?*DescriptorSet{null} ** lib.VULKAN_MAX_DESCRIPTOR_SETS;
if (sets.len > lib.VULKAN_MAX_DESCRIPTOR_SETS or first_set > lib.VULKAN_MAX_DESCRIPTOR_SETS or first_set + sets.len > lib.VULKAN_MAX_DESCRIPTOR_SETS)
return VkError.ValidationFailed;
var inner_sets: [lib.VULKAN_MAX_DESCRIPTOR_SETS]?*DescriptorSet = @splat(null);
for (sets, inner_sets[0..sets.len]) |set, *inner_set| {
inner_set.* = try NonDispatchable(DescriptorSet).fromHandleObject(set);
}
@@ -288,10 +294,26 @@ pub inline fn setEvent(self: *Self, event: *Event, stage: vk.PipelineStageFlags)
try self.dispatch_table.setEvent(self, event, stage);
}
pub inline fn setBlendConstants(self: *Self, constants: [4]f32) VkError!void {
try self.dispatch_table.setBlendConstants(self, constants);
}
pub inline fn setScissor(self: *Self, first: u32, scissor: []const vk.Rect2D) VkError!void {
try self.dispatch_table.setScissor(self, first, scissor);
}
pub inline fn setStencilCompareMask(self: *Self, face_mask: vk.StencilFaceFlags, compare_mask: u32) VkError!void {
try self.dispatch_table.setStencilCompareMask(self, face_mask, compare_mask);
}
pub inline fn setStencilReference(self: *Self, face_mask: vk.StencilFaceFlags, reference: u32) VkError!void {
try self.dispatch_table.setStencilReference(self, face_mask, reference);
}
pub inline fn setStencilWriteMask(self: *Self, face_mask: vk.StencilFaceFlags, write_mask: u32) VkError!void {
try self.dispatch_table.setStencilWriteMask(self, face_mask, write_mask);
}
pub inline fn setViewport(self: *Self, first: u32, viewports: []const vk.Viewport) VkError!void {
try self.dispatch_table.setViewport(self, first, viewports);
}
+25
View File
@@ -48,6 +48,11 @@ mode: union(enum) {
front_face: vk.FrontFace,
line_width: f32,
},
color_blend: struct {
attachments: ?[]vk.PipelineColorBlendAttachmentState,
constants: [4]f32,
},
depth_stencil: ?vk.PipelineDepthStencilStateCreateInfo,
dynamic_state: DynamicState,
},
},
@@ -142,6 +147,23 @@ pub fn initGraphics(device: *Device, allocator: std.mem.Allocator, cache: ?*Pipe
.front_face = if (info.p_rasterization_state) |state| state.front_face else return VkError.ValidationFailed,
.line_width = if (info.p_rasterization_state) |state| state.line_width else return VkError.ValidationFailed,
},
.color_blend = blk: {
if (info.p_color_blend_state) |state| {
break :blk .{
.attachments = if (state.p_attachments) |attachments|
allocator.dupe(vk.PipelineColorBlendAttachmentState, attachments[0..state.attachment_count]) catch return VkError.OutOfHostMemory
else
null,
.constants = state.blend_constants,
};
}
break :blk .{
.attachments = null,
.constants = .{ 0.0, 0.0, 0.0, 0.0 },
};
},
.depth_stencil = if (info.p_depth_stencil_state) |state| state.* else null,
.dynamic_state = blk: {
var state: DynamicState = .{};
@@ -181,6 +203,9 @@ pub inline fn destroy(self: *Self, allocator: std.mem.Allocator) void {
if (graphics.input_assembly.attribute_description) |attribute_description| {
allocator.free(attribute_description);
}
if (graphics.color_blend.attachments) |attachments| {
allocator.free(attachments);
}
},
}
self.layout.unref(allocator);
+4 -22
View File
@@ -1997,10 +1997,7 @@ pub export fn apeCmdSetBlendConstants(p_cmd: vk.CommandBuffer, p_constants: [*]f
const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err);
const constants = [4]f32{ p_constants[0], p_constants[1], p_constants[2], p_constants[3] };
notImplementedWarning();
_ = cmd;
_ = constants;
cmd.setBlendConstants(constants) catch |err| return errorLogger(err);
}
pub export fn apeCmdSetDepthBias(p_cmd: vk.CommandBuffer, constant_factor: f32, clamp: f32, slope_factor: f32) callconv(vk.vulkan_call_conv) void {
@@ -2064,12 +2061,7 @@ pub export fn apeCmdSetStencilCompareMask(p_cmd: vk.CommandBuffer, face_mask: vk
defer entryPointEndLogTrace();
const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err);
notImplementedWarning();
_ = cmd;
_ = face_mask;
_ = compare_mask;
cmd.setStencilCompareMask(face_mask, compare_mask) catch |err| return errorLogger(err);
}
pub export fn apeCmdSetStencilReference(p_cmd: vk.CommandBuffer, face_mask: vk.StencilFaceFlags, reference: u32) callconv(vk.vulkan_call_conv) void {
@@ -2077,12 +2069,7 @@ pub export fn apeCmdSetStencilReference(p_cmd: vk.CommandBuffer, face_mask: vk.S
defer entryPointEndLogTrace();
const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err);
notImplementedWarning();
_ = cmd;
_ = face_mask;
_ = reference;
cmd.setStencilReference(face_mask, reference) catch |err| return errorLogger(err);
}
pub export fn apeCmdSetStencilWriteMask(p_cmd: vk.CommandBuffer, face_mask: vk.StencilFaceFlags, write_mask: u32) callconv(vk.vulkan_call_conv) void {
@@ -2090,12 +2077,7 @@ pub export fn apeCmdSetStencilWriteMask(p_cmd: vk.CommandBuffer, face_mask: vk.S
defer entryPointEndLogTrace();
const cmd = Dispatchable(CommandBuffer).fromHandleObject(p_cmd) catch |err| return errorLogger(err);
notImplementedWarning();
_ = cmd;
_ = face_mask;
_ = write_mask;
cmd.setStencilWriteMask(face_mask, write_mask) catch |err| return errorLogger(err);
}
pub export fn apeCmdSetViewport(p_cmd: vk.CommandBuffer, first: u32, count: u32, viewports: [*]const vk.Viewport) callconv(vk.vulkan_call_conv) void {