diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..14c9be0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,34 @@ +name: Build + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + deployments: write + +jobs: + build: + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'ci skip')" + + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + + - name: Building + run: zig build + + - name: Generating docs + run: zig build docs + + - name: Publish to Cloudflare Pages + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy zig-out/docs --project-name=spirv-interpreter-docs + gitHubToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..df521d9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: Test + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + deployments: write + +jobs: + build: + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, 'ci skip')" + + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + + - name: Test + run: zig build test diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..271cb80 --- /dev/null +++ b/build.zig @@ -0,0 +1,85 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.createModule(.{ + .root_source_file = b.path("src/lib.zig"), + .target = target, + .optimize = optimize, + }); + + const spirv_headers = b.dependency("spirv_headers", .{}); + mod.addSystemIncludePath(spirv_headers.path("include/spirv/unified1")); + + const lib = b.addLibrary(.{ + .name = "spirv_interpreter", + .root_module = mod, + .linkage = .dynamic, + }); + const lib_install = b.addInstallArtifact(lib, .{}); + + // Zig example setup + + const example_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 }, + }, + }), + }); + + const spirv_target = b.resolveTargetQuery(.{ + .cpu_arch = .spirv32, + .cpu_model = .{ .explicit = &std.Target.spirv.cpu.vulkan_v1_2 }, + .os_tag = .vulkan, + .ofmt = .spirv, + }); + + const shader = b.addObject(.{ + .name = "shader.zig", + .root_module = b.createModule(.{ + .root_source_file = b.path("example/shader.zig"), + .target = spirv_target, + }), + .use_llvm = false, + }); + + example_exe.root_module.addAnonymousImport("shader", .{ .root_source_file = shader.getEmittedBin() }); + + const example_install = b.addInstallArtifact(example_exe, .{}); + example_install.step.dependOn(&lib_install.step); + + const run_example = b.addRunArtifact(example_exe); + run_example.step.dependOn(&example_install.step); + + const run_example_step = b.step("example", "Run the basic example"); + run_example_step.dependOn(&run_example.step); + + // Zig unit tests setup + + const lib_tests = b.addTest(.{ .root_module = mod }); + 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, + }); + const install_docs = b.addInstallDirectory(.{ + .source_dir = autodoc_test.getEmittedDocs(), + .install_dir = .prefix, + .install_subdir = "docs", + }); + + const docs_step = b.step("docs", "Build and install the documentation"); + docs_step.dependOn(&install_docs.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..f58030b --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,18 @@ +.{ + .name = .SPIRV_Interpreter, + .version = "0.0.1", + .dependencies = .{ + .spirv_headers = .{ + .url = "git+https://github.com/KhronosGroup/SPIRV-Headers#0a7f626a6ae86284a413d105b47a6fb413bf6c92", + .hash = "N-V-__8AAGOhPABkbhEc-SenlpOzOI4ppi0CDXVSteii5ywV", + }, + }, + .minimum_zig_version = "0.15.2", + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + "LICENSE", + }, + .fingerprint = 0xd87ec8ab9fa9396a, +} diff --git a/example/main.zig b/example/main.zig new file mode 100644 index 0000000..05765cd --- /dev/null +++ b/example/main.zig @@ -0,0 +1,20 @@ +const std = @import("std"); +const spv = @import("spv"); + +const shader_source = @embedFile("shader"); + +pub fn main() !void { + { + var gpa: std.heap.DebugAllocator(.{}) = .init; + defer _ = gpa.deinit(); + + const allocator = gpa.allocator(); + + const ctx = try spv.Interpreter.init(); + defer ctx.deinit(); + + const module = try spv.Module.init(allocator, &ctx, @ptrCast(@alignCast(shader_source))); + defer module.deinit(allocator); + } + std.log.info("Successfully executed", .{}); +} diff --git a/example/shader.zig b/example/shader.zig new file mode 100644 index 0000000..478259f --- /dev/null +++ b/example/shader.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const gpu = std.gpu; + +extern var frag_color: @Vector(4, f32) addrspace(.output); + +export fn main() callconv(.spirv_fragment) void { + gpu.location(&frag_color, 0); + + frag_color = .{ 1.0, 1.0, 1.0, 1.0 }; +} diff --git a/src/Image.zig b/src/Image.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/Interpreter.zig b/src/Interpreter.zig new file mode 100644 index 0000000..d2e8273 --- /dev/null +++ b/src/Interpreter.zig @@ -0,0 +1,9 @@ +const Self = @This(); + +pub fn init() !Self { + return .{}; +} + +pub fn deinit(self: *const Self) void { + _ = self; +} diff --git a/src/Module.zig b/src/Module.zig new file mode 100644 index 0000000..0746041 --- /dev/null +++ b/src/Module.zig @@ -0,0 +1,58 @@ +const std = @import("std"); +const lib = @import("lib.zig"); + +const SpvVoid = lib.SpvVoid; +const SpvByte = lib.SpvByte; +const SpvWord = lib.SpvWord; +const SpvBool = lib.SpvBool; +const spv = lib.spv; + +const Interpreter = @import("Interpreter.zig"); +const WordIterator = @import("WordIterator.zig"); + +const Self = @This(); + +const SpvEntryPoint = struct { + exec_model: spv.SpvExecutionModel, + id: SpvWord, + name: []const u8, + globals_count: SpvWord, + globals: []const SpvWord, +}; + +ctx: *const Interpreter, + +it: WordIterator, + +version_major: SpvByte, +version_minor: SpvByte, +generator_magic: SpvWord, + +bound: SpvWord, + +code: []const SpvWord, + +addressing: spv.SpvAddressingModel, +memory_model: spv.SpvMemoryModel, + +entry_point_count: SpvWord, +entry_points: []const SpvEntryPoint, + +local_size_x: SpvWord, +local_size_y: SpvWord, +local_size_z: SpvWord, + +pub fn init(allocator: std.mem.Allocator, ctx: *const Interpreter, source: []const SpvWord) !Self { + var self: Self = std.mem.zeroInit(Self, .{ + .ctx = ctx, + .code = try allocator.dupe(SpvWord, source), + }); + + self.it = WordIterator.init(self.code); + + return self; +} + +pub fn deinit(self: *const Self, allocator: std.mem.Allocator) void { + allocator.free(self.code); +} diff --git a/src/State.zig b/src/State.zig new file mode 100644 index 0000000..fabf8c6 --- /dev/null +++ b/src/State.zig @@ -0,0 +1,9 @@ +const std = @import("std"); + +const Interpreter = @import("Interpreter.zig"); +const Module = @import("Module.zig"); + +const Self = @This(); + +ctx: *Interpreter, +owner: *Module, diff --git a/src/WordIterator.zig b/src/WordIterator.zig new file mode 100644 index 0000000..59f1aa8 --- /dev/null +++ b/src/WordIterator.zig @@ -0,0 +1,33 @@ +const lib = @import("lib.zig"); + +const SpvWord = lib.SpvWord; + +const Self = @This(); + +buffer: []const SpvWord, +index: usize, + +pub fn init(buffer: []const SpvWord) Self { + return .{ + .buffer = buffer, + .index = 0, + }; +} + +pub fn next(self: *Self) ?SpvWord { + const word = self.peek() orelse return null; + self.index += 1; + return word; +} + +pub fn peek(self: *const Self) ?SpvWord { + return if (self.index >= self.buffer.len) null else self.buffer[self.index]; +} + +pub fn skip(self: *Self) bool { + if (self.index >= self.buffer.len) { + return false; + } + self.index += 1; + return true; +} diff --git a/src/lib.zig b/src/lib.zig new file mode 100644 index 0000000..5cd3975 --- /dev/null +++ b/src/lib.zig @@ -0,0 +1,21 @@ +const std = @import("std"); +pub const spv = @cImport(@cInclude("spirv.h")); + +pub const Image = @import("Image.zig"); +pub const Interpreter = @import("Interpreter.zig"); +pub const Module = @import("Module.zig"); +pub const State = @import("State.zig"); +const opcode = @import("opcode.zig"); + +pub const SpvVoid = void; +pub const SpvByte = u8; +pub const SpvWord = u32; +pub const SpvBool = bool; + +test { + std.testing.refAllDecls(Image); + std.testing.refAllDecls(Interpreter); + std.testing.refAllDecls(Module); + std.testing.refAllDecls(State); + std.testing.refAllDecls(opcode); +} diff --git a/src/opcode.zig b/src/opcode.zig new file mode 100644 index 0000000..e69de29