Load textures

This commit is contained in:
Przemyslaw Gasinski 2024-08-05 17:54:40 +02:00
parent de68ccac0d
commit c0591ecb24
8 changed files with 462 additions and 56 deletions

BIN
assets/textures/test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -41,12 +41,23 @@ pub fn build(b: *std.Build) void {
const zmath = b.dependency("zmath", .{}); const zmath = b.dependency("zmath", .{});
exe.root_module.addImport("zmath", zmath.module("root")); exe.root_module.addImport("zmath", zmath.module("root"));
// zigimg
const zigimg = b.dependency("zigimg", .{});
exe.root_module.addImport("zigimg", zigimg.module("zigimg"));
// --- // ---
b.installArtifact(exe); b.installArtifact(exe);
const exe_check = b.addExecutable(.{
.name = "vulkan-test",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const check = b.step("check", "Check if vulkan-test compiles"); const check = b.step("check", "Check if vulkan-test compiles");
check.dependOn(&exe.step); check.dependOn(&exe_check.step);
const run_cmd = b.addRunArtifact(exe); const run_cmd = b.addRunArtifact(exe);

View file

@ -13,6 +13,10 @@
// .url = "https://github.com/ikskuh/SDL.zig/archive/9663dc70c19b13afcb4b9f596c928d7b2838e548.tar.gz", // .url = "https://github.com/ikskuh/SDL.zig/archive/9663dc70c19b13afcb4b9f596c928d7b2838e548.tar.gz",
// .hash = "12202141beb92d68ef5088538ff761d5c3ecd2d4e11867c89fbbdcd9f814b8cba8ee", // .hash = "12202141beb92d68ef5088538ff761d5c3ecd2d4e11867c89fbbdcd9f814b8cba8ee",
// }, // },
.zigimg = .{
.url = "https://github.com/zigimg/zigimg/archive/d9dbbe22b5f7b5f1f4772169ed93ffeed8e8124d.tar.gz",
.hash = "122013646f7038ecc71ddf8a0d7de346d29a6ec40140af57f838b0a975c69af512b0",
},
}, },
.paths = .{ .paths = .{

View file

@ -11,6 +11,7 @@ const Model = @import("vulkan_renderer.zig").Model;
const Self = @This(); const Self = @This();
ubo_model: Model, ubo_model: Model,
tex_id: u32,
vertex_count: u32, vertex_count: u32,
vertex_buffer: vk.Buffer, vertex_buffer: vk.Buffer,
@ -34,6 +35,7 @@ pub fn new(
transfer_command_pool: vk.CommandPool, transfer_command_pool: vk.CommandPool,
vertices: []const Vertex, vertices: []const Vertex,
indices: []const u32, indices: []const u32,
tex_id: u32,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
) !Self { ) !Self {
var self: Self = undefined; var self: Self = undefined;
@ -50,6 +52,7 @@ pub fn new(
try self.createIndexBuffer(transfer_queue, transfer_command_pool, indices); try self.createIndexBuffer(transfer_queue, transfer_command_pool, indices);
self.ubo_model = .{ .model = zm.identity() }; self.ubo_model = .{ .model = zm.identity() };
self.tex_id = tex_id;
return self; return self;
} }
@ -122,7 +125,6 @@ fn createVertexBuffer(
staging_buffer, staging_buffer,
self.vertex_buffer, self.vertex_buffer,
buffer_size, buffer_size,
self.allocator,
); );
} }
@ -178,6 +180,5 @@ fn createIndexBuffer(
staging_buffer, staging_buffer,
self.index_buffer, self.index_buffer,
buffer_size, buffer_size,
self.allocator,
); );
} }

View file

@ -1,10 +1,13 @@
#version 450 #version 450
layout(location = 0) in vec3 fragCol; layout(location = 0) in vec3 fragCol;
layout(location = 1) in vec2 fragTex;
// Final output output (must also have location) // Final output output (must also have location)
layout(location = 0) out vec4 outColour; layout(location = 0) out vec4 outColour;
layout(set = 1, binding = 0) uniform sampler2D textureSampler;
void main() { void main() {
outColour = vec4(fragCol, 1.0); outColour = texture(textureSampler, fragTex);
} }

