| // 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. |
| |
| #include <array> |
| #include <fstream> |
| #include <functional> |
| #include <iterator> |
| #include <sstream> |
| |
| #include "config.hpp" |
| #include "device.hpp" |
| #include "init.hpp" |
| #include "kernel.hpp" |
| #include "log.hpp" |
| #include "memory.hpp" |
| |
| constexpr VkMemoryPropertyFlags cvk_device::buffer_supported_memory_types[]; |
| constexpr VkMemoryPropertyFlags cvk_device::image_supported_memory_types[]; |
| |
| cvk_device* cvk_device::create(cvk_platform* platform, VkInstance instance, |
| VkPhysicalDevice pdev) { |
| cvk_device* device = new cvk_device(platform, pdev); |
| |
| if (!device->init(instance)) { |
| delete device; |
| return nullptr; |
| } |
| |
| return device; |
| } |
| |
| void cvk_device::init_vulkan_properties(VkInstance instance) { |
| |
| cvk_info("Getting Vulkan device properties"); |
| |
| m_device_id_properties.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES_KHR; |
| m_driver_properties.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR; |
| m_pci_bus_info_properties.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PCI_BUS_INFO_PROPERTIES_EXT; |
| m_subgroup_properties.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES; |
| m_subgroup_size_control_properties.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES; |
| m_float_controls_properties.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT_CONTROLS_PROPERTIES; |
| m_integer_dot_product_properties.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_DOT_PRODUCT_PROPERTIES; |
| |
| //--- Get maxMemoryAllocationSize for figuring out the max single buffer |
| // allocation size and default init when the extension is not supported |
| m_maintenance3_properties.maxMemoryAllocationSize = |
| std::numeric_limits<VkDeviceSize>::max(); |
| m_maintenance3_properties.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES; |
| //--- |
| |
| #define VER_EXT_PROP(ver, ext, prop) \ |
| {ver, ext, reinterpret_cast<VkBaseOutStructure*>(&prop)} |
| std::vector<std::tuple<uint32_t, const char*, VkBaseOutStructure*>> |
| coreversion_extension_properties = { |
| VER_EXT_PROP(VK_MAKE_VERSION(1, 1, 0), nullptr, |
| m_device_id_properties), |
| VER_EXT_PROP(VK_MAKE_VERSION(1, 2, 0), |
| VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, |
| m_driver_properties), |
| VER_EXT_PROP(0, VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, |
| m_pci_bus_info_properties), |
| VER_EXT_PROP(VK_MAKE_VERSION(1, 1, 0), nullptr, |
| m_subgroup_properties), |
| VER_EXT_PROP(VK_MAKE_VERSION(1, 3, 0), nullptr, |
| m_subgroup_size_control_properties), |
| VER_EXT_PROP(VK_MAKE_VERSION(1, 1, 0), nullptr, |
| m_maintenance3_properties), |
| VER_EXT_PROP(VK_MAKE_VERSION(1, 2, 0), nullptr, |
| m_float_controls_properties), |
| VER_EXT_PROP(VK_MAKE_VERSION(1, 3, 0), nullptr, |
| m_integer_dot_product_properties), |
| }; |
| #undef VER_EXT_PROP |
| |
| VkBaseOutStructure* pNext = nullptr; |
| for (auto& ver_ext_prop : coreversion_extension_properties) { |
| auto corever = std::get<0>(ver_ext_prop); |
| auto ext = std::get<1>(ver_ext_prop); |
| auto prop = std::get<2>(ver_ext_prop); |
| if ((corever != 0) && (m_properties.apiVersion >= corever)) { |
| prop->pNext = pNext; |
| pNext = prop; |
| } else if ((ext != nullptr) && is_vulkan_extension_enabled(ext)) { |
| prop->pNext = pNext; |
| pNext = prop; |
| } |
| } |
| |
| VkPhysicalDeviceProperties2KHR properties; |
| properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR; |
| properties.pNext = pNext; |
| |
| auto func = GET_INSTANCE_PROC(instance, vkGetPhysicalDeviceProperties2KHR); |
| if (!func) { |
| cvk_fatal( |
| "Failed to get pointer to vkGetPhysicalDeviceProperties2KHR()"); |
| } |
| func(m_pdev, &properties); |
| } |
| |
| void cvk_device::init_clvk_runtime_behaviors() { |
| m_clvk_properties = create_cvk_device_properties( |
| m_properties.deviceName, m_properties.vendorID, m_properties.deviceID, |
| m_properties.driverVersion, m_driver_properties.driverID); |
| #define SET_DEVICE_PROPERTY(option, print) \ |
| do { \ |
| if (config.option.set) { \ |
| m_##option = config.option; \ |
| } else { \ |
| m_##option = m_clvk_properties->get_##option(); \ |
| } \ |
| print(option); \ |
| } while (0) |
| |
| #define PRINT_U(option) cvk_info_fn(#option ": %u", m_##option); |
| #define SET_DEVICE_PROPERTY_U(option) SET_DEVICE_PROPERTY(option, PRINT_U) |
| |
| #define PRINT_S(option) cvk_info_fn(#option ": %s", m_##option.c_str()); |
| #define SET_DEVICE_PROPERTY_S(option) SET_DEVICE_PROPERTY(option, PRINT_S) |
| |
| SET_DEVICE_PROPERTY_U(max_cmd_batch_size); |
| SET_DEVICE_PROPERTY_U(max_first_cmd_batch_size); |
| SET_DEVICE_PROPERTY_U(max_cmd_group_size); |
| SET_DEVICE_PROPERTY_U(max_first_cmd_group_size); |
| |
| SET_DEVICE_PROPERTY_U(physical_addressing); |
| SET_DEVICE_PROPERTY_S(spirv_arch); |
| |
| SET_DEVICE_PROPERTY_U(preferred_subgroup_size); |
| |
| #undef PRINT_U |
| #undef PRINT_S |
| #undef SET_DEVICE_PROPERTY_U |
| #undef SET_DEVICE_PROPERTY_S |
| #undef SET_DEVICE_PROPERTY |
| } |
| |
| void cvk_device::init_driver_behaviors() { |
| |
| cvk_info("Initialising driver behaviors"); |
| |
| // Disable all driver-specific behaviors by default |
| m_driver_behaviors = 0; |
| |
| if (is_vulkan_extension_enabled(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME) || |
| m_properties.apiVersion >= VK_MAKE_VERSION(1, 2, 0)) { |
| |
| // Log basic information about the target Vulkan device |
| cvk_info(" driverName = %s", m_driver_properties.driverName); |
| cvk_info(" driverInfo = %s", m_driver_properties.driverInfo); |
| cvk_info(" conformanceVersion = %d.%d.%d.%d", |
| m_driver_properties.conformanceVersion.major, |
| m_driver_properties.conformanceVersion.minor, |
| m_driver_properties.conformanceVersion.subminor, |
| m_driver_properties.conformanceVersion.patch); |
| |
| // Select behaviors based on the target Vulkan device and driver version |
| if (m_driver_properties.driverID == VK_DRIVER_ID_ARM_PROPRIETARY_KHR) { |
| // Workaround for resource management bug on Mali GPUs. |
| // TODO: Make this conditional on the driver version when this is |
| // fixed in the driver. |
| m_driver_behaviors |= use_reset_command_buffer_bit; |
| } |
| } else { |
| cvk_warn("The VK_KHR_driver_properties extension is not supported."); |
| cvk_warn("Using default Vulkan driver behaviors."); |
| } |
| |
| // List driver behaviors |
| cvk_info("Driver behaviors:"); |
| #define PRINT_BEHAVIOR(name) \ |
| cvk_info(" %s = %s", #name, (m_driver_behaviors & name) ? "true" : "false") |
| PRINT_BEHAVIOR(use_reset_command_buffer_bit); |
| #undef PRINT_BEHAVIOR |
| } |
| |
| static bool queue_flags_contains_compute(VkQueueFlags flags) { |
| return flags & VK_QUEUE_COMPUTE_BIT; |
| } |
| static bool queue_flags_contains_compute_not_graphics(VkQueueFlags flags) { |
| if (flags & VK_QUEUE_GRAPHICS_BIT) { |
| return false; |
| } |
| return queue_flags_contains_compute(flags); |
| } |
| |
| bool cvk_device::init_queues(uint32_t* num_queues, uint32_t* queue_family) { |
| // Get number of queue families |
| uint32_t num_families; |
| vkGetPhysicalDeviceQueueFamilyProperties(m_pdev, &num_families, nullptr); |
| |
| cvk_info( |
| "Physical device (%s) has %u queue families:", |
| vulkan_physical_device_type_string(m_properties.deviceType).c_str(), |
| num_families); |
| |
| // Get their properties |
| std::vector<VkQueueFamilyProperties> families(num_families); |
| vkGetPhysicalDeviceQueueFamilyProperties(m_pdev, &num_families, |
| families.data()); |
| |
| std::array<std::function<bool(VkQueueFlags)>, 2> queue_flags_tests = { |
| queue_flags_contains_compute_not_graphics, |
| queue_flags_contains_compute}; |
| |
| // Look for suitable queues |
| bool found_queues = false; |
| *num_queues = 0; |
| for (auto queue_flags_test : queue_flags_tests) { |
| for (uint32_t i = 0; i < num_families; i++) { |
| |
| cvk_info(" queue family %u: %2u queues | %s", i, |
| families[i].queueCount, |
| vulkan_queue_flags_string(families[i].queueFlags).c_str()); |
| |
| if (!found_queues && queue_flags_test(families[i].queueFlags)) { |
| *queue_family = i; |
| *num_queues = families[i].queueCount; |
| found_queues = true; |
| } |
| } |
| } |
| |
| if (!found_queues) { |
| cvk_error("Could not find a suitable queue family for this device"); |
| return false; |
| } |
| cvk_info( |
| " selecting queue %u: %2u queues | %s", *queue_family, *num_queues, |
| vulkan_queue_flags_string(families[*queue_family].queueFlags).c_str()); |
| |
| // Initialise the queue allocator |
| m_vulkan_queue_alloc_index = 0; |
| |
| return true; |
| } |
| |
| bool cvk_device::init_extensions() { |
| uint32_t numext; |
| VkResult res = |
| vkEnumerateDeviceExtensionProperties(m_pdev, nullptr, &numext, nullptr); |
| CVK_VK_CHECK_ERROR_RET( |
| res, false, "Failed to get the number of device extension properties"); |
| |
| cvk_info("%u extensions are supported", numext); |
| |
| std::vector<VkExtensionProperties> extensions(numext); |
| res = vkEnumerateDeviceExtensionProperties(m_pdev, nullptr, &numext, |
| extensions.data()); |
| CVK_VK_CHECK_ERROR_RET(res, false, |
| "Could not enumerate device extension properties"); |
| |
| std::vector<const char*> desired_extensions{ |
| VK_KHR_8BIT_STORAGE_EXTENSION_NAME, |
| VK_KHR_16BIT_STORAGE_EXTENSION_NAME, |
| VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, |
| VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, |
| VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, |
| VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, |
| VK_KHR_VARIABLE_POINTERS_EXTENSION_NAME, |
| VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME, |
| VK_EXT_PCI_BUS_INFO_EXTENSION_NAME, |
| VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME, |
| VK_KHR_VULKAN_MEMORY_MODEL_EXTENSION_NAME, |
| VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, |
| VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, |
| VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME, |
| }; |
| |
| if (m_properties.apiVersion < VK_MAKE_VERSION(1, 2, 0)) { |
| desired_extensions.push_back(VK_KHR_SPIRV_1_4_EXTENSION_NAME); |
| desired_extensions.push_back( |
| VK_KHR_SHADER_SUBGROUP_EXTENDED_TYPES_EXTENSION_NAME); |
| } |
| |
| if (m_properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) { |
| desired_extensions.push_back(VK_KHR_MAINTENANCE3_EXTENSION_NAME); |
| } |
| |
| if (m_properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) { |
| m_vulkan_device_extensions.push_back( |
| VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME); |
| } else { |
| desired_extensions.push_back( |
| VK_KHR_STORAGE_BUFFER_STORAGE_CLASS_EXTENSION_NAME); |
| } |
| |
| for (size_t i = 0; i < numext; i++) { |
| cvk_info(" %s, spec version %u", extensions[i].extensionName, |
| extensions[i].specVersion); |
| |
| for (auto name : desired_extensions) { |
| if (!strcmp(name, extensions[i].extensionName)) { |
| m_vulkan_device_extensions.push_back(name); |
| cvk_info(" ENABLING"); |
| break; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| void cvk_device::init_features(VkInstance instance) { |
| |
| cvk_info("Initialising features"); |
| |
| // Query supported features. |
| m_features_ubo_stdlayout.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES_KHR; |
| m_features_float16_int8.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR; |
| m_features_variable_pointer.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES_KHR; |
| m_features_8bit_storage.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES_KHR; |
| m_features_16bit_storage.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR; |
| m_features_shader_subgroup_extended_types.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES_KHR; |
| m_features_vulkan_memory_model.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES; |
| m_features_buffer_device_address.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR; |
| m_features_subgroup_size_control.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES; |
| m_features_shader_integer_dot_product.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_INTEGER_DOT_PRODUCT_FEATURES; |
| m_features_queue_global_priority.sType = |
| VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GLOBAL_PRIORITY_QUERY_FEATURES_KHR; |
| |
| std::vector<std::tuple<uint32_t, const char*, VkBaseOutStructure*>> |
| coreversion_extension_features = { |
| #define VER_EXT_FEAT(ver, ext, feat) \ |
| {ver, ext, reinterpret_cast<VkBaseOutStructure*>(&feat)} |
| VER_EXT_FEAT(VK_MAKE_VERSION(1, 2, 0), |
| VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, |
| m_features_ubo_stdlayout), |
| VER_EXT_FEAT(VK_MAKE_VERSION(1, 2, 0), |
| VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, |
| m_features_float16_int8), |
| VER_EXT_FEAT(VK_MAKE_VERSION(1, 1, 0), |
| VK_KHR_VARIABLE_POINTERS_EXTENSION_NAME, |
| m_features_variable_pointer), |
| VER_EXT_FEAT(VK_MAKE_VERSION(1, 2, 0), |
| VK_KHR_8BIT_STORAGE_EXTENSION_NAME, |
| m_features_8bit_storage), |
| VER_EXT_FEAT(VK_MAKE_VERSION(1, 1, 0), |
| VK_KHR_16BIT_STORAGE_EXTENSION_NAME, |
| m_features_16bit_storage), |
| VER_EXT_FEAT(VK_MAKE_VERSION(1, 2, 0), |
| VK_KHR_SHADER_SUBGROUP_EXTENDED_TYPES_EXTENSION_NAME, |
| m_features_shader_subgroup_extended_types), |
| VER_EXT_FEAT(VK_MAKE_VERSION(1, 3, 0), |
| VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, |
| m_features_subgroup_size_control), |
| VER_EXT_FEAT(VK_MAKE_VERSION(1, 2, 0), |
| VK_KHR_VULKAN_MEMORY_MODEL_EXTENSION_NAME, |
| m_features_vulkan_memory_model), |
| VER_EXT_FEAT(VK_MAKE_VERSION(1, 2, 0), |
| VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, |
| m_features_buffer_device_address), |
| VER_EXT_FEAT(VK_MAKE_VERSION(1, 3, 0), nullptr, |
| m_features_shader_integer_dot_product), |
| VER_EXT_FEAT(0, VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME, |
| m_features_queue_global_priority), |
| |
| #undef VER_EXT_FEAT |
| }; |
| |
| VkBaseOutStructure* pNext = nullptr; |
| for (auto& ver_ext_feat : coreversion_extension_features) { |
| auto corever = std::get<0>(ver_ext_feat); |
| auto ext = std::get<1>(ver_ext_feat); |
| auto feat = std::get<2>(ver_ext_feat); |
| if ((corever != 0) && (m_properties.apiVersion >= corever)) { |
| feat->pNext = pNext; |
| pNext = feat; |
| } else if ((ext != nullptr) && is_vulkan_extension_enabled(ext)) { |
| feat->pNext = pNext; |
| pNext = feat; |
| } |
| } |
| |
| VkPhysicalDeviceFeatures2 supported_features; |
| supported_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; |
| supported_features.pNext = pNext; |
| |
| if (m_properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) { |
| // Use the extension on Vulkan 1.0 platforms |
| auto func = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures2KHR>( |
| vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2KHR")); |
| if (!func) { |
| cvk_fatal( |
| "Failed to get pointer to vkGetPhysicalDeviceFeatures2KHR()"); |
| } |
| func(m_pdev, &supported_features); |
| } else { |
| vkGetPhysicalDeviceFeatures2(m_pdev, &supported_features); |
| } |
| |
| // Log supported features |
| cvk_info("8-bit storage: SSBO = %d, UBO = %d, Push constants = %d", |
| m_features_8bit_storage.storageBuffer8BitAccess, |
| m_features_8bit_storage.uniformAndStorageBuffer8BitAccess, |
| m_features_8bit_storage.storagePushConstant8); |
| cvk_info("16-bit storage: SSBO = %d, UBO = %d, Push constants = %d", |
| m_features_16bit_storage.storageBuffer16BitAccess, |
| m_features_16bit_storage.uniformAndStorageBuffer16BitAccess, |
| m_features_16bit_storage.storagePushConstant16); |
| cvk_info( |
| "subgroup extended types: %d", |
| m_features_shader_subgroup_extended_types.shaderSubgroupExtendedTypes); |
| |
| // Selectively enable core features. |
| if (supported_features.features.shaderInt16) { |
| m_features.features.shaderInt16 = VK_TRUE; |
| } |
| if (supported_features.features.shaderInt64) { |
| m_features.features.shaderInt64 = VK_TRUE; |
| } |
| if (supported_features.features.shaderFloat64) { |
| m_features.features.shaderFloat64 = VK_TRUE; |
| } |
| if (supported_features.features.shaderStorageImageReadWithoutFormat) { |
| m_features.features.shaderStorageImageReadWithoutFormat = VK_TRUE; |
| } |
| if (supported_features.features.shaderStorageImageWriteWithoutFormat) { |
| m_features.features.shaderStorageImageWriteWithoutFormat = VK_TRUE; |
| } |
| |
| // All queried extended features are enabled when supported. |
| m_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; |
| m_features.pNext = pNext; |
| } |
| |
| void cvk_device::init_command_pointers(VkInstance instance) { |
| // Buffer device address |
| if (m_properties.apiVersion >= VK_MAKE_VERSION(1, 2, 0)) { |
| m_vkfns.vkGetBufferDeviceAddressKHR = |
| GET_INSTANCE_PROC(instance, vkGetBufferDeviceAddress); |
| } else if (is_vulkan_extension_enabled( |
| VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME)) { |
| m_vkfns.vkGetBufferDeviceAddressKHR = |
| GET_INSTANCE_PROC(instance, vkGetBufferDeviceAddressKHR); |
| } |
| } |
| |
| void cvk_device::init_compiler_options() { |
| m_device_compiler_options = ""; |
| |
| if (!devices_support_images()) { |
| m_device_compiler_options += " -images=0 "; |
| } |
| |
| // 8-bit storage capability restrictions. |
| if (device_8bit_storage_features().storageBuffer8BitAccess == VK_FALSE) { |
| m_device_compiler_options += " -no-8bit-storage=ssbo "; |
| } |
| if (device_8bit_storage_features().uniformAndStorageBuffer8BitAccess == |
| VK_FALSE) { |
| m_device_compiler_options += " -no-8bit-storage=ubo "; |
| } |
| if (device_8bit_storage_features().storagePushConstant8 == VK_FALSE) { |
| m_device_compiler_options += " -no-8bit-storage=pushconstant "; |
| } |
| |
| // 16-bit storage capability restrictions. |
| if (device_16bit_storage_features().storageBuffer16BitAccess == VK_FALSE) { |
| m_device_compiler_options += " -no-16bit-storage=ssbo "; |
| } |
| if (device_16bit_storage_features().uniformAndStorageBuffer16BitAccess == |
| VK_FALSE) { |
| m_device_compiler_options += " -no-16bit-storage=ubo "; |
| } |
| if (device_16bit_storage_features().storagePushConstant16 == VK_FALSE) { |
| m_device_compiler_options += " -no-16bit-storage=pushconstant "; |
| } |
| std::vector<std::string> roundingModeRTE; |
| if (m_float_controls_properties.shaderRoundingModeRTEFloat16 && |
| supports_fp16()) { |
| roundingModeRTE.push_back("16"); |
| } |
| if (m_float_controls_properties.shaderRoundingModeRTEFloat32) { |
| roundingModeRTE.push_back("32"); |
| } |
| if (m_float_controls_properties.shaderRoundingModeRTEFloat64 && |
| supports_fp64()) { |
| roundingModeRTE.push_back("64"); |
| } |
| if (roundingModeRTE.size() > 0) { |
| m_device_compiler_options += " -rounding-mode-rte="; |
| for (unsigned i = 0; i < roundingModeRTE.size(); i++) { |
| if (i != 0) { |
| m_device_compiler_options += ","; |
| } |
| m_device_compiler_options += roundingModeRTE[i]; |
| } |
| m_device_compiler_options += " "; |
| } |
| |
| // Types support |
| if (!supports_fp16()) { |
| m_device_compiler_options += " -fp16=0 "; |
| } |
| if (!supports_fp64()) { |
| m_device_compiler_options += " -fp64=0 "; |
| } |
| if (supports_int8()) { |
| m_device_compiler_options += " -rewrite-packed-structs "; |
| } else { |
| m_device_compiler_options += " -int8=0 "; |
| } |
| if (supports_ubo_stdlayout()) { |
| m_device_compiler_options += " -std430-ubo-layout "; |
| } |
| if (supports_non_uniform_decoration()) { |
| m_device_compiler_options += " -decorate-nonuniform "; |
| } |
| |
| // Device specific options |
| m_device_compiler_options += |
| " " + m_clvk_properties->get_compile_options() + " "; |
| |
| m_device_compiler_options += " -arch=" + m_spirv_arch + " "; |
| |
| if (m_physical_addressing) { |
| m_device_compiler_options += " -physical-storage-buffers "; |
| } |
| |
| if (supports_dot_product()) { |
| m_device_compiler_options += " -cl-arm-integer-dot-product "; |
| } |
| |
| // Builtin options |
| auto parse_builtins = [](std::string s) { |
| std::set<std::string> builtins; |
| size_t pos = 0; |
| size_t comma = s.find(',', pos); |
| while (comma != std::string::npos) { |
| builtins.insert(s.substr(pos, comma - pos)); |
| pos = comma + 1; |
| comma = s.find(',', pos); |
| } |
| builtins.insert(s.substr(pos)); |
| return builtins; |
| }; |
| auto clspv_library_builtins = |
| parse_builtins(config.clspv_library_builtins()); |
| auto native_builtins = m_clvk_properties->get_native_builtins(); |
| auto clspv_native_builtins = parse_builtins(config.clspv_native_builtins()); |
| native_builtins.insert(clspv_native_builtins.begin(), |
| clspv_native_builtins.end()); |
| |
| std::string builtin_list = ""; |
| for (const auto& builtin : native_builtins) { |
| if (clspv_library_builtins.count(builtin) > 0) { |
| continue; |
| } |
| builtin_list += builtin + ","; |
| } |
| if (builtin_list != "") { |
| m_device_compiler_options += |
| " --use-native-builtins=" + builtin_list + " "; |
| } |
| |
| // Select target SPIR-V version |
| m_device_compiler_options += " -spv-version="; |
| switch (vulkan_spirv_env()) { |
| default: |
| case SPV_ENV_VULKAN_1_0: |
| m_device_compiler_options += "1.0 "; |
| break; |
| case SPV_ENV_VULKAN_1_1: |
| m_device_compiler_options += "1.3 "; |
| break; |
| case SPV_ENV_VULKAN_1_1_SPIRV_1_4: |
| m_device_compiler_options += "1.4 "; |
| break; |
| case SPV_ENV_VULKAN_1_2: |
| m_device_compiler_options += "1.5 "; |
| break; |
| case SPV_ENV_VULKAN_1_3: |
| m_device_compiler_options += "1.6 "; |
| break; |
| } |
| |
| // Limits |
| m_device_compiler_options += |
| " -max-pushconstant-size=" + |
| std::to_string(vulkan_max_push_constants_size()) + " "; |
| m_device_compiler_options += |
| " -max-ubo-size=" + std::to_string(vulkan_max_uniform_buffer_range()) + |
| " "; |
| m_device_compiler_options += " -global-offset "; |
| m_device_compiler_options += " -long-vector "; |
| m_device_compiler_options += " -module-constants-in-storage-buffer "; |
| m_device_compiler_options += " -cl-arm-non-uniform-work-group-size "; |
| } |
| |
| void cvk_device::build_extension_ils_list() { |
| |
| m_extensions = { |
| // Start with required extensions |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_global_int32_base_atomics"), |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_global_int32_extended_atomics"), |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_local_int32_base_atomics"), |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_local_int32_extended_atomics"), |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_byte_addressable_store"), |
| |
| // Add always supported extensions |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_extended_versioning"), |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_create_command_queue"), |
| #ifdef ENABLE_SPIRV_IL |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_il_program"), |
| #endif |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_spirv_no_integer_wrap_decoration"), |
| MAKE_NAME_VERSION(1, 0, 0, "cl_arm_non_uniform_work_group_size"), |
| MAKE_NAME_VERSION(1, 0, 0, "cl_arm_printf"), |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_suggested_local_work_size"), |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_3d_image_writes"), |
| // MAKE_NAME_VERSION(0, 9, 0, "cl_khr_semaphore"), |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_spirv_linkonce_odr"), |
| }; |
| |
| if (m_properties.apiVersion >= VK_MAKE_VERSION(1, 1, 0)) { |
| m_extensions.push_back( |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_device_uuid")); |
| VkSubgroupFeatureFlags required_subgroup_ops = |
| VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT; |
| if ((m_subgroup_properties.supportedOperations & |
| required_subgroup_ops) == required_subgroup_ops && |
| (m_features_shader_subgroup_extended_types |
| .shaderSubgroupExtendedTypes == VK_TRUE)) { |
| m_has_subgroups_support = true; |
| } |
| if (m_subgroup_properties.supportedOperations & |
| VK_SUBGROUP_FEATURE_SHUFFLE_BIT) { |
| m_extensions.push_back( |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_subgroup_shuffle")); |
| } |
| if (supports_subgroup_rotate()) { |
| m_extensions.push_back( |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_subgroup_rotate")); |
| } |
| if (m_subgroup_properties.supportedOperations & |
| VK_SUBGROUP_FEATURE_BALLOT_BIT) { |
| m_extensions.push_back( |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_subgroup_ballot")); |
| } |
| if (m_subgroup_properties.supportedOperations & |
| VK_SUBGROUP_FEATURE_VOTE_BIT) { |
| m_extensions.push_back( |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_subgroup_non_uniform_vote")); |
| } |
| } |
| |
| // Enable cl_khr_fp16 if we have 16-bit storage and shaderFloat16 |
| if ((is_vulkan_extension_enabled(VK_KHR_16BIT_STORAGE_EXTENSION_NAME) && |
| m_features_16bit_storage.storageBuffer16BitAccess) && |
| (is_vulkan_extension_enabled( |
| VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME) && |
| m_features_float16_int8.shaderFloat16)) { |
| m_has_fp16_support = true; |
| m_extensions.push_back(MAKE_NAME_VERSION(1, 0, 0, "cl_khr_fp16")); |
| } |
| |
| if (supports_fp64()) { |
| m_extensions.push_back(MAKE_NAME_VERSION(1, 0, 0, "cl_khr_fp64")); |
| } |
| |
| // Enable 8-bit integer support if possible |
| if ((is_vulkan_extension_enabled(VK_KHR_8BIT_STORAGE_EXTENSION_NAME) && |
| m_features_8bit_storage.storageBuffer8BitAccess) && |
| (is_vulkan_extension_enabled( |
| VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME) && |
| m_features_float16_int8.shaderInt8)) { |
| m_has_int8_support = true; |
| } |
| |
| // Report cl_khr_pci_bus_info if VK_EXT_pci_bus_info is supported |
| if (is_vulkan_extension_enabled(VK_EXT_PCI_BUS_INFO_EXTENSION_NAME)) { |
| m_extensions.push_back( |
| MAKE_NAME_VERSION(1, 0, 0, "cl_khr_pci_bus_info")); |
| } |
| |
| if ((m_properties.apiVersion >= VK_MAKE_VERSION(1, 3, 0) || |
| is_vulkan_extension_enabled( |
| VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME)) && |
| m_features_subgroup_size_control.subgroupSizeControl && |
| (m_subgroup_size_control_properties.requiredSubgroupSizeStages & |
| VK_SHADER_STAGE_COMPUTE_BIT)) { |
| m_extensions.push_back( |
| MAKE_NAME_VERSION(1, 0, 0, "cl_intel_required_subgroup_size")); |
| m_has_subgroup_size_selection = true; |
| } |
| |
| if (supports_dot_product()) { |
| if (supports_int8()) { |
| m_extensions.push_back(MAKE_NAME_VERSION( |
| 2, 0, 0, CL_KHR_INTEGER_DOT_PRODUCT_EXTENSION_NAME)); |
| m_extensions.push_back( |
| MAKE_NAME_VERSION(1, 0, 0, "cl_arm_integer_dot_product_int8")); |
| m_extensions.push_back(MAKE_NAME_VERSION( |
| 1, 0, 0, "cl_arm_integer_dot_product_accumulate_int8")); |
| m_extensions.push_back(MAKE_NAME_VERSION( |
| 1, 0, 0, |
| "cl_arm_integer_dot_product_accumulate_saturate_int8")); |
| } |
| m_extensions.push_back(MAKE_NAME_VERSION( |
| 1, 0, 0, "cl_arm_integer_dot_product_accumulate_int16")); |
| } |
| |
| auto split_string = [](std::string input, char delimiter) { |
| std::vector<std::string> outputs; |
| size_t pos = 0; |
| while ((pos = input.find(delimiter)) != std::string::npos) { |
| outputs.push_back(input.substr(0, pos)); |
| input.erase(0, pos + 1); |
| } |
| if (input.size() > 0) { |
| outputs.push_back(input); |
| } |
| return outputs; |
| }; |
| auto config_extensions = split_string(config.device_extensions(), ','); |
| for (auto& config_extension : config_extensions) { |
| cl_name_version extension; |
| extension.version = CL_MAKE_VERSION(0, 0, 0); |
| memcpy(extension.name, config_extension.c_str(), |
| std::min(config_extension.size(), |
| (size_t)CL_NAME_VERSION_MAX_NAME_SIZE)); |
| m_extensions.push_back(extension); |
| } |
| |
| auto config_extensions_masked = |
| split_string(config.device_extensions_masked(), ','); |
| for (auto& config_extension_masked : config_extensions_masked) { |
| for (auto it = m_extensions.begin(); it != m_extensions.end(); it++) { |
| if (strcmp(config_extension_masked.c_str(), |
| CL_KHR_FP16_EXTENSION_NAME) == 0) { |
| m_has_fp16_support = false; |
| } |
| if (strcmp(config_extension_masked.c_str(), it->name) == 0) { |
| m_extensions.erase(it); |
| break; |
| } |
| } |
| } |
| |
| // Build extension string |
| for (auto& ext : m_extensions) { |
| m_extension_string += ext.name; |
| m_extension_string += " "; |
| } |
| cvk_info_fn("extensions: '%s'", m_extension_string.c_str()); |
| |
| // Build list of ILs |
| m_ils = { |
| #ifdef ENABLE_SPIRV_IL |
| MAKE_NAME_VERSION(1, 0, 0, "SPIR-V"), |
| #endif |
| }; |
| |
| for (auto& il : m_ils) { |
| m_ils_string += il.name; |
| m_ils_string += "_"; |
| m_ils_string += std::to_string(CL_VERSION_MAJOR(il.version)); |
| m_ils_string += "."; |
| m_ils_string += std::to_string(CL_VERSION_MINOR(il.version)); |
| m_ils_string += " "; |
| } |
| |
| // Build list of supported OpenCL C versions |
| m_opencl_c_versions = { |
| MAKE_NAME_VERSION(1, 0, 0, "OpenCL C"), |
| MAKE_NAME_VERSION(1, 1, 0, "OpenCL C"), |
| MAKE_NAME_VERSION(1, 2, 0, "OpenCL C"), |
| MAKE_NAME_VERSION(3, 0, 0, "OpenCL C"), |
| }; |
| |
| // Build list of supported OpenCL C features |
| m_opencl_c_features = { |
| MAKE_NAME_VERSION(3, 0, 0, "__opencl_c_images"), |
| MAKE_NAME_VERSION(3, 0, 0, "__opencl_c_3d_image_writes"), |
| // TODO(#216) re-enable when clspv ready |
| // MAKE_NAME_VERSION(3, 0, 0, "__opencl_c_generic_address_space"), |
| }; |
| if (supports_read_write_images()) { |
| m_opencl_c_features.push_back( |
| MAKE_NAME_VERSION(3, 0, 0, "__opencl_c_read_write_images")); |
| } |
| if (supports_atomic_order_acq_rel()) { |
| m_opencl_c_features.push_back( |
| MAKE_NAME_VERSION(3, 0, 0, "__opencl_c_atomic_order_acq_rel")); |
| } |
| if (supports_atomic_scope_device()) { |
| m_opencl_c_features.push_back( |
| MAKE_NAME_VERSION(3, 0, 0, "__opencl_c_atomic_scope_device")); |
| } |
| if (supports_subgroups()) { |
| m_opencl_c_features.push_back( |
| MAKE_NAME_VERSION(3, 0, 0, "__opencl_c_subgroups")); |
| } |
| if (m_features.features.shaderInt64) { |
| m_opencl_c_features.push_back( |
| MAKE_NAME_VERSION(3, 0, 0, "__opencl_c_int64")); |
| } |
| if (supports_fp64()) { |
| m_opencl_c_features.push_back( |
| MAKE_NAME_VERSION(3, 0, 0, "__opencl_c_fp64")); |
| } |
| if (supports_dot_product()) { |
| if (supports_int8()) { |
| m_opencl_c_features.push_back(MAKE_NAME_VERSION( |
| 3, 0, 0, "__opencl_c_integer_dot_product_input_4x8bit")); |
| } |
| m_opencl_c_features.push_back(MAKE_NAME_VERSION( |
| 3, 0, 0, "__opencl_c_integer_dot_product_input_4x8bit_packed")); |
| } |
| } |
| |
| bool cvk_device::create_vulkan_queues_and_device(uint32_t num_queues, |
| uint32_t queue_family) { |
| cvk_info("Creating Vulkan device and queues"); |
| // Give all queues the same priority |
| std::vector<float> queuePriorities(num_queues, 1.0f); |
| void* pNext = nullptr; |
| VkDeviceQueueGlobalPriorityCreateInfoKHR globalPriorityCreateInfo; |
| cvk_info_fn("queue global priority: %u", |
| m_features_queue_global_priority.globalPriorityQuery); |
| if (m_features_queue_global_priority.globalPriorityQuery) { |
| const uint32_t num_priorities = 4; |
| static const VkQueueGlobalPriorityKHR priorities[num_priorities] = { |
| VK_QUEUE_GLOBAL_PRIORITY_LOW_KHR, |
| VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR, |
| VK_QUEUE_GLOBAL_PRIORITY_HIGH_KHR, |
| VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR}; |
| uint32_t queue_priority = |
| std::min(config.queue_global_priority(), num_priorities - 1); |
| |
| globalPriorityCreateInfo.sType = |
| VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_KHR; |
| globalPriorityCreateInfo.pNext = nullptr; |
| globalPriorityCreateInfo.globalPriority = priorities[queue_priority]; |
| pNext = &globalPriorityCreateInfo; |
| |
| cvk_info_fn("setting queue global priority to '%u': '%s' (%u)", |
| queue_priority, |
| queue_global_priority_to_string( |
| globalPriorityCreateInfo.globalPriority), |
| globalPriorityCreateInfo.globalPriority); |
| } |
| |
| VkDeviceQueueCreateInfo queueCreateInfo = { |
| VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, |
| pNext, |
| 0, // flags |
| queue_family, |
| num_queues, // queueCount |
| queuePriorities.data()}; |
| |
| // Create logical device |
| const VkDeviceCreateInfo createInfo = { |
| VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, // sType |
| &m_features, // pNext |
| 0, // flags |
| 1, // queueCreateInfoCount |
| &queueCreateInfo, // pQueueCreateInfos, |
| 0, // enabledLayerCount |
| nullptr, // ppEnabledLayerNames |
| static_cast<uint32_t>( |
| m_vulkan_device_extensions.size()), // enabledExtensionCount |
| m_vulkan_device_extensions.data(), // ppEnabledExtensionNames |
| nullptr, // pEnabledFeatures |
| }; |
| |
| VkResult res = vkCreateDevice(m_pdev, &createInfo, nullptr, &m_dev); |
| CVK_VK_CHECK_ERROR_RET(res, false, "Failed to create a device"); |
| |
| // Construct the queue wrappers now that our queues exist |
| m_vulkan_queues.reserve(num_queues); |
| for (auto i = 0U; i < num_queues; i++) { |
| VkQueue queue; |
| |
| vkGetDeviceQueue(m_dev, queue_family, i, &queue); |
| m_vulkan_queues.emplace_back(queue, queue_family); |
| } |
| |
| return true; |
| } |
| |
| bool cvk_device::init_time_management(VkInstance instance) { |
| |
| if (is_vulkan_extension_enabled( |
| VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME)) { |
| auto func = GET_INSTANCE_PROC( |
| instance, vkGetPhysicalDeviceCalibrateableTimeDomainsEXT); |
| uint32_t num_time_domains; |
| VkResult res; |
| res = func(m_pdev, &num_time_domains, nullptr); |
| if (res != VK_SUCCESS) { |
| cvk_error( |
| "Can't get number of available calibrateable time domains"); |
| return false; |
| } |
| |
| cvk_info("Device supports %u calibrateable time domains", |
| num_time_domains); |
| |
| std::vector<VkTimeDomainEXT> supported_time_domains(num_time_domains); |
| res = func(m_pdev, &num_time_domains, supported_time_domains.data()); |
| if (res != VK_SUCCESS) { |
| cvk_error("Can't get list of available calibrateable time domains"); |
| return false; |
| } |
| |
| bool has_device = false; |
| bool has_monotonic = false; |
| for (auto td : supported_time_domains) { |
| cvk_info(" %s", |
| vulkan_calibrateable_time_domain_string(td).c_str()); |
| if (td == VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT) { |
| has_monotonic = true; |
| } |
| |
| if (td == VK_TIME_DOMAIN_DEVICE_EXT) { |
| has_device = true; |
| } |
| } |
| |
| if (has_device && has_monotonic) { |
| m_has_timer_support = true; |
| m_vkfns.vkGetCalibratedTimestampsEXT = |
| GET_INSTANCE_PROC(instance, vkGetCalibratedTimestampsEXT); |
| } |
| } |
| |
| if (!m_has_timer_support) { |
| cvk_warn("This device does not support VK_EXT_calibrated_timestamps or " |
| "it does not support the required " |
| "VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT and " |
| "VK_TIME_DOMAIN_DEVICE_EXT time domains"); |
| cvk_warn("clGetHostTimer and clGetDeviceAndHostTimer will not work"); |
| cvk_warn("Command queue profiling will suffer from limitations"); |
| } |
| |
| return true; |
| } |
| |
| // Returns the pipeline cache file path for a given SPIR-V SHA-1 hash. |
| // If pipeline cache serialization is not enabled, an empty string is returned. |
| std::string |
| cvk_device::get_pipeline_cache_filename(const cvk_sha1_hash& sha1) const { |
| if (config.cache_dir().empty()) { |
| return ""; |
| } |
| |
| // The pipeline cache file path is: |
| // ${CLVK_CACHE_DIR}/clvk-pipeline-cache.<UUID>.<SHA1>.bin |
| std::string cache_path = config.cache_dir; |
| cache_path += "/"; |
| cache_path += "clvk-pipeline-cache."; |
| cache_path += to_hex_string(m_properties.pipelineCacheUUID, VK_UUID_SIZE); |
| cache_path += "."; |
| cache_path += to_hex_string(reinterpret_cast<const uint8_t*>(sha1.data()), |
| SHA1_DIGEST_NUM_BYTES); |
| cache_path += ".bin"; |
| return cache_path; |
| } |
| |
| bool cvk_device::get_pipeline_cache(const std::vector<uint32_t>& spirv, |
| VkPipelineCache& pipeline_cache) { |
| |
| std::lock_guard<std::mutex> lock(m_pipeline_cache_mutex); |
| |
| pipeline_cache = VK_NULL_HANDLE; |
| |
| // Compute SHA-1 hash of the SPIR-V binary |
| cvk_sha1_hash sha1 = |
| cvk_sha1(spirv.data(), spirv.size() * sizeof(uint32_t)); |
| |
| // Check the in-memory cache of pipeline caches |
| if (m_pipeline_caches.count(sha1)) { |
| pipeline_cache = m_pipeline_caches.at(sha1); |
| return true; |
| } |
| |
| std::vector<char> cache_data; |
| |
| // Load pipeline cache data from file if this is enabled |
| std::string cache_path = get_pipeline_cache_filename(sha1); |
| if (!cache_path.empty()) { |
| cvk_info("Looking for pipeline cache at %s", cache_path.c_str()); |
| std::ifstream cache_file(cache_path, std::ios::in | std::ios::binary); |
| if (cache_file.is_open()) { |
| // Get the size of the pipeline cache file |
| cache_file.seekg(0, std::ios::end); |
| uint32_t size = cache_file.tellg(); |
| cache_file.seekg(0, std::ios::beg); |
| |
| // Load the pipeline cache data into memory |
| cache_data.resize(size); |
| cache_file.read(cache_data.data(), size); |
| if (!cache_file.good()) { |
| cvk_warn("Failed to read pipeline cache data"); |
| cache_data.clear(); |
| } |
| } else { |
| cvk_warn("Failed to open pipeline cache file"); |
| } |
| } |
| |
| // Create pipeline cache |
| VkPipelineCacheCreateInfo pipelineCacheCreateInfo = { |
| VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, |
| nullptr, // pNext |
| 0, // flags |
| cache_data.size(), // initialDataSize |
| cache_data.data(), // pInitialData |
| }; |
| |
| VkResult res = vkCreatePipelineCache(m_dev, &pipelineCacheCreateInfo, |
| nullptr, &pipeline_cache); |
| if (res != VK_SUCCESS) { |
| cvk_error("Could not create pipeline cache."); |
| return false; |
| } |
| |
| // Add pipeline cache to the in-memory cache |
| m_pipeline_caches[sha1] = pipeline_cache; |
| |
| return cache_data.size() != 0; |
| } |
| |
| void cvk_device::save_pipeline_cache( |
| const cvk_sha1_hash& sha1, const VkPipelineCache& pipeline_cache) const { |
| VkResult res; |
| |
| std::string cache_path = get_pipeline_cache_filename(sha1); |
| if (cache_path.empty()) { |
| return; |
| } |
| |
| // Retrieve the pipeline cache data from the Vulkan implementation |
| size_t size; |
| res = vkGetPipelineCacheData(m_dev, pipeline_cache, &size, nullptr); |
| if (res != VK_SUCCESS) { |
| cvk_error("Failed to retrieve pipeline cache size"); |
| return; |
| } |
| std::vector<char> cache_data(size); |
| res = |
| vkGetPipelineCacheData(m_dev, pipeline_cache, &size, cache_data.data()); |
| if (res != VK_SUCCESS) { |
| cvk_error("Failed to retrieve pipeline cache data"); |
| return; |
| } |
| |
| cvk_info("Writing %lu bytes of pipeline cache data to file", size); |
| |
| // Write the pipeline cache data to file |
| std::ofstream cache_file(cache_path, std::ios::out | std::ios::binary); |
| if (!cache_file.is_open()) { |
| cvk_error("Failed to open pipeline cache file for writing: %s", |
| cache_path.c_str()); |
| return; |
| } |
| cache_file.write(cache_data.data(), size); |
| if (!cache_file.good()) { |
| cvk_error("Failed to write pipeline cache data"); |
| } |
| } |
| |
| void cvk_device::init_spirv_environment() { |
| if (m_properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) { |
| m_vulkan_spirv_env = SPV_ENV_VULKAN_1_0; |
| } else if (m_properties.apiVersion < VK_MAKE_VERSION(1, 2, 0)) { |
| if (is_vulkan_extension_enabled(VK_KHR_SPIRV_1_4_EXTENSION_NAME)) { |
| m_vulkan_spirv_env = SPV_ENV_VULKAN_1_1_SPIRV_1_4; |
| } else { |
| m_vulkan_spirv_env = SPV_ENV_VULKAN_1_1; |
| } |
| } else if (m_properties.apiVersion < VK_MAKE_VERSION(1, 3, 0)) { |
| m_vulkan_spirv_env = SPV_ENV_VULKAN_1_2; |
| } else { |
| // Assume 1.3 |
| m_vulkan_spirv_env = SPV_ENV_VULKAN_1_3; |
| } |
| cvk_info("Vulkan SPIR-V environment: %s", |
| spvTargetEnvDescription(m_vulkan_spirv_env)); |
| } |
| |
| void cvk_device::log_limits_and_memory_information() { |
| // Print relevant device limits |
| const VkPhysicalDeviceLimits& limits = vulkan_limits(); |
| cvk_info("Device's resources per stage limits:"); |
| cvk_info(" total = %u", limits.maxPerStageResources); |
| cvk_info(" uniform buffers = %u", |
| limits.maxPerStageDescriptorUniformBuffers); |
| cvk_info(" storage buffers = %u", |
| limits.maxPerStageDescriptorStorageBuffers); |
| cvk_info("Device's max buffer size = %s", |
| pretty_size(limits.maxStorageBufferRange).c_str()); |
| cvk_info("Device's max uniform buffer size = %s", |
| pretty_size(limits.maxUniformBufferRange).c_str()); |
| cvk_info("Device's max push constant size = %s", |
| pretty_size(limits.maxPushConstantsSize).c_str()); |
| cvk_info("Device's execution capabilities:"); |
| cvk_info(" Max work-group count: {%u,%u,%u}", |
| limits.maxComputeWorkGroupCount[0], |
| limits.maxComputeWorkGroupCount[1], |
| limits.maxComputeWorkGroupCount[2]); |
| cvk_info(" Max invocations per work-group: %u", |
| limits.maxComputeWorkGroupInvocations); |
| cvk_info(" Max work-group size: {%u,%u,%u}", |
| limits.maxComputeWorkGroupSize[0], |
| limits.maxComputeWorkGroupSize[1], |
| limits.maxComputeWorkGroupSize[2]); |
| cvk_info(" Min subgroup size: %u", |
| m_subgroup_size_control_properties.minSubgroupSize); |
| cvk_info(" Max subgroup size: %u", |
| m_subgroup_size_control_properties.maxSubgroupSize); |
| cvk_info(" Max subgroups per work-group: %u", |
| m_subgroup_size_control_properties.maxComputeWorkgroupSubgroups); |
| |
| // Print memoy information |
| cvk_info("Device has %u memory types:", m_mem_properties.memoryTypeCount); |
| for (uint32_t i = 0; i < m_mem_properties.memoryTypeCount; i++) { |
| VkMemoryType memtype = m_mem_properties.memoryTypes[i]; |
| auto heapsize = m_mem_properties.memoryHeaps[memtype.heapIndex].size; |
| cvk_info( |
| " %u: heap = %u, %s | %s", i, memtype.heapIndex, |
| pretty_size(heapsize).c_str(), |
| vulkan_memory_property_flags_string(memtype.propertyFlags).c_str()); |
| } |
| |
| cvk_info("Device has %u memory heaps:", m_mem_properties.memoryHeapCount); |
| for (uint32_t i = 0; i < m_mem_properties.memoryHeapCount; i++) { |
| VkMemoryHeap memheap = m_mem_properties.memoryHeaps[i]; |
| cvk_info(" %u: %s | %s", i, pretty_size(memheap.size).c_str(), |
| vulkan_memory_property_flags_string(memheap.flags).c_str()); |
| } |
| } |
| |
| bool cvk_device::init(VkInstance instance) { |
| cvk_info("Initialising device %s", m_properties.deviceName); |
| cvk_info(" API Version: %s", |
| vulkan_version_string(m_properties.apiVersion).c_str()); |
| |
| uint32_t num_queues, queue_family; |
| if (!init_queues(&num_queues, &queue_family)) { |
| return false; |
| } |
| |
| if (!init_extensions()) { |
| return false; |
| } |
| |
| init_vulkan_properties(instance); |
| |
| init_clvk_runtime_behaviors(); |
| |
| init_driver_behaviors(); |
| |
| init_features(instance); |
| |
| init_command_pointers(instance); |
| |
| build_extension_ils_list(); |
| |
| if (!init_time_management(instance)) { |
| return false; |
| } |
| |
| if (!create_vulkan_queues_and_device(num_queues, queue_family)) { |
| return false; |
| } |
| |
| init_spirv_environment(); |
| |
| log_limits_and_memory_information(); |
| |
| // Must be done last as it relies on info set up in several of the above. |
| init_compiler_options(); |
| |
| return true; |
| } |
| |
| std::string cvk_device::vendor() const { |
| // Is this a Khronos vendor ID? |
| if (m_properties.vendorID > 0xFFFF) { |
| return vulkan_vendor_id_string( |
| static_cast<VkVendorId>(m_properties.vendorID)); |
| } |
| |
| // If not, we are looking at a PCI Vendor ID. |
| // TODO support looking it up in the PCI DB. |
| |
| return m_clvk_properties->vendor(); |
| } |
| |
| bool cvk_device::supports_capability(spv::Capability capability) const { |
| switch (capability) { |
| // Capabilities required by all Vulkan implementations: |
| case spv::CapabilityShader: |
| case spv::CapabilitySampled1D: |
| case spv::CapabilityImage1D: |
| case spv::CapabilityImageQuery: |
| case spv::CapabilityImageBuffer: |
| case spv::CapabilitySampledBuffer: |
| return true; |
| // Optional capabilities: |
| case spv::CapabilityFloat16: |
| return m_features_float16_int8.shaderFloat16; |
| case spv::CapabilityFloat64: |
| return m_features.features.shaderFloat64; |
| case spv::CapabilityInt8: |
| return m_features_float16_int8.shaderInt8; |
| case spv::CapabilityInt16: |
| return m_features.features.shaderInt16; |
| case spv::CapabilityInt64: |
| return m_features.features.shaderInt64; |
| case spv::CapabilityStorageImageReadWithoutFormat: |
| return m_features.features.shaderStorageImageReadWithoutFormat; |
| case spv::CapabilityStorageImageWriteWithoutFormat: |
| return m_features.features.shaderStorageImageWriteWithoutFormat; |
| case spv::CapabilityVariablePointers: |
| return m_features_variable_pointer.variablePointers; |
| case spv::CapabilityVariablePointersStorageBuffer: |
| return m_features_variable_pointer.variablePointersStorageBuffer; |
| case spv::CapabilityGroupNonUniform: |
| return m_subgroup_properties.supportedOperations & |
| VK_SUBGROUP_FEATURE_BASIC_BIT; |
| case spv::CapabilityGroupNonUniformVote: |
| return m_subgroup_properties.supportedOperations & |
| VK_SUBGROUP_FEATURE_VOTE_BIT; |
| case spv::CapabilityGroupNonUniformArithmetic: |
| return m_subgroup_properties.supportedOperations & |
| VK_SUBGROUP_FEATURE_ARITHMETIC_BIT; |
| case spv::CapabilityGroupNonUniformBallot: |
| return m_subgroup_properties.supportedOperations & |
| VK_SUBGROUP_FEATURE_BALLOT_BIT; |
| case spv::CapabilityGroupNonUniformShuffle: |
| return m_subgroup_properties.supportedOperations & |
| VK_SUBGROUP_FEATURE_SHUFFLE_BIT; |
| case spv::CapabilityGroupNonUniformRotateKHR: |
| return supports_subgroup_rotate(); |
| case spv::CapabilityVulkanMemoryModel: |
| return m_features_vulkan_memory_model.vulkanMemoryModel; |
| case spv::CapabilityShaderNonUniform: |
| return supports_non_uniform_decoration(); |
| case spv::CapabilityPhysicalStorageBufferAddresses: |
| return m_features_buffer_device_address.bufferDeviceAddress; |
| case spv::CapabilityRoundingModeRTE: |
| return m_float_controls_properties.shaderRoundingModeRTEFloat32 || |
| m_float_controls_properties.shaderRoundingModeRTEFloat16 || |
| m_float_controls_properties.shaderRoundingModeRTEFloat64; |
| case spv::CapabilityDotProduct: |
| case spv::CapabilityDotProductInput4x8BitPacked: |
| return supports_dot_product(); |
| case spv::CapabilityDotProductInput4x8Bit: |
| case spv::CapabilityDotProductInputAll: |
| return supports_dot_product() && supports_int8(); |
| // Capabilities that have not yet been mapped to Vulkan features: |
| default: |
| cvk_warn_fn("Capability %d not yet mapped to a feature.", capability); |
| return false; |
| } |
| } |
| |
| void cvk_device::select_work_group_size( |
| cvk_kernel* kernel, const std::array<uint32_t, 3>& global_size, |
| std::array<uint32_t, 3>& local_size) const { |
| |
| auto required_work_group_size = kernel->required_work_group_size(); |
| if (required_work_group_size[0] != 0) { |
| local_size = required_work_group_size; |
| return; |
| } |
| // Start at (1,1,1), which is always valid. |
| local_size = {1, 1, 1}; |
| |
| // Cap the total work-group size to the Vulkan device's limit. |
| uint32_t max_size = m_properties.limits.maxComputeWorkGroupInvocations; |
| |
| // Further cap the total size to 64, as this is expected to be a |
| // reasonable size on many devices. |
| max_size = std::min(max_size, UINT32_C(64)); |
| |
| // TODO: We should also take into account the total number of |
| // work-groups that would be launched, to ensure the device is fully |
| // utilized for smaller global work sizes. |
| |
| // Increase the work-group size until we hit device limits. |
| bool changed; |
| do { |
| changed = false; |
| |
| // Alternate between increasing the X and Y dimensions. |
| // TODO: Consider increasing the Z dimension as well? |
| for (int i = 0; i < 2; i++) { |
| // Double the dimension if we can. |
| // TODO: Allow non power-of-two sizes? |
| // TODO: Allow non-uniform sizes if supported? |
| std::array<uint32_t, 3> new_local_size = local_size; |
| new_local_size[i] *= 2; |
| if (global_size[i] % new_local_size[i] == 0 && |
| new_local_size[i] <= |
| m_properties.limits.maxComputeWorkGroupCount[i] && |
| new_local_size[0] * new_local_size[1] * new_local_size[2] <= |
| max_size) { |
| local_size = new_local_size; |
| changed = true; |
| } |
| } |
| } while (changed); |
| } |
| |
| cl_int cvk_device::get_device_host_timer(cl_ulong* device_timestamp, |
| cl_ulong* host_timestamp) const { |
| auto vkdev = vulkan_device(); |
| |
| uint64_t timestamps[2]; |
| uint64_t max_deviation; |
| VkCalibratedTimestampInfoEXT timestamp_infos[2] = { |
| {VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT, nullptr, |
| VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT}, |
| {VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT, nullptr, |
| VK_TIME_DOMAIN_DEVICE_EXT}}; |
| |
| uint32_t num_requested_timestamps; |
| if (device_timestamp == nullptr) { |
| num_requested_timestamps = 1; |
| } else { |
| num_requested_timestamps = 2; |
| } |
| |
| auto res = m_vkfns.vkGetCalibratedTimestampsEXT( |
| vkdev, num_requested_timestamps, timestamp_infos, timestamps, |
| &max_deviation); |
| if (res != VK_SUCCESS) { |
| cvk_error_fn("vkGetCalibratedTimestampsEXT failed %d %s", res, |
| vulkan_error_string(res)); |
| return CL_OUT_OF_RESOURCES; |
| } |
| |
| *host_timestamp = timestamps[0]; |
| if (device_timestamp != nullptr) { |
| *device_timestamp = timestamp_to_ns(timestamps[1]); |
| } |
| |
| return CL_SUCCESS; |
| } |
| |
| cl_int cvk_device::update_device_host_timer_no_lock() { |
| return get_device_host_timer(&m_sync_dev, &m_sync_host); |
| } |
| |
| cl_int cvk_device::update_device_host_timer() { |
| std::lock_guard<std::mutex> lock(m_sync_mutex); |
| return update_device_host_timer_no_lock(); |
| } |
| |
| cl_int cvk_device::device_timer_to_host(cl_ulong dev, cl_ulong& host) { |
| std::lock_guard<std::mutex> lock(m_sync_mutex); |
| if (dev > m_sync_dev) { |
| cl_int err = update_device_host_timer_no_lock(); |
| if (err != CL_SUCCESS) { |
| return err; |
| } |
| } |
| if (m_sync_host > m_sync_dev) { |
| host = (m_sync_host - m_sync_dev) + dev; |
| } else { |
| host = dev - (m_sync_dev - m_sync_host); |
| } |
| return CL_SUCCESS; |
| } |