diff --git a/build.zig.zon b/build.zig.zon index 47cc462..7a2fd1e 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,12 +5,12 @@ .dependencies = .{ .vulkan_zig = .{ - .url = "https://github.com/Snektron/vulkan-zig/archive/66b7b773bb61e2102025f2d5ff0ae8c5f53e19cc.tar.gz", - .hash = "12208958f173b8b81bfac797955f0416ab38b21d1f69d4ebf6c7ca460a828a41cd45", + .url = "https://github.com/Snektron/vulkan-zig/archive/9f6e6177b1fdb3ed22231d9216a24480e84cfa5e.tar.gz", + .hash = "1220f2961df224f7d35dee774b26194b8b937cc252fa8e4023407776c58521d53e38", }, .sdl = .{ - .url = "https://github.com/ikskuh/SDL.zig/archive/fac81ec499cfd64da7b846de27f6db4a0d4943bf.tar.gz", - .hash = "12206c3d312175cf6a1bf1e8247ace5ac49ed8be80a94d8857d7d41fd7d1aee7ac4b", + .url = "https://github.com/MasterQ32/SDL.zig/archive/1432ed3f6a020973906fbc996868131ae1d631be.tar.gz", + .hash = "1220ebeeaade31e207a56977aff537a65e6338cddc68d50217ddf30bbc58fb27d367", }, }, diff --git a/src/Mesh.zig b/src/Mesh.zig index fb0bdcf..9cb3e4a 100644 --- a/src/Mesh.zig +++ b/src/Mesh.zig @@ -11,84 +11,166 @@ vertex_count: u32, vertex_buffer: vk.Buffer, vertex_buffer_memory: vk.DeviceMemory, +index_count: u32, +index_buffer: vk.Buffer, +index_buffer_memory: vk.DeviceMemory, + instance: Instance, physical_device: vk.PhysicalDevice, device: Device, -pub fn new(instance: Instance, pdev: vk.PhysicalDevice, device: Device, vertices: []const Vertex) !Self { - var mesh: Self = undefined; +allocator: std.mem.Allocator, - mesh.vertex_count = @intCast(vertices.len); - mesh.instance = instance; - mesh.physical_device = pdev; - mesh.device = device; - try mesh.createVertexBuffer(vertices); +pub fn new( + instance: Instance, + pdev: vk.PhysicalDevice, + device: Device, + transfer_queue: vk.Queue, + transfer_command_pool: vk.CommandPool, + vertices: []const Vertex, + indices: []const u32, + allocator: std.mem.Allocator, +) !Self { + var self: Self = undefined; - return mesh; + self.vertex_count = @intCast(vertices.len); + self.index_count = @intCast(indices.len); + + self.instance = instance; + self.physical_device = pdev; + self.device = device; + self.allocator = allocator; + + try self.createVertexBuffer(transfer_queue, transfer_command_pool, vertices); + try self.createIndexBuffer(transfer_queue, transfer_command_pool, indices); + + return self; } -pub fn destroyVertexBuffer(self: Self) void { +pub fn destroyBuffers(self: Self) void { self.device.destroyBuffer(self.vertex_buffer, null); self.device.freeMemory(self.vertex_buffer_memory, null); + + self.device.destroyBuffer(self.index_buffer, null); + self.device.freeMemory(self.index_buffer_memory, null); } -fn createVertexBuffer(self: *Self, vertices: []const Vertex) !void { - // Create vertex buffer - // Information to create buffer (doesn't include assigning memory) - const buffer_create_info: vk.BufferCreateInfo = .{ - .size = @sizeOf(Vertex) * vertices.len, // Size of buffer (size of 1 vertex * number of vertices) - .usage = .{ .vertex_buffer_bit = true }, // Multiple types of buffer possible, we want vertex buffer - .sharing_mode = .exclusive, // Similar to swapchain images, can share vertex buffers - }; +fn createVertexBuffer( + self: *Self, + transfer_queue: vk.Queue, + transfer_command_pool: vk.CommandPool, + vertices: []const Vertex, +) !void { + // Get size of buffer needed for vertices + const buffer_size: vk.DeviceSize = @sizeOf(Vertex) * vertices.len; - self.vertex_buffer = try self.device.createBuffer(&buffer_create_info, null); + // Temporary buffer to "stage" vertex data before transfering to GPU + var staging_buffer: vk.Buffer = undefined; + var staging_buffer_memory: vk.DeviceMemory = undefined; + defer self.device.destroyBuffer(staging_buffer, null); + defer self.device.freeMemory(staging_buffer_memory, null); - // Get buffer memory requirements - const mem_requirements = self.device.getBufferMemoryRequirements(self.vertex_buffer); - - // Allocate memory to buffer - const allocate_info: vk.MemoryAllocateInfo = .{ - .allocation_size = mem_requirements.size, - .memory_type_index = self.findMemoryTypeIndex( - mem_requirements.memory_type_bits, // Index of memory type of physical device that has required bit flags - .{ - .host_visible_bit = true, // CPU can interact with memory - .host_coherent_bit = true, // Allows placement of data straight into buffer after mapping (otherwise would have to specify manually) - }, - ), - }; - - // Allocate memory to vkDeviceMemory - self.vertex_buffer_memory = try self.device.allocateMemory(&allocate_info, null); - - // Allocate memory to given vertex buffer - try self.device.bindBufferMemory(self.vertex_buffer, self.vertex_buffer_memory, 0); + // Create buffer and allocate memory to it + try Utilities.createBuffer( + self.physical_device, + self.instance, + self.device, + buffer_size, + .{ .transfer_src_bit = true }, + .{ .host_visible_bit = true, .host_coherent_bit = true }, + &staging_buffer, + &staging_buffer_memory, + ); // Map memory to vertex // 1. Create pointer to a point in normal memory // 2. Map the vertex buffer memory to that point - const data = try self.device.mapMemory(self.vertex_buffer_memory, 0, vk.WHOLE_SIZE, .{}); - + const data = try self.device.mapMemory(staging_buffer_memory, 0, buffer_size, .{}); // 3. Copy memory from vertices vector to the point in memory const gpu_vertices: [*]Vertex = @ptrCast(@alignCast(data)); @memcpy(gpu_vertices, vertices[0..]); - // 4. Unmap the vertex buffer memory - self.device.unmapMemory(self.vertex_buffer_memory); + self.device.unmapMemory(staging_buffer_memory); + + // --- + + // Create buffer with TRANSFER_DST_BIT to mark as recipient of transfer data (also VERTEX_BUFFER) + // Buffer memory is to be DEVICE_LOCAL_BIT meaning memory is on the GPU and only accessible by it and not CPU (host) + try Utilities.createBuffer( + self.physical_device, + self.instance, + self.device, + buffer_size, + .{ .transfer_dst_bit = true, .vertex_buffer_bit = true }, + .{ .device_local_bit = true }, + &self.vertex_buffer, + &self.vertex_buffer_memory, + ); + + // Copy staging buffer to vertex buffer on GPU + try Utilities.copyBuffer( + self.device, + transfer_queue, + transfer_command_pool, + staging_buffer, + self.vertex_buffer, + buffer_size, + self.allocator, + ); } -fn findMemoryTypeIndex(self: Self, allowed_types: u32, properties: vk.MemoryPropertyFlags) u32 { - // Get properties of physical device memory - const memory_properties = self.instance.getPhysicalDeviceMemoryProperties(self.physical_device); - const mem_type_count = memory_properties.memory_type_count; +fn createIndexBuffer( + self: *Self, + transfer_queue: vk.Queue, + transfer_command_pool: vk.CommandPool, + indices: []const u32, +) !void { + // Get size of buffer needed for indices + const buffer_size: vk.DeviceSize = @sizeOf(u32) * indices.len; - 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); - } - } + // Temporary buffer to "stage" vertex data before transfering to GPU + var staging_buffer: vk.Buffer = undefined; + var staging_buffer_memory: vk.DeviceMemory = undefined; + defer self.device.destroyBuffer(staging_buffer, null); + defer self.device.freeMemory(staging_buffer_memory, null); - unreachable; + try Utilities.createBuffer( + self.physical_device, + self.instance, + self.device, + buffer_size, + .{ .transfer_src_bit = true }, + .{ .host_visible_bit = true, .host_coherent_bit = true }, + &staging_buffer, + &staging_buffer_memory, + ); + + // Map memory to index buffer + const data = try self.device.mapMemory(staging_buffer_memory, 0, buffer_size, .{}); + const gpu_vertices: [*]u32 = @ptrCast(@alignCast(data)); + @memcpy(gpu_vertices, indices[0..]); + self.device.unmapMemory(staging_buffer_memory); + + // Create buffer for index data on GPU access only + try Utilities.createBuffer( + self.physical_device, + self.instance, + self.device, + buffer_size, + .{ .transfer_dst_bit = true, .index_buffer_bit = true }, + .{ .device_local_bit = true }, + &self.index_buffer, + &self.index_buffer_memory, + ); + + // Copy from staging buffer to GPU access buffer + try Utilities.copyBuffer( + self.device, + transfer_queue, + transfer_command_pool, + staging_buffer, + self.index_buffer, + buffer_size, + self.allocator, + ); } diff --git a/src/utilities.zig b/src/utilities.zig index 6b62c5e..f99c00f 100644 --- a/src/utilities.zig +++ b/src/utilities.zig @@ -1,6 +1,10 @@ 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 device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name}; pub const Vector3 = @Vector(3, f32); @@ -31,3 +35,121 @@ pub const SwapchainImage = struct { image: vk.Image, image_view: vk.ImageView, }; + +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); +} + +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, + allocator: std.mem.Allocator, +) !void { + // Command buffer to hold transfer commands + const transfer_command_buffer_handle = try allocator.create(vk.CommandBuffer); + defer allocator.destroy(transfer_command_buffer_handle); + // Free temporary buffer back to pool + defer device.freeCommandBuffers(transfer_command_pool, 1, @ptrCast(transfer_command_buffer_handle)); + + // Command buffer details + const alloc_info: vk.CommandBufferAllocateInfo = .{ + .command_pool = transfer_command_pool, + .level = .primary, + .command_buffer_count = 1, + }; + + // Allocate command buffer from pool + try device.allocateCommandBuffers(&alloc_info, @ptrCast(transfer_command_buffer_handle)); + const transfer_command_buffer = CommandBuffer.init(transfer_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 transfer_command_buffer.beginCommandBuffer(&begin_info); + + // 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)); + + // End commands + try transfer_command_buffer.endCommandBuffer(); + + // Queue submission information + const submit_info: vk.SubmitInfo = .{ + .command_buffer_count = 1, + .p_command_buffers = @ptrCast(&transfer_command_buffer.handle), + }; + + // Submit transfer command to transfer queue and wait until it finishes + try device.queueSubmit(transfer_queue, 1, @ptrCast(&submit_info), .null_handle); + try device.queueWaitIdle(transfer_queue); +} diff --git a/src/vulkan_renderer.zig b/src/vulkan_renderer.zig index a02a9e7..b518e9d 100644 --- a/src/vulkan_renderer.zig +++ b/src/vulkan_renderer.zig @@ -48,7 +48,7 @@ pub const VulkanRenderer = struct { current_frame: u32 = 0, // Scene objects - first_mesh: Mesh, + meshes: [2]Mesh, // Main instance: Instance, @@ -101,24 +101,58 @@ pub const VulkanRenderer = struct { try self.getPhysicalDevice(); try self.createLogicalDevice(); - - // Create mesh - var mesh_vertices = [_]Vertex{ - .{ .pos = .{ -0.5, -0.5, 0.0 }, .col = .{ 1.0, 0.0, 0.0 } }, - .{ .pos = .{ 0.5, -0.5, 0.0 }, .col = .{ 0.0, 1.0, 0.0 } }, - .{ .pos = .{ 0.5, 0.5, 0.0 }, .col = .{ 0.0, 0.0, 1.0 } }, - .{ .pos = .{ 0.5, 0.5, 0.0 }, .col = .{ 0.0, 0.0, 1.0 } }, - .{ .pos = .{ -0.5, 0.5, 0.0 }, .col = .{ 0.0, 1.0, 0.0 } }, - .{ .pos = .{ -0.5, -0.5, 0.0 }, .col = .{ 1.0, 0.0, 0.0 } }, - }; - - self.first_mesh = try Mesh.new(self.instance, self.physical_device, self.device, &mesh_vertices); - try self.createSwapchain(); try self.createRenderPass(); try self.createGraphicsPipeline(); try self.createFramebuffers(); try self.createCommandPool(); + + // Create meshes + // Vertex Data + var mesh_vertices = [_]Vertex{ + .{ .pos = .{ -0.1, -0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 } }, // 0 + .{ .pos = .{ -0.1, 0.4, 0.0 }, .col = .{ 0.0, 1.0, 0.0 } }, // 1 + .{ .pos = .{ -0.9, 0.4, 0.0 }, .col = .{ 0.0, 0.0, 1.0 } }, // 2 + .{ .pos = .{ -0.9, -0.4, 0.0 }, .col = .{ 0.0, 1.0, 0.0 } }, // 3 + }; + + var mesh_vertices2 = [_]Vertex{ + .{ .pos = .{ 0.9, -0.3, 0.0 }, .col = .{ 1.0, 0.0, 0.0 } }, // 0 + .{ .pos = .{ 0.9, 0.1, 0.0 }, .col = .{ 0.0, 1.0, 0.0 } }, // 1 + .{ .pos = .{ 0.1, 0.3, 0.0 }, .col = .{ 0.0, 0.0, 1.0 } }, // 2 + .{ .pos = .{ 0.1, -0.3, 0.0 }, .col = .{ 0.0, 1.0, 0.0 } }, // 3 + }; + + // Index Data + const mesh_indices = [_]u32{ + 0, 1, 2, + 2, 3, 0, + }; + + const first_mesh = try Mesh.new( + self.instance, + self.physical_device, + self.device, + self.graphics_queue.handle, + self.graphics_command_pool, + &mesh_vertices, + &mesh_indices, + self.allocator, + ); + + const second_mesh = try Mesh.new( + self.instance, + self.physical_device, + self.device, + self.graphics_queue.handle, + self.graphics_command_pool, + &mesh_vertices2, + &mesh_indices, + self.allocator, + ); + + self.meshes = [_]Mesh{ first_mesh, second_mesh }; + try self.createCommandBuffers(); try self.recordCommands(); try self.createSynchronisation(); @@ -181,7 +215,9 @@ pub const VulkanRenderer = struct { self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null); } - self.first_mesh.destroyVertexBuffer(); + for (self.meshes) |mesh| { + mesh.destroyBuffers(); + } for (0..MAX_FRAME_DRAWS) |i| { self.device.destroySemaphore(self.render_finished[i], null); @@ -745,23 +781,26 @@ pub const VulkanRenderer = struct { // 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)); + + for (self.meshes) |mesh| { // Bind pipeline to be used in render pass command_buffer.bindPipeline(.graphics, self.graphics_pipeline); // Buffers to bind - const vertex_buffers = [_]vk.Buffer{self.first_mesh.vertex_buffer}; + 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); - // Needed when using dynamic state - command_buffer.setViewport(0, 1, @ptrCast(&self.viewport)); - command_buffer.setScissor(0, 1, @ptrCast(&self.scissor)); + // Bind mesh index buffer, with 0 offset and using the uint32 type + command_buffer.bindIndexBuffer(mesh.index_buffer, 0, .uint32); // Execute a pipeline - command_buffer.draw(self.first_mesh.vertex_count, 1, 0, 0); + command_buffer.drawIndexed(mesh.index_count, 1, 0, 0, 0); } // End render pass