From 27172539e57fb80a03f0b262125b81ba6558673b Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Fri, 23 Jan 2026 02:09:30 +0100 Subject: [PATCH] working on example and adding opcodes --- build.zig | 2 +- example/main.zig | 157 ++++++++++++-------- example/shader.nzsl | 5 +- example/shader.spv | Bin 1168 -> 1360 bytes example/shader.spv.txt | 135 +++++++++-------- sandbox/main.zig | 16 +- sandbox/shader.nzsl | 66 ++++++++- sandbox/shader.spv | Bin 544 -> 4620 bytes sandbox/shader.spv.txt | 325 ++++++++++++++++++++++++++++++++++++----- src/Module.zig | 2 + src/Runtime.zig | 11 +- src/opcodes.zig | 281 +++++++++++++++++++++++------------ src/spv.zig | 2 + 13 files changed, 736 insertions(+), 266 deletions(-) diff --git a/build.zig b/build.zig index e6057c5..b5ea803 100644 --- a/build.zig +++ b/build.zig @@ -4,7 +4,7 @@ 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") orelse false; + const use_llvm = b.option(bool, "use-llvm", "use llvm") orelse (b.release_mode != .off); const mod = b.createModule(.{ .root_source_file = b.path("src/lib.zig"), diff --git a/example/main.zig b/example/main.zig index dd105e0..88723e6 100644 --- a/example/main.zig +++ b/example/main.zig @@ -4,91 +4,120 @@ const spv = @import("spv"); const shader_source = @embedFile("shader.spv"); -const screen_width = 640; -const screen_height = 480; +const screen_width = 1250; +const screen_height = 720; pub fn main() !void { { //var gpa: std.heap.DebugAllocator(.{}) = .init; //defer _ = gpa.deinit(); - var gpa = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer gpa.deinit(); - defer sdl3.shutdown(); - const init_flags = sdl3.InitFlags{ .video = true }; + const init_flags = sdl3.InitFlags{ .video = true, .events = true }; try sdl3.init(init_flags); defer sdl3.quit(init_flags); const window = try sdl3.video.Window.init("Hello triangle", screen_width, screen_height, .{}); defer window.deinit(); - const allocator = gpa.allocator(); + const surface = try window.getSurface(); + + const allocator = std.heap.smp_allocator; var module = try spv.Module.init(allocator, @ptrCast(@alignCast(shader_source)), .{}); defer module.deinit(allocator); - const surface = try window.getSurface(); - try surface.clear(.{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 0.0 }); - - { - try surface.lock(); - defer surface.unlock(); - - var pixel_map: [*]u32 = @as([*]u32, @ptrCast(@alignCast((surface.getPixels() orelse return).ptr))); - - const margin_x = @divTrunc(screen_width, 3); - const margin_y = @divTrunc(screen_height, 3); - 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; - - var timer = try std.time.Timer.start(); - defer { - const ns = timer.lap(); - std.log.info("Took {d:.3}s to render", .{@as(f32, @floatFromInt(ns)) / std.time.ns_per_s}); - } - - 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| { - var rt = try spv.Runtime.init(allocator, &module); - defer rt.deinit(allocator); - - var output: [4]f32 = undefined; - - const entry = try rt.getEntryPointByName("main"); - const color = try rt.getResultByName("color"); - const dim = try rt.getResultByName("dim"); - const pos = try rt.getResultByName("pos"); - - try rt.writeInput(f32, &.{ @floatFromInt(x1 - x0), @floatFromInt(bottom_y - top_y) }, dim); - try rt.writeInput(f32, &.{ @floatFromInt(x), @floatFromInt(y) }, pos); - - try rt.callEntryPoint(allocator, entry); - try rt.readOutput(f32, output[0..], color); - - const rgba = surface.mapRgba( - @truncate(@as(u32, @intFromFloat(output[0] * 255.0))), - @truncate(@as(u32, @intFromFloat(output[1] * 255.0))), - @truncate(@as(u32, @intFromFloat(output[2] * 255.0))), - @truncate(@as(u32, @intFromFloat(output[3] * 255.0))), - ); - - pixel_map[(y * surface.getWidth()) + x] = rgba.value; - } + var runner_cache: std.ArrayList(Runner) = try .initCapacity(allocator, screen_height); + defer { + for (runner_cache.items) |*runner| { + runner.rt.deinit(allocator); } + runner_cache.deinit(allocator); } - try window.updateSurface(); + for (0..screen_height) |_| { + (try runner_cache.addOne(allocator)).* = .{ + .allocator = allocator, + .surface = surface, + .rt = try spv.Runtime.init(allocator, &module), + }; + } - std.Thread.sleep(2_000_000_000); + var thread_pool: std.Thread.Pool = undefined; + try thread_pool.init(.{ + .allocator = allocator, + }); + + var quit = false; + while (!quit) { + try surface.clear(.{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 0.0 }); + + while (sdl3.events.poll()) |event| + switch (event) { + .quit => quit = true, + .terminating => quit = true, + else => {}, + }; + + { + try surface.lock(); + defer surface.unlock(); + + const pixel_map: [*]u32 = @as([*]u32, @ptrCast(@alignCast((surface.getPixels() orelse return).ptr))); + + var timer = try std.time.Timer.start(); + defer { + const ns = timer.lap(); + const ms = @as(f32, @floatFromInt(ns)) / std.time.ns_per_s; + std.log.info("Took {d:.3}s - {d:.3}fps to render", .{ ms, 1.0 / ms }); + } + + var wait_group: std.Thread.WaitGroup = .{}; + for (0..screen_height) |y| { + const runner = &runner_cache.items[y]; + thread_pool.spawnWg(&wait_group, Runner.run, .{ runner, y, pixel_map }); + } + thread_pool.waitAndWork(&wait_group); + } + + try window.updateSurface(); + } } std.log.info("Successfully executed", .{}); } + +const Runner = struct { + const Self = @This(); + + allocator: std.mem.Allocator, + surface: sdl3.surface.Surface, + rt: spv.Runtime, + + fn run(self: *Self, y: usize, pixel_map: [*]u32) void { + const entry = self.rt.getEntryPointByName("main") catch |err| std.debug.panic("Catch error {s}", .{@errorName(err)}); + const color = self.rt.getResultByName("color") catch |err| std.debug.panic("Catch error {s}", .{@errorName(err)}); + const time = self.rt.getResultByName("time") catch |err| std.debug.panic("Catch error {s}", .{@errorName(err)}); + const pos = self.rt.getResultByName("pos") catch |err| std.debug.panic("Catch error {s}", .{@errorName(err)}); + const res = self.rt.getResultByName("res") catch |err| std.debug.panic("Catch error {s}", .{@errorName(err)}); + var output: [4]f32 = undefined; + + var rt = self.rt; // Copy to avoid pointer access of `self` at runtime. Okay as Runtime contains only pointers and trivially copyable fields + + for (0..screen_width) |x| { + rt.writeInput(f32, &.{@as(f32, @floatFromInt(std.time.milliTimestamp()))}, time) catch |err| std.debug.panic("Catch error {s}", .{@errorName(err)}); + rt.writeInput(f32, &.{ @floatFromInt(screen_width), @floatFromInt(screen_height) }, res) catch |err| std.debug.panic("Catch error {s}", .{@errorName(err)}); + rt.writeInput(f32, &.{ @floatFromInt(x), @floatFromInt(y) }, pos) catch |err| std.debug.panic("Catch error {s}", .{@errorName(err)}); + rt.callEntryPoint(self.allocator, entry) catch |err| std.debug.panic("Catch error {s}", .{@errorName(err)}); + rt.readOutput(f32, output[0..], color) catch |err| std.debug.panic("Catch error {s}", .{@errorName(err)}); + + const rgba = self.surface.mapRgba( + @truncate(@as(u32, @intFromFloat(output[0] * 255.0))), + @truncate(@as(u32, @intFromFloat(output[1] * 255.0))), + @truncate(@as(u32, @intFromFloat(output[2] * 255.0))), + @truncate(@as(u32, @intFromFloat(output[3] * 255.0))), + ); + + pixel_map[(y * self.surface.getWidth()) + x] = rgba.value; + } + } +}; diff --git a/example/shader.nzsl b/example/shader.nzsl index 7571d06..e918355 100644 --- a/example/shader.nzsl +++ b/example/shader.nzsl @@ -3,8 +3,9 @@ module; struct FragIn { - [location(0)] dim: vec2[f32], - [location(1)] pos: vec2[f32], + [location(0)] time: u32, + [location(1)] dim: vec2[f32], + [location(2)] pos: vec2[f32], } struct FragOut diff --git a/example/shader.spv b/example/shader.spv index 361d88032af2ac578246a79f6e35aad3d4bb7d37..f2a83eefbcdbfbc43530356e02a01f11284659d0 100644 GIT binary patch literal 1360 zcmZ9KO;1x%5QfLr)-Qx20*YEMRz*O;YGRB8(TyJ~b>p5WF-a)HrJYDX1^ySgF zKYh9M?N496y#J1BIbRRllbHEp=WCc8Vx5BJT$#I2-e{E>yer2dHJj)P@l^P}nK5zW z?Fl}`HjD3jdAx5A)Ax&5BFi_dkY!>Q(fZc&?j^L`;>mTnV8&;$<*LV>$NX;DhkY4W z%(;R#$6Z--0c}m^Fn$$nj(8W#y=WU-Mw=^UKjvLSB7W@|e~e|^`#bn@`foUczVrB| zb>BBl-`c*Z^`DU`?myyjraydF&~n+!4a9o-&ULf+ugP`Kw`$lPtort2+}-)_nBz{3 z>)T((Z(|#Gr^fZ|H{%s{#&_w#9j_wdpQzz}?jT~G%!BF9S{|B+ZbnzVd2fYkDV*mgE literal 1168 zcmZ9KO;1x%5QfK=i{J+o5m3}}u__>7!p0Ddpc_9{z=l0RW10vhMfS!OKbegQ&(oek z_}yBH^%$P1Xn;gaL~{UWPsUh`pzk9`2i;?dQREiV8c1`% zQH-o+jH9FVo}4Q7zx7YMz2i6a#;XnC3iJ;8WB=g$7b=YwU+!!7=*qY6N$+$JKULy) z&VIsIq3=2D{pj`46+NBDd~N5-T<7~6H}c;dKGEB}HsaAYSKt2n<|qG|>dx-z3{@m% zyx15bmNRDDblK}7^EHSueiqv;*T@{b+_AoVjaQ+K6QAMherrT*wfOe)81LSVF+Ta* z*v7X(on1~b{w~^har;l8?VtQ)@x|LP-bvfsduVgT>}B3G67g$W{w0>Y@o(|Hi@SD~ z^xbv(_Hx(Oc}=vq|C`5sT=1Pin``e`#QOT)W3Kog$aN173g&*csj6@PR^kxugrUfeSL=KVB(qg0$a?yoM8#udUCsL*SA|nYDCWWRqW&Yt{}dHcimzm c`o=r!@V^-H_O#X-vWj?&_r1mbFvTWv32` FragOut +fn main(input: FragIn) -> FragOut { - let output: FragOut; - output.color = vec4[f32](1.0, 1.0, 1.0, 1.0); - return output; + const I: i32 = 128; + const A: f32 = 7.5; + const MA: f32 = 100.0; + const MI: f32 = 0.001; + + let uv0 = input.pos / input.res * 2.0 - vec2[f32](1.0, 1.0); + let uv = vec2[f32](uv0.x * (input.res.x / input.res.y), uv0.y); + + let col = vec3[f32](0.0, 0.0, 0.0); + let ro = vec3[f32](0.0, 0.0, -2.0); + let rd = vec3[f32](uv.x, uv.y, 1.0); + let dt = 0.0; + let ds = 0.0; + let dm = -1.0; + let p = ro; + let c = vec3[f32](0.0, 0.0, 0.0); + + let l = vec3[f32](0.0, sin(input.time * 0.2) * 4.0, cos(input.time * 0.2) * 4.0); + + for i in 0 -> I + { + p = ro + rd * dt; + ds = length(c - p) - 1.0; + dt += ds; + + if (dm == -1.0 || ds < dm) + dm = ds; + + if (ds <= MI) + { + let value = max(dot(normalize(c - p), normalize(p - l)) - 0.35, 0.0); + col = vec3[f32](value, value, value); + break; + } + + if (ds >= MA) + { + if (dot(normalize(rd), normalize(l - ro)) <= 1.0) + { + let value = max(dot(normalize(rd), normalize(l - ro)) + 0.15, 0.05)/ 1.15 * (1.0 - dm * A); + col = vec3[f32](value, value, value); + } + break; + } + } + + if (col == vec3[f32](0.0, 0.0, 0.0)) + discard; + + let output: FragOut; + output.color = vec4[f32](col.x, col.y, col.z, 1.0); + return output; } diff --git a/sandbox/shader.spv b/sandbox/shader.spv index db7c59e3283a69e393568976cf771022cc8154e4..4306992f01fc8fff6973674ae84841881afed1e1 100644 GIT binary patch literal 4620 zcmZ9O_j6TM6ooGYf(p{B1p}dqMlf^?1eB&xf+%(cA!4KilVC3aM6qE33)ma>f}o-z zU`GW}{}O+)jn4Re@132w^X|;fS!?Zb_C4k1m36G_l4TvT4LwI@zh%XzSJn|F%erRe z*%-#Tb&Kk%T3VM^jh`^qi0)aLLm{R|c5K!Od>XcTY2zC8&RAEhJJuKLNxXh00+o@W zSW8~#Y;i+#OJmcTsTJd@#wmA3?a4T&dFhJzR4Yf3`?NN$Zg4D5G4|Np(9+I3!q+yn z6l(MXTVuhx)Wy@;R3Z1H}T=suyDuXapMvxb^E#>IO@ZlAPwLwE;ct4$8`i}>jpZE8Gn?E?czxDKM=JaAT z$MgwI? zb%(*T?9i<64~Om8KB(P40!|+_;U5XFuHM@I-lO1I_Is^sIvV8M@!qF`?bEfi@1u#k zuG8SIy?3TV;_ka=JQD&doJ+&!IA!l&lA^G_?`XXUtSU6bRU_vwkxWUb7f zk<(_digMRqzw1?tnd4p4FLyt}KNGCqyQW|6{)B%PIQ%v2fp_n0Og&=GNioh5F|)zy zo9V-xIhc9sTaq?6>E6Toq|F0+54DX+n-4Zt-*kHP45-Ci=YrK167M-W4^#7r9_zrd z4v}|0ntJ4209NyfyanLWym~bC$Xf_j^X&S*_HZF)J$;*!wg_x*`l5%6!1>-UhO1dC z)?_i*Tz$U3XI`zIuY}*9OEAAxVa9k5)FbB7oZs)1G2RXJ=;bo7wd1`m2dnu+%oX5# z%$0DpV!y?Hu7aDZ&wX?*@145XJ7=$A?^|H+n%_+4^1ZIc?9n*yojl&>8nAg`#!P~% zyLaA=C765X{kNw3vJ_i_#hzT7G{4Q=SWjb?VPi1wkN${V4%Rmqi#Zy=rE^?|rXG1K zz-sw9u7^8EcT9i8HiGpzN4)C|VBgdE_4}@q8P&y87=1^v-)MLB_uhZ^ zcO7Po`ue2Z1h$So-^YDdi<&os-RI!9fQ|86Va;1HW7K0mZv(s5ezWu&Yd^PR`UYUm z<{N8Ho~uXhLtu0Fr5+vz zs}05M;eJLRd(ig)_8?|mv31;^Em(g49)aig@6i;a?*5tc80P-X#`H(6$HC?X-wO6# z#97z|Hby;aJpqnd`nT~u?!%Lq^XQNB@l?_)8M|@AjC&e0e-x&FAm(~bW7HSxwH>S$ zd`Ag?Ch@rA&%(_yPMsC?g59J2X^&n4s|~~4qvsfX+#`L@ zV=rLV5yv{d2KL^@n(YL~n(2$U*TJrL@HfET|5*Pw!N#aZt+&8YOMk5Y+hFI>A7|_x zu)6ot{$FPFv44H9V6S5KFOELm1?T&J4<2*si@5i}`TjqE=lkCUH%2}B{}3Ft^hf`@ z!Oo*Uz7KoA>cg=(KOce3_x$J|fyHlzkHPv!V)p2H`vjYS#s7!BNi#pr>8D`VNMFQ# z2DY9)&(c|pYEk!duv+kaCH#xT<9zOin`4~5IG^gCNB72ka6j@~J%4^ZZ*tF~@s9RT z&UgTug0*pe*@X<2RUhDBj^)@W7n;T^iH)1W;}vf-+cX^1%IC$W&9bd!Mb3sq1egNOt)3#<0xueJ&JeVYyJg>z@l z%-l2QCavL0#8Uibhp`j$(~B0Bh)yI`!Mq#oSSD_QkMU$`qYW}Ek(2ME;a~inG3QPjd_YH^i{2`^T return, + else => return err, + }; } if (!self.it.did_jump) { _ = it_tmp.skipN(word_count); diff --git a/src/opcodes.zig b/src/opcodes.zig index a5d2b26..14942e0 100644 --- a/src/opcodes.zig +++ b/src/opcodes.zig @@ -13,7 +13,15 @@ const SpvByte = spv.SpvByte; const SpvWord = spv.SpvWord; const SpvBool = spv.SpvBool; +// OpExtInstImport +// OpExtInst Sin +// OpExtInst Cos +// OpExtInst Length +// OpExtInst Normalize +// OpExtInst FMax + const ValueType = enum { + Bool, Float, SInt, UInt, @@ -22,10 +30,15 @@ const ValueType = enum { const MathOp = enum { Add, Div, + MatrixTimesMatrix, + MatrixTimesScalar, + MatrixTimesVector, Mod, Mul, Rem, Sub, + VectorTimesMatrix, + VectorTimesScalar, }; const CondOp = enum { @@ -35,6 +48,11 @@ const CondOp = enum { Less, LessEqual, NotEqual, + LogicalEqual, + LogicalNotEqual, + LogicalAnd, + LogicalOr, + LogicalNot, }; const BitOp = enum { @@ -76,6 +94,7 @@ pub const SetupDispatcher = block: { .ConvertUToF = autoSetupConstant, .ConvertUToPtr = autoSetupConstant, .Decorate = opDecorate, + .Dot = autoSetupConstant, .EntryPoint = opEntryPoint, .ExecutionMode = opExecutionMode, .FAdd = autoSetupConstant, @@ -107,6 +126,14 @@ pub const SetupDispatcher = block: { .ISub = autoSetupConstant, .Label = opLabel, .Load = autoSetupConstant, + .LogicalAnd = autoSetupConstant, + .LogicalEqual = autoSetupConstant, + .LogicalNot = autoSetupConstant, + .LogicalNotEqual = autoSetupConstant, + .LogicalOr = autoSetupConstant, + .MatrixTimesMatrix = autoSetupConstant, + .MatrixTimesScalar = autoSetupConstant, + .MatrixTimesVector = autoSetupConstant, .MemberDecorate = opDecorateMember, .MemberName = opMemberName, .MemoryModel = opMemoryModel, @@ -145,85 +172,95 @@ pub const SetupDispatcher = block: { .ULessThanEqual = autoSetupConstant, .UMod = autoSetupConstant, .Variable = opVariable, + .VectorTimesMatrix = autoSetupConstant, + .VectorTimesScalar = autoSetupConstant, }); }; -pub const RuntimeDispatcher = block: { - @setEvalBranchQuota(65535); - break :block std.EnumMap(spv.SpvOp, OpCodeFunc).init(.{ - .AccessChain = opAccessChain, - .BitCount = BitEngine(.UInt, .BitCount).op, - .BitFieldInsert = BitEngine(.UInt, .BitFieldInsert).op, - .BitFieldSExtract = BitEngine(.SInt, .BitFieldSExtract).op, - .BitFieldUExtract = BitEngine(.UInt, .BitFieldUExtract).op, - .BitReverse = BitEngine(.UInt, .BitReverse).op, - .Bitcast = opBitcast, - .BitwiseAnd = BitEngine(.UInt, .BitwiseAnd).op, - .BitwiseOr = BitEngine(.UInt, .BitwiseOr).op, - .BitwiseXor = BitEngine(.UInt, .BitwiseXor).op, - .Branch = opBranch, - .BranchConditional = opBranchConditional, - .CompositeConstruct = opCompositeConstruct, - .CompositeExtract = opCompositeExtract, - .ConvertFToS = ConversionEngine(.Float, .SInt).op, - .ConvertFToU = ConversionEngine(.Float, .UInt).op, - .ConvertSToF = ConversionEngine(.SInt, .Float).op, - .ConvertUToF = ConversionEngine(.UInt, .Float).op, - .CopyMemory = opCopyMemory, - .FAdd = MathEngine(.Float, .Add).op, - .FConvert = ConversionEngine(.Float, .Float).op, - .FDiv = MathEngine(.Float, .Div).op, - .FMod = MathEngine(.Float, .Mod).op, - .FMul = MathEngine(.Float, .Mul).op, - .FOrdEqual = CondEngine(.Float, .Equal).op, - .FOrdGreaterThan = CondEngine(.Float, .Greater).op, - .FOrdGreaterThanEqual = CondEngine(.Float, .GreaterEqual).op, - .FOrdLessThan = CondEngine(.Float, .Less).op, - .FOrdLessThanEqual = CondEngine(.Float, .LessEqual).op, - .FOrdNotEqual = CondEngine(.Float, .NotEqual).op, - .FSub = MathEngine(.Float, .Sub).op, - .FUnordEqual = CondEngine(.Float, .Equal).op, - .FUnordGreaterThan = CondEngine(.Float, .Greater).op, - .FUnordGreaterThanEqual = CondEngine(.Float, .GreaterEqual).op, - .FUnordLessThan = CondEngine(.Float, .Less).op, - .FUnordLessThanEqual = CondEngine(.Float, .LessEqual).op, - .FUnordNotEqual = CondEngine(.Float, .NotEqual).op, - .FunctionCall = opFunctionCall, - .IAdd = MathEngine(.SInt, .Add).op, - .IEqual = CondEngine(.SInt, .Equal).op, - .IMul = MathEngine(.SInt, .Mul).op, - .INotEqual = CondEngine(.SInt, .NotEqual).op, - .ISub = MathEngine(.SInt, .Sub).op, - .Load = opLoad, - .Not = BitEngine(.UInt, .Not).op, - .Return = opReturn, - .ReturnValue = opReturnValue, - .SConvert = ConversionEngine(.SInt, .SInt).op, - .SDiv = MathEngine(.SInt, .Div).op, - .SGreaterThan = CondEngine(.SInt, .Greater).op, - .SGreaterThanEqual = CondEngine(.SInt, .GreaterEqual).op, - .SLessThan = CondEngine(.SInt, .Less).op, - .SLessThanEqual = CondEngine(.SInt, .LessEqual).op, - .SMod = MathEngine(.SInt, .Mod).op, - .ShiftLeftLogical = BitEngine(.UInt, .ShiftLeft).op, - .ShiftRightArithmetic = BitEngine(.SInt, .ShiftRightArithmetic).op, - .ShiftRightLogical = BitEngine(.UInt, .ShiftRight).op, - .Store = opStore, - .UConvert = ConversionEngine(.UInt, .UInt).op, - .UDiv = MathEngine(.UInt, .Div).op, - .UGreaterThan = CondEngine(.UInt, .Greater).op, - .UGreaterThanEqual = CondEngine(.UInt, .GreaterEqual).op, - .ULessThan = CondEngine(.UInt, .Less).op, - .ULessThanEqual = CondEngine(.UInt, .LessEqual).op, - .UMod = MathEngine(.UInt, .Mod).op, +/// Not an EnumMap as it is way too slow for this purpose +pub var runtime_dispatcher = [_]?OpCodeFunc{null} ** spv.SpvOpMaxValue; - //.QuantizeToF16 = , - //.ConvertPtrToU = , - //.SatConvertSToU = , - //.SatConvertUToS = , - //.ConvertUToPtr = , - }); -}; +pub fn initRuntimeDispatcher() void { + // zig fmt: off + runtime_dispatcher[@intFromEnum(spv.SpvOp.AccessChain)] = opAccessChain; + runtime_dispatcher[@intFromEnum(spv.SpvOp.BitCount)] = BitEngine(.UInt, .BitCount).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.BitFieldInsert)] = BitEngine(.UInt, .BitFieldInsert).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.BitFieldSExtract)] = BitEngine(.SInt, .BitFieldSExtract).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.BitFieldUExtract)] = BitEngine(.UInt, .BitFieldUExtract).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.BitReverse)] = BitEngine(.UInt, .BitReverse).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.Bitcast)] = opBitcast; + runtime_dispatcher[@intFromEnum(spv.SpvOp.BitwiseAnd)] = BitEngine(.UInt, .BitwiseAnd).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.BitwiseOr)] = BitEngine(.UInt, .BitwiseOr).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.BitwiseXor)] = BitEngine(.UInt, .BitwiseXor).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.Branch)] = opBranch; + runtime_dispatcher[@intFromEnum(spv.SpvOp.BranchConditional)] = opBranchConditional; + runtime_dispatcher[@intFromEnum(spv.SpvOp.CompositeConstruct)] = opCompositeConstruct; + runtime_dispatcher[@intFromEnum(spv.SpvOp.CompositeExtract)] = opCompositeExtract; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ConvertFToS)] = ConversionEngine(.Float, .SInt).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ConvertFToU)] = ConversionEngine(.Float, .UInt).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ConvertSToF)] = ConversionEngine(.SInt, .Float).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ConvertUToF)] = ConversionEngine(.UInt, .Float).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.CopyMemory)] = opCopyMemory; + runtime_dispatcher[@intFromEnum(spv.SpvOp.Dot)] = opDot; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FAdd)] = MathEngine(.Float, .Add).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FConvert)] = ConversionEngine(.Float, .Float).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FDiv)] = MathEngine(.Float, .Div).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FMod)] = MathEngine(.Float, .Mod).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FMul)] = MathEngine(.Float, .Mul).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdEqual)] = CondEngine(.Float, .Equal).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdGreaterThan)] = CondEngine(.Float, .Greater).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdGreaterThanEqual)] = CondEngine(.Float, .GreaterEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdLessThan)] = CondEngine(.Float, .Less).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdLessThanEqual)] = CondEngine(.Float, .LessEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FOrdNotEqual)] = CondEngine(.Float, .NotEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FSub)] = MathEngine(.Float, .Sub).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordEqual)] = CondEngine(.Float, .Equal).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordGreaterThan)] = CondEngine(.Float, .Greater).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordGreaterThanEqual)] = CondEngine(.Float, .GreaterEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordLessThan)] = CondEngine(.Float, .Less).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordLessThanEqual)] = CondEngine(.Float, .LessEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FUnordNotEqual)] = CondEngine(.Float, .NotEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.FunctionCall)] = opFunctionCall; + runtime_dispatcher[@intFromEnum(spv.SpvOp.IAdd)] = MathEngine(.SInt, .Add).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.IEqual)] = CondEngine(.SInt, .Equal).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.IMul)] = MathEngine(.SInt, .Mul).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.INotEqual)] = CondEngine(.SInt, .NotEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ISub)] = MathEngine(.SInt, .Sub).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.Kill)] = opKill; + runtime_dispatcher[@intFromEnum(spv.SpvOp.Load)] = opLoad; + runtime_dispatcher[@intFromEnum(spv.SpvOp.LogicalAnd)] = CondEngine(.Float, .LogicalAnd).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.LogicalEqual)] = CondEngine(.Float, .LogicalEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.LogicalNot)] = CondEngine(.Float, .LogicalNot).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.LogicalNotEqual)] = CondEngine(.Float, .LogicalNotEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.LogicalOr)] = CondEngine(.Float, .LogicalOr).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.MatrixTimesMatrix)] = MathEngine(.Float, .MatrixTimesMatrix).op; // TODO + runtime_dispatcher[@intFromEnum(spv.SpvOp.MatrixTimesScalar)] = MathEngine(.Float, .MatrixTimesScalar).op; // TODO + runtime_dispatcher[@intFromEnum(spv.SpvOp.MatrixTimesVector)] = MathEngine(.Float, .MatrixTimesVector).op; // TODO + runtime_dispatcher[@intFromEnum(spv.SpvOp.Not)] = BitEngine(.UInt, .Not).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.Return)] = opReturn; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ReturnValue)] = opReturnValue; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SConvert)] = ConversionEngine(.SInt, .SInt).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SDiv)] = MathEngine(.SInt, .Div).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SGreaterThan)] = CondEngine(.SInt, .Greater).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SGreaterThanEqual)] = CondEngine(.SInt, .GreaterEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SLessThan)] = CondEngine(.SInt, .Less).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SLessThanEqual)] = CondEngine(.SInt, .LessEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.SMod)] = MathEngine(.SInt, .Mod).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ShiftLeftLogical)] = BitEngine(.UInt, .ShiftLeft).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ShiftRightArithmetic)] = BitEngine(.SInt, .ShiftRightArithmetic).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ShiftRightLogical)] = BitEngine(.UInt, .ShiftRight).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.Store)] = opStore; + runtime_dispatcher[@intFromEnum(spv.SpvOp.UConvert)] = ConversionEngine(.UInt, .UInt).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.UDiv)] = MathEngine(.UInt, .Div).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.UGreaterThan)] = CondEngine(.UInt, .Greater).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.UGreaterThanEqual)] = CondEngine(.UInt, .GreaterEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ULessThan)] = CondEngine(.UInt, .Less).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ULessThanEqual)] = CondEngine(.UInt, .LessEqual).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.UMod)] = MathEngine(.UInt, .Mod).op; + runtime_dispatcher[@intFromEnum(spv.SpvOp.VectorTimesMatrix)] = MathEngine(.Float, .VectorTimesMatrix).op; // TODO + runtime_dispatcher[@intFromEnum(spv.SpvOp.VectorTimesScalar)] = MathEngine(.Float, .VectorTimesScalar).op; + // zig fmt: on +} fn BitEngine(comptime T: ValueType, comptime Op: BitOp) type { if (T == .Float) @compileError("Invalid value type"); @@ -362,7 +399,10 @@ fn CondEngine(comptime T: ValueType, comptime Op: CondOp) type { const op1_result = &rt.results[try rt.it.next()]; const op1_type = try op1_result.getValueTypeWord(); const op1_value = try op1_result.getValue(); - const op2_value = try rt.results[try rt.it.next()].getValue(); + const op2_value: ?*Result.Value = switch (Op) { + .LogicalNot => null, + else => try rt.results[try rt.it.next()].getValue(), + }; const size = sw: switch ((try rt.results[op1_type].getVariant()).Type) { .Vector => |v| continue :sw (try rt.results[v.components_type_word].getVariant()).Type, @@ -382,18 +422,21 @@ fn CondEngine(comptime T: ValueType, comptime Op: CondOp) type { }; const operator = struct { - fn operation(comptime TT: type, op1: TT, op2: TT) RuntimeError!bool { + fn operation(comptime TT: type, op1: TT, op2: ?TT) RuntimeError!bool { return switch (Op) { - .Equal => op1 == op2, - .NotEqual => op1 != op2, - .Greater => op1 > op2, - .GreaterEqual => op1 >= op2, - .Less => op1 < op2, - .LessEqual => op1 <= op2, + .Equal, .LogicalEqual => op1 == op2 orelse return RuntimeError.InvalidSpirV, + .NotEqual, .LogicalNotEqual => op1 != op2 orelse return RuntimeError.InvalidSpirV, + .Greater => op1 > op2 orelse return RuntimeError.InvalidSpirV, + .GreaterEqual => op1 >= op2 orelse return RuntimeError.InvalidSpirV, + .Less => op1 < op2 orelse return RuntimeError.InvalidSpirV, + .LessEqual => op1 <= op2 orelse return RuntimeError.InvalidSpirV, + .LogicalAnd => (op1 != @as(TT, 0)) and ((op2 orelse return RuntimeError.InvalidSpirV) != @as(TT, 0)), + .LogicalOr => (op1 != @as(TT, 0)) or ((op2 orelse return RuntimeError.InvalidSpirV) != @as(TT, 0)), + .LogicalNot => (op1 == @as(TT, 0)), }; } - fn process(bit_count: SpvWord, v: *Result.Value, op1_v: *const Result.Value, op2_v: *const Result.Value) RuntimeError!void { + fn process(bit_count: SpvWord, v: *Result.Value, op1_v: *const Result.Value, op2_v: ?*const Result.Value) RuntimeError!void { switch (bit_count) { inline 8, 16, 32, 64 => |i| { if (i == 8 and T == .Float) { // No f8 @@ -402,7 +445,7 @@ fn CondEngine(comptime T: ValueType, comptime Op: CondOp) type { v.Bool = try operation( getValuePrimitiveFieldType(T, i), (try getValuePrimitiveField(T, i, @constCast(op1_v))).*, - (try getValuePrimitiveField(T, i, @constCast(op2_v))).*, + if (op2_v) |val| (try getValuePrimitiveField(T, i, @constCast(val))).* else null, ); }, else => return RuntimeError.InvalidSpirV, @@ -412,7 +455,9 @@ fn CondEngine(comptime T: ValueType, comptime Op: CondOp) type { switch (value.*) { .Bool => try operator.process(size, value, op1_value, op2_value), - .Vector => |vec| for (vec, op1_value.Vector, op2_value.Vector) |*val, op1_v, op2_v| try operator.process(size, val, &op1_v, &op2_v), + .Vector => |vec| for (vec, op1_value.Vector, 0..) |*val, op1_v, i| { + try operator.process(size, val, &op1_v, if (op2_value) |op2_v| &op2_v.Vector[i] else null); + }, // No Vector specializations for booleans else => return RuntimeError.InvalidSpirV, } @@ -586,6 +631,7 @@ fn MathEngine(comptime T: ValueType, comptime Op: MathOp) type { }, .Mod => if (op2 == 0) return RuntimeError.DivisionByZero else @mod(op1, op2), .Rem => if (op2 == 0) return RuntimeError.DivisionByZero else @rem(op1, op2), + else => return RuntimeError.InvalidSpirV, }; } @@ -609,15 +655,29 @@ fn MathEngine(comptime T: ValueType, comptime Op: MathOp) type { switch (value.*) { .Float => if (T == .Float) try operator.process(size, value, op1_value, op2_value) else return RuntimeError.InvalidSpirV, .Int => if (T == .SInt or T == .UInt) try operator.process(size, value, op1_value, op2_value) else return RuntimeError.InvalidSpirV, - .Vector => |vec| for (vec, op1_value.Vector, op2_value.Vector) |*val, op1_v, op2_v| try operator.process(size, val, &op1_v, &op2_v), + .Vector => |vec| for (vec, op1_value.Vector, 0..) |*val, op1_v, i| { + switch (Op) { + .VectorTimesScalar => try operator.process(size, val, &op1_v, op2_value), + else => try operator.process(size, val, &op1_v, &op2_value.Vector[i]), + } + }, .Vector4f32 => |*vec| inline for (0..4) |i| { - vec[i] = try operator.operation(f32, op1_value.Vector4f32[i], op2_value.Vector4f32[i]); + switch (Op) { + .VectorTimesScalar => vec[i] = op1_value.Vector4f32[i] * op2_value.Float.float32, + else => vec[i] = try operator.operation(f32, op1_value.Vector4f32[i], op2_value.Vector4f32[i]), + } }, .Vector3f32 => |*vec| inline for (0..3) |i| { - vec[i] = try operator.operation(f32, op1_value.Vector3f32[i], op2_value.Vector3f32[i]); + switch (Op) { + .VectorTimesScalar => vec[i] = op1_value.Vector3f32[i] * op2_value.Float.float32, + else => vec[i] = try operator.operation(f32, op1_value.Vector3f32[i], op2_value.Vector3f32[i]), + } }, .Vector2f32 => |*vec| inline for (0..2) |i| { - vec[i] = try operator.operation(f32, op1_value.Vector2f32[i], op2_value.Vector2f32[i]); + switch (Op) { + .VectorTimesScalar => vec[i] = op1_value.Vector2f32[i] * op2_value.Float.float32, + else => vec[i] = try operator.operation(f32, op1_value.Vector2f32[i], op2_value.Vector2f32[i]), + } }, .Vector4i32 => |*vec| inline for (0..4) |i| { vec[i] = try operator.operation(i32, op1_value.Vector4i32[i], op2_value.Vector4i32[i]); @@ -737,6 +797,7 @@ fn copyValue(dst: *Result.Value, src: *const Result.Value) void { fn getValuePrimitiveField(comptime T: ValueType, comptime BitCount: SpvWord, v: *Result.Value) RuntimeError!*getValuePrimitiveFieldType(T, BitCount) { return switch (T) { + .Bool => &v.Bool, .Float => switch (BitCount) { inline 16, 32, 64 => |i| &@field(v.Float, std.fmt.comptimePrint("float{}", .{i})), else => return RuntimeError.InvalidSpirV, @@ -754,6 +815,7 @@ fn getValuePrimitiveField(comptime T: ValueType, comptime BitCount: SpvWord, v: fn getValuePrimitiveFieldType(comptime T: ValueType, 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), @@ -971,6 +1033,41 @@ fn opDecorateMember(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) Runt try addDecoration(allocator, rt, target, decoration_type, member); } +fn opDot(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + const target_type = (try rt.results[try rt.it.next()].getVariant()).Type; + var value = try rt.results[try rt.it.next()].getValue(); + const op1_value = try rt.results[try rt.it.next()].getValue(); + const op2_value = try rt.results[try rt.it.next()].getValue(); + + const size = switch (target_type) { + .Float => |f| f.bit_length, + else => return RuntimeError.InvalidSpirV, + }; + + value.Float.float64 = 0.0; + + switch (op1_value.*) { + .Vector => |vec| for (vec, op2_value.Vector) |*op1_v, *op2_v| { + switch (size) { + inline 16, 32, 64 => |i| { + (try getValuePrimitiveField(.Float, i, value)).* += (try getValuePrimitiveField(.Float, i, op1_v)).* * (try getValuePrimitiveField(.Float, i, op2_v)).*; + }, + else => return RuntimeError.InvalidSpirV, + } + }, + .Vector4f32 => |*vec| inline for (0..4) |i| { + value.Float.float32 += vec[i] * op2_value.Vector4f32[i]; + }, + .Vector3f32 => |*vec| inline for (0..3) |i| { + value.Float.float32 += vec[i] * op2_value.Vector3f32[i]; + }, + .Vector2f32 => |*vec| inline for (0..2) |i| { + value.Float.float32 += vec[i] * op2_value.Vector2f32[i]; + }, + else => return RuntimeError.InvalidSpirV, + } +} + fn opEntryPoint(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void { const entry = rt.mod.entry_points.addOne(allocator) catch return RuntimeError.OutOfMemory; entry.exec_model = try rt.it.nextAs(spv.SpvExecutionModel); @@ -1099,6 +1196,10 @@ fn opLabel(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { }; } +fn opKill(_: std.mem.Allocator, _: SpvWord, _: *Runtime) RuntimeError!void { + return RuntimeError.Killed; +} + fn opLoad(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { _ = rt.it.skip(); const id = try rt.it.next(); diff --git a/src/spv.zig b/src/spv.zig index 05ae91b..1e0b83e 100644 --- a/src/spv.zig +++ b/src/spv.zig @@ -2391,3 +2391,5 @@ pub const SpvOp = enum(u32) { ConvertHandleToSampledImageINTEL = 6531, Max = 0x7fffffff, }; + +pub const SpvOpMaxValue: comptime_int = @intFromEnum(SpvOp.ConvertHandleToSampledImageINTEL);