/ src / video_core / renderer_vulkan / vk_graphics_pipeline.cpp
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