// 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 #include #include #include #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::Clone() const { auto clone = MakeUnique(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& opts) { if (!shader) return Result("invalid shader specified for optimizations"); std::set 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& 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::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) { for (const auto& vtex : vertex_buffers_) { if (vtex.location == location) return Result("can not bind two vertex buffers to the same LOCATION"); if (vtex.buffer == buf) return Result("vertex buffer may only be bound to a PIPELINE once"); } vertex_buffers_.push_back(BufferInfo{buf}); vertex_buffers_.back().location = location; vertex_buffers_.back().type = BufferType::kVertex; vertex_buffers_.back().input_rate = rate; 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(type.get()); std::unique_ptr buf = MakeUnique(); 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 Pipeline::GenerateDefaultColorAttachmentBuffer() { TypeParser parser; auto type = parser.Parse(kDefaultColorBufferFormat); auto fmt = MakeUnique(type.get()); std::unique_ptr buf = MakeUnique(); buf->SetName(kGeneratedColorBuffer); buf->SetFormat(fmt.get()); formats_.push_back(std::move(fmt)); types_.push_back(std::move(type)); return buf; } std::unique_ptr Pipeline::GenerateDefaultDepthStencilAttachmentBuffer() { TypeParser parser; auto type = parser.Parse(kDefaultDepthBufferFormat); auto fmt = MakeUnique(type.get()); std::unique_ptr buf = MakeUnique(); 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) { 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; } 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::max(); info.binding = std::numeric_limits::max(); info.arg_no = std::numeric_limits::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::max(); info.binding = std::numeric_limits::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::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::max(); info.binding = std::numeric_limits::max(); info.arg_no = std::numeric_limits::max(); info.mask = std::numeric_limits::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::max(); info.binding = std::numeric_limits::max(); info.mask = std::numeric_limits::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::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::max() && info.binding == std::numeric_limits::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::max() && info.binding == std::numeric_limits::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::max(); uint32_t binding = std::numeric_limits::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::max() || binding == std::numeric_limits::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 = 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(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); } 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 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::max()) continue; auto literal_sampler = MakeUnique(); 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 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; } memcpy(buf->ValuePtr()->data() + pc.offset, bytes.data(), bytes.size() * sizeof(uint32_t)); } return {}; } } // namespace amber