From f008f6792179afff88b31a076b070dae65713d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Gasi=C7=B9ski?= Date: Sun, 30 Jun 2024 23:43:19 +0200 Subject: [PATCH] Create the graphics pipeline --- src/vulkan_renderer.zig | 208 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 2 deletions(-) diff --git a/src/vulkan_renderer.zig b/src/vulkan_renderer.zig index 5263ba8..f9bc3a4 100644 --- a/src/vulkan_renderer.zig +++ b/src/vulkan_renderer.zig @@ -49,6 +49,11 @@ pub const VulkanRenderer = struct { swapchain: vk.SwapchainKHR, swapchain_images: []SwapchainImage, + // Pipeline + graphics_pipeline: vk.Pipeline, + pipeline_layout: vk.PipelineLayout, + render_pass: vk.RenderPass, + // Utilities swapchain_image_format: vk.Format, extent: vk.Extent2D, @@ -72,6 +77,7 @@ pub const VulkanRenderer = struct { try self.getPhysicalDevice(); try self.createLogicalDevice(); try self.createSwapchain(); + try self.createRenderPass(); try self.createGraphicsPipeline(); return self; @@ -250,6 +256,72 @@ pub const VulkanRenderer = struct { } } + fn createRenderPass(self: *Self) !void { + // Colour attachment of the render pass + const colour_attachment: vk.AttachmentDescription = .{ + .format = self.swapchain_image_format, // Format to use for attachment + .samples = .{ .@"1_bit" = true }, // Number of samples to write for multisampling + .load_op = .clear, // Describes what to do with attachment before rendering + .store_op = .store, // Describes what to do with attachment after rendering + .stencil_load_op = .dont_care, // Describes what to do with stencil before rendering + .stencil_store_op = .dont_care, // Describes what to do with stencil after rendering + // Framebuffer data will be stored as an image, but images can be given different data layouts + // to give optimal use for certain operations + .initial_layout = vk.ImageLayout.undefined, // Image data layout before render pass starts + .final_layout = vk.ImageLayout.present_src_khr, // Image data layout after render pass (to change to) + }; + + // Attachment reference uses an attachment index that refers to index in the attachment list passed to render pass create info + const colour_attachment_reference: vk.AttachmentReference = .{ + .attachment = 0, + .layout = vk.ImageLayout.color_attachment_optimal, + }; + + // Information about a particular subpass the render pass is using + const subpass: vk.SubpassDescription = .{ + .pipeline_bind_point = .graphics, // Pipeline type subpass is to be bound to + .color_attachment_count = 1, + .p_color_attachments = @ptrCast(&colour_attachment_reference), + }; + + // Need to determine when layout transitions occur using subpass dependencies + const subpass_dependencies = [2]vk.SubpassDependency{ + // Conversion from VK_IMAGE_LAYOUT_UNDEFINED to VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL + vk.SubpassDependency{ + // Transition must happen after... + .src_subpass = vk.SUBPASS_EXTERNAL, // Subpass index (VK_SUBPASS_EXTERNAL = outside of renderpass) + .src_stage_mask = .{ .bottom_of_pipe_bit = true }, // Pipeline stage + .src_access_mask = .{ .memory_read_bit = true }, // Stage access mask (memory access) + // But must happen before... + .dst_subpass = 0, + .dst_stage_mask = .{ .color_attachment_output_bit = true }, + .dst_access_mask = .{ .memory_read_bit = true, .memory_write_bit = true }, + }, + // Conversion from VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR + vk.SubpassDependency{ + // Transition must happen after... + .src_subpass = 0, + .src_stage_mask = .{ .color_attachment_output_bit = true }, + .src_access_mask = .{ .memory_read_bit = true, .memory_write_bit = true }, + // But must happen before... + .dst_subpass = vk.SUBPASS_EXTERNAL, + .dst_stage_mask = .{ .bottom_of_pipe_bit = true }, + .dst_access_mask = .{ .memory_read_bit = true }, + }, + }; + + const render_pass_create_info: vk.RenderPassCreateInfo = .{ + .attachment_count = 1, + .p_attachments = @ptrCast(&colour_attachment), + .subpass_count = 1, + .p_subpasses = @ptrCast(&subpass), + .dependency_count = @intCast(subpass_dependencies.len), + .p_dependencies = &subpass_dependencies, + }; + + self.render_pass = try self.device.createRenderPass(&render_pass_create_info, null); + } + fn createGraphicsPipeline(self: *Self) !void { // Create shader modules const vert = try self.device.createShaderModule(&.{ @@ -264,7 +336,7 @@ pub const VulkanRenderer = struct { }, null); defer self.device.destroyShaderModule(frag, null); - // -- Shader stage creation information + // -- Shader stage creation information -- // Vertex stage creation information const vertex_shader_create_info: vk.PipelineShaderStageCreateInfo = .{ @@ -284,7 +356,135 @@ pub const VulkanRenderer = struct { vertex_shader_create_info, fragment_shader_create_info, }; - _ = shader_create_infos; + + // -- Vertex input (TODO: Put in vertex descriptions when resources created) -- + 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) + }; + + // -- Input assembly -- + const assembly_create_info: vk.PipelineInputAssemblyStateCreateInfo = .{ + .topology = .triangle_list, // Primitive type to assemble vertices as + .primitive_restart_enable = vk.FALSE, // Allow overrinding of strip topology to start new primitives + }; + + // -- Viewport & scissor -- + const viewport: vk.Viewport = .{ + .x = 0.0, + .y = 0.0, + .width = @floatFromInt(self.extent.width), + .height = @floatFromInt(self.extent.height), + .min_depth = 0.0, + .max_depth = 1.0, + }; + + const scissor: vk.Rect2D = .{ + .offset = .{ .x = 0, .y = 0 }, + .extent = self.extent, + }; + + const viewport_state_create_info: vk.PipelineViewportStateCreateInfo = .{ + .viewport_count = 1, + .p_viewports = @ptrCast(&viewport), + .scissor_count = 1, + .p_scissors = @ptrCast(&scissor), + }; + + // -- Dynamic states -- + // Dynamic states to enable (TODO: To investigate later) + const dynamic_states = [_]vk.DynamicState{ .viewport, .scissor }; + + const dynamic_state_create_info: vk.PipelineDynamicStateCreateInfo = .{ + .dynamic_state_count = 2, + .p_dynamic_states = &dynamic_states, + }; + + // -- Rasterizer -- + + const rasterizer_create_info: vk.PipelineRasterizationStateCreateInfo = .{ + .depth_clamp_enable = vk.FALSE, // Change if fragments beyond near/far planes are clipped (default) or clamped to planed + .rasterizer_discard_enable = vk.FALSE, // Whether to discard data and skip rasterizer (never creates fragments) + .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 + .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, + .depth_bias_slope_factor = 0, + }; + + // -- Multisampling -- + const multisampling_create_info: vk.PipelineMultisampleStateCreateInfo = .{ + .sample_shading_enable = vk.FALSE, // Enable multisample shading or not + .rasterization_samples = .{ .@"1_bit" = true }, // Number of samples to use per fragment + .min_sample_shading = 1, + .alpha_to_coverage_enable = vk.FALSE, + .alpha_to_one_enable = vk.FALSE, + }; + + // -- Blending -- + + // Blend attachment state (how blending is handled) + const colour_state: vk.PipelineColorBlendAttachmentState = .{ + .color_write_mask = .{ .r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true }, // Colours to apply blending to + .blend_enable = vk.TRUE, // Enable blending + .src_color_blend_factor = vk.BlendFactor.src_alpha, + .dst_color_blend_factor = vk.BlendFactor.one_minus_src_alpha, + .color_blend_op = vk.BlendOp.add, + // Summary: (VK_BLEND_FACTOR_SRC_ALPHA * new colour) + (VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA * old colour) + .src_alpha_blend_factor = vk.BlendFactor.one, + .dst_alpha_blend_factor = vk.BlendFactor.zero, + .alpha_blend_op = vk.BlendOp.add, + // Summary (1 * new alpha) + (0 * old alpha) = new alpha + }; + + // Blending uses equation: (srcColorBlendFactor * new colour) colorBlendOp (dstColorBlendFactor * old colour) + + const colour_blending_create_info: vk.PipelineColorBlendStateCreateInfo = .{ + .logic_op_enable = vk.FALSE, // Alternative to calculations is to use logical operations + .logic_op = .copy, + .attachment_count = 1, + .p_attachments = @ptrCast(&colour_state), + .blend_constants = [_]f32{ 0, 0, 0, 0 }, + }; + + // -- Pipeline layout (TODO: Apply future descriptor set layouts) -- + const pipeline_layout_create_info: vk.PipelineLayoutCreateInfo = .{}; + + self.pipeline_layout = try self.device.createPipelineLayout(&pipeline_layout_create_info, null); + + // -- Depth stencil testing -- + // TODO: Set a depth stencil testing + + // -- Graphics pipeline creation -- + const pipeline_create_info: vk.GraphicsPipelineCreateInfo = .{ + .stage_count = 2, // Number of shader stages + .p_stages = &shader_create_infos, // List of shader stages + .p_vertex_input_state = &vertex_input_create_info, + .p_input_assembly_state = &assembly_create_info, + .p_viewport_state = &viewport_state_create_info, + .p_dynamic_state = &dynamic_state_create_info, + .p_rasterization_state = &rasterizer_create_info, + .p_multisample_state = &multisampling_create_info, + .p_color_blend_state = &colour_blending_create_info, + .p_depth_stencil_state = null, + .layout = self.pipeline_layout, // Pipeline layout the pipeline should use + .render_pass = self.render_pass, // Renderpass description the pipeline is compatible with + .subpass = 0, // Subpass of renderpass to use with pipeline + // Pipeline derivatives: can create multiple pipelines that derive from one another for optimisation + .base_pipeline_handle = .null_handle, // Existing pipeline to derive from... + .base_pipeline_index = -1, // Or index of pipeline being created to derive from (in case creating multiple at once) + }; + + _ = try self.device.createGraphicsPipelines( + .null_handle, + 1, + @ptrCast(&pipeline_create_info), + null, + @ptrCast(&self.graphics_pipeline), + ); } fn getPhysicalDevice(self: *Self) !void { @@ -503,6 +703,10 @@ pub const VulkanRenderer = struct { self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null); } + self.device.destroyPipeline(self.graphics_pipeline, null); + self.device.destroyPipelineLayout(self.pipeline_layout, null); + self.device.destroyRenderPass(self.render_pass, null); + for (self.swapchain_images) |swapchain_image| { self.device.destroyImageView(swapchain_image.image_view, null); }