Texture loading

This commit is contained in:
duck
2025-08-11 12:45:34 +05:00
parent 3500ad97b5
commit b73f0d446a
10 changed files with 153 additions and 45 deletions

View File

@@ -50,8 +50,7 @@ fn stepBuildMain(
exe.root_module.addImport("sdl", sdl_module); exe.root_module.addImport("sdl", sdl_module);
exe.step.dependOn(sdl_step); exe.step.dependOn(sdl_step);
const c_module = cModule(b, target, optimize); exe.addIncludePath(b.path("lib/clibs"));
exe.root_module.addImport("c", c_module);
return exe; return exe;
} }
@@ -107,21 +106,6 @@ fn stepSdlModule(
}; };
} }
fn cModule(
b: *Build,
target: Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
) *Build.Module {
const c_module = b.addModule("c", .{
.root_source_file = b.path("lib/c.zig"),
.link_libc = true,
.target = target,
.optimize = optimize,
});
c_module.addIncludePath(b.path("lib/clibs"));
return c_module;
}
fn stepCopyData( fn stepCopyData(
b: *Build, b: *Build,
target: Build.ResolvedTarget, target: Build.ResolvedTarget,

BIN
data/wawa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

View File

@@ -1,3 +0,0 @@
pub usingnamespace @cImport({
@cInclude("stb_image.h");
});

104
src/assets.zig Normal file
View File

@@ -0,0 +1,104 @@
const std = @import("std");
const sdl = @import("sdl");
const err = @import("error.zig");
const c = @import("c.zig");
const comp = @import("components.zig");
const Game = @import("game.zig");
const Graphics = @import("graphics.zig");
const Assets = @This();
const Storage = comp.Storage(Asset, .{});
var storage: Storage = undefined;
pub const AssetType = enum {
texture,
};
pub const Texture = struct {
handle: Storage.Key,
};
const Asset = struct {
path: []const u8,
data: union(AssetType) {
texture: AssetTexture,
},
};
pub const AssetTexture = struct {
texture: *sdl.GPUTexture,
sampler: *sdl.GPUSampler,
};
pub fn init() void {
Assets.storage = Storage.init();
}
pub fn deinit() void {
var iter = Assets.storage.iter();
while (iter.next()) |asset| {
Assets.freeAsset(asset);
}
Assets.storage.deinit();
}
pub fn load(comptime asset_type: AssetType, path: []const u8) typeFromAssetType(asset_type) {
switch (asset_type) {
.texture => {
const data = loadFile(Game.alloc, path) catch |e| err.file(e, path);
var x: i32 = undefined;
var y: i32 = undefined;
var z: i32 = undefined;
const image = c.stbi_load_from_memory(@ptrCast(data), @intCast(data.len), &x, &y, &z, 4);
Game.alloc.free(data);
if (image == null) err.stbi();
const image_slice = image[0..@intCast(x * y * z)];
const texture, const sampler = Graphics.loadTexture(@intCast(x), @intCast(y), image_slice);
c.stbi_image_free(image);
return .{ .handle = Assets.storage.add(.{
.path = path,
.data = .{ .texture = .{
.texture = texture,
.sampler = sampler,
} },
}) };
},
}
}
pub fn free(asset: anytype) void {
if (Assets.storage.free(asset.handle)) |stored| {
freeAsset(stored);
}
}
pub fn freeAsset(asset: *Asset) void {
switch (asset.data) {
.texture => {
Graphics.unloadTexture(asset.data.texture.texture, asset.data.texture.sampler);
},
}
}
pub fn get(asset: anytype) ?assetTypeFromType(@TypeOf(asset)) {
if (Assets.storage.get(asset.handle)) |stored| {
switch (@TypeOf(asset)) {
Texture => {
return stored.data.texture;
},
else => @compileError("Cannot get asset of type " ++ @typeName(@TypeOf(asset))),
}
}
unreachable;
}
fn loadFile(alloc: std.mem.Allocator, path: []const u8) ![]u8 {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
return file.readToEndAlloc(alloc, std.math.maxInt(i32));
}
fn typeFromAssetType(comptime asset_type: AssetType) type {
return switch (asset_type) {
.texture => Texture,
};
}
fn assetTypeFromType(comptime T: type) type {
return switch (T) {
Texture => AssetTexture,
else => unreachable,
};
}

8
src/c.zig Normal file
View File

@@ -0,0 +1,8 @@
pub usingnamespace @cImport({
@cDefine("STBI_NO_GIF", "1");
@cDefine("STBI_NO_JPEG", "1");
@cDefine("STBI_NO_HDR", "1");
@cDefine("STBI_NO_TGA", "1");
@cDefine("STB_IMAGE_IMPLEMENTATION", "1");
@cInclude("stb_image.h");
});

View File

@@ -51,8 +51,12 @@ pub fn Storage(comptime T: type, comptime options: StorageOptions) type {
} }
return null; return null;
} }
pub fn free(self: *Self, key: Key) bool { pub fn free(self: *Self, key: Key) ?*T {
self.components.items[key.cell].count -= 1; self.components.items[key.cell].count -= 1;
if (self.components.items[key.cell].count == 0) {
return &self.components.items[key.cell].component;
}
return null;
} }
pub fn iter(self: *Self) Iterator { pub fn iter(self: *Self) Iterator {
return .{ return .{

View File

@@ -14,3 +14,7 @@ const FileError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.Fil
pub fn file(err: FileError, path: []const u8) noreturn { pub fn file(err: FileError, path: []const u8) noreturn {
std.debug.panic("Error while reading \"{s}\": {any}", .{ path, err }); std.debug.panic("Error while reading \"{s}\": {any}", .{ path, err });
} }
pub fn stbi() noreturn {
std.debug.panic("STBI error!\n", .{});
}

View File

@@ -5,6 +5,7 @@ const err = @import("error.zig");
const Mouse = @import("mouse.zig"); const Mouse = @import("mouse.zig");
const Keyboard = @import("keyboard.zig"); const Keyboard = @import("keyboard.zig");
pub const Assets = @import("assets.zig");
pub const Graphics = @import("graphics.zig"); pub const Graphics = @import("graphics.zig");
pub const World = @import("world.zig"); pub const World = @import("world.zig");
@@ -28,6 +29,7 @@ pub fn init(game_alloc: std.mem.Allocator) void {
Game.keyboard = .{}; Game.keyboard = .{};
Game.mouse = .{ .x = 0, .y = 0, .dx = 0, .dy = 0 }; Game.mouse = .{ .x = 0, .y = 0, .dx = 0, .dy = 0 };
Graphics.create(); Graphics.create();
Assets.init();
World.initDebug(); World.initDebug();
} }
@@ -114,6 +116,7 @@ fn processEvents() void {
pub fn deinit() void { pub fn deinit() void {
World.deinit(); World.deinit();
Assets.deinit();
Graphics.destroy(); Graphics.destroy();
sdl.Quit(); sdl.Quit();
} }

View File

@@ -2,6 +2,7 @@ const std = @import("std");
const sdl = @import("sdl"); const sdl = @import("sdl");
const err = @import("error.zig"); const err = @import("error.zig");
const presets = @import("graphics/presets.zig"); const presets = @import("graphics/presets.zig");
const Assets = @import("assets.zig");
pub const Transform = @import("graphics/transform.zig"); pub const Transform = @import("graphics/transform.zig");
pub const Camera = @import("graphics/camera.zig"); pub const Camera = @import("graphics/camera.zig");
@@ -11,11 +12,6 @@ pub const Mesh = struct {
vertex_count: usize, vertex_count: usize,
}; };
pub const Texture = struct {
texture: *sdl.GPUTexture,
sampler: *sdl.GPUSampler,
};
var window: *sdl.Window = undefined; var window: *sdl.Window = undefined;
var renderer: *sdl.Renderer = undefined; var renderer: *sdl.Renderer = undefined;
var device: *sdl.GPUDevice = undefined; var device: *sdl.GPUDevice = undefined;
@@ -45,7 +41,7 @@ var to_resize: ?[2]u32 = null;
const VERTEX_BUFFER_DEFAULT_CAPACITY = 1024; const VERTEX_BUFFER_DEFAULT_CAPACITY = 1024;
const VERTEX_BUFFER_GROWTH_MULTIPLIER = 2; const VERTEX_BUFFER_GROWTH_MULTIPLIER = 2;
const TRANSFER_BUFFER_DEFAULT_CAPACITY = 1024; const TRANSFER_BUFFER_DEFAULT_CAPACITY = 4096;
const BYTES_PER_VERTEX = 5 * 4; const BYTES_PER_VERTEX = 5 * 4;
const Graphics = @This(); const Graphics = @This();
@@ -193,8 +189,9 @@ pub fn destroy() void {
sdl.DestroyGPUDevice(Graphics.device); sdl.DestroyGPUDevice(Graphics.device);
} }
pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) Texture { pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) struct { *sdl.GPUTexture, *sdl.GPUSampler } {
const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window); // const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
const target_format = sdl.GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
const texture = sdl.CreateGPUTexture(Graphics.device, &sdl.GPUTextureCreateInfo{ const texture = sdl.CreateGPUTexture(Graphics.device, &sdl.GPUTextureCreateInfo{
.format = target_format, .format = target_format,
@@ -242,15 +239,15 @@ pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) Texture {
.min_filter = sdl.GPU_FILTER_LINEAR, .min_filter = sdl.GPU_FILTER_LINEAR,
}) orelse err.sdl(); }) orelse err.sdl();
return Texture{ return .{
.texture = texture, texture,
.sampler = sampler, sampler,
}; };
} }
pub fn unloadTexture(texture: Texture) void { pub fn unloadTexture(texture: *sdl.GPUTexture, sampler: *sdl.GPUSampler) void {
sdl.ReleaseGPUSampler(Graphics.device, texture.sampler); sdl.ReleaseGPUSampler(Graphics.device, sampler);
sdl.ReleaseGPUTexture(Graphics.device, texture.texture); sdl.ReleaseGPUTexture(Graphics.device, texture);
} }
pub fn loadMesh(mesh_bytes: []const u8) Mesh { pub fn loadMesh(mesh_bytes: []const u8) Mesh {
@@ -388,13 +385,14 @@ pub fn beginDraw() bool {
return true; return true;
} }
pub fn drawMesh(mesh: Mesh, texture: Texture, transform: Transform) void { pub fn drawMesh(mesh: Mesh, texture: Assets.Texture, transform: Transform) void {
if (Graphics.render_pass == null) return; if (Graphics.render_pass == null) return;
const asset_texture = Assets.get(texture) orelse return;
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &transform.matrix(), 16 * 4); sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &transform.matrix(), 16 * 4);
sdl.BindGPUFragmentSamplers(Graphics.render_pass, 0, &sdl.GPUTextureSamplerBinding{ sdl.BindGPUFragmentSamplers(Graphics.render_pass, 0, &sdl.GPUTextureSamplerBinding{
.texture = texture.texture, .texture = asset_texture.texture,
.sampler = texture.sampler, .sampler = asset_texture.sampler,
}, 1); }, 1);
sdl.DrawGPUPrimitives(Graphics.render_pass, @intCast(mesh.vertex_count), 1, @intCast(mesh.vertex_start), 0); sdl.DrawGPUPrimitives(Graphics.render_pass, @intCast(mesh.vertex_count), 1, @intCast(mesh.vertex_start), 0);
} }

