From 5fc7d561fc0c68062a7e8f3f712aef5f2a1e3a42 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Fri, 24 Apr 2026 02:41:37 +0200 Subject: [PATCH] implementing proper graphics pipeline creations --- build.zig | 2 + src/soft/SoftCommandBuffer.zig | 5 +- src/soft/SoftInstance.zig | 2 +- src/soft/SoftPipeline.zig | 115 ++++++++++++++++++-------- src/soft/device/ComputeDispatcher.zig | 42 ++++------ src/soft/device/Device.zig | 25 +++++- src/soft/device/Renderer.zig | 37 ++++++++- src/vulkan/logger.zig | 13 +-- 8 files changed, 161 insertions(+), 80 deletions(-) diff --git a/build.zig b/build.zig index 7fa9366..fc3de6a 100644 --- a/build.zig +++ b/build.zig @@ -155,12 +155,14 @@ fn customSoft(b: *std.Build, lib: *std.Build.Step.Compile, options: *std.Build.S }) orelse return error.UnresolvedDependency; 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 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 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"); + options.addOption(bool, "single_threaded", single_threaded_option); options.addOption(bool, "debug_allocator", debug_allocator_option); options.addOption(bool, "shaders_simd", shaders_simd_option); options.addOption(bool, "single_threaded_compute", single_threaded_compute_option); diff --git a/src/soft/SoftCommandBuffer.zig b/src/soft/SoftCommandBuffer.zig index bb1941b..dd20dfc 100644 --- a/src/soft/SoftCommandBuffer.zig +++ b/src/soft/SoftCommandBuffer.zig @@ -218,7 +218,7 @@ pub fn bindVertexBuffer(interface: *Interface, index: usize, buffer: *base.Buffe pub fn execute(context: *anyopaque, device: *ExecutionDevice) VkError!void { 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, .offset = impl.offset, .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 { const impl: *Impl = @ptrCast(@alignCast(context)); - _ = impl; - _ = device; + device.renderer.drawPrimitive(impl.vertex_count, impl.instance_count, impl.first_vertex, impl.first_instance); } }; diff --git a/src/soft/SoftInstance.zig b/src/soft/SoftInstance.zig index 45f90ce..a2fde30 100644 --- a/src/soft/SoftInstance.zig +++ b/src/soft/SoftInstance.zig @@ -33,7 +33,7 @@ pub fn create(allocator: std.mem.Allocator, infos: *const vk.InstanceCreateInfo) errdefer allocator.destroy(self); 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.interface = try base.Instance.init(allocator, infos); diff --git a/src/soft/SoftPipeline.zig b/src/soft/SoftPipeline.zig index 9bb037f..928fd1e 100644 --- a/src/soft/SoftPipeline.zig +++ b/src/soft/SoftPipeline.zig @@ -32,6 +32,7 @@ const Stages = enum { }; interface: Interface, +runtimes_allocator: std.heap.ArenaAllocator, stages: std.EnumMap(Stages, Shader), 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(); + 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 runtimes_count = switch (instance.threaded.async_limit) { .nothing => 1, @@ -62,22 +67,17 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: self.* = .{ .interface = interface, + .runtimes_allocator = runtimes_allocator_arena, .stages = std.EnumMap(Stages, Shader).init(.{ .compute = blk: { var shader: Shader = undefined; soft_module.ref(); shader.module = soft_module; - const runtimes = device_allocator.alloc(spv.Runtime, runtimes_count) catch return VkError.OutOfHostMemory; - errdefer { - for (runtimes) |*runtime| { - runtime.deinit(device_allocator); - } - device_allocator.free(runtimes); - } + const runtimes = runtimes_allocator.alloc(spv.Runtime, runtimes_count) catch return VkError.OutOfHostMemory; 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)}); return VkError.Unknown; }; @@ -85,7 +85,7 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: 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(device_allocator, .{ + runtime.addSpecializationInfo(runtimes_allocator, .{ .id = @intCast(entry.constant_id), .offset = @intCast(entry.offset), .size = @intCast(entry.size), @@ -96,7 +96,9 @@ pub fn createCompute(device: *base.Device, allocator: std.mem.Allocator, cache: } 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; }, }), @@ -114,6 +116,13 @@ pub fn createGraphics(device: *base.Device, allocator: std.mem.Allocator, cache: .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 runtimes_count = switch (instance.threaded.async_limit) { .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.* = .{ .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; } pub fn destroy(interface: *Interface, allocator: std.mem.Allocator) void { const self: *Self = @alignCast(@fieldParentPtr("interface", interface)); - const soft_device: *SoftDevice = @alignCast(@fieldParentPtr("interface", interface.owner)); - 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); - } + self.runtimes_allocator.deinit(); allocator.destroy(self); } diff --git a/src/soft/device/ComputeDispatcher.zig b/src/soft/device/ComputeDispatcher.zig index d2ba00f..57e4be8 100644 --- a/src/soft/device/ComputeDispatcher.zig +++ b/src/soft/device/ComputeDispatcher.zig @@ -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; for (0..@min(self.batch_size, group_count)) |batch_id| { - if (comptime base.config.single_threaded_compute) { - runWrapper( - 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, - }, - ); - } else { - wg.async(self.device.interface.io(), runWrapper, .{ - 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, - }, - }); - } + const 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, + }; + + if (comptime base.config.single_threaded_compute) + runWrapper(run_data) + else + wg.async(self.device.interface.io(), runWrapper, .{run_data}); } wg.await(self.device.interface.io()) catch return VkError.DeviceLost; } diff --git a/src/soft/device/Device.zig b/src/soft/device/Device.zig index 565eee4..7ea5ecc 100644 --- a/src/soft/device/Device.zig +++ b/src/soft/device/Device.zig @@ -1,6 +1,7 @@ const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); +const lib = @import("../lib.zig"); const SoftDescriptorSet = @import("../SoftDescriptorSet.zig"); const SoftDevice = @import("../SoftDevice.zig"); @@ -15,29 +16,45 @@ const VkError = base.VkError; const Self = @This(); +pub const GRAPHICS_PIPELINE_STATE = 0; +pub const COMPUTE_PIPELINE_STATE = 1; + pub const PipelineState = struct { pipeline: ?*SoftPipeline, sets: [base.VULKAN_MAX_DESCRIPTOR_SETS]?*SoftDescriptorSet, + data: union { + compute: struct {}, + graphics: struct { + vertex_buffers: [lib.MAX_VERTEX_INPUT_BINDINGS]Renderer.VertexBuffer, + }, + }, }; compute: ComputeDispatcher, renderer: Renderer, -/// .graphics = 0 -/// .compute = 1 pipeline_states: [2]PipelineState, /// Initializating an execution device and /// not creating one to avoid dangling pointers pub fn init(self: *Self, device: *SoftDevice) void { - for (self.pipeline_states[0..]) |*state| { + for (self.pipeline_states[0..], 0..) |*state, i| { state.* = .{ .pipeline = null, .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.renderer = .init(); + self.renderer = .init(device, &self.pipeline_states[@intFromEnum(vk.PipelineBindPoint.compute)]); } pub fn deinit(self: *Self) void { diff --git a/src/soft/device/Renderer.zig b/src/soft/device/Renderer.zig index 96592e5..961356c 100644 --- a/src/soft/device/Renderer.zig +++ b/src/soft/device/Renderer.zig @@ -1,8 +1,13 @@ const std = @import("std"); const vk = @import("vulkan"); const base = @import("base"); +const zm = @import("zmath"); const lib = @import("../lib.zig"); +const F32x4 = zm.F32x4; + +const PipelineState = @import("Device.zig").PipelineState; + const SoftBuffer = @import("../SoftBuffer.zig"); const SoftDescriptorSet = @import("../SoftDescriptorSet.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_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, framebuffer: ?*SoftFramebuffer, dynamic_state: DynamicState, -pub fn init() Self { +pub fn init(device: *SoftDevice, state: *PipelineState) Self { return .{ + .device = device, + .state = state, .render_pass = null, .framebuffer = null, .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 { _ = 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; +} diff --git a/src/vulkan/logger.zig b/src/vulkan/logger.zig index 4afb10c..4d3d19c 100644 --- a/src/vulkan/logger.zig +++ b/src/vulkan/logger.zig @@ -14,17 +14,19 @@ comptime { var mutex: std.Io.Mutex = .init; pub inline fn fixme(comptime format: []const u8, args: anytype) void { - if (!lib.config.logs) { + if (comptime !lib.config.logs) { return; } 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 { - if (!lib.config.logs) { + if (comptime !lib.config.logs) { return; } + const io = std.Options.debug_io; + const scope_name = @tagName(scope); const scope_prefix = comptime blk: { const limit = 30 - 4; @@ -42,13 +44,6 @@ pub fn log(comptime level: std.log.Level, comptime scope: @EnumLiteral(), compti .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 stdout_file = std.Io.File.stdout();