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 }