WIP: Refactor mesh & texture
This commit is contained in:
parent
d318dfa4a2
commit
0d4478acc6
7 changed files with 431 additions and 366 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
198
src/Texture.zig
Normal 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;
|
||||
}
|
|
@ -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
82
src/image.zig
Normal 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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue