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

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;
}
pub fn free(self: *Self, key: Key) bool {
pub fn free(self: *Self, key: Key) ?*T {
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 {
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 {
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 Keyboard = @import("keyboard.zig");
pub const Assets = @import("assets.zig");
pub const Graphics = @import("graphics.zig");
pub const World = @import("world.zig");
@@ -28,6 +29,7 @@ pub fn init(game_alloc: std.mem.Allocator) void {
Game.keyboard = .{};
Game.mouse = .{ .x = 0, .y = 0, .dx = 0, .dy = 0 };
Graphics.create();
Assets.init();
World.initDebug();
}
@@ -114,6 +116,7 @@ fn processEvents() void {
pub fn deinit() void {
World.deinit();
Assets.deinit();
Graphics.destroy();
sdl.Quit();
}

View File

@@ -2,6 +2,7 @@ const std = @import("std");
const sdl = @import("sdl");
const err = @import("error.zig");
const presets = @import("graphics/presets.zig");
const Assets = @import("assets.zig");
pub const Transform = @import("graphics/transform.zig");
pub const Camera = @import("graphics/camera.zig");
@@ -11,11 +12,6 @@ pub const Mesh = struct {
vertex_count: usize,
};
pub const Texture = struct {
texture: *sdl.GPUTexture,
sampler: *sdl.GPUSampler,
};
var window: *sdl.Window = undefined;
var renderer: *sdl.Renderer = 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_GROWTH_MULTIPLIER = 2;
const TRANSFER_BUFFER_DEFAULT_CAPACITY = 1024;
const TRANSFER_BUFFER_DEFAULT_CAPACITY = 4096;
const BYTES_PER_VERTEX = 5 * 4;
const Graphics = @This();
@@ -193,8 +189,9 @@ pub fn destroy() void {
sdl.DestroyGPUDevice(Graphics.device);
}
pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) Texture {
const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
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.GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
const texture = sdl.CreateGPUTexture(Graphics.device, &sdl.GPUTextureCreateInfo{
.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,
}) orelse err.sdl();
return Texture{
.texture = texture,
.sampler = sampler,
return .{
texture,
sampler,
};
}
pub fn unloadTexture(texture: Texture) void {
sdl.ReleaseGPUSampler(Graphics.device, texture.sampler);
sdl.ReleaseGPUTexture(Graphics.device, texture.texture);
pub fn unloadTexture(texture: *sdl.GPUTexture, sampler: *sdl.GPUSampler) void {
sdl.ReleaseGPUSampler(Graphics.device, sampler);
sdl.ReleaseGPUTexture(Graphics.device, texture);
}
pub fn loadMesh(mesh_bytes: []const u8) Mesh {
@@ -388,13 +385,14 @@ pub fn beginDraw() bool {
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;
const asset_texture = Assets.get(texture) orelse return;
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &transform.matrix(), 16 * 4);
sdl.BindGPUFragmentSamplers(Graphics.render_pass, 0, &sdl.GPUTextureSamplerBinding{
.texture = texture.texture,
.sampler = texture.sampler,
.texture = asset_texture.texture,
.sampler = asset_texture.sampler,
}, 1);
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 Assets = @import("assets.zig");
const Entity = @import("entity.zig");
const Time = @import("time.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 cube_mesh: Graphics.Mesh = undefined;
pub var texture: Graphics.Texture = undefined;
pub var texture: Assets.Texture = undefined;
const World = @This();
pub fn initDebug() void {
@@ -35,13 +36,13 @@ pub fn initDebug() void {
time = Time.ZERO;
World.plane_mesh = Graphics.loadMesh(@ptrCast(&PLANE_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 {
Graphics.unloadMesh(World.plane_mesh);
Graphics.unloadMesh(World.cube_mesh);
Graphics.unloadTexture(World.texture);
Assets.free(World.texture);
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, 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, 0.0, 0.0,
0.5, -0.5, 0.5, 1.0, 1.0,
-0.5, -0.5, 0.5, 0.0, 1.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,