diff --git a/assets/textures/test.png b/assets/textures/test.png new file mode 100644 index 0000000..72b5550 Binary files /dev/null and b/assets/textures/test.png differ diff --git a/build.zig b/build.zig index 466a919..b4210d8 100644 --- a/build.zig +++ b/build.zig @@ -41,12 +41,23 @@ pub fn build(b: *std.Build) void { const zmath = b.dependency("zmath", .{}); 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); + 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"); - check.dependOn(&exe.step); + check.dependOn(&exe_check.step); const run_cmd = b.addRunArtifact(exe); diff --git a/build.zig.zon b/build.zig.zon index 5c30a28..f20f1f2 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -13,6 +13,10 @@ // .url = "https://github.com/ikskuh/SDL.zig/archive/9663dc70c19b13afcb4b9f596c928d7b2838e548.tar.gz", // .hash = "12202141beb92d68ef5088538ff761d5c3ecd2d4e11867c89fbbdcd9f814b8cba8ee", // }, + .zigimg = .{ + .url = "https://github.com/zigimg/zigimg/archive/d9dbbe22b5f7b5f1f4772169ed93ffeed8e8124d.tar.gz", + .hash = "122013646f7038ecc71ddf8a0d7de346d29a6ec40140af57f838b0a975c69af512b0", + }, }, .paths = .{ diff --git a/src/Mesh.zig b/src/Mesh.zig index 0ba7b58..74e52d4 100644 --- a/src/Mesh.zig +++ b/src/Mesh.zig @@ -11,6 +11,7 @@ const Model = @import("vulkan_renderer.zig").Model; const Self = @This(); ubo_model: Model, +tex_id: u32, vertex_count: u32, vertex_buffer: vk.Buffer, @@ -34,6 +35,7 @@ pub fn new( transfer_command_pool: vk.CommandPool, vertices: []const Vertex, indices: []const u32, + tex_id: u32, allocator: std.mem.Allocator, ) !Self { var self: Self = undefined; @@ -50,6 +52,7 @@ pub fn new( try self.createIndexBuffer(transfer_queue, transfer_command_pool, indices); self.ubo_model = .{ .model = zm.identity() }; + self.tex_id = tex_id; return self; } @@ -122,7 +125,6 @@ fn createVertexBuffer( staging_buffer, self.vertex_buffer, buffer_size, - self.allocator, ); } @@ -178,6 +180,5 @@ fn createIndexBuffer( staging_buffer, self.index_buffer, buffer_size, - self.allocator, ); } diff --git a/src/shaders/shader.frag b/src/shaders/shader.frag index 76d73d7..3c347a6 100644 --- a/src/shaders/shader.frag +++ b/src/shaders/shader.frag @@ -1,10 +1,13 @@ #version 450 layout(location = 0) in vec3 fragCol; +layout(location = 1) in vec2 fragTex; // Final output output (must also have location) layout(location = 0) out vec4 outColour; +layout(set = 1, binding = 0) uniform sampler2D textureSampler; + void main() { - outColour = vec4(fragCol, 1.0); + outColour = texture(textureSampler, fragTex); } diff --git a/src/shaders/shader.vert b/src/shaders/shader.vert index baad5c6..04c6d1f 100644 --- a/src/shaders/shader.vert +++ b/src/shaders/shader.vert @@ -2,10 +2,12 @@ layout(location = 0) in vec3 pos; layout(location = 1) in vec3 col; +layout(location = 2) in vec2 tex; 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 view; } uboViewProjection; @@ -17,4 +19,5 @@ layout(push_constant) uniform PushModel { void main() { gl_Position = uboViewProjection.projection * uboViewProjection.view * pushModel.model * vec4(pos, 1.0); fragCol = col; + fragTex = tex; } diff --git a/src/utilities.zig b/src/utilities.zig index f9db076..94eed98 100644 --- a/src/utilities.zig +++ b/src/utilities.zig @@ -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 Vector3 = @Vector(3, f32); +pub const Vector2 = @Vector(2, f32); // Vertex data representation pub const Vertex = struct { - // Vertex position (x, y, z) - pos: Vector3, - col: Vector3, + pos: Vector3, // Vertex position (x, y, z) + col: Vector3, // Vertex colour (r, g, b) + tex: Vector2, // Texture coords (u, v) }; pub const QueueFamilyIndices = struct { @@ -96,31 +97,20 @@ pub fn createBuffer( 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 { +fn beginCommandBuffer(device: Device, command_pool: vk.CommandPool) !CommandBuffer { // 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)); + var command_buffer_handle: vk.CommandBuffer = undefined; // Command buffer details const alloc_info: vk.CommandBufferAllocateInfo = .{ - .command_pool = transfer_command_pool, + .command_pool = 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); + try device.allocateCommandBuffers(&alloc_info, @ptrCast(&command_buffer_handle)); + const command_buffer = CommandBuffer.init(command_buffer_handle, device.wrapper); // Information to begin the command buffer record const begin_info: vk.CommandBufferBeginInfo = .{ @@ -128,7 +118,39 @@ pub fn copyBuffer( }; // 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 const buffer_copy_region: vk.BufferCopy = .{ @@ -140,16 +162,96 @@ pub fn copyBuffer( // 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(); + // Command to copy src buffer to dst buffer + try endAndSubmitCommandBuffer(device, transfer_command_buffer, transfer_command_pool, transfer_queue); +} - // Queue submission information - const submit_info: vk.SubmitInfo = .{ - .command_buffer_count = 1, - .p_command_buffers = @ptrCast(&transfer_command_buffer.handle), +pub fn copyImageBuffer( + device: Device, + transfer_queue: vk.Queue, + 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 - try device.queueSubmit(transfer_queue, 1, @ptrCast(&submit_info), .null_handle); - try device.queueWaitIdle(transfer_queue); + transfer_command_buffer.copyBufferToImage(src_buffer, image, .transfer_dst_optimal, 1, @ptrCast(&image_region)); + + 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); } diff --git a/src/vulkan_renderer.zig b/src/vulkan_renderer.zig index 6a46d93..205662e 100644 --- a/src/vulkan_renderer.zig +++ b/src/vulkan_renderer.zig @@ -4,6 +4,7 @@ const vk = @import("vulkan"); const builtin = @import("builtin"); const shaders = @import("shaders"); const zm = @import("zmath"); +const img = @import("zigimg"); const Utilities = @import("utilities.zig"); const QueueFamilyIndices = Utilities.QueueFamilyIndices; @@ -74,6 +75,7 @@ pub const VulkanRenderer = struct { swapchain: vk.SwapchainKHR, viewport: vk.Viewport, scissor: vk.Rect2D, + texture_sampler: vk.Sampler, swapchain_images: []SwapchainImage, swapchain_framebuffers: []vk.Framebuffer, @@ -85,14 +87,22 @@ pub const VulkanRenderer = struct { // Descriptors descriptor_set_layout: vk.DescriptorSetLayout, + sampler_set_layout: vk.DescriptorSetLayout, push_constant_range: vk.PushConstantRange, descriptor_pool: vk.DescriptorPool, + sampler_descriptor_pool: vk.DescriptorPool, descriptor_sets: []vk.DescriptorSet, + sampler_descriptor_sets: std.ArrayList(vk.DescriptorSet), vp_uniform_buffer: []vk.Buffer, 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 graphics_pipeline: vk.Pipeline, pipeline_layout: vk.PipelineLayout, @@ -139,6 +149,19 @@ pub const VulkanRenderer = struct { try self.createFramebuffers(); 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)); self.ubo_view_projection.projection = zm.perspectiveFovRh( std.math.degreesToRadians(45.0), @@ -158,17 +181,17 @@ pub const VulkanRenderer = struct { // Create meshes // Vertex Data 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 } }, // 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 } }, // 3 + .{ .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 }, .tex = .{ 1.0, 0.0 } }, // 1 + .{ .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 }, .tex = .{ 0.0, 1.0 } }, // 3 }; 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 } }, // 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 } }, // 3 + .{ .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 }, .tex = .{ 1.0, 0.0 } }, // 1 + .{ .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 }, .tex = .{ 0.0, 1.0 } }, // 3 }; // Index Data @@ -185,6 +208,7 @@ pub const VulkanRenderer = struct { self.graphics_command_pool, &mesh_vertices, &mesh_indices, + try self.createTexture("test.png"), self.allocator, ); @@ -196,18 +220,12 @@ pub const VulkanRenderer = struct { self.graphics_command_pool, &mesh_vertices2, &mesh_indices, + try self.createTexture("test.png"), self.allocator, ); self.meshes = [_]Mesh{ first_mesh, second_mesh }; - try self.createCommandBuffers(); - try self.createUniformBuffers(); - try self.createDescriptorPool(); - try self.createDescriptorSets(); - - try self.createSynchronisation(); - return self; } @@ -280,12 +298,31 @@ pub const VulkanRenderer = struct { 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.destroyImage(self.depth_buffer_image, null); self.device.freeMemory(self.depth_buffer_image_memory, null); self.device.destroyDescriptorPool(self.descriptor_pool, 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| { self.device.destroyBuffer(self.vp_uniform_buffer[i], null); @@ -407,11 +444,17 @@ pub const VulkanRenderer = struct { else 2; + // Device features + const device_features: vk.PhysicalDeviceFeatures = .{ + .sampler_anisotropy = vk.TRUE, // Enable anisotropy + }; + const device_create_info: vk.DeviceCreateInfo = .{ .queue_create_info_count = queue_count, .p_queue_create_infos = &qci, .pp_enabled_extension_names = &Utilities.device_extensions, .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); @@ -595,6 +638,8 @@ pub const VulkanRenderer = struct { } fn createDescriptorSetLayout(self: *Self) !void { + // -- Uniform values descriptor set layout -- + // UboViewProjection binding info const vp_layout_binding: vk.DescriptorSetLayoutBinding = .{ .binding = 0, // Binding point in shader (designated by binding number in shader) @@ -614,6 +659,25 @@ pub const VulkanRenderer = struct { // Create descriptor set layout 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 { @@ -711,6 +775,13 @@ pub const VulkanRenderer = struct { .format = vk.Format.r32g32b32_sfloat, .offset = @offsetOf(Vertex, "col"), }, + // Texture attribute + .{ + .binding = 0, + .location = 2, + .format = vk.Format.r32g32_sfloat, + .offset = @offsetOf(Vertex, "tex"), + }, }; // -- Vertex input -- @@ -808,9 +879,11 @@ pub const VulkanRenderer = struct { }; // -- Pipeline layout -- + const descriptor_set_layouts = [_]vk.DescriptorSetLayout{ self.descriptor_set_layout, self.sampler_set_layout }; + const pipeline_layout_create_info: vk.PipelineLayoutCreateInfo = .{ - .set_layout_count = 1, - .p_set_layouts = @ptrCast(&self.descriptor_set_layout), + .set_layout_count = @intCast(descriptor_set_layouts.len), + .p_set_layouts = &descriptor_set_layouts, .push_constant_range_count = 1, .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 { // View projection buffer size const vp_buffer_size: vk.DeviceSize = @sizeOf(UboViewProjection); @@ -949,6 +1045,8 @@ pub const VulkanRenderer = struct { } fn createDescriptorPool(self: *Self) !void { + // -- Create uniform descriptor pool -- + // Type of descriptors + how many descriptors (!= descriptor sets) (combined makes the pool size) // View projection pool const vp_pool_size: vk.DescriptorPoolSize = .{ @@ -968,6 +1066,23 @@ pub const VulkanRenderer = struct { // Create descriptor pool 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 { @@ -1095,13 +1210,18 @@ pub const VulkanRenderer = struct { @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 command_buffer.bindDescriptorSets( .graphics, self.pipeline_layout, 0, - 1, - @ptrCast(&self.descriptor_sets[current_image]), + @intCast(descriptor_set_group.len), + &descriptor_set_group, 0, null, ); @@ -1247,8 +1367,8 @@ pub const VulkanRenderer = struct { return false; } + const pdev_features = self.instance.getPhysicalDeviceFeatures(pdev); const queue_family_indices = self.getQueueFamilies(pdev) catch return false; - const extension_support = self.checkDeviceExtensions(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; - 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 { @@ -1376,6 +1496,168 @@ pub const VulkanRenderer = struct { 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) @@ -1479,7 +1761,7 @@ fn debugCallback( const severity = getMessageSeverityLabel(message_severity); 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; }