diff --git a/LICENSE b/LICENSE
index e6dd6fd..21f254f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -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
diff --git a/README.md b/README.md
index ed99ac1..5ce173e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# SPIR-V Interpreter
-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");
@@ -29,3 +29,58 @@ pub fn main() !void {
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, sage] -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
+#include
+
+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;
+}
+```
diff --git a/build.zig b/build.zig
index 0c9f361..527b344 100644
--- a/build.zig
+++ b/build.zig
@@ -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);
+}
+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 example_exe = b.addExecutable(.{
+ 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);
}
diff --git a/ffi/SpirvInterpreter.h b/ffi/SpirvInterpreter.h
new file mode 100644
index 0000000..b134439
--- /dev/null
+++ b/ffi/SpirvInterpreter.h
@@ -0,0 +1,265 @@
+/*
+ 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;
+#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 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);
+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 SpvGetEntryPointByName(SpvRuntime runtime, const char* name, SpvWord* result);
+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 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,
+ };
+#endif /* spirv_H */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPIRV_INTERPRETER_H */
diff --git a/ffi/ffi.zig b/ffi/ffi.zig
new file mode 100644
index 0000000..9b6e81f
--- /dev/null
+++ b/ffi/ffi.zig
@@ -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");
+}
diff --git a/ffi/module.zig b/ffi/module.zig
new file mode 100644
index 0000000..0d52655
--- /dev/null
+++ b/ffi/module.zig
@@ -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);
+}
diff --git a/ffi/runtime.zig b/ffi/runtime.zig
new file mode 100644
index 0000000..5d2548b
--- /dev/null
+++ b/ffi/runtime.zig
@@ -0,0 +1,95 @@
+const std = @import("std");
+const ffi = @import("ffi.zig");
+const spv = ffi.spv;
+
+const CSpecializationEntry = extern struct {
+ id: spv.SpvWord,
+ offset: u32,
+ size: u32,
+};
+
+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,
+ };
+}
+
+export fn SpvInitRuntime(rt: **spv.Runtime, module: *spv.Module) callconv(.c) ffi.Result {
+ const allocator = std.heap.c_allocator;
+
+ rt.* = allocator.create(spv.Runtime) catch return .OutOfMemory;
+
+ rt.*.* = spv.Runtime.init(allocator, module) catch |err| {
+ allocator.destroy(rt.*);
+ return toCResult(err);
+ };
+ return .Success;
+}
+
+export fn SpvDeinitRuntime(rt: *spv.Runtime) callconv(.c) void {
+ const allocator = std.heap.c_allocator;
+ rt.deinit(allocator);
+ allocator.destroy(rt);
+}
+
+export fn SpvAddSpecializationInfo(rt: *spv.Runtime, entry: CSpecializationEntry, data: [*]const u8, data_size: u32) callconv(.c) ffi.Result {
+ const allocator = std.heap.c_allocator;
+ 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: *spv.Runtime, name: [*:0]const u8, result: *spv.SpvWord) callconv(.c) ffi.Result {
+ result.* = rt.getEntryPointByName(std.mem.span(name)) catch |err| return toCResult(err);
+ return .Success;
+}
+
+export fn SpvGetResultByName(rt: *spv.Runtime, name: [*:0]const u8, result: *spv.SpvWord) callconv(.c) ffi.Result {
+ result.* = rt.getResultByName(std.mem.span(name)) catch |err| return toCResult(err);
+ return .Success;
+}
+
+export fn SpvCallEntryPoint(rt: *spv.Runtime, entry_point: spv.SpvWord) callconv(.c) ffi.Result {
+ const allocator = std.heap.c_allocator;
+ rt.callEntryPoint(allocator, entry_point) catch |err| return toCResult(err);
+ return .Success;
+}
+
+export fn SpvReadOutput(rt: *spv.Runtime, output: [*]u8, output_size: u32, result: spv.SpvWord) callconv(.c) ffi.Result {
+ rt.readOutput(output[0..output_size], result) catch |err| return toCResult(err);
+ return .Success;
+}
+
+export fn SpvWriteInput(rt: *spv.Runtime, input: [*]const u8, input_size: u32, result: spv.SpvWord) callconv(.c) ffi.Result {
+ rt.writeInput(input[0..input_size], result) catch |err| return toCResult(err);
+ return .Success;
+}
+
+export fn SpvWriteBuiltIn(rt: *spv.Runtime, input: [*]const u8, input_size: u32, builtin: spv.spv.SpvBuiltIn) callconv(.c) ffi.Result {
+ rt.writeBuiltIn(input[0..input_size], builtin) catch |err| return toCResult(err);
+ return .Success;
+}
+
+export fn SpvWriteDescriptorSet(rt: *spv.Runtime, input: [*]const u8, input_size: u32, set: spv.SpvWord, binding: spv.SpvWord, descriptor_index: spv.SpvWord) callconv(.c) ffi.Result {
+ rt.writeDescriptorSet(input[0..input_size], set, binding, descriptor_index) catch |err| return toCResult(err);
+ return .Success;
+}
diff --git a/src/Runtime.zig b/src/Runtime.zig
index 9437d6b..1d264aa 100644
--- a/src/Runtime.zig
+++ b/src/Runtime.zig
@@ -210,19 +210,19 @@ fn pass(self: *Self, allocator: std.mem.Allocator, op_set: ?std.EnumSet(spv.SpvO
}
}
-pub fn writeDescriptorSet(self: *const Self, input: []u8, set: SpvWord, binding: SpvWord, descriptor_index: 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) {
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].write(input);
+ _ = try arr.values[descriptor_index].writeConst(input);
},
else => {
if (descriptor_index != 0)
return RuntimeError.NotFound;
- _ = try value.write(input);
+ _ = try value.writeConst(input);
},
}
} else {
diff --git a/src/lib.zig b/src/lib.zig
index 9657563..1bae108 100644
--- a/src/lib.zig
+++ b/src/lib.zig
@@ -35,7 +35,7 @@ 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;
diff --git a/test_c/main.c b/test_c/main.c
new file mode 100644
index 0000000..341fb2c
--- /dev/null
+++ b/test_c/main.c
@@ -0,0 +1,77 @@
+#include
+#include
+
+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) != 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;
+}