330 lines
11 KiB
Zig
330 lines
11 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const lib = @import("lib.zig");
|
|
const spv = @import("spv.zig");
|
|
const op = @import("opcodes.zig");
|
|
|
|
const SpvVoid = spv.SpvVoid;
|
|
const SpvByte = spv.SpvByte;
|
|
const SpvWord = spv.SpvWord;
|
|
const SpvBool = spv.SpvBool;
|
|
|
|
const SpvBinding = spv.SpvBinding;
|
|
|
|
const Result = @import("Result.zig");
|
|
const Runtime = @import("Runtime.zig");
|
|
const Value = @import("Value.zig").Value;
|
|
const WordIterator = @import("WordIterator.zig");
|
|
|
|
const Self = @This();
|
|
|
|
pub const ModuleOptions = struct {
|
|
use_simd_vectors_specializations: bool = true,
|
|
};
|
|
|
|
const SpvEntryPoint = struct {
|
|
exec_model: spv.SpvExecutionModel,
|
|
id: SpvWord,
|
|
name: []const u8,
|
|
globals: []SpvWord,
|
|
};
|
|
|
|
pub const ModuleError = error{
|
|
InvalidSpirV,
|
|
InvalidMagic,
|
|
UnsupportedEndianness,
|
|
UnsupportedExtension,
|
|
OutOfMemory,
|
|
};
|
|
|
|
const AllocatorWrapper = struct {
|
|
child_allocator: std.mem.Allocator,
|
|
total_bytes_allocated: usize = 0,
|
|
|
|
pub fn allocator(self: *AllocatorWrapper) std.mem.Allocator {
|
|
return .{
|
|
.ptr = self,
|
|
.vtable = &.{
|
|
.alloc = alloc,
|
|
.resize = resize,
|
|
.remap = remap,
|
|
.free = free,
|
|
},
|
|
};
|
|
}
|
|
|
|
fn alloc(ctx: *anyopaque, n: usize, alignment: std.mem.Alignment, ra: usize) ?[*]u8 {
|
|
const self: *AllocatorWrapper = @ptrCast(@alignCast(ctx));
|
|
self.total_bytes_allocated += alignment.toByteUnits() + n;
|
|
return self.child_allocator.rawAlloc(n, alignment, ra);
|
|
}
|
|
|
|
fn resize(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool {
|
|
const self: *AllocatorWrapper = @ptrCast(@alignCast(ctx));
|
|
return self.child_allocator.rawResize(buf, alignment, new_len, ret_addr);
|
|
}
|
|
|
|
fn remap(context: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, return_address: usize) ?[*]u8 {
|
|
const self: *AllocatorWrapper = @ptrCast(@alignCast(context));
|
|
return self.child_allocator.rawRemap(memory, alignment, new_len, return_address);
|
|
}
|
|
|
|
fn free(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, ret_addr: usize) void {
|
|
const self: *AllocatorWrapper = @ptrCast(@alignCast(ctx));
|
|
return self.child_allocator.rawFree(buf, alignment, ret_addr);
|
|
}
|
|
};
|
|
|
|
options: ModuleOptions,
|
|
|
|
it: WordIterator,
|
|
|
|
version_major: SpvByte,
|
|
version_minor: SpvByte,
|
|
|
|
generator_id: u16,
|
|
generator_version: u16,
|
|
|
|
bound: SpvWord,
|
|
|
|
code: []const SpvWord,
|
|
|
|
addressing: spv.SpvAddressingModel,
|
|
memory_model: spv.SpvMemoryModel,
|
|
|
|
extensions: std.ArrayList([]const u8),
|
|
|
|
results: []Result,
|
|
|
|
entry_points: std.ArrayList(SpvEntryPoint),
|
|
capabilities: std.EnumSet(spv.SpvCapability),
|
|
|
|
local_size_x: SpvWord,
|
|
local_size_y: SpvWord,
|
|
local_size_z: SpvWord,
|
|
|
|
geometry_invocations: SpvWord,
|
|
geometry_output_count: SpvWord,
|
|
geometry_input: SpvWord,
|
|
geometry_output: SpvWord,
|
|
|
|
input_locations: [lib.SPIRV_MAX_INPUT_LOCATIONS]SpvWord,
|
|
output_locations: [lib.SPIRV_MAX_OUTPUT_LOCATIONS]SpvWord,
|
|
bindings: [lib.SPIRV_MAX_SET][lib.SPIRV_MAX_SET_BINDINGS]SpvWord,
|
|
builtins: std.EnumMap(spv.SpvBuiltIn, SpvWord),
|
|
push_constants: []Value,
|
|
|
|
needed_runtime_bytes: usize,
|
|
|
|
pub fn init(allocator: std.mem.Allocator, source: []const SpvWord, options: ModuleOptions) ModuleError!Self {
|
|
var self: Self = std.mem.zeroInit(Self, .{
|
|
.options = options,
|
|
.code = allocator.dupe(SpvWord, source) catch return ModuleError.OutOfMemory,
|
|
.extensions = std.ArrayList([]const u8).empty,
|
|
.entry_points = std.ArrayList(SpvEntryPoint).empty,
|
|
.capabilities = std.EnumSet(spv.SpvCapability).initEmpty(),
|
|
.local_size_x = 1,
|
|
.local_size_y = 1,
|
|
.local_size_z = 1,
|
|
});
|
|
errdefer allocator.free(self.code);
|
|
|
|
op.initRuntimeDispatcher();
|
|
|
|
var wrapped_allocator: AllocatorWrapper = .{ .child_allocator = allocator };
|
|
|
|
self.it = WordIterator.init(self.code);
|
|
|
|
const magic = self.it.next() catch return ModuleError.InvalidSpirV;
|
|
if (magic != spv.SpvMagicNumber) {
|
|
return ModuleError.InvalidMagic;
|
|
}
|
|
if (!checkEndiannessFromSpvMagic(magic)) {
|
|
return ModuleError.UnsupportedEndianness;
|
|
}
|
|
|
|
const version = self.it.next() catch return ModuleError.InvalidSpirV;
|
|
self.version_major = @intCast((version & 0x00FF0000) >> 16);
|
|
self.version_minor = @intCast((version & 0x0000FF00) >> 8);
|
|
|
|
const generator = self.it.next() catch return ModuleError.InvalidSpirV;
|
|
self.generator_id = @intCast((generator & 0xFFFF0000) >> 16);
|
|
self.generator_version = @intCast(generator & 0x0000FFFF);
|
|
|
|
self.bound = self.it.next() catch return ModuleError.InvalidSpirV;
|
|
self.results = wrapped_allocator.allocator().alloc(Result, self.bound) catch return ModuleError.OutOfMemory;
|
|
errdefer allocator.free(self.results);
|
|
|
|
for (self.results) |*result| {
|
|
result.* = Result.init();
|
|
}
|
|
errdefer {
|
|
for (self.results) |*result| {
|
|
result.deinit(allocator);
|
|
}
|
|
}
|
|
|
|
_ = self.it.skip(); // Skip schema
|
|
|
|
try self.pass(allocator); // Setup pass
|
|
try self.applyDecorations();
|
|
|
|
if (std.process.hasEnvVarConstant("SPIRV_INTERPRETER_DEBUG_LOGS")) {
|
|
var capability_set_names: std.ArrayList([]const u8) = .empty;
|
|
defer capability_set_names.deinit(allocator);
|
|
|
|
var it = self.capabilities.iterator();
|
|
while (it.next()) |cap| {
|
|
capability_set_names.append(allocator, @tagName(cap)) catch return ModuleError.OutOfMemory;
|
|
}
|
|
|
|
const capabilities = std.mem.join(allocator, ", ", capability_set_names.items) catch return ModuleError.OutOfMemory;
|
|
defer allocator.free(capabilities);
|
|
|
|
var entry_points_names = std.ArrayList([]const u8).initCapacity(allocator, self.entry_points.items.len) catch return ModuleError.OutOfMemory;
|
|
defer entry_points_names.deinit(allocator);
|
|
|
|
for (self.entry_points.items) |entry_point| {
|
|
entry_points_names.appendAssumeCapacity(entry_point.name);
|
|
}
|
|
|
|
const entry_points = std.mem.join(allocator, ", ", entry_points_names.items) catch return ModuleError.OutOfMemory;
|
|
defer allocator.free(entry_points);
|
|
|
|
std.log.scoped(.SPIRV_Interpreter).debug(
|
|
\\Loaded shader module with infos:
|
|
\\ SPIR-V version: {d}.{d}
|
|
\\ Generator: {s} (ID {d}), encoded version 0x{X}
|
|
\\ Capabilities: [{s}]
|
|
\\ Entry points: [{s}]
|
|
, .{
|
|
self.version_major,
|
|
self.version_minor,
|
|
spv.vendorName(self.generator_id),
|
|
self.generator_id,
|
|
self.generator_version,
|
|
capabilities,
|
|
entry_points,
|
|
});
|
|
}
|
|
|
|
self.needed_runtime_bytes += wrapped_allocator.total_bytes_allocated;
|
|
|
|
//@import("pretty").print(allocator, self.results, .{
|
|
// .tab_size = 4,
|
|
// .max_depth = 0,
|
|
// .struct_max_len = 0,
|
|
// .array_max_len = 0,
|
|
//}) catch return ModuleError.OutOfMemory;
|
|
|
|
return self;
|
|
}
|
|
|
|
fn checkEndiannessFromSpvMagic(magic: SpvWord) bool {
|
|
const bytes: [4]u8 = @bitCast(magic);
|
|
if (0x03 == bytes[0] and 0x02 == bytes[1] and 0x23 == bytes[2] and 0x07 == bytes[3]) {
|
|
return builtin.cpu.arch.endian() == .little;
|
|
}
|
|
if (0x07 == bytes[0] and 0x23 == bytes[1] and 0x02 == bytes[2] and 0x03 == bytes[3]) {
|
|
return builtin.cpu.arch.endian() == .big;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
fn pass(self: *Self, allocator: std.mem.Allocator) ModuleError!void {
|
|
var rt = Runtime.init(allocator, self) catch return ModuleError.OutOfMemory;
|
|
defer rt.deinit(allocator);
|
|
|
|
var wrapped_allocator: AllocatorWrapper = .{ .child_allocator = allocator };
|
|
|
|
while (rt.it.nextOrNull()) |opcode_data| {
|
|
const word_count = ((opcode_data & (~spv.SpvOpCodeMask)) >> spv.SpvWordCountShift) - 1;
|
|
const opcode = (opcode_data & spv.SpvOpCodeMask);
|
|
|
|
var it_tmp = rt.it; // Save because operations may iter on this iterator
|
|
if (std.enums.fromInt(spv.SpvOp, opcode)) |spv_op| {
|
|
if (op.SetupDispatcher.get(spv_op)) |pfn| {
|
|
pfn(wrapped_allocator.allocator(), word_count, &rt) catch return ModuleError.InvalidSpirV;
|
|
}
|
|
}
|
|
_ = it_tmp.skipN(word_count);
|
|
rt.it = it_tmp;
|
|
}
|
|
|
|
self.needed_runtime_bytes += wrapped_allocator.total_bytes_allocated;
|
|
}
|
|
|
|
fn applyDecorations(self: *Self) ModuleError!void {
|
|
for (self.results, 0..) |result, id| {
|
|
if (result.variant == null)
|
|
continue;
|
|
|
|
var set: ?usize = null;
|
|
var binding: ?usize = null;
|
|
|
|
for (result.decorations.items) |decoration| {
|
|
switch (result.variant.?) {
|
|
.Variable => |v| {
|
|
switch (v.storage_class) {
|
|
.Input => {
|
|
switch (decoration.rtype) {
|
|
.BuiltIn => self.builtins.put(
|
|
std.enums.fromInt(spv.SpvBuiltIn, decoration.literal_1) orelse return ModuleError.InvalidSpirV,
|
|
@intCast(id),
|
|
),
|
|
.Location => self.input_locations[decoration.literal_1] = @intCast(id),
|
|
else => {},
|
|
}
|
|
},
|
|
.Output => {
|
|
if (decoration.rtype == .Location)
|
|
self.output_locations[decoration.literal_1] = @intCast(id);
|
|
},
|
|
.StorageBuffer, .Uniform, .UniformConstant => {
|
|
switch (decoration.rtype) {
|
|
.Binding => binding = decoration.literal_1,
|
|
.DescriptorSet => set = decoration.literal_1,
|
|
else => {},
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
},
|
|
.Type => |t| {
|
|
switch (t) {
|
|
.Structure => |*s| {
|
|
if (decoration.rtype == .Offset) {
|
|
s.members_offsets[decoration.index] = decoration.literal_1;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
if (set != null and binding != null) {
|
|
self.bindings[set.?][binding.?] = @intCast(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
|
|
allocator.free(self.code);
|
|
for (self.entry_points.items) |entry| {
|
|
allocator.free(entry.name);
|
|
allocator.free(entry.globals);
|
|
}
|
|
self.entry_points.deinit(allocator);
|
|
|
|
for (self.extensions.items) |ext| {
|
|
allocator.free(ext);
|
|
}
|
|
self.extensions.deinit(allocator);
|
|
|
|
for (self.results) |*result| {
|
|
result.deinit(allocator);
|
|
}
|
|
allocator.free(self.results);
|
|
}
|