| // 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/pipeline.h" |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <limits> |
| #include <set> |
| |
| #include "src/make_unique.h" |
| #include "src/type_parser.h" |
| |
| namespace amber { |
| namespace { |
| |
| const char* kDefaultColorBufferFormat = "B8G8R8A8_UNORM"; |
| const char* kDefaultDepthBufferFormat = "D32_SFLOAT_S8_UINT"; |
| |
| // OpenCL coordinates mode is bit 0 |
| const uint32_t kOpenCLNormalizedCoordsBit = 1; |
| // OpenCL address mode bits are bits 1,2,3. |
| const uint32_t kOpenCLAddressModeBits = 0xe; |
| // OpenCL address mode bit values. |
| const uint32_t kOpenCLAddressModeNone = 0; |
| const uint32_t kOpenCLAddressModeClampToEdge = 2; |
| const uint32_t kOpenCLAddressModeClamp = 4; |
| const uint32_t kOpenCLAddressModeRepeat = 6; |
| const uint32_t kOpenCLAddressModeMirroredRepeat = 8; |
| // OpenCL filter mode bits. |
| const uint32_t kOpenCLFilterModeNearestBit = 0x10; |
| const uint32_t kOpenCLFilterModeLinearBit = 0x20; |
| |
| } // namespace |
| |
| const char* Pipeline::kGeneratedColorBuffer = "framebuffer"; |
| const char* Pipeline::kGeneratedDepthBuffer = "depth_buffer"; |
| const char* Pipeline::kGeneratedPushConstantBuffer = "push_constant_buffer"; |
| |
| Pipeline::ShaderInfo::ShaderInfo(Shader* shader, ShaderType type) |
| : shader_(shader), |
| shader_type_(type), |
| entry_point_("main"), |
| required_subgroup_size_setting_(RequiredSubgroupSizeSetting::kNotSet), |
| required_subgroup_size_(0), |
| varying_subgroup_size_(false), |
| require_full_subgroups_(false), |
| emit_debug_info_(false) {} |
| |
| Pipeline::ShaderInfo::ShaderInfo(const ShaderInfo&) = default; |
| |
| Pipeline::ShaderInfo::~ShaderInfo() = default; |
| |
| Pipeline::Pipeline(PipelineType type) : pipeline_type_(type) {} |
| |
| Pipeline::~Pipeline() = default; |
| |
| std::unique_ptr<Pipeline> Pipeline::Clone() const { |
| auto clone = MakeUnique<Pipeline>(pipeline_type_); |
| clone->shaders_ = shaders_; |
| clone->color_attachments_ = color_attachments_; |
| clone->vertex_buffers_ = vertex_buffers_; |
| clone->buffers_ = buffers_; |
| clone->depth_stencil_buffer_ = depth_stencil_buffer_; |
| clone->index_buffer_ = index_buffer_; |
| clone->fb_width_ = fb_width_; |
| clone->fb_height_ = fb_height_; |
| clone->set_arg_values_ = set_arg_values_; |
| clone->pipeline_data_ = pipeline_data_; |
| |
| if (!opencl_pod_buffers_.empty()) { |
| // Generate specific buffers for the clone. |
| clone->GenerateOpenCLPodBuffers(); |
| } |
| |
| return clone; |
| } |
| |
| Result Pipeline::AddShader(Shader* shader, ShaderType shader_type) { |
| if (!shader) |
| return Result("shader can not be null when attached to pipeline"); |
| |
| if (pipeline_type_ == PipelineType::kCompute && |
| shader_type != kShaderTypeCompute) { |
| return Result("only compute shaders allowed in a compute pipeline"); |
| } |
| if (pipeline_type_ == PipelineType::kGraphics && |
| shader_type == kShaderTypeCompute) { |
| return Result("can not add a compute shader to a graphics pipeline"); |
| } |
| |
| for (auto& info : shaders_) { |
| const auto* is = info.GetShader(); |
| if (is == shader) |
| return Result("can not add duplicate shader to pipeline"); |
| if (is->GetType() == shader_type) { |
| info.SetShader(shader); |
| return {}; |
| } |
| } |
| |
| shaders_.emplace_back(shader, shader_type); |
| return {}; |
| } |
| |
| Result Pipeline::SetShaderOptimizations(const Shader* shader, |
| const std::vector<std::string>& opts) { |
| if (!shader) |
| return Result("invalid shader specified for optimizations"); |
| |
| std::set<std::string> seen; |
| for (const auto& opt : opts) { |
| if (seen.count(opt) != 0) |
| return Result("duplicate optimization flag (" + opt + ") set on shader"); |
| |
| seen.insert(opt); |
| } |
| |
| for (auto& info : shaders_) { |
| const auto* is = info.GetShader(); |
| if (is == shader) { |
| info.SetShaderOptimizations(opts); |
| return {}; |
| } |
| } |
| |
| return Result("unknown shader specified for optimizations: " + |
| shader->GetName()); |
| } |
| |
| Result Pipeline::SetShaderCompileOptions(const Shader* shader, |
| const std::vector<std::string>& opts) { |
| if (!shader) |
| return Result("invalid shader specified for compile options"); |
| |
| for (auto& info : shaders_) { |
| const auto* is = info.GetShader(); |
| if (is == shader) { |
| info.SetCompileOptions(opts); |
| return {}; |
| } |
| } |
| |
| return Result("unknown shader specified for compile options: " + |
| shader->GetName()); |
| } |
| |
| Result Pipeline::SetShaderRequiredSubgroupSize( |
| const Shader* shader, |
| const ShaderInfo::RequiredSubgroupSizeSetting setting, |
| const uint32_t size) { |
| if (!shader) |
| return Result("invalid shader specified for required subgroup size"); |
| |
| for (auto& info : shaders_) { |
| const auto* is = info.GetShader(); |
| if (is == shader) { |
| info.SetRequiredSubgroupSizeSetting(setting, size); |
| return {}; |
| } |
| } |
| |
| return Result("unknown shader specified for required subgroup size: " + |
| shader->GetName()); |
| } |
| |
| Result Pipeline::SetShaderRequiredSubgroupSize(const Shader* shader, |
| const uint32_t subgroupSize) { |
| const bool isPow2 = |
| subgroupSize > 0 && (subgroupSize & (subgroupSize - 1)) == 0; |
| if (subgroupSize == 0 || subgroupSize > 128 || !isPow2) { |
| return Result("invalid required subgroup size " + |
| std::to_string(subgroupSize) + " specified for shader name " + |
| shader->GetName()); |
| } |
| const ShaderInfo::RequiredSubgroupSizeSetting setting = |
| ShaderInfo::RequiredSubgroupSizeSetting::kSetToSpecificSize; |
| return SetShaderRequiredSubgroupSize(shader, setting, subgroupSize); |
| } |
| |
| Result Pipeline::SetShaderRequiredSubgroupSizeToMinimum(const Shader* shader) { |
| const ShaderInfo::RequiredSubgroupSizeSetting subgroupSizeSetting = |
| ShaderInfo::RequiredSubgroupSizeSetting::kSetToMinimumSize; |
| return SetShaderRequiredSubgroupSize(shader, subgroupSizeSetting, 0); |
| } |
| |
| Result Pipeline::SetShaderRequiredSubgroupSizeToMaximum(const Shader* shader) { |
| const ShaderInfo::RequiredSubgroupSizeSetting subgroupSizeSetting = |
| ShaderInfo::RequiredSubgroupSizeSetting::kSetToMaximumSize; |
| return SetShaderRequiredSubgroupSize(shader, subgroupSizeSetting, 0); |
| } |
| |
| Result Pipeline::SetShaderVaryingSubgroupSize(const Shader* shader, |
| const bool isSet) { |
| if (!shader) |
| return Result("invalid shader specified for varying subgroup size"); |
| |
| for (auto& info : shaders_) { |
| const auto* is = info.GetShader(); |
| if (is == shader) { |
| info.SetVaryingSubgroupSize(isSet); |
| return {}; |
| } |
| } |
| |
| return Result("unknown shader specified for varying subgroup size: " + |
| shader->GetName()); |
| } |
| |
| Result Pipeline::SetShaderRequireFullSubgroups(const Shader* shader, |
| const bool isSet) { |
| if (!shader) |
| return Result("invalid shader specified for optimizations"); |
| |
| for (auto& info : shaders_) { |
| const auto* is = info.GetShader(); |
| if (is == shader) { |
| info.SetRequireFullSubgroups(isSet); |
| return {}; |
| } |
| } |
| |
| return Result("unknown shader specified for optimizations: " + |
| shader->GetName()); |
| } |
| |
| Result Pipeline::SetShaderEntryPoint(const Shader* shader, |
| const std::string& name) { |
| if (!shader) |
| return Result("invalid shader specified for entry point"); |
| if (name.empty()) |
| return Result("entry point should not be blank"); |
| |
| for (auto& info : shaders_) { |
| if (info.GetShader() == shader) { |
| if (info.GetEntryPoint() != "main") |
| return Result("multiple entry points given for the same shader"); |
| |
| info.SetEntryPoint(name); |
| return {}; |
| } |
| } |
| |
| return Result("unknown shader specified for entry point: " + |
| shader->GetName()); |
| } |
| |
| Result Pipeline::SetShaderType(const Shader* shader, ShaderType type) { |
| if (!shader) |
| return Result("invalid shader specified for shader type"); |
| |
| for (auto& info : shaders_) { |
| if (info.GetShader() == shader) { |
| info.SetShaderType(type); |
| return {}; |
| } |
| } |
| |
| return Result("unknown shader specified for shader type: " + |
| shader->GetName()); |
| } |
| |
| Result Pipeline::Validate() const { |
| for (const auto& attachment : color_attachments_) { |
| if (attachment.buffer->ElementCount() != |
| (fb_width_ << attachment.base_mip_level) * |
| (fb_height_ << attachment.base_mip_level)) { |
| return Result( |
| "shared framebuffer must have same size over all PIPELINES"); |
| } |
| } |
| |
| if (depth_stencil_buffer_.buffer && |
| depth_stencil_buffer_.buffer->ElementCount() != fb_width_ * fb_height_) { |
| return Result("shared depth buffer must have same size over all PIPELINES"); |
| } |
| |
| for (auto& buf : GetBuffers()) { |
| if (buf.buffer->GetFormat() == nullptr) { |
| return Result("buffer (" + std::to_string(buf.descriptor_set) + ":" + |
| std::to_string(buf.binding) + ") requires a format"); |
| } |
| } |
| |
| if (pipeline_type_ == PipelineType::kGraphics) |
| return ValidateGraphics(); |
| |
| return ValidateCompute(); |
| } |
| |
| Result Pipeline::ValidateGraphics() const { |
| if (color_attachments_.empty()) |
| return Result("PIPELINE missing color attachment"); |
| |
| bool found_vertex = false; |
| for (const auto& info : shaders_) { |
| const auto* s = info.GetShader(); |
| if (s->GetType() == kShaderTypeVertex) { |
| found_vertex = true; |
| break; |
| } |
| } |
| |
| if (!found_vertex) |
| return Result("graphics pipeline requires a vertex shader"); |
| |
| for (const auto& att : color_attachments_) { |
| auto width = att.buffer->GetWidth(); |
| auto height = att.buffer->GetHeight(); |
| for (uint32_t level = 1; level < att.buffer->GetMipLevels(); level++) { |
| width >>= 1; |
| if (width == 0) |
| return Result("color attachment with " + |
| std::to_string(att.buffer->GetMipLevels()) + |
| " mip levels would have zero width for level " + |
| std::to_string(level)); |
| height >>= 1; |
| if (height == 0) |
| return Result("color attachment with " + |
| std::to_string(att.buffer->GetMipLevels()) + |
| " mip levels would have zero height for level " + |
| std::to_string(level)); |
| } |
| } |
| |
| return {}; |
| } |
| |
| Result Pipeline::ValidateCompute() const { |
| if (shaders_.empty()) |
| return Result("compute pipeline requires a compute shader"); |
| |
| return {}; |
| } |
| |
| void Pipeline::UpdateFramebufferSizes() { |
| uint32_t size = fb_width_ * fb_height_; |
| if (size == 0) |
| return; |
| |
| for (auto& attachment : color_attachments_) { |
| auto mip0_width = fb_width_ << attachment.base_mip_level; |
| auto mip0_height = fb_height_ << attachment.base_mip_level; |
| attachment.buffer->SetWidth(mip0_width); |
| attachment.buffer->SetHeight(mip0_height); |
| attachment.buffer->SetElementCount(mip0_width * mip0_height); |
| } |
| |
| if (depth_stencil_buffer_.buffer) { |
| depth_stencil_buffer_.buffer->SetWidth(fb_width_); |
| depth_stencil_buffer_.buffer->SetHeight(fb_height_); |
| depth_stencil_buffer_.buffer->SetElementCount(size); |
| } |
| } |
| |
| Result Pipeline::AddColorAttachment(Buffer* buf, |
| uint32_t location, |
| uint32_t base_mip_level) { |
| for (const auto& attachment : color_attachments_) { |
| if (attachment.location == location) |
| return Result("can not bind two color buffers to the same LOCATION"); |
| if (attachment.buffer == buf) |
| return Result("color buffer may only be bound to a PIPELINE once"); |
| } |
| |
| color_attachments_.push_back(BufferInfo{buf}); |
| |
| auto& info = color_attachments_.back(); |
| info.location = location; |
| info.type = BufferType::kColor; |
| info.base_mip_level = base_mip_level; |
| auto mip0_width = fb_width_ << base_mip_level; |
| auto mip0_height = fb_height_ << base_mip_level; |
| buf->SetWidth(mip0_width); |
| buf->SetHeight(mip0_height); |
| buf->SetElementCount(mip0_width * mip0_height); |
| |
| return {}; |
| } |
| |
| Result Pipeline::AddResolveTarget(Buffer* buf) { |
| resolve_targets_.push_back(BufferInfo{buf}); |
| |
| auto& info = resolve_targets_.back(); |
| info.type = BufferType::kResolve; |
| buf->SetWidth(fb_width_); |
| buf->SetHeight(fb_height_); |
| buf->SetElementCount(fb_width_ * fb_height_); |
| |
| return {}; |
| } |
| |
| Result Pipeline::GetLocationForColorAttachment(Buffer* buf, |
| uint32_t* loc) const { |
| for (const auto& info : color_attachments_) { |
| if (info.buffer == buf) { |
| *loc = info.location; |
| return {}; |
| } |
| } |
| return Result("Unable to find requested buffer"); |
| } |
| |
| Result Pipeline::SetDepthStencilBuffer(Buffer* buf) { |
| if (depth_stencil_buffer_.buffer != nullptr) |
| return Result("can only bind one depth/stencil buffer in a PIPELINE"); |
| |
| depth_stencil_buffer_.buffer = buf; |
| depth_stencil_buffer_.type = BufferType::kDepthStencil; |
| |
| buf->SetWidth(fb_width_); |
| buf->SetHeight(fb_height_); |
| buf->SetElementCount(fb_width_ * fb_height_); |
| return {}; |
| } |
| |
| Result Pipeline::SetIndexBuffer(Buffer* buf) { |
| if (index_buffer_ != nullptr) |
| return Result("can only bind one INDEX_DATA buffer in a pipeline"); |
| |
| index_buffer_ = buf; |
| return {}; |
| } |
| |
| Result Pipeline::AddVertexBuffer(Buffer* buf, |
| uint32_t location, |
| InputRate rate, |
| Format* format, |
| uint32_t offset, |
| uint32_t stride) { |
| for (const auto& vtex : vertex_buffers_) { |
| if (vtex.location == location) |
| return Result("can not bind two vertex buffers to the same LOCATION"); |
| } |
| |
| vertex_buffers_.push_back(BufferInfo{buf}); |
| vertex_buffers_.back().location = location; |
| vertex_buffers_.back().type = BufferType::kVertex; |
| vertex_buffers_.back().input_rate = rate; |
| vertex_buffers_.back().format = format; |
| vertex_buffers_.back().offset = offset; |
| vertex_buffers_.back().stride = stride; |
| return {}; |
| } |
| |
| Result Pipeline::SetPushConstantBuffer(Buffer* buf) { |
| if (push_constant_buffer_.buffer != nullptr) |
| return Result("can only bind one push constant buffer in a PIPELINE"); |
| |
| push_constant_buffer_.buffer = buf; |
| push_constant_buffer_.type = BufferType::kPushConstant; |
| return {}; |
| } |
| |
| Result Pipeline::CreatePushConstantBuffer() { |
| if (push_constant_buffer_.buffer != nullptr) |
| return Result("can only bind one push constant buffer in a PIPELINE"); |
| |
| TypeParser parser; |
| auto type = parser.Parse("R8_UINT"); |
| auto fmt = MakeUnique<Format>(type.get()); |
| |
| std::unique_ptr<Buffer> buf = MakeUnique<Buffer>(); |
| buf->SetName(kGeneratedPushConstantBuffer); |
| buf->SetFormat(fmt.get()); |
| |
| push_constant_buffer_.buffer = buf.get(); |
| push_constant_buffer_.type = BufferType::kPushConstant; |
| |
| formats_.push_back(std::move(fmt)); |
| types_.push_back(std::move(type)); |
| opencl_push_constants_ = std::move(buf); |
| |
| return {}; |
| } |
| |
| std::unique_ptr<Buffer> Pipeline::GenerateDefaultColorAttachmentBuffer() { |
| TypeParser parser; |
| auto type = parser.Parse(kDefaultColorBufferFormat); |
| auto fmt = MakeUnique<Format>(type.get()); |
| |
| std::unique_ptr<Buffer> buf = MakeUnique<Buffer>(); |
| buf->SetName(kGeneratedColorBuffer); |
| buf->SetFormat(fmt.get()); |
| |
| formats_.push_back(std::move(fmt)); |
| types_.push_back(std::move(type)); |
| return buf; |
| } |
| |
| std::unique_ptr<Buffer> |
| Pipeline::GenerateDefaultDepthStencilAttachmentBuffer() { |
| TypeParser parser; |
| auto type = parser.Parse(kDefaultDepthBufferFormat); |
| auto fmt = MakeUnique<Format>(type.get()); |
| |
| std::unique_ptr<Buffer> buf = MakeUnique<Buffer>(); |
| buf->SetName(kGeneratedDepthBuffer); |
| buf->SetFormat(fmt.get()); |
| |
| formats_.push_back(std::move(fmt)); |
| types_.push_back(std::move(type)); |
| return buf; |
| } |
| |
| Buffer* Pipeline::GetBufferForBinding(uint32_t descriptor_set, |
| uint32_t binding) const { |
| for (const auto& info : buffers_) { |
| if (info.descriptor_set == descriptor_set && info.binding == binding) |
| return info.buffer; |
| } |
| return nullptr; |
| } |
| |
| void Pipeline::AddBuffer(Buffer* buf, |
| BufferType type, |
| uint32_t descriptor_set, |
| uint32_t binding, |
| uint32_t base_mip_level, |
| uint32_t dynamic_offset, |
| uint64_t descriptor_offset, |
| uint64_t descriptor_range) { |
| buffers_.push_back(BufferInfo{buf}); |
| |
| auto& info = buffers_.back(); |
| info.descriptor_set = descriptor_set; |
| info.binding = binding; |
| info.type = type; |
| info.base_mip_level = base_mip_level; |
| info.dynamic_offset = dynamic_offset; |
| info.sampler = buf->GetSampler(); |
| info.descriptor_offset = descriptor_offset; |
| info.descriptor_range = descriptor_range; |
| } |
| |
| void Pipeline::AddBuffer(Buffer* buf, |
| BufferType type, |
| const std::string& arg_name) { |
| // If this buffer binding already exists, overwrite with the new buffer. |
| for (auto& info : buffers_) { |
| if (info.arg_name == arg_name) { |
| info.buffer = buf; |
| return; |
| } |
| } |
| |
| buffers_.push_back(BufferInfo{buf}); |
| |
| auto& info = buffers_.back(); |
| info.type = type; |
| info.arg_name = arg_name; |
| info.descriptor_set = std::numeric_limits<uint32_t>::max(); |
| info.binding = std::numeric_limits<uint32_t>::max(); |
| info.arg_no = std::numeric_limits<uint32_t>::max(); |
| info.base_mip_level = 0; |
| info.dynamic_offset = 0; |
| } |
| |
| void Pipeline::AddBuffer(Buffer* buf, BufferType type, uint32_t arg_no) { |
| // If this buffer binding already exists, overwrite with the new buffer. |
| for (auto& info : buffers_) { |
| if (info.arg_no == arg_no) { |
| info.buffer = buf; |
| return; |
| } |
| } |
| |
| buffers_.push_back(BufferInfo{buf}); |
| |
| auto& info = buffers_.back(); |
| info.type = type; |
| info.arg_no = arg_no; |
| info.descriptor_set = std::numeric_limits<uint32_t>::max(); |
| info.binding = std::numeric_limits<uint32_t>::max(); |
| info.base_mip_level = 0; |
| info.dynamic_offset = 0; |
| } |
| |
| void Pipeline::ClearBuffers(uint32_t descriptor_set, uint32_t binding) { |
| buffers_.erase( |
| std::remove_if(buffers_.begin(), buffers_.end(), |
| [descriptor_set, binding](BufferInfo& info) -> bool { |
| return (info.descriptor_set == descriptor_set && |
| info.binding == binding); |
| }), |
| buffers_.end()); |
| } |
| |
| void Pipeline::AddSampler(Sampler* sampler, |
| uint32_t descriptor_set, |
| uint32_t binding) { |
| samplers_.push_back(SamplerInfo{sampler}); |
| |
| auto& info = samplers_.back(); |
| info.descriptor_set = descriptor_set; |
| info.binding = binding; |
| info.mask = std::numeric_limits<uint32_t>::max(); |
| } |
| |
| void Pipeline::AddSampler(Sampler* sampler, const std::string& arg_name) { |
| for (auto& info : samplers_) { |
| if (info.arg_name == arg_name) { |
| info.sampler = sampler; |
| return; |
| } |
| } |
| |
| samplers_.push_back(SamplerInfo{sampler}); |
| |
| auto& info = samplers_.back(); |
| info.arg_name = arg_name; |
| info.descriptor_set = std::numeric_limits<uint32_t>::max(); |
| info.binding = std::numeric_limits<uint32_t>::max(); |
| info.arg_no = std::numeric_limits<uint32_t>::max(); |
| info.mask = std::numeric_limits<uint32_t>::max(); |
| } |
| |
| void Pipeline::AddSampler(Sampler* sampler, uint32_t arg_no) { |
| for (auto& info : samplers_) { |
| if (info.arg_no == arg_no) { |
| info.sampler = sampler; |
| return; |
| } |
| } |
| |
| samplers_.push_back(SamplerInfo{sampler}); |
| |
| auto& info = samplers_.back(); |
| info.arg_no = arg_no; |
| info.descriptor_set = std::numeric_limits<uint32_t>::max(); |
| info.binding = std::numeric_limits<uint32_t>::max(); |
| info.mask = std::numeric_limits<uint32_t>::max(); |
| } |
| |
| void Pipeline::AddSampler(uint32_t mask, |
| uint32_t descriptor_set, |
| uint32_t binding) { |
| samplers_.push_back(SamplerInfo{nullptr}); |
| |
| auto& info = samplers_.back(); |
| info.arg_name = ""; |
| info.arg_no = std::numeric_limits<uint32_t>::max(); |
| info.mask = mask; |
| info.descriptor_set = descriptor_set; |
| info.binding = binding; |
| } |
| |
| void Pipeline::ClearSamplers(uint32_t descriptor_set, uint32_t binding) { |
| samplers_.erase( |
| std::remove_if(samplers_.begin(), samplers_.end(), |
| [descriptor_set, binding](SamplerInfo& info) -> bool { |
| return (info.descriptor_set == descriptor_set && |
| info.binding == binding); |
| }), |
| samplers_.end()); |
| } |
| |
| Result Pipeline::UpdateOpenCLBufferBindings() { |
| if (!IsCompute() || GetShaders().empty() || |
| GetShaders()[0].GetShader()->GetFormat() != kShaderFormatOpenCLC) { |
| return {}; |
| } |
| |
| const auto& shader_info = GetShaders()[0]; |
| const auto& descriptor_map = shader_info.GetDescriptorMap(); |
| if (descriptor_map.empty()) |
| return {}; |
| |
| const auto iter = descriptor_map.find(shader_info.GetEntryPoint()); |
| if (iter == descriptor_map.end()) |
| return {}; |
| |
| for (auto& info : samplers_) { |
| if (info.descriptor_set == std::numeric_limits<uint32_t>::max() && |
| info.binding == std::numeric_limits<uint32_t>::max()) { |
| for (const auto& entry : iter->second) { |
| if (entry.arg_name == info.arg_name || |
| entry.arg_ordinal == info.arg_no) { |
| if (entry.kind != |
| Pipeline::ShaderInfo::DescriptorMapEntry::Kind::SAMPLER) { |
| return Result("Sampler bound to non-sampler kernel arg"); |
| } |
| info.descriptor_set = entry.descriptor_set; |
| info.binding = entry.binding; |
| } |
| } |
| } |
| } |
| |
| for (auto& info : buffers_) { |
| if (info.descriptor_set == std::numeric_limits<uint32_t>::max() && |
| info.binding == std::numeric_limits<uint32_t>::max()) { |
| for (const auto& entry : iter->second) { |
| if (entry.arg_name == info.arg_name || |
| entry.arg_ordinal == info.arg_no) { |
| // Buffer storage class consistency checks. |
| if (info.type == BufferType::kUnknown) { |
| // Set the appropriate buffer type. |
| switch (entry.kind) { |
| case Pipeline::ShaderInfo::DescriptorMapEntry::Kind::UBO: |
| case Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD_UBO: |
| info.type = BufferType::kUniform; |
| break; |
| case Pipeline::ShaderInfo::DescriptorMapEntry::Kind::SSBO: |
| case Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD: |
| info.type = BufferType::kStorage; |
| break; |
| case Pipeline::ShaderInfo::DescriptorMapEntry::Kind::RO_IMAGE: |
| info.type = BufferType::kSampledImage; |
| break; |
| case Pipeline::ShaderInfo::DescriptorMapEntry::Kind::WO_IMAGE: |
| info.type = BufferType::kStorageImage; |
| break; |
| default: |
| return Result("Unhandled buffer type for OPENCL-C shader"); |
| } |
| } else if (info.type == BufferType::kUniform) { |
| if (entry.kind != |
| Pipeline::ShaderInfo::DescriptorMapEntry::Kind::UBO && |
| entry.kind != |
| Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD_UBO) { |
| return Result("Buffer " + info.buffer->GetName() + |
| " must be a uniform binding"); |
| } |
| } else if (info.type == BufferType::kStorage) { |
| if (entry.kind != |
| Pipeline::ShaderInfo::DescriptorMapEntry::Kind::SSBO && |
| entry.kind != |
| Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD) { |
| return Result("Buffer " + info.buffer->GetName() + |
| " must be a storage binding"); |
| } |
| } else if (info.type == BufferType::kSampledImage) { |
| if (entry.kind != |
| Pipeline::ShaderInfo::DescriptorMapEntry::Kind::RO_IMAGE) { |
| return Result("Buffer " + info.buffer->GetName() + |
| " must be a read-only image binding"); |
| } |
| } else if (info.type == BufferType::kStorageImage) { |
| if (entry.kind != |
| Pipeline::ShaderInfo::DescriptorMapEntry::Kind::WO_IMAGE) { |
| return Result("Buffer " + info.buffer->GetName() + |
| " must be a write-only image binding"); |
| } |
| } else { |
| return Result("Unhandled buffer type for OPENCL-C shader"); |
| } |
| info.descriptor_set = entry.descriptor_set; |
| info.binding = entry.binding; |
| } |
| } |
| } |
| } |
| |
| return {}; |
| } |
| |
| Result Pipeline::GenerateOpenCLPodBuffers() { |
| if (!IsCompute() || GetShaders().empty() || |
| GetShaders()[0].GetShader()->GetFormat() != kShaderFormatOpenCLC) { |
| return {}; |
| } |
| |
| const auto& shader_info = GetShaders()[0]; |
| const auto& descriptor_map = shader_info.GetDescriptorMap(); |
| if (descriptor_map.empty()) |
| return {}; |
| |
| const auto iter = descriptor_map.find(shader_info.GetEntryPoint()); |
| if (iter == descriptor_map.end()) |
| return {}; |
| |
| // For each SET command, do the following: |
| // 1. Find the descriptor map entry for that argument. |
| // 2. Find or create the buffer for the descriptor set and binding pair. |
| // 3. Write the data for the SET command at the right offset. |
| for (const auto& arg_info : SetArgValues()) { |
| uint32_t descriptor_set = std::numeric_limits<uint32_t>::max(); |
| uint32_t binding = std::numeric_limits<uint32_t>::max(); |
| uint32_t offset = 0; |
| uint32_t arg_size = 0; |
| bool uses_name = !arg_info.name.empty(); |
| Pipeline::ShaderInfo::DescriptorMapEntry::Kind kind = |
| Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD; |
| for (const auto& entry : iter->second) { |
| if (entry.kind != Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD && |
| entry.kind != |
| Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD_UBO && |
| entry.kind != Pipeline::ShaderInfo::DescriptorMapEntry::Kind:: |
| POD_PUSHCONSTANT) { |
| continue; |
| } |
| |
| // Found the right entry. |
| if ((uses_name && entry.arg_name == arg_info.name) || |
| entry.arg_ordinal == arg_info.ordinal) { |
| descriptor_set = entry.descriptor_set; |
| binding = entry.binding; |
| offset = entry.pod_offset; |
| arg_size = entry.pod_arg_size; |
| kind = entry.kind; |
| break; |
| } |
| } |
| |
| Buffer* buffer = nullptr; |
| if (kind == |
| Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD_PUSHCONSTANT) { |
| if (GetPushConstantBuffer().buffer == nullptr) { |
| auto r = CreatePushConstantBuffer(); |
| if (!r.IsSuccess()) |
| return r; |
| } |
| buffer = GetPushConstantBuffer().buffer; |
| } else { |
| if (descriptor_set == std::numeric_limits<uint32_t>::max() || |
| binding == std::numeric_limits<uint32_t>::max()) { |
| std::string message = |
| "could not find descriptor map entry for SET command: kernel " + |
| shader_info.GetEntryPoint(); |
| if (uses_name) { |
| message += ", name " + arg_info.name; |
| } else { |
| message += ", number " + std::to_string(arg_info.ordinal); |
| } |
| return Result(message); |
| } |
| |
| auto buf_iter = opencl_pod_buffer_map_.lower_bound( |
| std::make_pair(descriptor_set, binding)); |
| if (buf_iter == opencl_pod_buffer_map_.end() || |
| buf_iter->first.first != descriptor_set || |
| buf_iter->first.second != binding) { |
| // Ensure no buffer was previously bound for this descriptor set and |
| // binding pair. |
| for (const auto& buf_info : GetBuffers()) { |
| if (buf_info.descriptor_set == descriptor_set && |
| buf_info.binding == binding) { |
| return Result("previously bound buffer " + |
| buf_info.buffer->GetName() + |
| " to PoD args at descriptor set " + |
| std::to_string(descriptor_set) + " binding " + |
| std::to_string(binding)); |
| } |
| } |
| |
| // Add a new buffer for this descriptor set and binding. |
| opencl_pod_buffers_.push_back(MakeUnique<Buffer>()); |
| buffer = opencl_pod_buffers_.back().get(); |
| auto buffer_type = |
| kind == Pipeline::ShaderInfo::DescriptorMapEntry::Kind::POD |
| ? BufferType::kStorage |
| : BufferType::kUniform; |
| |
| // Use an 8-bit type because all the data in the descriptor map is |
| // byte-based and it simplifies the logic for sizing below. |
| TypeParser parser; |
| auto type = parser.Parse("R8_UINT"); |
| auto fmt = MakeUnique<Format>(type.get()); |
| buffer->SetFormat(fmt.get()); |
| formats_.push_back(std::move(fmt)); |
| types_.push_back(std::move(type)); |
| |
| buffer->SetName(GetName() + "_pod_buffer_" + |
| std::to_string(descriptor_set) + "_" + |
| std::to_string(binding)); |
| opencl_pod_buffer_map_.insert( |
| buf_iter, |
| std::make_pair(std::make_pair(descriptor_set, binding), buffer)); |
| AddBuffer(buffer, buffer_type, descriptor_set, binding, 0, 0, 0, ~0ULL); |
| } else { |
| buffer = buf_iter->second; |
| } |
| |
| // Resize if necessary. |
| if (buffer->ValueCount() < offset + arg_size) { |
| buffer->SetSizeInElements(offset + arg_size); |
| } |
| |
| // Check the data size. |
| if (arg_size != arg_info.fmt->SizeInBytes()) { |
| std::string message = "SET command uses incorrect data size: kernel " + |
| shader_info.GetEntryPoint(); |
| if (uses_name) { |
| message += ", name " + arg_info.name; |
| } else { |
| message += ", number " + std::to_string(arg_info.ordinal); |
| } |
| return Result(message); |
| } |
| } |
| |
| // Convert the argument value into bytes. Currently, only scalar arguments |
| // are supported. |
| const auto arg_byte_size = arg_info.fmt->SizeInBytes(); |
| std::vector<Value> data_bytes; |
| for (uint32_t i = 0; i < arg_byte_size; ++i) { |
| Value v; |
| if (arg_info.value.IsFloat()) { |
| if (arg_byte_size == sizeof(double)) { |
| union { |
| uint64_t u; |
| double d; |
| } u; |
| u.d = arg_info.value.AsDouble(); |
| v.SetIntValue((u.u >> (i * 8)) & 0xff); |
| } else { |
| union { |
| uint32_t u; |
| float f; |
| } u; |
| u.f = arg_info.value.AsFloat(); |
| v.SetIntValue((u.u >> (i * 8)) & 0xff); |
| } |
| } else { |
| v.SetIntValue((arg_info.value.AsUint64() >> (i * 8)) & 0xff); |
| } |
| data_bytes.push_back(v); |
| } |
| Result r = buffer->SetDataWithOffset(data_bytes, offset); |
| if (!r.IsSuccess()) |
| return r; |
| } |
| |
| return {}; |
| } |
| |
| Result Pipeline::GenerateOpenCLLiteralSamplers() { |
| for (auto& info : samplers_) { |
| if (info.sampler || info.mask == std::numeric_limits<uint32_t>::max()) |
| continue; |
| |
| auto literal_sampler = MakeUnique<Sampler>(); |
| literal_sampler->SetName("literal." + std::to_string(info.descriptor_set) + |
| "." + std::to_string(info.binding)); |
| |
| // The values for addressing modes, filtering modes and coordinate |
| // normalization are all defined in the OpenCL header. |
| |
| literal_sampler->SetNormalizedCoords(info.mask & |
| kOpenCLNormalizedCoordsBit); |
| |
| uint32_t addressing_bits = info.mask & kOpenCLAddressModeBits; |
| AddressMode addressing_mode = AddressMode::kUnknown; |
| if (addressing_bits == kOpenCLAddressModeNone || |
| addressing_bits == kOpenCLAddressModeClampToEdge) { |
| // CLK_ADDRESS_NONE |
| // CLK_ADDERSS_CLAMP_TO_EDGE |
| addressing_mode = AddressMode::kClampToEdge; |
| } else if (addressing_bits == kOpenCLAddressModeClamp) { |
| // CLK_ADDRESS_CLAMP |
| addressing_mode = AddressMode::kClampToBorder; |
| } else if (addressing_bits == kOpenCLAddressModeRepeat) { |
| // CLK_ADDRESS_REPEAT |
| addressing_mode = AddressMode::kRepeat; |
| } else if (addressing_bits == kOpenCLAddressModeMirroredRepeat) { |
| // CLK_ADDRESS_MIRRORED_REPEAT |
| addressing_mode = AddressMode::kMirroredRepeat; |
| } |
| literal_sampler->SetAddressModeU(addressing_mode); |
| literal_sampler->SetAddressModeV(addressing_mode); |
| // TODO(alan-baker): If this is used with an arrayed image then W should use |
| // kClampToEdge always, but this information is not currently available. |
| literal_sampler->SetAddressModeW(addressing_mode); |
| |
| // Next bit is filtering mode. |
| FilterType filtering_mode = FilterType::kUnknown; |
| if (info.mask & kOpenCLFilterModeNearestBit) { |
| filtering_mode = FilterType::kNearest; |
| } else if (info.mask & kOpenCLFilterModeLinearBit) { |
| filtering_mode = FilterType::kLinear; |
| } |
| literal_sampler->SetMagFilter(filtering_mode); |
| literal_sampler->SetMinFilter(filtering_mode); |
| |
| // TODO(alan-baker): OpenCL wants the border color to be based on image |
| // channel orders which aren't accessible. |
| |
| // clspv never generates multiple MIPMAP levels. |
| literal_sampler->SetMinLOD(0.0f); |
| literal_sampler->SetMaxLOD(0.0f); |
| |
| opencl_literal_samplers_.push_back(std::move(literal_sampler)); |
| info.sampler = opencl_literal_samplers_.back().get(); |
| } |
| |
| return {}; |
| } |
| |
| Result Pipeline::GenerateOpenCLPushConstants() { |
| if (!IsCompute() || GetShaders().empty() || |
| GetShaders()[0].GetShader()->GetFormat() != kShaderFormatOpenCLC) { |
| return {}; |
| } |
| |
| const auto& shader_info = GetShaders()[0]; |
| if (shader_info.GetPushConstants().empty()) |
| return {}; |
| |
| Result r = CreatePushConstantBuffer(); |
| if (!r.IsSuccess()) |
| return r; |
| |
| auto* buf = GetPushConstantBuffer().buffer; |
| assert(buf); |
| |
| // Determine size and contents of the push constant buffer. |
| for (const auto& pc : shader_info.GetPushConstants()) { |
| assert(pc.size % sizeof(uint32_t) == 0); |
| assert(pc.offset % sizeof(uint32_t) == 0); |
| |
| if (buf->GetSizeInBytes() < pc.offset + pc.size) |
| buf->SetSizeInBytes(pc.offset + pc.size); |
| |
| std::vector<uint32_t> bytes(pc.size / sizeof(uint32_t)); |
| uint32_t base = 0; |
| switch (pc.type) { |
| case Pipeline::ShaderInfo::PushConstant::PushConstantType::kDimensions: |
| // All compute kernel launches are 3D. |
| bytes[base] = 3; |
| break; |
| case Pipeline::ShaderInfo::PushConstant::PushConstantType::kGlobalOffset: |
| // Global offsets are not currently supported. |
| bytes[base] = 0; |
| bytes[base + 1] = 0; |
| bytes[base + 2] = 0; |
| break; |
| case Pipeline::ShaderInfo::PushConstant::PushConstantType::kRegionOffset: |
| // Region offsets are not currently supported. |
| bytes[base] = 0; |
| bytes[base + 1] = 0; |
| bytes[base + 2] = 0; |
| break; |
| } |
| memcpy(buf->ValuePtr()->data() + pc.offset, bytes.data(), |
| bytes.size() * sizeof(uint32_t)); |
| } |
| |
| return {}; |
| } |
| |
| } // namespace amber |