diff --git a/src/Mesh.zig b/src/Mesh.zig index 9cb3e4a..fb46b79 100644 --- a/src/Mesh.zig +++ b/src/Mesh.zig @@ -1,12 +1,17 @@ const std = @import("std"); const vk = @import("vulkan"); +const zm = @import("zmath"); + const Utilities = @import("utilities.zig"); const Vertex = Utilities.Vertex; const Device = @import("vulkan_renderer.zig").Device; const Instance = @import("vulkan_renderer.zig").Instance; +const UboModel = @import("vulkan_renderer.zig").UboModel; const Self = @This(); +ubo_model: UboModel, + vertex_count: u32, vertex_buffer: vk.Buffer, vertex_buffer_memory: vk.DeviceMemory, @@ -44,6 +49,8 @@ pub fn new( try self.createVertexBuffer(transfer_queue, transfer_command_pool, vertices); try self.createIndexBuffer(transfer_queue, transfer_command_pool, indices); + self.ubo_model = .{ .model = zm.identity() }; + return self; } diff --git a/src/main.zig b/src/main.zig index 026747e..98c8fd8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -47,7 +47,17 @@ pub fn main() !void { angle -= 360.0; } - try vulkan_renderer.updateModel(zm.rotationZ(angle)); + var first_model = zm.identity(); + var second_model = zm.identity(); + + first_model = zm.mul(first_model, zm.rotationZ(angle)); + first_model = zm.mul(first_model, zm.translation(-2.0, 0.0, -5.0)); + + second_model = zm.mul(second_model, zm.rotationZ(-angle * 2)); + second_model = zm.mul(second_model, zm.translation(2.0, 0.0, -5.0)); + + try vulkan_renderer.updateModel(0, first_model); + try vulkan_renderer.updateModel(1, second_model); try vulkan_renderer.draw(); } diff --git a/src/shaders/shader.vert b/src/shaders/shader.vert index 19219b0..27aa613 100644 --- a/src/shaders/shader.vert +++ b/src/shaders/shader.vert @@ -3,15 +3,18 @@ layout(location = 0) in vec3 pos; layout(location = 1) in vec3 col; -layout(binding = 0) uniform MVP { +layout(binding = 0) uniform UboViewProjection { mat4 projection; mat4 view; +} uboViewProjection; + +layout(binding = 1) uniform UboModel { mat4 model; -} mvp; +} uboModel; layout(location = 0) out vec3 fragCol; void main() { - gl_Position = mvp.projection * mvp.view * mvp.model * vec4(pos, 1.0); + gl_Position = uboViewProjection.projection * uboViewProjection.view * uboModel.model * vec4(pos, 1.0); fragCol = col; } diff --git a/src/vulkan_renderer.zig b/src/vulkan_renderer.zig index 5d41005..f2c9401 100644 --- a/src/vulkan_renderer.zig +++ b/src/vulkan_renderer.zig @@ -17,6 +17,7 @@ const enable_validation_layers = builtin.mode == .Debug; const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; const MAX_FRAME_DRAWS: u32 = 2; +const MAX_OBJECTS: u32 = 2; const apis: []const vk.ApiInfo = &.{ vk.features.version_1_0, @@ -37,15 +38,18 @@ pub const Device = vk.DeviceProxy(apis); pub const Queue = vk.QueueProxy(apis); pub const CommandBuffer = vk.CommandBufferProxy(apis); +const UboViewProjection = struct { + projection: zm.Mat align(16), + view: zm.Mat align(16), +}; + +pub const UboModel = struct { + model: zm.Mat align(16), +}; + pub const VulkanRenderer = struct { const Self = @This(); - const Mvp = struct { - projection: zm.Mat, - view: zm.Mat, - model: zm.Mat, - }; - allocator: std.mem.Allocator, vkb: BaseDispatch, @@ -58,7 +62,7 @@ pub const VulkanRenderer = struct { meshes: [2]Mesh, // Scene settings - mvp: Mvp, + ubo_view_projection: UboViewProjection, // Main instance: Instance, @@ -81,8 +85,11 @@ pub const VulkanRenderer = struct { descriptor_pool: vk.DescriptorPool, descriptor_sets: []vk.DescriptorSet, - uniform_buffer: []vk.Buffer, - uniform_buffer_memory: []vk.DeviceMemory, + vp_uniform_buffer: []vk.Buffer, + vp_uniform_buffer_memory: []vk.DeviceMemory, + + model_duniform_buffer: []vk.Buffer, + model_duniform_buffer_memory: []vk.DeviceMemory, // Pipeline graphics_pipeline: vk.Pipeline, @@ -96,6 +103,10 @@ pub const VulkanRenderer = struct { swapchain_image_format: vk.Format, extent: vk.Extent2D, + min_uniform_buffer_offset: vk.DeviceSize, + model_uniform_alignment: usize, + model_transfer_space: [MAX_OBJECTS]UboModel, + // Synchronisation image_available: [MAX_FRAME_DRAWS]vk.Semaphore, render_finished: [MAX_FRAME_DRAWS]vk.Semaphore, @@ -128,36 +139,35 @@ pub const VulkanRenderer = struct { try self.createCommandPool(); const aspect: f32 = @as(f32, @floatFromInt(self.extent.width)) / @as(f32, @floatFromInt(self.extent.height)); - self.mvp.projection = zm.perspectiveFovRh( + self.ubo_view_projection.projection = zm.perspectiveFovRh( std.math.degreesToRadians(45.0), aspect, 0.1, 100.0, ); - self.mvp.view = zm.lookAtRh( + self.ubo_view_projection.view = zm.lookAtRh( zm.Vec{ 0.0, 0.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 }, ); - self.mvp.model = zm.identity(); // Invert y scale - self.mvp.projection[1][1] *= -1; + self.ubo_view_projection.projection[1][1] *= -1; // 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 + .{ .pos = .{ -0.4, 0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 } }, // 0 + .{ .pos = .{ -0.4, -0.4, 0.0 }, .col = .{ 0.0, 1.0, 0.0 } }, // 1 + .{ .pos = .{ 0.4, -0.4, 0.0 }, .col = .{ 0.0, 0.0, 1.0 } }, // 2 + .{ .pos = .{ 0.4, 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 + .{ .pos = .{ -0.25, 0.6, 0.0 }, .col = .{ 1.0, 0.0, 0.0 } }, // 0 + .{ .pos = .{ -0.25, -0.6, 0.0 }, .col = .{ 0.0, 1.0, 0.0 } }, // 1 + .{ .pos = .{ 0.25, -0.6, 0.0 }, .col = .{ 0.0, 0.0, 1.0 } }, // 2 + .{ .pos = .{ 0.25, 0.6, 0.0 }, .col = .{ 0.0, 1.0, 0.0 } }, // 3 }; // Index Data @@ -191,7 +201,7 @@ pub const VulkanRenderer = struct { self.meshes = [_]Mesh{ first_mesh, second_mesh }; try self.createCommandBuffers(); - + try self.allocateDynamicBufferTransferSpace(); try self.createUniformBuffers(); try self.createDescriptorPool(); try self.createDescriptorSets(); @@ -202,8 +212,10 @@ pub const VulkanRenderer = struct { return self; } - pub fn updateModel(self: *Self, new_model: zm.Mat) !void { - self.mvp.model = new_model; + pub fn updateModel(self: *Self, model_id: u32, new_model: zm.Mat) !void { + if (model_id < self.meshes.len) { + self.meshes[model_id].ubo_model.model = new_model; + } } pub fn draw(self: *Self) !void { @@ -221,7 +233,7 @@ pub const VulkanRenderer = struct { .null_handle, ); - try self.updateUniformBuffer(image_index_result.image_index); + try self.updateUniformBuffers(image_index_result.image_index); // -- Submit command buffer to render // Queue submission information @@ -266,12 +278,16 @@ pub const VulkanRenderer = struct { self.device.destroyDescriptorPool(self.descriptor_pool, null); self.device.destroyDescriptorSetLayout(self.descriptor_set_layout, null); - for (self.uniform_buffer, self.uniform_buffer_memory) |buffer, buffer_memory| { - self.device.destroyBuffer(buffer, null); - self.device.freeMemory(buffer_memory, null); + for (0..self.swapchain_images.len) |i| { + self.device.destroyBuffer(self.vp_uniform_buffer[i], null); + self.device.freeMemory(self.vp_uniform_buffer_memory[i], null); + self.device.destroyBuffer(self.model_duniform_buffer[i], null); + self.device.freeMemory(self.model_duniform_buffer_memory[i], null); } - self.allocator.free(self.uniform_buffer); - self.allocator.free(self.uniform_buffer_memory); + self.allocator.free(self.vp_uniform_buffer); + self.allocator.free(self.vp_uniform_buffer_memory); + self.allocator.free(self.model_duniform_buffer); + self.allocator.free(self.model_duniform_buffer_memory); self.allocator.free(self.descriptor_sets); for (self.meshes) |mesh| { @@ -551,8 +567,8 @@ pub const VulkanRenderer = struct { } fn createDescriptorSetLayout(self: *Self) !void { - // MVP binding info - const mvp_layout_binding: vk.DescriptorSetLayoutBinding = .{ + // 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 @@ -560,10 +576,21 @@ pub const VulkanRenderer = struct { .p_immutable_samplers = null, // For texture: can make smapler data immutable by specifying in layout }; + // Model binding info + const model_layout_binding: vk.DescriptorSetLayoutBinding = .{ + .binding = 1, + .descriptor_type = .uniform_buffer_dynamic, + .descriptor_count = 1, + .stage_flags = .{ .vertex_bit = true }, + .p_immutable_samplers = null, + }; + + const layout_bindings = [_]vk.DescriptorSetLayoutBinding{ vp_layout_binding, model_layout_binding }; + // Create descriptor set layout with given bindings const layout_create_info: vk.DescriptorSetLayoutCreateInfo = .{ - .binding_count = 1, // Number of binding infos - .p_bindings = @ptrCast(&mvp_layout_binding), // Array of binding infos + .binding_count = @intCast(layout_bindings.len), // Number of binding infos + .p_bindings = &layout_bindings, // Array of binding infos }; // Create descriptor set layout @@ -831,40 +858,66 @@ pub const VulkanRenderer = struct { } fn createUniformBuffers(self: *Self) !void { - // Buffer size will be size of all three variables (will offset to access) - const buffer_size: vk.DeviceSize = @sizeOf(@TypeOf(self.mvp)); + // View projection buffer size + const vp_buffer_size: vk.DeviceSize = @sizeOf(UboViewProjection); + + // Model buffer size + const model_buffer_size: vk.DeviceSize = self.model_uniform_alignment * MAX_OBJECTS; // One uniform buffer for each image (and by extension, command buffer) - self.uniform_buffer = try self.allocator.alloc(vk.Buffer, self.swapchain_images.len); - self.uniform_buffer_memory = try self.allocator.alloc(vk.DeviceMemory, self.swapchain_images.len); + self.vp_uniform_buffer = try self.allocator.alloc(vk.Buffer, self.swapchain_images.len); + self.vp_uniform_buffer_memory = try self.allocator.alloc(vk.DeviceMemory, self.swapchain_images.len); + self.model_duniform_buffer = try self.allocator.alloc(vk.Buffer, self.swapchain_images.len); + self.model_duniform_buffer_memory = try self.allocator.alloc(vk.DeviceMemory, self.swapchain_images.len); // Create the uniform buffers - for (0..self.uniform_buffer.len) |i| { + for (0..self.vp_uniform_buffer.len) |i| { try Utilities.createBuffer( self.physical_device, self.instance, self.device, - buffer_size, + vp_buffer_size, .{ .uniform_buffer_bit = true }, .{ .host_visible_bit = true, .host_coherent_bit = true }, - &self.uniform_buffer[i], - &self.uniform_buffer_memory[i], + &self.vp_uniform_buffer[i], + &self.vp_uniform_buffer_memory[i], + ); + + try Utilities.createBuffer( + self.physical_device, + self.instance, + self.device, + model_buffer_size, + .{ .uniform_buffer_bit = true }, + .{ .host_visible_bit = true, .host_coherent_bit = true }, + &self.model_duniform_buffer[i], + &self.model_duniform_buffer_memory[i], ); } } fn createDescriptorPool(self: *Self) !void { // Type of descriptors + how many descriptors (!= descriptor sets) (combined makes the pool size) - const pool_size: vk.DescriptorPoolSize = .{ + // View projection pool + const vp_pool_size: vk.DescriptorPoolSize = .{ .type = .uniform_buffer, - .descriptor_count = @intCast(self.uniform_buffer.len), + .descriptor_count = @intCast(self.vp_uniform_buffer.len), }; + // Model pool (dynamic) + const model_pool_size: vk.DescriptorPoolSize = .{ + .type = .uniform_buffer_dynamic, + .descriptor_count = @intCast(self.model_duniform_buffer.len), + }; + + // List of pool sizes + const descriptor_pool_sizes = [_]vk.DescriptorPoolSize{ vp_pool_size, model_pool_size }; + // Data to create descriptor pool const pool_create_info: vk.DescriptorPoolCreateInfo = .{ - .max_sets = @intCast(self.uniform_buffer.len), // Maximum number of descriptor sets that can be created from pool - .pool_size_count = 1, // Amount of pool sizes being passed - .p_pool_sizes = @ptrCast(&pool_size), // Pool sizes to create pool with + .max_sets = @intCast(self.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 @@ -873,9 +926,9 @@ pub const VulkanRenderer = struct { fn createDescriptorSets(self: *Self) !void { // One descriptor set for every buffer - self.descriptor_sets = try self.allocator.alloc(vk.DescriptorSet, self.uniform_buffer.len); + self.descriptor_sets = try self.allocator.alloc(vk.DescriptorSet, self.swapchain_images.len); - var set_layouts = try self.allocator.alloc(vk.DescriptorSetLayout, self.uniform_buffer.len); + var set_layouts = try self.allocator.alloc(vk.DescriptorSetLayout, self.swapchain_images.len); defer self.allocator.free(set_layouts); for (0..set_layouts.len) |i| { set_layouts[i] = self.descriptor_set_layout; @@ -884,7 +937,7 @@ pub const VulkanRenderer = struct { // 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.descriptor_sets.len), // Number of sets to allocate + .descriptor_set_count = @intCast(self.swapchain_images.len), // Number of sets to allocate .p_set_layouts = set_layouts.ptr, // Layouts to use to allocate sets (1:1 relationship) }; @@ -892,37 +945,83 @@ pub const VulkanRenderer = struct { try self.device.allocateDescriptorSets(&set_alloc_info, self.descriptor_sets.ptr); // Update all of descriptor set buffer bindings - for (0..self.descriptor_sets.len) |i| { + for (0..self.swapchain_images.len) |i| { + // -- View projection descriptor // Buffer info and data offset info - const mvp_buffer_info: vk.DescriptorBufferInfo = .{ - .buffer = self.uniform_buffer[i], // Bufer to get data from + 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(@TypeOf(self.mvp)), // Size of data + .range = @sizeOf(UboViewProjection), // Size of data }; // Data about connection between binding and buffer - const mvp_set_write: vk.WriteDescriptorSet = .{ + 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(&mvp_buffer_info), // Information about buffer data to bind + .p_buffer_info = @ptrCast(&vp_buffer_info), // Information about buffer data to bind .p_image_info = undefined, .p_texel_buffer_view = undefined, }; + // -- Model descriptor + // Model buffer binding info + const model_buffer_info: vk.DescriptorBufferInfo = .{ + .buffer = self.model_duniform_buffer[i], + .offset = 0, + .range = self.model_uniform_alignment, + }; + + const model_set_write: vk.WriteDescriptorSet = .{ + .dst_set = self.descriptor_sets[i], + .dst_binding = 1, + .dst_array_element = 0, + .descriptor_type = .uniform_buffer_dynamic, + .descriptor_count = 1, + .p_buffer_info = @ptrCast(&model_buffer_info), + .p_image_info = undefined, + .p_texel_buffer_view = undefined, + }; + + // List of descriptor set writes + const set_writes = [_]vk.WriteDescriptorSet{ vp_set_write, model_set_write }; + // Update the descriptor sets with new buffer/binding info - self.device.updateDescriptorSets(1, @ptrCast(&mvp_set_write), 0, null); + self.device.updateDescriptorSets(@intCast(set_writes.len), &set_writes, 0, null); } } - fn updateUniformBuffer(self: Self, image_index: u32) !void { - const data = try self.device.mapMemory(self.uniform_buffer_memory[image_index], 0, @sizeOf(Mvp), .{}); + fn updateUniformBuffers(self: *Self, image_index: u32) !void { + // Copy VP data + var data = try self.device.mapMemory( + self.vp_uniform_buffer_memory[image_index], + 0, + @sizeOf(UboViewProjection), + .{}, + ); - const mvp_data: *Mvp = @ptrCast(@alignCast(data)); - mvp_data.* = self.mvp; - self.device.unmapMemory(self.uniform_buffer_memory[image_index]); + const vp_data: *UboViewProjection = @ptrCast(@alignCast(data)); + vp_data.* = self.ubo_view_projection; + self.device.unmapMemory(self.vp_uniform_buffer_memory[image_index]); + + // Copy model data + for (self.meshes, 0..) |mesh, i| { + self.model_transfer_space[i] = mesh.ubo_model; + } + + // Map the list of model data + data = try self.device.mapMemory( + self.model_duniform_buffer_memory[image_index], + 0, + self.model_uniform_alignment * self.meshes.len, + .{}, + ); + + const model_data: [*]UboModel = @ptrCast(@alignCast(data)); + @memcpy(model_data, self.model_transfer_space[0..self.meshes.len]); + self.device.unmapMemory(self.model_duniform_buffer_memory[image_index]); } fn recordCommands(self: *Self) !void { @@ -962,7 +1061,7 @@ pub const VulkanRenderer = struct { command_buffer.setViewport(0, 1, @ptrCast(&self.viewport)); command_buffer.setScissor(0, 1, @ptrCast(&self.scissor)); - for (self.meshes) |mesh| { + for (self.meshes, 0..) |mesh, j| { // Bind pipeline to be used in render pass command_buffer.bindPipeline(.graphics, self.graphics_pipeline); @@ -976,6 +1075,9 @@ pub const VulkanRenderer = struct { // Bind mesh index buffer, with 0 offset and using the uint32 type command_buffer.bindIndexBuffer(mesh.index_buffer, 0, .uint32); + // Dynamic offset amount + const dynamic_offset: u32 = @intCast(self.model_uniform_alignment * j); + // Bind descriptor sets command_buffer.bindDescriptorSets( .graphics, @@ -983,8 +1085,8 @@ pub const VulkanRenderer = struct { 0, 1, @ptrCast(&self.descriptor_sets[i]), - 0, - null, + 1, + @ptrCast(&dynamic_offset), ); // Execute a pipeline @@ -1018,6 +1120,21 @@ pub const VulkanRenderer = struct { // TODO Obviously needs to be something else unreachable; } + + // Get properties of our new device + const device_props = self.instance.getPhysicalDeviceProperties(self.physical_device); + + self.min_uniform_buffer_offset = device_props.limits.min_uniform_buffer_offset_alignment; + } + + fn allocateDynamicBufferTransferSpace(self: *Self) !void { + // TODO Needed in zig (we have align())? + // Calculate alignment of model data + self.model_uniform_alignment = + (@sizeOf(UboModel) + self.min_uniform_buffer_offset - 1) & ~(self.min_uniform_buffer_offset - 1); + + // Create space in memory to hold dynamic buffer that is aligned to our required alignment and holds MAX_OBJECTS + // self.model_transfer_space = try self.allocator.create(UboModel); } fn getRequiredExtensions(self: Self) ![][*:0]const u8 {