// Copyright 2017 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "VideoBackends/Vulkan/VKPipeline.h" #include #include "Common/Assert.h" #include "Common/MsgHandler.h" #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/VKShader.h" #include "VideoBackends/Vulkan/VKTexture.h" #include "VideoBackends/Vulkan/VKVertexFormat.h" #include "VideoBackends/Vulkan/VulkanContext.h" #include "VideoCommon/DriverDetails.h" namespace Vulkan { VKPipeline::VKPipeline(const AbstractPipelineConfig& config, VkPipeline pipeline, VkPipelineLayout pipeline_layout, AbstractPipelineUsage usage) : AbstractPipeline(config), m_pipeline(pipeline), m_pipeline_layout(pipeline_layout), m_usage(usage) { } VKPipeline::~VKPipeline() { vkDestroyPipeline(g_vulkan_context->GetDevice(), m_pipeline, nullptr); } static bool IsStripPrimitiveTopology(VkPrimitiveTopology topology) { return topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP || topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP || topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY || topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY; } static VkPipelineRasterizationStateCreateInfo GetVulkanRasterizationState(const RasterizationState& state) { static constexpr std::array cull_modes = { {VK_CULL_MODE_NONE, VK_CULL_MODE_BACK_BIT, VK_CULL_MODE_FRONT_BIT, VK_CULL_MODE_FRONT_AND_BACK}}; bool depth_clamp = g_ActiveConfig.backend_info.bSupportsDepthClamp; return { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkPipelineRasterizationStateCreateFlags flags depth_clamp, // VkBool32 depthClampEnable VK_FALSE, // VkBool32 rasterizerDiscardEnable VK_POLYGON_MODE_FILL, // VkPolygonMode polygonMode cull_modes[u32(state.cullmode.Value())], // VkCullModeFlags cullMode VK_FRONT_FACE_CLOCKWISE, // VkFrontFace frontFace VK_FALSE, // VkBool32 depthBiasEnable 0.0f, // float depthBiasConstantFactor 0.0f, // float depthBiasClamp 0.0f, // float depthBiasSlopeFactor 1.0f // float lineWidth }; } static VkPipelineMultisampleStateCreateInfo GetVulkanMultisampleState(const FramebufferState& state) { return { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkPipelineMultisampleStateCreateFlags flags static_cast( state.samples.Value()), // VkSampleCountFlagBits rasterizationSamples static_cast(state.per_sample_shading), // VkBool32 sampleShadingEnable 1.0f, // float minSampleShading nullptr, // const VkSampleMask* pSampleMask; VK_FALSE, // VkBool32 alphaToCoverageEnable VK_FALSE // VkBool32 alphaToOneEnable }; } static VkPipelineDepthStencilStateCreateInfo GetVulkanDepthStencilState(const DepthState& state) { // Less/greater are swapped due to inverted depth. VkCompareOp compare_op; bool inverted_depth = !g_ActiveConfig.backend_info.bSupportsReversedDepthRange; switch (state.func) { case CompareMode::Never: compare_op = VK_COMPARE_OP_NEVER; break; case CompareMode::Less: compare_op = inverted_depth ? VK_COMPARE_OP_GREATER : VK_COMPARE_OP_LESS; break; case CompareMode::Equal: compare_op = VK_COMPARE_OP_EQUAL; break; case CompareMode::LEqual: compare_op = inverted_depth ? VK_COMPARE_OP_GREATER_OR_EQUAL : VK_COMPARE_OP_LESS_OR_EQUAL; break; case CompareMode::Greater: compare_op = inverted_depth ? VK_COMPARE_OP_LESS : VK_COMPARE_OP_GREATER; break; case CompareMode::NEqual: compare_op = VK_COMPARE_OP_NOT_EQUAL; break; case CompareMode::GEqual: compare_op = inverted_depth ? VK_COMPARE_OP_LESS_OR_EQUAL : VK_COMPARE_OP_GREATER_OR_EQUAL; break; case CompareMode::Always: compare_op = VK_COMPARE_OP_ALWAYS; break; default: PanicAlertFmt("Invalid compare mode {}", state.func); compare_op = VK_COMPARE_OP_ALWAYS; break; } return { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkPipelineDepthStencilStateCreateFlags flags state.testenable, // VkBool32 depthTestEnable state.updateenable, // VkBool32 depthWriteEnable compare_op, // VkCompareOp depthCompareOp VK_FALSE, // VkBool32 depthBoundsTestEnable VK_FALSE, // VkBool32 stencilTestEnable {}, // VkStencilOpState front {}, // VkStencilOpState back 0.0f, // float minDepthBounds 1.0f // float maxDepthBounds }; } static VkPipelineColorBlendAttachmentState GetVulkanAttachmentBlendState(const BlendingState& state, AbstractPipelineUsage usage) { VkPipelineColorBlendAttachmentState vk_state = {}; bool use_dual_source = state.usedualsrc; vk_state.blendEnable = static_cast(state.blendenable); vk_state.colorBlendOp = state.subtract ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD; vk_state.alphaBlendOp = state.subtractAlpha ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD; if (use_dual_source) { static constexpr std::array src_factors = { {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_DST_COLOR, VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_FACTOR_SRC1_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; static constexpr std::array dst_factors = { {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_SRC_COLOR, VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_FACTOR_SRC1_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; vk_state.srcColorBlendFactor = src_factors[u32(state.srcfactor.Value())]; vk_state.srcAlphaBlendFactor = src_factors[u32(state.srcfactoralpha.Value())]; vk_state.dstColorBlendFactor = dst_factors[u32(state.dstfactor.Value())]; vk_state.dstAlphaBlendFactor = dst_factors[u32(state.dstfactoralpha.Value())]; } else { static constexpr std::array src_factors = { {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_DST_COLOR, VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; static constexpr std::array dst_factors = { {VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_SRC_COLOR, VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_FACTOR_DST_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA}}; vk_state.srcColorBlendFactor = src_factors[u32(state.srcfactor.Value())]; vk_state.srcAlphaBlendFactor = src_factors[u32(state.srcfactoralpha.Value())]; vk_state.dstColorBlendFactor = dst_factors[u32(state.dstfactor.Value())]; vk_state.dstAlphaBlendFactor = dst_factors[u32(state.dstfactoralpha.Value())]; } if (state.colorupdate) { vk_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT; } else { vk_state.colorWriteMask = 0; } if (state.alphaupdate) vk_state.colorWriteMask |= VK_COLOR_COMPONENT_A_BIT; return vk_state; } static VkPipelineColorBlendStateCreateInfo GetVulkanColorBlendState(const BlendingState& state, const VkPipelineColorBlendAttachmentState* attachments, uint32_t num_attachments) { static constexpr std::array vk_logic_ops = { {VK_LOGIC_OP_CLEAR, VK_LOGIC_OP_AND, VK_LOGIC_OP_AND_REVERSE, VK_LOGIC_OP_COPY, VK_LOGIC_OP_AND_INVERTED, VK_LOGIC_OP_NO_OP, VK_LOGIC_OP_XOR, VK_LOGIC_OP_OR, VK_LOGIC_OP_NOR, VK_LOGIC_OP_EQUIVALENT, VK_LOGIC_OP_INVERT, VK_LOGIC_OP_OR_REVERSE, VK_LOGIC_OP_COPY_INVERTED, VK_LOGIC_OP_OR_INVERTED, VK_LOGIC_OP_NAND, VK_LOGIC_OP_SET}}; VkBool32 vk_logic_op_enable = static_cast(state.logicopenable); if (vk_logic_op_enable && !g_ActiveConfig.backend_info.bSupportsLogicOp) { // At the time of writing, Adreno and Mali drivers didn't support logic ops. // The "emulation" through blending path has been removed, so just disable it completely. // These drivers don't support dual-source blend either, so issues are to be expected. vk_logic_op_enable = VK_FALSE; } VkLogicOp vk_logic_op = vk_logic_op_enable ? vk_logic_ops[u32(state.logicmode.Value())] : VK_LOGIC_OP_CLEAR; VkPipelineColorBlendStateCreateInfo vk_state = { VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkPipelineColorBlendStateCreateFlags flags vk_logic_op_enable, // VkBool32 logicOpEnable vk_logic_op, // VkLogicOp logicOp num_attachments, // uint32_t attachmentCount attachments, // const VkPipelineColorBlendAttachmentState* pAttachments {1.0f, 1.0f, 1.0f, 1.0f} // float blendConstants[4] }; return vk_state; } std::unique_ptr VKPipeline::Create(const AbstractPipelineConfig& config) { DEBUG_ASSERT(config.vertex_shader && config.pixel_shader); // Get render pass for config. VkRenderPass render_pass = g_object_cache->GetRenderPass( VKTexture::GetVkFormatForHostTextureFormat(config.framebuffer_state.color_texture_format), VKTexture::GetVkFormatForHostTextureFormat(config.framebuffer_state.depth_texture_format), config.framebuffer_state.samples, VK_ATTACHMENT_LOAD_OP_LOAD, config.framebuffer_state.additional_color_attachment_count); if (render_pass == VK_NULL_HANDLE) { PanicAlertFmt("Failed to get render pass"); return nullptr; } // Get pipeline layout. VkPipelineLayout pipeline_layout; switch (config.usage) { case AbstractPipelineUsage::GX: pipeline_layout = g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD); break; case AbstractPipelineUsage::GXUber: pipeline_layout = g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_UBER); break; case AbstractPipelineUsage::Utility: pipeline_layout = g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_UTILITY); break; default: PanicAlertFmt("Unknown pipeline layout."); return nullptr; } // Declare descriptors for empty vertex buffers/attributes static const VkPipelineVertexInputStateCreateInfo empty_vertex_input_state = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkPipelineVertexInputStateCreateFlags flags 0, // uint32_t vertexBindingDescriptionCount nullptr, // const VkVertexInputBindingDescription* pVertexBindingDescriptions 0, // uint32_t vertexAttributeDescriptionCount nullptr // const VkVertexInputAttributeDescription* pVertexAttributeDescriptions }; // Vertex inputs const VkPipelineVertexInputStateCreateInfo& vertex_input_state = config.vertex_format ? static_cast(config.vertex_format)->GetVertexInputStateInfo() : empty_vertex_input_state; // Input assembly static constexpr std::array vk_primitive_topologies = { {VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP}}; VkPipelineInputAssemblyStateCreateInfo input_assembly_state = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, nullptr, 0, vk_primitive_topologies[static_cast(config.rasterization_state.primitive.Value())], VK_FALSE}; // See Vulkan spec, section 19: // If topology is VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST, // VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY, // VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY or VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, // primitiveRestartEnable must be VK_FALSE if (g_ActiveConfig.backend_info.bSupportsPrimitiveRestart && IsStripPrimitiveTopology(input_assembly_state.topology)) { input_assembly_state.primitiveRestartEnable = VK_TRUE; } // Shaders to stages VkPipelineShaderStageCreateInfo shader_stages[3]; uint32_t num_shader_stages = 0; if (config.vertex_shader) { shader_stages[num_shader_stages++] = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, 0, VK_SHADER_STAGE_VERTEX_BIT, static_cast(config.vertex_shader)->GetShaderModule(), "main"}; } if (config.geometry_shader) { shader_stages[num_shader_stages++] = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, 0, VK_SHADER_STAGE_GEOMETRY_BIT, static_cast(config.geometry_shader)->GetShaderModule(), "main"}; } if (config.pixel_shader) { shader_stages[num_shader_stages++] = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, 0, VK_SHADER_STAGE_FRAGMENT_BIT, static_cast(config.pixel_shader)->GetShaderModule(), "main"}; } // Fill in Vulkan descriptor structs from our state structures. VkPipelineRasterizationStateCreateInfo rasterization_state = GetVulkanRasterizationState(config.rasterization_state); VkPipelineMultisampleStateCreateInfo multisample_state = GetVulkanMultisampleState(config.framebuffer_state); VkPipelineDepthStencilStateCreateInfo depth_stencil_state = GetVulkanDepthStencilState(config.depth_state); VkPipelineColorBlendAttachmentState blend_attachment_state = GetVulkanAttachmentBlendState(config.blending_state, config.usage); std::vector blend_attachment_states; blend_attachment_states.push_back(blend_attachment_state); // Right now all our attachments have the same state for (u8 i = 0; i < static_cast(config.framebuffer_state.additional_color_attachment_count); i++) { blend_attachment_states.push_back(blend_attachment_state); } VkPipelineColorBlendStateCreateInfo blend_state = GetVulkanColorBlendState(config.blending_state, blend_attachment_states.data(), static_cast(blend_attachment_states.size())); // This viewport isn't used, but needs to be specified anyway. static const VkViewport viewport = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f}; static const VkRect2D scissor = {{0, 0}, {1, 1}}; static const VkPipelineViewportStateCreateInfo viewport_state = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, nullptr, 0, // VkPipelineViewportStateCreateFlags flags; 1, // uint32_t viewportCount &viewport, // const VkViewport* pViewports 1, // uint32_t scissorCount &scissor // const VkRect2D* pScissors }; // Set viewport and scissor dynamic state so we can change it elsewhere. static const std::array dynamic_states{ VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; static const VkPipelineDynamicStateCreateInfo dynamic_state = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, nullptr, 0, // VkPipelineDynamicStateCreateFlags flags static_cast(dynamic_states.size()), // uint32_t dynamicStateCount dynamic_states.data() // const VkDynamicState* pDynamicStates }; // Combine to full pipeline info structure. VkGraphicsPipelineCreateInfo pipeline_info = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, nullptr, // VkStructureType sType 0, // VkPipelineCreateFlags flags num_shader_stages, // uint32_t stageCount shader_stages, // const VkPipelineShaderStageCreateInfo* pStages &vertex_input_state, // const VkPipelineVertexInputStateCreateInfo* pVertexInputState &input_assembly_state, // const VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState nullptr, // const VkPipelineTessellationStateCreateInfo* pTessellationState &viewport_state, // const VkPipelineViewportStateCreateInfo* pViewportState &rasterization_state, // const VkPipelineRasterizationStateCreateInfo* pRasterizationState &multisample_state, // const VkPipelineMultisampleStateCreateInfo* pMultisampleState &depth_stencil_state, // const VkPipelineDepthStencilStateCreateInfo* pDepthStencilState &blend_state, // const VkPipelineColorBlendStateCreateInfo* pColorBlendState &dynamic_state, // const VkPipelineDynamicStateCreateInfo* pDynamicState pipeline_layout, // VkPipelineLayout layout render_pass, // VkRenderPass renderPass 0, // uint32_t subpass VK_NULL_HANDLE, // VkPipeline basePipelineHandle -1 // int32_t basePipelineIndex }; VkPipeline pipeline; VkResult res = vkCreateGraphicsPipelines(g_vulkan_context->GetDevice(), g_object_cache->GetPipelineCache(), 1, &pipeline_info, nullptr, &pipeline); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines failed: "); return VK_NULL_HANDLE; } return std::make_unique(config, pipeline, pipeline_layout, config.usage); } } // namespace Vulkan