WIP: Refactor mesh & texture

This commit is contained in:
Przemyslaw Gasinski 2024-10-06 12:27:09 +02:00
parent d318dfa4a2
commit 0d4478acc6
7 changed files with 431 additions and 366 deletions

View file

@ -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);

View file

@ -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,
);
}

View file

@ -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 {

198
src/Texture.zig Normal file
View file

@ -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;
}

View file

@ -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;
}

82
src/image.zig Normal file
View file

@ -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);
}

View file

@ -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;
}
};