blob: d9f6a86d51ebc4241a07a5701a450d1de6acbd6a [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "lib/escher/impl/model_pipeline_cache.h"
#include "lib/escher/geometry/types.h"
#include "lib/escher/impl/glsl_compiler.h"
#include "lib/escher/impl/mesh_shader_binding.h"
#include "lib/escher/impl/model_data.h"
#include "lib/escher/impl/model_pipeline.h"
#include "lib/escher/impl/model_render_pass.h"
#include "lib/escher/impl/vulkan_utils.h"
#include "lib/escher/resources/resource_recycler.h"
#include "lib/escher/util/trace_macros.h"
namespace escher {
namespace impl {
const ResourceTypeInfo ModelPipelineCache::kTypeInfo(
"ModelPipelineCache", ResourceType::kResource,
ResourceType::kImplModelPipelineCache);
ModelPipelineCache::ModelPipelineCache(ResourceRecycler* recycler,
ModelDataPtr model_data,
ModelRenderPass* render_pass)
: Resource(recycler),
model_data_(std::move(model_data)),
render_pass_(render_pass),
compiler_(std::make_unique<GlslToSpirvCompiler>()) {
FXL_DCHECK(model_data_);
FXL_DCHECK(render_pass_);
}
ModelPipelineCache::~ModelPipelineCache() { pipelines_.clear(); }
ModelPipeline* ModelPipelineCache::GetPipeline(const ModelPipelineSpec& spec) {
auto it = pipelines_.find(spec);
if (it != pipelines_.end()) {
return it->second.get();
}
auto new_pipeline = NewPipeline(spec);
auto new_pipeline_ptr = new_pipeline.get();
pipelines_[spec] = std::move(new_pipeline);
return new_pipeline_ptr;
}
namespace {
// Creates a new PipelineLayout and Pipeline using only the provided arguments.
std::pair<vk::Pipeline, vk::PipelineLayout> NewPipelineHelper(
ModelData* model_data, vk::ShaderModule vertex_module,
vk::ShaderModule fragment_module, bool enable_depth_test,
bool enable_depth_write, bool enable_blending,
vk::CompareOp depth_compare_op, vk::RenderPass render_pass,
std::vector<vk::DescriptorSetLayout> descriptor_set_layouts,
const ModelPipelineSpec& spec, vk::SampleCountFlagBits sample_count) {
vk::Device device = model_data->device();
// Depending on configuration, more dynamic states may be added later.
vk::PipelineDynamicStateCreateInfo dynamic_state_info;
std::vector<vk::DynamicState> dynamic_states{vk::DynamicState::eViewport,
vk::DynamicState::eScissor};
vk::PipelineShaderStageCreateInfo vertex_stage_info;
vertex_stage_info.stage = vk::ShaderStageFlagBits::eVertex;
vertex_stage_info.module = vertex_module;
vertex_stage_info.pName = "main";
vk::PipelineShaderStageCreateInfo fragment_stage_info;
fragment_stage_info.stage = vk::ShaderStageFlagBits::eFragment;
fragment_stage_info.module = fragment_module;
fragment_stage_info.pName = "main";
vk::PipelineShaderStageCreateInfo shader_stages[] = {vertex_stage_info,
fragment_stage_info};
vk::PipelineVertexInputStateCreateInfo vertex_input_info;
{
auto& mesh_shader_binding =
model_data->GetMeshShaderBinding(spec.mesh_spec);
vertex_input_info.vertexBindingDescriptionCount = 1;
vertex_input_info.pVertexBindingDescriptions =
mesh_shader_binding.binding();
vertex_input_info.vertexAttributeDescriptionCount =
mesh_shader_binding.attributes().size();
vertex_input_info.pVertexAttributeDescriptions =
mesh_shader_binding.attributes().data();
}
vk::PipelineInputAssemblyStateCreateInfo input_assembly_info;
input_assembly_info.topology = vk::PrimitiveTopology::eTriangleList;
input_assembly_info.primitiveRestartEnable = false;
vk::PipelineDepthStencilStateCreateInfo depth_stencil_info;
depth_stencil_info.depthTestEnable = enable_depth_test;
depth_stencil_info.depthWriteEnable = enable_depth_write;
depth_stencil_info.depthCompareOp = depth_compare_op;
depth_stencil_info.depthBoundsTestEnable = false;
// Set the stencil state appropriately, depending on whether we (i.e. the
// escher::Object eventually rendered by this pipeline) is a clipper and/or
// a clippee. See also ModelDisplayListBuilder, where these pipelines are
// used.
depth_stencil_info.stencilTestEnable = true;
auto& op_state = depth_stencil_info.front;
op_state.compareMask = 0xFF;
op_state.writeMask = 0xFF;
if (!spec.is_clippee) {
switch (spec.clipper_state) {
case ModelPipelineSpec::ClipperState::kNoClipChildren: {
// We neither clip nor are clipped, so we can disable the stencil test
// for this pipeline.
depth_stencil_info.stencilTestEnable = false;
} break;
case ModelPipelineSpec::ClipperState::kBeginClipChildren: {
// We are a top-level clipper that is not clipped by anyone else.
// Write to the stencil buffer to define where children are allowed
// to draw.
op_state.failOp = vk::StencilOp::eKeep;
op_state.passOp = vk::StencilOp::eReplace;
op_state.depthFailOp = vk::StencilOp::eReplace;
op_state.compareOp = vk::CompareOp::eAlways;
op_state.reference = 1;
} break;
case ModelPipelineSpec::ClipperState::kEndClipChildren: {
// We are a top-level clipper that is not clipped by anyone else.
// Clean up stencil buffer so that we do not clip subsequent objects.
op_state.failOp = vk::StencilOp::eKeep;
op_state.passOp = vk::StencilOp::eReplace;
op_state.depthFailOp = vk::StencilOp::eReplace;
op_state.compareOp = vk::CompareOp::eAlways;
op_state.reference = 0;
} break;
}
} else {
// In all cases where we are clipped by another object, we must be able
// to dynamically set the stencil reference value.
dynamic_states.push_back(vk::DynamicState::eStencilReference);
switch (spec.clipper_state) {
case ModelPipelineSpec::ClipperState::kNoClipChildren: {
// We are clipped by some other object, but do not clip any children.
// Therefore, test the stencil buffer, but do not update it.
op_state.failOp = vk::StencilOp::eKeep;
op_state.passOp = vk::StencilOp::eKeep;
op_state.depthFailOp = vk::StencilOp::eKeep;
op_state.compareOp = vk::CompareOp::eEqual;
} break;
case ModelPipelineSpec::ClipperState::kBeginClipChildren: {
// We are clipped by some other object, and also want to clip our
// children. This is achieved by incrementing the stencil buffer.
// Therefore, test the stencil buffer, and update it if successful.
op_state.failOp = vk::StencilOp::eKeep;
op_state.passOp = vk::StencilOp::eIncrementAndWrap;
op_state.depthFailOp = vk::StencilOp::eIncrementAndWrap;
op_state.compareOp = vk::CompareOp::eEqual;
} break;
case ModelPipelineSpec::ClipperState::kEndClipChildren: {
// We have finished clipping our children. Revert the stencil buffer to
// its previous state so that we don't clip subsequent objects.
op_state.failOp = vk::StencilOp::eKeep;
op_state.passOp = vk::StencilOp::eDecrementAndWrap;
op_state.depthFailOp = vk::StencilOp::eDecrementAndWrap;
op_state.compareOp = vk::CompareOp::eEqual;
} break;
}
}
// This is set dynamically during rendering.
vk::Viewport viewport;
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = 0.f;
viewport.height = 0.f;
viewport.minDepth = 0.0f;
viewport.maxDepth = 0.0f;
// This is set dynamically during rendering.
vk::Rect2D scissor;
scissor.offset = vk::Offset2D{0, 0};
scissor.extent = vk::Extent2D{0, 0};
vk::PipelineViewportStateCreateInfo viewport_state;
viewport_state.viewportCount = 1;
viewport_state.pViewports = &viewport;
viewport_state.scissorCount = 1;
viewport_state.pScissors = &scissor;
vk::PipelineRasterizationStateCreateInfo rasterizer;
rasterizer.depthClampEnable = false;
rasterizer.rasterizerDiscardEnable = false;
rasterizer.polygonMode = vk::PolygonMode::eFill;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = vk::CullModeFlagBits::eBack;
rasterizer.frontFace = vk::FrontFace::eClockwise;
rasterizer.depthBiasEnable = false;
vk::PipelineMultisampleStateCreateInfo multisampling;
multisampling.sampleShadingEnable = false;
multisampling.rasterizationSamples = sample_count;
vk::PipelineColorBlendAttachmentState color_blend_attachment;
if (fragment_module) {
color_blend_attachment.colorWriteMask =
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
}
if (enable_blending) {
// TODO(ES-28): In some cases we have a constant alpha, so we could
// optimize this with eConstantAlpha and eOneMinusConstantAlpha.
color_blend_attachment.blendEnable = true;
color_blend_attachment.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha;
color_blend_attachment.dstColorBlendFactor =
vk::BlendFactor::eOneMinusSrcAlpha;
color_blend_attachment.colorBlendOp = vk::BlendOp::eAdd;
color_blend_attachment.srcAlphaBlendFactor =
vk::BlendFactor::eOneMinusDstAlpha;
color_blend_attachment.dstAlphaBlendFactor = vk::BlendFactor::eOne;
color_blend_attachment.alphaBlendOp = vk::BlendOp::eAdd;
} else {
color_blend_attachment.blendEnable = false;
}
vk::PipelineColorBlendStateCreateInfo color_blending;
color_blending.logicOpEnable = false;
color_blending.logicOp = vk::LogicOp::eCopy;
color_blending.attachmentCount = 1;
color_blending.pAttachments = &color_blend_attachment;
color_blending.blendConstants[0] = 0.0f;
color_blending.blendConstants[1] = 0.0f;
color_blending.blendConstants[2] = 0.0f;
color_blending.blendConstants[3] = 0.0f;
vk::PipelineLayoutCreateInfo pipeline_layout_info;
pipeline_layout_info.setLayoutCount =
static_cast<uint32_t>(descriptor_set_layouts.size());
pipeline_layout_info.pSetLayouts = descriptor_set_layouts.data();
pipeline_layout_info.pushConstantRangeCount = 0;
vk::PipelineLayout pipeline_layout = ESCHER_CHECKED_VK_RESULT(
device.createPipelineLayout(pipeline_layout_info, nullptr));
// All dynamic states have been accumulated, so finalize them.
dynamic_state_info.dynamicStateCount =
static_cast<uint32_t>(dynamic_states.size());
dynamic_state_info.pDynamicStates = dynamic_states.data();
vk::GraphicsPipelineCreateInfo pipeline_info;
pipeline_info.stageCount = fragment_module ? 2 : 1;
pipeline_info.pStages = shader_stages;
pipeline_info.pVertexInputState = &vertex_input_info;
pipeline_info.pInputAssemblyState = &input_assembly_info;
pipeline_info.pViewportState = &viewport_state;
pipeline_info.pRasterizationState = &rasterizer;
pipeline_info.pDepthStencilState = &depth_stencil_info;
pipeline_info.pMultisampleState = &multisampling;
pipeline_info.pColorBlendState = &color_blending;
pipeline_info.pDynamicState = &dynamic_state_info;
pipeline_info.layout = pipeline_layout;
pipeline_info.renderPass = render_pass;
pipeline_info.subpass = 0;
pipeline_info.basePipelineHandle = vk::Pipeline();
vk::Pipeline pipeline = ESCHER_CHECKED_VK_RESULT(
device.createGraphicsPipeline(nullptr, pipeline_info));
return {pipeline, pipeline_layout};
}
} // namespace
std::unique_ptr<ModelPipeline> ModelPipelineCache::NewPipeline(
const ModelPipelineSpec& spec) {
TRACE_DURATION("gfx", "escher::ModelPipelineCache::NewPipeline");
// TODO: create customized pipelines for different shapes/materials/etc.
// Only specs with materials may be opaque.
FXL_DCHECK(!spec.is_opaque || spec.has_material);
std::future<SpirvData> vertex_spirv_future;
std::future<SpirvData> fragment_spirv_future;
vertex_spirv_future = compiler_->Compile(
vk::ShaderStageFlagBits::eVertex,
{{render_pass_->GetVertexShaderSourceCode(spec)}}, std::string(), "main");
// The depth-only pre-pass uses a different renderpass and a cheap fragment
// shader.
const bool enable_depth_write = spec.has_material && !spec.disable_depth_test;
const bool enable_depth_test = !spec.disable_depth_test;
const bool omit_fragment_shader =
render_pass_->OmitFragmentShader() || !spec.has_material;
const bool enable_blending = !spec.is_opaque && !omit_fragment_shader;
const vk::CompareOp depth_compare_op = vk::CompareOp::eLess;
if (!omit_fragment_shader) {
fragment_spirv_future =
compiler_->Compile(vk::ShaderStageFlagBits::eFragment,
{{render_pass_->GetFragmentShaderSourceCode(spec)}},
std::string(), "main");
}
// Wait for completion of asynchronous shader compilation.
vk::ShaderModule vertex_module;
vk::Device device = model_data_->device();
{
SpirvData spirv = vertex_spirv_future.get();
vk::ShaderModuleCreateInfo module_info;
module_info.codeSize = spirv.size() * sizeof(uint32_t);
module_info.pCode = spirv.data();
vertex_module =
ESCHER_CHECKED_VK_RESULT(device.createShaderModule(module_info));
}
vk::ShaderModule fragment_module;
if (!omit_fragment_shader) {
SpirvData spirv = fragment_spirv_future.get();
vk::ShaderModuleCreateInfo module_info;
module_info.codeSize = spirv.size() * sizeof(uint32_t);
module_info.pCode = spirv.data();
fragment_module =
ESCHER_CHECKED_VK_RESULT(device.createShaderModule(module_info));
}
auto pipeline_and_layout = NewPipelineHelper(
model_data_.get(), vertex_module, fragment_module, enable_depth_test,
enable_depth_write, enable_blending, depth_compare_op, render_pass_->vk(),
{model_data_->per_model_layout(), model_data_->per_object_layout()}, spec,
SampleCountFlagBitsFromInt(render_pass_->sample_count()));
device.destroyShaderModule(vertex_module);
if (fragment_module) {
device.destroyShaderModule(fragment_module);
}
return std::make_unique<ModelPipeline>(
spec, device, pipeline_and_layout.first, pipeline_and_layout.second);
}
} // namespace impl
} // namespace escher