adding primitive restart, integer texture sampling and mip/lod management in sampling
Test / build_and_test (push) Successful in 49s
Build / build (push) Successful in 1m0s

This commit is contained in:
2026-06-05 22:16:28 +02:00
parent b1279bde60
commit c7a298978a
10 changed files with 469 additions and 113 deletions
+42 -10
View File
@@ -53,6 +53,7 @@ pub const DynamicState = struct {
};
pub const Vertex = struct {
primitive_restart: bool,
position: F32x4,
outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS][4]?struct {
interpolation_type: enum { smooth, flat, noperspective },
@@ -100,6 +101,7 @@ pub const DrawCall = struct {
};
for (self.vertices) |*vertex| {
vertex.primitive_restart = false;
for (&vertex.outputs) |*location| {
@memset(location, null);
}
@@ -157,19 +159,19 @@ pub fn init(device: *SoftDevice, state: *PipelineState, active_occlusion_queries
pub fn draw(self: *Self, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize) VkError!void {
var bounded_allocator: BoundedAllocator = .init(self.device.device_allocator.allocator(), @"1GiB");
try self.drawCall(&bounded_allocator, vertex_count, instance_count, first_vertex, first_instance, null);
try self.drawCall(&bounded_allocator, vertex_count, instance_count, first_vertex, first_instance, null, null);
}
pub fn drawIndexed(self: *Self, index_count: usize, instance_count: usize, first_index: usize, first_instance: usize, vertex_offset: i32) VkError!void {
var bounded_allocator: BoundedAllocator = .init(self.device.device_allocator.allocator(), @"1GiB");
const allocator = bounded_allocator.allocator();
const indices = try self.readIndexBuffer(allocator, index_count, first_index, vertex_offset);
const indexed_draw = try self.readIndexBuffer(allocator, index_count, first_index, vertex_offset);
try self.drawCall(&bounded_allocator, index_count, instance_count, 0, first_instance, indices);
try self.drawCall(&bounded_allocator, index_count, instance_count, 0, first_instance, indexed_draw.indices, indexed_draw.primitive_restart);
}
fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize, indices: ?[]const i32) VkError!void {
fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize, indices: ?[]const u32, primitive_restart: ?[]const bool) VkError!void {
const io = self.device.interface.io();
const allocator = bounded_allocator.allocator();
@@ -215,7 +217,7 @@ fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usi
}
}
self.vertexShaderStage(allocator, &draw_call, vertex_count, instance_count, first_vertex, first_instance, indices) catch |err| {
self.vertexShaderStage(allocator, &draw_call, vertex_count, instance_count, first_vertex, first_instance, indices, primitive_restart) catch |err| {
std.log.scoped(.@"Vertex stage").err("catched a '{s}'", .{@errorName(err)});
if (comptime base.config.logs == .verbose) {
if (@errorReturnTrace()) |trace| {
@@ -232,7 +234,7 @@ fn drawCall(self: *Self, bounded_allocator: *BoundedAllocator, vertex_count: usi
try rasterizer.processThenFragmentStage(self, allocator, &draw_call);
}
fn vertexShaderStage(self: *Self, allocator: std.mem.Allocator, draw_call: *DrawCall, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize, indices: ?[]const i32) !void {
fn vertexShaderStage(self: *Self, allocator: std.mem.Allocator, draw_call: *DrawCall, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize, indices: ?[]const u32, primitive_restart: ?[]const bool) !void {
const pipeline = self.state.pipeline orelse return;
const batch_size = (pipeline.stages.getPtr(.vertex) orelse return).runtimes.len;
@@ -248,6 +250,7 @@ fn vertexShaderStage(self: *Self, allocator: std.mem.Allocator, draw_call: *Draw
.first_vertex = first_vertex,
.first_instance = first_instance,
.indices = indices,
.primitive_restart = primitive_restart,
.instance_index = instance_index,
.draw_call = draw_call,
};
@@ -258,7 +261,12 @@ fn vertexShaderStage(self: *Self, allocator: std.mem.Allocator, draw_call: *Draw
wg.await(self.device.interface.io()) catch return VkError.DeviceLost;
}
fn readIndexBuffer(self: *Self, allocator: std.mem.Allocator, index_count: usize, first_index: usize, vertex_offset: i32) VkError![]i32 {
const IndexedDrawData = struct {
indices: []u32,
primitive_restart: ?[]bool,
};
fn readIndexBuffer(self: *Self, allocator: std.mem.Allocator, index_count: usize, first_index: usize, vertex_offset: i32) VkError!IndexedDrawData {
const index_buffer = self.state.data.graphics.index_buffer;
const buffer = index_buffer.buffer;
const buffer_memory = if (buffer.interface.memory) |memory| memory else return VkError.InvalidDeviceMemoryDrv;
@@ -271,7 +279,11 @@ fn readIndexBuffer(self: *Self, allocator: std.mem.Allocator, index_count: usize
const byte_size = index_count * index_size;
const index_memory: []const u8 = try buffer_memory.map(byte_offset, byte_size);
const indices = allocator.alloc(i32, index_count) catch return VkError.OutOfDeviceMemory;
const indices = allocator.alloc(u32, index_count) catch return VkError.OutOfDeviceMemory;
const restart_enabled = (self.state.pipeline orelse return VkError.InvalidPipelineDrv).interface.mode.graphics.input_assembly.primitive_restart_enable == .true;
const restart_index = primitiveRestartIndex(index_buffer.index_type);
const primitive_restart = if (restart_enabled) allocator.alloc(bool, index_count) catch return VkError.OutOfDeviceMemory else null;
for (indices, 0..) |*index, i| {
const offset = i * index_size;
const raw_index: u32 = switch (index_size) {
@@ -280,10 +292,21 @@ fn readIndexBuffer(self: *Self, allocator: std.mem.Allocator, index_count: usize
4 => @intCast(std.mem.readInt(u32, index_memory[offset..][0..4], .little)),
else => unreachable,
};
index.* = vertex_offset + @as(i32, @intCast(raw_index));
if (primitive_restart) |restart| {
restart[i] = raw_index == restart_index;
if (restart[i]) {
index.* = 0;
continue;
}
}
const shifted = @as(i64, raw_index) + @as(i64, vertex_offset);
index.* = @as(u32, @truncate(@as(u64, @bitCast(shifted))));
}
return indices;
return .{
.indices = indices,
.primitive_restart = primitive_restart,
};
}
fn indexTypeSize(index_type: vk.IndexType) ?usize {
@@ -295,6 +318,15 @@ fn indexTypeSize(index_type: vk.IndexType) ?usize {
};
}
fn primitiveRestartIndex(index_type: vk.IndexType) u32 {
return switch (index_type) {
.uint8 => std.math.maxInt(u8),
.uint16 => std.math.maxInt(u16),
.uint32 => std.math.maxInt(u32),
else => unreachable,
};
}
fn resolveViewport(self: *Self, viewport_index: usize) VkError!vk.Viewport {
const pipeline_data =
&(self.state.pipeline orelse return VkError.InvalidPipelineDrv).interface.mode.graphics;
+1
View File
@@ -169,6 +169,7 @@ fn interpolateBlob(allocator: std.mem.Allocator, a: []const u8, b: []const u8, s
fn interpolateVertexForClipping(allocator: std.mem.Allocator, a: *const Vertex, b: *const Vertex, t: f32) VkError!Vertex {
var result: Vertex = .{
.primitive_restart = false,
.position = a.position + ((b.position - a.position) * zm.f32x4s(t)),
.outputs = undefined,
};
+99 -62
View File
@@ -121,6 +121,9 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
switch (topology) {
.point_list => for (draw_call.vertices) |*vertex| {
if (vertex.primitive_restart)
continue;
try clipTransformAndRasterizePoint(
allocator,
draw_call,
@@ -148,56 +151,71 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
if (stencil_attachment_access) |*access| access else null,
);
},
.triangle_fan => if (draw_call.vertices.len >= 3) {
const v0 = &draw_call.vertices[0];
for (1..(draw_call.vertices.len - 1)) |vertex_index| {
const v1 = &draw_call.vertices[vertex_index];
const v2 = &draw_call.vertices[vertex_index + 1];
.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,
);
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 => if (draw_call.vertices.len >= 3) {
for (0..(draw_call.vertices.len - 2)) |vertex_index| {
const v0 = &draw_call.vertices[vertex_index + 0];
const v1 = &draw_call.vertices[vertex_index + 1];
const v2 = &draw_call.vertices[vertex_index + 2];
.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 ((vertex_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,
);
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| {
@@ -215,20 +233,27 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
if (stencil_attachment_access) |*access| access else null,
);
},
.line_strip => if (draw_call.vertices.len >= 2) {
for (0..(draw_call.vertices.len - 1)) |vertex_index| {
const v0 = &draw_call.vertices[vertex_index + 0];
const v1 = &draw_call.vertices[vertex_index + 1];
.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,
);
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}),
@@ -237,6 +262,18 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
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,
@@ -253,10 +290,10 @@ fn clipTransformAndRasterizePoint(
clip.viewportTransformVertex(draw_call.viewport, &transformed);
const point_size = 1.0;
const min_x: i32 = @intFromFloat(@floor(transformed.position[0] - (point_size / 2.0)));
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 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;
+17 -10
View File
@@ -268,27 +268,34 @@ inline fn blendOp(op: vk.BlendOp, src: F32x4, dst: F32x4) F32x4 {
};
}
inline fn blendColor(src: F32x4, dst: F32x4, state: vk.PipelineColorBlendAttachmentState, constants: [4]f32) F32x4 {
inline fn blendColor(src: F32x4, dst: F32x4, state: vk.PipelineColorBlendAttachmentState, constants: [4]f32, format: vk.Format) 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
const min_value = zm.f32x4s(base.format.minElementValue(format));
const max_value = zm.f32x4s(base.format.maxElementValue(format));
const clamped_src = if (base.format.isFloat(format)) src else std.math.clamp(src, min_value, max_value);
const constant = if (base.format.isFloat(format))
F32x4{ constants[0], constants[1], constants[2], constants[3] }
else
src * blendFactor(state.src_color_blend_factor, src, dst, constant);
std.math.clamp(F32x4{ constants[0], constants[1], constants[2], constants[3] }, min_value, max_value);
const color_src = if (state.color_blend_op == .min or state.color_blend_op == .max)
clamped_src
else
clamped_src * blendFactor(state.src_color_blend_factor, clamped_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);
dst * blendFactor(state.dst_color_blend_factor, clamped_src, dst, constant);
const alpha_src = if (state.alpha_blend_op == .min or state.alpha_blend_op == .max)
src
clamped_src
else
src * blendFactor(state.src_alpha_blend_factor, src, dst, constant);
clamped_src * blendFactor(state.src_alpha_blend_factor, clamped_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);
dst * blendFactor(state.dst_alpha_blend_factor, clamped_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];
@@ -389,7 +396,7 @@ pub fn writeToTargets(
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);
const blended = blendColor(src, dst, attachments[location], constants, color.format);
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;
+14 -6
View File
@@ -21,7 +21,8 @@ pub const RunData = struct {
vertex_count: usize,
first_vertex: usize,
first_instance: usize,
indices: ?[]const i32,
indices: ?[]const u32,
primitive_restart: ?[]const bool,
instance_index: usize,
draw_call: *Renderer.DrawCall,
};
@@ -51,13 +52,22 @@ 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 output: *Renderer.Vertex = &data.draw_call.vertices[(data.instance_index * data.vertex_count) + invocation_index];
if (data.primitive_restart) |primitive_restart| {
if (primitive_restart[invocation_index]) {
output.primitive_restart = true;
continue;
}
}
rt.resetInvocation(data.allocator);
try rt.populatePushConstants(data.draw_call.renderer.state.push_constant_blob[0..]);
const vertex_index: usize = if (data.indices) |indices| @intCast(indices[invocation_index]) else data.first_vertex + invocation_index;
const vertex_index_u32: u32 = if (data.indices) |indices| indices[invocation_index] else @intCast(data.first_vertex + invocation_index);
const vertex_index: usize = vertex_index_u32;
const instance_index = data.first_instance + data.instance_index;
setupBuiltins(rt, vertex_index, instance_index) catch |err| switch (err) {
setupBuiltins(rt, vertex_index_u32, instance_index) catch |err| switch (err) {
SpvRuntimeError.NotFound => {},
else => return err,
};
@@ -88,7 +98,6 @@ inline fn run(data: RunData) !void {
else => return err,
};
const output: *Renderer.Vertex = &data.draw_call.vertices[(data.instance_index * data.vertex_count) + invocation_index];
try rt.readBuiltIn(std.mem.asBytes(&output.position), .Position);
for (0..spv.SPIRV_MAX_OUTPUT_LOCATIONS) |location| {
@@ -112,8 +121,7 @@ inline fn run(data: RunData) !void {
}
}
fn setupBuiltins(rt: *spv.Runtime, vertex_index: usize, instance_index: usize) !void {
const vertex_index_u32: u32 = @intCast(vertex_index);
fn setupBuiltins(rt: *spv.Runtime, vertex_index_u32: u32, instance_index: usize) !void {
const instance_index_u32: u32 = @intCast(instance_index);
try rt.writeBuiltIn(std.mem.asBytes(&vertex_index_u32), .VertexIndex);