diff --git a/ffi/SpirvInterpreter.h b/ffi/SpirvInterpreter.h index 648118c..d8c17b9 100644 --- a/ffi/SpirvInterpreter.h +++ b/ffi/SpirvInterpreter.h @@ -418,20 +418,19 @@ typedef enum SPV_RESULT_SUCCESS = 0, SPV_RESULT_BARRIER = 1, SPV_RESULT_KILLED = 2, - 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_NOT_FOUND = -5, - SPV_RESULT_OUT_OF_MEMORY = -6, - SPV_RESULT_OUT_OF_BOUNDS = -7, - SPV_RESULT_TODO = -8, - SPV_RESULT_UNREACHABLE = -9, - SPV_RESULT_UNSUPPORTED_SPIRV = -10, - SPV_RESULT_UNSUPPORTED_EXTENSION = -11, - SPV_RESULT_UNSUPPORTED_ENDIANNESS = -12, - SPV_RESULT_INVALID_MAGIC = -13, - SPV_RESULT_UNKNOWN = -14 + SPV_RESULT_INVALID_ENTRY_POINT = -1, + SPV_RESULT_INVALID_SPIRV = -2, + SPV_RESULT_INVALID_VALUE_TYPE = -3, + SPV_RESULT_NOT_FOUND = -4, + SPV_RESULT_OUT_OF_MEMORY = -5, + SPV_RESULT_OUT_OF_BOUNDS = -6, + SPV_RESULT_TODO = -7, + SPV_RESULT_UNREACHABLE = -8, + SPV_RESULT_UNSUPPORTED_SPIRV = -9, + SPV_RESULT_UNSUPPORTED_EXTENSION = -10, + SPV_RESULT_UNSUPPORTED_ENDIANNESS = -11, + SPV_RESULT_INVALID_MAGIC = -12, + SPV_RESULT_UNKNOWN = -13 } SpvResult; typedef struct @@ -538,6 +537,7 @@ typedef struct int x; int y; int z; + float w; float lod; SpvBool has_lod; SpvImageOffset offset; diff --git a/ffi/ffi.zig b/ffi/ffi.zig index ed98775..d6fb4e5 100644 --- a/ffi/ffi.zig +++ b/ffi/ffi.zig @@ -8,20 +8,19 @@ pub const Result = enum(c_int) { Success = 0, Barrier = 1, Killed = 2, - DivisionByZero = -1, - InvalidEntryPoint = -2, - InvalidSpirV = -3, - InvalidValueType = -4, - NotFound = -5, - OutOfMemory = -6, - OutOfBounds = -7, - ToDo = -8, - Unreachable = -9, - UnsupportedSpirV = -10, - UnsupportedExtension = -11, - UnsupportedEndianness = -12, - InvalidMagic = -13, - Unknown = -14, + InvalidEntryPoint = -1, + InvalidSpirV = -2, + InvalidValueType = -3, + NotFound = -4, + OutOfMemory = -5, + OutOfBounds = -6, + ToDo = -7, + Unreachable = -8, + UnsupportedSpirV = -9, + UnsupportedExtension = -10, + UnsupportedEndianness = -11, + InvalidMagic = -12, + Unknown = -13, }; comptime { diff --git a/ffi/runtime.zig b/ffi/runtime.zig index 4220c9e..9b46e0d 100644 --- a/ffi/runtime.zig +++ b/ffi/runtime.zig @@ -75,6 +75,7 @@ const SampleImageInfo = extern struct { x: f32, y: f32, z: f32, + w: f32, lod: f32, has_lod: ffi.SpvCBool, offset: ImageOffset, @@ -124,7 +125,6 @@ const ImageAPI = extern struct { fn toCResult(err: spv.Runtime.RuntimeError) ffi.Result { return switch (err) { spv.Runtime.RuntimeError.Barrier => ffi.Result.Barrier, - 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, @@ -143,7 +143,6 @@ fn toCResult(err: spv.Runtime.RuntimeError) ffi.Result { fn fromCResult(res: ffi.Result) spv.Runtime.RuntimeError!void { return switch (res) { ffi.Result.Barrier => spv.Runtime.RuntimeError.Barrier, - ffi.Result.DivisionByZero => spv.Runtime.RuntimeError.DivisionByZero, ffi.Result.InvalidEntryPoint => spv.Runtime.RuntimeError.InvalidEntryPoint, ffi.Result.InvalidSpirV => spv.Runtime.RuntimeError.InvalidSpirV, ffi.Result.InvalidValueType => spv.Runtime.RuntimeError.InvalidValueType, @@ -205,7 +204,7 @@ const ImageAPIBridge = struct { }; } - fn sampleImageInfo(driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) SampleImageInfo { + fn sampleImageInfo(driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.spv.SpvDim, x: f32, y: f32, z: f32, w: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) SampleImageInfo { return .{ .driver_image = driver_image, .driver_sampler = driver_sampler, @@ -213,6 +212,7 @@ const ImageAPIBridge = struct { .x = x, .y = y, .z = z, + .w = w, .lod = lod orelse 0.0, .has_lod = if (lod == null) 0 else 1, .offset = toCImageOffset(offset), @@ -279,7 +279,7 @@ const ImageAPIBridge = struct { const image_api = try getImageAPI(); var dst: Vec4f = undefined; - const result = image_api.sampleImageFloat4(sampleImageInfo(driver_image, driver_sampler, dim, x, y, z, lod, offset), &dst); + const result = image_api.sampleImageFloat4(sampleImageInfo(driver_image, driver_sampler, dim, x, y, z, 1.0, lod, offset), &dst); try fromCResult(result); @@ -295,7 +295,7 @@ const ImageAPIBridge = struct { const image_api = try getImageAPI(); var dst: Vec4u = undefined; - const result = image_api.sampleImageInt4(sampleImageInfo(driver_image, driver_sampler, dim, x, y, z, lod, offset), &dst); + const result = image_api.sampleImageInt4(sampleImageInfo(driver_image, driver_sampler, dim, x, y, z, 1.0, lod, offset), &dst); try fromCResult(result); @@ -307,11 +307,11 @@ const ImageAPIBridge = struct { }; } - fn sampleImageDref(driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.spv.SpvDim, x: f32, y: f32, z: f32, dref: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!f32 { + fn sampleImageDref(driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.spv.SpvDim, x: f32, y: f32, z: f32, w: f32, dref: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!f32 { const image_api = try getImageAPI(); var dst: f32 = undefined; - const result = image_api.sampleImageDref(sampleImageInfo(driver_image, driver_sampler, dim, x, y, z, lod, offset), dref, &dst); + const result = image_api.sampleImageDref(sampleImageInfo(driver_image, driver_sampler, dim, x, y, z, w, lod, offset), dref, &dst); try fromCResult(result); diff --git a/src/GLSL_std_450/opcodes.zig b/src/GLSL_std_450/opcodes.zig index bf9c73d..32616b1 100644 --- a/src/GLSL_std_450/opcodes.zig +++ b/src/GLSL_std_450/opcodes.zig @@ -889,11 +889,8 @@ fn opModfStruct(_: std.mem.Allocator, _: SpvWord, id: SpvWord, _: SpvWord, rt: * } fn frexpScalar(comptime T: type, x: T) struct { sig: T, exp: i32 } { - if (x == 0.0) - return .{ .sig = 0.0, .exp = 0 }; - - const exp = @as(i32, @intFromFloat(@floor(@log2(@abs(x))))) + 1; - return .{ .sig = x / @exp2(@as(T, @floatFromInt(exp))), .exp = exp }; + const result = std.math.frexp(x); + return .{ .sig = result.significand, .exp = result.exponent }; } fn opFrexp(_: std.mem.Allocator, target_type_id: SpvWord, id: SpvWord, _: SpvWord, rt: *Runtime) RuntimeError!void { @@ -951,8 +948,9 @@ fn opLdexp(_: std.mem.Allocator, target_type_id: SpvWord, id: SpvWord, _: SpvWor inline 16, 32, 64 => |bits| for (0..lane_count) |lane_index| { const exp_lane = if (exp_lane_count == 1) 0 else lane_index; const exponent = try readIntegralLaneAsI32(exp_value, exp_lane); - const scale = @exp2(@as(std.meta.Float(bits), @floatFromInt(exponent))); - try writeFloatLane(bits, dst, lane_index, try Value.readLane(.Float, bits, x, lane_index) * scale); + const value = try Value.readLane(.Float, bits, x, lane_index); + const result = if (value == 0.0) value else std.math.ldexp(value, exponent); + try writeFloatLane(bits, dst, lane_index, result); }, else => return RuntimeError.InvalidSpirV, } @@ -1121,7 +1119,51 @@ fn matrixSize(matrix: *const Value) RuntimeError!usize { }; } +fn determinant3Values(comptime T: type, a00: T, a01: T, a02: T, a10: T, a11: T, a12: T, a20: T, a21: T, a22: T) T { + return a00 * ((a11 * a22) - (a12 * a21)) - + a01 * ((a10 * a22) - (a12 * a20)) + + a02 * ((a10 * a21) - (a11 * a20)); +} + fn determinant(comptime bits: u32, src: *const Value, n: usize) RuntimeError!std.meta.Float(bits) { + const FloatT = std.meta.Float(bits); + if (n == 2) { + const a00 = try readMatrixElement(bits, src, 0, 0); + const a01 = try readMatrixElement(bits, src, 0, 1); + const a10 = try readMatrixElement(bits, src, 1, 0); + const a11 = try readMatrixElement(bits, src, 1, 1); + return (a00 * a11) - (a10 * a01); + } + + if (n == 3) { + return determinant3Values( + FloatT, + try readMatrixElement(bits, src, 0, 0), + try readMatrixElement(bits, src, 0, 1), + try readMatrixElement(bits, src, 0, 2), + try readMatrixElement(bits, src, 1, 0), + try readMatrixElement(bits, src, 1, 1), + try readMatrixElement(bits, src, 1, 2), + try readMatrixElement(bits, src, 2, 0), + try readMatrixElement(bits, src, 2, 1), + try readMatrixElement(bits, src, 2, 2), + ); + } + + if (n == 4) { + var m: [4][4]FloatT = undefined; + for (0..4) |row| { + for (0..4) |col| { + m[row][col] = try readMatrixElement(bits, src, row, col); + } + } + + return m[0][0] * determinant3Values(FloatT, m[1][1], m[1][2], m[1][3], m[2][1], m[2][2], m[2][3], m[3][1], m[3][2], m[3][3]) - + m[0][1] * determinant3Values(FloatT, m[1][0], m[1][2], m[1][3], m[2][0], m[2][2], m[2][3], m[3][0], m[3][2], m[3][3]) + + m[0][2] * determinant3Values(FloatT, m[1][0], m[1][1], m[1][3], m[2][0], m[2][1], m[2][3], m[3][0], m[3][1], m[3][3]) - + m[0][3] * determinant3Values(FloatT, m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2], m[3][0], m[3][1], m[3][2]); + } + var m: [16]std.meta.Float(bits) = undefined; for (0..n) |row| { for (0..n) |col| { @@ -1186,6 +1228,19 @@ fn opMatrixInverse(_: std.mem.Allocator, target_type_id: SpvWord, id: SpvWord, _ switch (lane_bits) { inline 16, 32, 64 => |bits| { + if (n == 2) { + const a00 = try readMatrixElement(bits, src, 0, 0); + const a01 = try readMatrixElement(bits, src, 0, 1); + const a10 = try readMatrixElement(bits, src, 1, 0); + const a11 = try readMatrixElement(bits, src, 1, 1); + const inv_det = 1.0 / ((a00 * a11) - (a10 * a01)); + try writeMatrixElement(bits, dst, 0, 0, a11 * inv_det); + try writeMatrixElement(bits, dst, 0, 1, -a01 * inv_det); + try writeMatrixElement(bits, dst, 1, 0, -a10 * inv_det); + try writeMatrixElement(bits, dst, 1, 1, a00 * inv_det); + return; + } + var aug: [32]std.meta.Float(bits) = undefined; const width = n * 2; for (0..n) |row| { diff --git a/src/Runtime.zig b/src/Runtime.zig index 37b9cc2..d67535d 100644 --- a/src/Runtime.zig +++ b/src/Runtime.zig @@ -21,7 +21,6 @@ const Self = @This(); pub const RuntimeError = error{ Barrier, - DivisionByZero, InvalidEntryPoint, InvalidSpirV, InvalidValueType, @@ -428,6 +427,8 @@ pub fn setDerivativeFromMemory(self: *Self, allocator: std.mem.Allocator, result var dx_value = try Value.init(allocator, self.results, target_type, false); defer dx_value.deinit(allocator); + const memory_size = try dx_value.getPlainMemorySize(); + if (dx.len < memory_size or dy.len < memory_size) return RuntimeError.OutOfBounds; _ = try dx_value.write(dx); var dy_value = try Value.init(allocator, self.results, target_type, false); diff --git a/src/opcodes.zig b/src/opcodes.zig index d19074c..333351f 100644 --- a/src/opcodes.zig +++ b/src/opcodes.zig @@ -1849,9 +1849,21 @@ fn ImageEngine(comptime Op: ImageOp) type { } } - fn explicitSampleLod(rt: *Runtime, driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.SpvDim, parsed: ParsedImageOperands) RuntimeError!?f32 { + fn explicitSampleLod(rt: *Runtime, driver_image: *anyopaque, driver_sampler: *anyopaque, dim: spv.SpvDim, x: f32, y: f32, z: f32, parsed: ParsedImageOperands) RuntimeError!?f32 { if (parsed.grad) |derivatives| { - const lod = try rt.image_api.queryImageLod(driver_image, driver_sampler, dim, derivatives); + const lod_dim, const lod_derivatives = if (dim == .Cube) blk: { + const center = cubeFaceCoord(x, y, z); + const dx = cubeFaceCoordForFace(center.face, x + derivatives.dx.x, y + derivatives.dx.y, z + derivatives.dx.z); + const dy = cubeFaceCoordForFace(center.face, x + derivatives.dy.x, y + derivatives.dy.y, z + derivatives.dy.z); + break :blk .{ + spv.SpvDim.@"2D", + Runtime.ImageDerivatives{ + .dx = .{ .x = dx.u - center.u, .y = dx.v - center.v, .z = 0.0, .w = 0.0 }, + .dy = .{ .x = dy.u - center.u, .y = dy.v - center.v, .z = 0.0, .w = 0.0 }, + }, + }; + } else .{ dim, derivatives }; + const lod = try rt.image_api.queryImageLod(driver_image, driver_sampler, lod_dim, lod_derivatives); return lod.y; } return parsed.lod; @@ -2054,20 +2066,49 @@ fn ImageEngine(comptime Op: ImageOp) type { fn queryImageLod(rt: *Runtime, dst: *Value, coordinate_id: SpvWord, image_operand: SampledImageOperand) RuntimeError!void { const coord_derivative = rt.derivatives.get(coordinate_id) orelse return RuntimeError.InvalidValueType; - const lod = try rt.image_api.queryImageLod(image_operand.driver_image, image_operand.driver_sampler, image_operand.dim, .{ - .dx = .{ - .x = try readSampleCoordLane(&coord_derivative.dx, 0), - .y = readSampleCoordLane(&coord_derivative.dx, 1) catch 0.0, - .z = readSampleCoordLane(&coord_derivative.dx, 2) catch 0.0, - .w = readSampleCoordLane(&coord_derivative.dx, 3) catch 0.0, + const coord = try rt.results[coordinate_id].getValue(); + const dim, const derivatives = if (image_operand.dim == .Cube) blk: { + const x = try readSampleCoordLane(coord, 0); + const y = try readSampleCoordLane(coord, 1); + const z = try readSampleCoordLane(coord, 2); + const center = cubeFaceCoord(x, y, z); + const dx = cubeFaceCoordForFace( + center.face, + x + try readSampleCoordLane(&coord_derivative.dx, 0), + y + (readSampleCoordLane(&coord_derivative.dx, 1) catch 0.0), + z + (readSampleCoordLane(&coord_derivative.dx, 2) catch 0.0), + ); + const dy = cubeFaceCoordForFace( + center.face, + x + try readSampleCoordLane(&coord_derivative.dy, 0), + y + (readSampleCoordLane(&coord_derivative.dy, 1) catch 0.0), + z + (readSampleCoordLane(&coord_derivative.dy, 2) catch 0.0), + ); + break :blk .{ + spv.SpvDim.@"2D", + Runtime.ImageDerivatives{ + .dx = .{ .x = dx.u - center.u, .y = dx.v - center.v, .z = 0.0, .w = 0.0 }, + .dy = .{ .x = dy.u - center.u, .y = dy.v - center.v, .z = 0.0, .w = 0.0 }, + }, + }; + } else .{ + image_operand.dim, + Runtime.ImageDerivatives{ + .dx = .{ + .x = try readSampleCoordLane(&coord_derivative.dx, 0), + .y = readSampleCoordLane(&coord_derivative.dx, 1) catch 0.0, + .z = readSampleCoordLane(&coord_derivative.dx, 2) catch 0.0, + .w = readSampleCoordLane(&coord_derivative.dx, 3) catch 0.0, + }, + .dy = .{ + .x = try readSampleCoordLane(&coord_derivative.dy, 0), + .y = readSampleCoordLane(&coord_derivative.dy, 1) catch 0.0, + .z = readSampleCoordLane(&coord_derivative.dy, 2) catch 0.0, + .w = readSampleCoordLane(&coord_derivative.dy, 3) catch 0.0, + }, }, - .dy = .{ - .x = try readSampleCoordLane(&coord_derivative.dy, 0), - .y = readSampleCoordLane(&coord_derivative.dy, 1) catch 0.0, - .z = readSampleCoordLane(&coord_derivative.dy, 2) catch 0.0, - .w = readSampleCoordLane(&coord_derivative.dy, 3) catch 0.0, - }, - }); + }; + const lod = try rt.image_api.queryImageLod(image_operand.driver_image, image_operand.driver_sampler, dim, derivatives); switch (dst.*) { .Vector2f32 => |*v| v.* = .{ lod.x, lod.y }, @@ -2302,6 +2343,9 @@ fn ImageEngine(comptime Op: ImageOp) type { sampled_image_operand.driver_image, sampled_image_operand.driver_sampler, sampled_image_operand.dim, + coords.x, + coords.y, + coords.z, parsed_operands, ), parsed_operands.offset, @@ -2345,6 +2389,9 @@ fn ImageEngine(comptime Op: ImageOp) type { sampled_image_operand.driver_image, sampled_image_operand.driver_sampler, sampled_image_operand.dim, + coords.x, + coords.y, + coords.z, parsed_operands, ), parsed_operands.offset, @@ -2695,6 +2742,30 @@ fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp, comptime IsAtomic: fn operation(comptime TT: type, op1: TT, op2: TT) RuntimeError!TT { const is_int = @typeInfo(TT) == .int or (@typeInfo(TT) == .vector and @typeInfo(std.meta.Child(TT)) == .int); const op2_is_zero = if (@typeInfo(TT) == .vector) std.simd.countElementsWithValue(op2, 0) != 0 else op2 == 0; + const zero: TT = switch (@typeInfo(TT)) { + .vector => @splat(0), + else => 0, + }; + + if (@typeInfo(TT) == .vector) { + switch (Op) { + .Div, .Mod, .Rem => { + var result: TT = undefined; + inline for (0..@typeInfo(TT).vector.len) |i| { + result[i] = if (op2[i] == 0) + 0 + else switch (Op) { + .Div => if (comptime is_int) @divTrunc(op1[i], op2[i]) else op1[i] / op2[i], + .Mod => @mod(op1[i], op2[i]), + .Rem => @rem(op1[i], op2[i]), + else => unreachable, + }; + } + return result; + }, + else => {}, + } + } return switch (Op) { .Add => if (comptime is_int) @addWithOverflow(op1, op2)[0] else op1 + op2, @@ -2707,12 +2778,12 @@ fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp, comptime IsAtomic: => if (comptime is_int) @mulWithOverflow(op1, op2)[0] else op1 * op2, .Div => blk: { if (comptime is_int) { - if (op2_is_zero) return RuntimeError.DivisionByZero; + if (op2_is_zero) return zero; } break :blk if (comptime is_int) @divTrunc(op1, op2) else op1 / op2; }, - .Mod => if (op2_is_zero) return RuntimeError.DivisionByZero else @mod(op1, op2), - .Rem => if (op2_is_zero) return RuntimeError.DivisionByZero else @rem(op1, op2), + .Mod => if (op2_is_zero) zero else @mod(op1, op2), + .Rem => if (op2_is_zero) zero else @rem(op1, op2), else => return RuntimeError.InvalidSpirV, }; } diff --git a/test/api.zig b/test/api.zig index 36683bf..de29d8c 100644 --- a/test/api.zig +++ b/test/api.zig @@ -48,7 +48,7 @@ fn sampleImageInt4(_: *anyopaque, _: *anyopaque, _: spv.spv.SpvDim, _: f32, _: f return spv.Runtime.RuntimeError.UnsupportedSpirV; } -fn sampleImageDref(driver_image: *anyopaque, driver_sampler: *anyopaque, _: spv.spv.SpvDim, x: f32, y: f32, z: f32, dref: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!f32 { +fn sampleImageDref(driver_image: *anyopaque, driver_sampler: *anyopaque, _: spv.spv.SpvDim, x: f32, y: f32, z: f32, _: f32, dref: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!f32 { const state: *ImageState = @ptrCast(@alignCast(driver_image)); if (state.expected_sampler != driver_sampler) return spv.Runtime.RuntimeError.InvalidSpirV; state.dref_calls += 1;