adding lots of unit tests, improving image sampling, adding image sampling deref
This commit is contained in:
+510
@@ -0,0 +1,510 @@
|
||||
const std = @import("std");
|
||||
const spv = @import("spv");
|
||||
const root = @import("root.zig");
|
||||
|
||||
const compileNzsl = root.compileNzsl;
|
||||
|
||||
const ImageState = struct {
|
||||
expected_sampler: *anyopaque,
|
||||
sample_calls: usize = 0,
|
||||
dref_calls: usize = 0,
|
||||
last_x: f32 = 0,
|
||||
last_y: f32 = 0,
|
||||
last_z: f32 = 0,
|
||||
last_dref: f32 = 0,
|
||||
last_lod: ?f32 = null,
|
||||
last_offset: spv.Runtime.ImageOffset = .{},
|
||||
};
|
||||
|
||||
fn readImageFloat4(_: *anyopaque, _: spv.spv.SpvDim, _: i32, _: i32, _: i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(f32) {
|
||||
return spv.Runtime.RuntimeError.UnsupportedSpirV;
|
||||
}
|
||||
|
||||
fn readImageInt4(_: *anyopaque, _: spv.spv.SpvDim, _: i32, _: i32, _: i32) spv.Runtime.RuntimeError!spv.Runtime.Vec4(u32) {
|
||||
return spv.Runtime.RuntimeError.UnsupportedSpirV;
|
||||
}
|
||||
|
||||
fn writeImageFloat4(_: *anyopaque, _: spv.spv.SpvDim, _: i32, _: i32, _: i32, _: spv.Runtime.Vec4(f32)) spv.Runtime.RuntimeError!void {
|
||||
return spv.Runtime.RuntimeError.UnsupportedSpirV;
|
||||
}
|
||||
|
||||
fn writeImageInt4(_: *anyopaque, _: spv.spv.SpvDim, _: i32, _: i32, _: i32, _: spv.Runtime.Vec4(u32)) spv.Runtime.RuntimeError!void {
|
||||
return spv.Runtime.RuntimeError.UnsupportedSpirV;
|
||||
}
|
||||
|
||||
fn sampleImageFloat4(driver_image: *anyopaque, driver_sampler: *anyopaque, _: spv.spv.SpvDim, x: f32, y: f32, z: f32, lod: ?f32, offset: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!spv.Runtime.Vec4(f32) {
|
||||
const state: *ImageState = @ptrCast(@alignCast(driver_image));
|
||||
if (state.expected_sampler != driver_sampler) return spv.Runtime.RuntimeError.InvalidSpirV;
|
||||
state.sample_calls += 1;
|
||||
state.last_x = x;
|
||||
state.last_y = y;
|
||||
state.last_z = z;
|
||||
state.last_lod = lod;
|
||||
state.last_offset = offset;
|
||||
return .{ .x = x, .y = y, .z = 9.0, .w = 1.0 };
|
||||
}
|
||||
|
||||
fn sampleImageInt4(_: *anyopaque, _: *anyopaque, _: spv.spv.SpvDim, _: f32, _: f32, _: f32, _: ?f32, _: spv.Runtime.ImageOffset) spv.Runtime.RuntimeError!spv.Runtime.Vec4(u32) {
|
||||
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 {
|
||||
const state: *ImageState = @ptrCast(@alignCast(driver_image));
|
||||
if (state.expected_sampler != driver_sampler) return spv.Runtime.RuntimeError.InvalidSpirV;
|
||||
state.dref_calls += 1;
|
||||
state.last_x = x;
|
||||
state.last_y = y;
|
||||
state.last_z = z;
|
||||
state.last_dref = dref;
|
||||
state.last_lod = lod;
|
||||
state.last_offset = offset;
|
||||
return dref + x + y;
|
||||
}
|
||||
|
||||
fn queryImageSize(_: *anyopaque, _: spv.spv.SpvDim, _: bool) spv.Runtime.RuntimeError!spv.Runtime.Vec4(u32) {
|
||||
return spv.Runtime.RuntimeError.UnsupportedSpirV;
|
||||
}
|
||||
|
||||
const image_api: spv.Runtime.ImageAPI = .{
|
||||
.readImageFloat4 = readImageFloat4,
|
||||
.readImageInt4 = readImageInt4,
|
||||
.writeImageFloat4 = writeImageFloat4,
|
||||
.writeImageInt4 = writeImageInt4,
|
||||
.sampleImageFloat4 = sampleImageFloat4,
|
||||
.sampleImageInt4 = sampleImageInt4,
|
||||
.sampleImageDref = sampleImageDref,
|
||||
.queryImageSize = queryImageSize,
|
||||
};
|
||||
|
||||
fn initModule(allocator: std.mem.Allocator, shader: []const u8) !struct { code: []const u32, module: spv.Module } {
|
||||
const code = try compileNzsl(allocator, shader);
|
||||
errdefer allocator.free(code);
|
||||
|
||||
const module = try spv.Module.init(allocator, code, .{
|
||||
.use_simd_vectors_specializations = false,
|
||||
});
|
||||
return .{ .code = code, .module = module };
|
||||
}
|
||||
|
||||
test "Runtime API lifecycle" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct FragIn
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main(input: FragIn) -> FragOut
|
||||
\\ {
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = input.color;
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
|
||||
var compiled = try initModule(allocator, shader);
|
||||
defer allocator.free(compiled.code);
|
||||
defer compiled.module.deinit(allocator);
|
||||
|
||||
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
|
||||
defer rt.deinit(allocator);
|
||||
|
||||
const input_result = try rt.getResultByLocationComponent(0, 0, .input);
|
||||
const output_result = try rt.getResultByName("color");
|
||||
const output_location_result = try rt.getResultByLocationComponent(0, 0, .output);
|
||||
try std.testing.expectEqual(input_result, try rt.getResultByLocation(0, .input));
|
||||
try std.testing.expectEqual(output_location_result, try rt.getResultByLocation(0, .output));
|
||||
try std.testing.expectEqual(@as(usize, 16), try rt.getInputLocationMemorySize(0));
|
||||
try std.testing.expectEqual(@as(usize, 16), try rt.getResultMemorySize(output_result));
|
||||
try std.testing.expectEqual(@as(usize, 16), try rt.getResultMemorySize(output_location_result));
|
||||
try std.testing.expectEqual(.Float, try rt.getResultPrimitiveType(output_result));
|
||||
try std.testing.expect(rt.hasResultDecoration(output_result, .Location));
|
||||
|
||||
const input = [_]f32{ 10.0, 20.0, 30.0, 40.0 };
|
||||
try rt.writeInputLocation(std.mem.asBytes(&input), 0);
|
||||
|
||||
const entry = try rt.getEntryPointByName("main");
|
||||
try std.testing.expectEqual(.completed, try rt.beginEntryPoint(allocator, entry));
|
||||
|
||||
var output: [4]f32 = undefined;
|
||||
try rt.readOutput(std.mem.asBytes(&output), output_location_result);
|
||||
try std.testing.expectEqualSlices(f32, &input, &output);
|
||||
|
||||
rt.resetInvocation(allocator);
|
||||
}
|
||||
|
||||
test "Module binding writes" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ [layout(std430)]
|
||||
\\ struct SSBO
|
||||
\\ {
|
||||
\\ value: u32
|
||||
\\ }
|
||||
\\
|
||||
\\ external
|
||||
\\ {
|
||||
\\ [set(2), binding(3)] ssbo: storage[SSBO],
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(compute)]
|
||||
\\ [workgroup(1, 1, 1)]
|
||||
\\ fn main()
|
||||
\\ {
|
||||
\\ ssbo.value = ssbo.value + 7;
|
||||
\\ }
|
||||
;
|
||||
|
||||
var compiled = try initModule(allocator, shader);
|
||||
defer allocator.free(compiled.code);
|
||||
defer compiled.module.deinit(allocator);
|
||||
|
||||
const binding_result = compiled.module.getBindingResult(2, 3) orelse return error.TestExpectedEqual;
|
||||
try std.testing.expectEqual(@as(?spv.SpvWord, null), compiled.module.getBindingResult(0, 0));
|
||||
|
||||
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
|
||||
defer rt.deinit(allocator);
|
||||
|
||||
var storage: u32 = 35;
|
||||
try rt.writeDescriptorSet(std.mem.asBytes(&storage), 2, 3, 0);
|
||||
try std.testing.expectEqual(error.NotFound, rt.writeDescriptorSet(std.mem.asBytes(&storage), 2, 4, 0));
|
||||
|
||||
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
|
||||
try rt.flushDescriptorSets(allocator);
|
||||
|
||||
_ = binding_result;
|
||||
try std.testing.expectEqual(@as(u32, 42), storage);
|
||||
}
|
||||
|
||||
test "Push constants" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct Data
|
||||
\\ {
|
||||
\\ color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ external
|
||||
\\ {
|
||||
\\ data: push_constant[Data]
|
||||
\\ }
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main() -> FragOut
|
||||
\\ {
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = data.color;
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
|
||||
var compiled = try initModule(allocator, shader);
|
||||
defer allocator.free(compiled.code);
|
||||
defer compiled.module.deinit(allocator);
|
||||
|
||||
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
|
||||
defer rt.deinit(allocator);
|
||||
|
||||
const push_constants = [_]f32{ 0.125, 0.25, 0.5, 1.0 };
|
||||
try rt.populatePushConstants(std.mem.asBytes(&push_constants));
|
||||
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
|
||||
|
||||
var output: [4]f32 = undefined;
|
||||
try rt.readOutput(std.mem.asBytes(&output), try rt.getResultByLocation(0, .output));
|
||||
try std.testing.expectEqualSlices(f32, &push_constants, &output);
|
||||
}
|
||||
|
||||
test "Built-in inputs and outputs" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct VertIn
|
||||
\\ {
|
||||
\\ [builtin(vertex_index)] vertex_index: i32
|
||||
\\ }
|
||||
\\
|
||||
\\ struct VertOut
|
||||
\\ {
|
||||
\\ [builtin(position)] position: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(vert)]
|
||||
\\ fn main(input: VertIn) -> VertOut
|
||||
\\ {
|
||||
\\ let value = f32(input.vertex_index);
|
||||
\\ let output: VertOut;
|
||||
\\ output.position = vec4[f32](value, value + 1.0, value + 2.0, 1.0);
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
|
||||
var compiled = try initModule(allocator, shader);
|
||||
defer allocator.free(compiled.code);
|
||||
defer compiled.module.deinit(allocator);
|
||||
|
||||
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
|
||||
defer rt.deinit(allocator);
|
||||
|
||||
const vertex_index: i32 = 7;
|
||||
try rt.writeBuiltIn(std.mem.asBytes(&vertex_index), .VertexIndex);
|
||||
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
|
||||
|
||||
var position: [4]f32 = undefined;
|
||||
try rt.readBuiltIn(std.mem.asBytes(&position), .Position);
|
||||
try std.testing.expectEqualSlices(f32, &.{ 7.0, 8.0, 9.0, 1.0 }, &position);
|
||||
}
|
||||
|
||||
test "Integer output metadata" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] value: u32
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main() -> FragOut
|
||||
\\ {
|
||||
\\ let output: FragOut;
|
||||
\\ output.value = 0xA5A5_A5A5;
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
|
||||
var compiled = try initModule(allocator, shader);
|
||||
defer allocator.free(compiled.code);
|
||||
defer compiled.module.deinit(allocator);
|
||||
|
||||
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
|
||||
defer rt.deinit(allocator);
|
||||
|
||||
const output_result = try rt.getResultByLocation(0, .output);
|
||||
try std.testing.expectEqual(.UInt, try rt.getResultPrimitiveType(output_result));
|
||||
try std.testing.expectEqual(@as(usize, 4), try rt.getResultMemorySize(output_result));
|
||||
|
||||
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
|
||||
|
||||
var output: u32 = 0;
|
||||
try rt.readOutput(std.mem.asBytes(&output), output_result);
|
||||
try std.testing.expectEqual(@as(u32, 0xA5A5_A5A5), output);
|
||||
}
|
||||
|
||||
test "Runtime API error paths" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main() -> FragOut
|
||||
\\ {
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = vec4[f32](1.0, 2.0, 3.0, 4.0);
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
|
||||
var compiled = try initModule(allocator, shader);
|
||||
defer allocator.free(compiled.code);
|
||||
defer compiled.module.deinit(allocator);
|
||||
|
||||
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
|
||||
defer rt.deinit(allocator);
|
||||
|
||||
try std.testing.expectEqual(error.NotFound, rt.getEntryPointByName("missing"));
|
||||
try std.testing.expectEqual(error.NotFound, rt.getResultByName("missing"));
|
||||
try std.testing.expectEqual(error.NotFound, rt.getResultByLocation(31, .input));
|
||||
try std.testing.expectEqual(error.NotFound, rt.getInputLocationMemorySize(31));
|
||||
|
||||
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
|
||||
|
||||
var too_small: [3]u8 = undefined;
|
||||
try std.testing.expectEqual(error.OutOfBounds, rt.readOutput(&too_small, try rt.getResultByLocation(0, .output)));
|
||||
}
|
||||
|
||||
test "Derivative memory buffers" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct FragIn
|
||||
\\ {
|
||||
\\ [location(0)] normal: vec3[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main(input: FragIn) -> FragOut
|
||||
\\ {
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = vec4[f32](input.normal.x, input.normal.y, input.normal.z, 1.0);
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
|
||||
var compiled = try initModule(allocator, shader);
|
||||
defer allocator.free(compiled.code);
|
||||
defer compiled.module.deinit(allocator);
|
||||
|
||||
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
|
||||
defer rt.deinit(allocator);
|
||||
|
||||
const input_result = try rt.getResultByLocation(0, .input);
|
||||
const dx = [_]f32{ -1.0, 2.0, -3.0 };
|
||||
const dy = [_]f32{ 4.0, -5.0, 6.0 };
|
||||
const short_dx = [_]f32{ -1.0, 2.0 };
|
||||
try std.testing.expectEqual(error.OutOfBounds, rt.setDerivativeFromMemory(allocator, input_result, std.mem.asBytes(&short_dx), std.mem.asBytes(&dy)));
|
||||
|
||||
const output_result = try rt.getResultByName("color");
|
||||
try rt.setDerivativeFromMemory(allocator, input_result, std.mem.asBytes(&dx), std.mem.asBytes(&dy));
|
||||
try rt.copyDerivative(allocator, output_result, input_result);
|
||||
rt.clearDerivative(allocator, input_result);
|
||||
rt.clearDerivative(allocator, output_result);
|
||||
|
||||
const input = [_]f32{ 1.0, 2.0, 3.0 };
|
||||
try rt.writeInput(std.mem.asBytes(&input), input_result);
|
||||
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
|
||||
|
||||
var output: [4]f32 = undefined;
|
||||
try rt.readOutput(std.mem.asBytes(&output), try rt.getResultByLocation(0, .output));
|
||||
try std.testing.expectEqualSlices(f32, &.{ 1.0, 2.0, 3.0, 1.0 }, &output);
|
||||
|
||||
try rt.setDerivativeFromMemory(allocator, input_result, std.mem.asBytes(&dx), std.mem.asBytes(&dy));
|
||||
try rt.copyDerivative(allocator, output_result, input_result);
|
||||
rt.clearDerivative(allocator, output_result);
|
||||
}
|
||||
|
||||
test "Image sampling callback" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ external
|
||||
\\ {
|
||||
\\ [set(0), binding(0)] tex: sampler2D[f32],
|
||||
\\ }
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main() -> FragOut
|
||||
\\ {
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = tex.Sample(vec2[f32](0.25, 0.75));
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
|
||||
var compiled = try initModule(allocator, shader);
|
||||
defer allocator.free(compiled.code);
|
||||
defer compiled.module.deinit(allocator);
|
||||
|
||||
var sampler: u8 = 0;
|
||||
var image_state: ImageState = .{ .expected_sampler = &sampler };
|
||||
var descriptor = [_]usize{
|
||||
@intFromPtr(&image_state),
|
||||
@intFromPtr(&sampler),
|
||||
};
|
||||
|
||||
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
|
||||
defer rt.deinit(allocator);
|
||||
|
||||
try rt.writeDescriptorSet(std.mem.asBytes(&descriptor), 0, 0, 0);
|
||||
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
|
||||
|
||||
var output: [4]f32 = undefined;
|
||||
try rt.readOutput(std.mem.asBytes(&output), try rt.getResultByName("color"));
|
||||
try std.testing.expectEqualSlices(f32, &.{ 0.25, 0.75, 9.0, 1.0 }, &output);
|
||||
try std.testing.expectEqual(@as(usize, 1), image_state.sample_calls);
|
||||
try std.testing.expectEqual(@as(?f32, null), image_state.last_lod);
|
||||
try std.testing.expectEqual(spv.Runtime.ImageOffset{}, image_state.last_offset);
|
||||
}
|
||||
|
||||
test "Depth sampling dref callback" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ external
|
||||
\\ {
|
||||
\\ [set(0), binding(0)] tex: depth_sampler2D[f32],
|
||||
\\ }
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main() -> FragOut
|
||||
\\ {
|
||||
\\ let value = tex.SampleDepthComp(vec2[f32](0.25, 0.75), 0.5);
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = vec4[f32](value, 0.0, 0.0, 1.0);
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
|
||||
var compiled = try initModule(allocator, shader);
|
||||
defer allocator.free(compiled.code);
|
||||
defer compiled.module.deinit(allocator);
|
||||
|
||||
var sampler: u8 = 0;
|
||||
var image_state: ImageState = .{ .expected_sampler = &sampler };
|
||||
var descriptor = [_]usize{
|
||||
@intFromPtr(&image_state),
|
||||
@intFromPtr(&sampler),
|
||||
};
|
||||
|
||||
var rt = try spv.Runtime.init(allocator, &compiled.module, image_api);
|
||||
defer rt.deinit(allocator);
|
||||
|
||||
try rt.writeDescriptorSet(std.mem.asBytes(&descriptor), 0, 0, 0);
|
||||
try rt.callEntryPoint(allocator, try rt.getEntryPointByName("main"));
|
||||
|
||||
var output: [4]f32 = undefined;
|
||||
try rt.readOutput(std.mem.asBytes(&output), try rt.getResultByName("color"));
|
||||
try std.testing.expectEqualSlices(f32, &.{ 1.5, 0.0, 0.0, 1.0 }, &output);
|
||||
try std.testing.expectEqual(@as(usize, 1), image_state.dref_calls);
|
||||
try std.testing.expectEqual(@as(f32, 0.5), image_state.last_dref);
|
||||
}
|
||||
@@ -33,3 +33,42 @@ test "Simple array" {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
test "Array fold" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main() -> FragOut
|
||||
\\ {
|
||||
\\ let values = array[f32](1.0, 2.0, 3.0, 4.0);
|
||||
\\ let sum = 0.0;
|
||||
\\ let weighted = 0.0;
|
||||
\\ for i in u32(0) -> values.Size()
|
||||
\\ {
|
||||
\\ sum += values[i];
|
||||
\\ weighted += values[i] * f32(i + 1);
|
||||
\\ }
|
||||
\\
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = vec4[f32](sum, weighted, values[2], f32(values.Size()));
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
const code = try compileNzsl(allocator, shader);
|
||||
defer allocator.free(code);
|
||||
|
||||
try case.expect(.{
|
||||
.source = code,
|
||||
.expected_outputs = &.{
|
||||
std.mem.asBytes(&[_]f32{ 10.0, 30.0, 3.0, 4.0 }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -157,3 +157,42 @@ test "Bitwise vectors" {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "Bit mix" {
|
||||
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 a: u32 = 0xF0F0_F0F0;
|
||||
\\ let b: u32 = 0x0F0F_00FF;
|
||||
\\ let c = ((a & b) << 4) | ((a ^ b) >> 8);
|
||||
\\ let d = (c & 0xFFFF) ^ 0x55AA;
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = vec4[u32](a & b, a | b, c, d);
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
const code = try compileNzsl(allocator, shader);
|
||||
defer allocator.free(code);
|
||||
|
||||
try case.expect(.{
|
||||
.source = code,
|
||||
.expected_outputs = &.{
|
||||
std.mem.asBytes(&[_]u32{
|
||||
0x0000_00F0,
|
||||
0xFFFF_F0FF,
|
||||
((0xF0F0_F0F0 & 0x0F0F_00FF) << 4) | ((0xF0F0_F0F0 ^ 0x0F0F_00FF) >> 8),
|
||||
((((0xF0F0_F0F0 & 0x0F0F_00FF) << 4) | ((0xF0F0_F0F0 ^ 0x0F0F_00FF) >> 8)) & 0xFFFF) ^ 0x55AA,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -103,3 +103,53 @@ test "Simple branching" {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "Nested if" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[i32]
|
||||
\\ }
|
||||
\\
|
||||
\\ fn classify(value: i32) -> i32
|
||||
\\ {
|
||||
\\ if (value < 0)
|
||||
\\ {
|
||||
\\ if ((value % 2) == 0)
|
||||
\\ return -2;
|
||||
\\ else
|
||||
\\ return -1;
|
||||
\\ }
|
||||
\\ else if (value == 0)
|
||||
\\ return 0;
|
||||
\\ else
|
||||
\\ {
|
||||
\\ if ((value % 2) == 0)
|
||||
\\ return 2;
|
||||
\\ else
|
||||
\\ return 1;
|
||||
\\ }
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main() -> FragOut
|
||||
\\ {
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = vec4[i32](classify(-4), classify(-3), classify(0), classify(5));
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
const code = try compileNzsl(allocator, shader);
|
||||
defer allocator.free(code);
|
||||
|
||||
try case.expect(.{
|
||||
.source = code,
|
||||
.expected_outputs = &.{
|
||||
std.mem.asBytes(&[_]i32{ -2, -1, 0, 1 }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -116,3 +116,35 @@ test "Primitives bitcasts" {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
test "Cast chain" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[i32]
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main() -> FragOut
|
||||
\\ {
|
||||
\\ let v = vec4[f32](1.25, 2.75, -3.25, 4.5);
|
||||
\\ let a = vec4[i32](v);
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = vec4[i32](a.x, a.y, a.z, a.w);
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
const code = try compileNzsl(allocator, shader);
|
||||
defer allocator.free(code);
|
||||
|
||||
try case.expect(.{
|
||||
.source = code,
|
||||
.expected_outputs = &.{
|
||||
std.mem.asBytes(&[_]i32{ 1, 2, -3, 4 }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -108,3 +108,90 @@ test "Nested function calls" {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
test "Function params" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ fn affine(value: f32, scale: f32, bias: f32) -> f32
|
||||
\\ {
|
||||
\\ return value * scale + bias;
|
||||
\\ }
|
||||
\\
|
||||
\\ fn combine(a: vec2[f32], b: vec2[f32]) -> vec4[f32]
|
||||
\\ {
|
||||
\\ let left = affine(a.x, b.x, b.y);
|
||||
\\ let right = affine(a.y, b.y, b.x);
|
||||
\\ return vec4[f32](left, right, left + right, left - right);
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main() -> FragOut
|
||||
\\ {
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = combine(vec2[f32](2.0, 3.0), vec2[f32](4.0, 5.0));
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
const code = try compileNzsl(allocator, shader);
|
||||
defer allocator.free(code);
|
||||
|
||||
try case.expect(.{
|
||||
.source = code,
|
||||
.expected_outputs = &.{
|
||||
std.mem.asBytes(&[_]f32{ 13.0, 19.0, 32.0, -6.0 }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
test "Struct logic" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct Pair
|
||||
\\ {
|
||||
\\ a: f32,
|
||||
\\ b: f32
|
||||
\\ }
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ fn eval(pair: Pair) -> vec2[f32]
|
||||
\\ {
|
||||
\\ return vec2[f32](pair.a + pair.b, pair.a * pair.b);
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main() -> FragOut
|
||||
\\ {
|
||||
\\ let pair: Pair;
|
||||
\\ pair.a = 3.0;
|
||||
\\ pair.b = 4.0;
|
||||
\\ let v = eval(pair);
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = vec4[f32](v.x, v.y, v.y - v.x, v.x + v.y);
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
const code = try compileNzsl(allocator, shader);
|
||||
defer allocator.free(code);
|
||||
|
||||
try case.expect(.{
|
||||
.source = code,
|
||||
.expected_outputs = &.{
|
||||
std.mem.asBytes(&[_]f32{ 7.0, 12.0, 5.0, 19.0 }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,3 +54,47 @@ test "Simple while loop" {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
test "For filter" {
|
||||
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 even_sum: u32 = 0;
|
||||
\\ let odd_sum: u32 = 0;
|
||||
\\ let product: u32 = 1;
|
||||
\\ for i in u32(1) -> u32(8)
|
||||
\\ {
|
||||
\\ if ((i % u32(2)) == u32(0))
|
||||
\\ {
|
||||
\\ even_sum += i;
|
||||
\\ product *= i;
|
||||
\\ }
|
||||
\\ else
|
||||
\\ odd_sum += i;
|
||||
\\ }
|
||||
\\
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = vec4[u32](even_sum, odd_sum, product, even_sum + odd_sum);
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
const code = try compileNzsl(allocator, shader);
|
||||
defer allocator.free(code);
|
||||
|
||||
try case.expect(.{
|
||||
.source = code,
|
||||
.expected_outputs = &.{
|
||||
std.mem.asBytes(&[_]u32{ 12, 16, 48, 28 }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+50
-6
@@ -245,12 +245,22 @@ test "Maths matrices" {
|
||||
e.* = switch (op.key) {
|
||||
.Add => b + r,
|
||||
.Sub => b - r,
|
||||
.Mul => b * r,
|
||||
.Mul => 0,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (op.key == .Mul) {
|
||||
for (0..L) |column_index| {
|
||||
for (0..L) |row_index| {
|
||||
for (0..L) |inner_index| {
|
||||
expected.val[column_index][row_index] += base.val[inner_index][row_index] * ratio.val[column_index][inner_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const shader = try std.fmt.allocPrint(
|
||||
allocator,
|
||||
\\ [nzsl_version("1.1")]
|
||||
@@ -307,11 +317,12 @@ test "Maths matrices with vectors" {
|
||||
const ratio: case.Vec(L, T) = .{ .val = case.random(@Vector(L, T)) };
|
||||
var expected: @Vector(L, T) = undefined;
|
||||
|
||||
expected[0] = (base.val[0][0] * ratio.val[0]) + (base.val[0][1] * ratio.val[1]) + (base.val[0][2] * ratio.val[2]) + if (L == 4) (base.val[0][3] * ratio.val[3]) else 0.0;
|
||||
expected[1] = (base.val[1][0] * ratio.val[0]) + (base.val[1][1] * ratio.val[1]) + (base.val[1][2] * ratio.val[2]) + if (L == 4) (base.val[1][3] * ratio.val[3]) else 0.0;
|
||||
expected[2] = (base.val[2][0] * ratio.val[0]) + (base.val[2][1] * ratio.val[1]) + (base.val[2][2] * ratio.val[2]) + if (L == 4) (base.val[2][3] * ratio.val[3]) else 0.0;
|
||||
if (L == 4)
|
||||
expected[3] = (base.val[3][0] * ratio.val[0]) + (base.val[3][1] * ratio.val[1]) + (base.val[3][2] * ratio.val[2]) + (base.val[3][3] * ratio.val[3]);
|
||||
expected = @splat(0);
|
||||
inline for (0..L) |row_index| {
|
||||
inline for (0..L) |column_index| {
|
||||
expected[row_index] += base.val[column_index][row_index] * ratio.val[column_index];
|
||||
}
|
||||
}
|
||||
|
||||
const shader = try std.fmt.allocPrint(
|
||||
allocator,
|
||||
@@ -355,3 +366,36 @@ test "Maths matrices with vectors" {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "Swizzle" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ struct FragOut
|
||||
\\ {
|
||||
\\ [location(0)] color: vec4[f32]
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(frag)]
|
||||
\\ fn main() -> FragOut
|
||||
\\ {
|
||||
\\ let v = vec4[f32](1.0, 2.0, 3.0, 4.0);
|
||||
\\ let a = v.yx;
|
||||
\\ let b = v.wz;
|
||||
\\ let output: FragOut;
|
||||
\\ output.color = vec4[f32](a.x, a.y, b.x, b.y);
|
||||
\\ return output;
|
||||
\\ }
|
||||
;
|
||||
const code = try compileNzsl(allocator, shader);
|
||||
defer allocator.free(code);
|
||||
|
||||
try case.expect(.{
|
||||
.source = code,
|
||||
.expected_outputs = &.{
|
||||
std.mem.asBytes(&[_]f32{ 2.0, 1.0, 4.0, 3.0 }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ pub const case = struct {
|
||||
};
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@import("api.zig"));
|
||||
std.testing.refAllDecls(@import("arrays.zig"));
|
||||
std.testing.refAllDecls(@import("basics.zig"));
|
||||
std.testing.refAllDecls(@import("bitwise.zig"));
|
||||
|
||||
+49
-1
@@ -3,7 +3,7 @@ const root = @import("root.zig");
|
||||
const compileNzsl = root.compileNzsl;
|
||||
const case = root.case;
|
||||
|
||||
test "Simple SSBO" {
|
||||
test "SSBO read" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
@@ -58,3 +58,51 @@ test "Simple SSBO" {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
test "SSBO write" {
|
||||
const allocator = std.testing.allocator;
|
||||
const shader =
|
||||
\\ [nzsl_version("1.1")]
|
||||
\\ module;
|
||||
\\
|
||||
\\ [layout(std430)]
|
||||
\\ struct SSBO
|
||||
\\ {
|
||||
\\ data: dyn_array[u32]
|
||||
\\ }
|
||||
\\
|
||||
\\ external
|
||||
\\ {
|
||||
\\ [set(0), binding(0)] ssbo: storage[SSBO],
|
||||
\\ }
|
||||
\\
|
||||
\\ [entry(compute)]
|
||||
\\ [workgroup(1, 1, 1)]
|
||||
\\ fn main()
|
||||
\\ {
|
||||
\\ for i in u32(2) -> u32(8)
|
||||
\\ {
|
||||
\\ ssbo.data[i] = ssbo.data[i - u32(1)] + ssbo.data[i - u32(2)];
|
||||
\\ }
|
||||
\\ }
|
||||
;
|
||||
const code = try compileNzsl(allocator, shader);
|
||||
defer allocator.free(code);
|
||||
|
||||
var ssbo = [_]u32{ 1, 1, 0, 0, 0, 0, 0, 0 };
|
||||
const expected = [_]u32{ 1, 1, 2, 3, 5, 8, 13, 21 };
|
||||
|
||||
try case.expect(.{
|
||||
.source = code,
|
||||
.descriptor_sets = &.{
|
||||
&.{
|
||||
std.mem.asBytes(&ssbo),
|
||||
},
|
||||
},
|
||||
.expected_descriptor_sets = &.{
|
||||
&.{
|
||||
std.mem.asBytes(&expected),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user