implementing proper graphics pipeline creations
Build / build (push) Successful in 1m8s
Test / build_and_test (push) Successful in 25m29s

This commit is contained in:
2026-04-24 02:41:37 +02:00
parent 2a427c0b51
commit 5fc7d561fc
8 changed files with 161 additions and 80 deletions
+2
View File
@@ -155,12 +155,14 @@ fn customSoft(b: *std.Build, lib: *std.Build.Step.Compile, options: *std.Build.S
}) orelse return error.UnresolvedDependency; }) orelse return error.UnresolvedDependency;
lib.root_module.addImport("spv", spv.module("spv")); lib.root_module.addImport("spv", spv.module("spv"));
const single_threaded_option = b.option(bool, "single-threaded", "Single threaded runtime mode") orelse false;
const debug_allocator_option = b.option(bool, "debug-allocator", "Debug device allocator") orelse false; const debug_allocator_option = b.option(bool, "debug-allocator", "Debug device allocator") orelse false;
const shaders_simd_option = b.option(bool, "shader-simd", "Shaders SIMD acceleration") orelse true; const shaders_simd_option = b.option(bool, "shader-simd", "Shaders SIMD acceleration") orelse true;
const single_threaded_compute_option = b.option(bool, "single-threaded-compute", "Single threaded compute shaders execution") orelse true; const single_threaded_compute_option = b.option(bool, "single-threaded-compute", "Single threaded compute shaders execution") orelse true;
const compute_dump_early_results_table_option = b.option(u32, "compute-dump-early-results-table", "Dump compute shaders results table before invocation"); const compute_dump_early_results_table_option = b.option(u32, "compute-dump-early-results-table", "Dump compute shaders results table before invocation");
const compute_dump_final_results_table_option = b.option(u32, "compute-dump-final-results-table", "Dump compute shaders results table after invocation"); const compute_dump_final_results_table_option = b.option(u32, "compute-dump-final-results-table", "Dump compute shaders results table after invocation");
options.addOption(bool, "single_threaded", single_threaded_option);
options.addOption(bool, "debug_allocator", debug_allocator_option); options.addOption(bool, "debug_allocator", debug_allocator_option);
options.addOption(bool, "shaders_simd", shaders_simd_option); options.addOption(bool, "shaders_simd", shaders_simd_option);
options.addOption(bool, "single_threaded_compute", single_threaded_compute_option); options.addOption(bool, "single_threaded_compute", single_threaded_compute_option);
+2 -3
View File
@@ -218,7 +218,7 @@ pub fn bindVertexBuffer(interface: *Interface, index: usize, buffer: *base.Buffe
pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void {
const impl: *Impl = @ptrCast(@alignCast(context)); const impl: *Impl = @ptrCast(@alignCast(context));
device.renderer.dynamic_state.vertex_buffers[impl.index] = .{ device.pipeline_states[ExecutionDevice.GRAPHICS_PIPELINE_STATE].data.graphics.vertex_buffers[impl.index] = .{
.buffer = impl.buffer, .buffer = impl.buffer,
.offset = impl.offset, .offset = impl.offset,
.size = 0, .size = 0,
@@ -482,8 +482,7 @@ pub fn draw(interface: *Interface, vertex_count: usize, instance_count: usize, f
pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void {
const impl: *Impl = @ptrCast(@alignCast(context)); const impl: *Impl = @ptrCast(@alignCast(context));
_ = impl; device.renderer.drawPrimitive(impl.vertex_count, impl.instance_count, impl.first_vertex, impl.first_instance);
_ = device;
} }
}; };
+1 -1
View File
@@ -33,7 +33,7 @@ pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo)
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
self.allocator = std.heap.smp_allocator; self.allocator = std.heap.smp_allocator;
self.threaded = std.Io.Threaded.init(self.allocator, .{}); self.threaded = if (comptime base.config.single_threaded) .init_single_threaded else std.Io.Threaded.init(self.allocator, .{});
self.io_impl = self.threaded.io(); self.io_impl = self.threaded.io();
self.interface = try base.Instance.init(allocator, infos); self.interface = try base.Instance.init(allocator, infos);
+82 -33
View File
@@ -32,6 +32,7 @@ const Stages = enum {
}; };
interface: Interface, interface: Interface,
runtimes_allocator: std.heap.ArenaAllocator,
stages: std.EnumMap(Stages, Shader), stages: std.EnumMap(Stages, Shader),
pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.ComputePipelineCreateInfo) VkError!*Self { pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: ?*base.PipelineCache, info: *const vk.ComputePipelineCreateInfo) VkError!*Self {
@@ -50,6 +51,10 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache:
const device_allocator = soft_device.device_allocator.allocator(); const device_allocator = soft_device.device_allocator.allocator();
var runtimes_allocator_arena: std.heap.ArenaAllocator = .init(device_allocator);
errdefer runtimes_allocator_arena.deinit();
const runtimes_allocator = runtimes_allocator_arena.allocator();
const instance: *SoftInstance = @alignCast(@fieldParentPtr("interface", device.instance)); const instance: *SoftInstance = @alignCast(@fieldParentPtr("interface", device.instance));
const runtimes_count = switch (instance.threaded.async_limit) { const runtimes_count = switch (instance.threaded.async_limit) {
.nothing => 1, .nothing => 1,
@@ -62,22 +67,17 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache:
self.* = .{ self.* = .{
.interface = interface, .interface = interface,
.runtimes_allocator = runtimes_allocator_arena,
.stages = std.EnumMap(Stages, Shader).init(.{ .stages = std.EnumMap(Stages, Shader).init(.{
.compute = blk: { .compute = blk: {
var shader: Shader = undefined; var shader: Shader = undefined;
soft_module.ref(); soft_module.ref();
shader.module = soft_module; shader.module = soft_module;
const runtimes = device_allocator.alloc(spv.Runtime, runtimes_count) catch return VkError.OutOfHostMemory; const runtimes = runtimes_allocator.alloc(spv.Runtime, runtimes_count) catch return VkError.OutOfHostMemory;
errdefer {
for (runtimes) |*runtime| {
runtime.deinit(device_allocator);
}
device_allocator.free(runtimes);
}
for (runtimes) |*runtime| { for (runtimes) |*runtime| {
runtime.* = spv.Runtime.init(device_allocator, &soft_module.module) catch |err| { runtime.* = spv.Runtime.init(runtimes_allocator, &soft_module.module) catch |err| {
std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to initialize, {s}", .{@errorName(err)}); std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to initialize, {s}", .{@errorName(err)});
return VkError.Unknown; return VkError.Unknown;
}; };
@@ -85,7 +85,7 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache:
if (specialization.p_map_entries) |map| { if (specialization.p_map_entries) |map| {
const data: []const u8 = @as([*]const u8, @ptrCast(@alignCast(specialization.p_data)))[0..specialization.data_size]; const data: []const u8 = @as([*]const u8, @ptrCast(@alignCast(specialization.p_data)))[0..specialization.data_size];
for (map[0..], 0..specialization.map_entry_count) |entry, _| { for (map[0..], 0..specialization.map_entry_count) |entry, _| {
runtime.addSpecializationInfo(device_allocator, .{ runtime.addSpecializationInfo(runtimes_allocator, .{
.id = @intCast(entry.constant_id), .id = @intCast(entry.constant_id),
.offset = @intCast(entry.offset), .offset = @intCast(entry.offset),
.size = @intCast(entry.size), .size = @intCast(entry.size),
@@ -96,7 +96,9 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache:
} }
shader.runtimes = runtimes; shader.runtimes = runtimes;
shader.entry = device_allocator.dupe(u8, std.mem.span(info.stage.p_name)) catch return VkError.OutOfHostMemory; shader.entry = runtimes_allocator.dupe(u8, std.mem.span(info.stage.p_name)) catch return VkError.OutOfHostMemory;
std.log.scoped(.ComputePipeline).debug("Created {d} runtimes for compute stage", .{runtimes_count});
break :blk shader; break :blk shader;
}, },
}), }),
@@ -114,6 +116,13 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache:
.destroy = destroy, .destroy = destroy,
}; };
const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", device));
const device_allocator = soft_device.device_allocator.allocator();
var runtimes_allocator_arena: std.heap.ArenaAllocator = .init(device_allocator);
errdefer runtimes_allocator_arena.deinit();
const runtimes_allocator = runtimes_allocator_arena.allocator();
const instance: *SoftInstance = @alignCast(@fieldParentPtr("interface", device.instance)); const instance: *SoftInstance = @alignCast(@fieldParentPtr("interface", device.instance));
const runtimes_count = switch (instance.threaded.async_limit) { const runtimes_count = switch (instance.threaded.async_limit) {
.nothing => 1, .nothing => 1,
@@ -124,36 +133,76 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache:
}, },
}; };
const runtimes = allocator.alloc(spv.Runtime, runtimes_count) catch return VkError.OutOfHostMemory;
errdefer allocator.free(runtimes);
//for (runtimes) |*runtime| {
// runtime.* = spv.Runtime.init() catch |err| {
// std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to initialize, {s}", .{@errorName(err)});
// return VkError.Unknown;
// };
//}
self.* = .{ self.* = .{
.interface = interface, .interface = interface,
.stages = std.enums.EnumMap(Stages, Shader).init(.{}), .runtimes_allocator = runtimes_allocator_arena,
.stages = std.EnumMap(Stages, Shader).init(.{}),
}; };
if (info.p_stages) |stages| {
for (stages[0..], 0..info.stage_count) |stage, _| {
var shader: Shader = undefined;
const module = try NonDispatchable(ShaderModule).fromHandleObject(stage.module);
const soft_module: *SoftShaderModule = @alignCast(@fieldParentPtr("interface", module));
soft_module.ref();
shader.module = soft_module;
const runtimes = runtimes_allocator.alloc(spv.Runtime, runtimes_count) catch return VkError.OutOfHostMemory;
for (runtimes) |*runtime| {
runtime.* = spv.Runtime.init(runtimes_allocator, &soft_module.module) catch |err| {
std.log.scoped(.SpvRuntimeInit).err("SPIR-V Runtime failed to initialize, {s}", .{@errorName(err)});
return VkError.Unknown;
};
if (stage.p_specialization_info) |specialization| {
if (specialization.p_map_entries) |map| {
const data: []const u8 = @as([*]const u8, @ptrCast(@alignCast(specialization.p_data)))[0..specialization.data_size];
for (map[0..], 0..specialization.map_entry_count) |entry, _| {
runtime.addSpecializationInfo(runtimes_allocator, .{
.id = @intCast(entry.constant_id),
.offset = @intCast(entry.offset),
.size = @intCast(entry.size),
}, data) catch return VkError.OutOfHostMemory;
}
}
}
}
shader.runtimes = runtimes;
shader.entry = runtimes_allocator.dupe(u8, std.mem.span(stage.p_name)) catch return VkError.OutOfHostMemory;
std.log.scoped(.GraphicsPipeline).debug("Created {d} runtimes for:", .{runtimes_count});
if (stage.stage.contains(.{ .vertex_bit = true })) {
std.log.scoped(.GraphicsPipeline).debug("> Vertex stage", .{});
self.stages.put(.vertex, shader);
} else if (stage.stage.contains(.{ .fragment_bit = true })) {
std.log.scoped(.GraphicsPipeline).debug("> Fragment stage", .{});
self.stages.put(.fragment, shader);
} else if (stage.stage.contains(.{ .tessellation_control_bit = true })) {
std.log.scoped(.GraphicsPipeline).debug("> Tessellation control stage", .{});
self.stages.put(.tessellation_control, shader);
} else if (stage.stage.contains(.{ .tessellation_evaluation_bit = true })) {
std.log.scoped(.GraphicsPipeline).debug("> Tessellation evaluation stage", .{});
self.stages.put(.tessellation_evaluation, shader);
} else if (stage.stage.contains(.{ .geometry_bit = true })) {
std.log.scoped(.GraphicsPipeline).debug("> Geometry stage", .{});
self.stages.put(.geometry, shader);
} else {
std.log.scoped(.GraphicsPipeline).err("> invalid stage", .{});
return VkError.Unknown;
}
}
} else {
return VkError.ValidationFailed;
}
return self; return self;
} }
pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void {
const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); const self: *Self = @alignCast(@fieldParentPtr("interface", interface));
const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", interface.owner)); self.runtimes_allocator.deinit();
const device_allocator = soft_device.device_allocator.allocator();
var it = self.stages.iterator();
while (it.next()) |stage| {
for (stage.value.runtimes) |*runtime| {
runtime.deinit(device_allocator);
}
device_allocator.free(stage.value.runtimes);
device_allocator.free(stage.value.entry);
stage.value.module.unref(allocator);
}
allocator.destroy(self); allocator.destroy(self);
} }
+15 -27
View File
@@ -63,33 +63,21 @@ pub fn dispatch(self: *Self, group_count_x: u32, group_count_y: u32, group_count
var wg: std.Io.Group = .init; var wg: std.Io.Group = .init;
for (0..@min(self.batch_size, group_count)) |batch_id| { for (0..@min(self.batch_size, group_count)) |batch_id| {
if (comptime base.config.single_threaded_compute) { const run_data: RunData = .{
runWrapper( .self = self,
RunData{ .batch_id = batch_id,
.self = self, .group_count = group_count,
.batch_id = batch_id, .group_count_x = @as(usize, @intCast(group_count_x)),
.group_count = group_count, .group_count_y = @as(usize, @intCast(group_count_y)),
.group_count_x = @as(usize, @intCast(group_count_x)), .group_count_z = @as(usize, @intCast(group_count_z)),
.group_count_y = @as(usize, @intCast(group_count_y)), .invocations_per_workgroup = invocations_per_workgroup,
.group_count_z = @as(usize, @intCast(group_count_z)), .pipeline = pipeline,
.invocations_per_workgroup = invocations_per_workgroup, };
.pipeline = pipeline,
}, if (comptime base.config.single_threaded_compute)
); runWrapper(run_data)
} else { else
wg.async(self.device.interface.io(), runWrapper, .{ wg.async(self.device.interface.io(), runWrapper, .{run_data});
RunData{
.self = self,
.batch_id = batch_id,
.group_count = group_count,
.group_count_x = @as(usize, @intCast(group_count_x)),
.group_count_y = @as(usize, @intCast(group_count_y)),
.group_count_z = @as(usize, @intCast(group_count_z)),
.invocations_per_workgroup = invocations_per_workgroup,
.pipeline = pipeline,
},
});
}
} }
wg.await(self.device.interface.io()) catch return VkError.DeviceLost; wg.await(self.device.interface.io()) catch return VkError.DeviceLost;
} }
+21 -4
View File
@@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const lib = @import("../lib.zig");
const SoftDescriptorSet = @import("../SoftDescriptorSet.zig"); const SoftDescriptorSet = @import("../SoftDescriptorSet.zig");
const SoftDevice = @import("../SoftDevice.zig"); const SoftDevice = @import("../SoftDevice.zig");
@@ -15,29 +16,45 @@ const VkError = base.VkError;
const Self = @This(); const Self = @This();
pub const GRAPHICS_PIPELINE_STATE = 0;
pub const COMPUTE_PIPELINE_STATE = 1;
pub const PipelineState = struct { pub const PipelineState = struct {
pipeline: ?*SoftPipeline, pipeline: ?*SoftPipeline,
sets: [base.VULKAN_MAX_DESCRIPTOR_SETS]?*SoftDescriptorSet, sets: [base.VULKAN_MAX_DESCRIPTOR_SETS]?*SoftDescriptorSet,
data: union {
compute: struct {},
graphics: struct {
vertex_buffers: [lib.MAX_VERTEX_INPUT_BINDINGS]Renderer.VertexBuffer,
},
},
}; };
compute: ComputeDispatcher, compute: ComputeDispatcher,
renderer: Renderer, renderer: Renderer,
/// .graphics = 0
/// .compute = 1
pipeline_states: [2]PipelineState, pipeline_states: [2]PipelineState,
/// Initializating an execution device and /// Initializating an execution device and
/// not creating one to avoid dangling pointers /// not creating one to avoid dangling pointers
pub fn init(self: *Self, device: *SoftDevice) void { pub fn init(self: *Self, device: *SoftDevice) void {
for (self.pipeline_states[0..]) |*state| { for (self.pipeline_states[0..], 0..) |*state, i| {
state.* = .{ state.* = .{
.pipeline = null, .pipeline = null,
.sets = [_]?*SoftDescriptorSet{null} ** base.VULKAN_MAX_DESCRIPTOR_SETS, .sets = [_]?*SoftDescriptorSet{null} ** base.VULKAN_MAX_DESCRIPTOR_SETS,
.data = switch (i) {
GRAPHICS_PIPELINE_STATE => .{
.graphics = .{
.vertex_buffers = undefined,
},
},
COMPUTE_PIPELINE_STATE => .{ .compute = .{} },
else => unreachable,
},
}; };
} }
self.compute = .init(device, &self.pipeline_states[@intFromEnum(vk.PipelineBindPoint.compute)]); self.compute = .init(device, &self.pipeline_states[@intFromEnum(vk.PipelineBindPoint.compute)]);
self.renderer = .init(); self.renderer = .init(device, &self.pipeline_states[@intFromEnum(vk.PipelineBindPoint.compute)]);
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
+34 -3
View File
@@ -1,8 +1,13 @@
const std = @import("std"); const std = @import("std");
const vk = @import("vulkan"); const vk = @import("vulkan");
const base = @import("base"); const base = @import("base");
const zm = @import("zmath");
const lib = @import("../lib.zig"); const lib = @import("../lib.zig");
const F32x4 = zm.F32x4;
const PipelineState = @import("Device.zig").PipelineState;
const SoftBuffer = @import("../SoftBuffer.zig"); const SoftBuffer = @import("../SoftBuffer.zig");
const SoftDescriptorSet = @import("../SoftDescriptorSet.zig"); const SoftDescriptorSet = @import("../SoftDescriptorSet.zig");
const SoftDevice = @import("../SoftDevice.zig"); const SoftDevice = @import("../SoftDevice.zig");
@@ -42,22 +47,48 @@ pub const DynamicState = struct {
vertex_input_bindings: [lib.MAX_VERTEX_INPUT_BINDINGS]VertexInputBindingState, vertex_input_bindings: [lib.MAX_VERTEX_INPUT_BINDINGS]VertexInputBindingState,
vertex_input_attributes: [lib.MAX_VERTEX_INPUT_ATTRIBUTES]VertexInputAttributeState, vertex_input_attributes: [lib.MAX_VERTEX_INPUT_ATTRIBUTES]VertexInputAttributeState,
vertex_buffers: [lib.MAX_VERTEX_INPUT_BINDINGS]VertexBuffer,
}; };
const Vertex = struct {
position: F32x4,
point_size: f32,
index: usize,
};
device: *SoftDevice,
state: *PipelineState,
render_pass: ?*SoftRenderPass, render_pass: ?*SoftRenderPass,
framebuffer: ?*SoftFramebuffer, framebuffer: ?*SoftFramebuffer,
dynamic_state: DynamicState, dynamic_state: DynamicState,
pub fn init() Self { pub fn init(device: *SoftDevice, state: *PipelineState) Self {
return .{ return .{
.device = device,
.state = state,
.render_pass = null, .render_pass = null,
.framebuffer = null, .framebuffer = null,
.dynamic_state = undefined, .dynamic_state = undefined,
}; };
} }
pub fn drawPrimitive(self: *Self, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize) void {
const allocator = self.device.device_allocator.allocator();
const vertices = self.fetchVertexInput(allocator, vertex_count, instance_count, first_vertex, first_instance);
_ = vertices;
}
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
_ = self; _ = self;
} }
fn fetchVertexInput(self: *const Self, allocator: std.mem.Allocator, vertex_count: usize, instance_count: usize, first_vertex: usize, first_instance: usize) []Vertex {
_ = self;
_ = allocator;
_ = vertex_count;
_ = instance_count;
_ = first_vertex;
_ = first_instance;
return undefined;
}
+4 -9
View File
@@ -14,17 +14,19 @@ comptime {
var mutex: std.Io.Mutex = .init; var mutex: std.Io.Mutex = .init;
pub inline fn fixme(comptime format: []const u8, args: anytype) void { pub inline fn fixme(comptime format: []const u8, args: anytype) void {
if (!lib.config.logs) { if (comptime !lib.config.logs) {
return; return;
} }
std.log.scoped(.FIXME).warn("FIXME: " ++ format, args); std.log.scoped(.FIXME).warn("FIXME: " ++ format, args);
} }
pub fn log(comptime level: std.log.Level, comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype) void { pub fn log(comptime level: std.log.Level, comptime scope: @EnumLiteral(), comptime format: []const u8, args: anytype) void {
if (!lib.config.logs) { if (comptime !lib.config.logs) {
return; return;
} }
const io = std.Options.debug_io;
const scope_name = @tagName(scope); const scope_name = @tagName(scope);
const scope_prefix = comptime blk: { const scope_prefix = comptime blk: {
const limit = 30 - 4; const limit = 30 - 4;
@@ -42,13 +44,6 @@ pub fn log(comptime level: std.log.Level, comptime scope: @EnumLiteral(), compti
.err => .red, .err => .red,
}; };
const allocator = std.heap.smp_allocator;
var threaded: std.Io.Threaded = .init(allocator, .{});
defer threaded.deinit();
const io = threaded.io();
const stderr_file = std.Io.File.stderr(); const stderr_file = std.Io.File.stderr();
const stdout_file = std.Io.File.stdout(); const stdout_file = std.Io.File.stdout();