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; +}