Files
VulkanDriver/src/software/interpreter/Shader.zig
T
kbz_8 f40e5b742d
Mirror Gitea refs to GitHub / mirror (push) Successful in 31s
Test / build_and_test (push) Successful in 11m51s
Build / build (push) Successful in 15m19s
[Soft] adding new experimental shader interpreter based on new IR
2026-08-08 00:42:13 +02:00

163 lines
6.0 KiB
Zig

const std = @import("std");
const vk = @import("vulkan");
const base = @import("base");
const shader_ir = @import("shader_ir");
const Program = @import("Program.zig");
const Runtime = @import("Runtime.zig");
const SoftShaderModule = @import("../SoftShaderModule.zig");
const VkError = base.VkError;
const ir = shader_ir.ir;
pub const RuntimeSlot = struct {
mutex: std.Io.Mutex = .init,
runtime: Runtime,
};
const Self = @This();
program: Program,
runtimes: []RuntimeSlot,
workgroup_size: ?[3]u32,
/// Compiles a stage when the current interpreter can execute its complete
/// interface. `null` deliberately selects the existing SPIR-V runtime.
pub fn compile(
allocator: std.mem.Allocator,
module: *SoftShaderModule,
stage: *const vk.PipelineShaderStageCreateInfo,
runtime_count: usize,
) VkError!?Self {
const expected_stage = commonStage(stage.stage) orelse return null;
if (expected_stage == .fragment)
return null;
const specializations = try specializationValues(allocator, stage.p_specialization_info);
defer if (specializations.len != 0) allocator.free(specializations);
var module_ir = module.interface.instantiateIr(allocator, .{
.entry_point = std.mem.span(stage.p_name),
.stage = expected_stage,
.specializations = specializations,
}) catch |err| {
if (err == error.OutOfMemory)
return VkError.OutOfDeviceMemory;
std.log.scoped(.SoftIrInterpreter).debug("IR translation fallback: {s}", .{@errorName(err)});
return null;
};
defer module_ir.deinit();
var program = Program.compile(allocator, &module_ir) catch |err| {
if (err == error.OutOfMemory)
return VkError.OutOfDeviceMemory;
std.log.scoped(.SoftIrInterpreter).debug("bytecode lowering fallback: {s}", .{@errorName(err)});
return null;
};
errdefer program.deinit();
if (!hasCompatibleInterface(&program, expected_stage) or
(expected_stage == .compute and module_ir.execution_modes.workgroup_size == null))
{
std.log.scoped(.SoftIrInterpreter).debug("stage interface or execution modes require the SPIR-V runtime", .{});
program.deinit();
return null;
}
const runtimes = allocator.alloc(RuntimeSlot, runtime_count) catch return VkError.OutOfDeviceMemory;
var initialized: usize = 0;
errdefer {
for (runtimes[0..initialized]) |*slot|
slot.runtime.deinit();
allocator.free(runtimes);
}
for (runtimes) |*slot| {
slot.* = .{ .runtime = Runtime.init(allocator, &program) catch return VkError.OutOfDeviceMemory };
initialized += 1;
}
std.log.scoped(.SoftIrInterpreter).debug("compiled {s} stage to {d} bytecode instructions", .{
@tagName(expected_stage),
program.code.len,
});
return .{
.program = program,
.runtimes = runtimes,
.workgroup_size = module_ir.execution_modes.workgroup_size,
};
}
pub fn deinit(self: *Self) void {
for (self.runtimes) |*slot|
slot.runtime.deinit();
self.program.deinit();
self.* = undefined;
}
fn hasCompatibleInterface(program: *const Program, stage: ir.module.Stage) bool {
var has_position = false;
for (program.interfaces) |optional_binding| {
const binding = optional_binding orelse continue;
switch (binding.semantic) {
.location => |location| {
if (stage == .compute or location.index != 0 or
@as(u16, location.component) + binding.span.components > 4)
return false;
},
.builtin => |builtin| switch (stage) {
.vertex => switch (builtin) {
.vertex_index, .instance_index => if (binding.direction != .input) return false,
.position => {
if (binding.direction != .output or binding.span.kind != .floating or binding.span.components != 4)
return false;
has_position = true;
},
else => return false,
},
.compute => if (builtin != .global_invocation_id or binding.direction != .input or binding.span.components != 3)
return false,
.fragment => return false,
},
}
}
return stage != .vertex or has_position;
}
fn specializationValues(allocator: std.mem.Allocator, info: ?*const vk.SpecializationInfo) VkError![]shader_ir.spirv.translator.SpecializationValue {
const specialization = info orelse return &.{};
if (specialization.map_entry_count == 0)
return &.{};
const entries = specialization.p_map_entries orelse return VkError.ValidationFailed;
const data: []const u8 = if (specialization.data_size == 0)
&.{}
else
@as([*]const u8, @ptrCast(@alignCast(specialization.p_data)))[0..specialization.data_size];
const values = allocator.alloc(shader_ir.spirv.translator.SpecializationValue, specialization.map_entry_count) catch
return VkError.OutOfDeviceMemory;
errdefer allocator.free(values);
for (entries[0..specialization.map_entry_count], values) |entry, *value| {
const offset: usize = entry.offset;
const end = std.math.add(usize, offset, entry.size) catch return VkError.ValidationFailed;
if (end > data.len)
return VkError.ValidationFailed;
value.* = .{ .constant_id = entry.constant_id, .data = data[offset..end] };
}
return values;
}
fn commonStage(stage: vk.ShaderStageFlags) ?ir.module.Stage {
const bits: u32 = @bitCast(stage);
const vertex_bits: u32 = @bitCast(vk.ShaderStageFlags{ .vertex_bit = true });
const fragment_bits: u32 = @bitCast(vk.ShaderStageFlags{ .fragment_bit = true });
const compute_bits: u32 = @bitCast(vk.ShaderStageFlags{ .compute_bit = true });
return if (bits == vertex_bits)
.vertex
else if (bits == fragment_bits)
.fragment
else if (bits == compute_bits)
.compute
else
null;
}