Add uniform buffers

This commit is contained in:
przmk 2024-07-22 00:27:24 +02:00
parent 8c60f79f7d
commit b1bbd65aaa
13 changed files with 5695 additions and 4 deletions

View file

@ -1,7 +1,10 @@
const std = @import("std");
const vk = @import("vulkan");
const sdl = @import("sdl2");
const zm = @import("zmath");
const VulkanRenderer = @import("vulkan_renderer.zig").VulkanRenderer;
const Vector3 = @import("utilities.zig").Vector3;
pub fn main() !void {
const window = try initWindow();
@ -16,6 +19,11 @@ pub fn main() !void {
var vulkan_renderer = try VulkanRenderer.init(window, allocator);
defer vulkan_renderer.deinit();
var angle: f32 = 0.0;
var now: u64 = sdl.getPerformanceCounter();
var last: u64 = 0;
var delta_time: f32 = 0.0;
mainLoop: while (true) {
while (sdl.pollEvent()) |ev| {
switch (ev) {
@ -29,6 +37,18 @@ pub fn main() !void {
}
}
last = now;
now = sdl.getPerformanceCounter();
delta_time = @as(f32, @floatFromInt(now - last)) * 1000.0 / @as(f32, @floatFromInt(now));
angle += 10.0 * delta_time;
if (angle > 360.0) {
angle -= 360.0;
}
try vulkan_renderer.updateModel(zm.rotationZ(angle));
try vulkan_renderer.draw();
}
}

View file

@ -3,9 +3,15 @@
layout(location = 0) in vec3 pos;
layout(location = 1) in vec3 col;
layout(binding = 0) uniform MVP {
mat4 projection;
mat4 view;
mat4 model;
} mvp;
layout(location = 0) out vec3 fragCol;
void main() {
gl_Position = vec4(pos, 1.0);
gl_Position = mvp.projection * mvp.view * mvp.model * vec4(pos, 1.0);
fragCol = col;
}

View file

@ -3,6 +3,7 @@ const sdl = @import("sdl2");
const vk = @import("vulkan");
const builtin = @import("builtin");
const shaders = @import("shaders");
const zm = @import("zmath");
const Utilities = @import("utilities.zig");
const QueueFamilyIndices = Utilities.QueueFamilyIndices;
@ -39,6 +40,12 @@ pub const CommandBuffer = vk.CommandBufferProxy(apis);
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,
@ -50,6 +57,9 @@ pub const VulkanRenderer = struct {
// Scene objects
meshes: [2]Mesh,
// Scene settings
mvp: Mvp,
// Main
instance: Instance,
physical_device: vk.PhysicalDevice,
@ -65,6 +75,15 @@ pub const VulkanRenderer = struct {
swapchain_framebuffers: []vk.Framebuffer,
command_buffers: []CommandBuffer,
// Descriptors
descriptor_set_layout: vk.DescriptorSetLayout,
descriptor_pool: vk.DescriptorPool,
descriptor_sets: []vk.DescriptorSet,
uniform_buffer: []vk.Buffer,
uniform_buffer_memory: []vk.DeviceMemory,
// Pipeline
graphics_pipeline: vk.Pipeline,
pipeline_layout: vk.PipelineLayout,
@ -103,10 +122,28 @@ pub const VulkanRenderer = struct {
try self.createLogicalDevice();
try self.createSwapchain();
try self.createRenderPass();
try self.createDescriptorSetLayout();
try self.createGraphicsPipeline();
try self.createFramebuffers();
try self.createCommandPool();
const aspect: f32 = @as(f32, @floatFromInt(self.extent.width)) / @as(f32, @floatFromInt(self.extent.height));
self.mvp.projection = zm.perspectiveFovRh(
std.math.degreesToRadians(45.0),
aspect,
0.1,
100.0,
);
self.mvp.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;
// Create meshes
// Vertex Data
var mesh_vertices = [_]Vertex{
@ -154,12 +191,21 @@ pub const VulkanRenderer = struct {
self.meshes = [_]Mesh{ first_mesh, second_mesh };
try self.createCommandBuffers();
try self.createUniformBuffers();
try self.createDescriptorPool();
try self.createDescriptorSets();
try self.recordCommands();
try self.createSynchronisation();
return self;
}
pub fn updateModel(self: *Self, new_model: zm.Mat) !void {
self.mvp.model = new_model;
}
pub fn draw(self: *Self) !void {
// Wait for given fence to signal (open) from last draw before continuing
_ = try self.device.waitForFences(1, @ptrCast(&self.draw_fences[self.current_frame]), vk.TRUE, std.math.maxInt(u64));
@ -175,6 +221,8 @@ pub const VulkanRenderer = struct {
.null_handle,
);
try self.updateUniformBuffer(image_index_result.image_index);
// -- Submit command buffer to render
// Queue submission information
const wait_stages = [_]vk.PipelineStageFlags{.{ .color_attachment_output_bit = true }};
@ -215,6 +263,17 @@ pub const VulkanRenderer = struct {
self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null);
}
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);
}
self.allocator.free(self.uniform_buffer);
self.allocator.free(self.uniform_buffer_memory);
self.allocator.free(self.descriptor_sets);
for (self.meshes) |mesh| {
mesh.destroyBuffers();
}
@ -491,6 +550,26 @@ pub const VulkanRenderer = struct {
self.render_pass = try self.device.createRenderPass(&render_pass_create_info, null);
}
fn createDescriptorSetLayout(self: *Self) !void {
// MVP binding info
const mvp_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
};
// 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
};
// Create descriptor set layout
self.descriptor_set_layout = try self.device.createDescriptorSetLayout(&layout_create_info, null);
}
fn createGraphicsPipeline(self: *Self) !void {
// Create shader modules
const vert = try self.device.createShaderModule(&.{
@ -606,7 +685,7 @@ pub const VulkanRenderer = struct {
.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 = true }, // Which face of a triangle to cull
.front_face = .clockwise, // Winding to determine which side is front
.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,
@ -647,8 +726,11 @@ pub const VulkanRenderer = struct {
.blend_constants = [_]f32{ 0, 0, 0, 0 },
};
// -- Pipeline layout (TODO: Apply future descriptor set layouts) --
const pipeline_layout_create_info: vk.PipelineLayoutCreateInfo = .{};
// -- Pipeline layout --
const pipeline_layout_create_info: vk.PipelineLayoutCreateInfo = .{
.set_layout_count = 1,
.p_set_layouts = @ptrCast(&self.descriptor_set_layout),
};
self.pipeline_layout = try self.device.createPipelineLayout(&pipeline_layout_create_info, null);
@ -748,6 +830,101 @@ 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));
// 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);
// Create the uniform buffers
for (0..self.uniform_buffer.len) |i| {
try Utilities.createBuffer(
self.physical_device,
self.instance,
self.device,
buffer_size,
.{ .uniform_buffer_bit = true },
.{ .host_visible_bit = true, .host_coherent_bit = true },
&self.uniform_buffer[i],
&self.uniform_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 = .{
.type = .uniform_buffer,
.descriptor_count = @intCast(self.uniform_buffer.len),
};
// 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
};
// Create descriptor pool
self.descriptor_pool = try self.device.createDescriptorPool(&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.uniform_buffer.len);
var set_layouts = try self.allocator.alloc(vk.DescriptorSetLayout, self.uniform_buffer.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.descriptor_sets.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.device.allocateDescriptorSets(&set_alloc_info, self.descriptor_sets.ptr);
// Update all of descriptor set buffer bindings
for (0..self.descriptor_sets.len) |i| {
// Buffer info and data offset info
const mvp_buffer_info: vk.DescriptorBufferInfo = .{
.buffer = self.uniform_buffer[i], // Bufer to get data from
.offset = 0, // Position of start of data
.range = @sizeOf(@TypeOf(self.mvp)), // Size of data
};
// Data about connection between binding and buffer
const mvp_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_image_info = undefined,
.p_texel_buffer_view = undefined,
};
// Update the descriptor sets with new buffer/binding info
self.device.updateDescriptorSets(1, @ptrCast(&mvp_set_write), 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), .{});
const mvp_data: *Mvp = @ptrCast(@alignCast(data));
mvp_data.* = self.mvp;
self.device.unmapMemory(self.uniform_buffer_memory[image_index]);
}
fn recordCommands(self: *Self) !void {
// Information about how to begin each command
const buffer_begin_info: vk.CommandBufferBeginInfo = .{
@ -799,6 +976,17 @@ pub const VulkanRenderer = struct {
// Bind mesh index buffer, with 0 offset and using the uint32 type
command_buffer.bindIndexBuffer(mesh.index_buffer, 0, .uint32);
// Bind descriptor sets
command_buffer.bindDescriptorSets(
.graphics,
self.pipeline_layout,
0,
1,
@ptrCast(&self.descriptor_sets[i]),
0,
null,
);
// Execute a pipeline
command_buffer.drawIndexed(mesh.index_count, 1, 0, 0, 0);
}