media: fix compilation errors, HDR loading; vecmath: ColorHdr

This commit is contained in:
2026-02-06 23:26:11 +01:00
parent 2756957f9b
commit 85f4957661
5 changed files with 190 additions and 35 deletions

View File

@@ -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);
}
};

View File

@@ -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);
}

View File

@@ -1,7 +1,7 @@
#include <stddef.h>
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)

View File

@@ -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 });
}
};

View File

@@ -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 ----------------------------------------------------------------