Add vertex input

This commit is contained in:
Przemysław Gasiǹski 2024-07-14 19:53:53 +02:00
parent c4193db891
commit 2ec8a315c6
9 changed files with 179 additions and 88 deletions

94
src/Mesh.zig Normal file
View file

@ -0,0 +1,94 @@
const std = @import("std");
const vk = @import("vulkan");
const Utilities = @import("utilities.zig");
const Vertex = Utilities.Vertex;
const Device = @import("vulkan_renderer.zig").Device;
const Instance = @import("vulkan_renderer.zig").Instance;
const Self = @This();
vertex_count: u32,
vertex_buffer: vk.Buffer,
vertex_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;
mesh.vertex_count = @intCast(vertices.len);
mesh.instance = instance;
mesh.physical_device = pdev;
mesh.device = device;
try mesh.createVertexBuffer(vertices);
return mesh;
}
pub fn destroyVertexBuffer(self: Self) void {
self.device.destroyBuffer(self.vertex_buffer, null);
self.device.freeMemory(self.vertex_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
};
self.vertex_buffer = try self.device.createBuffer(&buffer_create_info, 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);
// 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, .{});
// 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);
}
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;
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;
}

View file

@ -1,11 +1,8 @@
#version 450
// Interpolated colour from vertex (location must match)
layout(location = 0) in vec3 fragColour;
// Final output output (must also have location)
layout(location = 0) out vec4 outColour;
void main() {
outColour = vec4(fragColour, 1.0);
outColour = vec4(1.0, 0.0, 0.0, 1.0);
}

View file

@ -1,23 +1,8 @@
#version 450
// Output colour for vertex (location is required)
layout(location = 0) out vec3 fragColour;
// Triangle vertex positions
vec3 positions[3] = vec3[](
vec3(0.0, -0.4, 0.0),
vec3(0.4, 0.4, 0.0),
vec3(-0.4, 0.4, 0.0)
);
// Triangle vertex colours
vec3 colours[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
layout(location = 0) in vec3 pos;
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 1.0);
fragColour = colours[gl_VertexIndex];
gl_Position = vec4(pos, 1.0);
// fragColour = colours[gl_VertexIndex];
}

View file

@ -3,6 +3,12 @@ const vk = @import("vulkan");
pub const device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name};
// Vertex data representation
pub const Vertex = struct {
// Vertex position (x, y, z)
pos: @Vector(3, f32),
};
pub const QueueFamilyIndices = struct {
graphics_family: ?u32 = null,
presentation_family: ?u32 = null,

View file

@ -8,6 +8,9 @@ const Utilities = @import("utilities.zig");
const QueueFamilyIndices = Utilities.QueueFamilyIndices;
const SwapchainDetails = Utilities.SwapchainDetails;
const SwapchainImage = Utilities.SwapchainImage;
const Vertex = Utilities.Vertex;
const Mesh = @import("Mesh.zig");
const enable_validation_layers = builtin.mode == .Debug;
const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"};
@ -28,10 +31,10 @@ const BaseDispatch = vk.BaseWrapper(apis);
const InstanceDispatch = vk.InstanceWrapper(apis);
const DeviceDispatch = vk.DeviceWrapper(apis);
const Instance = vk.InstanceProxy(apis);
const Device = vk.DeviceProxy(apis);
const Queue = vk.QueueProxy(apis);
const CommandBuffer = vk.CommandBufferProxy(apis);
pub const Instance = vk.InstanceProxy(apis);
pub const Device = vk.DeviceProxy(apis);
pub const Queue = vk.QueueProxy(apis);
pub const CommandBuffer = vk.CommandBufferProxy(apis);
pub const VulkanRenderer = struct {
const Self = @This();
@ -44,6 +47,9 @@ pub const VulkanRenderer = struct {
current_frame: u32 = 0,
// Scene objects
first_mesh: Mesh,
// Main
instance: Instance,
physical_device: vk.PhysicalDevice,
@ -95,6 +101,16 @@ pub const VulkanRenderer = struct {
try self.getPhysicalDevice();
try self.createLogicalDevice();
// Create mesh
var mesh_vertices = [_]Vertex{
.{ .pos = .{ 0.0, -0.4, 0.0 } },
.{ .pos = .{ 0.4, 0.4, 0.0 } },
.{ .pos = .{ -0.4, 0.4, 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();
@ -157,11 +173,13 @@ pub const VulkanRenderer = struct {
}
pub fn deinit(self: *Self) void {
self.device.deviceWaitIdle() catch undefined;
if (enable_validation_layers) {
self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null);
}
self.device.deviceWaitIdle() catch undefined;
self.first_mesh.destroyVertexBuffer();
for (0..MAX_FRAME_DRAWS) |i| {
self.device.destroySemaphore(self.render_finished[i], null);
@ -470,10 +488,32 @@ pub const VulkanRenderer = struct {
fragment_shader_create_info,
};
// -- Vertex input (TODO: Put in vertex descriptions when resources created) --
// 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
},
};
// -- Vertex input --
const vertex_input_create_info: vk.PipelineVertexInputStateCreateInfo = .{
.p_vertex_binding_descriptions = null, // List of vertex binding descriptions (data spacing, stride info)
.p_vertex_attribute_descriptions = null, // List of vertex attribute descriptions (data format and where to bind to/from)
.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 --
@ -697,15 +737,24 @@ pub const VulkanRenderer = struct {
// Begin render pass
command_buffer.beginRenderPass(&render_pass_begin_info, vk.SubpassContents.@"inline");
// Bind pipeline to be used in render pass
command_buffer.bindPipeline(.graphics, self.graphics_pipeline);
{
// Bind pipeline to be used in render pass
command_buffer.bindPipeline(.graphics, self.graphics_pipeline);
// Needed when using dynamic state
command_buffer.setViewport(0, 1, @ptrCast(&self.viewport));
command_buffer.setScissor(0, 1, @ptrCast(&self.scissor));
// Buffers to bind
const vertex_buffers = [_]vk.Buffer{self.first_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);
// Execute a pipeline
command_buffer.draw(3, 1, 0, 0);
// Needed when using dynamic state
command_buffer.setViewport(0, 1, @ptrCast(&self.viewport));
command_buffer.setScissor(0, 1, @ptrCast(&self.scissor));
// Execute a pipeline
command_buffer.draw(self.first_mesh.vertex_count, 1, 0, 0);
}
// End render pass
command_buffer.endRenderPass();