| // Copyright 2018 The clvk 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. |
| |
| #pragma once |
| |
| #include <algorithm> |
| #include <limits> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include "spirv/unified1/NonSemanticClspvReflection.h" |
| |
| #include "memory.hpp" |
| #include "objects.hpp" |
| #include "program.hpp" |
| |
| struct cvk_kernel_argument_values; |
| |
| struct cvk_kernel : public _cl_kernel, api_object<object_magic::kernel> { |
| |
| cvk_kernel(cvk_program* program, const char* name) |
| : api_object(program->context()), m_program(program), |
| m_entry_point(nullptr), m_name(name), m_sampler_metadata(nullptr), |
| m_image_metadata(nullptr) {} |
| |
| CHECK_RETURN cl_int init(); |
| std::unique_ptr<cvk_kernel> clone(cl_int* errcode_ret) const; |
| |
| virtual ~cvk_kernel() { m_argument_values.reset(); } |
| |
| std::shared_ptr<cvk_kernel_argument_values> argument_values() const { |
| return m_argument_values; |
| } |
| |
| const kernel_sampler_metadata_map* get_sampler_metadata() const { |
| return m_sampler_metadata; |
| } |
| |
| const kernel_image_metadata_map* get_image_metadata() const { |
| return m_image_metadata; |
| } |
| |
| void set_sampler_metadata(cl_uint index, const void* sampler); |
| |
| void set_image_metadata(cl_uint index, const void* image); |
| |
| CHECK_RETURN cl_int set_arg(cl_uint index, size_t size, const void* value); |
| CHECK_RETURN VkPipeline |
| create_pipeline(const cvk_spec_constant_map& spec_constants); |
| |
| bool has_pod_arguments() const { |
| return m_entry_point->has_pod_arguments(); |
| } |
| |
| bool has_pod_buffer_arguments() const { |
| return m_entry_point->has_pod_buffer_arguments(); |
| } |
| |
| const std::string& name() const { return m_name; } |
| const std::string& attributes() const { |
| return m_program->kernel_attributes(m_name); |
| } |
| uint32_t num_args() const { return m_args.size(); } |
| uint32_t num_set_layouts() const { |
| return m_entry_point->num_set_layouts(); |
| } |
| VkPipelineLayout pipeline_layout() const { |
| return m_entry_point->pipeline_layout(); |
| } |
| cvk_program* program() const { return m_program; } |
| |
| const std::vector<kernel_argument>& arguments() const { return m_args; } |
| |
| kernel_argument_kind arg_kind(int index) const { |
| return m_args[index].kind; |
| } |
| |
| cl_ulong local_mem_size() const; |
| |
| size_t max_work_group_size(const cvk_device* device) const { |
| return device->max_work_group_size(); |
| } |
| |
| size_t max_sub_group_size_for_ndrange(const cvk_device* device) const { |
| return device->sub_group_size(); |
| } |
| |
| size_t |
| sub_group_count_for_ndrange(const cvk_device* device, |
| const std::array<uint32_t, 3>& lws) const { |
| uint32_t work_items_per_work_group = lws[0] * lws[1] * lws[2]; |
| return ceil_div(work_items_per_work_group, device->sub_group_size()); |
| } |
| std::array<size_t, 3> |
| local_size_for_sub_group_count(const cvk_device* device, |
| size_t num_sub_groups) const { |
| std::array<size_t, 3> ret = {1, 1, 1}; |
| size_t wgs = num_sub_groups * device->sub_group_size(); |
| if (wgs > max_work_group_size(device)) { |
| ret = {0, 0, 0}; |
| } else { |
| ret[0] = wgs; |
| } |
| return ret; |
| } |
| |
| size_t max_num_sub_groups(const cvk_device* device) const { |
| return device->max_num_sub_groups(); |
| } |
| |
| const std::array<uint32_t, 3>& required_work_group_size() const { |
| return m_program->required_work_group_size(m_name); |
| } |
| |
| bool args_valid() const; |
| |
| bool has_extended_arg_info(cl_uint arg_index) const { |
| return m_args.at(arg_index).info.extended_valid; |
| } |
| |
| const std::string arg_name(cl_uint arg_index) const { |
| return m_args.at(arg_index).info.name; |
| } |
| |
| const std::string arg_type_name(cl_uint arg_index) const { |
| return m_args.at(arg_index).info.type_name; |
| } |
| |
| cl_kernel_arg_address_qualifier |
| arg_address_qualifier(cl_uint arg_index) const { |
| return m_args.at(arg_index).info.address_qualifier; |
| } |
| |
| cl_kernel_arg_access_qualifier |
| arg_access_qualifier(cl_uint arg_index) const { |
| return m_args.at(arg_index).info.access_qualifier; |
| } |
| |
| cl_kernel_arg_type_qualifier arg_type_qualifier(cl_uint arg_index) const { |
| return m_args.at(arg_index).info.type_qualifier; |
| } |
| |
| bool uses_printf() const { return m_entry_point->uses_printf(); } |
| |
| bool requires_serialized_execution() const { return uses_printf(); } |
| |
| private: |
| friend cvk_kernel_argument_values; |
| |
| std::mutex m_lock; |
| cvk_program_holder m_program; |
| std::shared_ptr<cvk_entry_point> m_entry_point; |
| std::string m_name; |
| std::vector<kernel_argument> m_args; |
| std::shared_ptr<cvk_kernel_argument_values> m_argument_values; |
| const kernel_sampler_metadata_map* m_sampler_metadata; |
| const kernel_image_metadata_map* m_image_metadata; |
| }; |
| |
| static inline cvk_kernel* icd_downcast(cl_kernel kernel) { |
| return static_cast<cvk_kernel*>(kernel); |
| } |
| |
| using cvk_kernel_holder = refcounted_holder<cvk_kernel>; |
| |
| struct cvk_kernel_argument_values { |
| |
| cvk_kernel_argument_values(std::shared_ptr<cvk_entry_point> entry_point) |
| : m_entry_point(entry_point), m_is_enqueued(false), |
| m_args(m_entry_point->args()), m_pod_arg(nullptr), |
| m_kernel_resources(m_entry_point->num_resource_slots()), |
| m_local_args_size(m_entry_point->args().size(), 0), |
| m_args_set(m_args.size(), false), m_descriptor_sets{VK_NULL_HANDLE}, |
| m_descriptor_sets_refcount(0) {} |
| |
| cvk_kernel_argument_values(const cvk_kernel_argument_values& other) |
| : m_entry_point(other.m_entry_point), m_is_enqueued(false), |
| m_args(m_entry_point->args()), m_pod_arg(nullptr), |
| m_kernel_resources(other.m_kernel_resources), |
| m_local_args_size(other.m_local_args_size), |
| m_specialization_constants(other.m_specialization_constants), |
| m_args_set(other.m_args_set), m_descriptor_sets{VK_NULL_HANDLE}, |
| m_descriptor_sets_refcount(0) {} |
| |
| ~cvk_kernel_argument_values() { |
| for (auto ds : m_descriptor_sets) { |
| if (ds != VK_NULL_HANDLE) { |
| m_entry_point->free_descriptor_set(ds); |
| } |
| } |
| } |
| |
| static std::shared_ptr<cvk_kernel_argument_values> |
| create(std::shared_ptr<cvk_entry_point> entry_point) { |
| auto val = std::make_shared<cvk_kernel_argument_values>(entry_point); |
| |
| if (!val->init()) { |
| return nullptr; |
| } |
| |
| return val; |
| } |
| |
| static std::shared_ptr<cvk_kernel_argument_values> |
| create(const cvk_kernel_argument_values& other) { |
| auto val = std::make_shared<cvk_kernel_argument_values>(other); |
| |
| if (!val->init()) { |
| return nullptr; |
| } |
| |
| if (!val->init_copy(other)) { |
| return nullptr; |
| } |
| |
| return val; |
| } |
| |
| bool init() { |
| // Init POD arguments |
| if (m_entry_point->has_pod_arguments()) { |
| // Find out POD binding |
| for (auto& arg : m_args) { |
| if (arg.is_pod()) { |
| m_pod_arg = &arg; |
| break; |
| } |
| } |
| |
| if (m_pod_arg == nullptr) { |
| return CL_INVALID_PROGRAM; |
| } |
| } |
| |
| if (m_entry_point->has_pod_arguments() || |
| m_entry_point->has_image_metadata() || |
| m_entry_point->has_sampler_metadata()) { |
| // TODO(#101): host out-of-memory errors are currently unhandled. |
| auto buffer = std::make_unique<std::vector<uint8_t>>( |
| m_entry_point->pod_buffer_size()); |
| m_pod_data = std::move(buffer); |
| } |
| |
| return true; |
| } |
| |
| bool init_copy(const cvk_kernel_argument_values& other) { |
| if (m_entry_point->has_pod_arguments()) { |
| memcpy(&pod_data()[0], &other.pod_data()[0], pod_data().size()); |
| return true; |
| } else { |
| return true; |
| } |
| } |
| |
| void set_pod_data(uint32_t offset, size_t size, const void* value) { |
| memcpy(&pod_data()[offset], value, size); |
| } |
| |
| cl_int set_arg(const kernel_argument& arg, size_t size, const void* value) { |
| |
| if (arg.is_pod_pointer()) { |
| auto mem = *reinterpret_cast<const cl_mem*>(value); |
| if (mem == NULL) { |
| // OpenCL permits cl_mem to be NULL |
| uint64_t null = 0; |
| set_pod_data(arg.offset, arg.size, &null); |
| } else { |
| auto mem_downcast = icd_downcast(mem); |
| if (!mem_downcast->is_valid()) { |
| return CL_INVALID_MEM_OBJECT; |
| } |
| auto buff = reinterpret_cast<const cvk_buffer*>(mem_downcast); |
| auto dev_addr = buff->device_address(); |
| set_pod_data(arg.offset, arg.size, &dev_addr); |
| } |
| } else if (arg.is_pod()) { |
| // If the argument is a vec3, OpenCL requires to call clSetKernelArg |
| // with a size of 4 times the element size. But clspv arg size is |
| // only 3 times the element size. When size and arg.size do not |
| // match, make sure that we are not in this unusual case. |
| if (size != arg.size && |
| !(arg.is_vec3() && (size == arg.size * 4 / 3))) { |
| return CL_INVALID_ARG_SIZE; |
| } |
| |
| set_pod_data(arg.offset, arg.size, value); |
| } else if (arg.kind == kernel_argument_kind::local) { |
| CVK_ASSERT(value == nullptr); |
| m_local_args_size[arg.pos] = size; |
| CVK_ASSERT(size % arg.local_elem_size == 0); |
| m_specialization_constants[arg.local_spec_id] = |
| size / arg.local_elem_size; |
| } else if (!arg.is_unused()) { |
| // We only expect cl_mem or cl_sampler here |
| if (size != sizeof(void*)) { |
| return CL_INVALID_ARG_SIZE; |
| } |
| if (arg.kind == kernel_argument_kind::sampler) { |
| auto apisampler = *reinterpret_cast<const cl_sampler*>(value); |
| if (apisampler == nullptr) { |
| return CL_INVALID_SAMPLER; |
| } |
| auto sampler = icd_downcast(apisampler); |
| if (!sampler->is_valid()) { |
| return CL_INVALID_SAMPLER; |
| } |
| |
| m_kernel_resources[arg.binding] = sampler; |
| } else { |
| auto apimem = *reinterpret_cast<const cl_mem*>(value); |
| if (apimem == nullptr) { |
| return CL_INVALID_MEM_OBJECT; |
| } |
| auto mem = icd_downcast(apimem); |
| if ((arg.info.access_qualifier == |
| CL_KERNEL_ARG_ACCESS_READ_ONLY && |
| mem->has_flags(CL_MEM_WRITE_ONLY)) || |
| (arg.info.access_qualifier == |
| CL_KERNEL_ARG_ACCESS_WRITE_ONLY && |
| mem->has_flags(CL_MEM_READ_ONLY)) || |
| (arg.info.access_qualifier == |
| CL_KERNEL_ARG_ACCESS_READ_WRITE && |
| !mem->has_flags(CL_MEM_READ_WRITE))) { |
| return CL_INVALID_ARG_VALUE; |
| } |
| if (!mem->is_valid()) { |
| return CL_INVALID_MEM_OBJECT; |
| } |
| m_kernel_resources[arg.binding] = mem; |
| } |
| } |
| |
| m_args_set[arg.pos] = true; |
| return CL_SUCCESS; |
| } |
| |
| refcounted* get_arg_value(const kernel_argument& arg) { |
| return m_kernel_resources[arg.binding]; |
| } |
| |
| bool is_enqueued() const { return m_is_enqueued; } |
| |
| const std::vector<uint8_t>& pod_data() const { return *m_pod_data; } |
| std::vector<uint8_t>& pod_data() { return *m_pod_data; } |
| |
| size_t local_arg_size(int pos) const { return m_local_args_size[pos]; } |
| |
| const std::unordered_map<uint32_t, uint32_t>& |
| specialization_constants() const { |
| return m_specialization_constants; |
| } |
| |
| CHECK_RETURN bool setup_descriptor_sets(); |
| |
| VkDescriptorSet* descriptor_sets() { return m_descriptor_sets.data(); } |
| |
| // Take ownership of resources and retain them. |
| void retain_resources() { |
| for (auto& resource : m_kernel_resources) { |
| if (resource) |
| resource->retain(); |
| } |
| std::lock_guard<std::mutex> lock(m_lock); |
| m_descriptor_sets_refcount++; |
| } |
| |
| // Release all resources owned resources. |
| void release_resources() { |
| for (auto& resource : m_kernel_resources) { |
| if (resource) |
| resource->release(); |
| } |
| std::lock_guard<std::mutex> lock(m_lock); |
| if (--m_descriptor_sets_refcount == 0) { |
| m_is_enqueued = false; |
| for (auto& ds : m_descriptor_sets) { |
| if (ds != VK_NULL_HANDLE) { |
| m_entry_point->free_descriptor_set(ds); |
| ds = VK_NULL_HANDLE; |
| } |
| } |
| } |
| } |
| |
| const std::vector<cvk_mem*> memory_objects() const { |
| std::vector<cvk_mem*> mems; |
| mems.reserve(m_args.size()); |
| for (auto& arg : m_args) { |
| if (arg.is_mem_object_backed()) { |
| auto mem = |
| static_cast<cvk_mem*>(m_kernel_resources[arg.binding]); |
| mems.push_back(mem); |
| } |
| } |
| return mems; |
| } |
| |
| bool args_valid() const { |
| return std::all_of(m_args_set.cbegin(), m_args_set.cend(), |
| [](bool b) { return b; }); |
| } |
| |
| private: |
| bool create_pod_buffer() { |
| CVK_ASSERT(m_pod_data->size() >= m_entry_point->pod_buffer_size()); |
| |
| // Create POD buffer and copy data to it |
| m_pod_buffer = m_entry_point->allocate_pod_buffer(); |
| if (m_pod_buffer == nullptr) { |
| return false; |
| } |
| return m_pod_buffer->copy_from(m_pod_data->data(), 0, |
| m_entry_point->pod_buffer_size()); |
| } |
| |
| std::mutex m_lock; |
| std::shared_ptr<cvk_entry_point> m_entry_point; |
| std::unique_ptr<std::vector<uint8_t>> m_pod_data; |
| bool m_is_enqueued; |
| const std::vector<kernel_argument>& m_args; |
| const kernel_argument* m_pod_arg; |
| std::vector<refcounted*> m_kernel_resources; |
| std::vector<size_t> m_local_args_size; |
| std::unordered_map<uint32_t, uint32_t> m_specialization_constants; |
| std::vector<bool> m_args_set; |
| |
| std::unique_ptr<cvk_buffer> m_pod_buffer; |
| std::array<VkDescriptorSet, spir_binary::MAX_DESCRIPTOR_SETS> |
| m_descriptor_sets; |
| uint32_t m_descriptor_sets_refcount; |
| }; |