diff --git a/README.md b/README.md index bc2b688..03aa4f8 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ 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")); - std.log.info("Output: Vec4[{d}, {d}, {d}, {d}]", .{ output[0], output[1], output[2], output[3] }); + std.log.info("Output: Vec4{any}", .{output}); } std.log.info("Successfully executed", .{}); } diff --git a/build.zig b/build.zig index f12cc2b..ca86383 100644 --- a/build.zig +++ b/build.zig @@ -35,6 +35,9 @@ pub fn build(b: *std.Build) void { }), }); + const sdl3 = b.lazyDependency("sdl3", .{}) orelse return; + example_exe.root_module.addImport("sdl3", sdl3.module("sdl3")); + const example_install = b.addInstallArtifact(example_exe, .{}); example_install.step.dependOn(&lib_install.step); diff --git a/build.zig.zon b/build.zig.zon index ad2066f..1a5fa89 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -11,6 +11,11 @@ .hash = "NZSL-1.1.2-N0xSVMt6AAC1ncQHA_RafnclWolDA477iTnFmZgdvxd-", .lazy = true, }, + .sdl3 = .{ + .url = "git+https://codeberg.org/7Games/zig-sdl3?ref=master#eefd1b86205ed4fbc5f5274b5ba34aa97798d693", + .hash = "sdl3-0.1.6-NmT1Q8kQJgCrbdH9Z3ZmLo5uJ1et3oxhfYazrNTtfsgv", + .lazy = true, + }, }, .minimum_zig_version = "0.15.2", .paths = .{ diff --git a/example/main.zig b/example/main.zig index dd7ba41..7d91430 100644 --- a/example/main.zig +++ b/example/main.zig @@ -1,25 +1,95 @@ const std = @import("std"); +const sdl3 = @import("sdl3"); const spv = @import("spv"); const shader_source = @embedFile("shader.spv"); +const screen_width = 640; +const screen_height = 480; + pub fn main() !void { { var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); + defer sdl3.shutdown(); + const init_flags = sdl3.InitFlags{ .video = true }; + try sdl3.init(init_flags); + defer sdl3.quit(init_flags); + + const window = try sdl3.video.Window.init("Hello SDL3", screen_width, screen_height, .{}); + defer window.deinit(); + const allocator = gpa.allocator(); var module = try spv.Module.init(allocator, @ptrCast(@alignCast(shader_source))); defer module.deinit(allocator); - var rt = try spv.Runtime.init(allocator, &module); - defer rt.deinit(allocator); + const surface = try window.getSurface(); + try surface.clear(.{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 0.0 }); - try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main")); - var output: [4]i32 = undefined; - try rt.readOutput(i32, output[0..], try rt.getResultByName("color")); - std.log.info("Output: Vec4{any}", .{output}); + { + try surface.lock(); + defer surface.unlock(); + + const map = surface.getPixels() orelse return; + + var pool: std.Thread.Pool = undefined; + try pool.init(.{ + .allocator = allocator, + .n_jobs = 16384, + }); + defer pool.deinit(); + + var wait_group: std.Thread.WaitGroup = .{}; + + const margin_x = @divTrunc(screen_width, 5); + const margin_y = @divTrunc(screen_height, 5); + const top_y = margin_y; + const bottom_y = (screen_height - 1) - margin_y; + const center_x = @divTrunc(screen_width, 2); + const tri_h = bottom_y - top_y; + const max_half_w = @divTrunc(screen_width, 2) - margin_x; + + for (top_y..bottom_y) |y| { + const t: f32 = @as(f32, @floatFromInt(y - top_y)) / @as(f32, @floatFromInt(tri_h)); + const half_w: usize = @intFromFloat((t * @as(f32, @floatFromInt(max_half_w))) + 0.5); + const x0 = std.math.clamp(center_x - half_w, 0, screen_width - 1); + const x1 = std.math.clamp(center_x + half_w, 0, screen_width - 1); + + for (x0..x1) |x| { + pool.spawnWg(&wait_group, run, .{ allocator, &module, map, surface, x, y }); + } + } + pool.waitAndWork(&wait_group); + } + + try window.updateSurface(); + + std.Thread.sleep(5_000_000_000); } std.log.info("Successfully executed", .{}); } + +fn run(allocator: std.mem.Allocator, module: *spv.Module, map: []u8, surface: sdl3.surface.Surface, x: usize, y: usize) void { + var rt = spv.Runtime.init(allocator, module) catch |err| std.debug.panic("Failed with error {s}", .{@errorName(err)}); + defer rt.deinit(allocator); + + var output: [4]f32 = undefined; + + const entry = rt.getEntryPointByName("main") catch |err| std.debug.panic("Failed with error {s}", .{@errorName(err)}); + const color = rt.getResultByName("color") catch |err| std.debug.panic("Failed with error {s}", .{@errorName(err)}); + + rt.callEntryPoint(allocator, entry) catch |err| std.debug.panic("Failed with error {s}", .{@errorName(err)}); + rt.readOutput(f32, output[0..], color) catch |err| std.debug.panic("Failed with error {s}", .{@errorName(err)}); + + const rgba = surface.mapRgba( + @intFromFloat(output[0] * 255.0), + @intFromFloat(output[1] * 255.0), + @intFromFloat(output[2] * 255.0), + @intFromFloat(output[3] * 255.0), + ); + + var pixel_map: [*]u32 = @as([*]u32, @ptrCast(@alignCast(map.ptr))); + pixel_map[(y * surface.getWidth()) + x] = rgba.value; +} diff --git a/example/shader.nzsl b/example/shader.nzsl index 4e5c3e8..ebb0c2f 100644 --- a/example/shader.nzsl +++ b/example/shader.nzsl @@ -1,17 +1,20 @@ [nzsl_version("1.1")] module; +struct FragIn +{ + [location(0)] pos: vec2[f32] +} + struct FragOut { - [location(0)] color: vec4[i32] + [location(0)] color: vec4[f32] } [entry(frag)] -fn main() -> FragOut +fn main(input: FragIn) -> FragOut { - let base: i32 = 4; - let value: i32 = base >> 3; let output: FragOut; - output.color = vec4[i32](value, value, value, value); + output.color = vec4[f32](1.0, 0.0, 0.0, 1.0); return output; } diff --git a/example/shader.spv b/example/shader.spv index 09b51e0..77cdfa3 100644 Binary files a/example/shader.spv and b/example/shader.spv differ diff --git a/example/shader.spv.txt b/example/shader.spv.txt index 592e083..a2efb61 100644 --- a/example/shader.spv.txt +++ b/example/shader.spv.txt @@ -1,6 +1,6 @@ Version 1.0 Generator: 2560130 -Bound: 29 +Bound: 21 Schema: 0 OpCapability Capability(Shader) OpMemoryModel AddressingModel(Logical) MemoryModel(GLSL450) @@ -16,35 +16,25 @@ Schema: 0 OpMemberDecorate %7 0 Decoration(Offset) 0 %1 = OpTypeVoid %2 = OpTypeFunction %1 - %3 = OpTypeInt 32 1 + %3 = OpTypeFloat 32 %4 = OpTypeVector %3 4 %5 = OpTypePointer StorageClass(Output) %4 %7 = OpTypeStruct %4 - %8 = OpConstant %3 i32(4) - %9 = OpTypePointer StorageClass(Function) %3 -%10 = OpConstant %3 i32(3) -%11 = OpTypePointer StorageClass(Function) %7 -%12 = OpConstant %3 i32(0) -%26 = OpTypePointer StorageClass(Function) %4 + %8 = OpTypePointer StorageClass(Function) %7 + %9 = OpTypeInt 32 1 +%10 = OpConstant %9 i32(0) +%11 = OpConstant %3 f32(1) +%12 = OpConstant %3 f32(0) +%18 = OpTypePointer StorageClass(Function) %4 %6 = OpVariable %5 StorageClass(Output) %13 = OpFunction %1 FunctionControl(0) %2 %14 = OpLabel -%15 = OpVariable %9 StorageClass(Function) -%16 = OpVariable %9 StorageClass(Function) -%17 = OpVariable %11 StorageClass(Function) - OpStore %15 %8 -%18 = OpLoad %3 %15 -%19 = OpShiftRightArithmetic %3 %18 %10 - OpStore %16 %19 -%20 = OpLoad %3 %16 -%21 = OpLoad %3 %16 -%22 = OpLoad %3 %16 -%23 = OpLoad %3 %16 -%24 = OpCompositeConstruct %4 %20 %21 %22 %23 -%25 = OpAccessChain %26 %17 %12 - OpStore %25 %24 -%27 = OpLoad %7 %17 -%28 = OpCompositeExtract %4 %27 0 - OpStore %6 %28 +%15 = OpVariable %8 StorageClass(Function) +%16 = OpCompositeConstruct %4 %11 %12 %12 %11 +%17 = OpAccessChain %18 %15 %10 + OpStore %17 %16 +%19 = OpLoad %7 %15 +%20 = OpCompositeExtract %4 %19 0 + OpStore %6 %20 OpReturn OpFunctionEnd diff --git a/src/Module.zig b/src/Module.zig index 1e7f3df..e67d33e 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -159,15 +159,15 @@ pub fn init(allocator: std.mem.Allocator, source: []const SpvWord) ModuleError!S 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; } + //@import("pretty").print(allocator, self.results, .{ + // .tab_size = 4, + // .max_depth = 0, + // .struct_max_len = 0, + // .array_max_len = 0, + //}) catch return ModuleError.OutOfMemory; + return self; } @@ -205,6 +205,10 @@ fn populateMaps(self: *Self, allocator: std.mem.Allocator) ModuleError!void { for (self.results, 0..) |result, id| { if (result.variant == null or std.meta.activeTag(result.variant.?) != .Variable) continue; switch (result.variant.?.Variable.storage_class) { + .Input => for (result.decorations.items) |decoration| switch (decoration.rtype) { + .Location => self.input_locations.append(allocator, @intCast(id)) catch return ModuleError.OutOfMemory, + else => {}, + }, .Output => for (result.decorations.items) |decoration| switch (decoration.rtype) { .Location => self.output_locations.append(allocator, @intCast(id)) catch return ModuleError.OutOfMemory, else => {}, diff --git a/src/Runtime.zig b/src/Runtime.zig index e66d8cf..11f3519 100644 --- a/src/Runtime.zig +++ b/src/Runtime.zig @@ -22,6 +22,12 @@ pub const RuntimeError = error{ InvalidEntryPoint, ToDo, DivisionByZero, + InvalidValueType, +}; + +pub const ReadOutputError = error{ + NotFound, + InvalidValueType, }; pub const Function = struct { @@ -147,11 +153,11 @@ pub fn callEntryPoint(self: *Self, allocator: std.mem.Allocator, entry_point_ind //}) catch return RuntimeError.OutOfMemory; } -pub fn readOutput(self: *const Self, comptime T: type, output: []T, result: SpvWord) error{NotFound}!void { +pub fn readOutput(self: *const Self, comptime T: type, output: []T, result: SpvWord) ReadOutputError!void { if (std.mem.indexOf(SpvWord, self.mod.output_locations.items, &.{result})) |_| { - self.readValue(T, output, &self.results[result].variant.?.Variable.value); + try self.readValue(T, output, &self.results[result].variant.?.Variable.value); } else { - return error.NotFound; + return ReadOutputError.NotFound; } } @@ -160,13 +166,13 @@ fn reset(self: *Self) void { self.current_function = null; } -fn readValue(self: *const Self, comptime T: type, output: []T, value: *const Result.Value) void { +fn readValue(self: *const Self, comptime T: type, output: []T, value: *const Result.Value) ReadOutputError!void { switch (value.*) { .Bool => |b| { if (T == bool) { output[0] = b; } else { - unreachable; // Wanted value may not be composed of booleans + return ReadOutputError.InvalidValueType; } }, .Int => |i| { @@ -179,7 +185,7 @@ fn readValue(self: *const Self, comptime T: type, output: []T, value: *const Res u16 => output[0] = i.uint16, u32 => output[0] = i.uint32, u64 => output[0] = i.uint64, - inline else => unreachable, // Wanted value may not be composed of ints + inline else => return ReadOutputError.InvalidValueType, } }, .Float => |f| { @@ -187,13 +193,13 @@ fn readValue(self: *const Self, comptime T: type, output: []T, value: *const Res f16 => output[0] = f.float16, f32 => output[0] = f.float32, f64 => output[0] = f.float64, - inline else => unreachable, // Wanted value may not be composed of floats + inline else => return ReadOutputError.InvalidValueType, } }, - .Vector => |values| for (values, 0..) |v, i| self.readValue(T, output[i..], &v), - .Matrix => |values| for (values, 0..) |v, i| self.readValue(T, output[i..], &v), - .Array => unreachable, // TODO - .Structure => |values| for (values, 0..) |v, i| self.readValue(T, output[i..], &v), - else => unreachable, + .Vector => |values| for (values, 0..) |v, i| try self.readValue(T, output[i..], &v), + .Matrix => |values| for (values, 0..) |v, i| try self.readValue(T, output[i..], &v), + .Array => |values| for (values, 0..) |v, i| try self.readValue(T, output[i..], &v), // Doubt if this is allowed + .Structure => |values| for (values, 0..) |v, i| try self.readValue(T, output[i..], &v), + else => return ReadOutputError.InvalidValueType, } } diff --git a/src/lib.zig b/src/lib.zig index f64fbf1..e4a1bc1 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -1,3 +1,33 @@ +//! A small footprint SPIR-V interpreter with zero dependencies to execute SPIR-V shaders on the CPU. It is designed to be used with multiple runtimes concurrently. +//! +//! ```zig +//! const std = @import("std"); +//! const spv = @import("spv"); +//! +//! const shader_source = @embedFile("shader.spv"); +//! +//! pub fn main() !void { +//! { +//! var gpa: std.heap.DebugAllocator(.{}) = .init; +//! defer _ = gpa.deinit(); +//! +//! const allocator = gpa.allocator(); +//! +//! var module = try spv.Module.init(allocator, @ptrCast(@alignCast(shader_source))); +//! 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: [4]f32 = undefined; +//! try rt.readOutput(f32, output[0..output.len], try rt.getResultByName("color")); +//! std.log.info("Output: Vec4{any}", .{output}); +//! } +//! std.log.info("Successfully executed", .{}); +//! } +//! ``` + const std = @import("std"); pub const Image = @import("Image.zig"); @@ -6,11 +36,3 @@ pub const Runtime = @import("Runtime.zig"); const opcodes = @import("opcodes.zig"); const spv = @import("spv.zig"); - -test { - std.testing.refAllDecls(Image); - std.testing.refAllDecls(Module); - std.testing.refAllDecls(Runtime); - std.testing.refAllDecls(opcodes); - std.testing.refAllDecls(spv); -} diff --git a/src/opcodes.zig b/src/opcodes.zig index d475317..121655c 100644 --- a/src/opcodes.zig +++ b/src/opcodes.zig @@ -24,6 +24,7 @@ const MathOp = enum { Div, Mod, Mul, + Rem, Sub, }; @@ -463,10 +464,8 @@ fn MathEngine(comptime T: ValueType, comptime Op: MathOp) type { if (op2 == 0) return RuntimeError.DivisionByZero; break :blk if (@typeInfo(TT) == .int) @divTrunc(op1, op2) else op1 / op2; }, - .Mod => blk: { - if (op2 == 0) return RuntimeError.DivisionByZero; - break :blk @mod(op1, op2); - }, + .Mod => if (op2 == 0) return RuntimeError.DivisionByZero else @mod(op1, op2), + .Rem => if (op2 == 0) return RuntimeError.DivisionByZero else @rem(op1, op2), }; }