Fix loading textures

This commit is contained in:
Przemyslaw Gasinski 2024-08-06 15:54:44 +02:00
parent c0591ecb24
commit e182d73edc
14 changed files with 13216 additions and 35 deletions

BIN
assets/textures/giraffe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

View file

@ -41,9 +41,10 @@ pub fn build(b: *std.Build) void {
const zmath = b.dependency("zmath", .{}); const zmath = b.dependency("zmath", .{});
exe.root_module.addImport("zmath", zmath.module("root")); exe.root_module.addImport("zmath", zmath.module("root"));
// zigimg // zstbi
const zigimg = b.dependency("zigimg", .{}); const zstbi = b.dependency("zstbi", .{});
exe.root_module.addImport("zigimg", zigimg.module("zigimg")); exe.root_module.addImport("zstbi", zstbi.module("root"));
exe.linkLibrary(zstbi.artifact("zstbi"));
// --- // ---

View file

@ -5,18 +5,11 @@
.dependencies = .{ .dependencies = .{
.zmath = .{ .path = "libs/zmath" }, .zmath = .{ .path = "libs/zmath" },
.zstbi = .{ .path = "libs/zstbi" },
.vulkan_zig = .{ .vulkan_zig = .{
.url = "https://github.com/Snektron/vulkan-zig/archive/9f6e6177b1fdb3ed22231d9216a24480e84cfa5e.tar.gz", .url = "https://github.com/Snektron/vulkan-zig/archive/9f6e6177b1fdb3ed22231d9216a24480e84cfa5e.tar.gz",
.hash = "1220f2961df224f7d35dee774b26194b8b937cc252fa8e4023407776c58521d53e38", .hash = "1220f2961df224f7d35dee774b26194b8b937cc252fa8e4023407776c58521d53e38",
}, },
// .sdl = .{
// .url = "https://github.com/ikskuh/SDL.zig/archive/9663dc70c19b13afcb4b9f596c928d7b2838e548.tar.gz",
// .hash = "12202141beb92d68ef5088538ff761d5c3ecd2d4e11867c89fbbdcd9f814b8cba8ee",
// },
.zigimg = .{
.url = "https://github.com/zigimg/zigimg/archive/d9dbbe22b5f7b5f1f4772169ed93ffeed8e8124d.tar.gz",
.hash = "122013646f7038ecc71ddf8a0d7de346d29a6ec40140af57f838b0a975c69af512b0",
},
}, },
.paths = .{ .paths = .{

22
libs/zstbi/LICENSE Normal file
View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2021 Michal Ziulek
Copyright (c) 2024 zig-gamedev contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

93
libs/zstbi/README.md Normal file
View file

@ -0,0 +1,93 @@
# zstbi v0.10.0 - stb image bindings
## Features
* Supports Zig memory allocators
* Supports decoding most popular formats
* Supports HDR images
* Supports 8-bits and 16-bits per channel
* Supports image resizing
* Supports image writing (.png, .jpg)
## Getting started
Copy `zstbi` to a subdirectory of your project and add the following to your `build.zig.zon` .dependencies:
```zig
.zstbi = .{ .path = "libs/zstbi" },
```
Then in your `build.zig` add:
```zig
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{ ... });
const zstbi = b.dependency("zstbi", .{});
exe.root_module.addImport("zstbi", zstbi.module("root"));
exe.linkLibrary(zstbi.artifact("zstbi"));
}
```
Now in your code you may import and use `zstbi`.
Init the lib. `zstbi.init()` is cheap and you may call it whenever you need to change memory allocator. Must be called from the main thread.
```zig
const zstbi = @import("zstbi");
zstbi.init(allocator);
defer zstbi.deinit();
```
```zig
pub const Image = struct {
data: []u8,
width: u32,
height: u32,
num_components: u32,
bytes_per_component: u32,
bytes_per_row: u32,
is_hdr: bool,
...
```
```zig
pub fn loadFromFile(pathname: [:0]const u8, forced_num_components: u32) !Image
pub fn loadFromMemory(data: []const u8, forced_num_components: u32) !Image
pub fn createEmpty(width: u32, height: u32, num_components: u32, args: struct {
bytes_per_component: u32 = 0,
bytes_per_row: u32 = 0,
}) !Image
pub fn info(pathname: [:0]const u8) struct {
is_supported: bool,
width: u32,
height: u32,
num_components: u32,
}
pub fn resize(image: *const Image, new_width: u32, new_height: u32) Image
pub fn writeToFile(
image: *const Image,
filename: [:0]const u8,
image_format: ImageWriteFormat,
) ImageWriteError!void
pub fn writeToFn(
image: *const Image,
write_fn: *const fn (ctx: ?*anyopaque, data: ?*anyopaque, size: c_int) callconv(.C) void,
context: ?*anyopaque,
image_format: ImageWriteFormat,
) ImageWriteError!void
```
```zig
var image = try zstbi.Image.loadFromFile("data/image.png", forced_num_components);
defer image.deinit();
const new_resized_image = image.resize(1024, 1024);
```
Misc functions:
```zig
pub fn isHdr(filename: [:0]const u8) bool
pub fn is16bit(filename: [:0]const u8) bool
pub fn setFlipVerticallyOnLoad(should_flip: bool) void
```

52
libs/zstbi/build.zig Normal file
View file

@ -0,0 +1,52 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const optimize = b.standardOptimizeOption(.{});
const target = b.standardTargetOptions(.{});
_ = b.addModule("root", .{
.root_source_file = b.path("src/zstbi.zig"),
});
const zstbi_lib = b.addStaticLibrary(.{
.name = "zstbi",
.target = target,
.optimize = optimize,
});
zstbi_lib.addIncludePath(b.path("libs/stbi"));
if (optimize == .Debug) {
// TODO: Workaround for Zig bug.
zstbi_lib.addCSourceFile(.{
.file = b.path("src/zstbi.c"),
.flags = &.{
"-std=c99",
"-fno-sanitize=undefined",
"-g",
"-O0",
},
});
} else {
zstbi_lib.addCSourceFile(.{
.file = b.path("src/zstbi.c"),
.flags = &.{
"-std=c99",
"-fno-sanitize=undefined",
},
});
}
zstbi_lib.linkLibC();
b.installArtifact(zstbi_lib);
const test_step = b.step("test", "Run zstbi tests");
const tests = b.addTest(.{
.name = "zstbi-tests",
.root_source_file = b.path("src/zstbi.zig"),
.target = target,
.optimize = optimize,
});
tests.linkLibrary(zstbi_lib);
b.installArtifact(tests);
test_step.dependOn(&b.addRunArtifact(tests).step);
}

11
libs/zstbi/build.zig.zon Normal file
View file

@ -0,0 +1,11 @@
.{
.name = "zstbi",
.version = "0.10.0",
.paths = .{
"build.zig",
"build.zig.zon",
"libs",
"src",
"README.md",
},
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

32
libs/zstbi/src/zstbi.c Normal file
View file

@ -0,0 +1,32 @@
#include <stdlib.h>
void* (*zstbiMallocPtr)(size_t size) = NULL;
void* (*zstbiReallocPtr)(void* ptr, size_t size) = NULL;
void (*zstbiFreePtr)(void* ptr) = NULL;
#define STBI_MALLOC(size) zstbiMallocPtr(size)
#define STBI_REALLOC(ptr, size) zstbiReallocPtr(ptr, size)
#define STBI_FREE(ptr) zstbiFreePtr(ptr)
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
void* (*zstbirMallocPtr)(size_t size, void* context) = NULL;
void (*zstbirFreePtr)(void* ptr, void* context) = NULL;
#define STBIR_MALLOC(size, context) zstbirMallocPtr(size, context)
#define STBIR_FREE(ptr, context) zstbirFreePtr(ptr, context)
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize.h"
void* (*zstbiwMallocPtr)(size_t size) = NULL;
void* (*zstbiwReallocPtr)(void* ptr, size_t size) = NULL;
void (*zstbiwFreePtr)(void* ptr) = NULL;
#define STBIW_MALLOC(size) zstbiwMallocPtr(size)
#define STBIW_REALLOC(ptr, size) zstbiwReallocPtr(ptr, size)
#define STBIW_FREE(ptr) zstbiwFreePtr(ptr)
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

619
libs/zstbi/src/zstbi.zig Normal file
View file

@ -0,0 +1,619 @@
const std = @import("std");
const testing = std.testing;
const assert = std.debug.assert;
pub fn init(allocator: std.mem.Allocator) void {
assert(mem_allocator == null);
mem_allocator = allocator;
mem_allocations = std.AutoHashMap(usize, usize).init(allocator);
// stb image
zstbiMallocPtr = zstbiMalloc;
zstbiReallocPtr = zstbiRealloc;
zstbiFreePtr = zstbiFree;
// stb image resize
zstbirMallocPtr = zstbirMalloc;
zstbirFreePtr = zstbirFree;
// stb image write
zstbiwMallocPtr = zstbiMalloc;
zstbiwReallocPtr = zstbiRealloc;
zstbiwFreePtr = zstbiFree;
}
pub fn deinit() void {
assert(mem_allocator != null);
assert(mem_allocations.?.count() == 0);
setFlipVerticallyOnLoad(false);
setFlipVerticallyOnWrite(false);
mem_allocations.?.deinit();
mem_allocations = null;
mem_allocator = null;
}
pub const JpgWriteSettings = struct {
quality: u32,
};
pub const ImageWriteFormat = union(enum) {
png,
jpg: JpgWriteSettings,
};
pub const ImageWriteError = error{
CouldNotWriteImage,
};
pub const Image = struct {
data: []u8,
width: u32,
height: u32,
num_components: u32,
bytes_per_component: u32,
bytes_per_row: u32,
is_hdr: bool,
pub fn info(pathname: [:0]const u8) struct {
is_supported: bool,
width: u32,
height: u32,
num_components: u32,
} {
assert(mem_allocator != null);
var w: c_int = 0;
var h: c_int = 0;
var c: c_int = 0;
const is_supported = stbi_info(pathname, &w, &h, &c);
return .{
.is_supported = if (is_supported == 1) true else false,
.width = @as(u32, @intCast(w)),
.height = @as(u32, @intCast(h)),
.num_components = @as(u32, @intCast(c)),
};
}
pub fn loadFromFile(pathname: [:0]const u8, forced_num_components: u32) !Image {
assert(mem_allocator != null);
var width: u32 = 0;
var height: u32 = 0;
var num_components: u32 = 0;
var bytes_per_component: u32 = 0;
var bytes_per_row: u32 = 0;
var is_hdr = false;
const data = if (isHdr(pathname)) data: {
var x: c_int = undefined;
var y: c_int = undefined;
var ch: c_int = undefined;
const ptr = stbi_loadf(
pathname,
&x,
&y,
&ch,
@as(c_int, @intCast(forced_num_components)),
);
if (ptr == null) return error.ImageInitFailed;
num_components = if (forced_num_components == 0) @as(u32, @intCast(ch)) else forced_num_components;
width = @as(u32, @intCast(x));
height = @as(u32, @intCast(y));
bytes_per_component = 2;
bytes_per_row = width * num_components * bytes_per_component;
is_hdr = true;
// Convert each component from f32 to f16.
var ptr_f16 = @as([*]f16, @ptrCast(ptr.?));
const num = width * height * num_components;
var i: u32 = 0;
while (i < num) : (i += 1) {
ptr_f16[i] = @as(f16, @floatCast(ptr.?[i]));
}
break :data @as([*]u8, @ptrCast(ptr_f16))[0 .. height * bytes_per_row];
} else data: {
var x: c_int = undefined;
var y: c_int = undefined;
var ch: c_int = undefined;
const is_16bit = is16bit(pathname);
const ptr = if (is_16bit) @as(?[*]u8, @ptrCast(stbi_load_16(
pathname,
&x,
&y,
&ch,
@as(c_int, @intCast(forced_num_components)),
))) else stbi_load(
pathname,
&x,
&y,
&ch,
@as(c_int, @intCast(forced_num_components)),
);
if (ptr == null) return error.ImageInitFailed;
num_components = if (forced_num_components == 0) @as(u32, @intCast(ch)) else forced_num_components;
width = @as(u32, @intCast(x));
height = @as(u32, @intCast(y));
bytes_per_component = if (is_16bit) 2 else 1;
bytes_per_row = width * num_components * bytes_per_component;
is_hdr = false;
break :data @as([*]u8, @ptrCast(ptr))[0 .. height * bytes_per_row];
};
return Image{
.data = data,
.width = width,
.height = height,
.num_components = num_components,
.bytes_per_component = bytes_per_component,
.bytes_per_row = bytes_per_row,
.is_hdr = is_hdr,
};
}
pub fn loadFromMemory(data: []const u8, forced_num_components: u32) !Image {
assert(mem_allocator != null);
var width: u32 = 0;
var height: u32 = 0;
var num_components: u32 = 0;
var bytes_per_component: u32 = 0;
var bytes_per_row: u32 = 0;
var is_hdr = false;
const image_data = if (isHdrFromMem(data)) data: {
var x: c_int = undefined;
var y: c_int = undefined;
var ch: c_int = undefined;
const ptr = stbi_loadf_from_memory(
data.ptr,
@as(c_int, @intCast(data.len)),
&x,
&y,
&ch,
@as(c_int, @intCast(forced_num_components)),
);
if (ptr == null) return error.ImageInitFailed;
num_components = if (forced_num_components == 0) @as(u32, @intCast(ch)) else forced_num_components;
width = @as(u32, @intCast(x));
height = @as(u32, @intCast(y));
bytes_per_component = 2;
bytes_per_row = width * num_components * bytes_per_component;
is_hdr = true;
// Convert each component from f32 to f16.
var ptr_f16 = @as([*]f16, @ptrCast(ptr.?));
const num = width * height * num_components;
var i: u32 = 0;
while (i < num) : (i += 1) {
ptr_f16[i] = @as(f16, @floatCast(ptr.?[i]));
}
break :data @as([*]u8, @ptrCast(ptr_f16))[0 .. height * bytes_per_row];
} else data: {
var x: c_int = undefined;
var y: c_int = undefined;
var ch: c_int = undefined;
const ptr = stbi_load_from_memory(
data.ptr,
@as(c_int, @intCast(data.len)),
&x,
&y,
&ch,
@as(c_int, @intCast(forced_num_components)),
);
if (ptr == null) return error.ImageInitFailed;
num_components = if (forced_num_components == 0) @as(u32, @intCast(ch)) else forced_num_components;
width = @as(u32, @intCast(x));
height = @as(u32, @intCast(y));
bytes_per_component = 1;
bytes_per_row = width * num_components * bytes_per_component;
break :data @as([*]u8, @ptrCast(ptr))[0 .. height * bytes_per_row];
};
return Image{
.data = image_data,
.width = width,
.height = height,
.num_components = num_components,
.bytes_per_component = bytes_per_component,
.bytes_per_row = bytes_per_row,
.is_hdr = is_hdr,
};
}
pub fn createEmpty(width: u32, height: u32, num_components: u32, args: struct {
bytes_per_component: u32 = 0,
bytes_per_row: u32 = 0,
}) !Image {
assert(mem_allocator != null);
const bytes_per_component = if (args.bytes_per_component == 0) 1 else args.bytes_per_component;
const bytes_per_row = if (args.bytes_per_row == 0)
width * num_components * bytes_per_component
else
args.bytes_per_row;
const size = height * bytes_per_row;
const data = @as([*]u8, @ptrCast(zstbiMalloc(size)));
@memset(data[0..size], 0);
return Image{
.data = data[0..size],
.width = width,
.height = height,
.num_components = num_components,
.bytes_per_component = bytes_per_component,
.bytes_per_row = bytes_per_row,
.is_hdr = false,
};
}
pub fn resize(image: *const Image, new_width: u32, new_height: u32) Image {
assert(mem_allocator != null);
// TODO: Add support for HDR images
const new_bytes_per_row = new_width * image.num_components * image.bytes_per_component;
const new_size = new_height * new_bytes_per_row;
const new_data = @as([*]u8, @ptrCast(zstbiMalloc(new_size)));
stbir_resize_uint8(
image.data.ptr,
@as(c_int, @intCast(image.width)),
@as(c_int, @intCast(image.height)),
0,
new_data,
@as(c_int, @intCast(new_width)),
@as(c_int, @intCast(new_height)),
0,
@as(c_int, @intCast(image.num_components)),
);
return .{
.data = new_data[0..new_size],
.width = new_width,
.height = new_height,
.num_components = image.num_components,
.bytes_per_component = image.bytes_per_component,
.bytes_per_row = new_bytes_per_row,
.is_hdr = image.is_hdr,
};
}
pub fn writeToFile(
image: Image,
filename: [:0]const u8,
image_format: ImageWriteFormat,
) ImageWriteError!void {
assert(mem_allocator != null);
const w = @as(c_int, @intCast(image.width));
const h = @as(c_int, @intCast(image.height));
const comp = @as(c_int, @intCast(image.num_components));
const result = switch (image_format) {
.png => stbi_write_png(filename.ptr, w, h, comp, image.data.ptr, 0),
.jpg => |settings| stbi_write_jpg(
filename.ptr,
w,
h,
comp,
image.data.ptr,
@as(c_int, @intCast(settings.quality)),
),
};
// if the result is 0 then it means an error occured (per stb image write docs)
if (result == 0) {
return ImageWriteError.CouldNotWriteImage;
}
}
pub fn writeToFn(
image: Image,
write_fn: *const fn (ctx: ?*anyopaque, data: ?*anyopaque, size: c_int) callconv(.C) void,
context: ?*anyopaque,
image_format: ImageWriteFormat,
) ImageWriteError!void {
assert(mem_allocator != null);
const w = @as(c_int, @intCast(image.width));
const h = @as(c_int, @intCast(image.height));
const comp = @as(c_int, @intCast(image.num_components));
const result = switch (image_format) {
.png => stbi_write_png_to_func(write_fn, context, w, h, comp, image.data.ptr, 0),
.jpg => |settings| stbi_write_jpg_to_func(
write_fn,
context,
w,
h,
comp,
image.data.ptr,
@as(c_int, @intCast(settings.quality)),
),
};
// if the result is 0 then it means an error occured (per stb image write docs)
if (result == 0) {
return ImageWriteError.CouldNotWriteImage;
}
}
pub fn deinit(image: *Image) void {
stbi_image_free(image.data.ptr);
image.* = undefined;
}
};
/// `pub fn setHdrToLdrScale(scale: f32) void`
pub const setHdrToLdrScale = stbi_hdr_to_ldr_scale;
/// `pub fn setHdrToLdrGamma(gamma: f32) void`
pub const setHdrToLdrGamma = stbi_hdr_to_ldr_gamma;
/// `pub fn setLdrToHdrScale(scale: f32) void`
pub const setLdrToHdrScale = stbi_ldr_to_hdr_scale;
/// `pub fn setLdrToHdrGamma(gamma: f32) void`
pub const setLdrToHdrGamma = stbi_ldr_to_hdr_gamma;
pub fn isHdr(filename: [:0]const u8) bool {
return stbi_is_hdr(filename) != 0;
}
pub fn isHdrFromMem(buffer: []const u8) bool {
return stbi_is_hdr_from_memory(buffer.ptr, @as(c_int, @intCast(buffer.len))) != 0;
}
pub fn is16bit(filename: [:0]const u8) bool {
return stbi_is_16_bit(filename) != 0;
}
pub fn setFlipVerticallyOnLoad(should_flip: bool) void {
stbi_set_flip_vertically_on_load(if (should_flip) 1 else 0);
}
pub fn setFlipVerticallyOnWrite(should_flip: bool) void {
stbi_flip_vertically_on_write(if (should_flip) 1 else 0);
}
var mem_allocator: ?std.mem.Allocator = null;
var mem_allocations: ?std.AutoHashMap(usize, usize) = null;
var mem_mutex: std.Thread.Mutex = .{};
const mem_alignment = 16;
extern var zstbiMallocPtr: ?*const fn (size: usize) callconv(.C) ?*anyopaque;
extern var zstbiwMallocPtr: ?*const fn (size: usize) callconv(.C) ?*anyopaque;
fn zstbiMalloc(size: usize) callconv(.C) ?*anyopaque {
mem_mutex.lock();
defer mem_mutex.unlock();
const mem = mem_allocator.?.alignedAlloc(
u8,
mem_alignment,
size,
) catch @panic("zstbi: out of memory");
mem_allocations.?.put(@intFromPtr(mem.ptr), size) catch @panic("zstbi: out of memory");
return mem.ptr;
}
extern var zstbiReallocPtr: ?*const fn (ptr: ?*anyopaque, size: usize) callconv(.C) ?*anyopaque;
extern var zstbiwReallocPtr: ?*const fn (ptr: ?*anyopaque, size: usize) callconv(.C) ?*anyopaque;
fn zstbiRealloc(ptr: ?*anyopaque, size: usize) callconv(.C) ?*anyopaque {
mem_mutex.lock();
defer mem_mutex.unlock();
const old_size = if (ptr != null) mem_allocations.?.get(@intFromPtr(ptr.?)).? else 0;
const old_mem = if (old_size > 0)
@as([*]align(mem_alignment) u8, @ptrCast(@alignCast(ptr)))[0..old_size]
else
@as([*]align(mem_alignment) u8, undefined)[0..0];
const new_mem = mem_allocator.?.realloc(old_mem, size) catch @panic("zstbi: out of memory");
if (ptr != null) {
const removed = mem_allocations.?.remove(@intFromPtr(ptr.?));
std.debug.assert(removed);
}
mem_allocations.?.put(@intFromPtr(new_mem.ptr), size) catch @panic("zstbi: out of memory");
return new_mem.ptr;
}
extern var zstbiFreePtr: ?*const fn (maybe_ptr: ?*anyopaque) callconv(.C) void;
extern var zstbiwFreePtr: ?*const fn (maybe_ptr: ?*anyopaque) callconv(.C) void;
fn zstbiFree(maybe_ptr: ?*anyopaque) callconv(.C) void {
if (maybe_ptr) |ptr| {
mem_mutex.lock();
defer mem_mutex.unlock();
const size = mem_allocations.?.fetchRemove(@intFromPtr(ptr)).?.value;
const mem = @as([*]align(mem_alignment) u8, @ptrCast(@alignCast(ptr)))[0..size];
mem_allocator.?.free(mem);
}
}
extern var zstbirMallocPtr: ?*const fn (size: usize, maybe_context: ?*anyopaque) callconv(.C) ?*anyopaque;
fn zstbirMalloc(size: usize, _: ?*anyopaque) callconv(.C) ?*anyopaque {
return zstbiMalloc(size);
}
extern var zstbirFreePtr: ?*const fn (maybe_ptr: ?*anyopaque, maybe_context: ?*anyopaque) callconv(.C) void;
fn zstbirFree(maybe_ptr: ?*anyopaque, _: ?*anyopaque) callconv(.C) void {
zstbiFree(maybe_ptr);
}
extern fn stbi_info(filename: [*:0]const u8, x: *c_int, y: *c_int, comp: *c_int) c_int;
extern fn stbi_load(
filename: [*:0]const u8,
x: *c_int,
y: *c_int,
channels_in_file: *c_int,
desired_channels: c_int,
) ?[*]u8;
extern fn stbi_load_16(
filename: [*:0]const u8,
x: *c_int,
y: *c_int,
channels_in_file: *c_int,
desired_channels: c_int,
) ?[*]u16;
extern fn stbi_loadf(
filename: [*:0]const u8,
x: *c_int,
y: *c_int,
channels_in_file: *c_int,
desired_channels: c_int,
) ?[*]f32;
pub extern fn stbi_load_from_memory(
buffer: [*]const u8,
len: c_int,
x: *c_int,
y: *c_int,
channels_in_file: *c_int,
desired_channels: c_int,
) ?[*]u8;
pub extern fn stbi_loadf_from_memory(
buffer: [*]const u8,
len: c_int,
x: *c_int,
y: *c_int,
channels_in_file: *c_int,
desired_channels: c_int,
) ?[*]f32;
extern fn stbi_image_free(image_data: ?[*]u8) void;
extern fn stbi_hdr_to_ldr_scale(scale: f32) void;
extern fn stbi_hdr_to_ldr_gamma(gamma: f32) void;
extern fn stbi_ldr_to_hdr_scale(scale: f32) void;
extern fn stbi_ldr_to_hdr_gamma(gamma: f32) void;
extern fn stbi_is_16_bit(filename: [*:0]const u8) c_int;
extern fn stbi_is_hdr(filename: [*:0]const u8) c_int;
extern fn stbi_is_hdr_from_memory(buffer: [*]const u8, len: c_int) c_int;
extern fn stbi_set_flip_vertically_on_load(flag_true_if_should_flip: c_int) void;
extern fn stbi_flip_vertically_on_write(flag: c_int) void; // flag is non-zero to flip data vertically
extern fn stbir_resize_uint8(
input_pixels: [*]const u8,
input_w: c_int,
input_h: c_int,
input_stride_in_bytes: c_int,
output_pixels: [*]u8,
output_w: c_int,
output_h: c_int,
output_stride_in_bytes: c_int,
num_channels: c_int,
) void;
extern fn stbi_write_jpg(
filename: [*:0]const u8,
w: c_int,
h: c_int,
comp: c_int,
data: [*]const u8,
quality: c_int,
) c_int;
extern fn stbi_write_png(
filename: [*:0]const u8,
w: c_int,
h: c_int,
comp: c_int,
data: [*]const u8,
stride_in_bytes: c_int,
) c_int;
extern fn stbi_write_png_to_func(
func: *const fn (?*anyopaque, ?*anyopaque, c_int) callconv(.C) void,
context: ?*anyopaque,
w: c_int,
h: c_int,
comp: c_int,
data: [*]const u8,
stride_in_bytes: c_int,
) c_int;
extern fn stbi_write_jpg_to_func(
func: *const fn (?*anyopaque, ?*anyopaque, c_int) callconv(.C) void,
context: ?*anyopaque,
x: c_int,
y: c_int,
comp: c_int,
data: [*]const u8,
quality: c_int,
) c_int;
test "zstbi basic" {
init(testing.allocator);
defer deinit();
var im1 = try Image.createEmpty(8, 6, 4, .{});
defer im1.deinit();
try testing.expect(im1.width == 8);
try testing.expect(im1.height == 6);
try testing.expect(im1.num_components == 4);
}
test "zstbi resize" {
init(testing.allocator);
defer deinit();
var im1 = try Image.createEmpty(32, 32, 4, .{});
defer im1.deinit();
var im2 = im1.resize(8, 6);
defer im2.deinit();
try testing.expect(im2.width == 8);
try testing.expect(im2.height == 6);
try testing.expect(im2.num_components == 4);
}
test "zstbi write and load file" {
init(testing.allocator);
defer deinit();
const pth = try std.fs.selfExeDirPathAlloc(testing.allocator);
defer testing.allocator.free(pth);
try std.posix.chdir(pth);
var img = try Image.createEmpty(8, 6, 4, .{});
defer img.deinit();
try img.writeToFile("test_img.png", ImageWriteFormat.png);
try img.writeToFile("test_img.jpg", .{ .jpg = .{ .quality = 80 } });
var img_png = try Image.loadFromFile("test_img.png", 0);
defer img_png.deinit();
try testing.expect(img_png.width == img.width);
try testing.expect(img_png.height == img.height);
try testing.expect(img_png.num_components == img.num_components);
var img_jpg = try Image.loadFromFile("test_img.jpg", 0);
defer img_jpg.deinit();
try testing.expect(img_jpg.width == img.width);
try testing.expect(img_jpg.height == img.height);
try testing.expect(img_jpg.num_components == 3); // RGB JPEG
try std.fs.cwd().deleteFile("test_img.png");
try std.fs.cwd().deleteFile("test_img.jpg");
}

1
profile.json Normal file

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,8 @@ const vk = @import("vulkan");
const builtin = @import("builtin"); const builtin = @import("builtin");
const shaders = @import("shaders"); const shaders = @import("shaders");
const zm = @import("zmath"); const zm = @import("zmath");
const img = @import("zigimg"); // const img = @import("zigimg");
const img = @import("zstbi");
const Utilities = @import("utilities.zig"); const Utilities = @import("utilities.zig");
const QueueFamilyIndices = Utilities.QueueFamilyIndices; const QueueFamilyIndices = Utilities.QueueFamilyIndices;
@ -99,6 +100,7 @@ pub const VulkanRenderer = struct {
vp_uniform_buffer_memory: []vk.DeviceMemory, vp_uniform_buffer_memory: []vk.DeviceMemory,
// Assets // Assets
image_files: std.ArrayList(img.Image),
texture_images: std.ArrayList(vk.Image), texture_images: std.ArrayList(vk.Image),
texture_image_memory: std.ArrayList(vk.DeviceMemory), texture_image_memory: std.ArrayList(vk.DeviceMemory),
texture_image_views: std.ArrayList(vk.ImageView), texture_image_views: std.ArrayList(vk.ImageView),
@ -131,6 +133,8 @@ pub const VulkanRenderer = struct {
self.allocator = allocator; self.allocator = allocator;
self.vkb = try BaseDispatch.load(try sdl.vulkan.getVkGetInstanceProcAddr()); self.vkb = try BaseDispatch.load(try sdl.vulkan.getVkGetInstanceProcAddr());
img.init(allocator);
try self.createInstance(); try self.createInstance();
try self.createSurface(); try self.createSurface();
@ -157,6 +161,7 @@ pub const VulkanRenderer = struct {
try self.createSynchronisation(); try self.createSynchronisation();
self.image_files = std.ArrayList(img.Image).init(self.allocator);
self.texture_images = std.ArrayList(vk.Image).init(self.allocator); self.texture_images = std.ArrayList(vk.Image).init(self.allocator);
self.texture_image_memory = std.ArrayList(vk.DeviceMemory).init(self.allocator); self.texture_image_memory = std.ArrayList(vk.DeviceMemory).init(self.allocator);
self.texture_image_views = std.ArrayList(vk.ImageView).init(self.allocator); self.texture_image_views = std.ArrayList(vk.ImageView).init(self.allocator);
@ -186,7 +191,6 @@ pub const VulkanRenderer = struct {
.{ .pos = .{ 0.4, -0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 }, .tex = .{ 0.0, 0.0 } }, // 2 .{ .pos = .{ 0.4, -0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 }, .tex = .{ 0.0, 0.0 } }, // 2
.{ .pos = .{ 0.4, 0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 }, .tex = .{ 0.0, 1.0 } }, // 3 .{ .pos = .{ 0.4, 0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 }, .tex = .{ 0.0, 1.0 } }, // 3
}; };
var mesh_vertices2 = [_]Vertex{ var mesh_vertices2 = [_]Vertex{
.{ .pos = .{ -0.25, 0.6, 0.0 }, .col = .{ 0.0, 0.0, 1.0 }, .tex = .{ 1.0, 1.0 } }, // 0 .{ .pos = .{ -0.25, 0.6, 0.0 }, .col = .{ 0.0, 0.0, 1.0 }, .tex = .{ 1.0, 1.0 } }, // 0
.{ .pos = .{ -0.25, -0.6, 0.0 }, .col = .{ 0.0, 0.0, 1.0 }, .tex = .{ 1.0, 0.0 } }, // 1 .{ .pos = .{ -0.25, -0.6, 0.0 }, .col = .{ 0.0, 0.0, 1.0 }, .tex = .{ 1.0, 0.0 } }, // 1
@ -220,7 +224,7 @@ pub const VulkanRenderer = struct {
self.graphics_command_pool, self.graphics_command_pool,
&mesh_vertices2, &mesh_vertices2,
&mesh_indices, &mesh_indices,
try self.createTexture("test.png"), try self.createTexture("giraffe.png"),
self.allocator, self.allocator,
); );
@ -298,6 +302,11 @@ pub const VulkanRenderer = struct {
self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null); self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null);
} }
for (0..self.image_files.items.len) |i| {
self.image_files.items[i].deinit();
}
self.image_files.deinit();
self.device.destroySampler(self.texture_sampler, null); self.device.destroySampler(self.texture_sampler, null);
for ( for (
@ -367,6 +376,8 @@ pub const VulkanRenderer = struct {
self.allocator.destroy(self.device.wrapper); self.allocator.destroy(self.device.wrapper);
self.allocator.destroy(self.instance.wrapper); self.allocator.destroy(self.instance.wrapper);
img.deinit();
} }
fn createInstance(self: *Self) !void { fn createInstance(self: *Self) !void {
@ -607,14 +618,14 @@ pub const VulkanRenderer = struct {
// But must happen before... // But must happen before...
.dst_subpass = 0, .dst_subpass = 0,
.dst_stage_mask = .{ .color_attachment_output_bit = true }, .dst_stage_mask = .{ .color_attachment_output_bit = true },
.dst_access_mask = .{ .memory_read_bit = true, .memory_write_bit = true }, .dst_access_mask = .{ .color_attachment_read_bit = true, .color_attachment_write_bit = true },
}, },
// Conversion from VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR // Conversion from VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
vk.SubpassDependency{ vk.SubpassDependency{
// Transition must happen after... // Transition must happen after...
.src_subpass = 0, .src_subpass = 0,
.src_stage_mask = .{ .color_attachment_output_bit = true }, .src_stage_mask = .{ .color_attachment_output_bit = true },
.src_access_mask = .{ .memory_read_bit = true, .memory_write_bit = true }, .src_access_mask = .{ .color_attachment_read_bit = true, .color_attachment_write_bit = true },
// But must happen before... // But must happen before...
.dst_subpass = vk.SUBPASS_EXTERNAL, .dst_subpass = vk.SUBPASS_EXTERNAL,
.dst_stage_mask = .{ .bottom_of_pipe_bit = true }, .dst_stage_mask = .{ .bottom_of_pipe_bit = true },
@ -1170,10 +1181,9 @@ pub const VulkanRenderer = struct {
}, },
.p_clear_values = &clear_values, // List of clear values .p_clear_values = &clear_values, // List of clear values
.clear_value_count = @intCast(clear_values.len), .clear_value_count = @intCast(clear_values.len),
.framebuffer = undefined, .framebuffer = self.swapchain_framebuffers[current_image],
}; };
render_pass_begin_info.framebuffer = self.swapchain_framebuffers[current_image];
const command_buffer = self.command_buffers[current_image]; const command_buffer = self.command_buffers[current_image];
// Start recording commands to command buffer // Start recording commands to command buffer
@ -1506,8 +1516,8 @@ pub const VulkanRenderer = struct {
// Create staging buffer to hold loaded data, ready to copy to device // Create staging buffer to hold loaded data, ready to copy to device
var image_staging_buffer: vk.Buffer = undefined; var image_staging_buffer: vk.Buffer = undefined;
defer self.device.destroyBuffer(image_staging_buffer, null);
var image_staging_buffer_memory: vk.DeviceMemory = undefined; var image_staging_buffer_memory: vk.DeviceMemory = undefined;
defer self.device.destroyBuffer(image_staging_buffer, null);
defer self.device.freeMemory(image_staging_buffer_memory, null); defer self.device.freeMemory(image_staging_buffer_memory, null);
try Utilities.createBuffer( try Utilities.createBuffer(
@ -1521,10 +1531,16 @@ pub const VulkanRenderer = struct {
&image_staging_buffer_memory, &image_staging_buffer_memory,
); );
std.debug.print("Image size: {d}\n", .{image_size});
// Copy data to staging buffer // Copy data to staging buffer
const data = try self.device.mapMemory(image_staging_buffer_memory, 0, image_size, .{}); const data = try self.device.mapMemory(image_staging_buffer_memory, 0, image_size, .{});
const image_data: *[]const u8 = @ptrCast(@alignCast(data)); const image_data: [*]u8 = @ptrCast(@alignCast(data));
image_data.* = image;
// std.debug.print("Data len: {d}\tImage len:{d}\n", .{ image_data., image.len });
// image_data.* = image;
// std.mem.copyForwards(u8, image_data, image);
@memcpy(image_data, image[0..]);
self.device.unmapMemory(image_staging_buffer_memory); self.device.unmapMemory(image_staging_buffer_memory);
// Create image to hold final texture // Create image to hold final texture
@ -1532,7 +1548,7 @@ pub const VulkanRenderer = struct {
const tex_image: vk.Image = try self.createImage( const tex_image: vk.Image = try self.createImage(
width, width,
height, height,
.r8g8b8a8_unorm, .r8g8b8a8_srgb,
.optimal, .optimal,
.{ .transfer_dst_bit = true, .sampled_bit = true }, .{ .transfer_dst_bit = true, .sampled_bit = true },
.{ .device_local_bit = true }, .{ .device_local_bit = true },
@ -1585,7 +1601,7 @@ pub const VulkanRenderer = struct {
// Create image view and add to list // Create image view and add to list
const image_view = try self.createImageView( const image_view = try self.createImageView(
self.texture_images.items[texture_image_loc], self.texture_images.items[texture_image_loc],
.r8g8b8a8_unorm, .r8g8b8a8_srgb,
.{ .color_bit = true }, .{ .color_bit = true },
); );
@ -1640,23 +1656,19 @@ pub const VulkanRenderer = struct {
fn loadTextureFile(self: *Self, file_name: []const u8, width: *u32, height: *u32, image_size: *vk.DeviceSize) ![]const u8 { fn loadTextureFile(self: *Self, file_name: []const u8, width: *u32, height: *u32, image_size: *vk.DeviceSize) ![]const u8 {
const path_concat = [2][]const u8{ "./assets/textures/", file_name }; const path_concat = [2][]const u8{ "./assets/textures/", file_name };
const path = try std.mem.concat(self.allocator, u8, &path_concat); const path = try std.mem.concatWithSentinel(self.allocator, u8, &path_concat, 0);
defer self.allocator.free(path); defer self.allocator.free(path);
var image = try img.Image.fromFilePath(self.allocator, path); const image = try img.Image.loadFromFile(path, 0);
defer image.deinit(); try self.image_files.append(image);
if (!image.pixelFormat().isRgba()) { width.* = image.width;
try image.convert(.rgba32); height.* = image.height;
}
width.* = @intCast(image.width);
height.* = @intCast(image.height);
// Calculate image size using given and known data // Calculate image size using given and known data
image_size.* = width.* * height.* * 4; image_size.* = width.* * height.* * 4;
return image.rawBytes(); return image.data;
} }
}; };
@ -1666,13 +1678,13 @@ fn chooseBestSurfaceFormat(formats: []vk.SurfaceFormatKHR) vk.SurfaceFormatKHR {
// If only one format available and is undefined, then this means all formats are available // If only one format available and is undefined, then this means all formats are available
if (formats.len == 1 and formats[0].format == vk.Format.undefined) { if (formats.len == 1 and formats[0].format == vk.Format.undefined) {
return .{ return .{
.format = vk.Format.r8g8b8a8_unorm, .format = vk.Format.r8g8b8a8_srgb,
.color_space = vk.ColorSpaceKHR.srgb_nonlinear_khr, .color_space = vk.ColorSpaceKHR.srgb_nonlinear_khr,
}; };
} }
for (formats) |format| { for (formats) |format| {
if ((format.format == vk.Format.r8g8b8a8_unorm or format.format == vk.Format.b8g8r8a8_unorm) and format.color_space == vk.ColorSpaceKHR.srgb_nonlinear_khr) { if ((format.format == vk.Format.r8g8b8a8_srgb or format.format == vk.Format.b8g8r8a8_srgb) and format.color_space == vk.ColorSpaceKHR.srgb_nonlinear_khr) {
return format; return format;
} }
} }