fixing vector convertions and derivative propagation through composite construct

This commit is contained in:
2026-06-19 00:40:22 +02:00
parent 1f43dc95df
commit 391f4415d9
5 changed files with 349 additions and 226 deletions
+51 -13
View File
@@ -274,7 +274,56 @@ fn applyStructMemberInterfaceDecorations(self: *Self, storage_class: spv.SpvStor
}
fn applyDecorations(self: *Self, allocator: std.mem.Allocator) ModuleError!void {
for (self.results, 0..) |result, id| {
const helpers = struct {
fn applyValueLayout(results: []Result, value: *Value, type_word: SpvWord) ModuleError!void {
const type_variant = results[type_word].variant orelse return;
const type_data = switch (type_variant) {
.Type => |type_data| type_data,
else => return,
};
switch (value.*) {
.Structure => |*structure| switch (type_data) {
.Structure => |type_structure| {
@memcpy(@constCast(structure.offsets), type_structure.members_offsets);
@memcpy(@constCast(structure.matrix_strides), type_structure.members_matrix_strides);
for (structure.values, type_structure.members_type_word) |*member_value, member_type_word| {
try applyValueLayout(results, member_value, member_type_word);
}
},
else => {},
},
.Array => |array| switch (type_data) {
.Array => |type_array| {
for (array.values) |*element| {
try applyValueLayout(results, element, type_array.components_type_word);
}
},
else => {},
},
.Matrix => |columns| switch (type_data) {
.Matrix => |type_matrix| {
for (columns) |*column| {
try applyValueLayout(results, column, type_matrix.column_type_word);
}
},
else => {},
},
.Vector => |elements| switch (type_data) {
.Vector => |type_vector| {
for (elements) |*element| {
try applyValueLayout(results, element, type_vector.components_type_word);
}
},
else => {},
},
else => {},
}
}
};
for (self.results, 0..) |*result, id| {
if (result.variant == null)
continue;
@@ -323,18 +372,7 @@ fn applyDecorations(self: *Self, allocator: std.mem.Allocator) ModuleError!void
.StorageBuffer,
.Uniform,
.PushConstant,
=> if (v.value == .Structure) {
if (self.results[v.type_word].variant) |type_variant| switch (type_variant) {
.Type => |type_data| switch (type_data) {
.Structure => |s| {
@memcpy(@constCast(v.value.Structure.offsets), s.members_offsets);
@memcpy(@constCast(v.value.Structure.matrix_strides), s.members_matrix_strides);
},
else => {},
},
else => {},
};
},
=> try helpers.applyValueLayout(self.results, &v.value, v.type_word),
else => {},
}
},
+20 -12
View File
@@ -501,6 +501,24 @@ pub const Value = union(Type) {
return matrix_size;
}
pub fn writeMatrixWithStride(self: *Self, input: []const u8, matrix_stride: SpvWord) RuntimeError!usize {
const columns = switch (self.*) {
.Matrix => |columns| columns,
else => return RuntimeError.InvalidValueType,
};
if (columns.len == 0) return 0;
const matrix_stride_usize: usize = @intCast(matrix_stride);
const column_size = try columns[0].getPlainMemorySize();
const matrix_size = (columns.len - 1) * matrix_stride_usize + column_size;
if (input.len < matrix_size) return RuntimeError.OutOfBounds;
for (columns, 0..) |*column, column_index| {
_ = try column.write(input[column_index * matrix_stride_usize ..]);
}
return matrix_size;
}
pub fn write(self: *Self, input: []const u8) RuntimeError!usize {
const vecRoutine = struct {
inline fn routine(comptime T: type, vec: *T, in: []const u8) RuntimeError!usize {
@@ -605,18 +623,8 @@ pub const Value = union(Type) {
const write_size = switch (v.*) {
.Matrix => |*columns| blk: {
const matrix_stride = s.matrix_strides[i] orelse break :blk try v.write(input[member_offset..]);
if (columns.len == 0) break :blk @as(usize, 0);
const matrix_stride_usize: usize = @intCast(matrix_stride);
const column_size = try columns.*[0].getPlainMemorySize();
const matrix_size = (columns.len - 1) * matrix_stride_usize + column_size;
if (input.len - member_offset < matrix_size)
return RuntimeError.OutOfBounds;
for (columns.*, 0..) |*column, column_index| {
_ = try column.write(input[member_offset + (column_index * matrix_stride_usize) ..]);
}
break :blk matrix_size;
_ = columns;
break :blk try v.writeMatrixWithStride(input[member_offset..], matrix_stride);
},
else => try v.write(input[member_offset..]),
};
+145 -201
View File
@@ -972,36 +972,28 @@ fn CondEngine(comptime T: PrimitiveType, comptime Op: CondOp) type {
fn ConversionEngine(comptime from_kind: PrimitiveType, comptime to_kind: PrimitiveType) type {
return struct {
fn castLane(comptime ToT: type, from_bit_count: SpvWord, from: *Value) RuntimeError!ToT {
fn castLane(comptime ToT: type, from_bit_count: SpvWord, from: *const Value, lane_index: usize) RuntimeError!ToT {
return switch (from_bit_count) {
inline 8, 16, 32, 64 => |bits| blk: {
if (bits == 8 and from_kind == .Float) return RuntimeError.InvalidSpirV; // No f8
const v = (try Value.getPrimitiveField(from_kind, bits, from)).*;
const v = try Value.readLane(from_kind, bits, from, lane_index);
break :blk std.math.lossyCast(ToT, v);
},
else => return RuntimeError.InvalidSpirV,
};
}
fn applyScalar(from_bit_count: SpvWord, to_bit_count: SpvWord, dst: *Value, from: *Value) RuntimeError!void {
fn applyLane(from_bit_count: SpvWord, to_bit_count: SpvWord, dst: *Value, from: *const Value, lane_index: usize) RuntimeError!void {
switch (to_bit_count) {
inline 8, 16, 32, 64 => |bits| {
if (bits == 8 and to_kind == .Float) return RuntimeError.InvalidSpirV; // No f8
const ToT = Value.getPrimitiveFieldType(to_kind, bits);
(try Value.getPrimitiveField(to_kind, bits, dst)).* = try castLane(ToT, from_bit_count, from);
try Value.writeLane(to_kind, bits, dst, lane_index, try castLane(ToT, from_bit_count, from, lane_index));
},
else => return RuntimeError.InvalidSpirV,
}
}
fn castSIMDVector(comptime ToT: type, comptime N: usize, dst_arr: *@Vector(N, ToT), src_arr: *const @Vector(N, ToT)) void {
inline for (0..N) |i| dst_arr[i] = std.math.lossyCast(ToT, src_arr[i]);
}
fn castSIMDVectorFromOther(comptime ToT: type, comptime FromT: type, comptime N: usize, dst_arr: *@Vector(N, ToT), src_arr: *const @Vector(N, FromT)) void {
inline for (0..N) |i| dst_arr[i] = std.math.lossyCast(ToT, src_arr[i]);
}
fn op(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError!void {
const target_type = (try rt.results[try rt.it.next()].getVariant()).Type;
const dst_value = try rt.results[try rt.it.next()].getValue();
@@ -1013,81 +1005,12 @@ fn ConversionEngine(comptime from_kind: PrimitiveType, comptime to_kind: Primiti
const from_bits = try Result.resolveLaneBitWidth((try rt.results[src_type_word].getVariant()).Type, rt);
const to_bits = try Result.resolveLaneBitWidth(target_type, rt);
switch (dst_value.*) {
.Float => {
if (to_kind != .Float) return RuntimeError.InvalidSpirV;
try applyScalar(from_bits, to_bits, dst_value, src_value);
},
.Int => {
if (to_kind != .SInt and to_kind != .UInt) return RuntimeError.InvalidSpirV;
try applyScalar(from_bits, to_bits, dst_value, src_value);
},
.Vector => |dst_vec| {
const src_vec = src_value.Vector;
if (dst_vec.len != src_vec.len) return RuntimeError.InvalidSpirV;
for (dst_vec, src_vec) |*d_lane, *s_lane| {
try applyScalar(from_bits, to_bits, d_lane, s_lane);
}
},
const dst_lane_count = try dst_value.resolveLaneCount();
const src_lane_count = try src_value.resolveLaneCount();
if (dst_lane_count != src_lane_count) return RuntimeError.InvalidSpirV;
.Vector4f32 => |*dst| switch (src_value.*) {
.Vector4f32 => castSIMDVector(f32, 4, dst, &src_value.Vector4f32),
.Vector4i32 => castSIMDVectorFromOther(f32, i32, 4, dst, &src_value.Vector4i32),
.Vector4u32 => castSIMDVectorFromOther(f32, u32, 4, dst, &src_value.Vector4u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector3f32 => |*dst| switch (src_value.*) {
.Vector3f32 => castSIMDVector(f32, 3, dst, &src_value.Vector3f32),
.Vector3i32 => castSIMDVectorFromOther(f32, i32, 3, dst, &src_value.Vector3i32),
.Vector3u32 => castSIMDVectorFromOther(f32, u32, 3, dst, &src_value.Vector3u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector2f32 => |*dst| switch (src_value.*) {
.Vector2f32 => castSIMDVector(f32, 2, dst, &src_value.Vector2f32),
.Vector2i32 => castSIMDVectorFromOther(f32, i32, 2, dst, &src_value.Vector2i32),
.Vector2u32 => castSIMDVectorFromOther(f32, u32, 2, dst, &src_value.Vector2u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector4i32 => |*dst| switch (src_value.*) {
.Vector4f32 => castSIMDVectorFromOther(i32, f32, 4, dst, &src_value.Vector4f32),
.Vector4i32 => castSIMDVector(i32, 4, dst, &src_value.Vector4i32),
.Vector4u32 => castSIMDVectorFromOther(i32, u32, 4, dst, &src_value.Vector4u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector3i32 => |*dst| switch (src_value.*) {
.Vector3f32 => castSIMDVectorFromOther(i32, f32, 3, dst, &src_value.Vector3f32),
.Vector3i32 => castSIMDVector(i32, 3, dst, &src_value.Vector3i32),
.Vector3u32 => castSIMDVectorFromOther(i32, u32, 3, dst, &src_value.Vector3u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector2i32 => |*dst| switch (src_value.*) {
.Vector2f32 => castSIMDVectorFromOther(i32, f32, 2, dst, &src_value.Vector2f32),
.Vector2i32 => castSIMDVector(i32, 2, dst, &src_value.Vector2i32),
.Vector2u32 => castSIMDVectorFromOther(i32, u32, 2, dst, &src_value.Vector2u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector4u32 => |*dst| switch (src_value.*) {
.Vector4f32 => castSIMDVectorFromOther(u32, f32, 4, dst, &src_value.Vector4f32),
.Vector4i32 => castSIMDVectorFromOther(u32, i32, 4, dst, &src_value.Vector4i32),
.Vector4u32 => castSIMDVector(u32, 4, dst, &src_value.Vector4u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector3u32 => |*dst| switch (src_value.*) {
.Vector3f32 => castSIMDVectorFromOther(u32, f32, 3, dst, &src_value.Vector3f32),
.Vector3i32 => castSIMDVectorFromOther(u32, i32, 3, dst, &src_value.Vector3i32),
.Vector3u32 => castSIMDVector(u32, 3, dst, &src_value.Vector3u32),
else => return RuntimeError.InvalidSpirV,
},
.Vector2u32 => |*dst| switch (src_value.*) {
.Vector2f32 => castSIMDVectorFromOther(u32, f32, 2, dst, &src_value.Vector2f32),
.Vector2i32 => castSIMDVectorFromOther(u32, i32, 2, dst, &src_value.Vector2i32),
.Vector2u32 => castSIMDVector(u32, 2, dst, &src_value.Vector2u32),
else => return RuntimeError.InvalidSpirV,
},
else => return RuntimeError.InvalidSpirV,
for (0..dst_lane_count) |lane_index| {
try applyLane(from_bits, to_bits, dst_value, src_value, lane_index);
}
}
};
@@ -2452,7 +2375,9 @@ fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp, comptime IsAtomic:
.VectorTimesMatrix,
=> if (comptime is_int) @mulWithOverflow(op1, op2)[0] else op1 * op2,
.Div => blk: {
if (op2_is_zero) return RuntimeError.DivisionByZero;
if (comptime is_int) {
if (op2_is_zero) return RuntimeError.DivisionByZero;
}
break :blk if (comptime is_int) @divTrunc(op1, op2) else op1 / op2;
},
.Mod => if (op2_is_zero) return RuntimeError.DivisionByZero else @mod(op1, op2),
@@ -2624,7 +2549,7 @@ fn MathEngine(comptime T: PrimitiveType, comptime Op: MathOp, comptime IsAtomic:
fn operationSingle(comptime TT: type, ope: TT) RuntimeError!TT {
return switch (Op) {
.Negate => if (@typeInfo(TT) == .int) std.math.negate(ope) catch return RuntimeError.InvalidSpirV else -ope,
.Negate => if (@typeInfo(TT) == .int) @subWithOverflow(@as(TT, 0), ope)[0] else -ope,
else => return RuntimeError.InvalidSpirV,
};
}
@@ -3042,6 +2967,13 @@ fn copyValue(dst: *Value, src: *const Value) RuntimeError!void {
else => return RuntimeError.InvalidSpirV,
}
}
inline fn readWindow(dst_v: *Value, window: []const u8, matrix_stride: ?SpvWord) RuntimeError!void {
_ = if (matrix_stride) |stride|
try dst_v.writeMatrixWithStride(window, stride)
else
try dst_v.write(window);
}
};
if (std.meta.activeTag(dst.*) == .Pointer) {
@@ -3100,14 +3032,43 @@ fn copyValue(dst: *Value, src: *const Value) RuntimeError!void {
try helpers.copySlice(dst_slice.?, a.values);
},
.Structure => |s| {
if (dst.* == .Structure) {
@memcpy(@constCast(dst.Structure.offsets), s.offsets);
@memcpy(@constCast(dst.Structure.matrix_strides), s.matrix_strides);
}
const dst_slice = helpers.getDstSlice(dst);
try helpers.copySlice(dst_slice.?, s.values);
},
.Pointer => |ptr| switch (ptr.ptr) {
.common => |src_val_ptr| try copyValue(dst, src_val_ptr),
.f32_ptr => |src_f32_ptr| try helpers.readF32(dst, src_f32_ptr),
.i32_ptr => |src_i32_ptr| try helpers.readI32(dst, src_i32_ptr),
.u32_ptr => |src_u32_ptr| try helpers.readU32(dst, src_u32_ptr),
.common => |src_val_ptr| {
if (ptr.uniform_slice_window) |window| {
try copyValue(dst, src_val_ptr);
try helpers.readWindow(dst, window, ptr.matrix_stride);
} else {
try copyValue(dst, src_val_ptr);
}
},
.f32_ptr => |src_f32_ptr| {
if (ptr.uniform_slice_window) |window| {
try helpers.readWindow(dst, window, ptr.matrix_stride);
} else {
try helpers.readF32(dst, src_f32_ptr);
}
},
.i32_ptr => |src_i32_ptr| {
if (ptr.uniform_slice_window) |window| {
try helpers.readWindow(dst, window, ptr.matrix_stride);
} else {
try helpers.readI32(dst, src_i32_ptr);
}
},
.u32_ptr => |src_u32_ptr| {
if (ptr.uniform_slice_window) |window| {
try helpers.readWindow(dst, window, ptr.matrix_stride);
} else {
try helpers.readU32(dst, src_u32_ptr);
}
},
},
.RuntimeArray => |src_arr| switch (dst.*) {
.RuntimeArray => |dst_arr| @memcpy(dst_arr.data, src_arr.data),
@@ -3497,134 +3458,111 @@ fn opControlBarrier(_: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError
return RuntimeError.Barrier;
}
fn opCompositeConstruct(_: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void {
_ = rt.it.skip();
fn opCompositeConstruct(allocator: std.mem.Allocator, word_count: SpvWord, rt: *Runtime) RuntimeError!void {
const target_type_word = try rt.it.next();
const target_type = (try rt.results[target_type_word].getVariant()).Type;
const id = try rt.it.next();
const index_count = word_count - 2;
const index_count: usize = @intCast(word_count - 2);
const operand_ids = allocator.alloc(SpvWord, index_count) catch return RuntimeError.OutOfMemory;
defer allocator.free(operand_ids);
for (operand_ids) |*operand_id| {
operand_id.* = try rt.it.next();
}
const value = &(try rt.results[id].getVariant()).Constant.value;
if (value.getCompositeDataOrNull()) |target| {
for (target[0..index_count]) |*elem| {
const elem_value = (try rt.results[try rt.it.next()].getVariant()).Constant.value;
elem.* = elem_value;
for (target[0..index_count], operand_ids) |*elem, operand_id| {
try copyValue(elem, try rt.results[operand_id].getValue());
}
rt.clearDerivative(allocator, id);
return;
}
const vectorRoutines = struct {
fn routines(value2: *Value, rt2: *Runtime) RuntimeError!void {
switch (value2.*) {
.Vector4f32 => |*vec| inline for (0..4) |i| {
switch ((try rt2.results[try rt2.it.next()].getVariant()).Constant.value) {
.Vector4f32 => |v| {
vec.* = v;
return;
},
.Float => |f| vec[i] = f.value.float32,
else => return RuntimeError.InvalidValueType,
}
},
const primitive_type: PrimitiveType = switch (target_type) {
.Float, .Vector, .Vector2f32, .Vector3f32, .Vector4f32 => .Float,
.Int, .Vector2i32, .Vector3i32, .Vector4i32 => .SInt,
.Vector2u32, .Vector3u32, .Vector4u32 => .UInt,
else => return RuntimeError.InvalidValueType,
};
.Vector3f32 => |*vec| inline for (0..3) |i| {
switch ((try rt2.results[try rt2.it.next()].getVariant()).Constant.value) {
.Vector3f32 => |v| {
vec.* = v;
return;
},
.Float => |f| vec[i] = f.value.float32,
else => return RuntimeError.InvalidValueType,
}
},
const lane_bits = try Result.resolveLaneBitWidth(target_type, rt);
const target_lane_count = try value.resolveLaneCount();
.Vector2f32 => |*vec| inline for (0..2) |i| {
switch ((try rt2.results[try rt2.it.next()].getVariant()).Constant.value) {
.Vector2f32 => |v| {
vec.* = v;
return;
},
.Float => |f| vec[i] = f.value.float32,
else => return RuntimeError.InvalidValueType,
}
},
var dx: ?Value = if (primitive_type == .Float)
try Value.init(allocator, rt.results, target_type_word, false)
else
null;
defer if (dx) |*dx_value| dx_value.deinit(allocator);
var dy: ?Value = if (primitive_type == .Float)
try Value.init(allocator, rt.results, target_type_word, false)
else
null;
defer if (dy) |*dy_value| dy_value.deinit(allocator);
.Vector4i32 => |*vec| inline for (0..4) |i| {
switch ((try rt2.results[try rt2.it.next()].getVariant()).Constant.value) {
.Vector4i32 => |v| {
vec.* = v;
return;
},
.Int => |int| vec[i] = int.value.sint32,
else => return RuntimeError.InvalidValueType,
}
},
var has_derivative = false;
var target_lane_index: usize = 0;
for (operand_ids) |operand_id| {
const operand_value = try rt.results[operand_id].getValue();
const operand_lane_count = try operand_value.resolveLaneCount();
const derivative = rt.derivatives.get(operand_id);
.Vector3i32 => |*vec| inline for (0..3) |i| {
switch ((try rt2.results[try rt2.it.next()].getVariant()).Constant.value) {
.Vector3i32 => |v| {
vec.* = v;
return;
},
.Int => |int| vec[i] = int.value.sint32,
else => return RuntimeError.InvalidValueType,
}
},
for (0..operand_lane_count) |operand_lane_index| {
if (target_lane_index >= target_lane_count) return RuntimeError.InvalidSpirV;
.Vector2i32 => |*vec| inline for (0..2) |i| {
switch ((try rt2.results[try rt2.it.next()].getVariant()).Constant.value) {
.Vector2i32 => |v| {
vec.* = v;
return;
},
.Int => |int| vec[i] = int.value.sint32,
else => return RuntimeError.InvalidValueType,
}
},
switch (primitive_type) {
.Float => switch (lane_bits) {
inline 16, 32, 64 => |bits| {
{
const lane = try Value.readLane(.Float, bits, operand_value, operand_lane_index);
try Value.writeLane(.Float, bits, value, target_lane_index, lane);
.Vector4u32 => |*vec| inline for (0..4) |i| {
switch ((try rt2.results[try rt2.it.next()].getVariant()).Constant.value) {
.Vector4u32 => |v| {
vec.* = v;
return;
},
.Int => |int| vec[i] = int.value.uint32,
else => return RuntimeError.InvalidValueType,
}
const FloatT = Value.getPrimitiveFieldType(.Float, bits);
const dx_lane: FloatT = if (derivative) |d| blk: {
has_derivative = true;
break :blk try Value.readLane(.Float, bits, &d.dx, operand_lane_index);
} else 0;
const dy_lane: FloatT = if (derivative) |d| blk: {
has_derivative = true;
break :blk try Value.readLane(.Float, bits, &d.dy, operand_lane_index);
} else 0;
try Value.writeLane(.Float, bits, &dx.?, target_lane_index, dx_lane);
try Value.writeLane(.Float, bits, &dy.?, target_lane_index, dy_lane);
}
},
else => return RuntimeError.InvalidSpirV,
},
.Vector3u32 => |*vec| inline for (0..3) |i| {
switch ((try rt2.results[try rt2.it.next()].getVariant()).Constant.value) {
.Vector3u32 => |v| {
vec.* = v;
return;
},
.Int => |int| vec[i] = int.value.uint32,
else => return RuntimeError.InvalidValueType,
}
.SInt => switch (lane_bits) {
inline 8, 16, 32, 64 => |bits| {
{
const lane = try Value.readLane(.SInt, bits, operand_value, operand_lane_index);
try Value.writeLane(.SInt, bits, value, target_lane_index, lane);
}
},
else => return RuntimeError.InvalidSpirV,
},
.Vector2u32 => |*vec| inline for (0..2) |i| {
switch ((try rt2.results[try rt2.it.next()].getVariant()).Constant.value) {
.Vector2u32 => |v| {
vec.* = v;
return;
},
.Int => |int| vec[i] = int.value.uint32,
else => return RuntimeError.InvalidValueType,
}
.UInt => switch (lane_bits) {
inline 8, 16, 32, 64 => |bits| {
{
const lane = try Value.readLane(.UInt, bits, operand_value, operand_lane_index);
try Value.writeLane(.UInt, bits, value, target_lane_index, lane);
}
},
else => return RuntimeError.InvalidSpirV,
},
else => return RuntimeError.InvalidValueType,
}
target_lane_index += 1;
}
}.routines;
}
switch (value.*) {
.RuntimeArray => |arr| {
_ = arr;
return RuntimeError.ToDo;
},
if (target_lane_index != target_lane_count) return RuntimeError.InvalidSpirV;
else => try vectorRoutines(value, rt),
if (has_derivative) {
try rt.setDerivative(allocator, id, &dx.?, &dy.?);
} else {
rt.clearDerivative(allocator, id);
}
}
@@ -4544,17 +4482,23 @@ fn opFwidth(allocator: std.mem.Allocator, _: SpvWord, rt: *Runtime) RuntimeError
const id = try rt.it.next();
const operand = try rt.it.next();
const derivative = rt.derivatives.get(operand) orelse return RuntimeError.UnsupportedSpirV;
const dst = try rt.results[id].getValue();
const lane_bits = try Result.resolveLaneBitWidth(target_type, rt);
const lane_count = try Result.resolveLaneCount(target_type);
const derivative = rt.derivatives.get(operand);
switch (lane_bits) {
inline 16, 32, 64 => |bits| {
const FloatT = Value.getPrimitiveFieldType(.Float, bits);
for (0..lane_count) |lane_index| {
const dx = try Value.readLane(.Float, bits, &derivative.dx, lane_index);
const dy = try Value.readLane(.Float, bits, &derivative.dy, lane_index);
const dx: FloatT = if (derivative) |d|
try Value.readLane(.Float, bits, &d.dx, lane_index)
else
0;
const dy: FloatT = if (derivative) |d|
try Value.readLane(.Float, bits, &d.dy, lane_index)
else
0;
try Value.writeLane(.Float, bits, dst, lane_index, @as(FloatT, @abs(dx) + @abs(dy)));
}
},
+101
View File
@@ -64,6 +64,107 @@ test "Primitives casts" {
}
}
test "Vector float casts" {
const allocator = std.testing.allocator;
const cases = [_]struct {
len: usize,
source_type: []const u8,
target_type: []const u8,
values: []const f64,
}{
.{ .len = 2, .source_type = "f32", .target_type = "f64", .values = &.{ 1.25, -2.5 } },
.{ .len = 3, .source_type = "f32", .target_type = "f64", .values = &.{ 1.25, -2.5, 3.75 } },
.{ .len = 4, .source_type = "f32", .target_type = "f64", .values = &.{ 1.25, -2.5, 3.75, -4.5 } },
.{ .len = 2, .source_type = "f64", .target_type = "f32", .values = &.{ 1.25, -2.5 } },
.{ .len = 3, .source_type = "f64", .target_type = "f32", .values = &.{ 1.25, -2.5, 3.75 } },
.{ .len = 4, .source_type = "f64", .target_type = "f32", .values = &.{ 1.25, -2.5, 3.75, -4.5 } },
};
inline for (cases) |c| {
const constructor_values = try std.fmt.allocPrint(
allocator,
switch (c.len) {
2 => "{d}, {d}",
3 => "{d}, {d}, {d}",
4 => "{d}, {d}, {d}, {d}",
else => unreachable,
},
switch (c.len) {
2 => .{ c.values[0], c.values[1] },
3 => .{ c.values[0], c.values[1], c.values[2] },
4 => .{ c.values[0], c.values[1], c.values[2], c.values[3] },
else => unreachable,
},
);
defer allocator.free(constructor_values);
const output_values = switch (c.len) {
2 => "casted.x, casted.y, 0.0, 1.0",
3 => "casted.x, casted.y, casted.z, 1.0",
4 => "casted.x, casted.y, casted.z, casted.w",
else => unreachable,
};
const shader = try std.fmt.allocPrint(
allocator,
\\ [nzsl_version("1.1")]
\\ [feature(float64)]
\\ module;
\\
\\ struct FragOut
\\ {{
\\ [location(0)] color: vec4[{s}]
\\ }}
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {{
\\ let base = vec{d}[{s}]({s});
\\ let casted = vec{d}[{s}](base);
\\
\\ let output: FragOut;
\\ output.color = vec4[{s}]({s});
\\ return output;
\\ }}
,
.{
c.target_type,
c.len,
c.source_type,
constructor_values,
c.len,
c.target_type,
c.target_type,
output_values,
},
);
defer allocator.free(shader);
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
if (std.mem.eql(u8, c.target_type, "f32")) {
var expected = [_]f32{ 0.0, 0.0, 0.0, 1.0 };
for (c.values, 0..) |value, i| expected[i] = @floatCast(value);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&expected),
},
});
} else {
var expected = [_]f64{ 0.0, 0.0, 0.0, 1.0 };
for (c.values, 0..) |value, i| expected[i] = @floatCast(value);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&expected),
},
});
}
}
}
test "Primitives bitcasts" {
const allocator = std.testing.allocator;
const types = [_][2]type{
+32
View File
@@ -156,6 +156,38 @@ test "Maths vectors" {
}
}
test "Unsigned vector negate wraps" {
const allocator = std.testing.allocator;
const shader =
\\ [nzsl_version("1.1")]
\\ module;
\\
\\ struct FragOut
\\ {
\\ [location(0)] color: vec4[u32]
\\ }
\\
\\ [entry(frag)]
\\ fn main() -> FragOut
\\ {
\\ let v = vec4[u32](1, 2, 2147483648, 4294967295);
\\ let negated = -v;
\\ let output: FragOut;
\\ output.color = negated;
\\ return output;
\\ }
;
const code = try compileNzsl(allocator, shader);
defer allocator.free(code);
try case.expect(.{
.source = code,
.expected_outputs = &.{
std.mem.asBytes(&[_]u32{ 4294967295, 4294967294, 2147483648, 1 }),
},
});
}
// Tests all mathematical operation on vec2/3/4 with scalars with all NZSL supported primitive types
test "Maths vectors with scalars" {
const allocator = std.testing.allocator;