vulkan-zig/src/vulkan_renderer.zig

1334 lines
57 KiB
Zig

const std = @import("std");
const sdl = @import("sdl");
const vk = @import("vulkan");
const builtin = @import("builtin");
const shaders = @import("shaders");
const zm = @import("zmath");
const img = @import("zstbi");
const ai = @import("assimp.zig").c;
const QueueUtils = @import("queue_utils.zig");
const StringUtils = @import("string_utils.zig");
const Utilities = @import("utilities.zig");
const Vertex = Utilities.Vertex;
const Vector3 = Utilities.Vector3;
const Context = @import("Context.zig");
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 {
projection: zm.Mat align(16),
view: zm.Mat align(16),
};
pub const Model = struct {
model: zm.Mat align(16),
};
pub const VulkanRenderer = struct {
const Self = @This();
allocator: std.mem.Allocator,
current_frame: u32 = 0,
ctx: Context,
swapchain: Swapchain,
// Scene settings
ubo_view_projection: UboViewProjection,
// Main
viewport: vk.Viewport,
scissor: vk.Rect2D,
texture_sampler: vk.Sampler,
depth_buffer_image: []vk.Image,
depth_buffer_image_memory: []vk.DeviceMemory,
depth_buffer_image_view: []vk.ImageView,
colour_buffer_image: []vk.Image,
colour_buffer_image_memory: []vk.DeviceMemory,
colour_buffer_image_view: []vk.ImageView,
// Descriptors
descriptor_set_layout: vk.DescriptorSetLayout,
sampler_set_layout: vk.DescriptorSetLayout,
input_set_layout: vk.DescriptorSetLayout,
push_constant_range: vk.PushConstantRange,
descriptor_pool: vk.DescriptorPool,
sampler_descriptor_pool: vk.DescriptorPool,
input_descriptor_pool: vk.DescriptorPool,
descriptor_sets: []vk.DescriptorSet,
// sampler_descriptor_sets: std.ArrayList(vk.DescriptorSet),
input_descriptor_sets: []vk.DescriptorSet,
vp_uniform_buffer: []vk.Buffer,
vp_uniform_buffer_memory: []vk.DeviceMemory,
// TODO
command_buffers: []CommandBuffer,
// Assets
textures: std.ArrayList(Texture),
model_list: std.ArrayList(MeshModel),
// Pipeline
graphics_pipeline: vk.Pipeline,
pipeline_layout: vk.PipelineLayout,
second_pipeline: vk.Pipeline,
second_pipeline_layout: vk.PipelineLayout,
render_pass: vk.RenderPass,
// Pools
graphics_command_pool: vk.CommandPool,
// Utilities
depth_format: vk.Format,
// Synchronisation
image_available: [MAX_FRAME_DRAWS]vk.Semaphore,
render_finished: [MAX_FRAME_DRAWS]vk.Semaphore,
draw_fences: [MAX_FRAME_DRAWS]vk.Fence,
pub fn init(allocator: std.mem.Allocator, window: sdl.Window) !Self {
var self: Self = undefined;
self.allocator = allocator;
self.ctx = try Context.init(allocator, window);
self.current_frame = 0;
self.swapchain = try Swapchain.create(allocator, self.ctx);
try self.createColourBufferImage();
try self.createDepthBufferImage();
try self.createRenderPass();
try self.createDescriptorSetLayout();
try self.createPushConstantRange();
try self.createGraphicsPipeline();
try self.createFramebuffers();
try self.createCommandPool();
self.sampler_descriptor_sets = try std.ArrayList(vk.DescriptorSet).initCapacity(self.allocator, self.swapchain.swapchain_images.len);
try self.createCommandBuffers();
try self.createTextureSampler();
try self.createUniformBuffers();
try self.createDescriptorPool();
try self.createDescriptorSets();
try self.createInputDescriptorSets();
try self.createSynchronisation();
self.image_files = std.ArrayList(img.Image).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));
self.ubo_view_projection.projection = zm.perspectiveFovRh(
std.math.degreesToRadians(45.0),
aspect,
0.1,
100.0,
);
self.ubo_view_projection.view = zm.lookAtRh(
zm.Vec{ 0.0, 2.0, 2.0, 0.0 },
zm.Vec{ 0.0, 0.0, 0.0, 0.0 },
zm.Vec{ 0.0, 1.0, 0.0, 0.0 },
);
// Invert y scale
self.ubo_view_projection.projection[1][1] *= -1;
_ = try self.createTexture("giraffe.png");
return self;
}
pub fn updateModel(self: *Self, model_id: usize, new_model: zm.Mat) !void {
if (model_id < self.model_list.items.len) {
self.model_list.items[model_id].model = new_model;
}
}
pub fn updateCamera(self: *Self, movement: zm.Mat) void {
self.ubo_view_projection.view = zm.mul(self.ubo_view_projection.view, movement);
}
pub fn draw(self: *Self) !void {
// Wait for given fence to signal (open) from last draw before continuing
_ = try self.ctx.device.waitForFences(
1,
@ptrCast(&self.draw_fences[self.current_frame]),
vk.TRUE,
std.math.maxInt(u64),
);
// Manually reset (close) fences
try self.ctx.device.resetFences(1, @ptrCast(&self.draw_fences[self.current_frame]));
// -- Get next image
// Get index of next image to be drawn to, and signal semaphore when ready to be drawn to
const image_index_result = try self.ctx.device.acquireNextImageKHR(
self.swapchain.handle,
std.math.maxInt(u64),
self.image_available[self.current_frame],
.null_handle,
);
try self.recordCommands(image_index_result.image_index);
try self.updateUniformBuffers(image_index_result.image_index);
// -- Submit command buffer to render
// Queue submission information
const wait_stages = [_]vk.PipelineStageFlags{.{ .color_attachment_output_bit = true }};
const submit_info: vk.SubmitInfo = .{
.wait_semaphore_count = 1, // Number of semaphores to wait on
.p_wait_semaphores = @ptrCast(&self.image_available[self.current_frame]), // List of semaphores to wait on
.p_wait_dst_stage_mask = &wait_stages, // Stages to check semaphores at
.command_buffer_count = 1, // Number of command buffers to submit
.p_command_buffers = @ptrCast(&self.command_buffers[image_index_result.image_index]), // Command buffer to submit
.signal_semaphore_count = 1, // Number of semaphores to signal
.p_signal_semaphores = @ptrCast(&self.render_finished[self.current_frame]), // List of semaphores to signal when command buffer finishes
};
// Submit command buffer to queue
try self.ctx.device.queueSubmit(self.ctx.graphics_queue.handle, 1, @ptrCast(&submit_info), self.draw_fences[self.current_frame]);
// -- Present rendered image to screen
const present_info: vk.PresentInfoKHR = .{
.wait_semaphore_count = 1, // Number of semaphores to wait on
.p_wait_semaphores = @ptrCast(&self.render_finished[self.current_frame]), // Semaphores to wait on
.swapchain_count = 1, // Number of swapchains to present to
.p_swapchains = @ptrCast(&self.swapchain.handle), // Swapchains to present images to
.p_image_indices = @ptrCast(&image_index_result.image_index), // Index of images in swapchains to present
};
// Present image
_ = try self.ctx.device.queuePresentKHR(self.ctx.presentation_queue.handle, &present_info);
// Get next frame (use % to keep the current frame below MAX_FRAME_DRAWS)
self.current_frame = (self.current_frame + 1) % MAX_FRAME_DRAWS;
}
pub fn deinit(self: *Self) void {
self.ctx.device.deviceWaitIdle() catch undefined;
for (0..self.model_list.items.len) |i| {
self.model_list.items[i].destroy();
}
self.model_list.deinit();
for (0..self.image_files.items.len) |i| {
self.image_files.items[i].deinit();
}
self.image_files.deinit();
self.ctx.device.destroySampler(self.texture_sampler, null);
for (
self.texture_images.items,
self.texture_image_memory.items,
self.texture_image_views.items,
) |tex_image, tex_image_memory, tex_image_view| {
self.ctx.device.destroyImage(tex_image, null);
self.ctx.device.freeMemory(tex_image_memory, null);
self.ctx.device.destroyImageView(tex_image_view, null);
}
self.texture_images.deinit();
self.texture_image_memory.deinit();
self.texture_image_views.deinit();
for (0..self.depth_buffer_image.len) |i| {
self.ctx.device.destroyImageView(self.depth_buffer_image_view[i], null);
self.ctx.device.destroyImage(self.depth_buffer_image[i], null);
self.ctx.device.freeMemory(self.depth_buffer_image_memory[i], null);
}
self.allocator.free(self.depth_buffer_image);
self.allocator.free(self.depth_buffer_image_memory);
self.allocator.free(self.depth_buffer_image_view);
for (0..self.colour_buffer_image.len) |i| {
self.ctx.device.destroyImageView(self.colour_buffer_image_view[i], null);
self.ctx.device.destroyImage(self.colour_buffer_image[i], null);
self.ctx.device.freeMemory(self.colour_buffer_image_memory[i], null);
}
self.allocator.free(self.colour_buffer_image);
self.allocator.free(self.colour_buffer_image_memory);
self.allocator.free(self.colour_buffer_image_view);
self.ctx.device.destroyDescriptorPool(self.input_descriptor_pool, null);
self.ctx.device.destroyDescriptorPool(self.descriptor_pool, null);
self.ctx.device.destroyDescriptorSetLayout(self.descriptor_set_layout, null);
self.ctx.device.destroyDescriptorPool(self.sampler_descriptor_pool, null);
self.ctx.device.destroyDescriptorSetLayout(self.sampler_set_layout, null);
self.ctx.device.destroyDescriptorSetLayout(self.input_set_layout, null);
self.sampler_descriptor_sets.deinit();
self.allocator.free(self.input_descriptor_sets);
for (0..self.swapchain.swapchain_images.len) |i| {
self.ctx.device.destroyBuffer(self.vp_uniform_buffer[i], null);
self.ctx.device.freeMemory(self.vp_uniform_buffer_memory[i], null);
}
self.allocator.free(self.vp_uniform_buffer);
self.allocator.free(self.vp_uniform_buffer_memory);
self.allocator.free(self.descriptor_sets);
for (0..MAX_FRAME_DRAWS) |i| {
self.ctx.device.destroySemaphore(self.render_finished[i], null);
self.ctx.device.destroySemaphore(self.image_available[i], null);
self.ctx.device.destroyFence(self.draw_fences[i], null);
}
self.allocator.free(self.command_buffers);
self.ctx.device.destroyCommandPool(self.graphics_command_pool, null);
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);
self.swapchain.deinit();
self.ctx.deinit();
}
fn createRenderPass(self: *Self) !void {
// -- Attachments --
var subpasses: [2]vk.SubpassDescription = undefined;
// Subpass 1 attachments and references (input attachments)
// Colour attachment (input)
const colour_format = chooseSupportedFormat(
self.ctx.physical_device,
self.ctx.instance,
&[_]vk.Format{.r8g8b8a8_srgb},
.optimal,
.{ .color_attachment_bit = true },
);
const colour_attachment: vk.AttachmentDescription = .{
.format = colour_format.?,
.samples = .{ .@"1_bit" = true },
.load_op = .clear,
.store_op = .dont_care,
.stencil_load_op = .dont_care,
.stencil_store_op = .dont_care,
.initial_layout = .undefined,
.final_layout = .color_attachment_optimal,
};
// Depth attachment (input)
const depth_attachment: vk.AttachmentDescription = .{
.format = self.depth_format,
.samples = .{ .@"1_bit" = true },
.load_op = .clear,
.store_op = .dont_care,
.stencil_load_op = .dont_care,
.stencil_store_op = .dont_care,
.initial_layout = .undefined,
.final_layout = .depth_stencil_attachment_optimal,
};
// Colour attachment (input) reference
const colour_attachment_reference: vk.AttachmentReference = .{
.attachment = 1,
.layout = .color_attachment_optimal,
};
// Depth attachment (input) reference
const depth_attachment_reference: vk.AttachmentReference = .{
.attachment = 2,
.layout = .depth_stencil_attachment_optimal,
};
subpasses[0] = .{
.pipeline_bind_point = .graphics, // Pipeline type subpass is to be bound to
.color_attachment_count = 1,
.p_color_attachments = @ptrCast(&colour_attachment_reference),
.p_depth_stencil_attachment = &depth_attachment_reference,
};
// Subpass 2 attachments and references
// Colour attachment of the render pass
const swapchain_colour_attachment: vk.AttachmentDescription = .{
.format = self.swapchain.swapchain_image_format, // Format to use for attachment
.samples = .{ .@"1_bit" = true }, // Number of samples to write for multisampling
.load_op = .clear, // Describes what to do with attachment before rendering
.store_op = .store, // Describes what to do with attachment after rendering
.stencil_load_op = .dont_care, // Describes what to do with stencil before rendering
.stencil_store_op = .dont_care, // Describes what to do with stencil after rendering
// Framebuffer data will be stored as an image, but images can be given different data layouts
// to give optimal use for certain operations
.initial_layout = vk.ImageLayout.undefined, // Image data layout before render pass starts
.final_layout = vk.ImageLayout.present_src_khr, // Image data layout after render pass (to change to)
};
// Attachment reference uses an attachment index that refers to index in the attachment list passed to render pass create info
const swapchain_colour_attachment_reference: vk.AttachmentReference = .{
.attachment = 0,
.layout = vk.ImageLayout.color_attachment_optimal,
};
// References to attachments that subpass will take input from
const input_references = [_]vk.AttachmentReference{
.{
.attachment = 1, // Colour attachment
.layout = .shader_read_only_optimal,
},
.{
.attachment = 2, // Depth attachment
.layout = .shader_read_only_optimal,
},
};
subpasses[1] = .{
.pipeline_bind_point = .graphics,
.color_attachment_count = 1,
.p_color_attachments = @ptrCast(&swapchain_colour_attachment_reference),
.input_attachment_count = @intCast(input_references.len),
.p_input_attachments = &input_references,
};
// -- Subpass dependencies
// Need to determine when layout transitions occur using subpass dependencies
const subpass_dependencies = [_]vk.SubpassDependency{
// Conversion from VK_IMAGE_LAYOUT_UNDEFINED to VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
.{
// Transition must happen after...
.src_subpass = vk.SUBPASS_EXTERNAL, // Subpass index (VK_SUBPASS_EXTERNAL = outside of renderpass)
.src_stage_mask = .{ .bottom_of_pipe_bit = true }, // Pipeline stage
.src_access_mask = .{ .memory_read_bit = true }, // Stage access mask (memory access)
// But must happen before...
.dst_subpass = 0,
.dst_stage_mask = .{ .color_attachment_output_bit = true },
.dst_access_mask = .{ .color_attachment_read_bit = true, .color_attachment_write_bit = true },
},
// Subpass 1 layout (colour/depth) to subpass 2 layout (shader read)
.{
.src_subpass = 0,
.src_stage_mask = .{ .color_attachment_output_bit = true },
.src_access_mask = .{ .color_attachment_write_bit = true },
.dst_subpass = 1,
.dst_stage_mask = .{ .fragment_shader_bit = true },
.dst_access_mask = .{ .shader_read_bit = true },
},
// Conversion from VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
.{
// Transition must happen after...
.src_subpass = 0,
.src_stage_mask = .{ .color_attachment_output_bit = true },
.src_access_mask = .{ .color_attachment_read_bit = true, .color_attachment_write_bit = true },
// But must happen before...
.dst_subpass = vk.SUBPASS_EXTERNAL,
.dst_stage_mask = .{ .bottom_of_pipe_bit = true },
.dst_access_mask = .{ .memory_read_bit = true },
},
};
// Order matters
const render_pass_attachments = [_]vk.AttachmentDescription{ swapchain_colour_attachment, colour_attachment, depth_attachment };
const render_pass_create_info: vk.RenderPassCreateInfo = .{
.attachment_count = @intCast(render_pass_attachments.len),
.p_attachments = &render_pass_attachments,
.subpass_count = @intCast(subpasses.len),
.p_subpasses = &subpasses,
.dependency_count = @intCast(subpass_dependencies.len),
.p_dependencies = &subpass_dependencies,
};
self.render_pass = try self.ctx.device.createRenderPass(&render_pass_create_info, null);
}
fn createDescriptorSetLayout(self: *Self) !void {
// -- Uniform values descriptor set layout --
// UboViewProjection binding info
const vp_layout_binding: vk.DescriptorSetLayoutBinding = .{
.binding = 0, // Binding point in shader (designated by binding number in shader)
.descriptor_type = .uniform_buffer, // Type of descriptor (unifor, dynamic uniform, image sampler, etc)
.descriptor_count = 1, // Number of descriptors for binding
.stage_flags = .{ .vertex_bit = true }, // Shader stage to bind to
.p_immutable_samplers = null, // For texture: can make smapler data immutable by specifying in layout
};
const layout_bindings = [_]vk.DescriptorSetLayoutBinding{vp_layout_binding};
// Create descriptor set layout with given bindings
const layout_create_info: vk.DescriptorSetLayoutCreateInfo = .{
.binding_count = @intCast(layout_bindings.len), // Number of binding infos
.p_bindings = &layout_bindings, // Array of binding infos
};
// Create descriptor set layout
self.descriptor_set_layout = try self.ctx.device.createDescriptorSetLayout(&layout_create_info, null);
// -- Texture sampler descriptor set layout --
// Texture binding info
const sampler_layout_binding: vk.DescriptorSetLayoutBinding = .{
.binding = 0,
.descriptor_type = .combined_image_sampler,
.descriptor_count = 1,
.stage_flags = .{ .fragment_bit = true },
.p_immutable_samplers = null,
};
// Create a descriptor set layout with given bindings for texture
const texture_layout_info: vk.DescriptorSetLayoutCreateInfo = .{
.binding_count = 1,
.p_bindings = @ptrCast(&sampler_layout_binding),
};
self.sampler_set_layout = try self.ctx.device.createDescriptorSetLayout(&texture_layout_info, null);
// -- Create input attachment image descriptor set layout
// Colour input binding
const colour_input_layout_binding: vk.DescriptorSetLayoutBinding = .{
.binding = 0,
.descriptor_type = .input_attachment,
.descriptor_count = 1,
.stage_flags = .{ .fragment_bit = true },
};
// Depth input binding
const depth_input_layout_binding: vk.DescriptorSetLayoutBinding = .{
.binding = 1,
.descriptor_type = .input_attachment,
.descriptor_count = 1,
.stage_flags = .{ .fragment_bit = true },
};
// Array of input attachment bindings
const input_bindings = [_]vk.DescriptorSetLayoutBinding{ colour_input_layout_binding, depth_input_layout_binding };
// Create a descriptor set layout for input attachments
const input_layout_create_info: vk.DescriptorSetLayoutCreateInfo = .{
.binding_count = @intCast(input_bindings.len),
.p_bindings = &input_bindings,
};
self.input_set_layout = try self.ctx.device.createDescriptorSetLayout(&input_layout_create_info, null);
}
fn createPushConstantRange(self: *Self) !void {
// Define push constant values (no 'create' needed)
self.push_constant_range = .{
.stage_flags = .{ .vertex_bit = true }, // Shader stage push constant will go to
.offset = 0, // Offset into given data to pass to push constant
.size = @sizeOf(Model), // Size of data being passed
};
}
fn createColourBufferImage(self: *Self) !void {
self.colour_buffer_image = try self.allocator.alloc(vk.Image, self.swapchain.swapchain_images.len);
self.colour_buffer_image_memory = try self.allocator.alloc(vk.DeviceMemory, self.swapchain.swapchain_images.len);
self.colour_buffer_image_view = try self.allocator.alloc(vk.ImageView, self.swapchain.swapchain_images.len);
// Get supported format for colour attachment
const colour_format = chooseSupportedFormat(
self.ctx.physical_device,
self.ctx.instance,
&[_]vk.Format{.r8g8b8a8_srgb},
.optimal,
.{ .color_attachment_bit = true },
) orelse return error.FormatNotSupported;
// Create colour buffers
for (0..self.colour_buffer_image.len) |i| {
self.colour_buffer_image[i] = try Image.createImage(
self.ctx,
self.swapchain.extent.width,
self.swapchain.extent.height,
colour_format,
.optimal,
.{ .color_attachment_bit = true, .input_attachment_bit = true },
.{ .device_local_bit = true },
&self.colour_buffer_image_memory[i],
);
self.colour_buffer_image_view[i] = try Image.createImageView(
self.ctx,
self.colour_buffer_image[i],
colour_format,
.{ .color_bit = true },
);
}
}
fn createDepthBufferImage(self: *Self) !void {
self.depth_buffer_image = try self.allocator.alloc(vk.Image, self.swapchain.swapchain_images.len);
self.depth_buffer_image_memory = try self.allocator.alloc(vk.DeviceMemory, self.swapchain.swapchain_images.len);
self.depth_buffer_image_view = try self.allocator.alloc(vk.ImageView, self.swapchain.swapchain_images.len);
// Get supported depth buffer format
const formats = [_]vk.Format{ .d32_sfloat_s8_uint, .d32_sfloat, .d24_unorm_s8_uint };
self.depth_format = chooseSupportedFormat(
self.ctx.physical_device,
self.ctx.instance,
&formats,
.optimal,
.{ .depth_stencil_attachment_bit = true },
) orelse return error.UnsupportedDepthBufferFormat;
for (0..self.depth_buffer_image.len) |i| {
// Create depth buffer image
self.depth_buffer_image[i] = try Image.createImage(
self.ctx,
self.swapchain.extent.width,
self.swapchain.extent.height,
self.depth_format,
.optimal,
.{ .depth_stencil_attachment_bit = true, .input_attachment_bit = true },
.{ .device_local_bit = true },
&self.depth_buffer_image_memory[i],
);
// Create depth buffer image view
self.depth_buffer_image_view[i] = try Image.createImageView(self.ctx, self.depth_buffer_image[i], self.depth_format, .{ .depth_bit = true });
}
}
fn createGraphicsPipeline(self: *Self) !void {
// Create shader modules
const vert = try self.ctx.device.createShaderModule(&.{
.code_size = shaders.shader_vert.len,
.p_code = @ptrCast(&shaders.shader_vert),
}, null);
defer self.ctx.device.destroyShaderModule(vert, null);
const frag = try self.ctx.device.createShaderModule(&.{
.code_size = shaders.shader_frag.len,
.p_code = @ptrCast(&shaders.shader_frag),
}, null);
defer self.ctx.device.destroyShaderModule(frag, null);
// -- Shader stage creation information --
// Vertex stage creation information
var vertex_shader_create_info: vk.PipelineShaderStageCreateInfo = .{
.stage = .{ .vertex_bit = true },
.module = vert,
.p_name = "main",
};
// Fragment stage creation information
var fragment_shader_create_info: vk.PipelineShaderStageCreateInfo = .{
.stage = .{ .fragment_bit = true },
.module = frag,
.p_name = "main",
};
const shader_create_infos = [_]vk.PipelineShaderStageCreateInfo{
vertex_shader_create_info,
fragment_shader_create_info,
};
// How the data for a single vertex (including info such as position, colour, texture coords, normals, etc...) is as a whole
const binding_description: vk.VertexInputBindingDescription = .{
.binding = 0, // Can bind multiple streams of data, this defines which one
.stride = @sizeOf(Vertex), // Size of simple vertex object
.input_rate = .vertex, // How to move between data after each vertex
// vertex: move to the next vertex
// instance: move to a vertex for the next instance
};
// How the data for an attribute is defined within the vertex
const attribute_descriptions = [_]vk.VertexInputAttributeDescription{
// Position attribute
.{
.binding = 0, // Which binding the data is at (should be same as above)
.location = 0, // Location in shader where data will be read from
.format = vk.Format.r32g32b32_sfloat, // Format the data will take (also helps define size of data)
.offset = @offsetOf(Vertex, "pos"), // Where this attribute is defined in data for a single vertex
},
// Colour attribute
.{
.binding = 0,
.location = 1,
.format = vk.Format.r32g32b32_sfloat,
.offset = @offsetOf(Vertex, "col"),
},
// Texture attribute
.{
.binding = 0,
.location = 2,
.format = vk.Format.r32g32_sfloat,
.offset = @offsetOf(Vertex, "tex"),
},
};
// -- Vertex input --
var vertex_input_create_info: vk.PipelineVertexInputStateCreateInfo = .{
.vertex_binding_description_count = 1,
.p_vertex_binding_descriptions = @ptrCast(&binding_description), // List of vertex binding descriptions (data spacing, stride info)
.vertex_attribute_description_count = @intCast(attribute_descriptions.len),
.p_vertex_attribute_descriptions = &attribute_descriptions, // List of vertex attribute descriptions (data format and where to bind to/from)
};
// -- Input assembly --
const assembly_create_info: vk.PipelineInputAssemblyStateCreateInfo = .{
.topology = .triangle_list, // Primitive type to assemble vertices as
.primitive_restart_enable = vk.FALSE, // Allow overrinding of strip topology to start new primitives
};
// -- Viewport & scissor --
self.viewport = .{
.x = 0.0,
.y = 0.0,
.width = @floatFromInt(self.swapchain.extent.width),
.height = @floatFromInt(self.swapchain.extent.height),
.min_depth = 0.0,
.max_depth = 1.0,
};
self.scissor = .{
.offset = .{ .x = 0, .y = 0 },
.extent = self.swapchain.extent,
};
const viewport_state_create_info: vk.PipelineViewportStateCreateInfo = .{
.viewport_count = 1,
.p_viewports = @ptrCast(&self.viewport),
.scissor_count = 1,
.p_scissors = @ptrCast(&self.scissor),
};
// -- Dynamic states --
// Dynamic states to enable (TODO: To investigate later)
const dynamic_states = [_]vk.DynamicState{ .viewport, .scissor };
const dynamic_state_create_info: vk.PipelineDynamicStateCreateInfo = .{
.dynamic_state_count = @intCast(dynamic_states.len),
.p_dynamic_states = &dynamic_states,
};
// -- Rasterizer --
const rasterizer_create_info: vk.PipelineRasterizationStateCreateInfo = .{
.depth_clamp_enable = vk.FALSE, // Change if fragments beyond near/far planes are clipped (default) or clamped to plane
.rasterizer_discard_enable = vk.FALSE, // Whether to discard data and skip rasterizer (never creates fragments)
.polygon_mode = .fill, // How to handle filling points between vertices
.line_width = 1.0, // How thick the lines should be when drawn
.cull_mode = .{ .back_bit = false }, // Which face of a triangle to cull
.front_face = .counter_clockwise, // Winding to determine which side is front
.depth_bias_enable = vk.FALSE, // Whether to add depth bias to fragments (good for stopping "shadow acne" in shadow mapping)
.depth_bias_constant_factor = 0,
.depth_bias_clamp = 0,
.depth_bias_slope_factor = 0,
};
// -- Multisampling --
const multisampling_create_info: vk.PipelineMultisampleStateCreateInfo = .{
.sample_shading_enable = vk.FALSE, // Enable multisample shading or not
.rasterization_samples = .{ .@"1_bit" = true }, // Number of samples to use per fragment
.min_sample_shading = 1,
.alpha_to_coverage_enable = vk.FALSE,
.alpha_to_one_enable = vk.FALSE,
};
// -- Blending --
// Blend attachment state (how blending is handled)
const colour_state: vk.PipelineColorBlendAttachmentState = .{
.color_write_mask = .{ .r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true }, // Colours to apply blending to
.blend_enable = vk.TRUE, // Enable blending
.src_color_blend_factor = vk.BlendFactor.src_alpha,
.dst_color_blend_factor = vk.BlendFactor.one_minus_src_alpha,
.color_blend_op = vk.BlendOp.add,
// Summary: (VK_BLEND_FACTOR_SRC_ALPHA * new colour) + (VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA * old colour)
.src_alpha_blend_factor = vk.BlendFactor.one,
.dst_alpha_blend_factor = vk.BlendFactor.zero,
.alpha_blend_op = vk.BlendOp.add,
// Summary (1 * new alpha) + (0 * old alpha) = new alpha
};
// Blending uses equation: (srcColorBlendFactor * new colour) colorBlendOp (dstColorBlendFactor * old colour)
const colour_blending_create_info: vk.PipelineColorBlendStateCreateInfo = .{
.logic_op_enable = vk.FALSE, // Alternative to calculations is to use logical operations
.logic_op = .copy,
.attachment_count = 1,
.p_attachments = @ptrCast(&colour_state),
.blend_constants = [_]f32{ 0, 0, 0, 0 },
};
// -- Pipeline layout --
const descriptor_set_layouts = [_]vk.DescriptorSetLayout{ self.descriptor_set_layout, self.sampler_set_layout };
const pipeline_layout_create_info: vk.PipelineLayoutCreateInfo = .{
.set_layout_count = @intCast(descriptor_set_layouts.len),
.p_set_layouts = &descriptor_set_layouts,
.push_constant_range_count = 1,
.p_push_constant_ranges = @ptrCast(&self.push_constant_range),
};
self.pipeline_layout = try self.ctx.device.createPipelineLayout(&pipeline_layout_create_info, null);
// -- Depth stencil testing --
var depth_stencil_create_info: vk.PipelineDepthStencilStateCreateInfo = .{
.depth_test_enable = vk.TRUE, // Enable checking depth to determine fragment write
.depth_write_enable = vk.TRUE, // Enable writing to depth buffer to replace all values
.depth_compare_op = .less, // Comparison operation that allows an overwrite (is in front)
.depth_bounds_test_enable = vk.FALSE, // Depth bounds test: does the depth value exist between two bounds
.stencil_test_enable = vk.FALSE, // Enable stencil test
.front = undefined,
.back = undefined,
.min_depth_bounds = undefined,
.max_depth_bounds = undefined,
};
// -- Graphics pipeline creation --
var pipeline_create_info: vk.GraphicsPipelineCreateInfo = .{
.stage_count = @intCast(shader_create_infos.len), // Number of shader stages
.p_stages = &shader_create_infos, // List of shader stages
.p_vertex_input_state = &vertex_input_create_info,
.p_input_assembly_state = &assembly_create_info,
.p_viewport_state = &viewport_state_create_info,
.p_dynamic_state = &dynamic_state_create_info,
.p_rasterization_state = &rasterizer_create_info,
.p_multisample_state = &multisampling_create_info,
.p_color_blend_state = &colour_blending_create_info,
.p_depth_stencil_state = &depth_stencil_create_info,
.layout = self.pipeline_layout, // Pipeline layout the pipeline should use
.render_pass = self.render_pass, // Renderpass description the pipeline is compatible with
.subpass = 0, // Subpass of renderpass to use with pipeline
// Pipeline derivatives: can create multiple pipelines that derive from one another for optimisation
.base_pipeline_handle = .null_handle, // Existing pipeline to derive from...
.base_pipeline_index = -1, // Or index of pipeline being created to derive from (in case creating multiple at once)
};
_ = try self.ctx.device.createGraphicsPipelines(
.null_handle,
1,
@ptrCast(&pipeline_create_info),
null,
@ptrCast(&self.graphics_pipeline),
);
// -- Create second pass pipeline
// Second pass shaders
const second_vert_shader_module = try self.ctx.device.createShaderModule(&.{
.code_size = shaders.second_vert.len,
.p_code = @ptrCast(&shaders.second_vert),
}, null);
defer self.ctx.device.destroyShaderModule(second_vert_shader_module, null);
const second_frag_shader_module = try self.ctx.device.createShaderModule(&.{
.code_size = shaders.second_frag.len,
.p_code = @ptrCast(&shaders.second_frag),
}, null);
defer self.ctx.device.destroyShaderModule(second_frag_shader_module, null);
// Set new shaders
vertex_shader_create_info.module = second_vert_shader_module;
fragment_shader_create_info.module = second_frag_shader_module;
const second_shader_stages = [_]vk.PipelineShaderStageCreateInfo{ vertex_shader_create_info, fragment_shader_create_info };
// No vertex data for second pass
vertex_input_create_info.vertex_binding_description_count = 0;
vertex_input_create_info.p_vertex_binding_descriptions = null;
vertex_input_create_info.vertex_attribute_description_count = 0;
vertex_input_create_info.p_vertex_attribute_descriptions = null;
// Don't want to write to depth buffer
depth_stencil_create_info.depth_write_enable = vk.FALSE;
// Create new pipeline layout
const second_pipeline_layout_create_info: vk.PipelineLayoutCreateInfo = .{
.set_layout_count = 1,
.p_set_layouts = @ptrCast(&self.input_set_layout),
};
self.second_pipeline_layout = try self.ctx.device.createPipelineLayout(&second_pipeline_layout_create_info, null);
pipeline_create_info.stage_count = @intCast(second_shader_stages.len);
pipeline_create_info.p_stages = &second_shader_stages;
pipeline_create_info.layout = self.second_pipeline_layout;
pipeline_create_info.subpass = 1;
// Create second pipeline
_ = try self.ctx.device.createGraphicsPipelines(
.null_handle,
1,
@ptrCast(&pipeline_create_info),
null,
@ptrCast(&self.second_pipeline),
);
}
fn createFramebuffers(self: *Self) !void {
self.swapchain.swapchain_framebuffers = try self.allocator.alloc(vk.Framebuffer, self.swapchain.swapchain_images.len);
// Create a frammebuffer for each swapchain image
for (self.swapchain.swapchain_images, 0..) |swapchain_image, i| {
// Order matters
const attachments = [_]vk.ImageView{
swapchain_image.image_view,
self.colour_buffer_image_view[i],
self.depth_buffer_image_view[i],
};
const framebuffer_create_info: vk.FramebufferCreateInfo = .{
.render_pass = self.render_pass, // Render pass layout the frambuffer will be used with
.attachment_count = @intCast(attachments.len),
.p_attachments = &attachments, // List of attachments (1:1 with render pass)
.width = self.swapchain.extent.width, // Framebuffer width
.height = self.swapchain.extent.height, // Framebuffer height
.layers = 1, // Framebuffer layers
};
self.swapchain.swapchain_framebuffers[i] = try self.ctx.device.createFramebuffer(&framebuffer_create_info, null);
}
}
fn createCommandPool(self: *Self) !void {
// Get indices of queue families from device
const queue_family_indices = try QueueUtils.getQueueFamilies(self.ctx, self.ctx.physical_device);
const pool_create_info: vk.CommandPoolCreateInfo = .{
// Queue family type that buffers from this command pool will use
.queue_family_index = queue_family_indices.graphics_family.?,
.flags = .{ .reset_command_buffer_bit = true },
};
// Create a graphics queue family command pool
self.graphics_command_pool = try self.ctx.device.createCommandPool(&pool_create_info, null);
}
fn createCommandBuffers(self: *Self) !void {
// Allocate one command buffer for each framebuffer
const command_buffer_handles = try self.allocator.alloc(vk.CommandBuffer, self.swapchain.swapchain_framebuffers.len);
defer self.allocator.free(command_buffer_handles);
self.command_buffers = try self.allocator.alloc(CommandBuffer, command_buffer_handles.len);
const command_buffer_allocate_info: vk.CommandBufferAllocateInfo = .{
.command_pool = self.graphics_command_pool,
.level = .primary, // primary: buffer you submit directly to queue. Can't be called by other buffers
.command_buffer_count = @intCast(command_buffer_handles.len),
};
// Allocate command buffers and place handles in array of buffers
try self.ctx.device.allocateCommandBuffers(&command_buffer_allocate_info, command_buffer_handles.ptr);
for (command_buffer_handles, 0..) |command_buffer_handle, i| {
self.command_buffers[i] = CommandBuffer.init(command_buffer_handle, self.ctx.device.wrapper);
}
}
fn createSynchronisation(self: *Self) !void {
// Fence create information
const fence_create_info: vk.FenceCreateInfo = .{ .flags = .{ .signaled_bit = true } };
// Semaphore creation information
for (0..MAX_FRAME_DRAWS) |i| {
self.image_available[i] = try self.ctx.device.createSemaphore(&.{}, null);
self.render_finished[i] = try self.ctx.device.createSemaphore(&.{}, null);
self.draw_fences[i] = try self.ctx.device.createFence(&fence_create_info, null);
}
}
fn createTextureSampler(self: *Self) !void {
// Sampler create info
const sampler_create_info: vk.SamplerCreateInfo = .{
.mag_filter = .linear, // How to render when image is magnified on screen
.min_filter = .linear, // How to render when image is minified on screen
.address_mode_u = .repeat, // How to handle texture wrap in U (x direction)
.address_mode_v = .repeat, // How to handle texture wrap in U (y direction)
.address_mode_w = .repeat, // How to handle texture wrap in U (z direction)
.border_color = .int_opaque_black, // Border beyond texture (only works for border clamp)
.unnormalized_coordinates = vk.FALSE, // Whether coords should be normalized (between 0 and 1)
.mipmap_mode = .linear, // Mipmap interpolation mode
.mip_lod_bias = 0.0, // Level of detail bias for mip level
.min_lod = 0.0, // Minimum lod to pick mip level
.max_lod = 0.0, // Maximum lod to pick mip level
.anisotropy_enable = vk.TRUE, // Enable anisotropy
.max_anisotropy = 16.0, // Anisotropy sample level
.compare_enable = vk.FALSE,
.compare_op = .never,
};
self.texture_sampler = try self.ctx.device.createSampler(&sampler_create_info, null);
}
fn createUniformBuffers(self: *Self) !void {
// View projection buffer size
const vp_buffer_size: vk.DeviceSize = @sizeOf(UboViewProjection);
// One uniform buffer for each image (and by extension, command buffer)
self.vp_uniform_buffer = try self.allocator.alloc(vk.Buffer, self.swapchain.swapchain_images.len);
self.vp_uniform_buffer_memory = try self.allocator.alloc(vk.DeviceMemory, self.swapchain.swapchain_images.len);
// Create the uniform buffers
for (0..self.vp_uniform_buffer.len) |i| {
try Utilities.createBuffer(
self.ctx.physical_device,
self.ctx.instance,
self.ctx.device,
vp_buffer_size,
.{ .uniform_buffer_bit = true },
.{ .host_visible_bit = true, .host_coherent_bit = true },
&self.vp_uniform_buffer[i],
&self.vp_uniform_buffer_memory[i],
);
}
}
fn createDescriptorPool(self: *Self) !void {
// -- Create uniform descriptor pool --
// Type of descriptors + how many descriptors (!= descriptor sets) (combined makes the pool size)
// View projection pool
const vp_pool_size: vk.DescriptorPoolSize = .{
.type = .uniform_buffer,
.descriptor_count = @intCast(self.vp_uniform_buffer.len),
};
// List of pool sizes
const descriptor_pool_sizes = [_]vk.DescriptorPoolSize{vp_pool_size};
// Data to create descriptor pool
const pool_create_info: vk.DescriptorPoolCreateInfo = .{
.max_sets = @intCast(self.swapchain.swapchain_images.len), // Maximum number of descriptor sets that can be created from pool
.pool_size_count = @intCast(descriptor_pool_sizes.len), // Amount of pool sizes being passed
.p_pool_sizes = &descriptor_pool_sizes, // Pool sizes to create pool with
};
// Create descriptor pool
self.descriptor_pool = try self.ctx.device.createDescriptorPool(&pool_create_info, null);
// -- Create sampler descriptor pool --
// Texture sampler pool
const sampler_pool_size: vk.DescriptorPoolSize = .{
.type = .combined_image_sampler,
.descriptor_count = MAX_OBJECTS,
};
// FIXME Not the best (look into array layers)
const sampler_pool_create_info: vk.DescriptorPoolCreateInfo = .{
.max_sets = MAX_OBJECTS,
.pool_size_count = 1,
.p_pool_sizes = @ptrCast(&sampler_pool_size),
};
self.sampler_descriptor_pool = try self.ctx.device.createDescriptorPool(&sampler_pool_create_info, null);
// -- Create input attachment descriptor pool
// Colour attachment pool size
const colour_input_pool_size: vk.DescriptorPoolSize = .{
.type = .input_attachment,
.descriptor_count = @intCast(self.colour_buffer_image_view.len),
};
// Depth attachment pool size
const depth_input_pool_size: vk.DescriptorPoolSize = .{
.type = .input_attachment,
.descriptor_count = @intCast(self.depth_buffer_image_view.len),
};
const input_pool_sizes = [_]vk.DescriptorPoolSize{ colour_input_pool_size, depth_input_pool_size };
// Create input attachment pool
const input_pool_create_info: vk.DescriptorPoolCreateInfo = .{
.max_sets = @intCast(self.swapchain.swapchain_images.len),
.pool_size_count = @intCast(input_pool_sizes.len),
.p_pool_sizes = &input_pool_sizes,
};
self.input_descriptor_pool = try self.ctx.device.createDescriptorPool(&input_pool_create_info, null);
}
fn createDescriptorSets(self: *Self) !void {
// One descriptor set for every buffer
self.descriptor_sets = try self.allocator.alloc(vk.DescriptorSet, self.swapchain.swapchain_images.len);
var set_layouts = try self.allocator.alloc(vk.DescriptorSetLayout, self.swapchain.swapchain_images.len);
defer self.allocator.free(set_layouts);
for (0..set_layouts.len) |i| {
set_layouts[i] = self.descriptor_set_layout;
}
// Descriptor set allocation info
const set_alloc_info: vk.DescriptorSetAllocateInfo = .{
.descriptor_pool = self.descriptor_pool, // Pool to allocate descriptor set from
.descriptor_set_count = @intCast(self.swapchain.swapchain_images.len), // Number of sets to allocate
.p_set_layouts = set_layouts.ptr, // Layouts to use to allocate sets (1:1 relationship)
};
// Allocate descriptor sets (multiple)
try self.ctx.device.allocateDescriptorSets(&set_alloc_info, self.descriptor_sets.ptr);
// Update all of descriptor set buffer bindings
for (0..self.swapchain.swapchain_images.len) |i| {
// -- View projection descriptor
// Buffer info and data offset info
const vp_buffer_info: vk.DescriptorBufferInfo = .{
.buffer = self.vp_uniform_buffer[i], // Bufer to get data from
.offset = 0, // Position of start of data
.range = @sizeOf(UboViewProjection), // Size of data
};
// Data about connection between binding and buffer
const vp_set_write: vk.WriteDescriptorSet = .{
.dst_set = self.descriptor_sets[i], // Descriptor set to update
.dst_binding = 0, // Binding to update (matches with binding on layout/shader)
.dst_array_element = 0, // Index in array to update
.descriptor_type = .uniform_buffer, // Type of descriptor
.descriptor_count = 1, // Amount to update
.p_buffer_info = @ptrCast(&vp_buffer_info), // Information about buffer data to bind
.p_image_info = undefined,
.p_texel_buffer_view = undefined,
};
// List of descriptor set writes
const set_writes = [_]vk.WriteDescriptorSet{vp_set_write};
// Update the descriptor sets with new buffer/binding info
self.ctx.device.updateDescriptorSets(@intCast(set_writes.len), &set_writes, 0, null);
}
}
fn createInputDescriptorSets(self: *Self) !void {
self.input_descriptor_sets = try self.allocator.alloc(vk.DescriptorSet, self.swapchain.swapchain_images.len);
// Fill array of layouts ready for set creation
var set_layouts = try self.allocator.alloc(vk.DescriptorSetLayout, self.swapchain.swapchain_images.len);
defer self.allocator.free(set_layouts);
for (0..set_layouts.len) |i| {
set_layouts[i] = self.input_set_layout;
}
// Input attachment descriptor set allocation info
const set_alloc_info: vk.DescriptorSetAllocateInfo = .{
.descriptor_pool = self.input_descriptor_pool,
.descriptor_set_count = @intCast(self.swapchain.swapchain_images.len),
.p_set_layouts = set_layouts.ptr,
};
// Allocate descriptor sets
try self.ctx.device.allocateDescriptorSets(&set_alloc_info, self.input_descriptor_sets.ptr);
// Update each descriptor set with input attachment
for (0..self.swapchain.swapchain_images.len) |i| {
// Colour attachment descriptor
const colour_attachment_descriptor: vk.DescriptorImageInfo = .{
.image_layout = .shader_read_only_optimal,
.image_view = self.colour_buffer_image_view[i],
.sampler = .null_handle,
};
// Colour attachment descriptor write
const colour_write: vk.WriteDescriptorSet = .{
.dst_set = self.input_descriptor_sets[i],
.dst_binding = 0,
.dst_array_element = 0,
.descriptor_type = .input_attachment,
.descriptor_count = 1,
.p_image_info = @ptrCast(&colour_attachment_descriptor),
.p_buffer_info = undefined,
.p_texel_buffer_view = undefined,
};
// Depth attachment descriptor
const depth_attachment_descriptor: vk.DescriptorImageInfo = .{
.image_layout = .shader_read_only_optimal,
.image_view = self.depth_buffer_image_view[i],
.sampler = .null_handle,
};
// Depth attachment descriptor write
const depth_write: vk.WriteDescriptorSet = .{
.dst_set = self.input_descriptor_sets[i],
.dst_binding = 1,
.dst_array_element = 0,
.descriptor_type = .input_attachment,
.descriptor_count = 1,
.p_image_info = @ptrCast(&depth_attachment_descriptor),
.p_buffer_info = undefined,
.p_texel_buffer_view = undefined,
};
// List of input descriptor set writes
const set_writes = [_]vk.WriteDescriptorSet{ colour_write, depth_write };
// Update descriptor sets
self.ctx.device.updateDescriptorSets(@intCast(set_writes.len), &set_writes, 0, null);
}
}
fn updateUniformBuffers(self: *Self, image_index: u32) !void {
// Copy VP data
const data = try self.ctx.device.mapMemory(
self.vp_uniform_buffer_memory[image_index],
0,
@sizeOf(UboViewProjection),
.{},
);
const vp_data: *UboViewProjection = @ptrCast(@alignCast(data));
vp_data.* = self.ubo_view_projection;
self.ctx.device.unmapMemory(self.vp_uniform_buffer_memory[image_index]);
}
fn recordCommands(self: *Self, current_image: u32) !void {
// Information about how to begin each command
const buffer_begin_info: vk.CommandBufferBeginInfo = .{
// Buffer can be resubmitted when it has already been submitted and is awaiting execution
.flags = .{ .simultaneous_use_bit = true },
};
const clear_values = [_]vk.ClearValue{
.{ .color = .{ .float_32 = .{ 0.0, 0.0, 0.0, 0.0 } } },
.{ .color = .{ .float_32 = .{ 0.6, 0.65, 0.4, 1.0 } } },
.{ .depth_stencil = .{ .depth = 1.0, .stencil = 1 } },
};
// Information about how to begin a render pass (only needed for graphical application)
var render_pass_begin_info: vk.RenderPassBeginInfo = .{
.render_pass = self.render_pass, // Render pass to begin
.render_area = .{
.offset = .{ .x = 0, .y = 0 }, // Start point of render pass in pixels
.extent = self.swapchain.extent, // Size of region to run render pass on (starting at offset)
},
.p_clear_values = &clear_values, // List of clear values
.clear_value_count = @intCast(clear_values.len),
.framebuffer = self.swapchain.swapchain_framebuffers[current_image],
};
const command_buffer = self.command_buffers[current_image];
// Start recording commands to command buffer
try command_buffer.beginCommandBuffer(&buffer_begin_info);
{
// Begin render pass
command_buffer.beginRenderPass(&render_pass_begin_info, vk.SubpassContents.@"inline");
// Needed when using dynamic state
command_buffer.setViewport(0, 1, @ptrCast(&self.viewport));
command_buffer.setScissor(0, 1, @ptrCast(&self.scissor));
// Bind pipeline to be used in render pass
command_buffer.bindPipeline(.graphics, self.graphics_pipeline);
for (self.model_list.items) |model| {
// Push constants to given shader stage directly (no buffer)
command_buffer.pushConstants(
self.pipeline_layout,
.{ .vertex_bit = true }, // Stage to push constants to
0, // Offset of push constants to update
@sizeOf(Model), // Size of data being pushed
@ptrCast(&model.model), // Actual data being pushed (can be array)
);
for (model.mesh_list.items) |mesh| {
// Buffers to bind
const vertex_buffers = [_]vk.Buffer{mesh.vertex_buffer};
// Offsets into buffers being bound
const offsets = [_]vk.DeviceSize{0};
// Command to bind vertex buffer before drawing with them
command_buffer.bindVertexBuffers(0, 1, &vertex_buffers, &offsets);
// Bind mesh index buffer, with 0 offset and using the uint32 type
command_buffer.bindIndexBuffer(mesh.index_buffer, 0, .uint32);
const descriptor_set_group = [_]vk.DescriptorSet{
self.descriptor_sets[current_image],
self.sampler_descriptor_sets.items[mesh.tex_id],
};
// Bind descriptor sets
command_buffer.bindDescriptorSets(
.graphics,
self.pipeline_layout,
0,
@intCast(descriptor_set_group.len),
&descriptor_set_group,
0,
null,
);
// Execute a pipeline
command_buffer.drawIndexed(mesh.index_count, 1, 0, 0, 0);
}
}
// Start second subpass
command_buffer.nextSubpass(.@"inline");
command_buffer.bindPipeline(.graphics, self.second_pipeline);
command_buffer.bindDescriptorSets(
.graphics,
self.second_pipeline_layout,
0,
1,
@ptrCast(&self.input_descriptor_sets[current_image]),
0,
null,
);
command_buffer.draw(3, 1, 0, 0);
// End render pass
command_buffer.endRenderPass();
}
// Stop recording to command buffer
try command_buffer.endCommandBuffer();
}
pub fn createMeshModel(self: *Self, model_file: []const u8) !usize {
// Pass tex smapler
MeshModel.new(
self.allocator,
self.ctx,
self.graphics_command_pool,
self.texture_sampler,
model_file,
);
}
};
fn chooseSupportedFormat(
pdev: vk.PhysicalDevice,
instance: Instance,
formats: []const vk.Format,
tiling: vk.ImageTiling,
feature_flags: vk.FormatFeatureFlags,
) ?vk.Format {
// Loop through the options and find a compatible one
// Depending on tiling choice. Need to check for different bit flag
for (formats) |format| {
// Get properties for given format on this device
const properties = instance.getPhysicalDeviceFormatProperties(pdev, format);
if (tiling == .linear and properties.linear_tiling_features.contains(feature_flags)) {
return format;
} else if (tiling == .optimal and properties.optimal_tiling_features.contains(feature_flags)) {
return format;
}
}
return null;
}