// Copyright 2018 The Amber Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/vulkan/pipeline.h" #include #include #include #include "src/command.h" #include "src/engine.h" #include "src/make_unique.h" #include "src/vulkan/buffer_descriptor.h" #include "src/vulkan/compute_pipeline.h" #include "src/vulkan/device.h" #include "src/vulkan/graphics_pipeline.h" #include "src/vulkan/image_descriptor.h" #include "src/vulkan/sampler_descriptor.h" namespace amber { namespace vulkan { namespace { const char* kDefaultEntryPointName = "main"; } // namespace Pipeline::Pipeline( PipelineType type, Device* device, uint32_t fence_timeout_ms, const std::vector& shader_stage_info) : device_(device), pipeline_type_(type), shader_stage_info_(shader_stage_info), fence_timeout_ms_(fence_timeout_ms) {} Pipeline::~Pipeline() { // Command must be reset before we destroy descriptors or we get a validation // error. command_ = nullptr; for (auto& info : descriptor_set_info_) { if (info.layout != VK_NULL_HANDLE) { device_->GetPtrs()->vkDestroyDescriptorSetLayout(device_->GetVkDevice(), info.layout, nullptr); } if (info.empty) continue; if (info.pool != VK_NULL_HANDLE) { device_->GetPtrs()->vkDestroyDescriptorPool(device_->GetVkDevice(), info.pool, nullptr); } } } GraphicsPipeline* Pipeline::AsGraphics() { return static_cast(this); } ComputePipeline* Pipeline::AsCompute() { return static_cast(this); } Result Pipeline::Initialize(CommandPool* pool) { push_constant_ = MakeUnique(device_); command_ = MakeUnique(device_, pool); return command_->Initialize(); } Result Pipeline::CreateDescriptorSetLayouts() { for (auto& info : descriptor_set_info_) { VkDescriptorSetLayoutCreateInfo desc_info = VkDescriptorSetLayoutCreateInfo(); desc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; // If there are no descriptors for this descriptor set we only // need to create its layout and there will be no bindings. std::vector bindings; for (auto& desc : info.descriptors) { bindings.emplace_back(); bindings.back().binding = desc->GetBinding(); bindings.back().descriptorType = desc->GetVkDescriptorType(); bindings.back().descriptorCount = desc->GetDescriptorCount(); bindings.back().stageFlags = VK_SHADER_STAGE_ALL; } desc_info.bindingCount = static_cast(bindings.size()); desc_info.pBindings = bindings.data(); if (device_->GetPtrs()->vkCreateDescriptorSetLayout( device_->GetVkDevice(), &desc_info, nullptr, &info.layout) != VK_SUCCESS) { return Result("Vulkan::Calling vkCreateDescriptorSetLayout Fail"); } } return {}; } Result Pipeline::CreateDescriptorPools() { for (auto& info : descriptor_set_info_) { if (info.empty) continue; std::vector pool_sizes; for (auto& desc : info.descriptors) { VkDescriptorType type = desc->GetVkDescriptorType(); auto it = find_if(pool_sizes.begin(), pool_sizes.end(), [&type](const VkDescriptorPoolSize& size) { return size.type == type; }); if (it != pool_sizes.end()) { it->descriptorCount += desc->GetDescriptorCount(); continue; } pool_sizes.emplace_back(); pool_sizes.back().type = type; pool_sizes.back().descriptorCount = desc->GetDescriptorCount(); } VkDescriptorPoolCreateInfo pool_info = VkDescriptorPoolCreateInfo(); pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.maxSets = 1; pool_info.poolSizeCount = static_cast(pool_sizes.size()); pool_info.pPoolSizes = pool_sizes.data(); if (device_->GetPtrs()->vkCreateDescriptorPool(device_->GetVkDevice(), &pool_info, nullptr, &info.pool) != VK_SUCCESS) { return Result("Vulkan::Calling vkCreateDescriptorPool Fail"); } } return {}; } Result Pipeline::CreateDescriptorSets() { for (size_t i = 0; i < descriptor_set_info_.size(); ++i) { if (descriptor_set_info_[i].empty) continue; VkDescriptorSetAllocateInfo desc_set_info = VkDescriptorSetAllocateInfo(); desc_set_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; desc_set_info.descriptorPool = descriptor_set_info_[i].pool; desc_set_info.descriptorSetCount = 1; desc_set_info.pSetLayouts = &descriptor_set_info_[i].layout; VkDescriptorSet desc_set = VK_NULL_HANDLE; if (device_->GetPtrs()->vkAllocateDescriptorSets( device_->GetVkDevice(), &desc_set_info, &desc_set) != VK_SUCCESS) { return Result("Vulkan::Calling vkAllocateDescriptorSets Fail"); } descriptor_set_info_[i].vk_desc_set = desc_set; } return {}; } Result Pipeline::CreateVkPipelineLayout(VkPipelineLayout* pipeline_layout) { Result r = CreateVkDescriptorRelatedObjectsIfNeeded(); if (!r.IsSuccess()) return r; std::vector descriptor_set_layouts; for (const auto& desc_set : descriptor_set_info_) descriptor_set_layouts.push_back(desc_set.layout); VkPipelineLayoutCreateInfo pipeline_layout_info = VkPipelineLayoutCreateInfo(); pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipeline_layout_info.setLayoutCount = static_cast(descriptor_set_layouts.size()); pipeline_layout_info.pSetLayouts = descriptor_set_layouts.data(); VkPushConstantRange push_const_range = push_constant_->GetVkPushConstantRange(); if (push_const_range.size > 0) { pipeline_layout_info.pushConstantRangeCount = 1U; pipeline_layout_info.pPushConstantRanges = &push_const_range; } if (device_->GetPtrs()->vkCreatePipelineLayout( device_->GetVkDevice(), &pipeline_layout_info, nullptr, pipeline_layout) != VK_SUCCESS) { return Result("Vulkan::Calling vkCreatePipelineLayout Fail"); } return {}; } Result Pipeline::CreateVkDescriptorRelatedObjectsIfNeeded() { if (descriptor_related_objects_already_created_) return {}; Result r = CreateDescriptorSetLayouts(); if (!r.IsSuccess()) return r; r = CreateDescriptorPools(); if (!r.IsSuccess()) return r; r = CreateDescriptorSets(); if (!r.IsSuccess()) return r; descriptor_related_objects_already_created_ = true; return {}; } void Pipeline::UpdateDescriptorSetsIfNeeded() { for (auto& info : descriptor_set_info_) { for (auto& desc : info.descriptors) desc->UpdateDescriptorSetIfNeeded(info.vk_desc_set); } } Result Pipeline::RecordPushConstant(const VkPipelineLayout& pipeline_layout) { return push_constant_->RecordPushConstantVkCommand(command_.get(), pipeline_layout); } Result Pipeline::AddPushConstantBuffer(const Buffer* buf, uint32_t offset) { if (!buf) return Result("Missing push constant buffer data"); return push_constant_->AddBuffer(buf, offset); } Result Pipeline::GetDescriptorSlot(uint32_t desc_set, uint32_t binding, Descriptor** desc) { *desc = nullptr; if (desc_set >= descriptor_set_info_.size()) { for (size_t i = descriptor_set_info_.size(); i <= static_cast(desc_set); ++i) { descriptor_set_info_.emplace_back(); } } if (descriptor_set_info_[desc_set].empty && descriptor_related_objects_already_created_) { return Result( "Vulkan: Pipeline descriptor related objects were already created but " "try to put data on empty descriptor set '" + std::to_string(desc_set) + "'. Note that all used descriptor sets must be allocated before the " "first compute or draw."); } descriptor_set_info_[desc_set].empty = false; auto& descriptors = descriptor_set_info_[desc_set].descriptors; for (auto& descriptor : descriptors) { if (descriptor->GetBinding() == binding) *desc = descriptor.get(); } return {}; } Result Pipeline::AddBufferDescriptor(const BufferCommand* cmd) { if (cmd == nullptr) return Result("Pipeline::AddBufferDescriptor BufferCommand is nullptr"); if (!cmd->IsSSBO() && !cmd->IsUniform() && !cmd->IsStorageImage() && !cmd->IsSampledImage() && !cmd->IsCombinedImageSampler() && !cmd->IsUniformTexelBuffer() && !cmd->IsStorageTexelBuffer() && !cmd->IsUniformDynamic() && !cmd->IsSSBODynamic()) { return Result("Pipeline::AddBufferDescriptor not supported buffer type"); } Descriptor* desc; Result r = GetDescriptorSlot(cmd->GetDescriptorSet(), cmd->GetBinding(), &desc); if (!r.IsSuccess()) return r; auto& descriptors = descriptor_set_info_[cmd->GetDescriptorSet()].descriptors; bool is_image = false; DescriptorType desc_type = DescriptorType::kUniformBuffer; if (cmd->IsStorageImage()) { desc_type = DescriptorType::kStorageImage; is_image = true; } else if (cmd->IsSampledImage()) { desc_type = DescriptorType::kSampledImage; is_image = true; } else if (cmd->IsCombinedImageSampler()) { desc_type = DescriptorType::kCombinedImageSampler; is_image = true; } else if (cmd->IsUniformTexelBuffer()) { desc_type = DescriptorType::kUniformTexelBuffer; } else if (cmd->IsStorageTexelBuffer()) { desc_type = DescriptorType::kStorageTexelBuffer; } else if (cmd->IsSSBO()) { desc_type = DescriptorType::kStorageBuffer; } else if (cmd->IsUniformDynamic()) { desc_type = DescriptorType::kUniformBufferDynamic; } else if (cmd->IsSSBODynamic()) { desc_type = DescriptorType::kStorageBufferDynamic; } if (desc == nullptr) { if (is_image) { auto image_desc = MakeUnique( cmd->GetBuffer(), desc_type, device_, cmd->GetBaseMipLevel(), cmd->GetDescriptorSet(), cmd->GetBinding()); if (cmd->IsCombinedImageSampler()) image_desc->SetAmberSampler(cmd->GetSampler()); descriptors.push_back(std::move(image_desc)); } else { auto buffer_desc = MakeUnique( cmd->GetBuffer(), desc_type, device_, cmd->GetDescriptorSet(), cmd->GetBinding()); descriptors.push_back(std::move(buffer_desc)); } desc = descriptors.back().get(); } else { if (desc->GetDescriptorType() != desc_type) { return Result( "Descriptors bound to the same binding needs to have matching " "descriptor types"); } // Check that the buffer is not added already. const auto& buffers = desc->AsBufferBackedDescriptor()->GetAmberBuffers(); if (std::find(buffers.begin(), buffers.end(), cmd->GetBuffer()) != buffers.end()) { return Result("Buffer has been added already"); } desc->AsBufferBackedDescriptor()->AddAmberBuffer(cmd->GetBuffer()); } if (cmd->IsUniformDynamic() || cmd->IsSSBODynamic()) desc->AsBufferDescriptor()->AddDynamicOffset(cmd->GetDynamicOffset()); if (cmd->IsSSBO() && !desc->IsStorageBuffer()) { return Result( "Vulkan::AddBufferDescriptor BufferCommand for SSBO uses wrong " "descriptor " "set and binding"); } if (cmd->IsUniform() && !desc->IsUniformBuffer()) { return Result( "Vulkan::AddBufferDescriptor BufferCommand for UBO uses wrong " "descriptor set " "and binding"); } return {}; } Result Pipeline::AddSamplerDescriptor(const SamplerCommand* cmd) { if (cmd == nullptr) return Result("Pipeline::AddSamplerDescriptor SamplerCommand is nullptr"); Descriptor* desc; Result r = GetDescriptorSlot(cmd->GetDescriptorSet(), cmd->GetBinding(), &desc); if (!r.IsSuccess()) return r; auto& descriptors = descriptor_set_info_[cmd->GetDescriptorSet()].descriptors; if (desc == nullptr) { auto sampler_desc = MakeUnique( cmd->GetSampler(), DescriptorType::kSampler, device_, cmd->GetDescriptorSet(), cmd->GetBinding()); descriptors.push_back(std::move(sampler_desc)); } else { if (desc->GetDescriptorType() != DescriptorType::kSampler) { return Result( "Descriptors bound to the same binding needs to have matching " "descriptor types"); } desc->AsSamplerDescriptor()->AddAmberSampler(cmd->GetSampler()); } return {}; } Result Pipeline::SendDescriptorDataToDeviceIfNeeded() { { CommandBufferGuard guard(GetCommandBuffer()); if (!guard.IsRecording()) return guard.GetResult(); for (auto& info : descriptor_set_info_) { for (auto& desc : info.descriptors) { Result r = desc->CreateResourceIfNeeded(); if (!r.IsSuccess()) return r; } } // Note that if a buffer for a descriptor is host accessible and // does not need to record a command to copy data to device, it // directly writes data to the buffer. The direct write must be // done after resizing backed buffer i.e., copying data to the new // buffer from the old one. Thus, we must submit commands here to // guarantee this. Result r = guard.Submit(GetFenceTimeout()); if (!r.IsSuccess()) return r; } CommandBufferGuard guard(GetCommandBuffer()); if (!guard.IsRecording()) return guard.GetResult(); for (auto& info : descriptor_set_info_) { for (auto& desc : info.descriptors) { Result r = desc->RecordCopyDataToResourceIfNeeded(command_.get()); if (!r.IsSuccess()) return r; } } return guard.Submit(GetFenceTimeout()); } void Pipeline::BindVkDescriptorSets(const VkPipelineLayout& pipeline_layout) { for (size_t i = 0; i < descriptor_set_info_.size(); ++i) { if (descriptor_set_info_[i].empty) continue; // Sort descriptors by binding number to get correct order of dynamic // offsets. typedef std::pair> binding_offsets_pair; std::vector binding_offsets; for (const auto& desc : descriptor_set_info_[i].descriptors) { binding_offsets.push_back( {desc->GetBinding(), desc->GetDynamicOffsets()}); } std::sort(std::begin(binding_offsets), std::end(binding_offsets), [](const binding_offsets_pair& a, const binding_offsets_pair& b) { return a.first < b.first; }); // Add the sorted dynamic offsets. std::vector dynamic_offsets; for (const auto& binding_offset : binding_offsets) { for (auto offset : binding_offset.second) { dynamic_offsets.push_back(offset); } } device_->GetPtrs()->vkCmdBindDescriptorSets( command_->GetVkCommandBuffer(), IsGraphics() ? VK_PIPELINE_BIND_POINT_GRAPHICS : VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, static_cast(i), 1, &descriptor_set_info_[i].vk_desc_set, static_cast(dynamic_offsets.size()), dynamic_offsets.data()); } } Result Pipeline::ReadbackDescriptorsToHostDataQueue() { { CommandBufferGuard guard(GetCommandBuffer()); if (!guard.IsRecording()) return guard.GetResult(); for (auto& desc_set : descriptor_set_info_) { for (auto& desc : desc_set.descriptors) desc->RecordCopyDataToHost(command_.get()); } Result r = guard.Submit(GetFenceTimeout()); if (!r.IsSuccess()) return r; } for (auto& desc_set : descriptor_set_info_) { for (auto& desc : desc_set.descriptors) { Result r = desc->MoveResourceToBufferOutput(); if (!r.IsSuccess()) return r; } } return {}; } const char* Pipeline::GetEntryPointName(VkShaderStageFlagBits stage) const { auto it = entry_points_.find(stage); if (it != entry_points_.end()) return it->second.c_str(); return kDefaultEntryPointName; } } // namespace vulkan } // namespace amber