diff --git a/ffi/SpirvInterpreter.h b/ffi/SpirvInterpreter.h index ea97b8a..4f40abd 100644 --- a/ffi/SpirvInterpreter.h +++ b/ffi/SpirvInterpreter.h @@ -85,13 +85,42 @@ typedef enum SPV_LOCATION_OUTPUT = 1, } SpvLocationType; +typedef struct +{ + float x; + float y; + float z; + float w; +} SpvVec4f; + +typedef struct +{ + unsigned int x; + unsigned int y; + unsigned int z; + unsigned int w; +} SpvVec4u; + +typedef SpvResult (*SpvReadImageFloat4_PFN)(void* driver_image, int x, int y, int z, SpvVec4f* dst); +typedef SpvResult (*SpvReadImageInt4_PFN)(void* driver_image, int x, int y, int z, SpvVec4u* dst); +typedef SpvResult (*SpvWriteImageFloat4_PFN)(void* driver_image, int x, int y, int z, SpvVec4f src); +typedef SpvResult (*SpvWriteImageInt4_PFN)(void* driver_image, int x, int y, int z, SpvVec4u src); + +typedef struct +{ + SpvReadImageFloat4_PFN SpvReadImageFloat4; + SpvReadImageInt4_PFN SpvReadImageInt4; + SpvWriteImageFloat4_PFN SpvWriteImageFloat4; + SpvWriteImageInt4_PFN SpvWriteImageInt4; +} SpvImageAPI; + 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 SpvResult SpvInitRuntime(SpvRuntime* runtime, SpvModule module, SpvImageAPI image_api); SPV_API void SpvDeinitRuntime(SpvRuntime runtime); SPV_API SpvResult SpvFlushDescriptorSets(SpvRuntime runtime); diff --git a/ffi/runtime.zig b/ffi/runtime.zig index 5f0ee72..d21369d 100644 --- a/ffi/runtime.zig +++ b/ffi/runtime.zig @@ -13,6 +13,32 @@ const LocationType = enum(c_int) { output = 1, }; +const Vec4f = extern struct { + x: f32, + y: f32, + z: f32, + w: f32, +}; + +const Vec4u = extern struct { + x: c_uint, + y: c_uint, + z: c_uint, + w: c_uint, +}; + +const readImageFloat4_PFN = *const fn (driver_image: ?*anyopaque, x: c_int, y: c_int, z: c_int, dst: *Vec4f) callconv(.c) ffi.Result; +const readImageInt4_PFN = *const fn (driver_image: ?*anyopaque, x: c_int, y: c_int, z: c_int, dst: *Vec4u) callconv(.c) ffi.Result; +const writeImageFloat4_PFN = *const fn (driver_image: ?*anyopaque, x: c_int, y: c_int, z: c_int, src: Vec4f) callconv(.c) ffi.Result; +const writeImageInt4_PFN = *const fn (driver_image: ?*anyopaque, x: c_int, y: c_int, z: c_int, src: Vec4u) callconv(.c) ffi.Result; + +const ImageAPI = extern struct { + readImageFloat4: readImageFloat4_PFN, + readImageInt4: readImageInt4_PFN, + writeImageFloat4: writeImageFloat4_PFN, + writeImageInt4: writeImageInt4_PFN, +}; + fn toCResult(err: spv.Runtime.RuntimeError) ffi.Result { return switch (err) { spv.Runtime.RuntimeError.DivisionByZero => ffi.Result.DivisionByZero, @@ -31,27 +57,117 @@ fn toCResult(err: spv.Runtime.RuntimeError) ffi.Result { }; } -export fn SpvInitRuntime(rt: **spv.Runtime, module: *spv.Module) callconv(.c) ffi.Result { +fn fromCResult(res: ffi.Result) spv.Runtime.RuntimeError!void { + return switch (res) { + 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, + ffi.Result.Killed => spv.Runtime.RuntimeError.Killed, + ffi.Result.NotFound => spv.Runtime.RuntimeError.NotFound, + ffi.Result.OutOfMemory => spv.Runtime.RuntimeError.OutOfMemory, + ffi.Result.OutOfBounds => spv.Runtime.RuntimeError.OutOfBounds, + ffi.Result.ToDo => spv.Runtime.RuntimeError.ToDo, + ffi.Result.Unreachable => spv.Runtime.RuntimeError.Unreachable, + ffi.Result.UnsupportedSpirV => spv.Runtime.RuntimeError.UnsupportedSpirV, + ffi.Result.UnsupportedExtension => spv.Runtime.RuntimeError.UnsupportedExtension, + ffi.Result.Unknown => spv.Runtime.RuntimeError.Unknown, + else => {}, + }; +} + +const ImageAPIBridge = struct { + threadlocal var current_image_api: ?*const ImageAPI = null; // Hacky + + fn getImageAPI() spv.Runtime.RuntimeError!*const ImageAPI { + return current_image_api orelse spv.Runtime.RuntimeError.Unknown; + } + + fn readImageFloat4(driver_image: *anyopaque, x: i32, y: i32, z: i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(f32) { + const image_api = try getImageAPI(); + + var dst: Vec4f = undefined; + const result = image_api.readImageFloat4(driver_image, @intCast(x), @intCast(y), @intCast(z), &dst); + + try fromCResult(result); + + return .{ + .x = dst.x, + .y = dst.y, + .z = dst.z, + .w = dst.w, + }; + } + + fn readImageInt4(driver_image: *anyopaque, x: i32, y: i32, z: i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(u32) { + const image_api = try getImageAPI(); + + var dst: Vec4u = undefined; + const result = image_api.readImageInt4(driver_image, @intCast(x), @intCast(y), @intCast(z), &dst); + + try fromCResult(result); + + return .{ + .x = dst.x, + .y = dst.y, + .z = dst.z, + .w = dst.w, + }; + } + + fn writeImageFloat4(driver_image: *anyopaque, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(f32)) spv.Runtime.RuntimeError!void { + const image_api = try getImageAPI(); + + const result = image_api.writeImageFloat4(driver_image, @intCast(x), @intCast(y), @intCast(z), .{ .x = pixel.x, .y = pixel.y, .z = pixel.z, .w = pixel.w }); + + try fromCResult(result); + } + + fn writeImageInt4(driver_image: *anyopaque, x: i32, y: i32, z: i32, pixel: spv.Runtime.Vec4(u32)) spv.Runtime.RuntimeError!void { + const image_api = try getImageAPI(); + + const result = image_api.writeImageInt4(driver_image, @intCast(x), @intCast(y), @intCast(z), .{ .x = pixel.x, .y = pixel.y, .z = pixel.z, .w = pixel.w }); + + try fromCResult(result); + } +}; + +const RuntimeWrapper = struct { + rt: spv.Runtime, + image_api: ImageAPI, +}; + +export fn SpvInitRuntime(rt: **RuntimeWrapper, module: *spv.Module, image_api: ImageAPI) callconv(.c) ffi.Result { const allocator = std.heap.c_allocator; - rt.* = allocator.create(spv.Runtime) catch return .OutOfMemory; + rt.* = allocator.create(RuntimeWrapper) catch return .OutOfMemory; + rt.*.image_api = image_api; - rt.*.* = spv.Runtime.init(allocator, module) catch |err| { + rt.*.rt = spv.Runtime.init( + allocator, + module, + .{ + .readImageFloat4 = ImageAPIBridge.readImageFloat4, + .readImageInt4 = ImageAPIBridge.readImageInt4, + .writeImageFloat4 = ImageAPIBridge.writeImageFloat4, + .writeImageInt4 = ImageAPIBridge.writeImageInt4, + }, + ) catch |err| { allocator.destroy(rt.*); return toCResult(err); }; return .Success; } -export fn SpvDeinitRuntime(rt: *spv.Runtime) callconv(.c) void { +export fn SpvDeinitRuntime(rt: *RuntimeWrapper) callconv(.c) void { const allocator = std.heap.c_allocator; - rt.deinit(allocator); + rt.rt.deinit(allocator); allocator.destroy(rt); } -export fn SpvAddSpecializationInfo(rt: *spv.Runtime, entry: CSpecializationEntry, data: [*]const u8, data_size: c_ulong) callconv(.c) ffi.Result { +export fn SpvAddSpecializationInfo(rt: *RuntimeWrapper, entry: CSpecializationEntry, data: [*]const u8, data_size: c_ulong) callconv(.c) ffi.Result { const allocator = std.heap.c_allocator; - rt.addSpecializationInfo( + rt.rt.addSpecializationInfo( allocator, .{ .id = entry.id, @@ -63,60 +179,66 @@ export fn SpvAddSpecializationInfo(rt: *spv.Runtime, entry: CSpecializationEntry 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); +export fn SpvGetEntryPointByName(rt: *RuntimeWrapper, name: [*:0]const u8, result: *spv.SpvWord) callconv(.c) ffi.Result { + result.* = rt.rt.getEntryPointByName(std.mem.span(name)) catch |err| return toCResult(err); return .Success; } -export fn SpvGetResultByLocation(rt: *spv.Runtime, location: spv.SpvWord, kind: LocationType, result: *spv.SpvWord) callconv(.c) ffi.Result { - result.* = rt.getResultByLocation(location, switch (kind) { +export fn SpvGetResultByLocation(rt: *RuntimeWrapper, location: spv.SpvWord, kind: LocationType, result: *spv.SpvWord) callconv(.c) ffi.Result { + result.* = rt.rt.getResultByLocation(location, switch (kind) { .input => .input, .output => .output, }) 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); +export fn SpvGetResultByName(rt: *RuntimeWrapper, name: [*:0]const u8, result: *spv.SpvWord) callconv(.c) ffi.Result { + result.* = rt.rt.getResultByName(std.mem.span(name)) catch |err| return toCResult(err); return .Success; } -export fn SpvGetResultMemorySize(rt: *spv.Runtime, result: spv.SpvWord, size: *c_ulong) callconv(.c) ffi.Result { - size.* = rt.getResultMemorySize(result) catch |err| return toCResult(err); +export fn SpvGetResultMemorySize(rt: *RuntimeWrapper, result: spv.SpvWord, size: *c_ulong) callconv(.c) ffi.Result { + size.* = rt.rt.getResultMemorySize(result) catch |err| return toCResult(err); return .Success; } -export fn SpvHasResultDecoration(rt: *spv.Runtime, result: spv.SpvWord, decoration: spv.spv.SpvDecoration) callconv(.c) c_int { - return if (rt.hasResultDecoration(result, decoration)) 1 else 0; +export fn SpvHasResultDecoration(rt: *RuntimeWrapper, result: spv.SpvWord, decoration: spv.spv.SpvDecoration) callconv(.c) c_int { + return if (rt.rt.hasResultDecoration(result, decoration)) 1 else 0; } -export fn SpvCallEntryPoint(rt: *spv.Runtime, entry_point: spv.SpvWord) callconv(.c) ffi.Result { +export fn SpvCallEntryPoint(rt: *RuntimeWrapper, entry_point: spv.SpvWord) callconv(.c) ffi.Result { const allocator = std.heap.c_allocator; - rt.callEntryPoint(allocator, entry_point) catch |err| return toCResult(err); + + // Ultra hacky + const previous_image_api = ImageAPIBridge.current_image_api; + ImageAPIBridge.current_image_api = &rt.image_api; + defer ImageAPIBridge.current_image_api = previous_image_api; + + rt.rt.callEntryPoint(allocator, entry_point) catch |err| return toCResult(err); return .Success; } -export fn SpvReadOutput(rt: *spv.Runtime, output: [*]u8, output_size: c_ulong, result: spv.SpvWord) callconv(.c) ffi.Result { - rt.readOutput(output[0..output_size], result) catch |err| return toCResult(err); +export fn SpvReadOutput(rt: *RuntimeWrapper, output: [*]u8, output_size: c_ulong, result: spv.SpvWord) callconv(.c) ffi.Result { + rt.rt.readOutput(output[0..output_size], result) catch |err| return toCResult(err); return .Success; } -export fn SpvReadBuiltIn(rt: *spv.Runtime, output: [*]u8, output_size: c_ulong, builtin: spv.spv.SpvBuiltIn) callconv(.c) ffi.Result { - rt.readBuiltIn(output[0..output_size], builtin) catch |err| return toCResult(err); +export fn SpvReadBuiltIn(rt: *RuntimeWrapper, output: [*]u8, output_size: c_ulong, builtin: spv.spv.SpvBuiltIn) callconv(.c) ffi.Result { + rt.rt.readBuiltIn(output[0..output_size], builtin) catch |err| return toCResult(err); return .Success; } -export fn SpvWriteInput(rt: *spv.Runtime, input: [*]const u8, input_size: c_ulong, result: spv.SpvWord) callconv(.c) ffi.Result { - rt.writeInput(input[0..input_size], result) catch |err| return toCResult(err); +export fn SpvWriteInput(rt: *RuntimeWrapper, input: [*]const u8, input_size: c_ulong, result: spv.SpvWord) callconv(.c) ffi.Result { + rt.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: c_ulong, builtin: spv.spv.SpvBuiltIn) callconv(.c) ffi.Result { - rt.writeBuiltIn(input[0..input_size], builtin) catch |err| return toCResult(err); +export fn SpvWriteBuiltIn(rt: *RuntimeWrapper, input: [*]const u8, input_size: c_ulong, builtin: spv.spv.SpvBuiltIn) callconv(.c) ffi.Result { + rt.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: c_ulong, 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); +export fn SpvWriteDescriptorSet(rt: *RuntimeWrapper, input: [*]const u8, input_size: c_ulong, set: spv.SpvWord, binding: spv.SpvWord, descriptor_index: spv.SpvWord) callconv(.c) ffi.Result { + rt.rt.writeDescriptorSet(input[0..input_size], set, binding, descriptor_index) catch |err| return toCResult(err); return .Success; } diff --git a/src/Image.zig b/src/Image.zig deleted file mode 100644 index 0ec3a80..0000000 --- a/src/Image.zig +++ /dev/null @@ -1,13 +0,0 @@ -const std = @import("std"); -const spv = @import("spv.zig"); - -const SpvVoid = spv.SpvVoid; -const SpvByte = spv.SpvByte; -const SpvWord = spv.SpvWord; -const SpvBool = spv.SpvBool; - -width: u32, -height: u32, -depth: u32, -layers: u32, -levels: u32, diff --git a/src/Module.zig b/src/Module.zig index 6053483..5557930 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -142,7 +142,7 @@ fn checkEndiannessFromSpvMagic(magic: SpvWord) bool { } fn pass(self: *Self, allocator: std.mem.Allocator) ModuleError!void { - var rt = Runtime.init(allocator, self) catch return ModuleError.OutOfMemory; + var rt = Runtime.init(allocator, self, undefined) catch return ModuleError.OutOfMemory; defer { for (self.results, rt.results) |*result, new_result| { result.deinit(allocator); diff --git a/src/Runtime.zig b/src/Runtime.zig index 5f30322..a573372 100644 --- a/src/Runtime.zig +++ b/src/Runtime.zig @@ -11,7 +11,6 @@ const SpvByte = spv.SpvByte; const SpvWord = spv.SpvWord; const SpvBool = spv.SpvBool; -const Image = @import("Image.zig"); const Module = @import("Module.zig"); const Result = @import("Result.zig"); const WordIterator = @import("WordIterator.zig"); @@ -46,6 +45,22 @@ pub const Function = struct { ret: *Result, }; +pub fn Vec4(comptime T: type) type { + return struct { + x: T, + y: T, + z: T, + w: T, + }; +} + +pub const ImageAPI = struct { + readImageFloat4: *const fn (driver_image: *anyopaque, x: i32, y: i32, z: i32) RuntimeError!Vec4(f32), + readImageInt4: *const fn (driver_image: *anyopaque, x: i32, y: i32, z: i32) RuntimeError!Vec4(u32), + writeImageFloat4: *const fn (driver_image: *anyopaque, x: i32, y: i32, z: i32, pixel: Vec4(f32)) RuntimeError!void, + writeImageInt4: *const fn (driver_image: *anyopaque, x: i32, y: i32, z: i32, pixel: Vec4(u32)) RuntimeError!void, +}; + mod: *Module, it: WordIterator, @@ -61,7 +76,9 @@ previous_label: ?SpvWord, specialization_constants: std.AutoHashMapUnmanaged(u32, []const u8), -pub fn init(allocator: std.mem.Allocator, module: *Module) RuntimeError!Self { +image_api: ImageAPI, + +pub fn init(allocator: std.mem.Allocator, module: *Module, image_api: ImageAPI) RuntimeError!Self { return .{ .mod = module, .it = module.it, @@ -78,6 +95,7 @@ pub fn init(allocator: std.mem.Allocator, module: *Module) RuntimeError!Self { .current_label = null, .previous_label = null, .specialization_constants = .empty, + .image_api = image_api, }; } diff --git a/src/Value.zig b/src/Value.zig index 5481123..bffacd7 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -109,17 +109,7 @@ pub const Value = union(Type) { Function: noreturn, Image: struct { type_word: SpvWord, - data: []u8, - - pub inline fn getInfos(self: *const @This(), results: []const Result) RuntimeError!@FieldType(Result.TypeData, "Image") { - return switch (try results[self.type_word].getConstVariant()) { - .Type => |t| switch (t) { - .Image => |i| i, - else => RuntimeError.InvalidSpirV, - }, - else => RuntimeError.InvalidSpirV, - }; - } + driver_image: *anyopaque, }, Sampler: struct {}, SampledImage: struct {}, @@ -236,7 +226,7 @@ pub const Value = union(Type) { .Image => .{ .Image = .{ .type_word = resolved, - .data = &.{}, + .driver_image = undefined, }, }, .Sampler => RuntimeError.ToDo, @@ -545,7 +535,7 @@ pub const Value = union(Type) { return offset; }, .RuntimeArray => |*arr| arr.data = input[0..], - .Image => |*img| img.data = input[0..], + .Image => |*img| img.driver_image = @ptrFromInt(std.mem.bytesToValue(usize, input[0..])), else => return RuntimeError.InvalidValueType, } return 0; diff --git a/src/lib.zig b/src/lib.zig index 1bae108..664d962 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -30,7 +30,6 @@ const std = @import("std"); -pub const Image = @import("Image.zig"); pub const Module = @import("Module.zig"); pub const Runtime = @import("Runtime.zig"); diff --git a/src/opcodes.zig b/src/opcodes.zig index 6c2192c..22ae025 100644 --- a/src/opcodes.zig +++ b/src/opcodes.zig @@ -324,6 +324,8 @@ pub fn initRuntimeDispatcher() void { runtime_dispatcher[@intFromEnum(spv.SpvOp.VectorTimesMatrix)] = MathEngine(.Float, .VectorTimesMatrix, false).op; // TODO runtime_dispatcher[@intFromEnum(spv.SpvOp.VectorTimesScalar)] = MathEngine(.Float, .VectorTimesScalar, false).op; runtime_dispatcher[@intFromEnum(spv.SpvOp.SMulExtended)] = opSMulExtended; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ImageRead)] = opImageRead; + runtime_dispatcher[@intFromEnum(spv.SpvOp.ImageWrite)] = opImageWrite; // zig fmt: on // Extensions init @@ -2144,6 +2146,270 @@ fn opFunctionParameter(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeEr rt.current_parameter_index += 1; } +fn opImageRead(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + _ = try rt.it.next(); // result Type + const result_id = try rt.it.next(); + const image = &rt.results[try rt.it.next()]; + const coordinate = try rt.results[try rt.it.next()].getValue(); + const dst = try rt.results[result_id].getValue(); + + const driver_image = switch ((try image.getValue()).*) { + .Image => |img| img.driver_image, + else => return RuntimeError.InvalidSpirV, + }; + + const helpers = struct { + fn readCoordLane(coord: *const Value, lane_index: usize) RuntimeError!i32 { + return switch (coord.*) { + .Int => |i| { + if (lane_index != 0) return RuntimeError.OutOfBounds; + return if (i.is_signed) i.value.sint32 else @intCast(i.value.uint32); + }, + .Vector => |lanes| { + if (lane_index >= lanes.len) return RuntimeError.OutOfBounds; + return readCoordLane(&lanes[lane_index], 0); + }, + .Vector4i32 => |v| switch (lane_index) { + inline 0...3 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + .Vector3i32 => |v| switch (lane_index) { + inline 0...2 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + .Vector2i32 => |v| switch (lane_index) { + inline 0...1 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + .Vector4u32 => |v| switch (lane_index) { + inline 0...3 => |idx| @intCast(v[idx]), + else => return RuntimeError.OutOfBounds, + }, + .Vector3u32 => |v| switch (lane_index) { + inline 0...2 => |idx| @intCast(v[idx]), + else => return RuntimeError.OutOfBounds, + }, + .Vector2u32 => |v| switch (lane_index) { + inline 0...1 => |idx| @intCast(v[idx]), + else => return RuntimeError.OutOfBounds, + }, + else => return RuntimeError.InvalidValueType, + }; + } + + fn writeFloatTexel(dst_value: *Value, texel: Runtime.Vec4(f32)) RuntimeError!void { + switch (dst_value.*) { + .Vector4f32 => |*v| v.* = .{ texel.x, texel.y, texel.z, texel.w }, + .Vector3f32 => |*v| v.* = .{ texel.x, texel.y, texel.z }, + .Vector2f32 => |*v| v.* = .{ texel.x, texel.y }, + .Vector => |lanes| { + if (lanes.len > 4) return RuntimeError.InvalidSpirV; + const values = [_]f32{ texel.x, texel.y, texel.z, texel.w }; + for (lanes, 0..) |*lane, i| { + switch (lane.*) { + .Float => |*f| f.value.float32 = values[i], + else => return RuntimeError.InvalidValueType, + } + } + }, + else => return RuntimeError.InvalidValueType, + } + } + + fn writeIntTexel(dst_value: *Value, texel: Runtime.Vec4(u32)) RuntimeError!void { + switch (dst_value.*) { + .Vector4i32 => |*v| v.* = .{ @bitCast(texel.x), @bitCast(texel.y), @bitCast(texel.z), @bitCast(texel.w) }, + .Vector3i32 => |*v| v.* = .{ @bitCast(texel.x), @bitCast(texel.y), @bitCast(texel.z) }, + .Vector2i32 => |*v| v.* = .{ @bitCast(texel.x), @bitCast(texel.y) }, + .Vector4u32 => |*v| v.* = .{ texel.x, texel.y, texel.z, texel.w }, + .Vector3u32 => |*v| v.* = .{ texel.x, texel.y, texel.z }, + .Vector2u32 => |*v| v.* = .{ texel.x, texel.y }, + .Vector => |lanes| { + if (lanes.len > 4) return RuntimeError.InvalidSpirV; + const values = [_]u32{ texel.x, texel.y, texel.z, texel.w }; + for (lanes, 0..) |*lane, i| { + switch (lane.*) { + .Int => |*int| int.value.uint32 = values[i], + else => return RuntimeError.InvalidValueType, + } + } + }, + else => return RuntimeError.InvalidValueType, + } + } + }; + + const x = try helpers.readCoordLane(coordinate, 0); + const y = helpers.readCoordLane(coordinate, 1) catch 0; + const z = helpers.readCoordLane(coordinate, 2) catch 0; + + switch (dst.*) { + .Vector4f32, .Vector3f32, .Vector2f32 => try helpers.writeFloatTexel(dst, try rt.image_api.readImageFloat4(driver_image, x, y, z)), + .Vector4i32, .Vector3i32, .Vector2i32, .Vector4u32, .Vector3u32, .Vector2u32 => try helpers.writeIntTexel(dst, try rt.image_api.readImageInt4(driver_image, x, y, z)), + .Vector => |lanes| { + if (lanes.len == 0) return RuntimeError.InvalidSpirV; + switch (lanes[0]) { + .Float => try helpers.writeFloatTexel(dst, try rt.image_api.readImageFloat4(driver_image, x, y, z)), + .Int => try helpers.writeIntTexel(dst, try rt.image_api.readImageInt4(driver_image, x, y, z)), + else => return RuntimeError.InvalidValueType, + } + }, + else => return RuntimeError.InvalidValueType, + } +} + + +fn opImageWrite(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { + const image = &rt.results[try rt.it.next()]; + const coordinate = try rt.results[try rt.it.next()].getValue(); + const texel = try rt.results[try rt.it.next()].getValue(); + + const driver_image = switch ((try image.getValue()).*) { + .Image => |img| img.driver_image, + else => return RuntimeError.InvalidSpirV, + }; + + const helpers = struct { + fn readCoordLane(coord: *const Value, lane_index: usize) RuntimeError!i32 { + return switch (coord.*) { + .Int => |i| { + if (lane_index != 0) return RuntimeError.OutOfBounds; + return if (i.is_signed) i.value.sint32 else @intCast(i.value.uint32); + }, + .Vector => |lanes| { + if (lane_index >= lanes.len) return RuntimeError.OutOfBounds; + return readCoordLane(&lanes[lane_index], 0); + }, + .Vector4i32 => |v| switch (lane_index) { + inline 0...3 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + .Vector3i32 => |v| switch (lane_index) { + inline 0...2 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + .Vector2i32 => |v| switch (lane_index) { + inline 0...1 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + .Vector4u32 => |v| switch (lane_index) { + inline 0...3 => |idx| @intCast(v[idx]), + else => return RuntimeError.OutOfBounds, + }, + .Vector3u32 => |v| switch (lane_index) { + inline 0...2 => |idx| @intCast(v[idx]), + else => return RuntimeError.OutOfBounds, + }, + .Vector2u32 => |v| switch (lane_index) { + inline 0...1 => |idx| @intCast(v[idx]), + else => return RuntimeError.OutOfBounds, + }, + else => return RuntimeError.InvalidValueType, + }; + } + + fn readFloatLane(texel_value: *const Value, lane_index: usize) RuntimeError!f32 { + return switch (texel_value.*) { + .Float => |f| { + if (lane_index != 0) return RuntimeError.OutOfBounds; + return f.value.float32; + }, + .Vector => |lanes| { + if (lane_index >= lanes.len) return RuntimeError.OutOfBounds; + return readFloatLane(&lanes[lane_index], 0); + }, + .Vector4f32 => |v| switch (lane_index) { + inline 0...3 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + .Vector3f32 => |v| switch (lane_index) { + inline 0...2 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + .Vector2f32 => |v| switch (lane_index) { + inline 0...1 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + else => return RuntimeError.InvalidValueType, + }; + } + + fn readIntLane(texel_value: *const Value, lane_index: usize) RuntimeError!u32 { + return switch (texel_value.*) { + .Int => |i| { + if (lane_index != 0) return RuntimeError.OutOfBounds; + return if (i.is_signed) @bitCast(i.value.sint32) else i.value.uint32; + }, + .Vector => |lanes| { + if (lane_index >= lanes.len) return RuntimeError.OutOfBounds; + return readIntLane(&lanes[lane_index], 0); + }, + .Vector4i32 => |v| switch (lane_index) { + inline 0...3 => |idx| @bitCast(v[idx]), + else => return RuntimeError.OutOfBounds, + }, + .Vector3i32 => |v| switch (lane_index) { + inline 0...2 => |idx| @bitCast(v[idx]), + else => return RuntimeError.OutOfBounds, + }, + .Vector2i32 => |v| switch (lane_index) { + inline 0...1 => |idx| @bitCast(v[idx]), + else => return RuntimeError.OutOfBounds, + }, + .Vector4u32 => |v| switch (lane_index) { + inline 0...3 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + .Vector3u32 => |v| switch (lane_index) { + inline 0...2 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + .Vector2u32 => |v| switch (lane_index) { + inline 0...1 => |idx| v[idx], + else => return RuntimeError.OutOfBounds, + }, + else => return RuntimeError.InvalidValueType, + }; + } + + fn readFloatTexel(texel_value: *const Value) RuntimeError!Runtime.Vec4(f32) { + return .{ + .x = try readFloatLane(texel_value, 0), + .y = readFloatLane(texel_value, 1) catch 0.0, + .z = readFloatLane(texel_value, 2) catch 0.0, + .w = readFloatLane(texel_value, 3) catch 0.0, + }; + } + + fn readIntTexel(texel_value: *const Value) RuntimeError!Runtime.Vec4(u32) { + return .{ + .x = try readIntLane(texel_value, 0), + .y = readIntLane(texel_value, 1) catch 0, + .z = readIntLane(texel_value, 2) catch 0, + .w = readIntLane(texel_value, 3) catch 0, + }; + } + }; + + const x = try helpers.readCoordLane(coordinate, 0); + const y = helpers.readCoordLane(coordinate, 1) catch 0; + const z = helpers.readCoordLane(coordinate, 2) catch 0; + + switch (texel.*) { + .Float, .Vector4f32, .Vector3f32, .Vector2f32 => try rt.image_api.writeImageFloat4(driver_image, x, y, z, try helpers.readFloatTexel(texel)), + .Int, .Vector4i32, .Vector3i32, .Vector2i32, .Vector4u32, .Vector3u32, .Vector2u32 => try rt.image_api.writeImageInt4(driver_image, x, y, z, try helpers.readIntTexel(texel)), + .Vector => |lanes| { + if (lanes.len == 0) return RuntimeError.InvalidSpirV; + switch (lanes[0]) { + .Float => try rt.image_api.writeImageFloat4(driver_image, x, y, z, try helpers.readFloatTexel(texel)), + .Int => try rt.image_api.writeImageInt4(driver_image, x, y, z, try helpers.readIntTexel(texel)), + else => return RuntimeError.InvalidValueType, + } + }, + else => return RuntimeError.InvalidValueType, + } +} + fn opLabel(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void { const id = try rt.it.next(); rt.current_label = id; diff --git a/test/root.zig b/test/root.zig index 4f26fa4..d82ae2c 100644 --- a/test/root.zig +++ b/test/root.zig @@ -45,7 +45,7 @@ pub const case = struct { var module = try spv.Module.init(allocator, config.source, opt); defer module.deinit(allocator); - var rt = try spv.Runtime.init(allocator, &module); + var rt = try spv.Runtime.init(allocator, &module, undefined); defer rt.deinit(allocator); for (config.inputs, 0..) |input, n| { diff --git a/test_c/main.c b/test_c/main.c index 341fb2c..fbdfc9f 100644 --- a/test_c/main.c +++ b/test_c/main.c @@ -54,7 +54,7 @@ int main(void) } SpvRuntime runtime; - if(SpvInitRuntime(&runtime, module) != SPV_RESULT_SUCCESS) + if(SpvInitRuntime(&runtime, module, (SpvImageAPI){0}) != SPV_RESULT_SUCCESS) { fprintf(stderr, "Runtime init failed\n"); return -1;