vk_graphics_pipeline.cpp
1 // Copyright 2023 Citra Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 #include <boost/container/static_vector.hpp> 6 7 #include "common/hash.h" 8 #include "common/microprofile.h" 9 #include "video_core/renderer_vulkan/pica_to_vk.h" 10 #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" 11 #include "video_core/renderer_vulkan/vk_instance.h" 12 #include "video_core/renderer_vulkan/vk_renderpass_cache.h" 13 #include "video_core/renderer_vulkan/vk_shader_util.h" 14 15 namespace Vulkan { 16 17 MICROPROFILE_DEFINE(Vulkan_Pipeline, "Vulkan", "Pipeline Building", MP_RGB(0, 192, 32)); 18 19 vk::ShaderStageFlagBits MakeShaderStage(std::size_t index) { 20 switch (index) { 21 case 0: 22 return vk::ShaderStageFlagBits::eVertex; 23 case 1: 24 return vk::ShaderStageFlagBits::eFragment; 25 case 2: 26 return vk::ShaderStageFlagBits::eGeometry; 27 default: 28 LOG_CRITICAL(Render_Vulkan, "Invalid shader stage index!"); 29 UNREACHABLE(); 30 } 31 return vk::ShaderStageFlagBits::eVertex; 32 } 33 34 u64 PipelineInfo::Hash(const Instance& instance) const { 35 u64 info_hash = 0; 36 const auto append_hash = [&info_hash](const auto& data) { 37 const u64 data_hash = Common::ComputeStructHash64(data); 38 info_hash = Common::HashCombine(info_hash, data_hash); 39 }; 40 41 append_hash(vertex_layout); 42 append_hash(attachments); 43 append_hash(blending); 44 45 if (!instance.IsExtendedDynamicStateSupported()) { 46 append_hash(rasterization); 47 append_hash(depth_stencil); 48 } 49 50 return info_hash; 51 } 52 53 Shader::Shader(const Instance& instance) : device{instance.GetDevice()} {} 54 55 Shader::Shader(const Instance& instance, vk::ShaderStageFlagBits stage, std::string code) 56 : Shader{instance} { 57 module = Compile(code, stage, instance.GetDevice()); 58 MarkDone(); 59 } 60 61 Shader::~Shader() { 62 if (device && module) { 63 device.destroyShaderModule(module); 64 } 65 } 66 67 GraphicsPipeline::GraphicsPipeline(const Instance& instance_, RenderpassCache& renderpass_cache_, 68 const PipelineInfo& info_, vk::PipelineCache pipeline_cache_, 69 vk::PipelineLayout layout_, std::array<Shader*, 3> stages_, 70 Common::ThreadWorker* worker_) 71 : instance{instance_}, renderpass_cache{renderpass_cache_}, worker{worker_}, 72 pipeline_layout{layout_}, pipeline_cache{pipeline_cache_}, info{info_}, stages{stages_} {} 73 74 GraphicsPipeline::~GraphicsPipeline() = default; 75 76 bool GraphicsPipeline::TryBuild(bool wait_built) { 77 // The pipeline is currently being compiled. We can either wait for it 78 // or skip the draw. 79 if (is_pending) { 80 return wait_built; 81 } 82 83 // If the shaders haven't been compiled yet, we cannot proceed. 84 const bool shaders_pending = std::any_of( 85 stages.begin(), stages.end(), [](Shader* shader) { return shader && !shader->IsDone(); }); 86 if (!wait_built && shaders_pending) { 87 return false; 88 } 89 90 // Ask the driver if it can give us the pipeline quickly. 91 if (!shaders_pending && instance.IsPipelineCreationCacheControlSupported() && Build(true)) { 92 return true; 93 } 94 95 // Fallback to (a)synchronous compilation 96 worker->QueueWork([this] { Build(); }); 97 is_pending = true; 98 return wait_built; 99 } 100 101 bool GraphicsPipeline::Build(bool fail_on_compile_required) { 102 MICROPROFILE_SCOPE(Vulkan_Pipeline); 103 const vk::Device device = instance.GetDevice(); 104 105 std::array<vk::VertexInputBindingDescription, MAX_VERTEX_BINDINGS> bindings; 106 for (u32 i = 0; i < info.vertex_layout.binding_count; i++) { 107 const auto& binding = info.vertex_layout.bindings[i]; 108 bindings[i] = vk::VertexInputBindingDescription{ 109 .binding = binding.binding, 110 .stride = binding.stride, 111 .inputRate = binding.fixed.Value() ? vk::VertexInputRate::eInstance 112 : vk::VertexInputRate::eVertex, 113 }; 114 } 115 116 std::array<vk::VertexInputAttributeDescription, MAX_VERTEX_ATTRIBUTES> attributes; 117 for (u32 i = 0; i < info.vertex_layout.attribute_count; i++) { 118 const auto& attr = info.vertex_layout.attributes[i]; 119 const FormatTraits& traits = instance.GetTraits(attr.type, attr.size); 120 attributes[i] = vk::VertexInputAttributeDescription{ 121 .location = attr.location, 122 .binding = attr.binding, 123 .format = traits.native, 124 .offset = attr.offset, 125 }; 126 127 // At the end there's always the fixed binding which takes up 128 // at least 16 bytes so we should always be able to alias. 129 if (traits.needs_emulation) { 130 const FormatTraits& comp_four_traits = instance.GetTraits(attr.type, 4); 131 attributes[i].format = comp_four_traits.native; 132 } 133 } 134 135 const vk::PipelineVertexInputStateCreateInfo vertex_input_info = { 136 .vertexBindingDescriptionCount = info.vertex_layout.binding_count, 137 .pVertexBindingDescriptions = bindings.data(), 138 .vertexAttributeDescriptionCount = info.vertex_layout.attribute_count, 139 .pVertexAttributeDescriptions = attributes.data(), 140 }; 141 142 const vk::PipelineInputAssemblyStateCreateInfo input_assembly = { 143 .topology = PicaToVK::PrimitiveTopology(info.rasterization.topology), 144 .primitiveRestartEnable = false, 145 }; 146 147 const vk::PipelineRasterizationStateCreateInfo raster_state = { 148 .depthClampEnable = false, 149 .rasterizerDiscardEnable = false, 150 .cullMode = PicaToVK::CullMode(info.rasterization.cull_mode), 151 .frontFace = PicaToVK::FrontFace(info.rasterization.cull_mode), 152 .depthBiasEnable = false, 153 .lineWidth = 1.0f, 154 }; 155 156 const vk::PipelineMultisampleStateCreateInfo multisampling = { 157 .rasterizationSamples = vk::SampleCountFlagBits::e1, 158 .sampleShadingEnable = false, 159 }; 160 161 const vk::PipelineColorBlendAttachmentState colorblend_attachment = { 162 .blendEnable = info.blending.blend_enable, 163 .srcColorBlendFactor = PicaToVK::BlendFunc(info.blending.src_color_blend_factor), 164 .dstColorBlendFactor = PicaToVK::BlendFunc(info.blending.dst_color_blend_factor), 165 .colorBlendOp = PicaToVK::BlendEquation(info.blending.color_blend_eq), 166 .srcAlphaBlendFactor = PicaToVK::BlendFunc(info.blending.src_alpha_blend_factor), 167 .dstAlphaBlendFactor = PicaToVK::BlendFunc(info.blending.dst_alpha_blend_factor), 168 .alphaBlendOp = PicaToVK::BlendEquation(info.blending.alpha_blend_eq), 169 .colorWriteMask = static_cast<vk::ColorComponentFlags>(info.blending.color_write_mask), 170 }; 171 172 const vk::PipelineColorBlendStateCreateInfo color_blending = { 173 .logicOpEnable = !info.blending.blend_enable && !instance.NeedsLogicOpEmulation(), 174 .logicOp = PicaToVK::LogicOp(info.blending.logic_op), 175 .attachmentCount = 1, 176 .pAttachments = &colorblend_attachment, 177 .blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f}, 178 }; 179 180 const vk::Viewport viewport = { 181 .x = 0.0f, 182 .y = 0.0f, 183 .width = 1.0f, 184 .height = 1.0f, 185 .minDepth = 0.0f, 186 .maxDepth = 1.0f, 187 }; 188 189 const vk::Rect2D scissor = { 190 .offset = {0, 0}, 191 .extent = {1, 1}, 192 }; 193 194 const vk::PipelineViewportStateCreateInfo viewport_info = { 195 .viewportCount = 1, 196 .pViewports = &viewport, 197 .scissorCount = 1, 198 .pScissors = &scissor, 199 }; 200 201 boost::container::static_vector<vk::DynamicState, 14> dynamic_states = { 202 vk::DynamicState::eViewport, vk::DynamicState::eScissor, 203 vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask, 204 vk::DynamicState::eStencilReference, vk::DynamicState::eBlendConstants, 205 }; 206 207 if (instance.IsExtendedDynamicStateSupported()) { 208 constexpr std::array extended = { 209 vk::DynamicState::eCullModeEXT, vk::DynamicState::eDepthCompareOpEXT, 210 vk::DynamicState::eDepthTestEnableEXT, vk::DynamicState::eDepthWriteEnableEXT, 211 vk::DynamicState::eFrontFaceEXT, vk::DynamicState::ePrimitiveTopologyEXT, 212 vk::DynamicState::eStencilOpEXT, vk::DynamicState::eStencilTestEnableEXT, 213 }; 214 dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end()); 215 } 216 217 const vk::PipelineDynamicStateCreateInfo dynamic_info = { 218 .dynamicStateCount = static_cast<u32>(dynamic_states.size()), 219 .pDynamicStates = dynamic_states.data(), 220 }; 221 222 const vk::StencilOpState stencil_op_state = { 223 .failOp = PicaToVK::StencilOp(info.depth_stencil.stencil_fail_op), 224 .passOp = PicaToVK::StencilOp(info.depth_stencil.stencil_pass_op), 225 .depthFailOp = PicaToVK::StencilOp(info.depth_stencil.stencil_depth_fail_op), 226 .compareOp = PicaToVK::CompareFunc(info.depth_stencil.stencil_compare_op), 227 }; 228 229 const vk::PipelineDepthStencilStateCreateInfo depth_info = { 230 .depthTestEnable = static_cast<u32>(info.depth_stencil.depth_test_enable.Value()), 231 .depthWriteEnable = static_cast<u32>(info.depth_stencil.depth_write_enable.Value()), 232 .depthCompareOp = PicaToVK::CompareFunc(info.depth_stencil.depth_compare_op), 233 .depthBoundsTestEnable = false, 234 .stencilTestEnable = static_cast<u32>(info.depth_stencil.stencil_test_enable.Value()), 235 .front = stencil_op_state, 236 .back = stencil_op_state, 237 }; 238 239 u32 shader_count = 0; 240 std::array<vk::PipelineShaderStageCreateInfo, MAX_SHADER_STAGES> shader_stages; 241 for (std::size_t i = 0; i < stages.size(); i++) { 242 Shader* shader = stages[i]; 243 if (!shader) { 244 continue; 245 } 246 247 shader->WaitDone(); 248 shader_stages[shader_count++] = vk::PipelineShaderStageCreateInfo{ 249 .stage = MakeShaderStage(i), 250 .module = shader->Handle(), 251 .pName = "main", 252 }; 253 } 254 255 vk::GraphicsPipelineCreateInfo pipeline_info = { 256 .stageCount = shader_count, 257 .pStages = shader_stages.data(), 258 .pVertexInputState = &vertex_input_info, 259 .pInputAssemblyState = &input_assembly, 260 .pViewportState = &viewport_info, 261 .pRasterizationState = &raster_state, 262 .pMultisampleState = &multisampling, 263 .pDepthStencilState = &depth_info, 264 .pColorBlendState = &color_blending, 265 .pDynamicState = &dynamic_info, 266 .layout = pipeline_layout, 267 .renderPass = 268 renderpass_cache.GetRenderpass(info.attachments.color, info.attachments.depth, false), 269 }; 270 271 if (fail_on_compile_required) { 272 pipeline_info.flags |= vk::PipelineCreateFlagBits::eFailOnPipelineCompileRequiredEXT; 273 } 274 275 auto result = device.createGraphicsPipelineUnique(pipeline_cache, pipeline_info); 276 if (result.result == vk::Result::eSuccess) { 277 pipeline = std::move(result.value); 278 } else if (result.result == vk::Result::eErrorPipelineCompileRequiredEXT) { 279 return false; 280 } else { 281 UNREACHABLE_MSG("Graphics pipeline creation failed!"); 282 } 283 284 MarkDone(); 285 return true; 286 } 287 288 } // namespace Vulkan