From 85f49576611c2cbb63f9785ac39abb75403aa203 Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Fri, 6 Feb 2026 23:26:11 +0100 Subject: [PATCH] media: fix compilation errors, HDR loading; vecmath: ColorHdr --- packages/media/src/image.zig | 43 +++++++ packages/media/src/stbi.zig | 147 ++++++++++++++++++----- packages/media/src/stbi/stb_image.c | 4 +- packages/vecmath/src/colors/ColorHdr.zig | 30 +++++ packages/vecmath/src/root.zig | 1 + 5 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 packages/vecmath/src/colors/ColorHdr.zig diff --git a/packages/media/src/image.zig b/packages/media/src/image.zig index 62bf5ad..36f8114 100644 --- a/packages/media/src/image.zig +++ b/packages/media/src/image.zig @@ -66,3 +66,46 @@ pub const Dynamic = struct { @memset(self.data[0 .. self.width * self.height], color); } }; + +pub const Hdr = struct { + width: u32, + height: u32, + + data: [*]vm.ColorHdr, + + pub fn initBuffer(width: u32, height: u32, buffer: []vm.ColorHdr) @This() { + std.debug.assert(buffer.len == width * height); + return .{ + .width = width, + .height = height, + .data = buffer.ptr, + }; + } + + pub fn initAlloc(width: u32, height: u32, allocator: std.mem.Allocator) !@This() { + const buffer = try allocator.alloc(vm.ColorHdr, width * height); + return .{ + .width = width, + .height = height, + .data = buffer.ptr, + }; + } + + pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void { + allocator.free(self.data[0 .. self.width * self.height]); + } + + pub fn getPixel(self: *const @This(), x: u32, y: u32) vm.ColorHdr { + std.debug.assert(x < self.width and y < self.height); + return self.data[y * self.width + x]; + } + + pub fn setPixel(self: *@This(), x: u32, y: u32, color: vm.ColorHdr) void { + std.debug.assert(x < self.width and y < self.height); + self.data[y * self.width + x] = color; + } + + pub fn fill(self: *@This(), color: vm.ColorHdr) void { + @memset(self.data[0 .. self.width * self.height], color); + } +}; diff --git a/packages/media/src/stbi.zig b/packages/media/src/stbi.zig index 84c6d5f..ddef848 100644 --- a/packages/media/src/stbi.zig +++ b/packages/media/src/stbi.zig @@ -10,9 +10,10 @@ 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) void { +pub fn init(allocator: std.mem.Allocator) Self { return .{ .allocator = allocator, }; @@ -42,14 +43,14 @@ pub fn deinit(self: *Self) void { self.allocations.deinit(self.allocator); } -pub const import = struct { - extern fn stbi_load_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]u8; - extern fn stbi_load_16_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]u16; - extern fn stbi_loadf_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]f32; +const import = struct { + extern fn stbi_load_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u8; + extern fn stbi_load_16_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u16; + extern fn stbi_loadf_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) f32; - extern fn stbi_load_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]u8; - extern fn stbi_load_16_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]u16; - extern fn stbi_loadf_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]f32; + extern fn stbi_load_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u8; + extern fn stbi_load_16_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u16; + extern fn stbi_loadf_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) f32; extern fn stbi_info_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32) i32; extern fn stbi_is_16_bit_from_memory(buffer: [*]const u8, len: i32) i32; @@ -66,13 +67,26 @@ pub fn loadStaticBuf(self: *Self, comptime W: u32, comptime H: u32, buf: []const var x: i32 = undefined; var y: i32 = undefined; - var comp: i32 = undefined; - const res = import.stbi_load_from_memory(buf.ptr, @intCast(buf.len), &x, &y, &comp, 4) orelse return error.StbiError; + const res = import.stbi_load_from_memory(buf.ptr, @intCast(buf.len), &x, &y, null, 4) orelse return error.StbiError; + defer castle_media_stbi_free(res); + + if (x != W or y != H) return error.WrongDimensions; + + 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) { + current_self = self; + defer current_self = undefined; + + var x: i32 = undefined; + var y: i32 = undefined; + + const res = import.stbi_load_from_callbacks(&.std_io_reader_interface, reader, &x, &y, null, 4) orelse return error.StbiError; defer castle_media_stbi_free(res); if (x != W or y != H) return error.WrongDimensions; - if (comp != 4) return error.WrongComponentCount; return .{ .data = @as(*const [W * H]vm.Color, @ptrCast(@alignCast(res))).* }; } @@ -84,13 +98,14 @@ pub fn loadDynamicBuf(self: *Self, buf: []const u8) !image.Dynamic { var x: i32 = undefined; var y: i32 = undefined; - var comp: i32 = undefined; - const res = import.stbi_load_from_memory(buf.ptr, @intCast(buf.len), &x, &y, &comp, 4) orelse return error.StbiError; + const res = import.stbi_load_from_memory(buf.ptr, @intCast(buf.len), &x, &y, null, 4) orelse return error.StbiError; - if (comp != 4) return error.WrongComponentCount; + const buffer_ptr: [*]vm.Color = @ptrCast(@alignCast(res)); + const ux: u32 = @intCast(x); + const uy: u32 = @intCast(y); - return .initBuffer(@intCast(x), @intCast(y), res); + return .initBuffer(ux, uy, buffer_ptr[0 .. ux * uy]); } /// On success, must free memory by calling `freeDynamic` method. @@ -100,27 +115,93 @@ pub fn loadDynamicIo(self: *Self, reader: *std.io.Reader) !image.Dynamic { var x: i32 = undefined; var y: i32 = undefined; - var comp: i32 = undefined; - const res = import.stbi_load_from_callbacks(.std_io_reader_interface, reader, &x, &y, &comp, 4) orelse return error.StbiError; + const res = import.stbi_load_from_callbacks(&.std_io_reader_interface, reader, &x, &y, null, 4) orelse return error.StbiError; - if (comp != 4) return error.WrongComponentCount; + const buffer_ptr: [*]vm.Color = @ptrCast(@alignCast(res)); + const ux: u32 = @intCast(x); + const uy: u32 = @intCast(y); - return .initBuffer(@intCast(x), @intCast(y), res); + return .initBuffer(ux, uy, buffer_ptr[0 .. ux * uy]); +} + +/// On success, must free memory by calling `freeHdr` method. +pub fn loadHdrBuf(self: *Self, buf: []const u8) !image.Hdr { + current_self = self; + defer current_self = undefined; + + var x: i32 = undefined; + var y: i32 = undefined; + + const res_f32 = import.stbi_loadf_from_memory(buf.ptr, @intCast(buf.len), &x, &y, null, 4) orelse return error.StbiError; + defer castle_media_stbi_free(res_f32); + + const ux: u32 = @intCast(x); + const uy: u32 = @intCast(y); + const buffer_ptr_f32: [*]vm.Vector4 = @ptrCast(res_f32); + const buffer_ptr_f16: [*]vm.ColorHdr = @ptrCast(castle_media_stbi_malloc(ux * uy * @sizeOf(vm.ColorHdr)) orelse return error.OutOfMemory); + errdefer castle_media_stbi_free(buffer_ptr_f16); + + for (buffer_ptr_f16[0 .. ux * uy], buffer_ptr_f32[0 .. ux * uy]) |*sample_f16, sample_f32| { + sample_f16.* = .init( + std.math.clamp(@as(f16, @floatCast(sample_f32.x)), -std.math.floatMax(f16), std.math.floatMax(f16)), + std.math.clamp(@as(f16, @floatCast(sample_f32.y)), -std.math.floatMax(f16), std.math.floatMax(f16)), + std.math.clamp(@as(f16, @floatCast(sample_f32.z)), -std.math.floatMax(f16), std.math.floatMax(f16)), + std.math.clamp(@as(f16, @floatCast(sample_f32.w)), -std.math.floatMax(f16), std.math.floatMax(f16)), + ); + } + + return .initBuffer(ux, uy, buffer_ptr_f16[0 .. ux * uy]); +} + +/// On success, must free memory by calling `freeHdr` method. +pub fn loadHdrIo(self: *Self, reader: *std.io.Reader) !image.Hdr { + current_self = self; + defer current_self = undefined; + + var x: i32 = undefined; + var y: i32 = undefined; + + const res_f32 = import.stbi_loadf_from_callbacks(&.std_io_reader_interface, reader, &x, &y, null, 4) orelse return error.StbiError; + defer castle_media_stbi_free(res_f32); + + const ux: u32 = @intCast(x); + const uy: u32 = @intCast(y); + const buffer_ptr_f32: [*]vm.Vector4 = @ptrCast(res_f32); + const buffer_ptr_f16: [*]vm.ColorHdr = @ptrCast(castle_media_stbi_malloc(ux * uy * @sizeOf(vm.ColorHdr)) orelse return error.OutOfMemory); + errdefer castle_media_stbi_free(buffer_ptr_f16); + + for (buffer_ptr_f16[0 .. ux * uy], buffer_ptr_f32[0 .. ux * uy]) |*sample_f16, sample_f32| { + sample_f16.* = .init( + std.math.clamp(@as(f16, @floatCast(sample_f32.x)), -std.math.floatMax(f16), std.math.floatMax(f16)), + std.math.clamp(@as(f16, @floatCast(sample_f32.y)), -std.math.floatMax(f16), std.math.floatMax(f16)), + std.math.clamp(@as(f16, @floatCast(sample_f32.z)), -std.math.floatMax(f16), std.math.floatMax(f16)), + std.math.clamp(@as(f16, @floatCast(sample_f32.w)), -std.math.floatMax(f16), std.math.floatMax(f16)), + ); + } + + return .initBuffer(ux, uy, buffer_ptr_f16[0 .. ux * uy]); } pub fn freeDynamic(self: *Self, img: image.Dynamic) void { current_self = self; defer current_self = undefined; - castle_media_stbi_free(img.data); + castle_media_stbi_free(@ptrCast(@alignCast(img.data))); +} + +pub fn freeHdr(self: *Self, img: image.Hdr) void { + current_self = self; + defer current_self = undefined; + + castle_media_stbi_free(@ptrCast(@alignCast(img.data))); } // --- IO INTERFACE ------------------------------------------------------------ pub const IoCallbacks = extern struct { /// Fill `data` with `size` bytes. Return number of bytes actually read. - read: ?*const fn (ctx: ?*anyopaque, data: ?[*]u8, size: i32) callconv(.c) i32, + read: ?*const fn (ctx: ?*anyopaque, data: [*]u8, size: i32) callconv(.c) i32, /// Skip the next `n` bytes, or backtrack `-n` bytes if `n < 0`. skip: ?*const fn (ctx: ?*anyopaque, n: i32) callconv(.c) i32, /// Return non-zero value if at the end of file/data. @@ -132,23 +213,23 @@ pub const IoCallbacks = extern struct { .eof = stdIoReader_EofFn, }; - pub fn stdIoReader_ReadFn(ctx: ?*anyopaque, data: ?[*]u8, size: i32) callconv(.c) i32 { - const reader: *std.Io.Reader = @ptrCast(ctx.?); - const bytes_read = reader.readSliceShort(data[0..size]) catch return 0; - return bytes_read; + pub fn stdIoReader_ReadFn(ctx: ?*anyopaque, data: [*]u8, size: i32) callconv(.c) i32 { + const reader: *std.Io.Reader = @ptrCast(@alignCast(ctx.?)); + const bytes_read = reader.readSliceShort(data[0..@intCast(size)]) catch return 0; + return @intCast(bytes_read); } pub fn stdIoReader_SkipFn(ctx: ?*anyopaque, n: i32) callconv(.c) i32 { - const reader: *std.Io.Reader = @ptrCast(ctx.?); + const reader: *std.Io.Reader = @ptrCast(@alignCast(ctx.?)); // NOTE stb_image.h actually discards the return value from this // callback. If an actual error occurs, we're cooked (but it will be // very likely caught as a parsing error later). - _ = reader.discardAll(n) catch return 0; + _ = reader.discardAll(@intCast(n)) catch return 0; return 0; } pub fn stdIoReader_EofFn(ctx: ?*anyopaque) callconv(.c) i32 { - const reader: *std.Io.Reader = @ptrCast(ctx.?); + const reader: *std.Io.Reader = @ptrCast(@alignCast(ctx.?)); _ = reader.peekByte() catch return 1; return 0; @@ -159,7 +240,7 @@ pub const IoCallbacks = extern struct { threadlocal var current_self: *Self = undefined; -export fn castle_media_stbi_malloc(size: usize) callconv(.c) ?*anyopaque { +export fn castle_media_stbi_malloc(size: usize) callconv(.c) VoidPtr { const self = current_self; self.mutex.lock(); @@ -175,7 +256,7 @@ export fn castle_media_stbi_malloc(size: usize) callconv(.c) ?*anyopaque { return memory.ptr; } -export fn castle_media_stbi_realloc(maybe_ptr: ?*anyopaque, size: usize) callconv(.c) ?*anyopaque { +export fn castle_media_stbi_realloc(maybe_ptr: VoidPtr, size: usize) callconv(.c) VoidPtr { const self = current_self; self.mutex.lock(); @@ -187,7 +268,7 @@ export fn castle_media_stbi_realloc(maybe_ptr: ?*anyopaque, size: usize) callcon const old_memory = if (maybe_ptr) |ptr| blk_then: { const old_size = self.allocations.get(ptr).?; - break :blk_then @as([*]align(alignment.toByteUnits()) u8, @ptrCast(@alignCast(ptr)))[0..old_size]; + break :blk_then @as([*]align(alignment.toByteUnits()) u8, @ptrCast(ptr))[0..old_size]; } else blk_else: { break :blk_else @as([]align(alignment.toByteUnits()) u8, &.{}); }; @@ -206,7 +287,7 @@ export fn castle_media_stbi_realloc(maybe_ptr: ?*anyopaque, size: usize) callcon return memory.ptr; } -export fn castle_media_stbi_free(maybe_ptr: ?*anyopaque) callconv(.c) void { +export fn castle_media_stbi_free(maybe_ptr: VoidPtr) callconv(.c) void { const self = current_self; if (maybe_ptr) |ptr| { @@ -215,7 +296,7 @@ export fn castle_media_stbi_free(maybe_ptr: ?*anyopaque) callconv(.c) void { const size = self.allocations.fetchRemove(ptr).?.value; self.allocated_bytes -= size; - const memory = @as([*]align(alignment) u8, @ptrCast(@alignCast(ptr)))[0..size]; + const memory = @as([*]align(alignment.toByteUnits()) u8, @ptrCast(ptr))[0..size]; self.allocator.free(memory); } diff --git a/packages/media/src/stbi/stb_image.c b/packages/media/src/stbi/stb_image.c index b2f41f1..4118e95 100644 --- a/packages/media/src/stbi/stb_image.c +++ b/packages/media/src/stbi/stb_image.c @@ -1,7 +1,7 @@ #include -void castle_media_stbi_malloc(size_t size); -void castle_media_stbi_realloc(void *ptr, size_t size); +void *castle_media_stbi_malloc(size_t size); +void *castle_media_stbi_realloc(void *ptr, size_t size); void castle_media_stbi_free(void *ptr); #define STBI_MALLOC(size) castle_media_stbi_malloc(size) diff --git a/packages/vecmath/src/colors/ColorHdr.zig b/packages/vecmath/src/colors/ColorHdr.zig new file mode 100644 index 0000000..3b47dc9 --- /dev/null +++ b/packages/vecmath/src/colors/ColorHdr.zig @@ -0,0 +1,30 @@ +const std = @import("std"); +const vm = @import("../root.zig"); + +pub const ColorHdr = extern struct { + r: f16, + g: f16, + b: f16, + a: f16, + + pub const Array = [4]f16; + + pub const zero = init(0, 0, 0, 0); + pub const one = init(1, 1, 1, 1); + + pub inline fn init(r: f16, g: f16, b: f16, a: f16) ColorHdr { + return .{ .r = r, .g = g, .b = b, .a = a }; + } + + pub inline fn initArray(array: Array) ColorHdr { + return @bitCast(array); + } + + pub inline fn asArray(self: ColorHdr) Array { + return @bitCast(self); + } + + pub fn format(self: ColorHdr, w: *std.io.Writer) !void { + try w.print("ColorHdr[{d}, {d}, {d}, {d}]", .{ self.r, self.g, self.b, self.a }); + } +}; diff --git a/packages/vecmath/src/root.zig b/packages/vecmath/src/root.zig index 7c22415..5141115 100644 --- a/packages/vecmath/src/root.zig +++ b/packages/vecmath/src/root.zig @@ -3,6 +3,7 @@ const std = @import("std"); // --- COLORS ------------------------------------------------------------------ pub const Color = @import("colors/Color.zig").Color; +pub const ColorHdr = @import("colors/ColorHdr.zig").ColorHdr; // --- MATRICES ----------------------------------------------------------------