yes
Build / build (push) Successful in 33s
Test / build_and_test (push) Successful in 24s

This commit is contained in:
2026-06-04 02:45:06 +02:00
parent 7da8eec788
commit decdc79ae7
21 changed files with 540 additions and 108 deletions
+4 -2
View File
@@ -157,9 +157,10 @@ inline fn run(data: RunData) !void {
continue;
}
try setupWorkgroupBuiltins(data.self, rt, group_count_vec, group_id_vec);
for (0..data.invocations_per_workgroup) |i| {
rt.resetInvocation(allocator);
try setupWorkgroupBuiltins(data.self, rt, group_count_vec, group_id_vec);
const invocation_index = data.self.invocation_index.fetchAdd(1, .monotonic);
try setupSubgroupBuiltins(data.self, rt, .{
@@ -202,6 +203,7 @@ fn runBarrierWorkgroup(
const allocator = data.self.device.device_allocator.allocator();
for (runtimes, 0..) |*rt, i| {
rt.resetInvocation(allocator);
try ExecutionDevice.writeDescriptorSets(data.self.state, rt);
try setupWorkgroupBuiltins(data.self, rt, group_count, group_id);
try setupSubgroupBuiltins(data.self, rt, group_id, i);
+48 -10
View File
@@ -19,10 +19,12 @@ const Self = @This();
pub const GRAPHICS_PIPELINE_STATE = 0;
pub const COMPUTE_PIPELINE_STATE = 1;
pub const MAX_DYNAMIC_DESCRIPTORS_PER_SET = 64;
pub const PipelineState = struct {
pipeline: ?*SoftPipeline,
sets: [base.VULKAN_MAX_DESCRIPTOR_SETS]?*SoftDescriptorSet,
dynamic_offsets: [base.VULKAN_MAX_DESCRIPTOR_SETS][MAX_DYNAMIC_DESCRIPTORS_PER_SET]u32,
push_constant_blob: [lib.PUSH_CONSTANT_SIZE]u8,
data: union {
compute: struct {},
@@ -45,6 +47,7 @@ pub fn setup(self: *Self, device: *SoftDevice) void {
state.* = .{
.pipeline = null,
.sets = [_]?*SoftDescriptorSet{null} ** base.VULKAN_MAX_DESCRIPTOR_SETS,
.dynamic_offsets = [_][MAX_DYNAMIC_DESCRIPTORS_PER_SET]u32{[_]u32{0} ** MAX_DYNAMIC_DESCRIPTORS_PER_SET} ** base.VULKAN_MAX_DESCRIPTOR_SETS,
.push_constant_blob = @splat(0),
.data = switch (i) {
GRAPHICS_PIPELINE_STATE => .{
@@ -71,37 +74,66 @@ pub fn writeDescriptorSets(state: *PipelineState, rt: *spv.Runtime) !void {
switch (binding) {
.buffer => |buffer_data_array| for (buffer_data_array, 0..) |buffer_data, descriptor_index| {
if (buffer_data.object) |buffer| {
const map = buffer.mapAsSliceWithAddedOffset(u8, buffer_data.offset, buffer_data.size) catch continue :bindings;
try rt.writeDescriptorSet(
const binding_layout = set.?.interface.layout.bindings[binding_index];
const dynamic_offset: vk.DeviceSize = switch (binding_layout.descriptor_type) {
.uniform_buffer_dynamic, .storage_buffer_dynamic => state.dynamic_offsets[set_index][binding_layout.dynamic_index + descriptor_index],
else => 0,
};
const map = buffer.mapAsSliceWithAddedOffset(u8, buffer_data.offset + dynamic_offset, buffer_data.size) catch continue :bindings;
rt.writeDescriptorSet(
map,
@as(u32, @intCast(set_index)),
@as(u32, @intCast(binding_index)),
@as(u32, @intCast(descriptor_index)),
);
) catch |err| switch (err) {
error.NotFound => {},
else => return err,
};
}
},
.image => |image_data_array| for (image_data_array, 0..) |image_data, descriptor_index| {
if (image_data.object) |image_view| {
const addr: usize = @intFromPtr(image_view);
try rt.writeDescriptorSet(
rt.writeDescriptorSet(
std.mem.asBytes(&addr),
@as(u32, @intCast(set_index)),
@as(u32, @intCast(binding_index)),
@as(u32, @intCast(descriptor_index)),
);
) catch |err| switch (err) {
error.NotFound => {},
else => return err,
};
}
},
.sampler => |sampler_data_array| for (sampler_data_array, 0..) |sampler_data, descriptor_index| {
if (sampler_data.object) |sampler| {
const addr: usize = @intFromPtr(sampler);
rt.writeDescriptorSet(
std.mem.asBytes(&addr),
@as(u32, @intCast(set_index)),
@as(u32, @intCast(binding_index)),
@as(u32, @intCast(descriptor_index)),
) catch |err| switch (err) {
error.NotFound => {},
else => return err,
};
}
},
.texel_buffer => |texel_data_array| for (texel_data_array, 0..) |texel_data, descriptor_index| {
if (texel_data.object) |buffer_view| {
const addr: usize = @intFromPtr(buffer_view);
try rt.writeDescriptorSet(
rt.writeDescriptorSet(
std.mem.asBytes(&addr),
@as(u32, @intCast(set_index)),
@as(u32, @intCast(binding_index)),
@as(u32, @intCast(descriptor_index)),
);
) catch |err| switch (err) {
error.NotFound => {},
else => return err,
};
}
},
@@ -111,7 +143,10 @@ pub fn writeDescriptorSets(state: *PipelineState, rt: *spv.Runtime) !void {
sampler: usize,
};
var data: SampledImage = undefined;
var data: SampledImage = .{
.image = 0,
.sampler = 0,
};
if (texture_data.view) |image_view| {
const addr: usize = @intFromPtr(image_view);
@@ -122,12 +157,15 @@ pub fn writeDescriptorSets(state: *PipelineState, rt: *spv.Runtime) !void {
data.sampler = addr;
}
try rt.writeDescriptorSet(
rt.writeDescriptorSet(
std.mem.asBytes(&data),
@as(u32, @intCast(set_index)),
@as(u32, @intCast(binding_index)),
@as(u32, @intCast(descriptor_index)),
);
) catch |err| switch (err) {
error.NotFound => {},
else => return err,
};
},
else => {},
+1
View File
@@ -50,6 +50,7 @@ pub const Vertex = struct {
outputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]?struct {
interpolation_type: enum { smooth, flat, noperspective },
blob: []u8,
size: usize,
},
};
+8 -5
View File
@@ -10,6 +10,7 @@ const Renderer = @import("Renderer.zig");
const Vertex = Renderer.Vertex;
const VkError = base.VkError;
const INTERFACE_BLOB_PADDING = @sizeOf(F32x4);
const ClipPlane = enum {
Left,
@@ -142,9 +143,10 @@ fn isVertexInsidePlane(vertex: *const Vertex, plane: ClipPlane) bool {
return clipDistance(vertex.position, plane) >= 0.0;
}
fn interpolateBlob(allocator: std.mem.Allocator, a: []const u8, b: []const u8, t: f32) VkError![]u8 {
const len = @min(a.len, b.len);
const result = allocator.alloc(u8, len) catch return VkError.OutOfDeviceMemory;
fn interpolateBlob(allocator: std.mem.Allocator, a: []const u8, b: []const u8, size: usize, t: f32) VkError![]u8 {
const len = @min(size, a.len, b.len);
const result = allocator.alloc(u8, len + INTERFACE_BLOB_PADDING) catch return VkError.OutOfDeviceMemory;
@memset(result, 0);
var byte_index: usize = 0;
while (byte_index + @sizeOf(F32x4) <= len) : (byte_index += @sizeOf(F32x4)) {
@@ -160,7 +162,7 @@ fn interpolateBlob(allocator: std.mem.Allocator, a: []const u8, b: []const u8, t
}
if (byte_index < len)
@memcpy(result[byte_index..], a[byte_index..len]);
@memcpy(result[byte_index..len], a[byte_index..len]);
return result;
}
@@ -182,7 +184,8 @@ fn interpolateVertexForClipping(allocator: std.mem.Allocator, a: *const Vertex,
.blob = if (out_a.interpolation_type == .flat)
allocator.dupe(u8, out_a.blob) catch return VkError.OutOfDeviceMemory
else
try interpolateBlob(allocator, out_a.blob, out_b.blob, t),
try interpolateBlob(allocator, out_a.blob, out_b.blob, @min(out_a.size, out_b.size), t),
.size = @min(out_a.size, out_b.size),
};
}
+34 -4
View File
@@ -11,6 +11,7 @@ const SoftImage = @import("../SoftImage.zig");
const VkError = base.VkError;
const SpvRuntimeError = spv.Runtime.RuntimeError;
const INTERFACE_BLOB_PADDING = @sizeOf(zm.F32x4);
pub fn shaderInvocation(
allocator: std.mem.Allocator,
@@ -19,9 +20,11 @@ pub fn shaderInvocation(
position: zm.F32x4,
inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation,
) SpvRuntimeError![spv.SPIRV_MAX_OUTPUT_LOCATIONS][@sizeOf(zm.F32x4)]u8 {
var fragment_inputs = inputs;
errdefer freeOwnedInputs(allocator, fragment_inputs);
const io = draw_call.renderer.device.interface.io();
_ = position;
const pipeline = draw_call.renderer.state.pipeline orelse return undefined;
const shader = pipeline.stages.getPtr(.fragment) orelse return undefined;
@@ -32,7 +35,12 @@ pub fn shaderInvocation(
mutex.lock(io) catch return SpvRuntimeError.Unknown;
defer mutex.unlock(io);
rt.resetInvocation(allocator);
try rt.populatePushConstants(draw_call.renderer.state.push_constant_blob[0..]);
rt.writeBuiltIn(std.mem.asBytes(&position), .FragCoord) catch |err| switch (err) {
SpvRuntimeError.NotFound => {},
else => return err,
};
const entry = try rt.getEntryPointByName(shader.entry);
@@ -41,9 +49,23 @@ pub fn shaderInvocation(
SpvRuntimeError.NotFound => continue,
else => return err,
};
try rt.writeInput(inputs[location].blob, result_word);
if (inputs[location].free_responsability)
allocator.free(inputs[location].blob);
var input = fragment_inputs[location];
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] = .{
.blob = zeroes,
.size = memory_size,
.free_responsability = true,
};
input = fragment_inputs[location];
}
if (input.blob.len != 0) {
try rt.writeInput(input.blob, result_word);
}
}
rt.callEntryPoint(allocator, entry) catch |err| switch (err) {
@@ -66,6 +88,14 @@ pub fn shaderInvocation(
}
try rt.flushDescriptorSets(allocator);
freeOwnedInputs(allocator, fragment_inputs);
return outputs;
}
fn freeOwnedInputs(allocator: std.mem.Allocator, inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation) void {
for (inputs) |input| {
if (input.free_responsability)
allocator.free(input.blob);
}
}
+68 -1
View File
@@ -1,12 +1,15 @@
const std = @import("std");
const vk = @import("vulkan");
const base = @import("base");
const zm = base.zm;
const clip = @import("clip.zig");
const bresenham = @import("rasterizer/bresenham.zig");
const edge_function = @import("rasterizer/edge_function.zig");
const common = @import("rasterizer/common.zig");
const fragment = @import("fragment.zig");
const blitter = @import("blitter.zig");
const Renderer = @import("Renderer.zig");
const Vertex = Renderer.Vertex;
@@ -21,7 +24,7 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
const pipeline_data = (renderer.state.pipeline orelse return VkError.InvalidHandleDrv).interface.mode.graphics;
const topology = pipeline_data.input_assembly.topology;
const color_attachments = draw_call.render_pass.interface.subpasses[renderer.subpass_index].color_attachments orelse return VkError.InvalidAttachmentDrv;
const color_attachments = draw_call.render_pass.interface.subpasses[renderer.subpass_index].color_attachments orelse &.{};
const color_attachment_access = allocator.alloc(?common.RenderTargetAccess, color_attachments.len) catch return VkError.OutOfDeviceMemory;
@memset(color_attachment_access, null);
@@ -76,6 +79,15 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
};
switch (topology) {
.point_list => for (draw_call.vertices) |*vertex| {
try clipTransformAndRasterizePoint(
allocator,
draw_call,
vertex,
color_attachment_access,
if (depth_attachment_access) |*access| access else null,
);
},
.triangle_list => for (0..@divTrunc(draw_call.vertices.len, 3)) |triangle_index| {
const first_vertex = triangle_index * 3;
const v0 = &draw_call.vertices[first_vertex + 0];
@@ -177,6 +189,61 @@ pub fn processThenFragmentStage(renderer: *Renderer, allocator: std.mem.Allocato
draw_call.rasterizer_wait_group.await(io) catch return VkError.DeviceLost;
}
fn clipTransformAndRasterizePoint(
allocator: std.mem.Allocator,
draw_call: *DrawCall,
vertex: *Vertex,
color_attachment_access: []const ?common.RenderTargetAccess,
depth_attachment_access: ?*common.RenderTargetAccess,
) VkError!void {
const x, const y, const z, const w = vertex.position;
if (w == 0.0 or x < -w or x > w or y < -w or y > w or z < 0.0 or z > w)
return;
var transformed = vertex.*;
clip.viewportTransformVertex(draw_call.viewport, &transformed);
const point_size = 1.0;
const min_x: i32 = @intFromFloat(@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);
var py = min_y;
while (py <= max_y) : (py += 1) {
var px = min_x;
while (px <= max_x) : (px += 1) {
if (!common.scissorContainsPixel(draw_call.scissor, px, py))
continue;
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(
allocator,
draw_call,
0,
zm.f32x4(@floatFromInt(px), @floatFromInt(py), transformed.position[2], 1.0),
try common.interpolateVertexOutputs(allocator, &transformed, &transformed, &transformed, 1.0, 0.0, 0.0),
) catch |err| {
std.log.scoped(.@"Fragment stage").err("catched a '{s}'", .{@errorName(err)});
if (comptime base.config.logs == .verbose) {
if (@errorReturnTrace()) |trace| {
std.debug.dumpErrorReturnTrace(trace);
}
}
return;
};
try common.writeToTargets(outputs, draw_call, color_attachment_access, depth_attachment_access, @intCast(px), @intCast(py), transformed.position[2]);
}
}
}
fn clipTransformAndRasterizeLine(
allocator: std.mem.Allocator,
draw_call: *DrawCall,
+13 -7
View File
@@ -21,6 +21,7 @@ pub const RenderTargetAccess = struct {
pub const VertexInterpolation = struct {
blob: []const u8,
size: usize,
free_responsability: bool,
};
@@ -49,20 +50,25 @@ pub fn interpolateVertexOutputs(
b1: f32,
b2: f32,
) VkError![spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation {
var inputs: [spv.SPIRV_MAX_OUTPUT_LOCATIONS]VertexInterpolation = undefined;
var inputs = [_]VertexInterpolation{.{
.blob = &.{},
.size = 0,
.free_responsability = false,
}} ** 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;
if (out0.interpolation_type == .flat or out0.blob.len == 0) {
inputs[location] = .{ .blob = out0.blob, .free_responsability = false };
if (out0.interpolation_type == .flat or out0.size == 0) {
inputs[location] = .{ .blob = out0.blob, .size = out0.size, .free_responsability = false };
continue;
}
const len = @min(out0.blob.len, out1.blob.len, out2.blob.len);
const input = allocator.alloc(u8, len) catch return VkError.OutOfDeviceMemory;
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)) {
@@ -80,9 +86,9 @@ pub fn interpolateVertexOutputs(
}
if (byte_index < len)
@memcpy(input[byte_index..], out0.blob[byte_index..len]);
@memcpy(input[byte_index..len], out0.blob[byte_index..len]);
inputs[location] = .{ .blob = input, .free_responsability = true };
inputs[location] = .{ .blob = input, .size = len, .free_responsability = true };
}
return inputs;
+53 -6
View File
@@ -1,8 +1,9 @@
const std = @import("std");
const spv = @import("spv");
const base = @import("base");
const vk = @import("vulkan");
const F32x4 = Renderer.F32x4;
const F32x4 = base.zm.F32x4;
const SpvRuntimeError = spv.Runtime.RuntimeError;
@@ -10,6 +11,7 @@ const Renderer = @import("Renderer.zig");
const SoftPipeline = @import("../SoftPipeline.zig");
const VkError = base.VkError;
const INTERFACE_BLOB_PADDING = @sizeOf(F32x4);
pub const RunData = struct {
allocator: std.mem.Allocator,
@@ -38,12 +40,14 @@ pub fn runWrapper(data: RunData) void {
inline fn run(data: RunData) !void {
const shader = data.pipeline.stages.getPtrAssertContains(.vertex);
const rt = &shader.runtimes[data.batch_id].rt;
try rt.populatePushConstants(data.draw_call.renderer.state.push_constant_blob[0..]);
const entry = try rt.getEntryPointByName(shader.entry);
var invocation_index: usize = data.batch_id;
while (invocation_index < data.vertex_count) : (invocation_index += data.batch_size) {
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 instance_index = data.first_instance + data.instance_index;
@@ -54,8 +58,6 @@ inline fn run(data: RunData) !void {
if (data.pipeline.interface.mode.graphics.input_assembly.attribute_description) |attributes| {
for (attributes) |attribute| {
const location_result = try rt.getResultByLocation(attribute.location, .input);
const binding_info = (data.pipeline.interface.mode.graphics.input_assembly.binding_description orelse return)[attribute.binding];
const vertex_buffer = data.draw_call.renderer.state.data.graphics.vertex_buffers[attribute.binding];
@@ -66,7 +68,7 @@ inline fn run(data: RunData) !void {
const buffer_memory_map: []u8 = try buffer_memory.map(offset, buffer_memory_size);
try rt.writeInput(buffer_memory_map, location_result);
try writeVertexInput(rt, data.allocator, buffer_memory_map, attribute.format, attribute.location);
}
}
@@ -86,10 +88,13 @@ inline fn run(data: RunData) !void {
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
.blob = data.allocator.alloc(u8, try rt.getResultMemorySize(result_word)) catch return VkError.OutOfDeviceMemory,
.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);
}
@@ -104,3 +109,45 @@ fn setupBuiltins(rt: *spv.Runtime, vertex_index: usize, instance_index: usize) !
try rt.writeBuiltIn(std.mem.asBytes(&vertex_index_u32), .VertexIndex);
try rt.writeBuiltIn(std.mem.asBytes(&instance_index_u32), .InstanceIndex);
}
fn writeVertexInput(
rt: *spv.Runtime,
allocator: std.mem.Allocator,
raw_input: []const u8,
format: vk.Format,
location: u32,
) !void {
const input_memory_size = try rt.getInputLocationMemorySize(location);
if (raw_input.len >= input_memory_size) {
try rt.writeInputLocation(raw_input[0..input_memory_size], location);
return;
}
const input = allocator.alloc(u8, input_memory_size) catch return VkError.OutOfDeviceMemory;
defer allocator.free(input);
@memset(input, 0);
@memcpy(input[0..raw_input.len], raw_input);
fillMissingVertexComponents(input, raw_input.len, format);
try rt.writeInputLocation(input, location);
}
fn fillMissingVertexComponents(input: []u8, raw_input_size: usize, format: vk.Format) void {
if (input.len < @sizeOf(F32x4) or raw_input_size > 3 * @sizeOf(f32))
return;
const component_count = base.format.componentCount(format);
if (component_count >= 4)
return;
const alpha_offset = 3 * @sizeOf(f32);
if (base.format.isUnnormalizedInteger(format)) {
const one: u32 = 1;
@memcpy(input[alpha_offset .. alpha_offset + @sizeOf(u32)], std.mem.asBytes(&one));
} else {
const one: f32 = 1.0;
@memcpy(input[alpha_offset .. alpha_offset + @sizeOf(f32)], std.mem.asBytes(&one));
}
}