adding storage image read and writes
Build / build (push) Successful in 1m42s
Test / build (push) Successful in 8m27s

This commit is contained in:
2026-04-29 01:19:48 +02:00
parent cc041c9677
commit 046b1c8f9e
10 changed files with 473 additions and 62 deletions
-13
View File
@@ -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,
+1 -1
View File
@@ -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);
+20 -2
View File
@@ -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,
};
}
+3 -13
View File
@@ -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;
-1
View File
@@ -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");
+266
View File
@@ -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;