From 0d4478acc6240ff8830691e5446eb7b90c464d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Gasi=C7=B9ski?= Date: Sun, 6 Oct 2024 12:27:09 +0200 Subject: [PATCH] WIP: Refactor mesh & texture --- src/Mesh.zig | 6 +- src/MeshModel.zig | 124 ++++++++++--------- src/Swapchain.zig | 81 ++----------- src/Texture.zig | 198 +++++++++++++++++++++++++++++++ src/assimp.zig | 50 +++++++- src/image.zig | 82 +++++++++++++ src/vulkan_renderer.zig | 256 ++++------------------------------------ 7 files changed, 431 insertions(+), 366 deletions(-) create mode 100644 src/Texture.zig create mode 100644 src/image.zig diff --git a/src/Mesh.zig b/src/Mesh.zig index 1f577e6..3766f1c 100644 --- a/src/Mesh.zig +++ b/src/Mesh.zig @@ -27,7 +27,8 @@ device: Device, allocator: std.mem.Allocator, -pub fn new( +pub fn create( + allocator: std.mem.Allocator, instance: Instance, pdev: vk.PhysicalDevice, device: Device, @@ -36,7 +37,6 @@ pub fn new( vertices: []const Vertex, indices: []const u32, tex_id: u32, - allocator: std.mem.Allocator, ) !Self { var self: Self = undefined; @@ -57,7 +57,7 @@ pub fn new( return self; } -pub fn destroyBuffers(self: Self) void { +pub fn destroy(self: Self) void { self.device.destroyBuffer(self.vertex_buffer, null); self.device.freeMemory(self.vertex_buffer_memory, null); diff --git a/src/MeshModel.zig b/src/MeshModel.zig index 258fedd..5636390 100644 --- a/src/MeshModel.zig +++ b/src/MeshModel.zig @@ -4,30 +4,92 @@ const zm = @import("zmath"); const ai = @import("assimp.zig").c; const Mesh = @import("Mesh.zig"); -const Device = @import("Context.zig").Device; +const Context = @import("Context.zig"); +const Device = Context.Device; const Instance = @import("Context.zig").Instance; const Vertex = @import("utilities.zig").Vertex; +const StringUtils = @import("string_utils.zig"); +const Texture = @import("Texture.zig"); const Self = @This(); allocator: std.mem.Allocator, mesh_list: std.ArrayList(Mesh), +textures: std.ArrayList(Texture), model: zm.Mat, -pub fn new(allocator: std.mem.Allocator, mesh_list: std.ArrayList(Mesh)) Self { +sampler_descriptor_sets: std.ArrayList(vk.DescriptorSet), + +pub fn new( + allocator: std.mem.Allocator, + ctx: Context, + graphics_command_pool: vk.CommandPool, + texture_sampler: vk.Sampler, + model_file: []const u8, +) Self { var new_mesh_model: Self = undefined; new_mesh_model.allocator = allocator; - new_mesh_model.mesh_list = mesh_list; new_mesh_model.model = zm.identity(); + new_mesh_model.sampler_descriptor_sets = try std.ArrayList(vk.DescriptorSet) + .init(allocator); + + const path = try StringUtils.concat("assets/models/", model_file, allocator); + defer allocator.free(path); + + // Import model scene + const scene = ai.aiImportFile( + path.ptr, + ai.aiProcess_Triangulate | ai.aiProcess_FlipUVs | ai.aiProcess_JoinIdenticalVertices, + ); + defer ai.aiReleaseImport(scene); + + // Get array of all materials with 1:1 ID placement + const texture_names = try ai.loadMaterials(allocator, scene); + defer { + for (0..texture_names.items.len) |i| { + if (texture_names.items[i]) |texture_name| { + allocator.free(texture_name); + } + } + texture_names.deinit(); + } + + // Conversion from the material list IDs to our descriptor array IDs + new_mesh_model.textures = try std.ArrayList(Texture).initCapacity(allocator, texture_names.items.len); + + // Loop over texture names and create textures for them + for (texture_names.items) |texture_name| { + if (texture_name != null) { + // Create texture and set value to index of new texture + new_mesh_model.textures.appendAssumeCapacity(try Texture.create(texture_name.?)); + } else { + // If material had no texture, set to 0 to indicate no texture. Texture 0 will be reserver for a default texture + // TODO Put the default texture somewhere else where it's shared + new_mesh_model.textures.appendAssumeCapacity(try Texture.create("giraffe.png")); + } + } + + // Load in all our meshes + new_mesh_model.mesh_list = try loadNode( + allocator, + ctx.instance, + ctx.physical_device, + ctx.device, + ctx.graphics_queue.handle, + graphics_command_pool, + scene.*.mRootNode, + scene, + ); + return new_mesh_model; } pub fn destroy(self: *Self) void { for (0..self.mesh_list.items.len) |i| { - self.mesh_list.items[i].destroyBuffers(); + self.mesh_list.items[i].destroy(); } self.mesh_list.deinit(); } @@ -40,50 +102,6 @@ pub fn getMesh(self: Self, idx: usize) !Mesh { return self.mesh_list.items[idx]; } -pub fn loadMaterials(allocator: std.mem.Allocator, scene: *const ai.aiScene) !std.ArrayList(?[]const u8) { - // Create 1:1 sized list of textures - var texture_list = try std.ArrayList(?[]const u8).initCapacity(allocator, scene.mNumMaterials); - - // Go through each material and copy its texture file name (if it exists) - for (0..scene.mNumMaterials) |i| { - // Get the material - const material = scene.mMaterials[i]; - - // Initialise the texture to empty string (will be replaced if the texture exists) - // try texture_list.append(""); - - // Check for diffuse texture (standard detail texture) - if (ai.aiGetMaterialTextureCount(material, ai.aiTextureType_DIFFUSE) != 0) { - // Get the path of the texture file - var path: ai.aiString = undefined; - if (ai.aiGetMaterialTexture( - material, - ai.aiTextureType_DIFFUSE, - 0, - &path, - null, - null, - null, - null, - null, - null, - ) == ai.AI_SUCCESS) { - // Cut of any directory information already present - var it = std.mem.splitBackwardsAny(u8, &path.data, "\\/"); - if (it.next()) |filename| { - texture_list.appendAssumeCapacity(try allocator.dupe(u8, filename)); - } - } else { - texture_list.appendAssumeCapacity(null); - } - } else { - texture_list.appendAssumeCapacity(null); - } - } - - return texture_list; -} - pub fn loadNode( allocator: std.mem.Allocator, instance: Instance, @@ -93,7 +111,6 @@ pub fn loadNode( transfer_command_pool: vk.CommandPool, node: *const ai.aiNode, scene: *const ai.aiScene, - mat_to_tex: []u32, ) !std.ArrayList(Mesh) { var mesh_list = std.ArrayList(Mesh).init(allocator); @@ -108,7 +125,6 @@ pub fn loadNode( transfer_queue, transfer_command_pool, scene.mMeshes[node.mMeshes[i]], - mat_to_tex, )); } @@ -123,7 +139,6 @@ pub fn loadNode( transfer_command_pool, node.mChildren[i], scene, - mat_to_tex, ); defer new_list.deinit(); @@ -141,7 +156,6 @@ pub fn loadMesh( transfer_queue: vk.Queue, transfer_command_pool: vk.CommandPool, mesh: *const ai.aiMesh, - mat_to_tex: []u32, ) !Mesh { var vertices = try std.ArrayList(Vertex).initCapacity(allocator, mesh.mNumVertices); var indices = std.ArrayList(u32).init(allocator); @@ -180,7 +194,8 @@ pub fn loadMesh( } } - return try Mesh.new( + return try Mesh.create( + allocator, instance, pdev, device, @@ -188,7 +203,6 @@ pub fn loadMesh( transfer_command_pool, vertices.items, indices.items, - mat_to_tex[mesh.mMaterialIndex], - allocator, + mesh.mMaterialIndex, ); } diff --git a/src/Swapchain.zig b/src/Swapchain.zig index 3eb6f37..7339a8c 100644 --- a/src/Swapchain.zig +++ b/src/Swapchain.zig @@ -6,6 +6,7 @@ const Context = @import("Context.zig"); const Instance = Context.Instance; const QueueUtils = @import("queue_utils.zig"); const Utilities = @import("utilities.zig"); +const Image = @import("image.zig"); pub const SwapchainDetails = struct { surface_capabilities: vk.SurfaceCapabilitiesKHR, @@ -109,83 +110,25 @@ pub fn create(allocator: std.mem.Allocator, context: Context) !Self { for (images, 0..) |image, i| { self.swapchain_images[i] = .{ .image = image, - .image_view = try self.createImageView(image, self.swapchain_image_format, .{ .color_bit = true }), + .image_view = try Image.createImageView(self.ctx, image, self.swapchain_image_format, .{ .color_bit = true }), }; } return self; } -pub fn createImage( - self: *Self, - width: u32, - height: u32, - format: vk.Format, - tiling: vk.ImageTiling, - use_flags: vk.ImageUsageFlags, - prop_flags: vk.MemoryPropertyFlags, - image_memory: *vk.DeviceMemory, -) !vk.Image { - // -- Create Image -- - const image_create_info: vk.ImageCreateInfo = .{ - .image_type = .@"2d", // Type of image (1D, 2D or 3D) - .extent = .{ - .width = width, // Width of image extent - .height = height, // Height of image extent - .depth = 1, // Depth of image (just 1, no 3D aspecct) - }, - .mip_levels = 1, // Number of mipmap levels - .array_layers = 1, // Number of level in image array - .format = format, // Format type of image - .tiling = tiling, // How image data should be tiled (arranged for optimal reading) - .initial_layout = .undefined, // Layout of image data on creation - .usage = use_flags, // Bit flags defining what image will be used for - .samples = .{ .@"1_bit" = true }, // Number of samples for multi-sampling - .sharing_mode = .exclusive, // Whether image can be shared between queues - }; +pub fn destroy(self: *Self) void { + for (self.swapchain_framebuffers) |framebuffer| { + self.ctx.device.destroyFramebuffer(framebuffer, null); + } + self.allocator.free(self.swapchain_framebuffers); - const image = try self.ctx.device.createImage(&image_create_info, null); + for (self.swapchain_images) |swapchain_image| { + self.ctx.device.destroyImageView(swapchain_image.image_view, null); + } + self.allocator.free(self.swapchain_images); - // -- Create memory for image -- - // Get memory requirements for a type of image - const memory_requirements = self.ctx.device.getImageMemoryRequirements(image); - - // Allocate memory using image requirements and user-defined properties - const memory_alloc_info: vk.MemoryAllocateInfo = .{ - .allocation_size = memory_requirements.size, - .memory_type_index = Utilities.findMemoryTypeIndex(self.ctx.physical_device, self.ctx.instance, memory_requirements.memory_type_bits, prop_flags), - }; - - image_memory.* = try self.ctx.device.allocateMemory(&memory_alloc_info, null); - - // Connect memory to image - try self.ctx.device.bindImageMemory(image, image_memory.*, 0); - - return image; -} - -pub fn createImageView(self: Self, image: vk.Image, format: vk.Format, aspect_flags: vk.ImageAspectFlags) !vk.ImageView { - const image_view_create_info: vk.ImageViewCreateInfo = .{ - .image = image, - .format = format, - .view_type = .@"2d", - .components = .{ - // Used for remapping rgba values to other rgba values - .r = .identity, - .g = .identity, - .b = .identity, - .a = .identity, - }, - .subresource_range = .{ - .aspect_mask = aspect_flags, // Which aspect of image to view (e.g.: colour, depth, stencil, etc...) - .base_mip_level = 0, // Start mipmap level to view from - .level_count = 1, // Number of mipmap levels to view - .base_array_layer = 0, // Start array level to view from - .layer_count = 1, // Number of array levels to view - }, - }; - - return try self.ctx.device.createImageView(&image_view_create_info, null); + self.ctx.device.destroySwapchainKHR(self.handle, null); } pub fn getSwapchainDetails(allocator: std.mem.Allocator, instance: Instance, pdev: vk.PhysicalDevice, surface: vk.SurfaceKHR) !SwapchainDetails { diff --git a/src/Texture.zig b/src/Texture.zig new file mode 100644 index 0000000..4c2af86 --- /dev/null +++ b/src/Texture.zig @@ -0,0 +1,198 @@ +const std = @import("std"); +const vk = @import("vulkan"); +const img = @import("zstbi"); + +const Context = @import("Context.zig"); +const Image = @import("image.zig"); +const Utilities = @import("utilities.zig"); + +const Self = @This(); + +allocator: std.mem.Allocator, + +ctx: Context, + +idx: u32, +texture_image: vk.Image, +texture_image_memory: vk.DeviceMemory, +texture_image_view: vk.ImageView, + +sampler_descriptor_set: vk.DescriptorSet, + +image_file: img.Image, + +pub fn create( + file_name: []const u8, + ctx: Context, + graphics_command_pool: vk.CommandPool, + texture_sampler: vk.Sampler, + sampler_set_layout: vk.DescriptorSetLayout, + sampler_descriptor_pool: vk.DescriptorPool, +) Self { + var self: Self = undefined; + + self.ctx = ctx; + + // Create texture image and get its location in the array + const texture_image_loc = try self.createTextureImage(file_name, graphics_command_pool); + + // Create image view + self.texture_image_view = try Image.createImageView( + ctx, + self.texture_images.items[texture_image_loc], + .r8g8b8a8_srgb, + .{ .color_bit = true }, + ); + + // Create texture descriptor + try self.createTextureDescriptor( + texture_sampler, + sampler_set_layout, + sampler_descriptor_pool, + ); + + // Return location of set with texture + return self; +} + +pub fn destroy(self: *Self) void {} + +fn createTextureImage( + self: *Self, + file_name: []const u8, + graphics_command_pool: vk.CommandPool, +) !u32 { + // Load image file + var width: u32 = undefined; + var height: u32 = undefined; + var image_size: vk.DeviceSize = undefined; + const image = try self.loadTextureFile(file_name, &width, &height, &image_size); + + // Create staging buffer to hold loaded data, ready to copy to device + var image_staging_buffer: vk.Buffer = undefined; + var image_staging_buffer_memory: vk.DeviceMemory = undefined; + defer self.ctx.device.destroyBuffer(image_staging_buffer, null); + defer self.ctx.device.freeMemory(image_staging_buffer_memory, null); + + try Utilities.createBuffer( + self.ctx.physical_device, + self.ctx.instance, + self.ctx.device, + image_size, + .{ .transfer_src_bit = true }, + .{ .host_visible_bit = true, .host_coherent_bit = true }, + &image_staging_buffer, + &image_staging_buffer_memory, + ); + + // Copy data to staging buffer + const data = try self.ctx.device.mapMemory(image_staging_buffer_memory, 0, image_size, .{}); + const image_data: [*]u8 = @ptrCast(@alignCast(data)); + + @memcpy(image_data, image[0..]); + self.ctx.device.unmapMemory(image_staging_buffer_memory); + + // Create image to hold final texture + var tex_image_memory: vk.DeviceMemory = undefined; + const tex_image = try Image.createImage( + self.ctx, + width, + height, + .r8g8b8a8_srgb, + .optimal, + .{ .transfer_dst_bit = true, .sampled_bit = true }, + .{ .device_local_bit = true }, + &tex_image_memory, + ); + + // Transition image to be DST for copy operation + try Utilities.transitionImageLayout( + self.ctx.device, + self.ctx.graphics_queue.handle, + graphics_command_pool, + tex_image, + .undefined, + .transfer_dst_optimal, + ); + + // Copy data to image + try Utilities.copyImageBuffer( + self.ctx.device, + self.ctx.graphics_queue.handle, + graphics_command_pool, + image_staging_buffer, + tex_image, + width, + height, + ); + + // Transition image to be shader readable for shader usage + try Utilities.transitionImageLayout( + self.ctx.device, + self.ctx.graphics_queue.handle, + graphics_command_pool, + tex_image, + .transfer_dst_optimal, + .shader_read_only_optimal, + ); + + self.texture_image = tex_image; + self.texture_image_memory = tex_image_memory; + + // Return index of new texture image + return @intCast(self.texture_images.items.len - 1); +} + +fn createTextureDescriptor( + self: *Self, + texture_sampler: vk.Sampler, + sampler_set_layout: vk.DescriptorSetLayout, + sampler_descriptor_pool: vk.DescriptorPool, +) !u32 { + // Descriptor set allocation info + const set_alloc_info: vk.DescriptorSetAllocateInfo = .{ + .descriptor_pool = sampler_descriptor_pool, + .descriptor_set_count = 1, + .p_set_layouts = @ptrCast(&sampler_set_layout), + }; + + // Allocate descriptor sets + try self.ctx.device.allocateDescriptorSets(&set_alloc_info, @ptrCast(&self.sampler_descriptor_set)); + + const image_info: vk.DescriptorImageInfo = .{ + .image_layout = .shader_read_only_optimal, // Image layout when in use + .image_view = self.texture_image_view, // Image to bind to set + .sampler = texture_sampler, // Sampler to use for set + }; + + // Descriptor write info + const descriptor_write: vk.WriteDescriptorSet = .{ + .dst_set = self.sampler_descriptor_set, + .dst_binding = 0, + .dst_array_element = 0, + .descriptor_type = .combined_image_sampler, + .descriptor_count = 1, + .p_image_info = @ptrCast(&image_info), + .p_buffer_info = undefined, + .p_texel_buffer_view = undefined, + }; + + // Update the new descriptor set + self.ctx.device.updateDescriptorSets(1, @ptrCast(&descriptor_write), 0, null); +} + +fn loadTextureFile(self: *Self, file_name: []const u8, width: *u32, height: *u32, image_size: *vk.DeviceSize) !void { + const path_concat = [2][]const u8{ "./assets/textures/", file_name }; + const path = try std.mem.concatWithSentinel(self.allocator, u8, &path_concat, 0); + defer self.allocator.free(path); + + const image = try img.Image.loadFromFile(path, 0); + + width.* = image.width; + height.* = image.height; + + // Calculate image size using given and known data + image_size.* = width.* * height.* * 4; + + self.image_file = image; +} diff --git a/src/assimp.zig b/src/assimp.zig index 5d17599..76e06f2 100644 --- a/src/assimp.zig +++ b/src/assimp.zig @@ -1,9 +1,53 @@ +const std = @import("std"); + pub const c = @cImport({ @cInclude("assimp/cimport.h"); @cInclude("assimp/scene.h"); @cInclude("assimp/postprocess.h"); }); -// pub fn importFile(path: [:0]const u8, flags: c_uint) *const c.aiScene { -// return c.aiImportFile(path.ptr, flags); -// } +/// Load the texture material names in a scene. +/// Don't forget to free each element after use. +pub fn loadMaterials(allocator: std.mem.Allocator, scene: *const c.aiScene) !std.ArrayList(?[]const u8) { + // Create 1:1 sized list of textures + var texture_list = try std.ArrayList(?[]const u8).initCapacity(allocator, scene.mNumMaterials); + + // Go through each material and copy its texture file name (if it exists) + for (0..scene.mNumMaterials) |i| { + // Get the material + const material = scene.mMaterials[i]; + + // Initialise the texture to empty string (will be replaced if the texture exists) + // try texture_list.append(""); + + // Check for diffuse texture (standard detail texture) + if (c.aiGetMaterialTextureCount(material, c.aiTextureType_DIFFUSE) != 0) { + // Get the path of the texture file + var path: c.aiString = undefined; + if (c.aiGetMaterialTexture( + material, + c.aiTextureType_DIFFUSE, + 0, + &path, + null, + null, + null, + null, + null, + null, + ) == c.AI_SUCCESS) { + // Cut of any directory information already present + var it = std.mem.splitBackwardsAny(u8, &path.data, "\\/"); + if (it.next()) |filename| { + texture_list.appendAssumeCapacity(try allocator.dupe(u8, filename)); + } + } else { + texture_list.appendAssumeCapacity(null); + } + } else { + texture_list.appendAssumeCapacity(null); + } + } + + return texture_list; +} diff --git a/src/image.zig b/src/image.zig new file mode 100644 index 0000000..168ad46 --- /dev/null +++ b/src/image.zig @@ -0,0 +1,82 @@ +const std = @import("std"); +const vk = @import("vulkan"); + +const Context = @import("Context.zig"); +const Utilities = @import("utilities.zig"); + +pub fn createImage( + ctx: Context, + width: u32, + height: u32, + format: vk.Format, + tiling: vk.ImageTiling, + use_flags: vk.ImageUsageFlags, + prop_flags: vk.MemoryPropertyFlags, + image_memory: *vk.DeviceMemory, +) !vk.Image { + // -- Create Image -- + const image_create_info: vk.ImageCreateInfo = .{ + .image_type = .@"2d", // Type of image (1D, 2D or 3D) + .extent = .{ + .width = width, // Width of image extent + .height = height, // Height of image extent + .depth = 1, // Depth of image (just 1, no 3D aspecct) + }, + .mip_levels = 1, // Number of mipmap levels + .array_layers = 1, // Number of level in image array + .format = format, // Format type of image + .tiling = tiling, // How image data should be tiled (arranged for optimal reading) + .initial_layout = .undefined, // Layout of image data on creation + .usage = use_flags, // Bit flags defining what image will be used for + .samples = .{ .@"1_bit" = true }, // Number of samples for multi-sampling + .sharing_mode = .exclusive, // Whether image can be shared between queues + }; + + const image = try ctx.device.createImage(&image_create_info, null); + + // -- Create memory for image -- + // Get memory requirements for a type of image + const memory_requirements = ctx.device.getImageMemoryRequirements(image); + + // Allocate memory using image requirements and user-defined properties + const memory_alloc_info: vk.MemoryAllocateInfo = .{ + .allocation_size = memory_requirements.size, + .memory_type_index = Utilities.findMemoryTypeIndex(ctx.physical_device, ctx.instance, memory_requirements.memory_type_bits, prop_flags), + }; + + image_memory.* = try ctx.device.allocateMemory(&memory_alloc_info, null); + + // Connect memory to image + try ctx.device.bindImageMemory(image, image_memory.*, 0); + + return image; +} + +pub fn createImageView( + ctx: Context, + image: vk.Image, + format: vk.Format, + aspect_flags: vk.ImageAspectFlags, +) !vk.ImageView { + const image_view_create_info: vk.ImageViewCreateInfo = .{ + .image = image, + .format = format, + .view_type = .@"2d", + .components = .{ + // Used for remapping rgba values to other rgba values + .r = .identity, + .g = .identity, + .b = .identity, + .a = .identity, + }, + .subresource_range = .{ + .aspect_mask = aspect_flags, // Which aspect of image to view (e.g.: colour, depth, stencil, etc...) + .base_mip_level = 0, // Start mipmap level to view from + .level_count = 1, // Number of mipmap levels to view + .base_array_layer = 0, // Start array level to view from + .layer_count = 1, // Number of array levels to view + }, + }; + + return try ctx.device.createImageView(&image_view_create_info, null); +} diff --git a/src/vulkan_renderer.zig b/src/vulkan_renderer.zig index 14925d5..b092f31 100644 --- a/src/vulkan_renderer.zig +++ b/src/vulkan_renderer.zig @@ -14,14 +14,17 @@ const Vertex = Utilities.Vertex; const Vector3 = Utilities.Vector3; const Context = @import("Context.zig"); -const Instance = @import("Context.zig").Instance; +const Instance = Context.Instance; const Swapchain = @import("Swapchain.zig"); +const Texture = @import("Texture.zig"); +const Image = @import("image.zig"); const Mesh = @import("Mesh.zig"); const MeshModel = @import("MeshModel.zig"); const MAX_FRAME_DRAWS: u32 = 2; const MAX_OBJECTS: u32 = 20; + pub const CommandBuffer = vk.CommandBufferProxy(Context.apis); const UboViewProjection = struct { @@ -69,7 +72,7 @@ pub const VulkanRenderer = struct { sampler_descriptor_pool: vk.DescriptorPool, input_descriptor_pool: vk.DescriptorPool, descriptor_sets: []vk.DescriptorSet, - sampler_descriptor_sets: std.ArrayList(vk.DescriptorSet), + // sampler_descriptor_sets: std.ArrayList(vk.DescriptorSet), input_descriptor_sets: []vk.DescriptorSet, vp_uniform_buffer: []vk.Buffer, @@ -79,10 +82,7 @@ pub const VulkanRenderer = struct { command_buffers: []CommandBuffer, // Assets - image_files: std.ArrayList(img.Image), - texture_images: std.ArrayList(vk.Image), - texture_image_memory: std.ArrayList(vk.DeviceMemory), - texture_image_views: std.ArrayList(vk.ImageView), + textures: std.ArrayList(Texture), model_list: std.ArrayList(MeshModel), // Pipeline @@ -133,9 +133,7 @@ pub const VulkanRenderer = struct { 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_image_memory = std.ArrayList(vk.DeviceMemory).init(self.allocator); - self.texture_image_views = std.ArrayList(vk.ImageView).init(self.allocator); + self.textures = std.ArrayList(Texture).init(self.allocator); self.model_list = std.ArrayList(MeshModel).init(allocator); const aspect: f32 = @as(f32, @floatFromInt(self.swapchain.extent.width)) / @as(f32, @floatFromInt(self.swapchain.extent.height)); @@ -300,24 +298,13 @@ pub const VulkanRenderer = struct { self.allocator.free(self.command_buffers); self.ctx.device.destroyCommandPool(self.graphics_command_pool, null); - for (self.swapchain.swapchain_framebuffers) |framebuffer| { - self.ctx.device.destroyFramebuffer(framebuffer, null); - } - - self.allocator.free(self.swapchain.swapchain_framebuffers); - self.ctx.device.destroyPipeline(self.second_pipeline, null); self.ctx.device.destroyPipelineLayout(self.second_pipeline_layout, null); self.ctx.device.destroyPipeline(self.graphics_pipeline, null); self.ctx.device.destroyPipelineLayout(self.pipeline_layout, null); self.ctx.device.destroyRenderPass(self.render_pass, null); - for (self.swapchain.swapchain_images) |swapchain_image| { - self.ctx.device.destroyImageView(swapchain_image.image_view, null); - } - - self.allocator.free(self.swapchain.swapchain_images); - self.ctx.device.destroySwapchainKHR(self.swapchain.handle, null); + self.swapchain.deinit(); self.ctx.deinit(); } @@ -569,7 +556,8 @@ pub const VulkanRenderer = struct { // Create colour buffers for (0..self.colour_buffer_image.len) |i| { - self.colour_buffer_image[i] = try self.swapchain.createImage( + self.colour_buffer_image[i] = try Image.createImage( + self.ctx, self.swapchain.extent.width, self.swapchain.extent.height, colour_format, @@ -579,7 +567,8 @@ pub const VulkanRenderer = struct { &self.colour_buffer_image_memory[i], ); - self.colour_buffer_image_view[i] = try self.swapchain.createImageView( + self.colour_buffer_image_view[i] = try Image.createImageView( + self.ctx, self.colour_buffer_image[i], colour_format, .{ .color_bit = true }, @@ -604,7 +593,8 @@ pub const VulkanRenderer = struct { for (0..self.depth_buffer_image.len) |i| { // Create depth buffer image - self.depth_buffer_image[i] = try self.swapchain.createImage( + self.depth_buffer_image[i] = try Image.createImage( + self.ctx, self.swapchain.extent.width, self.swapchain.extent.height, self.depth_format, @@ -615,7 +605,7 @@ pub const VulkanRenderer = struct { ); // Create depth buffer image view - self.depth_buffer_image_view[i] = try self.swapchain.createImageView(self.depth_buffer_image[i], self.depth_format, .{ .depth_bit = true }); + self.depth_buffer_image_view[i] = try Image.createImageView(self.ctx, self.depth_buffer_image[i], self.depth_format, .{ .depth_bit = true }); } } @@ -1306,221 +1296,15 @@ pub const VulkanRenderer = struct { try command_buffer.endCommandBuffer(); } - fn createTextureImage(self: *Self, file_name: []const u8) !u32 { - // Load image file - var width: u32 = undefined; - var height: u32 = undefined; - var image_size: vk.DeviceSize = undefined; - const image = try self.loadTextureFile(file_name, &width, &height, &image_size); - - // Create staging buffer to hold loaded data, ready to copy to device - var image_staging_buffer: vk.Buffer = undefined; - var image_staging_buffer_memory: vk.DeviceMemory = undefined; - defer self.ctx.device.destroyBuffer(image_staging_buffer, null); - defer self.ctx.device.freeMemory(image_staging_buffer_memory, null); - - try Utilities.createBuffer( - self.ctx.physical_device, - self.ctx.instance, - self.ctx.device, - image_size, - .{ .transfer_src_bit = true }, - .{ .host_visible_bit = true, .host_coherent_bit = true }, - &image_staging_buffer, - &image_staging_buffer_memory, - ); - - // Copy data to staging buffer - const data = try self.ctx.device.mapMemory(image_staging_buffer_memory, 0, image_size, .{}); - const image_data: [*]u8 = @ptrCast(@alignCast(data)); - - @memcpy(image_data, image[0..]); - self.ctx.device.unmapMemory(image_staging_buffer_memory); - - // Create image to hold final texture - var tex_image_memory: vk.DeviceMemory = undefined; - const tex_image: vk.Image = try self.swapchain.createImage( - width, - height, - .r8g8b8a8_srgb, - .optimal, - .{ .transfer_dst_bit = true, .sampled_bit = true }, - .{ .device_local_bit = true }, - &tex_image_memory, - ); - - // Transition image to be DST for copy operation - try Utilities.transitionImageLayout( - self.ctx.device, - self.ctx.graphics_queue.handle, - self.graphics_command_pool, - tex_image, - .undefined, - .transfer_dst_optimal, - ); - - // Copy data to image - try Utilities.copyImageBuffer( - self.ctx.device, - self.ctx.graphics_queue.handle, - self.graphics_command_pool, - image_staging_buffer, - tex_image, - width, - height, - ); - - // Transition image to be shader readable for shader usage - try Utilities.transitionImageLayout( - self.ctx.device, - self.ctx.graphics_queue.handle, - self.graphics_command_pool, - tex_image, - .transfer_dst_optimal, - .shader_read_only_optimal, - ); - - // Add texture data to array for reference - try self.texture_images.append(tex_image); - try self.texture_image_memory.append(tex_image_memory); - - // Return index of new texture image - return @intCast(self.texture_images.items.len - 1); - } - - fn createTexture(self: *Self, file_name: []const u8) !u32 { - // Create texture image and get its location in the array - const texture_image_loc = try self.createTextureImage(file_name); - - // Create image view and add to list - const image_view = try self.swapchain.createImageView( - self.texture_images.items[texture_image_loc], - .r8g8b8a8_srgb, - .{ .color_bit = true }, - ); - - try self.texture_image_views.append(image_view); - - // Create texture descriptor - const descriptor_loc = try self.createTextureDescriptor(image_view); - - // Return location of set with texture - return descriptor_loc; - } - pub fn createMeshModel(self: *Self, model_file: []const u8) !usize { - const path = try StringUtils.concat("assets/models/", model_file, self.allocator); - defer self.allocator.free(path); - - // Import model scene - const scene = ai.aiImportFile( - path.ptr, - ai.aiProcess_Triangulate | ai.aiProcess_FlipUVs | ai.aiProcess_JoinIdenticalVertices, - ); - defer ai.aiReleaseImport(scene); - - // Get array of all materials with 1:1 ID placement - const texture_names = try MeshModel.loadMaterials(self.allocator, scene); - defer { - for (0..texture_names.items.len) |i| { - if (texture_names.items[i]) |texture_name| { - self.allocator.free(texture_name); - } - } - texture_names.deinit(); - } - - // Conversion from the material list IDs to our descriptor array IDs - var mat_to_tex = try std.ArrayList(u32).initCapacity(self.allocator, texture_names.items.len); - defer mat_to_tex.deinit(); - - // Loop over texture names and create textures for them - for (texture_names.items) |texture_name| { - if (texture_name != null) { - // Create texture and set value to index of new texture - mat_to_tex.appendAssumeCapacity(try self.createTexture(texture_name.?)); - } else { - - // If material had no texture, set to 0 to indicate no texture. Texture 0 will be reserver for a default texture - mat_to_tex.appendAssumeCapacity(0); - } - } - - // Load in all our meshes - const model_meshes = try MeshModel.loadNode( + // Pass tex smapler + MeshModel.new( self.allocator, - self.ctx.instance, - self.ctx.physical_device, - self.ctx.device, - self.ctx.graphics_queue.handle, + self.ctx, self.graphics_command_pool, - scene.*.mRootNode, - scene, - mat_to_tex.items, + self.texture_sampler, + model_file, ); - - // Create a mesh model and add to our list - const mesh_model = MeshModel.new(self.allocator, model_meshes); - try self.model_list.append(mesh_model); - - return self.model_list.items.len - 1; - } - - fn createTextureDescriptor(self: *Self, texture_image: vk.ImageView) !u32 { - var descriptor_set: vk.DescriptorSet = undefined; - - // Descriptor set allocation info - const set_alloc_info: vk.DescriptorSetAllocateInfo = .{ - .descriptor_pool = self.sampler_descriptor_pool, - .descriptor_set_count = 1, - .p_set_layouts = @ptrCast(&self.sampler_set_layout), - }; - - // Allocate descriptor sets - try self.ctx.device.allocateDescriptorSets(&set_alloc_info, @ptrCast(&descriptor_set)); - - const image_info: vk.DescriptorImageInfo = .{ - .image_layout = .shader_read_only_optimal, // Image layout when in use - .image_view = texture_image, // Image to bind to set - .sampler = self.texture_sampler, // Sampler to use for set - }; - - // Descriptor write info - const descriptor_write: vk.WriteDescriptorSet = .{ - .dst_set = descriptor_set, - .dst_binding = 0, - .dst_array_element = 0, - .descriptor_type = .combined_image_sampler, - .descriptor_count = 1, - .p_image_info = @ptrCast(&image_info), - .p_buffer_info = undefined, - .p_texel_buffer_view = undefined, - }; - - // Update the new descriptor set - self.ctx.device.updateDescriptorSets(1, @ptrCast(&descriptor_write), 0, null); - - try self.sampler_descriptor_sets.append(descriptor_set); - - // Return descriptor set location - return @intCast(self.sampler_descriptor_sets.items.len - 1); - } - - 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 = try std.mem.concatWithSentinel(self.allocator, u8, &path_concat, 0); - defer self.allocator.free(path); - - const image = try img.Image.loadFromFile(path, 0); - try self.image_files.append(image); - - width.* = image.width; - height.* = image.height; - - // Calculate image size using given and known data - image_size.* = width.* * height.* * 4; - - return image.data; } };