View file

@ -2,10 +2,12 @@
layout(location = 0) in vec3 pos; layout(location = 0) in vec3 pos;
layout(location = 1) in vec3 col; layout(location = 1) in vec3 col;
layout(location = 2) in vec2 tex;
layout(location = 0) out vec3 fragCol; layout(location = 0) out vec3 fragCol;
layout(location = 1) out vec2 fragTex;
layout(binding = 0) uniform UboViewProjection { layout(set = 0, binding = 0) uniform UboViewProjection {
mat4 projection; mat4 projection;
mat4 view; mat4 view;
} uboViewProjection; } uboViewProjection;
@ -17,4 +19,5 @@ layout(push_constant) uniform PushModel {
void main() { void main() {
gl_Position = uboViewProjection.projection * uboViewProjection.view * pushModel.model * vec4(pos, 1.0); gl_Position = uboViewProjection.projection * uboViewProjection.view * pushModel.model * vec4(pos, 1.0);
fragCol = col; fragCol = col;
fragTex = tex;
} }

View file

@ -8,12 +8,13 @@ const CommandBuffer = @import("vulkan_renderer.zig").CommandBuffer;
pub const device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name}; pub const device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name};
pub const Vector3 = @Vector(3, f32); pub const Vector3 = @Vector(3, f32);
pub const Vector2 = @Vector(2, f32);
// Vertex data representation // Vertex data representation
pub const Vertex = struct { pub const Vertex = struct {
// Vertex position (x, y, z) pos: Vector3, // Vertex position (x, y, z)
pos: Vector3, col: Vector3, // Vertex colour (r, g, b)
col: Vector3, tex: Vector2, // Texture coords (u, v)
}; };
pub const QueueFamilyIndices = struct { pub const QueueFamilyIndices = struct {
@ -96,31 +97,20 @@ pub fn createBuffer(
try device.bindBufferMemory(buffer.*, buffer_memory.*, 0); try device.bindBufferMemory(buffer.*, buffer_memory.*, 0);
} }
pub fn copyBuffer( fn beginCommandBuffer(device: Device, command_pool: vk.CommandPool) !CommandBuffer {
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 // Command buffer to hold transfer commands
const transfer_command_buffer_handle = try allocator.create(vk.CommandBuffer); var command_buffer_handle: vk.CommandBuffer = undefined;
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 // Command buffer details
const alloc_info: vk.CommandBufferAllocateInfo = .{ const alloc_info: vk.CommandBufferAllocateInfo = .{
.command_pool = transfer_command_pool, .command_pool = command_pool,
.level = .primary, .level = .primary,
.command_buffer_count = 1, .command_buffer_count = 1,
}; };
// Allocate command buffer from pool // Allocate command buffer from pool
try device.allocateCommandBuffers(&alloc_info, @ptrCast(transfer_command_buffer_handle)); try device.allocateCommandBuffers(&alloc_info, @ptrCast(&command_buffer_handle));
const transfer_command_buffer = CommandBuffer.init(transfer_command_buffer_handle.*, device.wrapper); const command_buffer = CommandBuffer.init(command_buffer_handle, device.wrapper);
// Information to begin the command buffer record // Information to begin the command buffer record
const begin_info: vk.CommandBufferBeginInfo = .{ const begin_info: vk.CommandBufferBeginInfo = .{
@ -128,7 +118,39 @@ pub fn copyBuffer(
}; };
// Begin recording transfer commands // Begin recording transfer commands
try transfer_command_buffer.beginCommandBuffer(&begin_info); 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 // Region of data to copy from and to
const buffer_copy_region: vk.BufferCopy = .{ const buffer_copy_region: vk.BufferCopy = .{
@ -140,16 +162,96 @@ pub fn copyBuffer(
// Command to copy src buffer to dst buffer // Command to copy src buffer to dst buffer
transfer_command_buffer.copyBuffer(src_buffer, dst_buffer, 1, @ptrCast(&buffer_copy_region)); transfer_command_buffer.copyBuffer(src_buffer, dst_buffer, 1, @ptrCast(&buffer_copy_region));
// End commands // Command to copy src buffer to dst buffer
try transfer_command_buffer.endCommandBuffer(); try endAndSubmitCommandBuffer(device, transfer_command_buffer, transfer_command_pool, transfer_queue);
}
// Queue submission information pub fn copyImageBuffer(
const submit_info: vk.SubmitInfo = .{ device: Device,
.command_buffer_count = 1, transfer_queue: vk.Queue,
.p_command_buffers = @ptrCast(&transfer_command_buffer.handle), 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
}; };
// Submit transfer command to transfer queue and wait until it finishes transfer_command_buffer.copyBufferToImage(src_buffer, image, .transfer_dst_optimal, 1, @ptrCast(&image_region));
try device.queueSubmit(transfer_queue, 1, @ptrCast(&submit_info), .null_handle);
try device.queueWaitIdle(transfer_queue); 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);
} }

View file

@ -4,6 +4,7 @@ const vk = @import("vulkan");
const builtin = @import("builtin"); const builtin = @import("builtin");
const shaders = @import("shaders"); const shaders = @import("shaders");
const zm = @import("zmath"); const zm = @import("zmath");
const img = @import("zigimg");
const Utilities = @import("utilities.zig"); const Utilities = @import("utilities.zig");
const QueueFamilyIndices = Utilities.QueueFamilyIndices; const QueueFamilyIndices = Utilities.QueueFamilyIndices;
@ -74,6 +75,7 @@ pub const VulkanRenderer = struct {
swapchain: vk.SwapchainKHR, swapchain: vk.SwapchainKHR,
viewport: vk.Viewport, viewport: vk.Viewport,
scissor: vk.Rect2D, scissor: vk.Rect2D,
texture_sampler: vk.Sampler,
swapchain_images: []SwapchainImage, swapchain_images: []SwapchainImage,
swapchain_framebuffers: []vk.Framebuffer, swapchain_framebuffers: []vk.Framebuffer,
@ -85,14 +87,22 @@ pub const VulkanRenderer = struct {
// Descriptors // Descriptors
descriptor_set_layout: vk.DescriptorSetLayout, descriptor_set_layout: vk.DescriptorSetLayout,
sampler_set_layout: vk.DescriptorSetLayout,
push_constant_range: vk.PushConstantRange, push_constant_range: vk.PushConstantRange,
descriptor_pool: vk.DescriptorPool, descriptor_pool: vk.DescriptorPool,
sampler_descriptor_pool: vk.DescriptorPool,
descriptor_sets: []vk.DescriptorSet, descriptor_sets: []vk.DescriptorSet,
sampler_descriptor_sets: std.ArrayList(vk.DescriptorSet),
vp_uniform_buffer: []vk.Buffer, vp_uniform_buffer: []vk.Buffer,
vp_uniform_buffer_memory: []vk.DeviceMemory, vp_uniform_buffer_memory: []vk.DeviceMemory,
// Assets
texture_images: std.ArrayList(vk.Image),
texture_image_memory: std.ArrayList(vk.DeviceMemory),
texture_image_views: std.ArrayList(vk.ImageView),
// Pipeline // Pipeline
graphics_pipeline: vk.Pipeline, graphics_pipeline: vk.Pipeline,
pipeline_layout: vk.PipelineLayout, pipeline_layout: vk.PipelineLayout,
@ -139,6 +149,19 @@ pub const VulkanRenderer = struct {
try self.createFramebuffers(); try self.createFramebuffers();
try self.createCommandPool(); try self.createCommandPool();
try self.createCommandBuffers();
try self.createTextureSampler();
try self.createUniformBuffers();
try self.createDescriptorPool();
try self.createDescriptorSets();
try self.createSynchronisation();
self.texture_images = std.ArrayList(vk.Image).init(self.allocator);
self.texture_image_memory = std.ArrayList(vk.DeviceMemory).init(self.allocator);
self.texture_image_views = std.ArrayList(vk.ImageView).init(self.allocator);
self.sampler_descriptor_sets = std.ArrayList(vk.DescriptorSet).init(self.allocator);
const aspect: f32 = @as(f32, @floatFromInt(self.extent.width)) / @as(f32, @floatFromInt(self.extent.height)); const aspect: f32 = @as(f32, @floatFromInt(self.extent.width)) / @as(f32, @floatFromInt(self.extent.height));
self.ubo_view_projection.projection = zm.perspectiveFovRh( self.ubo_view_projection.projection = zm.perspectiveFovRh(
std.math.degreesToRadians(45.0), std.math.degreesToRadians(45.0),
@ -158,17 +181,17 @@ pub const VulkanRenderer = struct {
// Create meshes // Create meshes
// Vertex Data // Vertex Data
var mesh_vertices = [_]Vertex{ var mesh_vertices = [_]Vertex{
.{ .pos = .{ -0.4, 0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 } }, // 0 .{ .pos = .{ -0.4, 0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 }, .tex = .{ 1.0, 1.0 } }, // 0
.{ .pos = .{ -0.4, -0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 } }, // 1 .{ .pos = .{ -0.4, -0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 }, .tex = .{ 1.0, 0.0 } }, // 1
.{ .pos = .{ 0.4, -0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 } }, // 2 .{ .pos = .{ 0.4, -0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 }, .tex = .{ 0.0, 0.0 } }, // 2
.{ .pos = .{ 0.4, 0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 } }, // 3 .{ .pos = .{ 0.4, 0.4, 0.0 }, .col = .{ 1.0, 0.0, 0.0 }, .tex = .{ 0.0, 1.0 } }, // 3
}; };
var mesh_vertices2 = [_]Vertex{ var mesh_vertices2 = [_]Vertex{
.{ .pos = .{ -0.25, 0.6, 0.0 }, .col = .{ 0.0, 0.0, 1.0 } }, // 0 .{ .pos = .{ -0.25, 0.6, 0.0 }, .col = .{ 0.0, 0.0, 1.0 }, .tex = .{ 1.0, 1.0 } }, // 0
.{ .pos = .{ -0.25, -0.6, 0.0 }, .col = .{ 0.0, 0.0, 1.0 } }, // 1 .{ .pos = .{ -0.25, -0.6, 0.0 }, .col = .{ 0.0, 0.0, 1.0 }, .tex = .{ 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, 0.0, 1.0 }, .tex = .{ 0.0, 0.0 } }, // 2
.{ .pos = .{ 0.25, 0.6, 0.0 }, .col = .{ 0.0, 0.0, 1.0 } }, // 3 .{ .pos = .{ 0.25, 0.6, 0.0 }, .col = .{ 0.0, 0.0, 1.0 }, .tex = .{ 0.0, 1.0 } }, // 3
}; };
// Index Data // Index Data
@ -185,6 +208,7 @@ pub const VulkanRenderer = struct {
self.graphics_command_pool, self.graphics_command_pool,
&mesh_vertices, &mesh_vertices,
&mesh_indices, &mesh_indices,
try self.createTexture("test.png"),
self.allocator, self.allocator,
); );
@ -196,18 +220,12 @@ pub const VulkanRenderer = struct {
self.graphics_command_pool, self.graphics_command_pool,
&mesh_vertices2, &mesh_vertices2,
&mesh_indices, &mesh_indices,
try self.createTexture("test.png"),
self.allocator, self.allocator,
); );
self.meshes = [_]Mesh{ first_mesh, second_mesh }; self.meshes = [_]Mesh{ first_mesh, second_mesh };
try self.createCommandBuffers();
try self.createUniformBuffers();
try self.createDescriptorPool();
try self.createDescriptorSets();
try self.createSynchronisation();
return self; return self;
} }
@ -280,12 +298,31 @@ pub const VulkanRenderer = struct {
self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null); self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null);
} }
self.device.destroySampler(self.texture_sampler, null);
for (
self.texture_images.items,
self.texture_image_memory.items,
self.texture_image_views.items,
) |tex_image, tex_image_memory, tex_image_view| {
self.device.destroyImage(tex_image, null);
self.device.freeMemory(tex_image_memory, null);
self.device.destroyImageView(tex_image_view, null);
}
self.texture_images.deinit();
self.texture_image_memory.deinit();
self.texture_image_views.deinit();
self.device.destroyImageView(self.depth_buffer_image_view, null); self.device.destroyImageView(self.depth_buffer_image_view, null);
self.device.destroyImage(self.depth_buffer_image, null); self.device.destroyImage(self.depth_buffer_image, null);
self.device.freeMemory(self.depth_buffer_image_memory, null); self.device.freeMemory(self.depth_buffer_image_memory, null);
self.device.destroyDescriptorPool(self.descriptor_pool, null); self.device.destroyDescriptorPool(self.descriptor_pool, null);
self.device.destroyDescriptorSetLayout(self.descriptor_set_layout, null); self.device.destroyDescriptorSetLayout(self.descriptor_set_layout, null);
self.device.destroyDescriptorPool(self.sampler_descriptor_pool, null);
self.device.destroyDescriptorSetLayout(self.sampler_set_layout, null);
self.sampler_descriptor_sets.deinit();
for (0..self.swapchain_images.len) |i| { for (0..self.swapchain_images.len) |i| {
self.device.destroyBuffer(self.vp_uniform_buffer[i], null); self.device.destroyBuffer(self.vp_uniform_buffer[i], null);
@ -407,11 +444,17 @@ pub const VulkanRenderer = struct {
else else
2; 2;
// Device features
const device_features: vk.PhysicalDeviceFeatures = .{
.sampler_anisotropy = vk.TRUE, // Enable anisotropy
};
const device_create_info: vk.DeviceCreateInfo = .{ const device_create_info: vk.DeviceCreateInfo = .{
.queue_create_info_count = queue_count, .queue_create_info_count = queue_count,
.p_queue_create_infos = &qci, .p_queue_create_infos = &qci,
.pp_enabled_extension_names = &Utilities.device_extensions, .pp_enabled_extension_names = &Utilities.device_extensions,
.enabled_extension_count = @intCast(Utilities.device_extensions.len), .enabled_extension_count = @intCast(Utilities.device_extensions.len),
.p_enabled_features = &device_features,
}; };
const device_handle = try self.instance.createDevice(self.physical_device, &device_create_info, null); const device_handle = try self.instance.createDevice(self.physical_device, &device_create_info, null);
@ -595,6 +638,8 @@ pub const VulkanRenderer = struct {
} }
fn createDescriptorSetLayout(self: *Self) !void { fn createDescriptorSetLayout(self: *Self) !void {
// -- Uniform values descriptor set layout --
// UboViewProjection binding info // UboViewProjection binding info
const vp_layout_binding: vk.DescriptorSetLayoutBinding = .{ const vp_layout_binding: vk.DescriptorSetLayoutBinding = .{
.binding = 0, // Binding point in shader (designated by binding number in shader) .binding = 0, // Binding point in shader (designated by binding number in shader)
@ -614,6 +659,25 @@ pub const VulkanRenderer = struct {
// Create descriptor set layout // Create descriptor set layout
self.descriptor_set_layout = try self.device.createDescriptorSetLayout(&layout_create_info, null); self.descriptor_set_layout = try self.device.createDescriptorSetLayout(&layout_create_info, null);
// -- Texture sampler descriptor set layout --
// Texture binding info
const sampler_layout_binding: vk.DescriptorSetLayoutBinding = .{
.binding = 0,
.descriptor_type = .combined_image_sampler,
.descriptor_count = 1,
.stage_flags = .{ .fragment_bit = true },
.p_immutable_samplers = null,
};
// Create a descriptor set layout with given bindings for texture
const texture_layout_info: vk.DescriptorSetLayoutCreateInfo = .{
.binding_count = 1,
.p_bindings = @ptrCast(&sampler_layout_binding),
};
self.sampler_set_layout = try self.device.createDescriptorSetLayout(&texture_layout_info, null);
} }
fn createPushConstantRange(self: *Self) !void { fn createPushConstantRange(self: *Self) !void {
@ -711,6 +775,13 @@ pub const VulkanRenderer = struct {
.format = vk.Format.r32g32b32_sfloat, .format = vk.Format.r32g32b32_sfloat,
.offset = @offsetOf(Vertex, "col"), .offset = @offsetOf(Vertex, "col"),
}, },
// Texture attribute
.{
.binding = 0,
.location = 2,
.format = vk.Format.r32g32_sfloat,
.offset = @offsetOf(Vertex, "tex"),
},
}; };
// -- Vertex input -- // -- Vertex input --
@ -808,9 +879,11 @@ pub const VulkanRenderer = struct {
}; };
// -- Pipeline layout -- // -- Pipeline layout --
const descriptor_set_layouts = [_]vk.DescriptorSetLayout{ self.descriptor_set_layout, self.sampler_set_layout };
const pipeline_layout_create_info: vk.PipelineLayoutCreateInfo = .{ const pipeline_layout_create_info: vk.PipelineLayoutCreateInfo = .{
.set_layout_count = 1, .set_layout_count = @intCast(descriptor_set_layouts.len),
.p_set_layouts = @ptrCast(&self.descriptor_set_layout), .p_set_layouts = &descriptor_set_layouts,
.push_constant_range_count = 1, .push_constant_range_count = 1,
.p_push_constant_ranges = @ptrCast(&self.push_constant_range), .p_push_constant_ranges = @ptrCast(&self.push_constant_range),
}; };
@ -925,6 +998,29 @@ pub const VulkanRenderer = struct {
} }
} }
fn createTextureSampler(self: *Self) !void {
// Sampler create info
const sampler_create_info: vk.SamplerCreateInfo = .{
.mag_filter = .linear, // How to render when image is magnified on screen
.min_filter = .linear, // How to render when image is minified on screen
.address_mode_u = .repeat, // How to handle texture wrap in U (x direction)
.address_mode_v = .repeat, // How to handle texture wrap in U (y direction)
.address_mode_w = .repeat, // How to handle texture wrap in U (z direction)
.border_color = .int_opaque_black, // Border beyond texture (only works for border clamp)
.unnormalized_coordinates = vk.FALSE, // Whether coords should be normalized (between 0 and 1)
.mipmap_mode = .linear, // Mipmap interpolation mode
.mip_lod_bias = 0.0, // Level of detail bias for mip level
.min_lod = 0.0, // Minimum lod to pick mip level
.max_lod = 0.0, // Maximum lod to pick mip level
.anisotropy_enable = vk.TRUE, // Enable anisotropy
.max_anisotropy = 16.0, // Anisotropy sample level
.compare_enable = vk.FALSE,
.compare_op = .never,
};
self.texture_sampler = try self.device.createSampler(&sampler_create_info, null);
}
fn createUniformBuffers(self: *Self) !void { fn createUniformBuffers(self: *Self) !void {
// View projection buffer size // View projection buffer size
const vp_buffer_size: vk.DeviceSize = @sizeOf(UboViewProjection); const vp_buffer_size: vk.DeviceSize = @sizeOf(UboViewProjection);
@ -949,6 +1045,8 @@ pub const VulkanRenderer = struct {
} }
fn createDescriptorPool(self: *Self) !void { fn createDescriptorPool(self: *Self) !void {
// -- Create uniform descriptor pool --
// Type of descriptors + how many descriptors (!= descriptor sets) (combined makes the pool size) // Type of descriptors + how many descriptors (!= descriptor sets) (combined makes the pool size)
// View projection pool // View projection pool
const vp_pool_size: vk.DescriptorPoolSize = .{ const vp_pool_size: vk.DescriptorPoolSize = .{
@ -968,6 +1066,23 @@ pub const VulkanRenderer = struct {
// Create descriptor pool // Create descriptor pool
self.descriptor_pool = try self.device.createDescriptorPool(&pool_create_info, null); self.descriptor_pool = try self.device.createDescriptorPool(&pool_create_info, null);
// -- Create sampler descriptor pool --
// Texture sampler pool
const sampler_pool_size: vk.DescriptorPoolSize = .{
.type = .combined_image_sampler,
.descriptor_count = MAX_OBJECTS,
};
// FIXME Not the best (look into array layers)
const sampler_pool_create_info: vk.DescriptorPoolCreateInfo = .{
.max_sets = MAX_OBJECTS,
.pool_size_count = 1,
.p_pool_sizes = @ptrCast(&sampler_pool_size),
};
self.sampler_descriptor_pool = try self.device.createDescriptorPool(&sampler_pool_create_info, null);
} }
fn createDescriptorSets(self: *Self) !void { fn createDescriptorSets(self: *Self) !void {
@ -1095,13 +1210,18 @@ pub const VulkanRenderer = struct {
@ptrCast(&mesh.ubo_model.model), // Actual data being pushed (can be array) @ptrCast(&mesh.ubo_model.model), // Actual data being pushed (can be array)
); );
const descriptor_set_group = [_]vk.DescriptorSet{
self.descriptor_sets[current_image],
self.sampler_descriptor_sets.items[mesh.tex_id],
};
// Bind descriptor sets // Bind descriptor sets
command_buffer.bindDescriptorSets( command_buffer.bindDescriptorSets(
.graphics, .graphics,
self.pipeline_layout, self.pipeline_layout,
0, 0,
1, @intCast(descriptor_set_group.len),
@ptrCast(&self.descriptor_sets[current_image]), &descriptor_set_group,
0, 0,
null, null,
); );
@ -1247,8 +1367,8 @@ pub const VulkanRenderer = struct {
return false; return false;
} }
const pdev_features = self.instance.getPhysicalDeviceFeatures(pdev);
const queue_family_indices = self.getQueueFamilies(pdev) catch return false; const queue_family_indices = self.getQueueFamilies(pdev) catch return false;
const extension_support = self.checkDeviceExtensions(pdev) catch return false; const extension_support = self.checkDeviceExtensions(pdev) catch return false;
const swapchain_details = self.getSwapchainDetails(pdev) catch return false; const swapchain_details = self.getSwapchainDetails(pdev) catch return false;
@ -1257,7 +1377,7 @@ pub const VulkanRenderer = struct {
const swapchain_valid = swapchain_details.formats.len != 0 and swapchain_details.formats.len != 0; const swapchain_valid = swapchain_details.formats.len != 0 and swapchain_details.formats.len != 0;
return queue_family_indices.isValid() and extension_support and swapchain_valid; return queue_family_indices.isValid() and extension_support and swapchain_valid and pdev_features.sampler_anisotropy == vk.TRUE;
} }
fn checkValidationLayersSupport(self: Self) bool { fn checkValidationLayersSupport(self: Self) bool {
@ -1376,6 +1496,168 @@ pub const VulkanRenderer = struct {
return try self.device.createImageView(&image_view_create_info, null); return try self.device.createImageView(&image_view_create_info, null);
} }
fn createTextureImage(self: *Self, file_name: []const u8) !u32 {
// Load image file
var width: u32 = undefined;
var height: u32 = undefined;
var image_size: vk.DeviceSize = undefined;
const image = try self.loadTextureFile(file_name, &width, &height, &image_size);
// Create staging buffer to hold loaded data, ready to copy to device
var image_staging_buffer: vk.Buffer = undefined;
defer self.device.destroyBuffer(image_staging_buffer, null);
var image_staging_buffer_memory: vk.DeviceMemory = undefined;
defer self.device.freeMemory(image_staging_buffer_memory, null);
try Utilities.createBuffer(
self.physical_device,
self.instance,
self.device,
image_size,
.{ .transfer_src_bit = true },
.{ .host_visible_bit = true, .host_coherent_bit = true },
&image_staging_buffer,
&image_staging_buffer_memory,
);
// Copy data to staging buffer
const data = try self.device.mapMemory(image_staging_buffer_memory, 0, image_size, .{});
const image_data: *[]const u8 = @ptrCast(@alignCast(data));
image_data.* = image;
self.device.unmapMemory(image_staging_buffer_memory);
// Create image to hold final texture
var tex_image_memory: vk.DeviceMemory = undefined;
const tex_image: vk.Image = try self.createImage(
width,
height,
.r8g8b8a8_unorm,
.optimal,
.{ .transfer_dst_bit = true, .sampled_bit = true },
.{ .device_local_bit = true },
&tex_image_memory,
);
// Transition image to be DST for copy operation
try Utilities.transitionImageLayout(
self.device,
self.graphics_queue.handle,
self.graphics_command_pool,
tex_image,
.undefined,
.transfer_dst_optimal,
);
// Copy data to image
try Utilities.copyImageBuffer(
self.device,
self.graphics_queue.handle,
self.graphics_command_pool,
image_staging_buffer,
tex_image,
width,
height,
);
// Transition image to be shader readable for shader usage
try Utilities.transitionImageLayout(
self.device,
self.graphics_queue.handle,
self.graphics_command_pool,
tex_image,
.transfer_dst_optimal,
.shader_read_only_optimal,
);
// Add texture data to array for reference
try self.texture_images.append(tex_image);
try self.texture_image_memory.append(tex_image_memory);
// Return index of new texture image
return @intCast(self.texture_images.items.len - 1);
}
fn createTexture(self: *Self, file_name: []const u8) !u32 {
// Create texture image and get its location in the array
const texture_image_loc = try self.createTextureImage(file_name);
// Create image view and add to list
const image_view = try self.createImageView(
self.texture_images.items[texture_image_loc],
.r8g8b8a8_unorm,
.{ .color_bit = true },
);
try self.texture_image_views.append(image_view);
// Create texture descriptor
const descriptor_loc = try self.createTextureDescriptor(image_view);
// Return location of set with texture
return descriptor_loc;
}
fn createTextureDescriptor(self: *Self, texture_image: vk.ImageView) !u32 {
var descriptor_set: vk.DescriptorSet = undefined;
// Descriptor set allocation info
const set_alloc_info: vk.DescriptorSetAllocateInfo = .{
.descriptor_pool = self.sampler_descriptor_pool,
.descriptor_set_count = 1,
.p_set_layouts = @ptrCast(&self.sampler_set_layout),
};
// Allocate descriptor sets
try self.device.allocateDescriptorSets(&set_alloc_info, @ptrCast(&descriptor_set));
const image_info: vk.DescriptorImageInfo = .{
.image_layout = .shader_read_only_optimal, // Image layout when in use
.image_view = texture_image, // Image to bind to set
.sampler = self.texture_sampler, // Sampler to use for set
};
// Descriptor write info
const descriptor_write: vk.WriteDescriptorSet = .{
.dst_set = descriptor_set,
.dst_binding = 0,
.dst_array_element = 0,
.descriptor_type = .combined_image_sampler,
.descriptor_count = 1,
.p_image_info = @ptrCast(&image_info),
.p_buffer_info = undefined,
.p_texel_buffer_view = undefined,
};
// Update the new descriptor set
self.device.updateDescriptorSets(1, @ptrCast(&descriptor_write), 0, null);
try self.sampler_descriptor_sets.append(descriptor_set);
// Return descriptor set location
return @intCast(self.sampler_descriptor_sets.items.len - 1);
}
fn loadTextureFile(self: *Self, file_name: []const u8, width: *u32, height: *u32, image_size: *vk.DeviceSize) ![]const u8 {
const path_concat = [2][]const u8{ "./assets/textures/", file_name };
const path = try std.mem.concat(self.allocator, u8, &path_concat);
defer self.allocator.free(path);
var image = try img.Image.fromFilePath(self.allocator, path);
defer image.deinit();
if (!image.pixelFormat().isRgba()) {
try image.convert(.rgba32);
}
width.* = @intCast(image.width);
height.* = @intCast(image.height);
// Calculate image size using given and known data
image_size.* = width.* * height.* * 4;
return image.rawBytes();
}
}; };
// Format: VK_FORMAT_R8G8B8A8_UNORM (VK_FORMAT_B8G8R8A8_UNORM as backup) // Format: VK_FORMAT_R8G8B8A8_UNORM (VK_FORMAT_B8G8R8A8_UNORM as backup)
@ -1479,7 +1761,7 @@ fn debugCallback(
const severity = getMessageSeverityLabel(message_severity); const severity = getMessageSeverityLabel(message_severity);
const message_type = getMessageTypeLabel(message_types); const message_type = getMessageTypeLabel(message_types);
std.debug.print("[{s}] ({s}): {s}\n", .{ severity, message_type, p_callback_data.?.p_message.? }); std.debug.print("[{s}] ({s}): {s}\n=====\n", .{ severity, message_type, p_callback_data.?.p_message.? });
return vk.TRUE; return vk.TRUE;
} }