const std = @import("std"); const vk = @import("vulkan"); const Instance = @import("vulkan_renderer.zig").Instance; const Device = @import("vulkan_renderer.zig").Device; const CommandBuffer = @import("vulkan_renderer.zig").CommandBuffer; pub const Vector3 = @Vector(3, f32); pub const Vector2 = @Vector(2, f32); // Vertex data representation pub const Vertex = struct { pos: Vector3, // Vertex position (x, y, z) col: Vector3, // Vertex colour (r, g, b) tex: Vector2, // Texture coords (u, v) }; pub fn findMemoryTypeIndex(pdev: vk.PhysicalDevice, instance: Instance, allowed_types: u32, properties: vk.MemoryPropertyFlags) u32 { // Get properties of physical device memory const memory_properties = instance.getPhysicalDeviceMemoryProperties(pdev); const mem_type_count = memory_properties.memory_type_count; for (memory_properties.memory_types[0..mem_type_count], 0..mem_type_count) |mem_type, i| { // Index of memory type must match corresponding bit in allowed_types if (allowed_types & (@as(u32, 1) << @truncate(i)) != 0 and mem_type.property_flags.contains(properties)) { // Return the index of the valid memory type return @truncate(i); } } unreachable; } pub fn createBuffer( pdev: vk.PhysicalDevice, instance: Instance, device: Device, buffer_size: vk.DeviceSize, buffer_usage: vk.BufferUsageFlags, buffer_properties: vk.MemoryPropertyFlags, buffer: *vk.Buffer, buffer_memory: *vk.DeviceMemory, ) !void { // Create vertex buffer // Information to create buffer (doesn't include assigning memory) const buffer_create_info: vk.BufferCreateInfo = .{ .size = buffer_size, // Size of buffer (size of 1 vertex * number of vertices) .usage = buffer_usage, // Multiple types of buffer possible .sharing_mode = .exclusive, // Similar to swapchain images, can share vertex buffers }; buffer.* = try device.createBuffer(&buffer_create_info, null); // Get buffer memory requirements const mem_requirements = device.getBufferMemoryRequirements(buffer.*); // Allocate memory to buffer const allocate_info: vk.MemoryAllocateInfo = .{ .allocation_size = mem_requirements.size, .memory_type_index = findMemoryTypeIndex( pdev, instance, mem_requirements.memory_type_bits, // Index of memory type of physical device that has required bit flags // Host visible: CPU can interact with memory // Host coherent: Allows placement of data straight into buffer after mapping (otherwise would have to specify manually) buffer_properties, ), }; // Allocate memory to vkDeviceMemory buffer_memory.* = try device.allocateMemory(&allocate_info, null); // Allocate memory to given vertex buffer try device.bindBufferMemory(buffer.*, buffer_memory.*, 0); } fn beginCommandBuffer(device: Device, command_pool: vk.CommandPool) !CommandBuffer { // Command buffer to hold transfer commands var command_buffer_handle: vk.CommandBuffer = undefined; // Command buffer details const alloc_info: vk.CommandBufferAllocateInfo = .{ .command_pool = command_pool, .level = .primary, .command_buffer_count = 1, }; // Allocate command buffer from pool try device.allocateCommandBuffers(&alloc_info, @ptrCast(&command_buffer_handle)); const command_buffer = CommandBuffer.init(command_buffer_handle, device.wrapper); // Information to begin the command buffer record const begin_info: vk.CommandBufferBeginInfo = .{ .flags = .{ .one_time_submit_bit = true }, // We're only using the command buffer once, so set to one time submit }; // Begin recording transfer commands try command_buffer.beginCommandBuffer(&begin_info); return command_buffer; } fn endAndSubmitCommandBuffer(device: Device, command_buffer: CommandBuffer, command_pool: vk.CommandPool, queue: vk.Queue) !void { // End commands try command_buffer.endCommandBuffer(); // Queue submission information const submit_info: vk.SubmitInfo = .{ .command_buffer_count = 1, .p_command_buffers = @ptrCast(&command_buffer.handle), }; // Submit transfer command to transfer queue and wait until it finishes try device.queueSubmit(queue, 1, @ptrCast(&submit_info), .null_handle); try device.queueWaitIdle(queue); // Free temporary buffer back to pool device.freeCommandBuffers(command_pool, 1, @ptrCast(&command_buffer.handle)); } pub fn copyBuffer( device: Device, transfer_queue: vk.Queue, transfer_command_pool: vk.CommandPool, src_buffer: vk.Buffer, dst_buffer: vk.Buffer, buffer_size: vk.DeviceSize, ) !void { // Create buffer const transfer_command_buffer = try beginCommandBuffer(device, transfer_command_pool); // Region of data to copy from and to const buffer_copy_region: vk.BufferCopy = .{ .src_offset = 0, .dst_offset = 0, .size = buffer_size, }; // Command to copy src buffer to dst buffer transfer_command_buffer.copyBuffer(src_buffer, dst_buffer, 1, @ptrCast(&buffer_copy_region)); // Command to copy src buffer to dst buffer try endAndSubmitCommandBuffer(device, transfer_command_buffer, transfer_command_pool, transfer_queue); } pub fn copyImageBuffer( device: Device, transfer_queue: vk.Queue, transfer_command_pool: vk.CommandPool, src_buffer: vk.Buffer, image: vk.Image, width: u32, height: u32, ) !void { const transfer_command_buffer = try beginCommandBuffer(device, transfer_command_pool); const image_region: vk.BufferImageCopy = .{ .buffer_offset = 0, // Offset into data .buffer_row_length = 0, // Row length of data to calculate data spacing .buffer_image_height = 0, // Image height to calculate data spacing .image_subresource = .{ .aspect_mask = .{ .color_bit = true }, // Which aspect of image to copy .mip_level = 0, // Mipmap level to copy .base_array_layer = 0, // Starting array layer (if array) .layer_count = 1, // Number of layers of copy starting at base_array_layer }, .image_offset = .{ .x = 0, .y = 0, .z = 0 }, // Offset to image (as opposed to raw data buffer offset) .image_extent = .{ .width = width, .height = height, .depth = 1 }, // Size of the region to copy as x, y, z values }; transfer_command_buffer.copyBufferToImage(src_buffer, image, .transfer_dst_optimal, 1, @ptrCast(&image_region)); try endAndSubmitCommandBuffer(device, transfer_command_buffer, transfer_command_pool, transfer_queue); } pub fn transitionImageLayout( device: Device, queue: vk.Queue, command_pool: vk.CommandPool, image: vk.Image, old_layout: vk.ImageLayout, new_layout: vk.ImageLayout, ) !void { const command_buffer = try beginCommandBuffer(device, command_pool); var image_memory_barrier: vk.ImageMemoryBarrier = .{ .old_layout = old_layout, // Layout to transition from .new_layout = new_layout, // Layout to transition to .src_queue_family_index = vk.QUEUE_FAMILY_IGNORED, // Queue family to transition from .dst_queue_family_index = vk.QUEUE_FAMILY_IGNORED, // Queue family to transition to .image = image, // Image being accessed and modified as part of barrier .subresource_range = .{ .aspect_mask = .{ .color_bit = true }, // Aspect of image being altered .base_mip_level = 0, // First mip level to start alterations on .level_count = 1, // Number of mipmap levels to alter starting from base mipmap level .base_array_layer = 0, // First layer to start alterations on .layer_count = 1, // Number of layers to alter starting from base array layer }, .src_access_mask = .{}, // Memory access stage transition must happen after .dst_access_mask = .{}, // Memory access stage transition must happen before }; var src_stage: vk.PipelineStageFlags = .{}; var dst_stage: vk.PipelineStageFlags = .{}; // If transitioning from new image to image ready to receive data if (old_layout == vk.ImageLayout.undefined and new_layout == vk.ImageLayout.transfer_dst_optimal) { image_memory_barrier.dst_access_mask.transfer_write_bit = true; src_stage.top_of_pipe_bit = true; dst_stage.transfer_bit = true; } else if (old_layout == vk.ImageLayout.transfer_dst_optimal and new_layout == vk.ImageLayout.shader_read_only_optimal) { // If transitioning from transfer destination to shader readable image_memory_barrier.src_access_mask.transfer_write_bit = true; image_memory_barrier.dst_access_mask.shader_read_bit = true; src_stage.transfer_bit = true; dst_stage.fragment_shader_bit = true; } command_buffer.pipelineBarrier( src_stage, // Pipeline stages (match to src and dst access mask) dst_stage, .{}, // Dependency flags 0, // Memory barrier count null, // Memory barrier data 0, // Buffer memory barrier count null, // Buffer memory barrier data 1, // Image memory barrier count @ptrCast(&image_memory_barrier), // Image memory barrier data ); try endAndSubmitCommandBuffer(device, command_buffer, command_pool, queue); }