citra/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp

289 lines
11 KiB
C++

// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/container/static_vector.hpp>
#include "common/hash.h"
#include "common/microprofile.h"
#include "video_core/renderer_vulkan/pica_to_vk.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
namespace Vulkan {
MICROPROFILE_DEFINE(Vulkan_Pipeline, "Vulkan", "Pipeline Building", MP_RGB(0, 192, 32));
vk::ShaderStageFlagBits MakeShaderStage(std::size_t index) {
switch (index) {
case 0:
return vk::ShaderStageFlagBits::eVertex;
case 1:
return vk::ShaderStageFlagBits::eFragment;
case 2:
return vk::ShaderStageFlagBits::eGeometry;
default:
LOG_CRITICAL(Render_Vulkan, "Invalid shader stage index!");
UNREACHABLE();
}
return vk::ShaderStageFlagBits::eVertex;
}
u64 PipelineInfo::Hash(const Instance& instance) const {
u64 info_hash = 0;
const auto append_hash = [&info_hash](const auto& data) {
const u64 data_hash = Common::ComputeStructHash64(data);
info_hash = Common::HashCombine(info_hash, data_hash);
};
append_hash(vertex_layout);
append_hash(attachments);
append_hash(blending);
if (!instance.IsExtendedDynamicStateSupported()) {
append_hash(rasterization);
append_hash(depth_stencil);
}
return info_hash;
}
Shader::Shader(const Instance& instance) : device{instance.GetDevice()} {}
Shader::Shader(const Instance& instance, vk::ShaderStageFlagBits stage, std::string code)
: Shader{instance} {
module = Compile(code, stage, instance.GetDevice());
MarkDone();
}
Shader::~Shader() {
if (device && module) {
device.destroyShaderModule(module);
}
}
GraphicsPipeline::GraphicsPipeline(const Instance& instance_, RenderpassCache& renderpass_cache_,
const PipelineInfo& info_, vk::PipelineCache pipeline_cache_,
vk::PipelineLayout layout_, std::array<Shader*, 3> stages_,
Common::ThreadWorker* worker_)
: instance{instance_}, renderpass_cache{renderpass_cache_}, worker{worker_},
pipeline_layout{layout_}, pipeline_cache{pipeline_cache_}, info{info_}, stages{stages_} {}
GraphicsPipeline::~GraphicsPipeline() = default;
bool GraphicsPipeline::TryBuild(bool wait_built) {
// The pipeline is currently being compiled. We can either wait for it
// or skip the draw.
if (is_pending) {
return wait_built;
}
// If the shaders haven't been compiled yet, we cannot proceed.
const bool shaders_pending = std::any_of(
stages.begin(), stages.end(), [](Shader* shader) { return shader && !shader->IsDone(); });
if (!wait_built && shaders_pending) {
return false;
}
// Ask the driver if it can give us the pipeline quickly.
if (!shaders_pending && instance.IsPipelineCreationCacheControlSupported() && Build(true)) {
return true;
}
// Fallback to (a)synchronous compilation
worker->QueueWork([this] { Build(); });
is_pending = true;
return wait_built;
}
bool GraphicsPipeline::Build(bool fail_on_compile_required) {
MICROPROFILE_SCOPE(Vulkan_Pipeline);
const vk::Device device = instance.GetDevice();
std::array<vk::VertexInputBindingDescription, MAX_VERTEX_BINDINGS> bindings;
for (u32 i = 0; i < info.vertex_layout.binding_count; i++) {
const auto& binding = info.vertex_layout.bindings[i];
bindings[i] = vk::VertexInputBindingDescription{
.binding = binding.binding,
.stride = binding.stride,
.inputRate = binding.fixed.Value() ? vk::VertexInputRate::eInstance
: vk::VertexInputRate::eVertex,
};
}
std::array<vk::VertexInputAttributeDescription, MAX_VERTEX_ATTRIBUTES> attributes;
for (u32 i = 0; i < info.vertex_layout.attribute_count; i++) {
const auto& attr = info.vertex_layout.attributes[i];
const FormatTraits& traits = instance.GetTraits(attr.type, attr.size);
attributes[i] = vk::VertexInputAttributeDescription{
.location = attr.location,
.binding = attr.binding,
.format = traits.native,
.offset = attr.offset,
};
// At the end there's always the fixed binding which takes up
// at least 16 bytes so we should always be able to alias.
if (traits.needs_emulation) {
const FormatTraits& comp_four_traits = instance.GetTraits(attr.type, 4);
attributes[i].format = comp_four_traits.native;
}
}
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
.vertexBindingDescriptionCount = info.vertex_layout.binding_count,
.pVertexBindingDescriptions = bindings.data(),
.vertexAttributeDescriptionCount = info.vertex_layout.attribute_count,
.pVertexAttributeDescriptions = attributes.data(),
};
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
.topology = PicaToVK::PrimitiveTopology(info.rasterization.topology),
.primitiveRestartEnable = false,
};
const vk::PipelineRasterizationStateCreateInfo raster_state = {
.depthClampEnable = false,
.rasterizerDiscardEnable = false,
.cullMode = PicaToVK::CullMode(info.rasterization.cull_mode),
.frontFace = PicaToVK::FrontFace(info.rasterization.cull_mode),
.depthBiasEnable = false,
.lineWidth = 1.0f,
};
const vk::PipelineMultisampleStateCreateInfo multisampling = {
.rasterizationSamples = vk::SampleCountFlagBits::e1,
.sampleShadingEnable = false,
};
const vk::PipelineColorBlendAttachmentState colorblend_attachment = {
.blendEnable = info.blending.blend_enable,
.srcColorBlendFactor = PicaToVK::BlendFunc(info.blending.src_color_blend_factor),
.dstColorBlendFactor = PicaToVK::BlendFunc(info.blending.dst_color_blend_factor),
.colorBlendOp = PicaToVK::BlendEquation(info.blending.color_blend_eq),
.srcAlphaBlendFactor = PicaToVK::BlendFunc(info.blending.src_alpha_blend_factor),
.dstAlphaBlendFactor = PicaToVK::BlendFunc(info.blending.dst_alpha_blend_factor),
.alphaBlendOp = PicaToVK::BlendEquation(info.blending.alpha_blend_eq),
.colorWriteMask = static_cast<vk::ColorComponentFlags>(info.blending.color_write_mask),
};
const vk::PipelineColorBlendStateCreateInfo color_blending = {
.logicOpEnable = !info.blending.blend_enable && !instance.NeedsLogicOpEmulation(),
.logicOp = PicaToVK::LogicOp(info.blending.logic_op),
.attachmentCount = 1,
.pAttachments = &colorblend_attachment,
.blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f},
};
const vk::Viewport viewport = {
.x = 0.0f,
.y = 0.0f,
.width = 1.0f,
.height = 1.0f,
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
const vk::Rect2D scissor = {
.offset = {0, 0},
.extent = {1, 1},
};
const vk::PipelineViewportStateCreateInfo viewport_info = {
.viewportCount = 1,
.pViewports = &viewport,
.scissorCount = 1,
.pScissors = &scissor,
};
boost::container::static_vector<vk::DynamicState, 14> dynamic_states = {
vk::DynamicState::eViewport, vk::DynamicState::eScissor,
vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask,
vk::DynamicState::eStencilReference, vk::DynamicState::eBlendConstants,
};
if (instance.IsExtendedDynamicStateSupported()) {
constexpr std::array extended = {
vk::DynamicState::eCullModeEXT, vk::DynamicState::eDepthCompareOpEXT,
vk::DynamicState::eDepthTestEnableEXT, vk::DynamicState::eDepthWriteEnableEXT,
vk::DynamicState::eFrontFaceEXT, vk::DynamicState::ePrimitiveTopologyEXT,
vk::DynamicState::eStencilOpEXT, vk::DynamicState::eStencilTestEnableEXT,
};
dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
}
const vk::PipelineDynamicStateCreateInfo dynamic_info = {
.dynamicStateCount = static_cast<u32>(dynamic_states.size()),
.pDynamicStates = dynamic_states.data(),
};
const vk::StencilOpState stencil_op_state = {
.failOp = PicaToVK::StencilOp(info.depth_stencil.stencil_fail_op),
.passOp = PicaToVK::StencilOp(info.depth_stencil.stencil_pass_op),
.depthFailOp = PicaToVK::StencilOp(info.depth_stencil.stencil_depth_fail_op),
.compareOp = PicaToVK::CompareFunc(info.depth_stencil.stencil_compare_op),
};
const vk::PipelineDepthStencilStateCreateInfo depth_info = {
.depthTestEnable = static_cast<u32>(info.depth_stencil.depth_test_enable.Value()),
.depthWriteEnable = static_cast<u32>(info.depth_stencil.depth_write_enable.Value()),
.depthCompareOp = PicaToVK::CompareFunc(info.depth_stencil.depth_compare_op),
.depthBoundsTestEnable = false,
.stencilTestEnable = static_cast<u32>(info.depth_stencil.stencil_test_enable.Value()),
.front = stencil_op_state,
.back = stencil_op_state,
};
u32 shader_count = 0;
std::array<vk::PipelineShaderStageCreateInfo, MAX_SHADER_STAGES> shader_stages;
for (std::size_t i = 0; i < stages.size(); i++) {
Shader* shader = stages[i];
if (!shader) {
continue;
}
shader->WaitDone();
shader_stages[shader_count++] = vk::PipelineShaderStageCreateInfo{
.stage = MakeShaderStage(i),
.module = shader->Handle(),
.pName = "main",
};
}
vk::GraphicsPipelineCreateInfo pipeline_info = {
.stageCount = shader_count,
.pStages = shader_stages.data(),
.pVertexInputState = &vertex_input_info,
.pInputAssemblyState = &input_assembly,
.pViewportState = &viewport_info,
.pRasterizationState = &raster_state,
.pMultisampleState = &multisampling,
.pDepthStencilState = &depth_info,
.pColorBlendState = &color_blending,
.pDynamicState = &dynamic_info,
.layout = pipeline_layout,
.renderPass =
renderpass_cache.GetRenderpass(info.attachments.color, info.attachments.depth, false),
};
if (fail_on_compile_required) {
pipeline_info.flags |= vk::PipelineCreateFlagBits::eFailOnPipelineCompileRequiredEXT;
}
auto result = device.createGraphicsPipelineUnique(pipeline_cache, pipeline_info);
if (result.result == vk::Result::eSuccess) {
pipeline = std::move(result.value);
} else if (result.result == vk::Result::eErrorPipelineCompileRequiredEXT) {
return false;
} else {
UNREACHABLE_MSG("Graphics pipeline creation failed!");
}
MarkDone();
return true;
}
} // namespace Vulkan