From 6f933449a15b06b578b5bdd5c37467e1923208f9 Mon Sep 17 00:00:00 2001 From: duck Date: Sun, 31 Aug 2025 04:12:10 +0500 Subject: [PATCH] Iterative texture loading --- src/assets.zig | 8 +++- src/assets/texture.zig | 94 ++++++++++++++++++++++++++++++++++++----- src/graphics.zig | 95 ++++++++++++------------------------------ 3 files changed, 117 insertions(+), 80 deletions(-) diff --git a/src/assets.zig b/src/assets.zig index 7816609..1c0bb8d 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -47,6 +47,9 @@ const AssetContext = struct { pub const LoadError = error{ DependencyError, + ParsingError, + SdlError, + FileTooBig, } || std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError; pub const AssetType = enum { @@ -129,8 +132,11 @@ pub fn AssetContainer(comptime T: type) type { // TODO: Do something else while the asset is locked? self.asset_pointer.mutex.lock(); defer self.asset_pointer.mutex.unlock(); - self.asset_pointer.load(Game.alloc); self.last_state = self.asset_pointer.state; + if (self.last_state == .not_loaded) { + self.asset_pointer.load(Game.alloc); + self.last_state = self.asset_pointer.state; + } if (self.last_state == .loaded) { self.data_pointer = @alignCast(@ptrCast(self.asset_pointer.data)); } diff --git a/src/assets/texture.zig b/src/assets/texture.zig index ecb0959..ea9e066 100644 --- a/src/assets/texture.zig +++ b/src/assets/texture.zig @@ -11,17 +11,88 @@ sampler: *sdl.GPUSampler, pub fn load(path: []const u8, alloc: std.mem.Allocator) Assets.LoadError!@This() { _ = alloc; var file = Assets.load(.file, path); - defer Assets.free(file); - const data = (try file.getSync()).bytes; + const data = (file.getSync() catch return error.DependencyError).bytes; + + var width: u32 = undefined; + var height: u32 = undefined; + var channels: u32 = undefined; + const image = c.stbi_load_from_memory( + @ptrCast(data), + @intCast(data.len), + @ptrCast(&width), + @ptrCast(&height), + @ptrCast(&channels), + 4, + ); + Assets.free(file); + if (image == null) return error.ParsingError; + defer c.stbi_image_free(image); + const image_slice = image[0..@intCast(width * height * channels)]; + + if (width > 8192 or height > 8192) return error.FileTooBig; + + const target_format = sdl.GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; + const bytes_per_pixel = 4; + + const texture = Graphics.createTexture( + width, + height, + target_format, + sdl.GPU_TEXTUREUSAGE_SAMPLER | sdl.GPU_TEXTUREUSAGE_COLOR_TARGET, + Graphics.MIP_LEVEL, + ); + errdefer Graphics.freeTexture(texture); + + const transfer_buffer_capacity = Graphics.TRANSFER_BUFFER_DEFAULT_CAPACITY; + const transfer_buffer = sdl.CreateGPUTransferBuffer(Graphics.device, &.{ + .size = transfer_buffer_capacity, + .usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD, + }) orelse return error.SdlError; + defer sdl.ReleaseGPUTransferBuffer(Graphics.device, transfer_buffer); + + var rows_uploaded: u32 = 0; + while (rows_uploaded < height) { + const rows_to_upload = @min(height - rows_uploaded, transfer_buffer_capacity / width / bytes_per_pixel); + if (rows_to_upload == 0) return error.FileTooBig; + + const command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse return error.SdlError; + { + errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer); + const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return error.SdlError; + defer sdl.EndGPUCopyPass(copy_pass); + + const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, transfer_buffer, false) orelse err.sdl()); + @memcpy(map, image_slice[(rows_uploaded * width * bytes_per_pixel)..((rows_uploaded + rows_to_upload) * width * bytes_per_pixel)]); + sdl.UnmapGPUTransferBuffer(Graphics.device, transfer_buffer); + + sdl.UploadToGPUTexture(copy_pass, &sdl.GPUTextureTransferInfo{ + .offset = 0, + .pixels_per_row = width, + .rows_per_layer = rows_to_upload, + .transfer_buffer = transfer_buffer, + }, &sdl.GPUTextureRegion{ + .texture = texture, + .mip_level = 0, + .layer = 0, + .x = 0, + .y = rows_uploaded, + .z = 0, + .w = width, + .h = rows_to_upload, + .d = 1, + }, false); + } + rows_uploaded += rows_to_upload; + if (rows_to_upload == height) { + sdl.GenerateMipmapsForGPUTexture(command_buffer, texture); + } + const fence = sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return error.SdlError; + defer sdl.ReleaseGPUFence(Graphics.device, fence); + if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) return error.SdlError; + } + + const sampler = Graphics.createSampler(); - 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); - 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 .{ .texture = texture, .sampler = sampler, @@ -30,5 +101,6 @@ pub fn load(path: []const u8, alloc: std.mem.Allocator) Assets.LoadError!@This() pub fn unload(self: @This(), alloc: std.mem.Allocator) void { _ = alloc; - Graphics.unloadTexture(self.texture, self.sampler); + Graphics.freeTexture(self.texture); + Graphics.freeSampler(self.sampler); } diff --git a/src/graphics.zig b/src/graphics.zig index 14b0f1c..97c786b 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -12,8 +12,8 @@ pub const Mesh = struct { vertex_count: usize, }; -var window: *sdl.Window = undefined; -var device: *sdl.GPUDevice = undefined; +pub var window: *sdl.Window = undefined; +pub var device: *sdl.GPUDevice = undefined; /// Only available while drawing var command_buffer: ?*sdl.GPUCommandBuffer = null; var render_pass: ?*sdl.GPURenderPass = null; @@ -43,10 +43,10 @@ var to_resize: ?[2]u32 = null; const VERTEX_BUFFER_DEFAULT_CAPACITY = 1024; const VERTEX_BUFFER_GROWTH_MULTIPLIER = 2; -const TRANSFER_BUFFER_DEFAULT_CAPACITY = 4096 * 1024; const BYTES_PER_VERTEX = 5 * 4; const DEPTH_FORMAT = sdl.GPU_TEXTUREFORMAT_D32_FLOAT; -const MIP_LEVEL = 4; +pub const TRANSFER_BUFFER_DEFAULT_CAPACITY = 256 * 1024; +pub const MIP_LEVEL = 4; const Graphics = @This(); pub fn create() void { @@ -200,69 +200,6 @@ pub fn destroy() void { sdl.DestroyGPUDevice(Graphics.device); } -pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) struct { *sdl.GPUTexture, *sdl.GPUSampler } { - const target_format = sdl.GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; - - const texture = Graphics.createTexture( - width, - height, - target_format, - sdl.GPU_TEXTUREUSAGE_SAMPLER | sdl.GPU_TEXTUREUSAGE_COLOR_TARGET, - MIP_LEVEL, - ); - - const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl(); - { - const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer) orelse err.sdl(); - defer sdl.EndGPUCopyPass(copy_pass); - - const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, true) orelse err.sdl()); - @memcpy(map, texture_bytes); - sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer); - - sdl.UploadToGPUTexture(copy_pass, &sdl.GPUTextureTransferInfo{ - .offset = 0, - .pixels_per_row = width, - .rows_per_layer = height, - .transfer_buffer = Graphics.transfer_buffer, - }, &sdl.GPUTextureRegion{ - .texture = texture, - .mip_level = 0, - .layer = 0, - .x = 0, - .y = 0, - .z = 0, - .w = width, - .h = height, - .d = 1, - }, false); - } - sdl.GenerateMipmapsForGPUTexture(temp_command_buffer, texture); - if (!sdl.SubmitGPUCommandBuffer(temp_command_buffer)) err.sdl(); - - const sampler = sdl.CreateGPUSampler(Graphics.device, &sdl.GPUSamplerCreateInfo{ - .address_mode_u = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, - .address_mode_v = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, - .address_mode_w = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, - .mag_filter = sdl.GPU_FILTER_NEAREST, - .min_filter = sdl.GPU_FILTER_LINEAR, - .mipmap_mode = sdl.GPU_SAMPLERMIPMAPMODE_LINEAR, - .min_lod = 0, - .max_lod = 16, - .mip_lod_bias = -2, - }) orelse err.sdl(); - - return .{ - texture, - sampler, - }; -} - -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 { std.debug.assert(mesh_bytes.len < Graphics.transfer_buffer_capacity); @@ -471,7 +408,7 @@ fn loadShader(path: []const u8, info: sdl.GPUShaderCreateInfo) *sdl.GPUShader { return sdl.CreateGPUShader(device, &updated_info) orelse err.sdl(); } -fn createTexture(width: u32, height: u32, format: c_uint, usage: c_uint, mip_level: u32) *sdl.GPUTexture { +pub fn createTexture(width: u32, height: u32, format: c_uint, usage: c_uint, mip_level: u32) *sdl.GPUTexture { return sdl.CreateGPUTexture(device, &.{ .format = format, .layer_count_or_depth = 1, @@ -483,6 +420,28 @@ fn createTexture(width: u32, height: u32, format: c_uint, usage: c_uint, mip_lev }) orelse err.sdl(); } +pub fn freeTexture(texture: *sdl.GPUTexture) void { + sdl.ReleaseGPUTexture(Graphics.device, texture); +} + +pub fn createSampler() *sdl.GPUSampler { + return sdl.CreateGPUSampler(Graphics.device, &sdl.GPUSamplerCreateInfo{ + .address_mode_u = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, + .address_mode_v = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, + .address_mode_w = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, + .mag_filter = sdl.GPU_FILTER_NEAREST, + .min_filter = sdl.GPU_FILTER_LINEAR, + .mipmap_mode = sdl.GPU_SAMPLERMIPMAPMODE_LINEAR, + .min_lod = 0, + .max_lod = 16, + .mip_lod_bias = -2, + }) orelse err.sdl(); +} + +pub fn freeSampler(sampler: *sdl.GPUSampler) void { + sdl.ReleaseGPUSampler(Graphics.device, sampler); +} + fn resetTextures(width: u32, height: u32) void { sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture); Graphics.depth_texture = createTexture(