diff --git a/packages/media/build.zig b/packages/media/build.zig index a069b58..00454cd 100644 --- a/packages/media/build.zig +++ b/packages/media/build.zig @@ -1,15 +1,23 @@ const std = @import("std"); pub fn build(b: *std.Build) void { - const vm_dep = b.dependency("vecmath", .{}); - const vm_module = vm_dep.module("vecmath"); + const target = b.standardTargetOptions(.{}); - const root_module = b.addModule("media", .{ + const vm_dep = b.dependency("vecmath", .{ + .target = target, + }); + const vm_mod = vm_dep.module("vecmath"); + + const mod = b.addModule("media", .{ .root_source_file = b.path("src/root.zig"), + .target = target, .link_libc = true, + .imports = &.{ + .{ .name = "vecmath", .module = vm_mod }, + }, }); - root_module.addCSourceFile(.{ + mod.addCSourceFile(.{ .file = b.path("src/stbi/stb_image.c"), .flags = &.{ "-std=c17", @@ -19,5 +27,12 @@ pub fn build(b: *std.Build) void { .language = .c, }); - root_module.addImport("vecmath", vm_module); + const mod_tests = b.addTest(.{ + .root_module = mod, + }); + + const run_mod_tests = b.addRunArtifact(mod_tests); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_mod_tests.step); } diff --git a/packages/media/build.zig.zon b/packages/media/build.zig.zon index ae81272..f2b991b 100644 --- a/packages/media/build.zig.zon +++ b/packages/media/build.zig.zon @@ -1,7 +1,7 @@ .{ .name = .media, .version = "0.0.0", - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0", .paths = .{ "src", "build.zig", diff --git a/packages/media/src/Format.zig b/packages/media/src/Format.zig index a604e31..e789735 100644 --- a/packages/media/src/Format.zig +++ b/packages/media/src/Format.zig @@ -19,3 +19,7 @@ media_type: []const u8, /// to contain the entire file, only its beginning. The caller asserts that /// `buffer` is at least `magic_length` bytes long. isFormat: *const fn (buffer: []const u8) bool, + +test "refAllDecls" { + std.testing.refAllDecls(@This()); +} diff --git a/packages/media/src/audio.zig b/packages/media/src/audio.zig index e7ce1c0..e29a21d 100644 --- a/packages/media/src/audio.zig +++ b/packages/media/src/audio.zig @@ -9,14 +9,26 @@ pub const Static = struct { const sample_rate: f64 = @floatFromInt(self.sample_rate); return @floatCast(samples / sample_rate); } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; pub const Sample = extern struct { left: i16, right: i16, + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; pub const Stream = struct { source: *std.Io.Reader, sample_rate: u32, + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; diff --git a/packages/media/src/image.zig b/packages/media/src/image.zig index 36f8114..c2a0050 100644 --- a/packages/media/src/image.zig +++ b/packages/media/src/image.zig @@ -19,11 +19,15 @@ pub fn Static(comptime W: u32, comptime H: u32) type { } pub fn fill(self: *@This(), color: vm.Color) void { - @memset(self.data, color); + @memset(&self.data, color); } }; } +test "Static - refAllDecls" { + std.testing.refAllDecls(Static(16, 16)); +} + pub const Dynamic = struct { width: u32, height: u32, @@ -65,6 +69,10 @@ pub const Dynamic = struct { pub fn fill(self: *@This(), color: vm.Color) void { @memset(self.data[0 .. self.width * self.height], color); } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; pub const Hdr = struct { @@ -108,4 +116,8 @@ pub const Hdr = struct { pub fn fill(self: *@This(), color: vm.ColorHdr) void { @memset(self.data[0 .. self.width * self.height], color); } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; diff --git a/packages/media/src/jpeg.zig b/packages/media/src/jpeg.zig index 18db41e..1bcd0fc 100644 --- a/packages/media/src/jpeg.zig +++ b/packages/media/src/jpeg.zig @@ -20,6 +20,10 @@ const Info = union(enum) { pub fn makeFull(full: Header) Info { return .{ .full = full }; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; const Header = struct { @@ -107,6 +111,10 @@ const Marker = enum(u8) { self == .EOI or self == .TEM; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; /// The caller asserts that the buffer is at least `format.magic_length` bytes @@ -137,3 +145,7 @@ pub fn info(buffer: []const u8) ?Info { @panic("TODO"); } + +test "refAllDecls" { + std.testing.refAllDecls(@This()); +} diff --git a/packages/media/src/jxl.zig b/packages/media/src/jxl.zig index aef89b6..1544e4c 100644 --- a/packages/media/src/jxl.zig +++ b/packages/media/src/jxl.zig @@ -35,3 +35,7 @@ pub fn info(buffer: []const u8) ?Header { @panic("TODO"); } + +test "refAllDecls" { + std.testing.refAllDecls(@This()); +} diff --git a/packages/media/src/png.zig b/packages/media/src/png.zig index faebbaf..f1eab2b 100644 --- a/packages/media/src/png.zig +++ b/packages/media/src/png.zig @@ -27,7 +27,7 @@ pub const Header = struct { filter_method: FilterMethod, interlace_method: InterlaceMethod, - pub fn decode(chunk: Chunk) Header { + pub fn decode(chunk: Chunk) !Header { std.debug.assert(chunk.chunk_type == .IHDR); if (chunk.data.len != 13) return error.InvalidPng; @@ -79,6 +79,10 @@ pub const Header = struct { .interlace_method = @enumFromInt(interlace_method), }; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; pub const BitDepth = enum(u8) { @@ -89,7 +93,11 @@ pub const BitDepth = enum(u8) { @"16" = 16, pub fn range(self: BitDepth) usize { - return @as(usize, 2) << @intFromEnum(@intFromEnum(self)); + return @as(usize, 2) << @intCast(@intFromEnum(self)); + } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); } }; @@ -111,6 +119,10 @@ pub const ColorType = enum(u8) { pub fn alphaChannelUsed(self: ColorType) bool { return @intFromEnum(self) & 0b0000_0100 != 0; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; pub const CompressionMethod = enum(u8) { @@ -156,6 +168,10 @@ pub const Palette = struct { .entries = entries, }; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; // --- tRNS -------------------------------------------------------------------- @@ -209,6 +225,10 @@ pub const Transparency = union(enum) { .rgba => error.InvalidPng, }; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; const TransparencyPalette = struct { @@ -218,6 +238,10 @@ const TransparencyPalette = struct { pub fn asSlice(self: *const TransparencyPalette) []const u8 { return self.entries[0..self.len]; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; // --- gAMA -------------------------------------------------------------------- @@ -245,6 +269,10 @@ const Gamma = struct { const gamma = std.mem.readInt(u32, chunk.data[0..4], .big); return .{ .gamma = gamma }; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; // --- cHRM -------------------------------------------------------------------- @@ -274,6 +302,10 @@ const Chromaticities = struct { .blue_y = std.mem.readInt(u32, chunk.data[28..32], .big), }; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; // --- sRGB -------------------------------------------------------------------- @@ -301,6 +333,10 @@ const RenderingIntent = enum(u8) { if (rendering_intent > 3) return error.InvalidPng; return @enumFromInt(rendering_intent); } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; // --- pHYs -------------------------------------------------------------------- @@ -310,7 +346,7 @@ pub const PhysicalPixelDimensions = struct { pixels_per_unit_y: u32, unit: PhysicalUnit, - pub fn decode(chunk: Chunk) PhysicalPixelDimensions { + pub fn decode(chunk: Chunk) !PhysicalPixelDimensions { std.debug.assert(chunk.chunk_type == .pHYs); if (chunk.data.len != 9) return error.InvalidPng; @@ -328,6 +364,10 @@ pub const PhysicalPixelDimensions = struct { .unit = @enumFromInt(unit), }; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; pub const PhysicalUnit = enum(u8) { @@ -381,41 +421,61 @@ pub const LastModificationTime = struct { .second = second, }; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; // ----------------------------------------------------------------------------- pub const ChunkType = enum(u32) { - IHDR = @bitCast("IHDR".*), - PLTE = @bitCast("PLTE".*), - IDAT = @bitCast("IDAT".*), - IEND = @bitCast("IEND".*), - tRNS = @bitCast("tRNS".*), - gAMA = @bitCast("gAMA".*), - cHRM = @bitCast("cHRM".*), - sRGB = @bitCast("sRGB".*), - iCCP = @bitCast("iCCP".*), - tEXt = @bitCast("tEXt".*), - zTXt = @bitCast("zTXt".*), - iTXt = @bitCast("iTXt".*), - bKGD = @bitCast("bKGD".*), - pHYs = @bitCast("pHYs".*), - sBIT = @bitCast("sBIT".*), - sPLT = @bitCast("sPLT".*), - hIST = @bitCast("hIST".*), - tIME = @bitCast("tIME".*), + IHDR = fromName("IHDR"), + PLTE = fromName("PLTE"), + IDAT = fromName("IDAT"), + IEND = fromName("IEND"), + tRNS = fromName("tRNS"), + gAMA = fromName("gAMA"), + cHRM = fromName("cHRM"), + sRGB = fromName("sRGB"), + iCCP = fromName("iCCP"), + tEXt = fromName("tEXt"), + zTXt = fromName("zTXt"), + iTXt = fromName("iTXt"), + bKGD = fromName("bKGD"), + pHYs = fromName("pHYs"), + sBIT = fromName("sBIT"), + sPLT = fromName("sPLT"), + hIST = fromName("hIST"), + tIME = fromName("tIME"), _, + fn fromName(name: *const [4]u8) u32 { + return @bitCast(name.*); + } + + pub fn fromBytes(bytes: *const [4]u8) ChunkType { + return @enumFromInt(fromName(bytes)); + } + + pub fn toBytes(self: ChunkType) [4]u8 { + return @bitCast(@intFromEnum(self)); + } + pub fn ancillary(self: ChunkType) bool { - return @as([4]u8, @bitCast(self))[0] & 0b0010_0000 != 0; + return self.toBytes()[0] & 0b0010_0000 != 0; } pub fn private(self: ChunkType) bool { - return @as([4]u8, @bitCast(self))[1] & 0b0010_0000 != 0; + return self.toBytes()[1] & 0b0010_0000 != 0; } pub fn safeToCopy(self: ChunkType) bool { - return @as([4]u8, @bitCast(self))[3] & 0b0010_0000 != 0; + return self.toBytes()[3] & 0b0010_0000 != 0; + } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); } }; @@ -460,6 +520,10 @@ pub const StandardKeyword = enum { pub fn isStandardKeyword(keyword: []const u8) ?StandardKeyword { return map.get(keyword); } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; /// The caller asserts that the buffer is at least `format.magic_length` bytes @@ -477,7 +541,7 @@ pub fn info(buffer: []const u8) ?Header { return null; } - const chunk, _ = decodeChunk(buffer[format.magic_length..]) catch return null orelse return null; + const chunk, _ = (decodeChunk(buffer[format.magic_length..]) catch return null) orelse return null; if (chunk.chunk_type != .IHDR) { return null; } @@ -495,7 +559,7 @@ pub fn decodeChunk(buffer: []const u8) !?struct { Chunk, []const u8 } { } const length = std.mem.readInt(u32, rest[0..4], .big); - const chunk_type: ChunkType = @bitCast(rest[4..8].*); + const chunk_type: ChunkType = .fromBytes(rest[4..8]); rest = rest[8..]; if (length > 0x7FFF_FFFF) { @@ -575,3 +639,7 @@ pub fn encodeChunks(chunks: []const Chunk, writer: *std.Io.Writer) !void { try writer.writeInt(u32, crc.final(), .big); } } + +test "refAllDecls" { + std.testing.refAllDecls(@This()); +} diff --git a/packages/media/src/qoa.zig b/packages/media/src/qoa.zig index 75d3efa..5be7c85 100644 --- a/packages/media/src/qoa.zig +++ b/packages/media/src/qoa.zig @@ -22,6 +22,10 @@ const Header = union(enum) { pub fn initStatic(static: HeaderStatic) Header { return .{ .static = static }; } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; const HeaderStreaming = void; @@ -36,6 +40,10 @@ const HeaderStatic = struct { const sample_rate: f64 = @floatFromInt(self.sample_rate); return @floatCast(samples / sample_rate); } + + test "refAllDecls" { + std.testing.refAllDecls(@This()); + } }; /// The caller asserts that the buffer is at least `format.magic_length` bytes @@ -73,3 +81,7 @@ pub fn info(buffer: []const u8) ?Header { }); } } + +test "refAllDecls" { + std.testing.refAllDecls(@This()); +} diff --git a/packages/media/src/qoi.zig b/packages/media/src/qoi.zig index 96a80e9..edc70c4 100644 --- a/packages/media/src/qoi.zig +++ b/packages/media/src/qoi.zig @@ -68,3 +68,7 @@ pub fn info(buffer: []const u8) ?Header { .color_space = @enumFromInt(color_space), }; } + +test "refAllDecls" { + std.testing.refAllDecls(@This()); +} diff --git a/packages/media/src/root.zig b/packages/media/src/root.zig index dd3e039..7050bfc 100644 --- a/packages/media/src/root.zig +++ b/packages/media/src/root.zig @@ -1,3 +1,5 @@ +const std = @import("std"); + pub const audio = @import("audio.zig"); pub const Format = @import("Format.zig"); pub const image = @import("image.zig"); @@ -7,3 +9,7 @@ pub const png = @import("png.zig"); pub const qoa = @import("qoa.zig"); pub const qoi = @import("qoi.zig"); pub const stbi = @import("stbi.zig"); + +test "refAllDecls" { + std.testing.refAllDecls(@This()); +} diff --git a/packages/media/src/stbi.zig b/packages/media/src/stbi.zig index ddef848..2229c64 100644 --- a/packages/media/src/stbi.zig +++ b/packages/media/src/stbi.zig @@ -5,17 +5,20 @@ const image = @import("image.zig"); const vm = @import("vecmath"); allocator: std.mem.Allocator, +io: std.Io, + +mutex: std.Io.Mutex = .init, allocations: std.AutoHashMapUnmanaged(*anyopaque, usize) = .empty, -mutex: std.Thread.Mutex = .{}, allocated_bytes: usize = 0, const alignment: std.mem.Alignment = .@"16"; const VoidPtr = ?*align(alignment.toByteUnits()) anyopaque; const log = std.log.scoped(.stbi); -pub fn init(allocator: std.mem.Allocator) Self { +pub fn init(allocator: std.mem.Allocator, io: std.Io) Self { return .{ .allocator = allocator, + .io = io, }; } @@ -76,7 +79,7 @@ pub fn loadStaticBuf(self: *Self, comptime W: u32, comptime H: u32, buf: []const return .{ .data = @as(*const [W * H]vm.Color, @ptrCast(@alignCast(res))).* }; } -pub fn loadStaticIo(self: *Self, comptime W: u32, comptime H: u32, reader: *std.io.Reader) !image.Static(W, H) { +pub fn loadStaticIo(self: *Self, comptime W: u32, comptime H: u32, reader: *std.Io.Reader) !image.Static(W, H) { current_self = self; defer current_self = undefined; @@ -109,7 +112,7 @@ pub fn loadDynamicBuf(self: *Self, buf: []const u8) !image.Dynamic { } /// On success, must free memory by calling `freeDynamic` method. -pub fn loadDynamicIo(self: *Self, reader: *std.io.Reader) !image.Dynamic { +pub fn loadDynamicIo(self: *Self, reader: *std.Io.Reader) !image.Dynamic { current_self = self; defer current_self = undefined; @@ -155,7 +158,7 @@ pub fn loadHdrBuf(self: *Self, buf: []const u8) !image.Hdr { } /// On success, must free memory by calling `freeHdr` method. -pub fn loadHdrIo(self: *Self, reader: *std.io.Reader) !image.Hdr { +pub fn loadHdrIo(self: *Self, reader: *std.Io.Reader) !image.Hdr { current_self = self; defer current_self = undefined; @@ -243,8 +246,8 @@ threadlocal var current_self: *Self = undefined; export fn castle_media_stbi_malloc(size: usize) callconv(.c) VoidPtr { const self = current_self; - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lock(self.io) catch return null; + defer self.mutex.unlock(self.io); self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null; const memory = self.allocator.alignedAlloc(u8, alignment, size) catch return null; @@ -259,8 +262,8 @@ export fn castle_media_stbi_malloc(size: usize) callconv(.c) VoidPtr { export fn castle_media_stbi_realloc(maybe_ptr: VoidPtr, size: usize) callconv(.c) VoidPtr { const self = current_self; - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lock(self.io) catch return null; + defer self.mutex.unlock(self.io); // NOTE If we were pedantic, we would consider the fact that we might not // need unused capacity if the memory doesn't get relocated. @@ -291,8 +294,8 @@ export fn castle_media_stbi_free(maybe_ptr: VoidPtr) callconv(.c) void { const self = current_self; if (maybe_ptr) |ptr| { - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(self.io); + defer self.mutex.unlock(self.io); const size = self.allocations.fetchRemove(ptr).?.value; self.allocated_bytes -= size; @@ -301,3 +304,7 @@ export fn castle_media_stbi_free(maybe_ptr: VoidPtr) callconv(.c) void { self.allocator.free(memory); } } + +test "refAllDecls" { + std.testing.refAllDecls(@This()); +}