lib/simd/src/image/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const simd = @import("../root.zig");
3 const image_api = @import("root.zig");
4 const tag = simd.tag;
5
6 const vector_size = image_api.vector_size;
7 const storage_alignment = image_api.storage_alignment;
8 const bytesPerRow = image_api.bytesPerRow;
9 const bytesPerRowFor = image_api.bytesPerRowFor;
10 const Image = image_api.Image;
11 const Image3 = image_api.Image3;
12 const Rect = image_api.Rect;
13 const sameSize = image_api.sameSize;
14 const mirror = image_api.mirror;
15 const WrapRowMirror = image_api.WrapRowMirror;
16
17 fn verifyAlignedWrites(comptime T: type) !void {
18 const D = tag.ScalableTag(T);
19 const maximum_stride = comptime bytesPerRowFor(128, @sizeOf(T), vector_size) catch unreachable;
20 var storage: [maximum_stride * 3]u8 align(storage_alignment) = undefined;
21 for (1..4) |ysize| {
22 for (1..64) |xsize| {
23 const stride = try bytesPerRowFor(xsize, @sizeOf(T), vector_size);
24 const image = try Image(T).initBorrowed(xsize, ysize, stride, &storage);
25 for (0..ysize) |y| {
26 const row = image.mutableRow(y);
27 var x: usize = 0;
28 while (x < xsize) : (x += D.lane_count) {
29 var values: D.Vector = undefined;
30 inline for (0..D.lane_count) |lane| values[lane] = @intCast(x + lane);
31 row[x..][0..D.lane_count].* = @bitCast(values);
32 }
33 }
34 try std.testing.expect(image.constRow(ysize - 1)[xsize - 1] < xsize + D.lane_count);
35 }
36 }
37 }
38
39 fn verifyUnalignedWrites(comptime T: type) !void {
40 const D = tag.ScalableTag(T);
41 const maximum_stride = comptime bytesPerRowFor(128, @sizeOf(T), vector_size) catch unreachable;
42 var storage: [maximum_stride * 3]u8 align(storage_alignment) = undefined;
43 for (1..4) |ysize| {
44 for (1..128) |xsize| {
45 const stride = try bytesPerRowFor(xsize, @sizeOf(T), vector_size);
46 var image = try Image(T).initBorrowed(xsize, ysize, stride, &storage);
47 try image.initializePaddingForUnalignedAccesses();
48 for (0..ysize) |y| {
49 const row = image.mutableRow(y);
50 for (row[0..xsize], 0..) |*value, x| value.* = @intCast(x);
51 row[xsize - 1 ..][0..D.lane_count].* = @splat(0);
52 for (row[0 .. xsize - 1], 0..) |value, x| {
53 try std.testing.expectEqual(@as(T, @intCast(x)), value);
54 }
55 }
56 }
57 }
58 }
59
60 fn checkImageInitFailures(allocator: std.mem.Allocator) !void {
61 var image = try Image(u32).init(allocator, 127, 3);
62 image.deinit(allocator);
63 var image3 = try Image3(u32).init(allocator, 127, 3);
64 image3.deinit(allocator);
65 }
66
67 test "pinned Highway image row layout oracle" {
68 const widths = [_]usize{ 0, 1, 15, 16, 17, 31, 32, 63, 64, 127, 128, 255, 256, 511, 512, 1024 };
69 const expected = [4][widths.len]usize{
70 .{ 256, 256, 256, 256, 256, 256, 256, 256, 256, 384, 384, 512, 512, 768, 768, 1280 },
71 .{ 256, 256, 256, 256, 256, 256, 256, 384, 384, 512, 512, 768, 768, 1280, 1280, 2304 },
72 .{ 256, 256, 256, 256, 256, 384, 384, 512, 512, 768, 768, 1280, 1280, 2304, 2304, 4352 },
73 .{ 256, 256, 256, 384, 384, 384, 512, 640, 768, 1152, 1280, 2176, 2304, 4224, 4352, 8448 },
74 };
75 const sizes = [_]usize{ 1, 2, 4, 8 };
76 for (sizes, expected) |component_size, row| {
77 for (widths, row) |width, oracle| {
78 try std.testing.expectEqual(oracle, try bytesPerRowFor(width, component_size, 16));
79 }
80 }
81 }
82
83 test "Highway image permits full aligned vector writes" {
84 try verifyAlignedWrites(u8);
85 try verifyAlignedWrites(u16);
86 try verifyAlignedWrites(u32);
87 try verifyAlignedWrites(u64);
88 }
89
90 test "Highway image permits unaligned writes at the last sample" {
91 try verifyUnalignedWrites(u8);
92 try verifyUnalignedWrites(u16);
93 try verifyUnalignedWrites(u32);
94 try verifyUnalignedWrites(u64);
95 }
96
97 test "image borrowed storage validates alignment stride and capacity" {
98 var storage: [512]u8 align(storage_alignment) = undefined;
99 var image = try Image(u32).initBorrowed(17, 2, 256, &storage);
100 try std.testing.expectEqual(@as(usize, 17), image.xsize());
101 try std.testing.expectEqual(@as(usize, 2), image.ysize());
102 try std.testing.expectEqual(@as(usize, 64), image.pixelsPerRow());
103 image.mutableRow(1)[16] = 91;
104 try std.testing.expectEqual(@as(u32, 91), image.constRow(1)[16]);
105 image.deinit(std.testing.allocator);
106 try std.testing.expectError(
107 error.MisalignedStorage,
108 Image(u32).initBorrowed(1, 1, 256, storage[1..]),
109 );
110 try std.testing.expectError(
111 error.InvalidStride,
112 Image(u32).initBorrowed(17, 2, 64, &storage),
113 );
114 try std.testing.expectError(
115 error.BufferTooSmall,
116 Image(u32).initBorrowed(17, 2, 256, storage[0..511]),
117 );
118 var tight_storage: [vector_size]u8 align(storage_alignment) = undefined;
119 var tight = try Image(u8).initBorrowed(
120 vector_size,
121 1,
122 vector_size,
123 &tight_storage,
124 );
125 if (vector_size == 1) {
126 try tight.initializePaddingForUnalignedAccesses();
127 } else {
128 try std.testing.expectError(
129 error.InsufficientRowPadding,
130 tight.initializePaddingForUnalignedAccesses(),
131 );
132 }
133 }
134
135 test "zero-area images preserve each reported dimension without allocation" {
136 var wide = try Image(u32).init(std.testing.allocator, 17, 0);
137 defer wide.deinit(std.testing.allocator);
138 try std.testing.expectEqual(@as(usize, 17), wide.xsize());
139 try std.testing.expectEqual(@as(usize, 0), wide.ysize());
140 try std.testing.expectEqual(@as(usize, 0), wide.bytesPerRow());
141 try std.testing.expectEqual(@as(usize, 0), wide.bytes().len);
142
143 var tall = try Image(u32).init(std.testing.allocator, 0, 3);
144 defer tall.deinit(std.testing.allocator);
145 try std.testing.expectEqual(@as(usize, 0), tall.xsize());
146 try std.testing.expectEqual(@as(usize, 3), tall.ysize());
147 try std.testing.expectEqual(@as(usize, 0), tall.constRow(2).len);
148
149 var planes = try Image3(u32).init(std.testing.allocator, 19, 0);
150 defer planes.deinit(std.testing.allocator);
151 try std.testing.expectEqual(@as(usize, 19), planes.xsize());
152 try std.testing.expectEqual(@as(usize, 0), planes.ysize());
153 }
154
155 test "image padding initialization preserves valid samples" {
156 var image = try Image(u8).init(std.testing.allocator, 129, 2);
157 defer image.deinit(std.testing.allocator);
158 for (0..image.ysize()) |y| {
159 @memset(image.mutableRow(y)[0..image.xsize()], 0xa5);
160 }
161 try image.initializePaddingForUnalignedAccesses();
162 for (0..image.ysize()) |y| {
163 const row = image.constRow(y);
164 for (row[0..image.xsize()]) |value| try std.testing.expectEqual(@as(u8, 0xa5), value);
165 for (row[image.xsize() .. image.xsize() + vector_size - 1]) |value| {
166 try std.testing.expectEqual(@as(u8, 0), value);
167 }
168 }
169 }
170
171 test "image swap and shrink preserve storage layout" {
172 var first = try Image(u16).init(std.testing.allocator, 31, 3);
173 defer first.deinit(std.testing.allocator);
174 var second = try Image(u16).init(std.testing.allocator, 7, 2);
175 defer second.deinit(std.testing.allocator);
176 const first_ptr = first.bytes().ptr;
177 first.swap(&second);
178 try std.testing.expectEqual(@as(usize, 7), first.xsize());
179 try std.testing.expectEqual(first_ptr, second.bytes().ptr);
180 try second.shrinkTo(9, 1);
181 try std.testing.expectEqual(@as(usize, 9), second.xsize());
182 try std.testing.expectError(error.InvalidShrink, second.shrinkTo(32, 1));
183 }
184
185 test "image3 owns one packed allocation and consumes matching planes" {
186 var packed_image = try Image3(u32).init(std.testing.allocator, 17, 3);
187 defer packed_image.deinit(std.testing.allocator);
188 const plane_size = packed_image.bytesPerRow() * packed_image.ysize();
189 try std.testing.expectEqual(
190 plane_size,
191 @intFromPtr(packed_image.plane(1).constBytes().ptr) -
192 @intFromPtr(packed_image.plane(0).constBytes().ptr),
193 );
194 packed_image.mutablePlaneRow(2, 1)[16] = 0x1234;
195 try std.testing.expectEqual(@as(u32, 0x1234), packed_image.constPlaneRow(2, 1)[16]);
196
197 var p0 = try Image(u32).init(std.testing.allocator, 9, 2);
198 var p1 = try Image(u32).init(std.testing.allocator, 9, 2);
199 var p2 = try Image(u32).init(std.testing.allocator, 9, 2);
200 var moved = try Image3(u32).initPlanes(&p0, &p1, &p2);
201 defer moved.deinit(std.testing.allocator);
202 try std.testing.expectEqual(@as(usize, 0), p0.xsize());
203 try std.testing.expectEqual(@as(usize, 9), moved.xsize());
204 }
205
206 test "image3 borrowed planes and plane transfer reject broken invariants" {
207 var storage: [3][768]u8 align(storage_alignment) = undefined;
208 var borrowed = try Image3(u16).initBorrowed(
209 31,
210 2,
211 256,
212 .{ &storage[0], &storage[1], &storage[2] },
213 );
214 defer borrowed.deinit(std.testing.allocator);
215 borrowed.mutablePlaneRow(1, 1)[30] = 73;
216 try std.testing.expectEqual(@as(u16, 73), borrowed.constPlaneRow(1, 1)[30]);
217
218 var small = try Image(u16).init(std.testing.allocator, 7, 2);
219 defer small.deinit(std.testing.allocator);
220 var large = try Image(u16).init(std.testing.allocator, 8, 2);
221 defer large.deinit(std.testing.allocator);
222 var third = try Image(u16).init(std.testing.allocator, 7, 2);
223 defer third.deinit(std.testing.allocator);
224 try std.testing.expectError(
225 error.PlaneSizeMismatch,
226 Image3(u16).initPlanes(&small, &large, &third),
227 );
228 try std.testing.expectError(
229 error.AliasedPlanes,
230 Image3(u16).initPlanes(&small, &small, &third),
231 );
232 try std.testing.expectEqual(@as(usize, 7), small.xsize());
233
234 var stride0 = try Image(u16).initBorrowed(7, 2, 256, &storage[0]);
235 defer stride0.deinit(std.testing.allocator);
236 var stride1 = try Image(u16).initBorrowed(7, 2, 384, &storage[1]);
237 defer stride1.deinit(std.testing.allocator);
238 var stride2 = try Image(u16).initBorrowed(7, 2, 256, &storage[2]);
239 defer stride2.deinit(std.testing.allocator);
240 try std.testing.expectError(
241 error.PlaneStrideMismatch,
242 Image3(u16).initPlanes(&stride0, &stride1, &stride2),
243 );
244 }
245
246 test "image construction recovers from every allocation failure" {
247 try std.testing.checkAllAllocationFailures(
248 std.testing.allocator,
249 checkImageInitFailures,
250 .{},
251 );
252 }
253
254 test "rectangles clamp compose and address image rows" {
255 var image = try Image(u32).init(std.testing.allocator, 20, 10);
256 defer image.deinit(std.testing.allocator);
257 const rect = Rect.initClamped(17, 8, 8, 8, image.xsize(), image.ysize());
258 try std.testing.expectEqual(@as(usize, 3), rect.xsize());
259 try std.testing.expectEqual(@as(usize, 2), rect.ysize());
260 try std.testing.expect(rect.isInside(&image));
261 rect.mutableRow(u32, &image, 1)[0] = 77;
262 try std.testing.expectEqual(@as(u32, 77), image.constRow(9)[17]);
263 const sub = try rect.subrect(2, 1, 9, 9);
264 try std.testing.expectEqual(@as(usize, 1), sub.xsize());
265 try std.testing.expectEqual(@as(usize, 1), sub.ysize());
266 try std.testing.expect(sameSize(sub, Rect.init(0, 0, 1, 1)));
267 }
268
269 test "pinned Highway mirror and row wrappers agree" {
270 const expected = [_]usize{
271 3, 4, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 4, 3, 2, 1, 0, 0,
272 1, 2, 3, 4, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 4, 3, 2,
273 };
274 for (expected, 0..) |oracle, index| {
275 try std.testing.expectEqual(oracle, mirror(@as(i64, @intCast(index)) - 17, 5));
276 }
277 var image = try Image(f32).init(std.testing.allocator, 8, 4);
278 defer image.deinit(std.testing.allocator);
279 const wrap = WrapRowMirror.init(&image, image.ysize());
280 const stride: i64 = @intCast(image.pixelsPerRow());
281 const first = image.constRow(0).ptr;
282 const last = image.constRow(3).ptr;
283 try std.testing.expectEqual(first, wrap.call(first - @as(usize, @intCast(stride)), stride));
284 try std.testing.expectEqual(last, wrap.call(last + @as(usize, @intCast(stride)), stride));
285 try std.testing.expectEqual(
286 first + @as(usize, @intCast(stride)),
287 wrap.call(first - 2 * @as(usize, @intCast(stride)), stride),
288 );
289 }
290
291 test "image capacity boundaries reject overflow before allocation" {
292 try std.testing.expectError(error.RowSizeOverflow, bytesPerRow(std.math.maxInt(usize), 8));
293 if (comptime @bitSizeOf(usize) > 32) {
294 try std.testing.expectError(
295 error.DimensionTooLarge,
296 Image(u8).init(
297 std.testing.allocator,
298 @as(usize, std.math.maxInt(u32)) + 1,
299 1,
300 ),
301 );
302 try std.testing.expectError(
303 error.AllocationSizeOverflow,
304 Image(u64).init(
305 std.testing.allocator,
306 std.math.maxInt(u32),
307 std.math.maxInt(u32),
308 ),
309 );
310 }
311 }
312
313 test {
314 std.testing.refAllDecls(image_api);
315 }