View File

@@ -1,4 +1,5 @@
const Graphics = @import("graphics.zig"); const Graphics = @import("graphics.zig");
const Assets = @import("assets.zig");
const Entity = @import("entity.zig"); const Entity = @import("entity.zig");
const Time = @import("time.zig"); const Time = @import("time.zig");
const comp = @import("components.zig"); const comp = @import("components.zig");
@@ -9,7 +10,7 @@ var entities: comp.Storage(Entity, .{}) = undefined;
pub var plane_mesh: Graphics.Mesh = undefined; pub var plane_mesh: Graphics.Mesh = undefined;
pub var cube_mesh: Graphics.Mesh = undefined; pub var cube_mesh: Graphics.Mesh = undefined;
pub var texture: Graphics.Texture = undefined; pub var texture: Assets.Texture = undefined;
const World = @This(); const World = @This();
pub fn initDebug() void { pub fn initDebug() void {
@@ -35,13 +36,13 @@ pub fn initDebug() void {
time = Time.ZERO; time = Time.ZERO;
World.plane_mesh = Graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA)); World.plane_mesh = Graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA));
World.cube_mesh = Graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA)); World.cube_mesh = Graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA));
World.texture = Graphics.loadTexture(2, 2, @ptrCast(&TEXTURE_DATA)); World.texture = Assets.load(.texture, "data/wawa.png");
} }
pub fn deinit() void { pub fn deinit() void {
Graphics.unloadMesh(World.plane_mesh); Graphics.unloadMesh(World.plane_mesh);
Graphics.unloadMesh(World.cube_mesh); Graphics.unloadMesh(World.cube_mesh);
Graphics.unloadTexture(World.texture); Assets.free(World.texture);
World.entities.deinit(); World.entities.deinit();
} }
@@ -99,30 +100,35 @@ const CUBE_MESH_DATA = [_]f32{
0.5, -0.5, -0.5, 0.0, 0.0, 0.5, -0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 0.0, -0.5, -0.5, -0.5, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0, 0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0, 0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0, 0.5, 0.5, 0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0, 0.5, -0.5, -0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 0.0, 0.0, 0.5, -0.5, 0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0, 0.5, -0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0, 0.5, 0.5, 0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 1.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0, -0.5, 0.5, 0.5, 0.0, 0.0,
0.5, -0.5, 0.5, 0.0, 0.0, 0.5, -0.5, 0.5, 1.0, 1.0,
-0.5, -0.5, 0.5, 0.0, 0.0, -0.5, -0.5, 0.5, 0.0, 1.0,
0.5, -0.5, 0.5, 0.0, 0.0, 0.5, -0.5, 0.5, 1.0, 1.0,
-0.5, 0.5, 0.5, 0.0, 0.0, -0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0, -0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0, -0.5, 0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0, -0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 0.0, -0.5, -0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0, -0.5, -0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0, -0.5, 0.5, -0.5, 0.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 0.0, -0.5, 0.5, 0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0, 0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0, -0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0, 0.5, 0.5, -0.5, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 0.0, -0.5, 0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0, 0.5, 0.5, 0.5, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, 0.0, -0.5, -0.5, -0.5, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0, 0.5, -0.5, -0.5, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0, -0.5, -0.5, 0.5, 0.0, 0.0,