Compare commits

...

40 Commits

Author SHA1 Message Date
kbz_8 ce17209004 fixing runtime array access chain
Build / build (push) Successful in 1m22s
Test / build (push) Successful in 8m28s
2026-04-29 23:52:07 +02:00
kbz_8 11a59d8d7f yes 2026-04-29 23:44:55 +02:00
kbz_8 046b1c8f9e adding storage image read and writes
Build / build (push) Successful in 1m42s
Test / build (push) Successful in 8m27s
2026-04-29 01:19:48 +02:00
kbz_8 cc041c9677 adding api functions
Build / build (push) Successful in 1m37s
Test / build (push) Successful in 8m54s
2026-04-27 23:51:08 +02:00
kbz_8 9cdb683f3f adding decoration members propagation
Build / build (push) Successful in 1m40s
Test / build (push) Successful in 7m45s
2026-04-27 15:42:29 +02:00
kbz_8 5b7380eea0 yes
Build / build (push) Successful in 1m40s
Test / build (push) Successful in 8m22s
2026-04-26 03:58:59 +02:00
kbz_8 f617fe417c adding read builtin
Build / build (push) Successful in 1m45s
Test / build (push) Has been cancelled
2026-04-26 03:54:05 +02:00
kbz_8 147126c06f adding result get from location
Build / build (push) Successful in 1m46s
Test / build (push) Successful in 7m49s
2026-04-26 02:39:21 +02:00
kbz_8 ab34c3b49a Update README.md
Build / build (push) Successful in 1m40s
Test / build (push) Successful in 7m50s
2026-04-25 19:42:28 +02:00
kbz_8 ef69470183 adding C bindings
Build / build (push) Successful in 1m52s
Test / build (push) Successful in 7m57s
2026-04-25 19:30:09 +02:00
kbz_8 664ea9b92b fixing pretty
Build / build (push) Successful in 1m25s
Test / build (push) Successful in 8m10s
2026-04-18 14:10:58 +02:00
kbz_8 c61514feea yes
Build / build (push) Successful in 1m30s
Test / build (push) Successful in 7m34s
2026-04-16 02:12:13 +02:00
kbz_8 4bd688cf07 updating to Zig 0.16
Build / build (push) Failing after 32s
Test / build (push) Successful in 7m50s
2026-04-16 01:50:42 +02:00
kbz_8 9f586ae9c0 yes
Build / build (push) Successful in 1m33s
Test / build (push) Successful in 7m17s
2026-04-12 22:34:05 +02:00
kbz_8 b051c71f02 adding base image
Build / build (push) Successful in 2m5s
Test / build (push) Successful in 10m33s
2026-04-06 22:49:23 +02:00
kbz_8 85e0fe4362 adding OpPhi
Build / build (push) Successful in 5m6s
Test / build (push) Successful in 10m36s
2026-04-04 23:43:13 +02:00
kbz_8 a83a761afa yes
Build / build (push) Successful in 5m39s
Test / build (push) Successful in 11m34s
2026-04-04 03:58:39 +02:00
kbz_8 15eb36ea4d adding base atomic management
Build / build (push) Successful in 3m29s
Test / build (push) Successful in 11m30s
2026-04-02 03:33:18 +02:00
kbz_8 1c26e16eb7 adding vector shuffle
Build / build (push) Successful in 2m12s
Test / build (push) Successful in 9m42s
2026-03-31 03:28:09 +02:00
kbz_8 8072d671af adding spec constant management
Build / build (push) Successful in 2m11s
Test / build (push) Successful in 8m24s
2026-03-30 04:45:52 +02:00
kbz_8 6c8b364c7d adding msb, lsb and spec constants
Build / build (push) Successful in 2m3s
Test / build (push) Successful in 8m40s
2026-03-30 01:00:00 +02:00
kbz_8 fbaf85a849 skip CI
Build / build (push) Successful in 5m9s
Test / build (push) Successful in 10m43s
2026-03-29 00:48:33 +01:00
kbz_8 3c7054fae0 adding stride to regular arrays
Build / build (push) Successful in 6m0s
Test / build (push) Successful in 11m19s
2026-03-26 01:10:29 +01:00
kbz_8 a503f54e43 adding some opcodes
Build / build (push) Successful in 1m59s
Test / build (push) Successful in 8m50s
2026-03-24 04:34:04 +01:00
kbz_8 d1bf1c23f2 fixing bit insert and extract
Build / build (push) Successful in 4m58s
Test / build (push) Successful in 11m48s
2026-03-23 04:59:38 +01:00
kbz_8 2d0d3b40fd fixes
Build / build (push) Successful in 2m3s
Test / build (push) Successful in 8m21s
2026-03-22 04:47:59 +01:00
kbz_8 cb0cdaab36 reworking opBitcast, fixing some glsl functions
Build / build (push) Successful in 5m3s
Test / build (push) Successful in 10m46s
2026-03-21 22:30:39 +01:00
kbz_8 569d7fda01 fixing pointer syncing
Build / build (push) Successful in 2m3s
Test / build (push) Successful in 9m35s
2026-03-21 05:18:27 +01:00
kbz_8 c18deb432f yes
Build / build (push) Successful in 2m1s
Test / build (push) Successful in 8m28s
2026-03-20 16:39:06 +01:00
kbz_8 fe47277468 adding array stride to runtime arrays
Build / build (push) Successful in 5m9s
Test / build (push) Successful in 10m34s
2026-03-20 03:14:36 +01:00
kbz_8 e8a08d7885 fixing opSelect and opConstantComposite
Build / build (push) Successful in 4m53s
Test / build (push) Successful in 10m46s
2026-03-16 03:52:59 +01:00
kbz_8 9e72a6d2bc small refactor of values
Build / build (push) Successful in 1m54s
Test / build (push) Successful in 7m56s
2026-03-14 03:59:11 +01:00
kbz_8 7074345540 small improvments of
Build / build (push) Successful in 1m52s
Test / build (push) Successful in 8m23s
2026-03-12 14:49:52 +01:00
kbz_8 72faa35357 fixing example
Build / build (push) Successful in 3m15s
Test / build (push) Successful in 9m50s
2026-03-12 01:06:20 +01:00
kbz_8 7dd86b021d fixing tests failure
Build / build (push) Successful in 2m17s
Test / build (push) Successful in 9m19s
2026-03-11 12:37:08 +01:00
kbz_8 5d704dcc5f adding some opcodes
Build / build (push) Successful in 1m54s
Test / build (push) Failing after 9m37s
2026-03-11 01:28:53 +01:00
kbz_8 1a48af468d adding descriptor index to descriptor write
Build / build (push) Successful in 2m18s
Test / build (push) Successful in 8m55s
2026-03-10 17:46:38 +01:00
kbz_8 ad013d23fc ugly implementation of new runtime arrays management
Build / build (push) Successful in 56s
Test / build (push) Successful in 4m54s
2026-03-08 02:25:25 +01:00
kbz_8 16eb184808 adding SSBO unit test
Build / build (push) Successful in 1m49s
Test / build (push) Successful in 4m36s
2026-03-07 15:20:02 +01:00
kbz_8 7bf671d974 improving tests
Build / build (push) Successful in 2m31s
Test / build (push) Successful in 6m48s
2026-03-06 22:34:47 +01:00
35 changed files with 4915 additions and 1379 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: https://codeberg.org/mlugg/setup-zig@v2
- uses: actions/setup-node@v6
with:
node-version: 24
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
- uses: https://codeberg.org/mlugg/setup-zig@v2
- name: Test
run: zig build test -Dno-example=true
+1
View File
@@ -1,5 +1,6 @@
.zig-cache/
zig-out/
zig-pkg/
.gdb_history
*.o
vgcore*
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 kbz_8
Copyright (c) 2026 kbz_8
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+57 -2
View File
@@ -1,6 +1,6 @@
# SPIR-V Interpreter <a href="https://git.kbz8.me/kbz_8/SPIRV-Interpreter/actions?workflows=build.yml"><img src="https://git.kbz8.me/kbz_8/SPIRV-Interpreter/actions/workflows/build.yml/badge.svg"></a> <a href="https://git.kbz8.me/kbz_8/SPIRV-Interpreter/actions?workflows=test.yml"><img src="https://git.kbz8.me/kbz_8/SPIRV-Interpreter/actions/workflows/test.yml/badge.svg"></a>
A small footprint SPIR-V interpreter to execute SPIR-V shaders on the CPU. It is designed to be used with multiple runtimes concurrently.
A small footprint SPIR-V interpreter to execute SPIR-V shaders on the CPU. It is designed to be used with multiple runtimes concurrently and can be SIMD accelerated.
```zig
const std = @import("std");
@@ -23,9 +23,64 @@ pub fn main() !void {
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var output: [4]f32 = undefined;
try rt.readOutput(f32, output[0..output.len], try rt.getResultByName("color"));
try rt.readOutput(std.mem.asBytes(output[0..output.len]), try rt.getResultByName("color"));
std.log.info("Output: Vec4{any}", .{output});
}
std.log.info("Successfully executed", .{});
}
```
## C bindings
### Build
To build the FFI bindings just
```
zig build ffi-c --release=[fast, small, safe]
```
You can also build a shared lib using
```
zig build ffi-c --release=[fast, small, safe] -Dffi-build-static=false
```
You'll find the library in `./zig-out/lib/` and the header file in `./zig-out/include/` or in `./ffi/`.
### Example
```c
#include <stdio.h>
#include <SpirvInterpreter.h>
static const unsigned char shader_source[] = {
/* Shader bytecode */
}
int main(void)
{
SpvModule module;
SpvModuleOptions options;
options.use_simd_vectors_specializations = 1;
if(SpvInitModule(&module, (SpvWord*)shader_source, sizeof(shader_source) / 4, options) != SPV_RESULT_SUCCESS)
return -1;
SpvRuntime runtime;
if(SpvInitRuntime(&runtime, module) != SPV_RESULT_SUCCESS)
return -1;
SpvWord main_entry_index;
SpvGetEntryPointByName(runtime, "main", &main_entry_index);
SpvCallEntryPoint(runtime, main_entry_index);
float output[4];
SpvWord output_result;
SpvGetResultByName(runtime, "color", &output_result);
SpvReadOutput(runtime, (SpvByte*)output, sizeof(output), output_result);
printf("Output: Vec4[%f, %f, %f, %f]\n", output[0], output[1], output[2], output[3]);
SpvDeinitRuntime(runtime);
SpvDeinitModule(module);
return 0;
}
```
+197 -70
View File
@@ -3,129 +3,256 @@ const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const use_llvm = b.option(bool, "use-llvm", "Use LLVM backend") orelse (b.release_mode != .off);
const use_llvm = b.option(bool, "use-llvm", "use llvm") orelse (b.release_mode != .off);
const mod = b.addModule("spv", .{
const spv_mod = b.addModule("spv", .{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
const zmath = b.dependency("zmath", .{});
mod.addImport("zmath", zmath.module("root"));
spv_mod.addImport("zmath", zmath.module("root"));
const pretty = b.dependency("pretty", .{ .target = target, .optimize = optimize });
mod.addImport("pretty", pretty.module("pretty"));
const pretty = b.dependency("pretty", .{
.target = target,
.optimize = optimize,
});
spv_mod.addImport("pretty", pretty.module("pretty"));
const lib = b.addLibrary(.{
const spv_lib = b.addLibrary(.{
.name = "spirv_interpreter",
.root_module = mod,
.root_module = spv_mod,
.linkage = .dynamic,
.use_llvm = use_llvm,
});
const lib_install = b.addInstallArtifact(lib, .{});
// Zig example setup
const install_spv_lib = b.addInstallArtifact(spv_lib, .{});
const no_example = b.option(bool, "no-example", "skips example dependencies fetch") orelse false;
addSandbox(b, target, optimize, use_llvm, spv_mod, &install_spv_lib.step);
addExample(b, target, optimize, use_llvm, spv_mod, &install_spv_lib.step);
addZigTests(b, target, optimize, spv_mod, zmath);
addCffi(b, target, optimize, use_llvm, spv_mod);
addDocs(b, spv_mod);
}
if (!no_example) {
const sdl3 = b.lazyDependency("sdl3", .{ .target = target, .optimize = optimize }) orelse return;
const example_exe = b.addExecutable(.{
fn addExample(
b: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
use_llvm: bool,
spv_mod: *std.Build.Module,
install_spv_lib_step: *std.Build.Step,
) void {
const no_example = b.option(bool, "no-example", "Skip example build") orelse false;
if (!no_example and false) {
const sdl3 = b.lazyDependency("sdl3", .{
.target = target,
.optimize = optimize,
}) orelse return;
const exe = b.addExecutable(.{
.name = "spirv_interpreter_example",
.root_module = b.createModule(.{
.root_source_file = b.path("example/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "spv", .module = mod },
.{ .name = "spv", .module = spv_mod },
.{ .name = "sdl3", .module = sdl3.module("sdl3") },
//.{ .name = "pretty", .module = pretty.module("pretty") },
},
}),
.use_llvm = use_llvm,
});
const example_install = b.addInstallArtifact(example_exe, .{});
example_install.step.dependOn(&lib_install.step);
const install_exe = b.addInstallArtifact(exe, .{});
install_exe.step.dependOn(install_spv_lib_step);
const run_example = b.addRunArtifact(example_exe);
run_example.step.dependOn(&example_install.step);
const run_exe = b.addRunArtifact(exe);
run_exe.step.dependOn(&install_exe.step);
const run_example_step = b.step("example", "Run the example");
run_example_step.dependOn(&run_example.step);
const run_step = b.step("example", "Run the example");
run_step.dependOn(&run_exe.step);
const compile_shader_cmd = b.addSystemCommand(&[_][]const u8{ "nzslc", "example/shader.nzsl", "--compile=spv,spv-dis", "-o", "example" });
const compile_shader_step = b.step("example-shader", "Compiles example's shader (needs nzslc installed)");
compile_shader_step.dependOn(&compile_shader_cmd.step);
addShaderCompileStep(
b,
"example-shader",
"Compile example shader using nzslc",
"example/shader.nzsl",
"example",
);
}
}
// Zig sandbox setup
const sandbox_exe = b.addExecutable(.{
fn addSandbox(
b: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
use_llvm: bool,
spv_mod: *std.Build.Module,
install_spv_lib_step: *std.Build.Step,
) void {
const exe = b.addExecutable(.{
.name = "spirv_interpreter_sandbox",
.root_module = b.createModule(.{
.root_source_file = b.path("sandbox/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "spv", .module = mod },
//.{ .name = "pretty", .module = pretty.module("pretty") },
.{ .name = "spv", .module = spv_mod },
},
}),
.use_llvm = use_llvm,
});
const sandbox_install = b.addInstallArtifact(sandbox_exe, .{});
sandbox_install.step.dependOn(&lib_install.step);
const install_exe = b.addInstallArtifact(exe, .{});
install_exe.step.dependOn(install_spv_lib_step);
const run_sandbox = b.addRunArtifact(sandbox_exe);
run_sandbox.step.dependOn(&sandbox_install.step);
const run_exe = b.addRunArtifact(exe);
run_exe.step.dependOn(&install_exe.step);
const run_sandbox_step = b.step("sandbox", "Run the sandbox");
run_sandbox_step.dependOn(&run_sandbox.step);
const run_step = b.step("sandbox", "Run the sandbox");
run_step.dependOn(&run_exe.step);
const compile_shader_cmd = b.addSystemCommand(&[_][]const u8{ "nzslc", "sandbox/shader.nzsl", "--compile=spv,spv-dis", "-o", "sandbox" });
const compile_shader_step = b.step("sandbox-shader", "Compiles sandbox's shader (needs nzslc installed)");
compile_shader_step.dependOn(&compile_shader_cmd.step);
addShaderCompileStep(
b,
"sandbox-shader",
"Compile sandbox shader using nzslc",
"sandbox/shader.nzsl",
"sandbox",
);
}
// Zig unit tests setup
const no_test = b.option(bool, "no-test", "skips unit test dependencies fetch") orelse false;
if (!no_test) {
const nzsl = b.lazyDependency("NZSL", .{ .target = target, .optimize = optimize }) orelse return;
const lib_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("test/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "spv", .module = mod },
.{ .name = "nzsl", .module = nzsl.module("nzigsl") },
.{ .name = "zmath", .module = zmath.module("root") },
},
}),
.test_runner = .{ .path = b.path("test/test_runner.zig"), .mode = .simple },
});
const run_tests = b.addRunArtifact(lib_tests);
const test_step = b.step("test", "Run Zig unit tests");
test_step.dependOn(&run_tests.step);
}
// Docs generation
const autodoc_test = b.addObject(.{
.name = "lib",
.root_module = mod,
fn addShaderCompileStep(
b: *std.Build,
step_name: []const u8,
description: []const u8,
shader_path: []const u8,
output_dir: []const u8,
) void {
const cmd = b.addSystemCommand(&.{
"nzslc",
shader_path,
"--compile=spv,spv-dis",
"-o",
output_dir,
});
const step = b.step(step_name, description);
step.dependOn(&cmd.step);
}
fn addZigTests(
b: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
spv_mod: *std.Build.Module,
zmath: *std.Build.Dependency,
) void {
const no_test = b.option(bool, "no-test", "Skip unit test dependencies fetch") orelse false;
if (no_test) return;
const nzsl = b.lazyDependency("NZSL", .{
.target = target,
.optimize = optimize,
}) orelse return;
const tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("test/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "spv", .module = spv_mod },
.{ .name = "nzsl", .module = nzsl.module("nzigsl") },
.{ .name = "zmath", .module = zmath.module("root") },
},
}),
.test_runner = .{
.path = b.path("test/test_runner.zig"),
.mode = .simple,
},
});
const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run Zig unit tests");
test_step.dependOn(&run_tests.step);
}
fn addCffi(
b: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
use_llvm: bool,
spv_mod: *std.Build.Module,
) void {
const static_c_ffi = b.option(bool, "ffi-build-static", "Build C FFI statically") orelse true;
const c_ffi_mod = b.addModule("c_ffi_spv", .{
.root_source_file = b.path("ffi/ffi.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{ .name = "spv", .module = spv_mod },
},
});
const install_header = b.addInstallHeaderFile(
b.path("ffi/SpirvInterpreter.h"),
"SpirvInterpreter.h",
);
const c_ffi_lib = b.addLibrary(.{
.name = "spirv_interpreter_c_ffi",
.root_module = c_ffi_mod,
.linkage = if (static_c_ffi) .static else .dynamic,
.use_llvm = use_llvm,
});
const install_lib = b.addInstallArtifact(c_ffi_lib, .{});
const ffi_step = b.step("ffi-c", "Build C FFI");
ffi_step.dependOn(&install_lib.step);
ffi_step.dependOn(&install_header.step);
const c_test = b.addExecutable(.{
.name = "c_test",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
.use_llvm = use_llvm,
});
c_test.root_module.addCSourceFile(.{ .file = b.path("test_c/main.c") });
c_test.root_module.linkLibrary(c_ffi_lib);
c_test.root_module.addSystemIncludePath(b.path("ffi"));
const install_c_test = b.addInstallArtifact(c_test, .{});
install_c_test.step.dependOn(&install_lib.step);
const run_c_test = b.addRunArtifact(c_test);
run_c_test.step.dependOn(&install_c_test.step);
const test_c_step = b.step("test-c", "Run C test");
test_c_step.dependOn(&run_c_test.step);
}
fn addDocs(b: *std.Build, spv_mod: *std.Build.Module) void {
const autodoc_obj = b.addObject(.{
.name = "lib",
.root_module = spv_mod,
});
const install_docs = b.addInstallDirectory(.{
.source_dir = autodoc_test.getEmittedDocs(),
.source_dir = autodoc_obj.getEmittedDocs(),
.install_dir = .prefix,
.install_subdir = "docs",
});
const docs_step = b.step("docs", "Build and install the documentation");
const docs_step = b.step("docs", "Build and install documentation");
docs_step.dependOn(&install_docs.step);
}
+11 -11
View File
@@ -6,22 +6,22 @@
.url = "git+https://github.com/zig-gamedev/zmath.git#3a5955b2b72cd081563fbb084eff05bffd1e3fbb",
.hash = "zmath-0.11.0-dev-wjwivdMsAwD-xaLj76YHUq3t9JDH-X16xuMTmnDzqbu2",
},
.pretty = .{ // For debugging purposes
.url = "git+https://github.com/Kbz-8/pretty#117674465efd4d07d5ae9d9d8ca59c2c323a65ba",
.hash = "pretty-0.10.6-Tm65r99UAQDEJMgZysD10qE8dinBHr064fPM6YkxVPfB",
},
.NZSL = .{ // For unit tests
.url = "git+https://git.kbz8.me/kbz_8/NZigSL#5377dbdf9935b0de767f76ea4650e7aba4516b32",
.hash = "NZSL-1.1.2-N0xSVMt6AAC1ncQHA_RafnclWolDA477iTnFmZgdvxd-",
.url = "git+https://git.kbz8.me/kbz_8/NZigSL#ab95fc3734da46079fda2a4cd0f14143d92bf633",
.hash = "NZSL-1.1.2-N0xSVCR7AACeI_Wa6JPggJzy9_MPCpWC-2OHkMowwX-7",
.lazy = true,
},
.sdl3 = .{
.url = "git+https://codeberg.org/7Games/zig-sdl3?ref=v0.1.6#9c1842246c59f03f87ba59b160ca7e3d5e5ce972",
.hash = "sdl3-0.1.6-NmT1Q5sQJgCzT6hLj7WOSrwxE0Qsef1wIkDopbOOFru0",
.lazy = true,
//.sdl3 = .{
// .url = "git+https://codeberg.org/7Games/zig-sdl3?ref=v0.1.6#9c1842246c59f03f87ba59b160ca7e3d5e5ce972",
// .hash = "sdl3-0.1.6-NmT1Q5sQJgCzT6hLj7WOSrwxE0Qsef1wIkDopbOOFru0",
// .lazy = true,
//},
.pretty = .{
.url = "git+https://github.com/Kbz-8/pretty.git#f91d534d033277ca1ae7fcd598a070e8b3ddc532",
.hash = "pretty-0.10.6-Tm65r7FTAQA5BEL8tcIcF-Wp4XRC7J7BhuRI0KUnFj2X",
},
},
.minimum_zig_version = "0.15.2",
.minimum_zig_version = "0.16.0",
.paths = .{
"build.zig",
"build.zig.zon",
+12 -6
View File
@@ -30,13 +30,17 @@ pub fn main() !void {
var runner_cache: std.ArrayList(Runner) = try .initCapacity(allocator, screen_height);
defer {
for (runner_cache.items) |*runner| {
runner.rt.deinit(allocator);
allocator.free(runner.heap);
}
runner_cache.deinit(allocator);
}
for (0..screen_height) |_| {
var rt = try spv.Runtime.init(allocator, &module);
const heap = try allocator.alloc(u8, module.needed_runtime_bytes);
errdefer allocator.free(heap);
var buffer_allocator: std.heap.FixedBufferAllocator = .init(heap);
var rt = try spv.Runtime.init(buffer_allocator.allocator(), &module);
(try runner_cache.addOne(allocator)).* = .{
.allocator = allocator,
.surface = surface,
@@ -46,6 +50,7 @@ pub fn main() !void {
.time = try rt.getResultByName("time"),
.pos = try rt.getResultByName("pos"),
.res = try rt.getResultByName("res"),
.heap = heap,
};
}
@@ -105,6 +110,7 @@ const Runner = struct {
time: spv.SpvWord,
pos: spv.SpvWord,
res: spv.SpvWord,
heap: []u8,
fn runWrapper(self: *Self, y: usize, pixel_map: [*]u32, timer: f32) void {
@call(.always_inline, Self.run, .{ self, y, pixel_map, timer }) catch |err| {
@@ -122,11 +128,11 @@ const Runner = struct {
var output: [4]f32 = undefined;
for (0..screen_width) |x| {
try rt.writeInput(&.{timer}, self.time);
try rt.writeInput(&.{ @floatFromInt(screen_width), @floatFromInt(screen_height) }, self.res);
try rt.writeInput(&.{ @floatFromInt(x), @floatFromInt(y) }, self.pos);
try rt.writeInput(std.mem.asBytes(&timer), self.time);
try rt.writeInput(std.mem.asBytes(&[_]f32{ @floatFromInt(screen_width), @floatFromInt(screen_height) }), self.res);
try rt.writeInput(std.mem.asBytes(&[_]f32{ @floatFromInt(x), @floatFromInt(y) }), self.pos);
try rt.callEntryPoint(self.allocator, self.entry);
try rt.readOutput(output[0..], self.color);
try rt.readOutput(std.mem.asBytes(output[0..]), self.color);
const rgba = self.surface.mapRgba(
@intCast(@max(@min(@as(i32, @intFromFloat(output[0] * 255.0)), 255), 0)),
+508
View File
@@ -0,0 +1,508 @@
/*
Copyright (C) 2026 kbz_8 (contact@kbz8.me)
This file is part of the "SPIR-V Interpreter - FFI C Bindings" project
MIT License
Copyright (c) 2026 kbz_8
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef SPIRV_INTERPRETER_H
#define SPIRV_INTERPRETER_H
#ifndef SPV_API
#define SPV_API extern
#endif /* SPV_API */
#ifdef __cplusplus
extern "C"
{
#endif
#ifndef spirv_H
typedef enum SpvBuiltIn_ SpvBuiltIn;
typedef enum SpvDecoration_ SpvDecoration;
#endif /* spirv_H */
typedef int SpvBool;
typedef unsigned char SpvByte;
typedef unsigned long SpvWord;
typedef unsigned long SpvSize;
typedef enum
{
SPV_RESULT_SUCCESS = 0,
SPV_RESULT_DIVISION_BY_ZERO = -1,
SPV_RESULT_INVALID_ENTRY_POINT = -2,
SPV_RESULT_INVALID_SPIRV = -3,
SPV_RESULT_INVALID_VALUE_TYPE = -4,
SPV_RESULT_KILLED = -5,
SPV_RESULT_NOT_FOUND = -6,
SPV_RESULT_OUT_OF_MEMORY = -7,
SPV_RESULT_OUT_OF_BOUNDS = -8,
SPV_RESULT_TODO = -9,
SPV_RESULT_UNREACHABLE = -10,
SPV_RESULT_UNSUPPORTED_SPIRV = -11,
SPV_RESULT_UNSUPPORTED_EXTENSION = -12,
SPV_RESULT_UNSUPPORTED_ENDIANNESS = -13,
SPV_RESULT_INVALID_MAGIC = -14,
SPV_RESULT_UNKNOWN = -15,
} SpvResult;
typedef struct
{
SpvBool use_simd_vectors_specializations;
} SpvModuleOptions;
typedef struct
{
SpvWord id;
SpvSize offset;
SpvSize size;
} SpvRuntimeSpecializationEntry;
typedef enum
{
SPV_LOCATION_INPUT = 0,
SPV_LOCATION_OUTPUT = 1,
} SpvLocationType;
typedef struct
{
float x;
float y;
float z;
float w;
} SpvVec4f;
typedef struct
{
unsigned int x;
unsigned int y;
unsigned int z;
unsigned int w;
} SpvVec4u;
typedef SpvResult (*SpvReadImageFloat4_PFN)(void* driver_image, int x, int y, int z, SpvVec4f* dst);
typedef SpvResult (*SpvReadImageInt4_PFN)(void* driver_image, int x, int y, int z, SpvVec4u* dst);
typedef SpvResult (*SpvWriteImageFloat4_PFN)(void* driver_image, int x, int y, int z, SpvVec4f src);
typedef SpvResult (*SpvWriteImageInt4_PFN)(void* driver_image, int x, int y, int z, SpvVec4u src);
typedef struct
{
SpvReadImageFloat4_PFN SpvReadImageFloat4;
SpvReadImageInt4_PFN SpvReadImageInt4;
SpvWriteImageFloat4_PFN SpvWriteImageFloat4;
SpvWriteImageInt4_PFN SpvWriteImageInt4;
} SpvImageAPI;
typedef void* SpvModule;
typedef void* SpvRuntime;
SPV_API SpvResult SpvInitModule(SpvModule* module, const SpvWord* source, SpvSize source_len, SpvModuleOptions options);
SPV_API void SpvDeinitModule(SpvModule module);
SPV_API SpvResult SpvInitRuntime(SpvRuntime* runtime, SpvModule module, SpvImageAPI image_api);
SPV_API void SpvDeinitRuntime(SpvRuntime runtime);
SPV_API SpvResult SpvFlushDescriptorSets(SpvRuntime runtime);
SPV_API SpvResult SpvAddSpecializationInfo(SpvRuntime runtime, SpvRuntimeSpecializationEntry entry, const SpvByte* data, SpvSize data_size);
SPV_API SpvResult SpvGetResultByName(SpvRuntime runtime, const char* name, SpvWord* result);
SPV_API SpvResult SpvGetResultLocation(SpvRuntime runtime, SpvWord location, SpvLocationType type, SpvWord* result);
SPV_API SpvResult SpvGetEntryPointByName(SpvRuntime runtime, const char* name, SpvWord* result);
SPV_API SpvResult SpvGetResultMemorySize(SpvRuntime runtime, SpvWord result, SpvSize* size);
SPV_API SpvBool SpvHasResultDecoration(SpvRuntime runtime, SpvWord result, SpvDecoration decoration);
SPV_API SpvResult SpvCallEntryPoint(SpvRuntime runtime, SpvWord entry_point_index);
SPV_API SpvResult SpvReadOutput(SpvRuntime runtime, SpvByte* output, SpvSize output_size, SpvWord result);
SPV_API SpvResult SpvReadBuiltIn(SpvRuntime runtime, SpvByte* output, SpvSize output_size, SpvBuiltIn builtin);
SPV_API SpvResult SpvWriteInput(SpvRuntime runtime, const SpvByte* input, SpvSize input_size, SpvWord result);
SPV_API SpvResult SpvWriteBuiltIn(SpvRuntime runtime, const SpvByte* input, SpvSize input_size, SpvBuiltIn builtin);
SPV_API SpvResult SpvWriteDescriptorSet(SpvRuntime runtime, const SpvByte* input, SpvSize input_size, SpvWord set, SpvWord binding, SpvWord descriptor_index);
#ifndef spirv_H
enum SpvBuiltIn_
{
SpvBuiltInPosition = 0,
SpvBuiltInPointSize = 1,
SpvBuiltInClipDistance = 3,
SpvBuiltInCullDistance = 4,
SpvBuiltInVertexId = 5,
SpvBuiltInInstanceId = 6,
SpvBuiltInPrimitiveId = 7,
SpvBuiltInInvocationId = 8,
SpvBuiltInLayer = 9,
SpvBuiltInViewportIndex = 10,
SpvBuiltInTessLevelOuter = 11,
SpvBuiltInTessLevelInner = 12,
SpvBuiltInTessCoord = 13,
SpvBuiltInPatchVertices = 14,
SpvBuiltInFragCoord = 15,
SpvBuiltInPointCoord = 16,
SpvBuiltInFrontFacing = 17,
SpvBuiltInSampleId = 18,
SpvBuiltInSamplePosition = 19,
SpvBuiltInSampleMask = 20,
SpvBuiltInFragDepth = 22,
SpvBuiltInHelperInvocation = 23,
SpvBuiltInNumWorkgroups = 24,
SpvBuiltInWorkgroupSize = 25,
SpvBuiltInWorkgroupId = 26,
SpvBuiltInLocalInvocationId = 27,
SpvBuiltInGlobalInvocationId = 28,
SpvBuiltInLocalInvocationIndex = 29,
SpvBuiltInWorkDim = 30,
SpvBuiltInGlobalSize = 31,
SpvBuiltInEnqueuedWorkgroupSize = 32,
SpvBuiltInGlobalOffset = 33,
SpvBuiltInGlobalLinearId = 34,
SpvBuiltInSubgroupSize = 36,
SpvBuiltInSubgroupMaxSize = 37,
SpvBuiltInNumSubgroups = 38,
SpvBuiltInNumEnqueuedSubgroups = 39,
SpvBuiltInSubgroupId = 40,
SpvBuiltInSubgroupLocalInvocationId = 41,
SpvBuiltInVertexIndex = 42,
SpvBuiltInInstanceIndex = 43,
SpvBuiltInCoreIDARM = 4160,
SpvBuiltInCoreCountARM = 4161,
SpvBuiltInCoreMaxIDARM = 4162,
SpvBuiltInWarpIDARM = 4163,
SpvBuiltInWarpMaxIDARM = 4164,
SpvBuiltInSubgroupEqMask = 4416,
SpvBuiltInSubgroupEqMaskKHR = 4416,
SpvBuiltInSubgroupGeMask = 4417,
SpvBuiltInSubgroupGeMaskKHR = 4417,
SpvBuiltInSubgroupGtMask = 4418,
SpvBuiltInSubgroupGtMaskKHR = 4418,
SpvBuiltInSubgroupLeMask = 4419,
SpvBuiltInSubgroupLeMaskKHR = 4419,
SpvBuiltInSubgroupLtMask = 4420,
SpvBuiltInSubgroupLtMaskKHR = 4420,
SpvBuiltInBaseVertex = 4424,
SpvBuiltInBaseInstance = 4425,
SpvBuiltInDrawIndex = 4426,
SpvBuiltInPrimitiveShadingRateKHR = 4432,
SpvBuiltInDeviceIndex = 4438,
SpvBuiltInViewIndex = 4440,
SpvBuiltInShadingRateKHR = 4444,
SpvBuiltInTileOffsetQCOM = 4492,
SpvBuiltInTileDimensionQCOM = 4493,
SpvBuiltInTileApronSizeQCOM = 4494,
SpvBuiltInBaryCoordNoPerspAMD = 4992,
SpvBuiltInBaryCoordNoPerspCentroidAMD = 4993,
SpvBuiltInBaryCoordNoPerspSampleAMD = 4994,
SpvBuiltInBaryCoordSmoothAMD = 4995,
SpvBuiltInBaryCoordSmoothCentroidAMD = 4996,
SpvBuiltInBaryCoordSmoothSampleAMD = 4997,
SpvBuiltInBaryCoordPullModelAMD = 4998,
SpvBuiltInFragStencilRefEXT = 5014,
SpvBuiltInRemainingRecursionLevelsAMDX = 5021,
SpvBuiltInShaderIndexAMDX = 5073,
SpvBuiltInSamplerHeapEXT = 5122,
SpvBuiltInResourceHeapEXT = 5123,
SpvBuiltInViewportMaskNV = 5253,
SpvBuiltInSecondaryPositionNV = 5257,
SpvBuiltInSecondaryViewportMaskNV = 5258,
SpvBuiltInPositionPerViewNV = 5261,
SpvBuiltInViewportMaskPerViewNV = 5262,
SpvBuiltInFullyCoveredEXT = 5264,
SpvBuiltInTaskCountNV = 5274,
SpvBuiltInPrimitiveCountNV = 5275,
SpvBuiltInPrimitiveIndicesNV = 5276,
SpvBuiltInClipDistancePerViewNV = 5277,
SpvBuiltInCullDistancePerViewNV = 5278,
SpvBuiltInLayerPerViewNV = 5279,
SpvBuiltInMeshViewCountNV = 5280,
SpvBuiltInMeshViewIndicesNV = 5281,
SpvBuiltInBaryCoordKHR = 5286,
SpvBuiltInBaryCoordNV = 5286,
SpvBuiltInBaryCoordNoPerspKHR = 5287,
SpvBuiltInBaryCoordNoPerspNV = 5287,
SpvBuiltInFragSizeEXT = 5292,
SpvBuiltInFragmentSizeNV = 5292,
SpvBuiltInFragInvocationCountEXT = 5293,
SpvBuiltInInvocationsPerPixelNV = 5293,
SpvBuiltInPrimitivePointIndicesEXT = 5294,
SpvBuiltInPrimitiveLineIndicesEXT = 5295,
SpvBuiltInPrimitiveTriangleIndicesEXT = 5296,
SpvBuiltInCullPrimitiveEXT = 5299,
SpvBuiltInLaunchIdKHR = 5319,
SpvBuiltInLaunchIdNV = 5319,
SpvBuiltInLaunchSizeKHR = 5320,
SpvBuiltInLaunchSizeNV = 5320,
SpvBuiltInWorldRayOriginKHR = 5321,
SpvBuiltInWorldRayOriginNV = 5321,
SpvBuiltInWorldRayDirectionKHR = 5322,
SpvBuiltInWorldRayDirectionNV = 5322,
SpvBuiltInObjectRayOriginKHR = 5323,
SpvBuiltInObjectRayOriginNV = 5323,
SpvBuiltInObjectRayDirectionKHR = 5324,
SpvBuiltInObjectRayDirectionNV = 5324,
SpvBuiltInRayTminKHR = 5325,
SpvBuiltInRayTminNV = 5325,
SpvBuiltInRayTmaxKHR = 5326,
SpvBuiltInRayTmaxNV = 5326,
SpvBuiltInInstanceCustomIndexKHR = 5327,
SpvBuiltInInstanceCustomIndexNV = 5327,
SpvBuiltInObjectToWorldKHR = 5330,
SpvBuiltInObjectToWorldNV = 5330,
SpvBuiltInWorldToObjectKHR = 5331,
SpvBuiltInWorldToObjectNV = 5331,
SpvBuiltInHitTNV = 5332,
SpvBuiltInHitKindKHR = 5333,
SpvBuiltInHitKindNV = 5333,
SpvBuiltInCurrentRayTimeNV = 5334,
SpvBuiltInHitTriangleVertexPositionsKHR = 5335,
SpvBuiltInHitMicroTriangleVertexPositionsNV = 5337,
SpvBuiltInHitMicroTriangleVertexBarycentricsNV = 5344,
SpvBuiltInIncomingRayFlagsKHR = 5351,
SpvBuiltInIncomingRayFlagsNV = 5351,
SpvBuiltInRayGeometryIndexKHR = 5352,
SpvBuiltInHitIsSphereNV = 5359,
SpvBuiltInHitIsLSSNV = 5360,
SpvBuiltInHitSpherePositionNV = 5361,
SpvBuiltInWarpsPerSMNV = 5374,
SpvBuiltInSMCountNV = 5375,
SpvBuiltInWarpIDNV = 5376,
SpvBuiltInSMIDNV = 5377,
SpvBuiltInHitLSSPositionsNV = 5396,
SpvBuiltInHitKindFrontFacingMicroTriangleNV = 5405,
SpvBuiltInHitKindBackFacingMicroTriangleNV = 5406,
SpvBuiltInHitSphereRadiusNV = 5420,
SpvBuiltInHitLSSRadiiNV = 5421,
SpvBuiltInClusterIDNV = 5436,
SpvBuiltInCullMaskKHR = 6021,
SpvBuiltInMax = 0x7fffffff,
};
typedef enum SpvDecoration_ {
SpvDecorationRelaxedPrecision = 0,
SpvDecorationSpecId = 1,
SpvDecorationBlock = 2,
SpvDecorationBufferBlock = 3,
SpvDecorationRowMajor = 4,
SpvDecorationColMajor = 5,
SpvDecorationArrayStride = 6,
SpvDecorationMatrixStride = 7,
SpvDecorationGLSLShared = 8,
SpvDecorationGLSLPacked = 9,
SpvDecorationCPacked = 10,
SpvDecorationBuiltIn = 11,
SpvDecorationNoPerspective = 13,
SpvDecorationFlat = 14,
SpvDecorationPatch = 15,
SpvDecorationCentroid = 16,
SpvDecorationSample = 17,
SpvDecorationInvariant = 18,
SpvDecorationRestrict = 19,
SpvDecorationAliased = 20,
SpvDecorationVolatile = 21,
SpvDecorationConstant = 22,
SpvDecorationCoherent = 23,
SpvDecorationNonWritable = 24,
SpvDecorationNonReadable = 25,
SpvDecorationUniform = 26,
SpvDecorationUniformId = 27,
SpvDecorationSaturatedConversion = 28,
SpvDecorationStream = 29,
SpvDecorationLocation = 30,
SpvDecorationComponent = 31,
SpvDecorationIndex = 32,
SpvDecorationBinding = 33,
SpvDecorationDescriptorSet = 34,
SpvDecorationOffset = 35,
SpvDecorationXfbBuffer = 36,
SpvDecorationXfbStride = 37,
SpvDecorationFuncParamAttr = 38,
SpvDecorationFPRoundingMode = 39,
SpvDecorationFPFastMathMode = 40,
SpvDecorationLinkageAttributes = 41,
SpvDecorationNoContraction = 42,
SpvDecorationInputAttachmentIndex = 43,
SpvDecorationAlignment = 44,
SpvDecorationMaxByteOffset = 45,
SpvDecorationAlignmentId = 46,
SpvDecorationMaxByteOffsetId = 47,
SpvDecorationSaturatedToLargestFloat8NormalConversionEXT = 4216,
SpvDecorationNoSignedWrap = 4469,
SpvDecorationNoUnsignedWrap = 4470,
SpvDecorationWeightTextureQCOM = 4487,
SpvDecorationBlockMatchTextureQCOM = 4488,
SpvDecorationBlockMatchSamplerQCOM = 4499,
SpvDecorationExplicitInterpAMD = 4999,
SpvDecorationNodeSharesPayloadLimitsWithAMDX = 5019,
SpvDecorationNodeMaxPayloadsAMDX = 5020,
SpvDecorationTrackFinishWritingAMDX = 5078,
SpvDecorationPayloadNodeNameAMDX = 5091,
SpvDecorationPayloadNodeBaseIndexAMDX = 5098,
SpvDecorationPayloadNodeSparseArrayAMDX = 5099,
SpvDecorationPayloadNodeArraySizeAMDX = 5100,
SpvDecorationPayloadDispatchIndirectAMDX = 5105,
SpvDecorationArrayStrideIdEXT = 5124,
SpvDecorationOffsetIdEXT = 5125,
SpvDecorationUTFEncodedKHR = 5145,
SpvDecorationOverrideCoverageNV = 5248,
SpvDecorationPassthroughNV = 5250,
SpvDecorationViewportRelativeNV = 5252,
SpvDecorationSecondaryViewportRelativeNV = 5256,
SpvDecorationPerPrimitiveEXT = 5271,
SpvDecorationPerPrimitiveNV = 5271,
SpvDecorationPerViewNV = 5272,
SpvDecorationPerTaskNV = 5273,
SpvDecorationPerVertexKHR = 5285,
SpvDecorationPerVertexNV = 5285,
SpvDecorationNonUniform = 5300,
SpvDecorationNonUniformEXT = 5300,
SpvDecorationRestrictPointer = 5355,
SpvDecorationRestrictPointerEXT = 5355,
SpvDecorationAliasedPointer = 5356,
SpvDecorationAliasedPointerEXT = 5356,
SpvDecorationMemberOffsetNV = 5358,
SpvDecorationHitObjectShaderRecordBufferNV = 5386,
SpvDecorationHitObjectShaderRecordBufferEXT = 5389,
SpvDecorationBankNV = 5397,
SpvDecorationBindlessSamplerNV = 5398,
SpvDecorationBindlessImageNV = 5399,
SpvDecorationBoundSamplerNV = 5400,
SpvDecorationBoundImageNV = 5401,
SpvDecorationSIMTCallINTEL = 5599,
SpvDecorationReferencedIndirectlyINTEL = 5602,
SpvDecorationClobberINTEL = 5607,
SpvDecorationSideEffectsINTEL = 5608,
SpvDecorationVectorComputeVariableINTEL = 5624,
SpvDecorationFuncParamIOKindINTEL = 5625,
SpvDecorationVectorComputeFunctionINTEL = 5626,
SpvDecorationStackCallINTEL = 5627,
SpvDecorationGlobalVariableOffsetINTEL = 5628,
SpvDecorationCounterBuffer = 5634,
SpvDecorationHlslCounterBufferGOOGLE = 5634,
SpvDecorationHlslSemanticGOOGLE = 5635,
SpvDecorationUserSemantic = 5635,
SpvDecorationUserTypeGOOGLE = 5636,
SpvDecorationFunctionRoundingModeINTEL = 5822,
SpvDecorationFunctionDenormModeINTEL = 5823,
SpvDecorationRegisterALTERA = 5825,
SpvDecorationRegisterINTEL = 5825,
SpvDecorationMemoryALTERA = 5826,
SpvDecorationMemoryINTEL = 5826,
SpvDecorationNumbanksALTERA = 5827,
SpvDecorationNumbanksINTEL = 5827,
SpvDecorationBankwidthALTERA = 5828,
SpvDecorationBankwidthINTEL = 5828,
SpvDecorationMaxPrivateCopiesALTERA = 5829,
SpvDecorationMaxPrivateCopiesINTEL = 5829,
SpvDecorationSinglepumpALTERA = 5830,
SpvDecorationSinglepumpINTEL = 5830,
SpvDecorationDoublepumpALTERA = 5831,
SpvDecorationDoublepumpINTEL = 5831,
SpvDecorationMaxReplicatesALTERA = 5832,
SpvDecorationMaxReplicatesINTEL = 5832,
SpvDecorationSimpleDualPortALTERA = 5833,
SpvDecorationSimpleDualPortINTEL = 5833,
SpvDecorationMergeALTERA = 5834,
SpvDecorationMergeINTEL = 5834,
SpvDecorationBankBitsALTERA = 5835,
SpvDecorationBankBitsINTEL = 5835,
SpvDecorationForcePow2DepthALTERA = 5836,
SpvDecorationForcePow2DepthINTEL = 5836,
SpvDecorationStridesizeALTERA = 5883,
SpvDecorationStridesizeINTEL = 5883,
SpvDecorationWordsizeALTERA = 5884,
SpvDecorationWordsizeINTEL = 5884,
SpvDecorationTrueDualPortALTERA = 5885,
SpvDecorationTrueDualPortINTEL = 5885,
SpvDecorationBurstCoalesceALTERA = 5899,
SpvDecorationBurstCoalesceINTEL = 5899,
SpvDecorationCacheSizeALTERA = 5900,
SpvDecorationCacheSizeINTEL = 5900,
SpvDecorationDontStaticallyCoalesceALTERA = 5901,
SpvDecorationDontStaticallyCoalesceINTEL = 5901,
SpvDecorationPrefetchALTERA = 5902,
SpvDecorationPrefetchINTEL = 5902,
SpvDecorationStallEnableALTERA = 5905,
SpvDecorationStallEnableINTEL = 5905,
SpvDecorationFuseLoopsInFunctionALTERA = 5907,
SpvDecorationFuseLoopsInFunctionINTEL = 5907,
SpvDecorationMathOpDSPModeALTERA = 5909,
SpvDecorationMathOpDSPModeINTEL = 5909,
SpvDecorationAliasScopeINTEL = 5914,
SpvDecorationNoAliasINTEL = 5915,
SpvDecorationInitiationIntervalALTERA = 5917,
SpvDecorationInitiationIntervalINTEL = 5917,
SpvDecorationMaxConcurrencyALTERA = 5918,
SpvDecorationMaxConcurrencyINTEL = 5918,
SpvDecorationPipelineEnableALTERA = 5919,
SpvDecorationPipelineEnableINTEL = 5919,
SpvDecorationBufferLocationALTERA = 5921,
SpvDecorationBufferLocationINTEL = 5921,
SpvDecorationIOPipeStorageALTERA = 5944,
SpvDecorationIOPipeStorageINTEL = 5944,
SpvDecorationFunctionFloatingPointModeINTEL = 6080,
SpvDecorationSingleElementVectorINTEL = 6085,
SpvDecorationVectorComputeCallableFunctionINTEL = 6087,
SpvDecorationMediaBlockIOINTEL = 6140,
SpvDecorationStallFreeALTERA = 6151,
SpvDecorationStallFreeINTEL = 6151,
SpvDecorationFPMaxErrorDecorationINTEL = 6170,
SpvDecorationLatencyControlLabelALTERA = 6172,
SpvDecorationLatencyControlLabelINTEL = 6172,
SpvDecorationLatencyControlConstraintALTERA = 6173,
SpvDecorationLatencyControlConstraintINTEL = 6173,
SpvDecorationConduitKernelArgumentALTERA = 6175,
SpvDecorationConduitKernelArgumentINTEL = 6175,
SpvDecorationRegisterMapKernelArgumentALTERA = 6176,
SpvDecorationRegisterMapKernelArgumentINTEL = 6176,
SpvDecorationMMHostInterfaceAddressWidthALTERA = 6177,
SpvDecorationMMHostInterfaceAddressWidthINTEL = 6177,
SpvDecorationMMHostInterfaceDataWidthALTERA = 6178,
SpvDecorationMMHostInterfaceDataWidthINTEL = 6178,
SpvDecorationMMHostInterfaceLatencyALTERA = 6179,
SpvDecorationMMHostInterfaceLatencyINTEL = 6179,
SpvDecorationMMHostInterfaceReadWriteModeALTERA = 6180,
SpvDecorationMMHostInterfaceReadWriteModeINTEL = 6180,
SpvDecorationMMHostInterfaceMaxBurstALTERA = 6181,
SpvDecorationMMHostInterfaceMaxBurstINTEL = 6181,
SpvDecorationMMHostInterfaceWaitRequestALTERA = 6182,
SpvDecorationMMHostInterfaceWaitRequestINTEL = 6182,
SpvDecorationStableKernelArgumentALTERA = 6183,
SpvDecorationStableKernelArgumentINTEL = 6183,
SpvDecorationHostAccessINTEL = 6188,
SpvDecorationInitModeALTERA = 6190,
SpvDecorationInitModeINTEL = 6190,
SpvDecorationImplementInRegisterMapALTERA = 6191,
SpvDecorationImplementInRegisterMapINTEL = 6191,
SpvDecorationConditionalINTEL = 6247,
SpvDecorationCacheControlLoadINTEL = 6442,
SpvDecorationCacheControlStoreINTEL = 6443,
SpvDecorationMax = 0x7fffffff,
} SpvDecoration;
#endif /* spirv_H */
#ifdef __cplusplus
}
#endif
#endif /* SPIRV_INTERPRETER_H */
+29
View File
@@ -0,0 +1,29 @@
pub const spv = @import("spv");
pub const SpvCBool = c_int;
pub const SpvCWord = c_ulong;
pub const SpvCSize = c_ulong;
pub const Result = enum(c_int) {
Success = 0,
DivisionByZero = -1,
InvalidEntryPoint = -2,
InvalidSpirV = -3,
InvalidValueType = -4,
Killed = -5,
NotFound = -6,
OutOfMemory = -7,
OutOfBounds = -8,
ToDo = -9,
Unreachable = -10,
UnsupportedSpirV = -11,
UnsupportedExtension = -12,
UnsupportedEndianness = -13,
InvalidMagic = -14,
Unknown = -15,
};
comptime {
_ = @import("module.zig");
_ = @import("runtime.zig");
}
+42
View File
@@ -0,0 +1,42 @@
const std = @import("std");
const ffi = @import("ffi.zig");
const spv = ffi.spv;
const Options = extern struct {
use_simd_vectors_specializations: ffi.SpvCBool,
};
fn toCResult(err: spv.Module.ModuleError) ffi.Result {
return switch (err) {
spv.Module.ModuleError.InvalidSpirV => ffi.Result.InvalidSpirV,
spv.Module.ModuleError.InvalidMagic => ffi.Result.InvalidMagic,
spv.Module.ModuleError.UnsupportedEndianness => ffi.Result.UnsupportedEndianness,
spv.Module.ModuleError.UnsupportedExtension => ffi.Result.UnsupportedExtension,
spv.Module.ModuleError.OutOfMemory => ffi.Result.OutOfMemory,
};
}
export fn SpvInitModule(module: **spv.Module, source: [*]const ffi.SpvCWord, source_len: ffi.SpvCSize, options: Options) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
module.* = allocator.create(spv.Module) catch return .OutOfMemory;
const cast_source: []const u32 = @as([*]const u32, @ptrCast(source[0..source_len]))[0..source_len];
module.*.* = spv.Module.init(
allocator,
cast_source[0..source_len],
.{
.use_simd_vectors_specializations = if (options.use_simd_vectors_specializations == 0) false else true,
},
) catch |err| {
allocator.destroy(module.*);
return toCResult(err);
};
return .Success;
}
export fn SpvDeinitModule(module: *spv.Module) callconv(.c) void {
const allocator = std.heap.c_allocator;
module.deinit(allocator);
allocator.destroy(module);
}
+244
View File
@@ -0,0 +1,244 @@
const std = @import("std");
const ffi = @import("ffi.zig");
const spv = ffi.spv;
const CSpecializationEntry = extern struct {
id: spv.SpvWord,
offset: c_ulong,
size: c_ulong,
};
const LocationType = enum(c_int) {
input = 0,
output = 1,
};
const Vec4f = extern struct {
x: f32,
y: f32,
z: f32,
w: f32,
};
const Vec4u = extern struct {
x: c_uint,
y: c_uint,
z: c_uint,
w: c_uint,
};
const readImageFloat4_PFN = *const fn (driver_image: ?*anyopaque, x: c_int, y: c_int, z: c_int, dst: *Vec4f) callconv(.c) ffi.Result;
const readImageInt4_PFN = *const fn (driver_image: ?*anyopaque, x: c_int, y: c_int, z: c_int, dst: *Vec4u) callconv(.c) ffi.Result;
const writeImageFloat4_PFN = *const fn (driver_image: ?*anyopaque, x: c_int, y: c_int, z: c_int, src: Vec4f) callconv(.c) ffi.Result;
const writeImageInt4_PFN = *const fn (driver_image: ?*anyopaque, x: c_int, y: c_int, z: c_int, src: Vec4u) callconv(.c) ffi.Result;
const ImageAPI = extern struct {
readImageFloat4: readImageFloat4_PFN,
readImageInt4: readImageInt4_PFN,
writeImageFloat4: writeImageFloat4_PFN,
writeImageInt4: writeImageInt4_PFN,
};
fn toCResult(err: spv.Runtime.RuntimeError) ffi.Result {
return switch (err) {
spv.Runtime.RuntimeError.DivisionByZero => ffi.Result.DivisionByZero,
spv.Runtime.RuntimeError.InvalidEntryPoint => ffi.Result.InvalidEntryPoint,
spv.Runtime.RuntimeError.InvalidSpirV => ffi.Result.InvalidSpirV,
spv.Runtime.RuntimeError.InvalidValueType => ffi.Result.InvalidValueType,
spv.Runtime.RuntimeError.Killed => ffi.Result.Killed,
spv.Runtime.RuntimeError.NotFound => ffi.Result.NotFound,
spv.Runtime.RuntimeError.OutOfMemory => ffi.Result.OutOfMemory,
spv.Runtime.RuntimeError.OutOfBounds => ffi.Result.OutOfBounds,
spv.Runtime.RuntimeError.ToDo => ffi.Result.ToDo,
spv.Runtime.RuntimeError.Unreachable => ffi.Result.Unreachable,
spv.Runtime.RuntimeError.UnsupportedSpirV => ffi.Result.UnsupportedSpirV,
spv.Runtime.RuntimeError.UnsupportedExtension => ffi.Result.UnsupportedExtension,
spv.Runtime.RuntimeError.Unknown => ffi.Result.Unknown,
};
}
fn fromCResult(res: ffi.Result) spv.Runtime.RuntimeError!void {
return switch (res) {
ffi.Result.DivisionByZero => spv.Runtime.RuntimeError.DivisionByZero,
ffi.Result.InvalidEntryPoint => spv.Runtime.RuntimeError.InvalidEntryPoint,
ffi.Result.InvalidSpirV => spv.Runtime.RuntimeError.InvalidSpirV,
ffi.Result.InvalidValueType => spv.Runtime.RuntimeError.InvalidValueType,
ffi.Result.Killed => spv.Runtime.RuntimeError.Killed,
ffi.Result.NotFound => spv.Runtime.RuntimeError.NotFound,
ffi.Result.OutOfMemory => spv.Runtime.RuntimeError.OutOfMemory,
ffi.Result.OutOfBounds => spv.Runtime.RuntimeError.OutOfBounds,
ffi.Result.ToDo => spv.Runtime.RuntimeError.ToDo,
ffi.Result.Unreachable => spv.Runtime.RuntimeError.Unreachable,
ffi.Result.UnsupportedSpirV => spv.Runtime.RuntimeError.UnsupportedSpirV,
ffi.Result.UnsupportedExtension => spv.Runtime.RuntimeError.UnsupportedExtension,
ffi.Result.Unknown => spv.Runtime.RuntimeError.Unknown,
else => {},
};
}
const ImageAPIBridge = struct {
threadlocal var current_image_api: ?*const ImageAPI = null; // Hacky
fn getImageAPI() spv.Runtime.RuntimeError!*const ImageAPI {
return current_image_api orelse spv.Runtime.RuntimeError.Unknown;
}
fn readImageFloat4(driver_image: *anyopaque, x: i32, y: i32, z: i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(f32) {
const image_api = try getImageAPI();
var dst: Vec4f = undefined;
const result = image_api.readImageFloat4(driver_image, @intCast(x), @intCast(y), @intCast(z), &dst);
try fromCResult(result);
return .{
.x = dst.x,
.y = dst.y,
.z = dst.z,
.w = dst.w,
};
}
fn readImageInt4(driver_image: *anyopaque, x: i32, y: i32, z: i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(u32) {
const image_api = try getImageAPI();
var dst: Vec4u = undefined;
const result = image_api.readImageInt4(driver_image, @intCast(x), @intCast(y), @intCast(z), &dst);
try fromCResult(result);
return .{
.x = dst.x,
.y = dst.y,
.z = dst.z,
.w = dst.w,
};
}
fn writeImageFloat4(driver_image: *anyopaque, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(f32)) spv.Runtime.RuntimeError!void {
const image_api = try getImageAPI();
const result = image_api.writeImageFloat4(driver_image, @intCast(x), @intCast(y), @intCast(z), .{ .x = pixel.x, .y = pixel.y, .z = pixel.z, .w = pixel.w });
try fromCResult(result);
}
fn writeImageInt4(driver_image: *anyopaque, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(u32)) spv.Runtime.RuntimeError!void {
const image_api = try getImageAPI();
const result = image_api.writeImageInt4(driver_image, @intCast(x), @intCast(y), @intCast(z), .{ .x = pixel.x, .y = pixel.y, .z = pixel.z, .w = pixel.w });
try fromCResult(result);
}
};
const RuntimeWrapper = struct {
rt: spv.Runtime,
image_api: ImageAPI,
};
export fn SpvInitRuntime(rt: **RuntimeWrapper, module: *spv.Module, image_api: ImageAPI) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
rt.* = allocator.create(RuntimeWrapper) catch return .OutOfMemory;
rt.*.image_api = image_api;
rt.*.rt = spv.Runtime.init(
allocator,
module,
.{
.readImageFloat4 = ImageAPIBridge.readImageFloat4,
.readImageInt4 = ImageAPIBridge.readImageInt4,
.writeImageFloat4 = ImageAPIBridge.writeImageFloat4,
.writeImageInt4 = ImageAPIBridge.writeImageInt4,
},
) catch |err| {
allocator.destroy(rt.*);
return toCResult(err);
};
return .Success;
}
export fn SpvDeinitRuntime(rt: *RuntimeWrapper) callconv(.c) void {
const allocator = std.heap.c_allocator;
rt.rt.deinit(allocator);
allocator.destroy(rt);
}
export fn SpvAddSpecializationInfo(rt: *RuntimeWrapper, entry: CSpecializationEntry, data: [*]const u8, data_size: c_ulong) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
rt.rt.addSpecializationInfo(
allocator,
.{
.id = entry.id,
.offset = @intCast(entry.offset),
.size = @intCast(entry.size),
},
data[0..data_size],
) catch |err| return toCResult(err);
return .Success;
}
export fn SpvGetEntryPointByName(rt: *RuntimeWrapper, name: [*:0]const u8, result: *spv.SpvWord) callconv(.c) ffi.Result {
result.* = rt.rt.getEntryPointByName(std.mem.span(name)) catch |err| return toCResult(err);
return .Success;
}
export fn SpvGetResultByLocation(rt: *RuntimeWrapper, location: spv.SpvWord, kind: LocationType, result: *spv.SpvWord) callconv(.c) ffi.Result {
result.* = rt.rt.getResultByLocation(location, switch (kind) {
.input => .input,
.output => .output,
}) catch |err| return toCResult(err);
return .Success;
}
export fn SpvGetResultByName(rt: *RuntimeWrapper, name: [*:0]const u8, result: *spv.SpvWord) callconv(.c) ffi.Result {
result.* = rt.rt.getResultByName(std.mem.span(name)) catch |err| return toCResult(err);
return .Success;
}
export fn SpvGetResultMemorySize(rt: *RuntimeWrapper, result: spv.SpvWord, size: *c_ulong) callconv(.c) ffi.Result {
size.* = rt.rt.getResultMemorySize(result) catch |err| return toCResult(err);
return .Success;
}
export fn SpvHasResultDecoration(rt: *RuntimeWrapper, result: spv.SpvWord, decoration: spv.spv.SpvDecoration) callconv(.c) c_int {
return if (rt.rt.hasResultDecoration(result, decoration)) 1 else 0;
}
export fn SpvCallEntryPoint(rt: *RuntimeWrapper, entry_point: spv.SpvWord) callconv(.c) ffi.Result {
const allocator = std.heap.c_allocator;
// Ultra hacky
const previous_image_api = ImageAPIBridge.current_image_api;
ImageAPIBridge.current_image_api = &rt.image_api;
defer ImageAPIBridge.current_image_api = previous_image_api;
rt.rt.callEntryPoint(allocator, entry_point) catch |err| return toCResult(err);
return .Success;
}
export fn SpvReadOutput(rt: *RuntimeWrapper, output: [*]u8, output_size: c_ulong, result: spv.SpvWord) callconv(.c) ffi.Result {
rt.rt.readOutput(output[0..output_size], result) catch |err| return toCResult(err);
return .Success;
}
export fn SpvReadBuiltIn(rt: *RuntimeWrapper, output: [*]u8, output_size: c_ulong, builtin: spv.spv.SpvBuiltIn) callconv(.c) ffi.Result {
rt.rt.readBuiltIn(output[0..output_size], builtin) catch |err| return toCResult(err);
return .Success;
}
export fn SpvWriteInput(rt: *RuntimeWrapper, input: [*]const u8, input_size: c_ulong, result: spv.SpvWord) callconv(.c) ffi.Result {
rt.rt.writeInput(input[0..input_size], result) catch |err| return toCResult(err);
return .Success;
}
export fn SpvWriteBuiltIn(rt: *RuntimeWrapper, input: [*]const u8, input_size: c_ulong, builtin: spv.spv.SpvBuiltIn) callconv(.c) ffi.Result {
rt.rt.writeBuiltIn(input[0..input_size], builtin) catch |err| return toCResult(err);
return .Success;
}
export fn SpvWriteDescriptorSet(rt: *RuntimeWrapper, input: [*]const u8, input_size: c_ulong, set: spv.SpvWord, binding: spv.SpvWord, descriptor_index: spv.SpvWord) callconv(.c) ffi.Result {
rt.rt.writeDescriptorSet(input[0..input_size], set, binding, descriptor_index) catch |err| return toCResult(err);
return .Success;
}
+3 -2
View File
@@ -26,6 +26,8 @@ pub fn main() !void {
var ssbo: SSBO = .{};
try rt.writeDescriptorSet(std.mem.asBytes(&ssbo), 0, 0, 0);
for (0..16) |i| {
for (0..16) |x| {
const global_invocation_indices = [3]i32{
@@ -35,12 +37,11 @@ pub fn main() !void {
};
try rt.writeBuiltIn(std.mem.asBytes(&global_invocation_indices), .GlobalInvocationId);
try rt.writeDescriptorSet(allocator, std.mem.asBytes(&ssbo), 0, 0);
rt.callEntryPoint(allocator, entry) catch |err| switch (err) {
spv.Runtime.RuntimeError.OutOfBounds => continue,
else => return err,
};
try rt.readDescriptorSet(std.mem.asBytes(&ssbo), 0, 0);
try rt.flushDescriptorSets(allocator);
}
}
+161 -44
View File
@@ -8,18 +8,18 @@ const Module = @import("../Module.zig");
const Runtime = @import("../Runtime.zig");
const Result = @import("../Result.zig");
const WordIterator = @import("../WordIterator.zig");
const value_ns = @import("../Value.zig");
const RuntimeError = Runtime.RuntimeError;
const ValueType = opc.ValueType;
const getValuePrimitiveField = opc.getValuePrimitiveField;
const getValuePrimitiveFieldType = opc.getValuePrimitiveFieldType;
const SpvVoid = spv.SpvVoid;
const SpvByte = spv.SpvByte;
const SpvWord = spv.SpvWord;
const SpvBool = spv.SpvBool;
const Value = value_ns.Value;
const PrimitiveType = value_ns.PrimitiveType;
const MathOp = enum {
Acos,
Acosh,
@@ -66,6 +66,12 @@ const MathOp = enum {
UMin,
};
const IntBitOp = enum {
FindILsb,
FindSMsb,
FindUMsb,
};
pub const OpCodeExtFunc = opc.OpCodeExtFunc;
/// Not an EnumMap as it is way too slow for this purpose
@@ -79,6 +85,7 @@ pub fn initRuntimeDispatcher() void {
runtime_dispatcher[@intFromEnum(ext.GLSLOp.Exp2)] = MathEngine(.Float, .Exp2).opSingleOperator;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.FAbs)] = MathEngine(.Float, .FAbs).opSingleOperator;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.FMax)] = MathEngine(.Float, .FMax).opDoubleOperators;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.FSign)] = MathEngine(.Float, .FSign).opSingleOperator;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.Floor)] = MathEngine(.Float, .Floor).opSingleOperator;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.Length)] = opLength;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.Log)] = MathEngine(.Float, .Log).opSingleOperator;
@@ -86,14 +93,26 @@ pub fn initRuntimeDispatcher() void {
runtime_dispatcher[@intFromEnum(ext.GLSLOp.Normalize)] = opNormalize;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.Round)] = MathEngine(.Float, .Round).opSingleOperator;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.SAbs)] = MathEngine(.SInt, .SAbs).opSingleOperator;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.SSign)] = MathEngine(.SInt, .SSign).opSingleOperator;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.Sin)] = MathEngine(.Float, .Sin).opSingleOperator;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.Sqrt)] = MathEngine(.Float, .Sqrt).opSingleOperator;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.Tan)] = MathEngine(.Float, .Tan).opSingleOperator;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.Trunc)] = MathEngine(.Float, .Trunc).opSingleOperator;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.FindILsb)] = IntBitEngine(.FindILsb).op;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.FindSMsb)] = IntBitEngine(.FindSMsb).op;
runtime_dispatcher[@intFromEnum(ext.GLSLOp.FindUMsb)] = IntBitEngine(.FindUMsb).op;
// zig fmt: on
}
fn MathEngine(comptime T: ValueType, comptime Op: MathOp) type {
fn isFloatOrF32Vector(comptime T: type) bool {
return switch (@typeInfo(T)) {
.float => true,
.vector => |vec| vec.child == f32,
else => false,
};
}
fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp) type {
return struct {
fn opSingleOperator(_: std.mem.Allocator, target_type_id: SpvWord, id: SpvWord, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target_type = (try rt.results[target_type_id].getVariant()).Type;
@@ -104,33 +123,41 @@ fn MathEngine(comptime T: ValueType, comptime Op: MathOp) type {
const operator = struct {
fn operation(comptime TT: type, x: TT) RuntimeError!TT {
return switch (Op) {
.Ceil => @ceil(x),
.Cos => @cos(x),
.Exp => @exp(x),
.Exp2 => @exp2(x),
.FAbs => @abs(x),
.Floor => @floor(x),
.Log => @log(x),
.Log2 => @log2(x),
.Round => @round(x),
.SAbs => if (comptime @typeInfo(TT) == .int) @intCast(@abs(x)) else return RuntimeError.InvalidSpirV,
.Sin => @sin(x),
.Sqrt => @sqrt(x),
.Tan => @tan(x),
.Trunc => @trunc(x),
else => RuntimeError.InvalidSpirV,
};
if (comptime isFloatOrF32Vector(TT)) {
return switch (Op) {
.Ceil => @ceil(x),
.Cos => @cos(x),
.Exp => @exp(x),
.Exp2 => @exp2(x),
.FAbs => @abs(x),
.FSign => std.math.sign(x),
.Floor => @floor(x),
.Log => @log(x),
.Log2 => @log2(x),
.Round => @round(x),
.Sin => @sin(x),
.Sqrt => @sqrt(x),
.Tan => @tan(x),
.Trunc => @trunc(x),
else => return RuntimeError.InvalidSpirV,
};
} else {
return switch (Op) {
.SAbs => @intCast(@abs(x)),
.SSign => std.math.sign(x),
else => RuntimeError.InvalidSpirV,
};
}
}
fn applyScalar(bit_count: SpvWord, d: *Result.Value, s: *const Result.Value) RuntimeError!void {
fn applyScalar(bit_count: SpvWord, d: *Value, s: *const Value) RuntimeError!void {
switch (bit_count) {
inline 8, 16, 32, 64 => |bits| {
if (bits == 8 and T == .Float) return RuntimeError.InvalidSpirV;
const ScalarT = getValuePrimitiveFieldType(T, bits);
const d_field = try getValuePrimitiveField(T, bits, d);
const s_field = try getValuePrimitiveField(T, bits, @constCast(s));
const ScalarT = Value.getPrimitiveFieldType(T, bits);
const d_field = try Value.getPrimitiveField(T, bits, d);
const s_field = try Value.getPrimitiveField(T, bits, @constCast(s));
d_field.* = try operation(ScalarT, s_field.*);
},
else => return RuntimeError.InvalidSpirV,
@@ -149,13 +176,13 @@ fn MathEngine(comptime T: ValueType, comptime Op: MathOp) type {
.Vector3f32 => |*d| d.* = try operator.operation(@Vector(3, f32), src.Vector3f32),
.Vector2f32 => |*d| d.* = try operator.operation(@Vector(2, f32), src.Vector2f32),
//.Vector4i32 => |*d| d.* = try operator.operation(@Vector(4, i32), src.Vector4i32),
//.Vector3i32 => |*d| d.* = try operator.operation(@Vector(3, i32), src.Vector3i32),
//.Vector2i32 => |*d| d.* = try operator.operation(@Vector(2, i32), src.Vector2i32),
.Vector4i32 => |*d| d.* = try operator.operation(@Vector(4, i32), src.Vector4i32),
.Vector3i32 => |*d| d.* = try operator.operation(@Vector(3, i32), src.Vector3i32),
.Vector2i32 => |*d| d.* = try operator.operation(@Vector(2, i32), src.Vector2i32),
//.Vector4u32 => |*d| d.* = try operator.operation(@Vector(4, u32), src.Vector4u32),
//.Vector3u32 => |*d| d.* = try operator.operation(@Vector(3, u32), src.Vector3u32),
//.Vector2u32 => |*d| d.* = try operator.operation(@Vector(2, u32), src.Vector2u32),
.Vector4u32 => |*d| d.* = try operator.operation(@Vector(4, u32), src.Vector4u32),
.Vector3u32 => |*d| d.* = try operator.operation(@Vector(3, u32), src.Vector3u32),
.Vector2u32 => |*d| d.* = try operator.operation(@Vector(2, u32), src.Vector2u32),
else => return RuntimeError.InvalidSpirV,
}
@@ -177,15 +204,15 @@ fn MathEngine(comptime T: ValueType, comptime Op: MathOp) type {
};
}
fn applyScalar(bit_count: SpvWord, d: *Result.Value, l: *const Result.Value, r: *const Result.Value) RuntimeError!void {
fn applyScalar(bit_count: SpvWord, d: *Value, l: *const Value, r: *const Value) RuntimeError!void {
switch (bit_count) {
inline 8, 16, 32, 64 => |bits| {
if (bits == 8 and T == .Float) return RuntimeError.InvalidSpirV;
const ScalarT = getValuePrimitiveFieldType(T, bits);
const d_field = try getValuePrimitiveField(T, bits, d);
const l_field = try getValuePrimitiveField(T, bits, @constCast(l));
const r_field = try getValuePrimitiveField(T, bits, @constCast(r));
const ScalarT = Value.getPrimitiveFieldType(T, bits);
const d_field = try Value.getPrimitiveField(T, bits, d);
const l_field = try Value.getPrimitiveField(T, bits, @constCast(l));
const r_field = try Value.getPrimitiveField(T, bits, @constCast(r));
d_field.* = try operation(ScalarT, l_field.*, r_field.*);
},
else => return RuntimeError.InvalidSpirV,
@@ -218,6 +245,96 @@ fn MathEngine(comptime T: ValueType, comptime Op: MathOp) type {
};
}
fn IntBitEngine(comptime op_kind: IntBitOp) type {
return struct {
inline fn findILsb32(x: u32) i32 {
if (x == 0) return -1;
return @intCast(@ctz(x));
}
inline fn findUMsb32(x: u32) i32 {
if (x == 0) return -1;
return 31 - @as(i32, @intCast(@clz(x)));
}
inline fn findSMsb32(x: i32) i32 {
if (x == 0 or x == -1) return -1;
if (x > 0) {
return findUMsb32(@bitCast(x));
}
return findUMsb32(@bitCast(~x));
}
inline fn computeSigned(x: i32) i32 {
return switch (op_kind) {
.FindILsb => findILsb32(@bitCast(x)),
.FindSMsb => findSMsb32(x),
.FindUMsb => findUMsb32(@bitCast(x)),
};
}
inline fn computeUnsigned(x: u32) u32 {
const result: i32 = switch (op_kind) {
.FindILsb => findILsb32(x),
.FindSMsb => findSMsb32(@bitCast(x)),
.FindUMsb => findUMsb32(x),
};
return @bitCast(result);
}
fn readSourceLane(src: *const Value, lane_index: usize) RuntimeError!u32 {
return switch (op_kind) {
.FindSMsb => @bitCast(try Value.readLane(.SInt, 32, src, lane_index)),
.FindILsb, .FindUMsb => try Value.readLane(.UInt, 32, src, lane_index),
};
}
fn writeDestLane(dst: *Value, lane_index: usize, bits: u32, dst_is_signed: bool) RuntimeError!void {
if (dst_is_signed) {
try Value.writeLane(.SInt, 32, dst, lane_index, @as(i32, @bitCast(bits)));
} else {
try Value.writeLane(.UInt, 32, dst, lane_index, bits);
}
}
fn apply(dst: *Value, src: *const Value, lane_count: usize, dst_is_signed: bool) RuntimeError!void {
for (0..lane_count) |lane_index| {
const src_bits = try readSourceLane(src, lane_index);
const out_bits: u32 = if (dst_is_signed)
@bitCast(computeSigned(@bitCast(src_bits)))
else
computeUnsigned(src_bits);
try writeDestLane(dst, lane_index, out_bits, dst_is_signed);
}
}
fn op(
_: std.mem.Allocator,
target_type_id: SpvWord,
id: SpvWord,
_: SpvWord,
rt: *Runtime,
) RuntimeError!void {
const target_type = (try rt.results[target_type_id].getVariant()).Type;
const dst = try rt.results[id].getValue();
const src = try rt.results[try rt.it.next()].getValue();
const lane_bits = try Result.resolveLaneBitWidth(target_type, rt);
if (lane_bits != 32)
return RuntimeError.InvalidSpirV;
const lane_count = try Result.resolveLaneCount(target_type);
const dst_sign = try Result.resolveSign(target_type, rt);
try apply(dst, src, lane_count, dst_sign == .signed);
}
};
}
fn opLength(_: std.mem.Allocator, target_type_id: SpvWord, id: SpvWord, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target_type = (try rt.results[target_type_id].getVariant()).Type;
const dst = try rt.results[id].getValue();
@@ -228,7 +345,7 @@ fn opLength(_: std.mem.Allocator, target_type_id: SpvWord, id: SpvWord, _: SpvWo
switch (lane_bits) {
inline 16, 32, 64 => |bits| {
var sum: std.meta.Float(bits) = 0.0;
const d_field = try getValuePrimitiveField(.Float, bits, dst);
const d_field = try Value.getPrimitiveField(.Float, bits, dst);
if (bits == 32) { // More likely to be SIMD if f32
switch (src.*) {
@@ -251,12 +368,12 @@ fn opLength(_: std.mem.Allocator, target_type_id: SpvWord, id: SpvWord, _: SpvWo
switch (src.*) {
.Float => {
// Fast path
const s_field = try getValuePrimitiveField(.Float, bits, src);
const s_field = try Value.getPrimitiveField(.Float, bits, src);
d_field.* = s_field.*;
return;
},
.Vector => |src_vec| for (src_vec) |*s_lane| {
const s_field = try getValuePrimitiveField(.Float, bits, s_lane);
const s_field = try Value.getPrimitiveField(.Float, bits, s_lane);
sum += s_field.*;
},
else => return RuntimeError.InvalidSpirV,
@@ -304,11 +421,11 @@ fn opNormalize(_: std.mem.Allocator, target_type_id: SpvWord, id: SpvWord, _: Sp
switch (src.*) {
.Float => {
const s_field = try getValuePrimitiveField(.Float, bits, src);
const s_field = try Value.getPrimitiveField(.Float, bits, src);
sum = s_field.*;
},
.Vector => |src_vec| for (src_vec) |*s_lane| {
const s_field = try getValuePrimitiveField(.Float, bits, s_lane);
const s_field = try Value.getPrimitiveField(.Float, bits, s_lane);
sum += s_field.*;
},
else => return RuntimeError.InvalidSpirV,
@@ -318,8 +435,8 @@ fn opNormalize(_: std.mem.Allocator, target_type_id: SpvWord, id: SpvWord, _: Sp
switch (dst.*) {
.Vector => |dst_vec| for (dst_vec, src.Vector) |*d_lane, *s_lane| {
const d_field = try getValuePrimitiveField(.Float, bits, d_lane);
const s_field = try getValuePrimitiveField(.Float, bits, s_lane);
const d_field = try Value.getPrimitiveField(.Float, bits, d_lane);
const s_field = try Value.getPrimitiveField(.Float, bits, s_lane);
d_field.* = s_field.* / sum;
},
else => return RuntimeError.InvalidSpirV,
View File
+137 -72
View File
@@ -13,10 +13,9 @@ 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 Value = Result.Value;
const Self = @This();
pub const ModuleOptions = struct {
@@ -126,53 +125,7 @@ pub fn init(allocator: std.mem.Allocator, source: []const SpvWord, options: Modu
_ = self.it.skip(); // Skip schema
try self.pass(allocator); // Setup pass
try self.populateMaps();
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,
});
}
//@import("pretty").print(allocator, self.results, .{
// .tab_size = 4,
// .max_depth = 0,
// .struct_max_len = 0,
// .array_max_len = 0,
//}) catch return ModuleError.OutOfMemory;
try self.applyDecorations();
return self;
}
@@ -189,8 +142,14 @@ fn checkEndiannessFromSpvMagic(magic: SpvWord) bool {
}
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 rt = Runtime.init(allocator, self, undefined) catch return ModuleError.OutOfMemory;
defer {
for (self.results, rt.results) |*result, new_result| {
result.deinit(allocator);
result.* = new_result.dupe(allocator) catch unreachable; // OutOfMemory if unreachable
}
rt.deinit(allocator);
}
while (rt.it.nextOrNull()) |opcode_data| {
const word_count = ((opcode_data & (~spv.SpvOpCodeMask)) >> spv.SpvWordCountShift) - 1;
@@ -207,43 +166,149 @@ fn pass(self: *Self, allocator: std.mem.Allocator) ModuleError!void {
}
}
fn populateMaps(self: *Self) ModuleError!void {
fn resolveConstantWord(self: *const Self, id: SpvWord) ?SpvWord {
if (id >= self.results.len) return null;
const variant = self.results[id].variant orelse return null;
return switch (variant) {
.Constant => |c| switch (c.value) {
.Int => |i| i.value.uint32,
else => null,
},
else => null,
};
}
fn findAccessChainToMember(self: *const Self, base_id: SpvWord, member_index: SpvWord) ?SpvWord {
for (self.results, 0..) |result, id| {
if (result.variant == null or std.meta.activeTag(result.variant.?) != .Variable)
const variant = result.variant orelse continue;
switch (variant) {
.AccessChain => |a| {
if (a.base != base_id or a.indexes.len == 0) continue;
const first_index = self.resolveConstantWord(a.indexes[0]) orelse continue;
if (first_index == member_index) return @intCast(id);
},
else => {},
}
}
return null;
}
fn applyInterfaceDecoration(
self: *Self,
storage_class: spv.SpvStorageClass,
decoration: Result.Decoration,
id: SpvWord,
) ModuleError!void {
switch (storage_class) {
.Input => switch (decoration.rtype) {
.BuiltIn => self.builtins.put(
std.enums.fromInt(spv.SpvBuiltIn, decoration.literal_1) orelse return ModuleError.InvalidSpirV,
id,
),
.Location => self.input_locations[decoration.literal_1] = id,
else => {},
},
.Output => switch (decoration.rtype) {
.BuiltIn => self.builtins.put(
std.enums.fromInt(spv.SpvBuiltIn, decoration.literal_1) orelse return ModuleError.InvalidSpirV,
id,
),
.Location => self.output_locations[decoration.literal_1] = id,
else => {},
},
else => {},
}
}
fn applyStructMemberInterfaceDecorations(
self: *Self,
storage_class: spv.SpvStorageClass,
type_word: SpvWord,
id: SpvWord,
) ModuleError!void {
switch (storage_class) {
.Input, .Output => {},
else => return,
}
const type_result = &self.results[type_word];
const target_type_word = if (type_result.variant) |variant| switch (variant) {
.Type => |t| switch (t) {
.Pointer => |ptr| ptr.target,
else => type_word,
},
else => type_word,
} else type_word;
const target_result = &self.results[target_type_word];
if (target_result.variant) |variant| {
switch (variant) {
.Type => |t| switch (t) {
.Structure => {
for (target_result.decorations.items) |decoration| {
switch (decoration.rtype) {
.BuiltIn, .Location => {
const member_id = self.findAccessChainToMember(id, decoration.index) orelse continue;
try self.applyInterfaceDecoration(storage_class, decoration, member_id);
},
else => {},
}
}
},
else => {},
},
else => {},
}
}
}
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.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),
switch (result.variant.?) {
.Variable => |v| {
try self.applyInterfaceDecoration(v.storage_class, decoration, @intCast(id));
switch (v.storage_class) {
.StorageBuffer, .Uniform, .UniformConstant => {
switch (decoration.rtype) {
.Binding => binding = decoration.literal_1,
.DescriptorSet => set = decoration.literal_1,
else => {},
}
},
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,
.Type => |t| {
switch (t) {
.Structure => |*s| {
if (decoration.rtype == .Offset) {
s.members_offsets[decoration.index] = decoration.literal_1;
}
},
else => {},
}
},
else => {},
}
}
switch (result.variant.?) {
.Variable => |v| try self.applyStructMemberInterfaceDecorations(v.storage_class, v.type_word, @intCast(id)),
else => {},
}
if (set != null and binding != null) {
self.bindings[set.?][binding.?] = @intCast(id);
}
+78 -276
View File
@@ -1,6 +1,8 @@
const std = @import("std");
const spv = @import("spv.zig");
const op = @import("opcodes.zig");
const lib = @import("lib.zig");
const Value = @import("Value.zig").Value;
const Runtime = @import("Runtime.zig");
const RuntimeError = Runtime.RuntimeError;
@@ -10,17 +12,17 @@ const SpvByte = spv.SpvByte;
const SpvWord = spv.SpvWord;
const SpvBool = spv.SpvBool;
pub const Vec4f32 = @Vector(4, f32);
pub const Vec3f32 = @Vector(3, f32);
pub const Vec2f32 = @Vector(2, f32);
const Vec4f32 = lib.Vec4f32;
const Vec3f32 = lib.Vec3f32;
const Vec2f32 = lib.Vec2f32;
pub const Vec4i32 = @Vector(4, i32);
pub const Vec3i32 = @Vector(3, i32);
pub const Vec2i32 = @Vector(2, i32);
const Vec4i32 = lib.Vec4i32;
const Vec3i32 = lib.Vec3i32;
const Vec2i32 = lib.Vec2i32;
pub const Vec4u32 = @Vector(4, u32);
pub const Vec3u32 = @Vector(3, u32);
pub const Vec2u32 = @Vector(2, u32);
const Vec4u32 = lib.Vec4u32;
const Vec3u32 = lib.Vec3u32;
const Vec2u32 = lib.Vec2u32;
pub const Variant = enum {
String,
@@ -70,198 +72,13 @@ const ImageInfo = struct {
access: spv.SpvAccessQualifier,
};
const Decoration = struct {
pub const Decoration = struct {
rtype: spv.SpvDecoration,
literal_1: SpvWord,
literal_2: ?SpvWord,
index: SpvWord,
};
pub const Value = union(Type) {
Void: struct {},
Bool: bool,
Int: struct {
bit_count: usize,
value: extern union {
sint8: i8,
sint16: i16,
sint32: i32,
sint64: i64,
uint8: u8,
uint16: u16,
uint32: u32,
uint64: u64,
},
},
Float: struct {
bit_count: usize,
value: extern union {
float16: f16,
float32: f32,
float64: f64,
},
},
Vector: []Value,
Vector4f32: Vec4f32,
Vector3f32: Vec3f32,
Vector2f32: Vec2f32,
Vector4i32: Vec4i32,
Vector3i32: Vec3i32,
Vector2i32: Vec2i32,
Vector4u32: Vec4u32,
Vector3u32: Vec3u32,
Vector2u32: Vec2u32,
Matrix: []Value,
Array: []Value,
RuntimeArray: ?[]Value,
Structure: []Value,
Function: noreturn,
Image: struct {},
Sampler: struct {},
SampledImage: struct {},
Pointer: union(enum) {
common: *Value,
f32_ptr: *f32,
i32_ptr: *i32, //< For vector specializations
u32_ptr: *u32,
},
pub inline fn getCompositeDataOrNull(self: *const Value) ?[]Value {
return switch (self.*) {
.Vector, .Matrix, .Array, .Structure => |v| v,
.RuntimeArray => |v| v,
else => null,
};
}
fn init(allocator: std.mem.Allocator, results: []const Self, target: SpvWord) RuntimeError!Value {
const resolved = results[target].resolveType(results);
const member_count = resolved.getMemberCounts();
return switch (resolved.variant.?) {
.Type => |t| switch (t) {
.Bool => .{ .Bool = false },
.Int => |i| .{ .Int = .{
.bit_count = i.bit_length,
.value = .{ .uint64 = 0 },
} },
.Float => |f| .{ .Float = .{
.bit_count = f.bit_length,
.value = .{ .float64 = 0 },
} },
.Vector => |v| blk: {
var self: Value = .{ .Vector = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer self.deinit(allocator);
for (self.Vector) |*value| {
value.* = try Value.init(allocator, results, v.components_type_word);
}
break :blk self;
},
.Vector4f32 => .{ .Vector4f32 = Vec4f32{ 0.0, 0.0, 0.0, 0.0 } },
.Vector3f32 => .{ .Vector3f32 = Vec3f32{ 0.0, 0.0, 0.0 } },
.Vector2f32 => .{ .Vector2f32 = Vec2f32{ 0.0, 0.0 } },
.Vector4i32 => .{ .Vector4i32 = Vec4i32{ 0, 0, 0, 0 } },
.Vector3i32 => .{ .Vector3i32 = Vec3i32{ 0, 0, 0 } },
.Vector2i32 => .{ .Vector2i32 = Vec2i32{ 0, 0 } },
.Vector4u32 => .{ .Vector4u32 = Vec4u32{ 0, 0, 0, 0 } },
.Vector3u32 => .{ .Vector3u32 = Vec3u32{ 0, 0, 0 } },
.Vector2u32 => .{ .Vector2u32 = Vec2u32{ 0, 0 } },
.Matrix => |m| blk: {
var self: Value = .{ .Matrix = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer self.deinit(allocator);
for (self.Matrix) |*value| {
value.* = try Value.init(allocator, results, m.column_type_word);
}
break :blk self;
},
.Array => |a| blk: {
var self: Value = .{ .Array = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer self.deinit(allocator);
for (self.Array) |*value| {
value.* = try Value.init(allocator, results, a.components_type_word);
}
break :blk self;
},
.Structure => |s| blk: {
var self: Value = .{ .Structure = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer self.deinit(allocator);
for (self.Structure, s.members_type_word) |*value, member_type_word| {
value.* = try Value.init(allocator, results, member_type_word);
}
break :blk self;
},
.RuntimeArray => .{ .RuntimeArray = null },
else => unreachable,
},
else => unreachable,
};
}
/// Performs a deep copy
pub fn dupe(self: *const Value, allocator: std.mem.Allocator) RuntimeError!Value {
return switch (self.*) {
.Vector => |v| .{
.Vector = blk: {
const values = allocator.dupe(Value, v) catch return RuntimeError.OutOfMemory;
for (values, v) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
},
},
.Matrix => |m| .{
.Matrix = blk: {
const values = allocator.dupe(Value, m) catch return RuntimeError.OutOfMemory;
for (values, m) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
},
},
.Array => |a| .{
.Array = blk: {
const values = allocator.dupe(Value, a) catch return RuntimeError.OutOfMemory;
for (values, a) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
},
},
.RuntimeArray => |opt_a| .{
.RuntimeArray = blk: {
if (opt_a) |a| {
const values = allocator.dupe(Value, a) catch return RuntimeError.OutOfMemory;
for (values, a) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
} else {
break :blk null;
}
},
},
.Structure => |s| .{
.Structure = blk: {
const values = allocator.dupe(Value, s) catch return RuntimeError.OutOfMemory;
for (values, s) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
},
},
else => self.*,
};
}
fn deinit(self: *Value, allocator: std.mem.Allocator) void {
switch (self.*) {
.Vector, .Matrix, .Array, .Structure => |values| {
for (values) |*value| value.deinit(allocator);
allocator.free(values);
},
.RuntimeArray => |opt_values| if (opt_values) |values| {
for (values) |*value| value.deinit(allocator);
allocator.free(values);
},
else => {},
}
}
};
pub const TypeData = union(Type) {
Void: struct {},
Bool: struct {},
@@ -295,13 +112,16 @@ pub const TypeData = union(Type) {
components_type_word: SpvWord,
components_type: Type,
member_count: SpvWord,
stride: SpvWord,
},
RuntimeArray: struct {
components_type_word: SpvWord,
components_type: Type,
stride: SpvWord,
},
Structure: struct {
members_type_word: []const SpvWord,
members_offsets: []?SpvWord,
member_names: std.ArrayList([]const u8),
},
Function: struct {
@@ -309,7 +129,15 @@ pub const TypeData = union(Type) {
return_type: SpvWord,
params: []const SpvWord,
},
Image: struct {},
Image: struct {
dim: spv.SpvDim,
depth: SpvByte,
arrayed: SpvByte,
ms: SpvByte,
sampled: SpvByte,
format: spv.SpvImageFormat,
access: ?spv.SpvAccessQualifier,
},
Sampler: struct {},
SampledImage: struct {},
Pointer: struct {
@@ -323,12 +151,18 @@ pub const TypeData = union(Type) {
.Int => |i| @divExact(i.bit_length, 8),
.Float => |f| @divExact(f.bit_length, 8),
.Vector => |v| results[v.components_type_word].variant.?.Type.getSize(results),
.Array => |a| results[a.components_type_word].variant.?.Type.getSize(results),
.Array => |a| a.stride,
.Matrix => |m| results[m.column_type_word].variant.?.Type.getSize(results),
.RuntimeArray => |a| results[a.components_type_word].variant.?.Type.getSize(results),
.RuntimeArray => |a| a.stride,
.Structure => |s| blk: {
var total: usize = 0;
for (s.members_type_word) |type_word| {
for (s.members_type_word, 0..) |type_word, i| {
if (i + 1 < s.members_offsets.len) {
if (s.members_offsets[i + 1]) |offset| {
total = offset;
continue;
}
}
total += results[type_word].variant.?.Type.getSize(results);
}
break :blk total;
@@ -367,6 +201,8 @@ pub const VariantData = union(Variant) {
},
AccessChain: struct {
target: SpvWord,
base: SpvWord,
indexes: []SpvWord,
value: Value,
},
FunctionParameter: struct {
@@ -406,12 +242,17 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
for (data.member_names.items) |name| {
allocator.free(name);
}
allocator.free(data.members_offsets);
data.member_names.deinit(allocator);
},
else => {},
},
.Constant => |*c| c.value.deinit(allocator),
.Variable => |*v| v.value.deinit(allocator),
.AccessChain => |*a| {
allocator.free(a.indexes);
a.value.deinit(allocator);
},
.Function => |f| allocator.free(f.params),
else => {},
}
@@ -482,6 +323,7 @@ pub fn dupe(self: *const Self, allocator: std.mem.Allocator) RuntimeError!Self {
.Type = .{
.Structure = .{
.members_type_word = allocator.dupe(SpvWord, s.members_type_word) catch return RuntimeError.OutOfMemory,
.members_offsets = allocator.dupe(?SpvWord, s.members_offsets) catch return RuntimeError.OutOfMemory,
.member_names = blk2: {
const member_names = s.member_names.clone(allocator) catch return RuntimeError.OutOfMemory;
for (member_names.items, s.member_names.items) |*new_name, name| {
@@ -526,6 +368,14 @@ pub fn dupe(self: *const Self, allocator: std.mem.Allocator) RuntimeError!Self {
.params = allocator.dupe(SpvWord, f.params) catch return RuntimeError.OutOfMemory,
},
},
.AccessChain => |a| break :blk .{
.AccessChain = .{
.target = a.target,
.base = a.base,
.indexes = allocator.dupe(SpvWord, a.indexes) catch return RuntimeError.OutOfMemory,
.value = try a.value.dupe(allocator),
},
},
else => break :blk variant,
}
}
@@ -554,6 +404,17 @@ pub fn resolveLaneBitWidth(target_type: TypeData, rt: *const Runtime) RuntimeErr
};
}
pub fn resolveLaneCount(target_type: TypeData) RuntimeError!SpvWord {
return switch (target_type) {
.Bool, .Float, .Int => 1,
.Vector => |v| v.member_count,
.Vector4f32, .Vector4i32, .Vector4u32 => 4,
.Vector3f32, .Vector3i32, .Vector3u32 => 3,
.Vector2f32, .Vector2i32, .Vector2u32 => 2,
else => return RuntimeError.InvalidSpirV,
};
}
pub fn resolveSign(target_type: TypeData, rt: *const Runtime) RuntimeError!enum { signed, unsigned } {
return sw: switch (target_type) {
.Int => |i| if (i.is_signed) .signed else .unsigned,
@@ -568,17 +429,21 @@ pub fn resolveSign(target_type: TypeData, rt: *const Runtime) RuntimeError!enum
};
}
pub fn resolveType(self: *const Self, results: []const Self) *const Self {
pub inline fn resolveType(self: *const Self, results: []const Self) *const Self {
return if (self.resolveTypeWordOrNull()) |word| &results[word] else self;
}
pub fn resolveTypeWordOrNull(self: *const Self) ?SpvWord {
return if (self.variant) |variant|
switch (variant) {
.Type => |t| switch (t) {
.Pointer => |ptr| &results[ptr.target],
else => self,
.Pointer => |ptr| ptr.target,
else => null,
},
else => self,
else => null,
}
else
self;
null;
}
pub fn getMemberCounts(self: *const Self) usize {
@@ -603,76 +468,13 @@ pub fn getMemberCounts(self: *const Self) usize {
return 0;
}
pub fn initValue(allocator: std.mem.Allocator, member_count: usize, results: []const Self, resolved: *const Self) RuntimeError!Value {
return switch (resolved.variant.?) {
.Type => |t| switch (t) {
.Void => .{ .Void = .{} },
.Bool => .{ .Bool = false },
.Int => |i| .{ .Int = .{
.bit_count = i.bit_length,
.value = .{ .uint64 = 0 },
} },
.Float => |f| .{ .Float = .{
.bit_count = f.bit_length,
.value = .{ .float64 = 0 },
} },
.Vector => |v| blk: {
const value: Value = .{ .Vector = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(value.Vector);
for (value.Vector) |*val| {
val.* = try Value.init(allocator, results, v.components_type_word);
}
break :blk value;
},
.Vector4f32 => .{ .Vector4f32 = Vec4f32{ 0.0, 0.0, 0.0, 0.0 } },
.Vector3f32 => .{ .Vector3f32 = Vec3f32{ 0.0, 0.0, 0.0 } },
.Vector2f32 => .{ .Vector2f32 = Vec2f32{ 0.0, 0.0 } },
.Vector4i32 => .{ .Vector4i32 = Vec4i32{ 0, 0, 0, 0 } },
.Vector3i32 => .{ .Vector3i32 = Vec3i32{ 0, 0, 0 } },
.Vector2i32 => .{ .Vector2i32 = Vec2i32{ 0, 0 } },
.Vector4u32 => .{ .Vector4u32 = Vec4u32{ 0, 0, 0, 0 } },
.Vector3u32 => .{ .Vector3u32 = Vec3u32{ 0, 0, 0 } },
.Vector2u32 => .{ .Vector2u32 = Vec2u32{ 0, 0 } },
.Matrix => |m| blk: {
const value: Value = .{ .Matrix = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(value.Matrix);
for (value.Matrix) |*v| {
v.* = try Value.init(allocator, results, m.column_type_word);
}
break :blk value;
},
.Array => |a| blk: {
const value: Value = .{ .Array = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(value.Array);
for (value.Array) |*val| {
val.* = try Value.init(allocator, results, a.components_type_word);
}
break :blk value;
},
.RuntimeArray => |a| blk: {
if (member_count == 0) {
break :blk Value{ .RuntimeArray = null };
}
const value: Value = .{ .RuntimeArray = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(value.RuntimeArray.?);
for (value.RuntimeArray.?) |*val| {
val.* = try Value.init(allocator, results, a.components_type_word);
}
break :blk value;
},
.Structure => |s| blk: {
const value: Value = .{ .Structure = allocator.alloc(Value, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(value.Structure);
for (value.Structure, s.members_type_word) |*v, member_type_word| {
v.* = try Value.init(allocator, results, member_type_word);
}
break :blk value;
},
.Image => RuntimeError.ToDo,
.Sampler => RuntimeError.ToDo,
.SampledImage => RuntimeError.ToDo,
else => RuntimeError.InvalidSpirV,
},
else => RuntimeError.InvalidSpirV,
};
pub fn flushPtr(self: *Self, allocator: std.mem.Allocator) RuntimeError!void {
if (self.variant) |*variant| {
switch (variant.*) {
.Constant => |*c| try c.value.flushPtr(allocator),
.Variable => |*v| try v.value.flushPtr(allocator),
.AccessChain => |*a| try a.value.flushPtr(allocator),
else => {},
}
}
}
+182 -271
View File
@@ -4,6 +4,7 @@ const std = @import("std");
const spv = @import("spv.zig");
const op = @import("opcodes.zig");
const lib = @import("lib.zig");
const pretty = @import("pretty");
const SpvVoid = spv.SpvVoid;
const SpvByte = spv.SpvByte;
@@ -29,6 +30,13 @@ pub const RuntimeError = error{
Unreachable,
UnsupportedSpirV,
UnsupportedExtension,
Unknown,
};
pub const SpecializationEntry = struct {
id: SpvWord,
offset: usize,
size: usize,
};
pub const Function = struct {
@@ -37,6 +45,22 @@ pub const Function = struct {
ret: *Result,
};
pub fn Vec4(comptime T: type) type {
return struct {
x: T,
y: T,
z: T,
w: T,
};
}
pub const ImageAPI = struct {
readImageFloat4: *const fn (driver_image: *anyopaque, x: i32, y: i32, z: i32) RuntimeError!Vec4(f32),
readImageInt4: *const fn (driver_image: *anyopaque, x: i32, y: i32, z: i32) RuntimeError!Vec4(u32),
writeImageFloat4: *const fn (driver_image: *anyopaque, x: i32, y: i32, z: i32, pixel: Vec4(f32)) RuntimeError!void,
writeImageInt4: *const fn (driver_image: *anyopaque, x: i32, y: i32, z: i32, pixel: Vec4(u32)) RuntimeError!void,
};
mod: *Module,
it: WordIterator,
@@ -47,7 +71,14 @@ current_parameter_index: SpvWord,
current_function: ?*Result,
function_stack: std.ArrayList(Function),
pub fn init(allocator: std.mem.Allocator, module: *Module) RuntimeError!Self {
current_label: ?SpvWord,
previous_label: ?SpvWord,
specialization_constants: std.AutoHashMapUnmanaged(u32, []const u8),
image_api: ImageAPI,
pub fn init(allocator: std.mem.Allocator, module: *Module, image_api: ImageAPI) RuntimeError!Self {
return .{
.mod = module,
.it = module.it,
@@ -61,6 +92,10 @@ pub fn init(allocator: std.mem.Allocator, module: *Module) RuntimeError!Self {
.current_parameter_index = 0,
.current_function = null,
.function_stack = .empty,
.current_label = null,
.previous_label = null,
.specialization_constants = .empty,
.image_api = image_api,
};
}
@@ -70,22 +105,35 @@ pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
}
allocator.free(self.results);
self.function_stack.deinit(allocator);
var it = self.specialization_constants.iterator();
while (it.next()) |entry| {
allocator.free(entry.value_ptr.*);
}
self.specialization_constants.deinit(allocator);
}
pub fn getEntryPointByName(self: *const Self, name: []const u8) error{NotFound}!SpvWord {
pub fn addSpecializationInfo(self: *Self, allocator: std.mem.Allocator, entry: SpecializationEntry, data: []const u8) RuntimeError!void {
const slice = allocator.dupe(u8, data[entry.offset .. entry.offset + entry.size]) catch return RuntimeError.OutOfMemory;
self.specialization_constants.put(allocator, entry.id, slice) catch return RuntimeError.OutOfMemory;
}
pub fn getEntryPointByName(self: *const Self, name: []const u8) RuntimeError!SpvWord {
for (self.mod.entry_points.items, 0..) |entry_point, i| {
if (blk: {
// Not using std.mem.eql as entry point names may have longer size than their content
for (0..@min(name.len, entry_point.name.len)) |j| {
if (name[j] != entry_point.name[j]) break :blk false;
if (name[j] != entry_point.name[j])
break :blk false;
}
if (entry_point.name.len != name.len and entry_point.name[name.len] != 0)
break :blk false;
break :blk true;
}) return @intCast(i);
}
return error.NotFound;
return RuntimeError.NotFound;
}
pub fn getResultByName(self: *const Self, name: []const u8) error{NotFound}!SpvWord {
pub fn getResultByName(self: *const Self, name: []const u8) RuntimeError!SpvWord {
for (self.results, 0..) |result, i| {
if (result.name) |result_name| {
if (blk: {
@@ -97,14 +145,48 @@ pub fn getResultByName(self: *const Self, name: []const u8) error{NotFound}!SpvW
}) return @intCast(i);
}
}
return error.NotFound;
return RuntimeError.NotFound;
}
pub fn getResultByLocation(self: *const Self, location: SpvWord, kind: enum { input, output }) RuntimeError!SpvWord {
switch (kind) {
.input => if (location < self.mod.input_locations.len and self.mod.input_locations[location] != 0) {
return self.mod.input_locations[location];
},
.output => if (location < self.mod.output_locations.len and self.mod.output_locations[location] != 0) {
return self.mod.output_locations[location];
},
}
return RuntimeError.NotFound;
}
pub fn dumpResultsTable(self: *Self, allocator: std.mem.Allocator, writer: *std.Io.Writer) RuntimeError!void {
const dump = pretty.dump(allocator, self.results, .{
.tab_size = 4,
.max_depth = 0,
.struct_max_len = 0,
.array_max_len = 0,
}) catch return RuntimeError.OutOfMemory;
defer allocator.free(dump);
writer.print("{s}", .{dump}) catch return RuntimeError.Unknown;
writer.flush() catch return RuntimeError.Unknown;
}
/// Calls an entry point, `entry_point_index` being the index of the entry point ordered by declaration in the bytecode
pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_index: SpvWord) RuntimeError!void {
self.reset();
if (entry_point_index > self.mod.entry_points.items.len) return RuntimeError.InvalidEntryPoint;
if (entry_point_index > self.mod.entry_points.items.len)
return RuntimeError.InvalidEntryPoint;
// Spec constants pass
try self.pass(allocator, .initMany(&.{
.SpecConstantTrue,
.SpecConstantFalse,
.SpecConstantComposite,
.SpecConstant,
.SpecConstantOp,
}));
{
const entry_point_desc = &self.mod.entry_points.items[entry_point_index];
@@ -112,7 +194,8 @@ pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_ind
if (entry_point_result.variant) |variant| {
switch (variant) {
.Function => |f| {
if (!self.it.jumpToSourceLocation(f.source_location)) return RuntimeError.InvalidEntryPoint;
if (!self.it.jumpToSourceLocation(f.source_location))
return RuntimeError.InvalidEntryPoint;
self.function_stack.append(allocator, .{
.source_location = f.source_location,
.result = entry_point_result,
@@ -126,11 +209,24 @@ pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_ind
}
}
// Execution pass
try self.pass(allocator, null);
}
fn pass(self: *Self, allocator: std.mem.Allocator, op_set: ?std.EnumSet(spv.SpvOp)) RuntimeError!void {
self.it.did_jump = false; // To reset function jump
while (self.it.nextOrNull()) |opcode_data| {
const word_count = ((opcode_data & (~spv.SpvOpCodeMask)) >> spv.SpvWordCountShift) - 1;
const opcode = (opcode_data & spv.SpvOpCodeMask);
if (op_set) |set| {
@branchHint(.unlikely);
if (!set.contains(@enumFromInt(opcode))) {
_ = self.it.skipN(word_count);
continue;
}
}
var it_tmp = self.it; // Save because operations may iter on this iterator
if (op.runtime_dispatcher[opcode]) |pfn| {
try pfn(allocator, word_count, self);
@@ -142,60 +238,76 @@ pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_ind
self.it.did_jump = false;
}
}
//@import("pretty").print(allocator, self.results, .{
// .tab_size = 4,
// .max_depth = 0,
// .struct_max_len = 0,
// .array_max_len = 0,
//}) catch return RuntimeError.OutOfMemory;
}
pub fn readDescriptorSet(self: *const Self, output: []u8, set: SpvWord, binding: SpvWord) RuntimeError!void {
pub fn writeDescriptorSet(self: *const Self, input: []const u8, set: SpvWord, binding: SpvWord, descriptor_index: SpvWord) RuntimeError!void {
if (set < lib.SPIRV_MAX_SET and binding < lib.SPIRV_MAX_SET_BINDINGS) {
_ = try self.readValue(output, &self.results[self.mod.bindings[set][binding]].variant.?.Variable.value);
const value = &self.results[self.mod.bindings[set][binding]].variant.?.Variable.value;
switch (value.*) {
.Array => |arr| {
if (descriptor_index >= arr.values.len)
return RuntimeError.NotFound;
_ = try arr.values[descriptor_index].writeConst(input);
},
else => {
if (descriptor_index != 0)
return RuntimeError.NotFound;
_ = try value.writeConst(input);
},
}
} else {
return RuntimeError.NotFound;
}
}
pub fn writeDescriptorSet(self: *const Self, allocator: std.mem.Allocator, input: []const u8, set: SpvWord, binding: SpvWord) RuntimeError!void {
if (set < lib.SPIRV_MAX_SET and binding < lib.SPIRV_MAX_SET_BINDINGS) {
const variable = &self.results[self.mod.bindings[set][binding]].variant.?.Variable;
fn readResultValue(self: *const Self, output: []u8, result: SpvWord) RuntimeError!void {
const variant = self.results[result].variant orelse return RuntimeError.InvalidSpirV;
switch (variant) {
.Variable => |v| _ = try v.value.read(output),
.AccessChain => |a| switch (a.value) {
.Pointer => |ptr| switch (ptr.ptr) {
.common => |value_ptr| _ = try value_ptr.read(output),
.f32_ptr => |value_ptr| std.mem.copyForwards(u8, output[0..@sizeOf(f32)], std.mem.asBytes(value_ptr)),
.i32_ptr => |value_ptr| std.mem.copyForwards(u8, output[0..@sizeOf(i32)], std.mem.asBytes(value_ptr)),
.u32_ptr => |value_ptr| std.mem.copyForwards(u8, output[0..@sizeOf(u32)], std.mem.asBytes(value_ptr)),
},
else => _ = try a.value.read(output),
},
else => return RuntimeError.InvalidSpirV,
}
}
const helper = struct {
fn init(allocator2: std.mem.Allocator, len: usize, value: *Result.Value, type_word: SpvWord, results: []Result) RuntimeError!void {
const resolved = results[type_word].resolveType(results);
switch (value.*) {
.RuntimeArray => |a| if (a == null) {
const elem_size = resolved.variant.?.Type.getSize(results);
value.* = try Result.initValue(allocator2, std.math.divCeil(usize, len, elem_size) catch unreachable, results, resolved);
},
.Structure => |*s| for (s.*, 0..) |*elem, i| {
try @This().init(allocator2, len, elem, resolved.variant.?.Type.Structure.members_type_word[i], results);
},
else => {},
}
}
};
try helper.init(allocator, input.len, &variable.value, variable.type_word, self.results);
//@import("pretty").print(allocator, variable, .{
// .tab_size = 4,
// .max_depth = 0,
// .struct_max_len = 0,
// .array_max_len = 0,
//}) catch return RuntimeError.OutOfMemory;
_ = try self.writeValue(input, &variable.value);
fn writeResultValue(self: *const Self, input: []const u8, result: SpvWord) RuntimeError!void {
if (self.results[result].variant) |*variant| {
switch (variant.*) {
.Variable => |*v| _ = try v.value.writeConst(input),
.AccessChain => |*a| switch (a.value) {
.Pointer => |ptr| switch (ptr.ptr) {
.common => |value_ptr| _ = try value_ptr.writeConst(input),
.f32_ptr => |value_ptr| std.mem.copyForwards(u8, std.mem.asBytes(value_ptr), input[0..@sizeOf(f32)]),
.i32_ptr => |value_ptr| std.mem.copyForwards(u8, std.mem.asBytes(value_ptr), input[0..@sizeOf(i32)]),
.u32_ptr => |value_ptr| std.mem.copyForwards(u8, std.mem.asBytes(value_ptr), input[0..@sizeOf(u32)]),
},
else => _ = try a.value.writeConst(input),
},
else => return RuntimeError.InvalidSpirV,
}
} else {
return RuntimeError.NotFound;
return RuntimeError.InvalidSpirV;
}
}
pub fn readOutput(self: *const Self, output: []u8, result: SpvWord) RuntimeError!void {
if (std.mem.indexOfScalar(SpvWord, &self.mod.output_locations, result)) |_| {
_ = try self.readValue(output, &self.results[result].variant.?.Variable.value);
try self.readResultValue(output, result);
} else {
return RuntimeError.NotFound;
}
}
pub fn readBuiltIn(self: *const Self, output: []u8, builtin: spv.SpvBuiltIn) RuntimeError!void {
if (self.mod.builtins.get(builtin)) |result| {
try self.readResultValue(output, result);
} else {
return RuntimeError.NotFound;
}
@@ -203,7 +315,7 @@ pub fn readOutput(self: *const Self, output: []u8, result: SpvWord) RuntimeError
pub fn writeInput(self: *const Self, input: []const u8, result: SpvWord) RuntimeError!void {
if (std.mem.indexOfScalar(SpvWord, &self.mod.input_locations, result)) |_| {
_ = try self.writeValue(input, &self.results[result].variant.?.Variable.value);
try self.writeResultValue(input, result);
} else {
return RuntimeError.NotFound;
}
@@ -211,235 +323,34 @@ pub fn writeInput(self: *const Self, input: []const u8, result: SpvWord) Runtime
pub fn writeBuiltIn(self: *const Self, input: []const u8, builtin: spv.SpvBuiltIn) RuntimeError!void {
if (self.mod.builtins.get(builtin)) |result| {
_ = try self.writeValue(input, &self.results[result].variant.?.Variable.value);
try self.writeResultValue(input, result);
} else {
return RuntimeError.NotFound;
}
}
pub fn flushDescriptorSets(self: *const Self, allocator: std.mem.Allocator) RuntimeError!void {
for (self.results) |*result| {
try result.flushPtr(allocator);
}
}
pub fn getResultMemorySize(self: *const Self, result: SpvWord) RuntimeError!usize {
const value = try self.results[result].getConstValue();
return value.getPlainMemorySize();
}
pub fn hasResultDecoration(self: *const Self, result: SpvWord, decoration: spv.SpvDecoration) bool {
for (self.results[result].decorations.items) |result_decoration| {
if (result_decoration.rtype == decoration)
return true;
}
return false;
}
fn reset(self: *Self) void {
self.function_stack.clearRetainingCapacity();
self.current_function = null;
}
fn readValue(self: *const Self, output: []u8, value: *const Result.Value) RuntimeError!usize {
switch (value.*) {
.Bool => |b| {
output[0] = if (b == true) 1 else 0;
return 1;
},
.Int => |i| {
switch (i.bit_count) {
8 => output[0] = @bitCast(i.value.uint8),
16 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&i.value.uint16)),
32 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&i.value.uint32)),
64 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&i.value.uint64)),
else => return RuntimeError.InvalidValueType,
}
return @divExact(i.bit_count, 8);
},
.Float => |f| {
switch (f.bit_count) {
16 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&f.value.float16)),
32 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&f.value.float32)),
64 => std.mem.copyForwards(u8, output[0..], std.mem.asBytes(&f.value.float64)),
else => return RuntimeError.InvalidValueType,
}
return @divExact(f.bit_count, 8);
},
.Vector4f32 => |vec| {
inline for (0..4) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 4 * 4;
},
.Vector3f32 => |vec| {
inline for (0..3) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 3 * 4;
},
.Vector2f32 => |vec| {
inline for (0..2) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 2 * 4;
},
.Vector4i32 => |vec| {
inline for (0..4) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 4 * 4;
},
.Vector3i32 => |vec| {
inline for (0..3) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 3 * 4;
},
.Vector2i32 => |vec| {
inline for (0..2) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 2 * 4;
},
.Vector4u32 => |vec| {
inline for (0..4) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 4 * 4;
},
.Vector3u32 => |vec| {
inline for (0..3) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 3 * 4;
},
.Vector2u32 => |vec| {
inline for (0..2) |i| {
std.mem.copyForwards(u8, output[(i * 4)..], std.mem.asBytes(&vec[i]));
}
return 2 * 4;
},
.Vector,
.Matrix,
.Array,
.Structure,
=> |values| {
var offset: usize = 0;
for (values) |v| {
offset += try self.readValue(output[offset..], &v);
}
return offset;
},
.RuntimeArray => |opt_values| if (opt_values) |values| {
var offset: usize = 0;
for (values) |v| {
offset += try self.readValue(output[offset..], &v);
}
return offset;
},
else => return RuntimeError.InvalidValueType,
}
return 0;
}
fn writeValue(self: *const Self, input: []const u8, value: *Result.Value) RuntimeError!usize {
switch (value.*) {
.Bool => |*b| {
b.* = if (input[0] != 0) true else false;
return 1;
},
.Int => |*i| {
switch (i.bit_count) {
8 => i.value.uint8 = @bitCast(input[0]),
16 => std.mem.copyForwards(u8, std.mem.asBytes(&i.value.uint16), input[0..2]),
32 => std.mem.copyForwards(u8, std.mem.asBytes(&i.value.uint32), input[0..4]),
64 => std.mem.copyForwards(u8, std.mem.asBytes(&i.value.uint64), input[0..8]),
else => return RuntimeError.InvalidValueType,
}
return @divExact(i.bit_count, 8);
},
.Float => |*f| {
switch (f.bit_count) {
16 => std.mem.copyForwards(u8, std.mem.asBytes(&f.value.float16), input[0..2]),
32 => std.mem.copyForwards(u8, std.mem.asBytes(&f.value.float32), input[0..4]),
64 => std.mem.copyForwards(u8, std.mem.asBytes(&f.value.float64), input[0..8]),
else => return RuntimeError.InvalidValueType,
}
return @divExact(f.bit_count, 8);
},
.Vector4f32 => |*vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 4 * 4;
},
.Vector3f32 => |*vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 3 * 4;
},
.Vector2f32 => |*vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 2 * 4;
},
.Vector4i32 => |*vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 4 * 4;
},
.Vector3i32 => |*vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 3 * 4;
},
.Vector2i32 => |*vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 2 * 4;
},
.Vector4u32 => |*vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 4 * 4;
},
.Vector3u32 => |*vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 3 * 4;
},
.Vector2u32 => |*vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
std.mem.copyForwards(u8, std.mem.asBytes(&vec[i]), input[start..end]);
}
return 2 * 4;
},
.Vector,
.Matrix,
.Array,
.Structure,
=> |*values| {
var offset: usize = 0;
for (values.*) |*v| {
offset += try self.writeValue(input[offset..], v);
}
return offset;
},
.RuntimeArray => |opt_values| if (opt_values) |*values| {
var offset: usize = 0;
for (values.*) |*v| {
offset += try self.writeValue(input[offset..], v);
}
return offset;
},
else => return RuntimeError.InvalidValueType,
}
return 0;
self.current_label = null;
self.previous_label = null;
}
+929
View File
@@ -0,0 +1,929 @@
const std = @import("std");
const lib = @import("lib.zig");
const Result = @import("Result.zig");
const Runtime = @import("Runtime.zig");
const RuntimeError = Runtime.RuntimeError;
const SpvVoid = lib.SpvVoid;
const SpvByte = lib.SpvByte;
const SpvWord = lib.SpvWord;
const SpvBool = lib.SpvBool;
const Vec4f32 = lib.Vec4f32;
const Vec3f32 = lib.Vec3f32;
const Vec2f32 = lib.Vec2f32;
const Vec4i32 = lib.Vec4i32;
const Vec3i32 = lib.Vec3i32;
const Vec2i32 = lib.Vec2i32;
const Vec4u32 = lib.Vec4u32;
const Vec3u32 = lib.Vec3u32;
const Vec2u32 = lib.Vec2u32;
const Type = Result.Type;
pub const PrimitiveType = enum {
Bool,
Float,
SInt,
UInt,
};
pub const Value = union(Type) {
const Self = @This();
Void: struct {},
Bool: bool,
Int: struct {
bit_count: usize,
is_signed: bool,
value: extern union {
sint8: i8,
sint16: i16,
sint32: i32,
sint64: i64,
uint8: u8,
uint16: u16,
uint32: u32,
uint64: u64,
},
},
Float: struct {
bit_count: usize,
value: extern union {
float16: f16,
float32: f32,
float64: f64,
},
},
Vector: []Self,
Vector4f32: Vec4f32,
Vector3f32: Vec3f32,
Vector2f32: Vec2f32,
Vector4i32: Vec4i32,
Vector3i32: Vec3i32,
Vector2i32: Vec2i32,
Vector4u32: Vec4u32,
Vector3u32: Vec3u32,
Vector2u32: Vec2u32,
Matrix: []Self,
Array: struct {
stride: SpvWord,
values: []Self,
},
RuntimeArray: struct {
type_word: SpvWord,
stride: SpvWord,
data: []u8,
pub inline fn createValueFromIndex(self: *const @This(), allocator: std.mem.Allocator, results: []const Result, index: usize) RuntimeError!*Value {
const value = allocator.create(Value) catch return RuntimeError.OutOfMemory;
errdefer allocator.destroy(value);
value.* = try Value.init(allocator, results, self.type_word, false);
_ = try value.writeConst(self.data[self.getOffsetOfIndex(index)..]);
return value;
}
pub inline fn createLocalValueFromIndex(self: *const @This(), allocator: std.mem.Allocator, results: []const Result, index: usize) RuntimeError!Value {
var value = try Value.init(allocator, results, self.type_word, false);
_ = try value.writeConst(self.data[self.getOffsetOfIndex(index)..]);
return value;
}
pub inline fn getOffsetOfIndex(self: *const @This(), index: usize) usize {
return self.stride * index;
}
pub inline fn getLen(self: *const @This()) usize {
return @divTrunc(self.data.len, self.stride);
}
},
Structure: struct {
offsets: []const ?SpvWord,
values: []Self,
},
Function: noreturn,
Image: struct {
type_word: SpvWord,
driver_image: *anyopaque,
},
Sampler: struct {},
SampledImage: struct {},
Pointer: struct {
ptr: union(enum) {
common: *Self,
f32_ptr: *f32,
i32_ptr: *i32, //< For vector specializations
u32_ptr: *u32,
},
/// Exact byte window in externally visible descriptor storage that
/// corresponds to this pointer. For a pointer to struct member N this
/// starts at the member offset, not at the containing struct.
uniform_slice_window: ?[]u8 = null,
/// Heap-owned value that backs a pointer into a materialized runtime
/// array element. This may differ from ptr.common when the pointer is
/// to a child/member of that materialized value.
uniform_backing_value: ?*Self = null,
},
pub inline fn getCompositeDataOrNull(self: *const Self) ?[]Self {
return switch (self.*) {
.Structure => |*s| s.values,
.Array => |*a| a.values,
.Vector, .Matrix => |v| v,
else => null,
};
}
pub fn init(allocator: std.mem.Allocator, results: []const Result, target_type: SpvWord, is_externally_visible: bool) RuntimeError!Self {
const resolved = results[target_type].resolveTypeWordOrNull() orelse target_type;
const member_count = results[resolved].getMemberCounts();
return switch (results[resolved].variant.?) {
.Type => |t| switch (t) {
.Void => .{ .Void = .{} },
.Bool => .{ .Bool = false },
.Int => |i| .{ .Int = .{
.bit_count = i.bit_length,
.is_signed = i.is_signed,
.value = .{ .uint64 = 0 },
} },
.Float => |f| .{ .Float = .{
.bit_count = f.bit_length,
.value = .{ .float64 = 0 },
} },
.Vector => |v| blk: {
const self: Self = .{ .Vector = allocator.alloc(Self, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(self.Vector);
for (self.Vector) |*value| {
value.* = try Self.init(allocator, results, v.components_type_word, is_externally_visible);
}
break :blk self;
},
.Vector4f32 => .{ .Vector4f32 = Vec4f32{ 0.0, 0.0, 0.0, 0.0 } },
.Vector3f32 => .{ .Vector3f32 = Vec3f32{ 0.0, 0.0, 0.0 } },
.Vector2f32 => .{ .Vector2f32 = Vec2f32{ 0.0, 0.0 } },
.Vector4i32 => .{ .Vector4i32 = Vec4i32{ 0, 0, 0, 0 } },
.Vector3i32 => .{ .Vector3i32 = Vec3i32{ 0, 0, 0 } },
.Vector2i32 => .{ .Vector2i32 = Vec2i32{ 0, 0 } },
.Vector4u32 => .{ .Vector4u32 = Vec4u32{ 0, 0, 0, 0 } },
.Vector3u32 => .{ .Vector3u32 = Vec3u32{ 0, 0, 0 } },
.Vector2u32 => .{ .Vector2u32 = Vec2u32{ 0, 0 } },
.Matrix => |m| blk: {
const self: Self = .{ .Matrix = allocator.alloc(Self, member_count) catch return RuntimeError.OutOfMemory };
errdefer allocator.free(self.Matrix);
for (self.Matrix) |*value| {
value.* = try Self.init(allocator, results, m.column_type_word, is_externally_visible);
}
break :blk self;
},
.Array => |a| blk: {
// If an array is in externally visible storage we treat it as a runtime array
if (is_externally_visible) {
break :blk .{
.RuntimeArray = .{
.type_word = a.components_type_word,
.stride = a.stride,
.data = &.{},
},
};
}
const self: Self = .{
.Array = .{
.stride = a.stride,
.values = allocator.alloc(Self, member_count) catch return RuntimeError.OutOfMemory,
},
};
errdefer allocator.free(self.Array.values);
for (self.Array.values) |*value| {
value.* = try Self.init(allocator, results, a.components_type_word, is_externally_visible);
}
break :blk self;
},
.Structure => |s| blk: {
const self: Self = .{
.Structure = .{
.offsets = allocator.dupe(?SpvWord, s.members_offsets) catch return RuntimeError.OutOfMemory,
.values = allocator.alloc(Self, member_count) catch return RuntimeError.OutOfMemory,
},
};
errdefer allocator.free(self.Structure.values);
for (self.Structure.values, s.members_type_word) |*value, type_word| {
value.* = try Self.init(allocator, results, type_word, is_externally_visible);
}
break :blk self;
},
.RuntimeArray => |a| .{
.RuntimeArray = .{
.type_word = a.components_type_word,
.stride = a.stride,
.data = &.{},
},
},
.Image => .{
.Image = .{
.type_word = resolved,
.driver_image = undefined,
},
},
.Sampler => RuntimeError.ToDo,
.SampledImage => RuntimeError.ToDo,
else => RuntimeError.InvalidSpirV,
},
else => RuntimeError.InvalidSpirV,
};
}
/// Performs a deep copy
pub fn dupe(self: *const Self, allocator: std.mem.Allocator) RuntimeError!Self {
return switch (self.*) {
.Vector => |v| .{
.Vector = blk: {
const values = allocator.dupe(Self, v) catch return RuntimeError.OutOfMemory;
errdefer allocator.free(values);
for (values, v) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
},
},
.Matrix => |m| .{
.Matrix = blk: {
const values = allocator.dupe(Self, m) catch return RuntimeError.OutOfMemory;
errdefer allocator.free(values);
for (values, m) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk values;
},
},
.Array => |*a| .{
.Array = blk: {
const values = allocator.dupe(Self, a.values) catch return RuntimeError.OutOfMemory;
errdefer allocator.free(values);
for (values, a.values) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk .{
.stride = a.stride,
.values = values,
};
},
},
.Structure => |*s| .{
.Structure = blk: {
const values = allocator.dupe(Self, s.values) catch return RuntimeError.OutOfMemory;
errdefer allocator.free(values);
for (values, s.values) |*new_value, value| new_value.* = try value.dupe(allocator);
break :blk .{
.offsets = allocator.dupe(?SpvWord, s.offsets) catch return RuntimeError.OutOfMemory,
.values = values,
};
},
},
else => self.*,
};
}
pub fn read(self: *const Self, output: []u8) RuntimeError!usize {
switch (self.*) {
.Bool => |b| {
output[0] = if (b == true) 1 else 0;
return 1;
},
.Int => |i| {
switch (i.bit_count) {
8 => output[0] = @bitCast(i.value.uint8),
16 => @memcpy(output[0..2], std.mem.asBytes(&i.value.uint16)),
32 => @memcpy(output[0..4], std.mem.asBytes(&i.value.uint32)),
64 => @memcpy(output[0..8], std.mem.asBytes(&i.value.uint64)),
else => return RuntimeError.InvalidValueType,
}
return @divExact(i.bit_count, 8);
},
.Float => |f| {
switch (f.bit_count) {
16 => @memcpy(output[0..2], std.mem.asBytes(&f.value.float16)),
32 => @memcpy(output[0..4], std.mem.asBytes(&f.value.float32)),
64 => @memcpy(output[0..8], std.mem.asBytes(&f.value.float64)),
else => return RuntimeError.InvalidValueType,
}
return @divExact(f.bit_count, 8);
},
.Vector4f32 => |vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= output.len or end > output.len) return RuntimeError.OutOfBounds;
@memcpy(output[start..end], std.mem.asBytes(&vec[i]));
}
return 4 * 4;
},
.Vector3f32 => |vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= output.len or end > output.len) return RuntimeError.OutOfBounds;
@memcpy(output[start..end], std.mem.asBytes(&vec[i]));
}
return 3 * 4;
},
.Vector2f32 => |vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= output.len or end > output.len) return RuntimeError.OutOfBounds;
@memcpy(output[start..end], std.mem.asBytes(&vec[i]));
}
return 2 * 4;
},
.Vector4i32 => |vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= output.len or end > output.len) return RuntimeError.OutOfBounds;
@memcpy(output[start..end], std.mem.asBytes(&vec[i]));
}
return 4 * 4;
},
.Vector3i32 => |vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= output.len or end > output.len) return RuntimeError.OutOfBounds;
@memcpy(output[start..end], std.mem.asBytes(&vec[i]));
}
return 3 * 4;
},
.Vector2i32 => |vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= output.len or end > output.len) return RuntimeError.OutOfBounds;
@memcpy(output[start..end], std.mem.asBytes(&vec[i]));
}
return 2 * 4;
},
.Vector4u32 => |vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= output.len or end > output.len) return RuntimeError.OutOfBounds;
@memcpy(output[start..end], std.mem.asBytes(&vec[i]));
}
return 4 * 4;
},
.Vector3u32 => |vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= output.len or end > output.len) return RuntimeError.OutOfBounds;
@memcpy(output[start..end], std.mem.asBytes(&vec[i]));
}
return 3 * 4;
},
.Vector2u32 => |vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= output.len or end > output.len) return RuntimeError.OutOfBounds;
@memcpy(output[start..end], std.mem.asBytes(&vec[i]));
}
return 2 * 4;
},
.Vector, .Matrix => |values| {
var offset: usize = 0;
for (values) |v| {
offset += try v.read(output[offset..]);
}
return offset;
},
.Array => |arr| {
var offset: usize = 0;
for (arr.values) |v| {
_ = try v.read(output[offset..]);
offset += arr.stride;
}
return offset;
},
.Structure => |s| {
var end_offset: usize = 0;
for (s.values, 0..) |v, i| {
const member_offset: usize = @intCast(s.offsets[i] orelse end_offset);
const read_size = try v.read(output[member_offset..]);
end_offset = @max(end_offset, member_offset + read_size);
}
return end_offset;
},
else => return RuntimeError.InvalidValueType,
}
return 0;
}
pub fn writeConst(self: *Self, input: []const u8) RuntimeError!usize {
return self.write(@constCast(input));
}
pub fn write(self: *Self, input: []u8) RuntimeError!usize {
switch (self.*) {
.Bool => |*b| {
b.* = if (input[0] != 0) true else false;
return 1;
},
.Int => |*i| {
switch (i.bit_count) {
8 => i.value.uint8 = @bitCast(input[0]),
16 => @memcpy(std.mem.asBytes(&i.value.uint16), input[0..2]),
32 => @memcpy(std.mem.asBytes(&i.value.uint32), input[0..4]),
64 => @memcpy(std.mem.asBytes(&i.value.uint64), input[0..8]),
else => return RuntimeError.InvalidValueType,
}
return @divExact(i.bit_count, 8);
},
.Float => |*f| {
switch (f.bit_count) {
16 => @memcpy(std.mem.asBytes(&f.value.float16), input[0..2]),
32 => @memcpy(std.mem.asBytes(&f.value.float32), input[0..4]),
64 => @memcpy(std.mem.asBytes(&f.value.float64), input[0..8]),
else => return RuntimeError.InvalidValueType,
}
return @divExact(f.bit_count, 8);
},
.Vector4f32 => |*vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= input.len or end > input.len) return RuntimeError.OutOfBounds;
@memcpy(std.mem.asBytes(&vec[i]), input[start..end]);
}
return 4 * 4;
},
.Vector3f32 => |*vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= input.len or end > input.len) return RuntimeError.OutOfBounds;
@memcpy(std.mem.asBytes(&vec[i]), input[start..end]);
}
return 3 * 4;
},
.Vector2f32 => |*vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= input.len or end > input.len) return RuntimeError.OutOfBounds;
@memcpy(std.mem.asBytes(&vec[i]), input[start..end]);
}
return 2 * 4;
},
.Vector4i32 => |*vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= input.len or end > input.len) return RuntimeError.OutOfBounds;
@memcpy(std.mem.asBytes(&vec[i]), input[start..end]);
}
return 4 * 4;
},
.Vector3i32 => |*vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= input.len or end > input.len) return RuntimeError.OutOfBounds;
@memcpy(std.mem.asBytes(&vec[i]), input[start..end]);
}
return 3 * 4;
},
.Vector2i32 => |*vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= input.len or end > input.len) return RuntimeError.OutOfBounds;
@memcpy(std.mem.asBytes(&vec[i]), input[start..end]);
}
return 2 * 4;
},
.Vector4u32 => |*vec| {
inline for (0..4) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= input.len or end > input.len) return RuntimeError.OutOfBounds;
@memcpy(std.mem.asBytes(&vec[i]), input[start..end]);
}
return 4 * 4;
},
.Vector3u32 => |*vec| {
inline for (0..3) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= input.len or end > input.len) return RuntimeError.OutOfBounds;
@memcpy(std.mem.asBytes(&vec[i]), input[start..end]);
}
return 3 * 4;
},
.Vector2u32 => |*vec| {
inline for (0..2) |i| {
const start = i * 4;
const end = (i + 1) * 4;
if (start >= input.len or end > input.len) return RuntimeError.OutOfBounds;
@memcpy(std.mem.asBytes(&vec[i]), input[start..end]);
}
return 2 * 4;
},
.Vector, .Matrix => |*values| {
var offset: usize = 0;
for (values.*) |*v| {
offset += try v.write(input[offset..]);
}
return offset;
},
.Array => |*arr| {
var offset: usize = 0;
for (arr.values) |*v| {
_ = try v.write(input[offset..]);
offset += arr.stride;
}
return offset;
},
.Structure => |s| {
var end_offset: usize = 0;
for (s.values, 0..) |*v, i| {
const member_offset: usize = @intCast(s.offsets[i] orelse end_offset);
const write_size = try v.write(input[member_offset..]);
end_offset = @max(end_offset, member_offset + write_size);
}
return end_offset;
},
.RuntimeArray => |*arr| arr.data = input[0..],
.Image => |*img| img.driver_image = @ptrFromInt(std.mem.bytesToValue(usize, input[0..])),
else => return RuntimeError.InvalidValueType,
}
return 0;
}
pub fn getPlainMemorySize(self: *const Self) RuntimeError!usize {
return switch (self.*) {
.Bool => 1,
.Int => |i| @divExact(i.bit_count, 8),
.Float => |f| @divExact(f.bit_count, 8),
.Vector4f32, .Vector4i32, .Vector4u32 => 4 * 4,
.Vector3f32, .Vector3i32, .Vector3u32 => 3 * 4,
.Vector2f32, .Vector2i32, .Vector2u32 => 2 * 4,
.Vector, .Matrix => |values| blk: {
var size: usize = 0;
for (values) |v| {
size += try v.getPlainMemorySize();
}
break :blk size;
},
.Array => |arr| arr.stride * arr.values.len,
.Structure => |s| blk: {
var size: usize = 0;
for (s.values, 0..) |v, i| {
const member_offset: usize = @intCast(s.offsets[i] orelse size);
size = @max(size, member_offset + try v.getPlainMemorySize());
}
break :blk size;
},
.RuntimeArray => |arr| arr.getLen(),
else => return RuntimeError.InvalidValueType,
};
}
pub inline fn getLaneCount(self: *const Self) RuntimeError!usize {
return switch (self.*) {
.Vector => |lanes| lanes.len,
.Vector2i32, .Vector2u32, .Vector2f32 => 2,
.Vector3i32, .Vector3u32, .Vector3f32 => 3,
.Vector4i32, .Vector4u32, .Vector4f32 => 4,
.Int, .Float, .Bool => 1,
else => RuntimeError.InvalidSpirV,
};
}
pub inline fn isScalar(self: *const Self) bool {
return switch (self.*) {
.Bool, .Int, .Float => true,
else => false,
};
}
pub inline fn isVector(self: *const Self) bool {
return switch (self.*) {
.Vector,
.Vector2i32,
.Vector2u32,
.Vector2f32,
.Vector3i32,
.Vector3u32,
.Vector3f32,
.Vector4i32,
.Vector4u32,
.Vector4f32,
=> true,
else => false,
};
}
pub fn flushPtr(self: *Self, allocator: std.mem.Allocator) RuntimeError!void {
switch (self.*) {
.Pointer => |*p| {
if (p.uniform_slice_window) |window| {
switch (p.ptr) {
.common => |ptr| {
_ = try ptr.read(window);
},
.f32_ptr => |ptr| {
if (window.len < @sizeOf(f32)) return RuntimeError.OutOfBounds;
std.mem.copyForwards(u8, window[0..@sizeOf(f32)], std.mem.asBytes(ptr));
},
.i32_ptr => |ptr| {
if (window.len < @sizeOf(i32)) return RuntimeError.OutOfBounds;
std.mem.copyForwards(u8, window[0..@sizeOf(i32)], std.mem.asBytes(ptr));
},
.u32_ptr => |ptr| {
if (window.len < @sizeOf(u32)) return RuntimeError.OutOfBounds;
std.mem.copyForwards(u8, window[0..@sizeOf(u32)], std.mem.asBytes(ptr));
},
}
if (p.uniform_backing_value) |backing| {
backing.deinit(allocator);
allocator.destroy(backing);
p.uniform_backing_value = null;
}
p.uniform_slice_window = null;
}
},
else => {},
}
}
pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
switch (self.*) {
.Vector, .Matrix => |*values| {
for (values.*) |*value| value.deinit(allocator);
allocator.free(values.*);
},
.Array => |*arr| {
for (arr.values) |*value| value.deinit(allocator);
allocator.free(arr.values);
},
.Structure => |*s| {
for (s.values) |*value| value.deinit(allocator);
allocator.free(s.values);
allocator.free(s.offsets);
},
else => {},
}
}
pub inline fn readLane(comptime T: PrimitiveType, comptime bits: u32, v: *const Value, lane_index: usize) RuntimeError!getPrimitiveFieldType(T, bits) {
const TT = getPrimitiveFieldType(T, bits);
return switch (v.*) {
.Int => (try getPrimitiveField(T, bits, @constCast(v))).*,
.Vector => |lanes| (try getPrimitiveField(T, bits, &lanes[lane_index])).*,
.Vector2i32 => |*vec| switch (lane_index) {
inline 0...1 => |i| blk: {
if (bits == 32) {
break :blk @as(TT, @bitCast(vec[i]));
} else {
return RuntimeError.InvalidSpirV;
}
},
else => return RuntimeError.InvalidSpirV,
},
.Vector3i32 => |*vec| switch (lane_index) {
inline 0...2 => |i| blk: {
if (bits == 32) {
break :blk @as(TT, @bitCast(vec[i]));
} else {
return RuntimeError.InvalidSpirV;
}
},
else => return RuntimeError.InvalidSpirV,
},
.Vector4i32 => |*vec| switch (lane_index) {
inline 0...3 => |i| blk: {
if (bits == 32) {
break :blk @as(TT, @bitCast(vec[i]));
} else {
return RuntimeError.InvalidSpirV;
}
},
else => return RuntimeError.InvalidSpirV,
},
.Vector2u32 => |*vec| switch (lane_index) {
inline 0...1 => |i| blk: {
if (bits == 32) {
break :blk @as(TT, @bitCast(vec[i]));
} else {
return RuntimeError.InvalidSpirV;
}
},
else => return RuntimeError.InvalidSpirV,
},
.Vector3u32 => |*vec| switch (lane_index) {
inline 0...2 => |i| blk: {
if (bits == 32) {
break :blk @as(TT, @bitCast(vec[i]));
} else {
return RuntimeError.InvalidSpirV;
}
},
else => return RuntimeError.InvalidSpirV,
},
.Vector4u32 => |*vec| switch (lane_index) {
inline 0...3 => |i| blk: {
if (bits == 32) {
break :blk @as(TT, @bitCast(vec[i]));
} else {
return RuntimeError.InvalidSpirV;
}
},
else => return RuntimeError.InvalidSpirV,
},
else => RuntimeError.InvalidSpirV,
};
}
pub inline fn writeLane(comptime T: PrimitiveType, comptime bits: u32, dst: *Value, lane_index: usize, value: getPrimitiveFieldType(T, bits)) RuntimeError!void {
switch (dst.*) {
.Int => (try getPrimitiveField(T, bits, dst)).* = value,
.Vector => |lanes| try setScalarLaneValue(T, bits, &lanes[lane_index], value),
.Vector2i32 => |*vec| switch (lane_index) {
inline 0...1 => |i| if (bits == 32) {
vec[i] = @bitCast(value);
} else {
return RuntimeError.InvalidSpirV;
},
else => return RuntimeError.InvalidSpirV,
},
.Vector3i32 => |*vec| switch (lane_index) {
inline 0...2 => |i| if (bits == 32) {
vec[i] = @bitCast(value);
} else {
return RuntimeError.InvalidSpirV;
},
else => return RuntimeError.InvalidSpirV,
},
.Vector4i32 => |*vec| switch (lane_index) {
inline 0...3 => |i| if (bits == 32) {
vec[i] = @bitCast(value);
} else {
return RuntimeError.InvalidSpirV;
},
else => return RuntimeError.InvalidSpirV,
},
.Vector2u32 => |*vec| switch (lane_index) {
inline 0...1 => |i| if (bits == 32) {
vec[i] = @bitCast(value);
} else {
return RuntimeError.InvalidSpirV;
},
else => return RuntimeError.InvalidSpirV,
},
.Vector3u32 => |*vec| switch (lane_index) {
inline 0...2 => |i| if (bits == 32) {
vec[i] = @bitCast(value);
} else {
return RuntimeError.InvalidSpirV;
},
else => return RuntimeError.InvalidSpirV,
},
.Vector4u32 => |*vec| switch (lane_index) {
inline 0...3 => |i| if (bits == 32) {
vec[i] = @bitCast(value);
} else {
return RuntimeError.InvalidSpirV;
},
else => return RuntimeError.InvalidSpirV,
},
else => return RuntimeError.InvalidSpirV,
}
}
fn setScalarLaneValue(comptime value_type: PrimitiveType, comptime bits: u32, dst: *Value, v: getPrimitiveFieldType(value_type, bits)) RuntimeError!void {
switch (bits) {
inline 8, 16, 32, 64 => {
dst.* = .{ .Int = .{
.bit_count = bits,
.is_signed = if (value_type == .SInt) true else false,
.value = switch (value_type) {
.SInt => switch (bits) {
8 => .{ .sint8 = v },
16 => .{ .sint16 = v },
32 => .{ .sint32 = v },
64 => .{ .sint64 = v },
else => unreachable,
},
.UInt => switch (bits) {
8 => .{ .uint8 = v },
16 => .{ .uint16 = v },
32 => .{ .uint32 = v },
64 => .{ .uint64 = v },
else => unreachable,
},
else => return RuntimeError.InvalidSpirV,
},
} };
},
else => return RuntimeError.InvalidSpirV,
}
}
pub fn getPrimitiveField(comptime T: PrimitiveType, comptime BitCount: SpvWord, v: *Value) RuntimeError!*getPrimitiveFieldType(T, BitCount) {
if (std.meta.activeTag(v.*) == .Pointer) {
return switch (v.Pointer.ptr) {
.common => |value| getPrimitiveField(T, BitCount, value),
.f32_ptr => |ptr| @ptrCast(@alignCast(ptr)),
.u32_ptr => |ptr| @ptrCast(@alignCast(ptr)),
.i32_ptr => |ptr| @ptrCast(@alignCast(ptr)),
};
}
return switch (T) {
.Bool => &v.Bool,
.Float => switch (BitCount) {
inline 16, 32, 64 => |i| &@field(v.Float.value, std.fmt.comptimePrint("float{}", .{i})),
else => return RuntimeError.InvalidSpirV,
},
.SInt => switch (BitCount) {
inline 8, 16, 32, 64 => |i| &@field(v.Int.value, std.fmt.comptimePrint("sint{}", .{i})),
else => return RuntimeError.InvalidSpirV,
},
.UInt => switch (BitCount) {
inline 8, 16, 32, 64 => |i| &@field(v.Int.value, std.fmt.comptimePrint("uint{}", .{i})),
else => return RuntimeError.InvalidSpirV,
},
};
}
pub fn getPrimitiveFieldType(comptime T: PrimitiveType, comptime BitCount: SpvWord) type {
return switch (T) {
.Bool => bool,
.Float => std.meta.Float(BitCount),
.SInt => std.meta.Int(.signed, BitCount),
.UInt => std.meta.Int(.unsigned, BitCount),
};
}
pub fn resolveLaneBitWidth(self: *const Self) RuntimeError!SpvWord {
return switch (self.*) {
.Bool => 8,
.Float => |f| @intCast(f.bit_count),
.Int => |i| @intCast(i.bit_count),
.Vector => |v| v[0].resolveLaneBitWidth(),
.Vector4f32,
.Vector3f32,
.Vector2f32,
.Vector4i32,
.Vector3i32,
.Vector2i32,
.Vector4u32,
.Vector3u32,
.Vector2u32,
=> return 32,
else => return RuntimeError.InvalidSpirV,
};
}
pub fn resolveLaneCount(self: *const Self) RuntimeError!SpvWord {
return switch (self.*) {
.Bool, .Float, .Int => 1,
.Vector => |v| @intCast(v.len),
.Vector4f32, .Vector4i32, .Vector4u32 => 4,
.Vector3f32, .Vector3i32, .Vector3u32 => 3,
.Vector2f32, .Vector2i32, .Vector2u32 => 2,
else => return RuntimeError.InvalidSpirV,
};
}
pub fn resolveSign(self: *const Self) RuntimeError!enum { signed, unsigned } {
return switch (self.*) {
.Int => |i| if (i.is_signed) .signed else .unsigned,
.Vector => |v| v[0].resolveSign(),
.Vector4i32 => .signed,
.Vector3i32 => .signed,
.Vector2i32 => .signed,
.Vector4u32 => .unsigned,
.Vector3u32 => .unsigned,
.Vector2u32 => .unsigned,
else => .unsigned,
};
}
};
+32
View File
@@ -10,12 +10,14 @@ const Self = @This();
buffer: []const SpvWord,
index: usize,
did_jump: bool,
next_force_skip: ?usize,
pub fn init(buffer: []const SpvWord) Self {
return .{
.buffer = buffer,
.index = 0,
.did_jump = false,
.next_force_skip = null,
};
}
@@ -25,15 +27,37 @@ pub inline fn nextOrNull(self: *Self) ?SpvWord {
return word;
}
/// self.index + index will be automatically skipped
pub inline fn forceSkipIndex(self: *Self, index: SpvWord) void {
self.next_force_skip = self.index + index;
}
pub inline fn nextAsOrNull(self: *Self, comptime E: type) ?E {
if (self.next_force_skip) |skip_index| {
if (self.index == skip_index) {
_ = self.skip();
self.next_force_skip = null;
}
}
return if (self.nextOrNull()) |word| std.enums.fromInt(E, word) else null;
}
pub inline fn next(self: *Self) RuntimeError!SpvWord {
if (self.next_force_skip) |skip_index| {
if (self.index == skip_index) {
_ = self.skip();
self.next_force_skip = null;
}
}
return self.nextOrNull() orelse return RuntimeError.InvalidSpirV;
}
pub inline fn nextAs(self: *Self, comptime E: type) RuntimeError!E {
if (self.next_force_skip) |skip_index| {
if (self.index == skip_index) {
_ = self.skip();
}
}
return self.nextAsOrNull(E) orelse return RuntimeError.InvalidSpirV;
}
@@ -59,6 +83,7 @@ pub inline fn skipN(self: *Self, count: usize) bool {
pub inline fn skipToEnd(self: *Self) void {
self.index = self.buffer.len;
self.did_jump = true;
}
pub inline fn emitSourceLocation(self: *const Self) usize {
@@ -71,3 +96,10 @@ pub inline fn jumpToSourceLocation(self: *Self, source_location: usize) bool {
self.did_jump = true;
return true;
}
/// Like jumpToSourceLocation without toggling self.did_jump
pub inline fn goToSourceLocation(self: *Self, source_location: usize) bool {
if (source_location > self.buffer.len) return false;
self.index = source_location;
return true;
}
+14 -3
View File
@@ -21,7 +21,7 @@
//!
//! try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
//! var output: [4]f32 = undefined;
//! try rt.readOutput(f32, output[0..output.len], try rt.getResultByName("color"));
//! try rt.readOutput(std.mem.asBytes(output[0..output.len]), try rt.getResultByName("color"));
//! std.log.info("Output: Vec4{any}", .{output});
//! }
//! std.log.info("Successfully executed", .{});
@@ -30,18 +30,29 @@
const std = @import("std");
pub const Image = @import("Image.zig");
pub const Module = @import("Module.zig");
pub const Runtime = @import("Runtime.zig");
const opcodes = @import("opcodes.zig");
const spv = @import("spv.zig");
pub const spv = @import("spv.zig");
pub const SpvVoid = spv.SpvVoid;
pub const SpvByte = spv.SpvByte;
pub const SpvWord = spv.SpvWord;
pub const SpvBool = spv.SpvBool;
pub const Vec4f32 = @Vector(4, f32);
pub const Vec3f32 = @Vector(3, f32);
pub const Vec2f32 = @Vector(2, f32);
pub const Vec4i32 = @Vector(4, i32);
pub const Vec3i32 = @Vector(3, i32);
pub const Vec2i32 = @Vector(2, i32);
pub const Vec4u32 = @Vector(4, u32);
pub const Vec3u32 = @Vector(3, u32);
pub const Vec2u32 = @Vector(2, u32);
pub const GLSL_std_450 = @import("GLSL_std_450/opcodes.zig");
/// Maximum number of input locations per module
+1999 -494
View File
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -26,5 +26,10 @@ test "Simple array" {
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(f32, 4, code, "color", &.{ 4, 3, 2, 1 });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]f32{ 4, 3, 2, 1 }),
},
});
}
+6 -1
View File
@@ -25,5 +25,10 @@ test "Simple fragment shader" {
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(f32, 4, code, "color", &.{ 4, 3, 2, 1 });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]f32{ 4, 3, 2, 1 }),
},
});
}
+13 -3
View File
@@ -73,7 +73,12 @@ test "Bitwise primitives" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T{ expected, expected, expected, expected }),
},
});
}
}
}
@@ -96,7 +101,7 @@ test "Bitwise vectors" {
inline for (types) |T| {
const op1: case.Vec(L, T) = .{ .val = case.random(@Vector(L, T)) };
var op2: case.Vec(L, T) = .{ .val = case.random(@Vector(L, T)) };
for (0..L) |i| op2.val[i] = @mod(op2.val[i], @bitSizeOf(T));
inline for (0..L) |i| op2.val[i] = @mod(op2.val[i], @bitSizeOf(T));
const expected = switch (op.key) {
.BitwiseAnd => op1.val & op2.val,
.BitwiseOr => op1.val | op2.val,
@@ -142,7 +147,12 @@ test "Bitwise vectors" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, L, code, "color", &@as([L]T, expected));
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&@as([L]T, expected)),
},
});
}
}
}
+6 -1
View File
@@ -93,7 +93,12 @@ test "Simple branching" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T{ expected, expected, expected, expected }),
},
});
}
}
}
+12 -2
View File
@@ -55,7 +55,12 @@ test "Primitives casts" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T[1], 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T[1]{ expected, expected, expected, expected }),
},
});
}
}
@@ -103,6 +108,11 @@ test "Primitives bitcasts" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T[1], 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T[1]{ expected, expected, expected, expected }),
},
});
}
}
+12 -2
View File
@@ -44,7 +44,12 @@ test "Simple function calls" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, 4, code, "color", &.{ n, n, n, n });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T{ n, n, n, n }),
},
});
}
}
@@ -95,6 +100,11 @@ test "Nested function calls" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, 4, code, "color", &.{ n, n, n, n });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T{ n, n, n, n }),
},
});
}
}
+9 -1
View File
@@ -45,7 +45,15 @@ test "Inputs" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutputWithInput(T, L, code, "color", &@as([L]T, input.val), "pos", &@as([L]T, input.val));
try case.expect(.{
.source = code,
.inputs = &.{
std.mem.asBytes(&@as([L]T, input.val)),
},
.expected_outputs = &.{
std.mem.asBytes(&@as([L]T, input.val)),
},
});
}
}
}
+6 -1
View File
@@ -47,5 +47,10 @@ test "Simple while loop" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(f32, 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]f32{ expected, expected, expected, expected }),
},
});
}
+12 -2
View File
@@ -72,7 +72,12 @@ test "Maths primitives" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, 4, code, "color", &.{ expected, expected, expected, expected });
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]T{ expected, expected, expected, expected }),
},
});
}
}
}
@@ -139,7 +144,12 @@ test "Maths vectors" {
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expectOutput(T, L, code, "color", &@as([L]T, expected));
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&@as([L]T, expected)),
},
});
}
}
}
+36 -35
View File
@@ -20,34 +20,15 @@ pub fn compileNzsl(allocator: std.mem.Allocator, source: []const u8) ![]const u3
}
pub const case = struct {
pub fn expectOutput(comptime T: type, comptime len: usize, source: []const u32, output_name: []const u8, expected: []const T) !void {
const allocator = std.testing.allocator;
pub const Config = struct {
source: []const u32,
inputs: []const []const u8 = &.{},
expected_outputs: []const []const u8 = &.{},
descriptor_sets: []const []const []u8 = &.{},
expected_descriptor_sets: []const []const []const u8 = &.{},
};
const module_options = [_]spv.Module.ModuleOptions{
.{
.use_simd_vectors_specializations = true,
},
.{
.use_simd_vectors_specializations = false,
},
};
for (module_options) |opt| {
var module = try spv.Module.init(allocator, source, opt);
defer module.deinit(allocator);
var rt = try spv.Runtime.init(allocator, &module);
defer rt.deinit(allocator);
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var output: [len]T = undefined;
try rt.readOutput(std.mem.sliceAsBytes(output[0..]), try rt.getResultByName(output_name));
try std.testing.expectEqualSlices(T, expected, &output);
}
}
pub fn expectOutputWithInput(comptime T: type, comptime len: usize, source: []const u32, output_name: []const u8, expected: []const T, input_name: []const u8, input: []const T) !void {
pub fn expect(config: Config) !void {
const allocator = std.testing.allocator;
// To test with all important module options
@@ -61,24 +42,43 @@ pub const case = struct {
};
for (module_options) |opt| {
var module = try spv.Module.init(allocator, source, opt);
var module = try spv.Module.init(allocator, config.source, opt);
defer module.deinit(allocator);
var rt = try spv.Runtime.init(allocator, &module);
var rt = try spv.Runtime.init(allocator, &module, undefined);
defer rt.deinit(allocator);
try rt.writeInput(std.mem.sliceAsBytes(input[0..len]), try rt.getResultByName(input_name));
for (config.inputs, 0..) |input, n| {
try rt.writeInput(input[0..], module.input_locations[n]);
}
for (config.descriptor_sets, 0..) |descriptor_set, set_index| {
for (descriptor_set, 0..) |descriptor_binding, binding_index| {
try rt.writeDescriptorSet(descriptor_binding, @intCast(set_index), @intCast(binding_index), 0);
}
}
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
var output: [len]T = undefined;
try rt.readOutput(std.mem.sliceAsBytes(output[0..]), try rt.getResultByName(output_name));
try rt.flushDescriptorSets(allocator);
try std.testing.expectEqualSlices(T, expected, &output);
for (config.expected_outputs, 0..) |expected, n| {
const output = try allocator.alloc(u8, expected.len);
defer allocator.free(output);
try rt.readOutput(output[0..], module.output_locations[n]);
try std.testing.expectEqualSlices(u8, expected, output);
}
for (config.expected_descriptor_sets, config.descriptor_sets) |expected_descriptor_set, descriptor_set| {
for (expected_descriptor_set, descriptor_set) |expected_descriptor_binding, descriptor_binding| {
try std.testing.expectEqualSlices(u8, expected_descriptor_binding, descriptor_binding);
}
}
}
}
pub fn random(comptime T: type) T {
var prng: std.Random.DefaultPrng = .init(@intCast(std.time.microTimestamp()));
var prng: std.Random.DefaultPrng = .init(@intCast(std.Io.Timestamp.now(std.testing.io, .real).toMicroseconds()));
const rand = prng.random();
return switch (@typeInfo(T)) {
@@ -86,7 +86,7 @@ pub const case = struct {
.float => rand.float(T),
.vector => |v| blk: {
var vec: @Vector(v.len, v.child) = undefined;
for (0..v.len) |i| {
inline for (0..v.len) |i| {
vec[i] = random(v.child);
}
break :blk vec;
@@ -119,4 +119,5 @@ test {
std.testing.refAllDecls(@import("inputs.zig"));
std.testing.refAllDecls(@import("loops.zig"));
std.testing.refAllDecls(@import("maths.zig"));
std.testing.refAllDecls(@import("ssbo.zig"));
}
+60
View File
@@ -0,0 +1,60 @@
const std = @import("std");
const root = @import("root.zig");
const compileNzsl = root.compileNzsl;
const case = root.case;
test "Simple SSBO" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ [layout(std430)]
\\ struct SSBO
\\ {
\\ data: dyn_array[u32]
\\ }
\\
\\ external
\\ {
\\ [set(0), binding(0)] ssbo: storage[SSBO],
\\ }
\\
\\ [entry(compute)]
\\ [workgroup(16, 1, 1)]
\\ fn main()
\\ {
\\ for i in 0 -> 256
\\ {
\\ ssbo.data[i] = u32(i);
\\ }
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
var ssbo = [_]u32{0} ** 256;
var expected = [_]u32{0} ** 256;
for (expected[0..], 0..) |*val, i| {
val.* = @intCast(i);
}
try case.expect(.{
.source = code,
.descriptor_sets = &.{
// Set 0
&.{
// Binding 0
std.mem.asBytes(&ssbo),
},
},
.expected_descriptor_sets = &.{
// Set 0
&.{
// Binding 0
std.mem.asBytes(&expected),
},
},
});
}
+21 -76
View File
@@ -16,11 +16,8 @@ pub fn main() !void {
const allocator = fba.allocator();
const env = Env.init(allocator);
defer env.deinit(allocator);
var slowest = SlowTracker.init(allocator, 5);
defer slowest.deinit();
defer slowest.deinit(allocator);
var pass: usize = 0;
var fail: usize = 0;
@@ -46,13 +43,6 @@ pub fn main() !void {
var status = Status.pass;
slowest.startTiming();
const is_unnamed_test = isUnnamed(t);
if (env.filter) |f| {
if (!is_unnamed_test and std.mem.indexOf(u8, t.name, f) == null) {
continue;
}
}
const friendly_name = blk: {
const name = t.name;
var it = std.mem.splitScalar(u8, name, '.');
@@ -70,7 +60,7 @@ pub fn main() !void {
const result = t.func();
current_test = null;
const ns_taken = slowest.endTiming(friendly_name);
const ns_taken = slowest.endTiming(allocator, friendly_name);
if (std.testing.allocator_instance.deinit() == .leak) {
leak += 1;
@@ -89,20 +79,13 @@ pub fn main() !void {
fail += 1;
Printer.status(.fail, "\n{s}\n\"{s}\" - {s}\n{s}\n", .{ BORDER, friendly_name, @errorName(err), BORDER });
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
if (env.fail_first) {
break;
std.debug.dumpErrorReturnTrace(trace);
}
},
}
if (env.verbose) {
const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0;
Printer.status(status, "\x1b[35m[{d: >10.2} ms]\x1b[0m {s: <30}", .{ ms, friendly_name });
} else {
Printer.status(status, ".", .{});
}
const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0;
Printer.status(status, "\x1b[35m[{d: >10.2} ms]\x1b[0m {s: <30}", .{ ms, friendly_name });
}
for (builtin.test_functions) |t| {
@@ -126,7 +109,7 @@ pub fn main() !void {
Printer.fmt("\n", .{});
try slowest.display();
Printer.fmt("\n", .{});
std.posix.exit(if (fail == 0) 0 else 1);
std.process.exit(if (fail == 0) 0 else 1);
}
const Printer = struct {
@@ -155,42 +138,41 @@ const SlowTracker = struct {
const SlowestQueue = std.PriorityDequeue(TestInfo, void, compareTiming);
max: usize,
slowest: SlowestQueue,
timer: std.time.Timer,
timer: std.Io.Timestamp,
fn init(allocator: Allocator, count: u32) SlowTracker {
const timer = std.time.Timer.start() catch @panic("failed to start timer");
var slowest = SlowestQueue.init(allocator, {});
slowest.ensureTotalCapacity(count) catch @panic("OOM");
var slowest = SlowestQueue.empty;
slowest.ensureTotalCapacity(allocator, count) catch @panic("OOM");
return .{
.max = count,
.timer = timer,
.timer = std.Io.Timestamp.now(std.testing.io, .real),
.slowest = slowest,
};
}
const TestInfo = struct {
ns: u64,
ns: i96,
name: []const u8,
};
fn deinit(self: SlowTracker) void {
self.slowest.deinit();
fn deinit(self: *SlowTracker, allocator: std.mem.Allocator) void {
self.slowest.deinit(allocator);
}
fn startTiming(self: *SlowTracker) void {
self.timer.reset();
self.timer = std.Io.Timestamp.now(std.testing.io, .real);
}
fn endTiming(self: *SlowTracker, test_name: []const u8) u64 {
var timer = self.timer;
const ns = timer.lap();
fn endTiming(self: *SlowTracker, allocator: std.mem.Allocator, test_name: []const u8) i96 {
const duration = self.timer.untilNow(std.testing.io, .real);
const ns = duration.toNanoseconds();
var slowest = &self.slowest;
if (slowest.count() < self.max) {
// Capacity is fixed to the # of slow tests we want to track
// If we've tracked fewer tests than this capacity, than always add
slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing");
slowest.push(allocator, TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing");
return ns;
}
@@ -205,8 +187,8 @@ const SlowTracker = struct {
}
// the previous fastest of our slow tests, has been pushed off.
_ = slowest.removeMin();
slowest.add(TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing");
_ = slowest.popMin();
slowest.push(allocator, TestInfo{ .ns = ns, .name = test_name }) catch @panic("failed to track test timing");
return ns;
}
@@ -214,7 +196,7 @@ const SlowTracker = struct {
var slowest = self.slowest;
const count = slowest.count();
Printer.fmt("Slowest {d} test{s}: \n", .{ count, if (count != 1) "s" else "" });
while (slowest.removeMinOrNull()) |info| {
while (slowest.popMin()) |info| {
const ms = @as(f64, @floatFromInt(info.ns)) / 1_000_000.0;
Printer.fmt(" {d:.2}ms\t{s}\n", .{ ms, info.name });
}
@@ -226,43 +208,6 @@ const SlowTracker = struct {
}
};
const Env = struct {
verbose: bool,
fail_first: bool,
filter: ?[]const u8,
fn init(allocator: Allocator) Env {
return .{
.verbose = readEnvBool(allocator, "TEST_VERBOSE", true),
.fail_first = readEnvBool(allocator, "TEST_FAIL_FIRST", false),
.filter = readEnv(allocator, "TEST_FILTER"),
};
}
fn deinit(self: Env, allocator: Allocator) void {
if (self.filter) |f| {
allocator.free(f);
}
}
fn readEnv(allocator: Allocator, key: []const u8) ?[]const u8 {
const v = std.process.getEnvVarOwned(allocator, key) catch |err| {
if (err == error.EnvironmentVariableNotFound) {
return null;
}
std.log.warn("failed to get env var {s} due to err {}", .{ key, err });
return null;
};
return v;
}
fn readEnvBool(allocator: Allocator, key: []const u8, deflt: bool) bool {
const value = readEnv(allocator, key) orelse return deflt;
defer allocator.free(value);
return std.ascii.eqlIgnoreCase(value, "true");
}
};
pub const panic = std.debug.FullPanic(struct {
pub fn panicFn(msg: []const u8, first_trace_addr: ?usize) noreturn {
if (current_test) |ct| {
+77
View File
@@ -0,0 +1,77 @@
#include <stdio.h>
#include <SpirvInterpreter.h>
static const unsigned char shader_source[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x82, 0x10, 0x27, 0x00, 0x17, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00,
0x0f, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x10, 0x00, 0x03, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00,
0x09, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x04, 0x00, 0x05, 0x00, 0x56, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
0x07, 0x00, 0x00, 0x00, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x00, 0x00, 0x06, 0x00, 0x05, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00,
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x15, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x2b, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2b, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f,
0x2b, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
0x2b, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40,
0x2b, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40,
0x20, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x36, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x08, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00,
0x04, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00,
0x13, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x13, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
0x15, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x04, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00,
0x06, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00
};
int main(void)
{
SpvModule module;
SpvModuleOptions options;
options.use_simd_vectors_specializations = 1;
if(SpvInitModule(&module, (SpvWord*)shader_source, sizeof(shader_source) / 4, options) != SPV_RESULT_SUCCESS)
{
fprintf(stderr, "Module init failed\n");
return -1;
}
SpvRuntime runtime;
if(SpvInitRuntime(&runtime, module, (SpvImageAPI){0}) != SPV_RESULT_SUCCESS)
{
fprintf(stderr, "Runtime init failed\n");
return -1;
}
SpvWord main_entry_index;
SpvGetEntryPointByName(runtime, "main", &main_entry_index);
SpvCallEntryPoint(runtime, main_entry_index);
float output[4];
SpvWord output_result;
SpvGetResultByName(runtime, "color", &output_result);
SpvReadOutput(runtime, (SpvByte*)output, sizeof(output), output_result);
printf("Output: Vec4[%f, %f, %f, %f]\n", output[0], output[1], output[2], output[3]);
SpvDeinitRuntime(runtime);
SpvDeinitModule(module);
return 0;
}