| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <chrono> |
| #include <future> |
| #include <thread> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| #include <vulkan/vulkan.h> |
| |
| #define PRINT_STDERR(format, ...) \ |
| fprintf(stderr, "%s:%d " format "\n", __FILE__, __LINE__, ##__VA_ARGS__) |
| |
| namespace { |
| |
| class VkPriorityTest { |
| public: |
| explicit VkPriorityTest(bool different_priority) : different_priority_(different_priority) {} |
| |
| bool Initialize(); |
| bool Exec(); |
| |
| private: |
| bool InitVulkan(); |
| bool InitCommandPool(); |
| bool InitCommandBuffer(VkCommandBuffer* command_buffer, uint32_t executions); |
| |
| bool is_initialized_ = false; |
| VkPhysicalDevice vk_physical_device_; |
| VkDevice vk_device_; |
| VkQueue low_prio_vk_queue_; |
| VkQueue high_prio_vk_queue_; |
| bool different_priority_; |
| |
| VkCommandPool vk_command_pool_; |
| VkCommandBuffer low_prio_vk_command_buffer_; |
| VkCommandBuffer high_prio_vk_command_buffer_; |
| uint32_t low_priority_execution_count_ = 1000; |
| }; |
| |
| bool VkPriorityTest::Initialize() { |
| if (is_initialized_) |
| return false; |
| |
| if (!InitVulkan()) { |
| PRINT_STDERR("failed to initialize Vulkan"); |
| return false; |
| } |
| |
| if (!InitCommandPool()) { |
| PRINT_STDERR("InitCommandPool failed"); |
| return false; |
| } |
| |
| if (!InitCommandBuffer(&low_prio_vk_command_buffer_, low_priority_execution_count_)) { |
| PRINT_STDERR("InitImage failed"); |
| return false; |
| } |
| |
| if (!InitCommandBuffer(&high_prio_vk_command_buffer_, 1)) { |
| PRINT_STDERR("InitImage failed"); |
| return false; |
| } |
| |
| is_initialized_ = true; |
| |
| return true; |
| } |
| |
| bool VkPriorityTest::InitVulkan() { |
| VkInstanceCreateInfo create_info{ |
| VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // VkStructureType sType; |
| nullptr, // const void* pNext; |
| 0, // VkInstanceCreateFlags flags; |
| nullptr, // const VkApplicationInfo* pApplicationInfo; |
| 0, // uint32_t enabledLayerCount; |
| nullptr, // const char* const* ppEnabledLayerNames; |
| 0, // uint32_t enabledExtensionCount; |
| nullptr, // const char* const* ppEnabledExtensionNames; |
| }; |
| VkAllocationCallbacks* allocation_callbacks = nullptr; |
| VkInstance instance; |
| VkResult result; |
| |
| if ((result = vkCreateInstance(&create_info, allocation_callbacks, &instance)) != VK_SUCCESS) { |
| PRINT_STDERR("vkCreateInstance failed %d", result); |
| return false; |
| } |
| |
| uint32_t physical_device_count; |
| if ((result = vkEnumeratePhysicalDevices(instance, &physical_device_count, nullptr)) != |
| VK_SUCCESS) { |
| PRINT_STDERR("vkEnumeratePhysicalDevices failed %d", result); |
| return false; |
| } |
| |
| if (physical_device_count < 1) { |
| PRINT_STDERR("unexpected physical_device_count %d", physical_device_count); |
| return false; |
| } |
| |
| std::vector<VkPhysicalDevice> physical_devices(physical_device_count); |
| if ((result = vkEnumeratePhysicalDevices(instance, &physical_device_count, |
| physical_devices.data())) != VK_SUCCESS) { |
| PRINT_STDERR("vkEnumeratePhysicalDevices failed %d", result); |
| return false; |
| } |
| |
| VkPhysicalDeviceProperties properties; |
| vkGetPhysicalDeviceProperties(physical_devices[0], &properties); |
| if (properties.vendorID == 0x13b5 && properties.deviceID >= 0x1000) { |
| printf("Upping low priority execution count for ARM Bifrost GPU"); |
| // With the default execution count the test completes too quickly and |
| // the commands won't be preempted. |
| low_priority_execution_count_ = 100000; |
| } |
| |
| uint32_t queue_family_count; |
| vkGetPhysicalDeviceQueueFamilyProperties(physical_devices[0], &queue_family_count, nullptr); |
| |
| if (queue_family_count < 1) { |
| PRINT_STDERR("invalid queue_family_count %d", queue_family_count); |
| return false; |
| } |
| |
| std::vector<VkQueueFamilyProperties> queue_family_properties(queue_family_count); |
| vkGetPhysicalDeviceQueueFamilyProperties(physical_devices[0], &queue_family_count, |
| queue_family_properties.data()); |
| |
| int32_t queue_family_index = -1; |
| for (uint32_t i = 0; i < queue_family_count; i++) { |
| if (queue_family_properties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) { |
| queue_family_index = i; |
| break; |
| } |
| } |
| |
| if (queue_family_index < 0) { |
| PRINT_STDERR("couldn't find an appropriate queue"); |
| return false; |
| } |
| |
| if (queue_family_properties[queue_family_index].queueCount < 2) { |
| PRINT_STDERR("Need 2 queues to use priorities"); |
| return false; |
| } |
| |
| float queue_priorities[2] = {1.0, 1.0}; |
| if (different_priority_) { |
| queue_priorities[0] = 0.0; |
| } |
| |
| VkDeviceQueueCreateInfo queue_create_info = {.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, |
| .pNext = nullptr, |
| .flags = 0, |
| .queueFamilyIndex = 0, |
| .queueCount = 2, |
| .pQueuePriorities = queue_priorities}; |
| |
| std::vector<const char*> enabled_extension_names; |
| |
| VkDeviceCreateInfo createInfo = { |
| .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, |
| .pNext = nullptr, |
| .flags = 0, |
| .queueCreateInfoCount = 1, |
| .pQueueCreateInfos = &queue_create_info, |
| .enabledLayerCount = 0, |
| .ppEnabledLayerNames = nullptr, |
| .enabledExtensionCount = static_cast<uint32_t>(enabled_extension_names.size()), |
| .ppEnabledExtensionNames = enabled_extension_names.data(), |
| .pEnabledFeatures = nullptr}; |
| VkDevice vkdevice; |
| |
| if ((result = vkCreateDevice(physical_devices[0], &createInfo, nullptr /* allocationcallbacks */, |
| &vkdevice)) != VK_SUCCESS) { |
| PRINT_STDERR("vkCreateDevice failed: %d", result); |
| return false; |
| } |
| |
| vk_physical_device_ = physical_devices[0]; |
| vk_device_ = vkdevice; |
| |
| vkGetDeviceQueue(vkdevice, queue_family_index, 0, &low_prio_vk_queue_); |
| vkGetDeviceQueue(vkdevice, queue_family_index, 1, &high_prio_vk_queue_); |
| |
| return true; |
| } |
| |
| bool VkPriorityTest::InitCommandPool() { |
| VkCommandPoolCreateInfo command_pool_create_info = { |
| .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, |
| .pNext = nullptr, |
| .flags = 0, |
| .queueFamilyIndex = 0, |
| }; |
| VkResult result; |
| if ((result = vkCreateCommandPool(vk_device_, &command_pool_create_info, nullptr, |
| &vk_command_pool_)) != VK_SUCCESS) { |
| PRINT_STDERR("vkCreateCommandPool failed: %d", result); |
| return false; |
| } |
| return true; |
| } |
| |
| bool VkPriorityTest::InitCommandBuffer(VkCommandBuffer* command_buffer, uint32_t executions) { |
| VkCommandBufferAllocateInfo command_buffer_create_info = { |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, |
| .pNext = nullptr, |
| .commandPool = vk_command_pool_, |
| .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, |
| .commandBufferCount = 1}; |
| VkResult result; |
| if ((result = vkAllocateCommandBuffers(vk_device_, &command_buffer_create_info, |
| command_buffer)) != VK_SUCCESS) { |
| PRINT_STDERR("vkAllocateCommandBuffers failed: %d", result); |
| return false; |
| } |
| |
| VkCommandBufferBeginInfo begin_info = { |
| .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, |
| .pNext = nullptr, |
| .flags = 0, |
| .pInheritanceInfo = nullptr, // ignored for primary buffers |
| }; |
| if ((result = vkBeginCommandBuffer(*command_buffer, &begin_info)) != VK_SUCCESS) { |
| PRINT_STDERR("vkBeginCommandBuffer failed: %d", result); |
| return false; |
| } |
| |
| VkShaderModule compute_shader_module_; |
| VkShaderModuleCreateInfo sh_info = {}; |
| sh_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; |
| |
| #include "priority.comp.h" |
| sh_info.codeSize = sizeof(priority_comp); |
| sh_info.pCode = priority_comp; |
| if ((result = vkCreateShaderModule(vk_device_, &sh_info, NULL, &compute_shader_module_)) != |
| VK_SUCCESS) { |
| PRINT_STDERR("vkCreateShaderModule failed: %d", result); |
| return false; |
| } |
| |
| VkPipelineLayout layout; |
| |
| VkPipelineLayoutCreateInfo pipeline_create_info = { |
| .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, |
| .pNext = nullptr, |
| .flags = 0, |
| .setLayoutCount = 0, |
| .pSetLayouts = nullptr, |
| .pushConstantRangeCount = 0, |
| .pPushConstantRanges = nullptr}; |
| |
| if ((result = vkCreatePipelineLayout(vk_device_, &pipeline_create_info, nullptr, &layout)) != |
| VK_SUCCESS) { |
| PRINT_STDERR("vkCreatePipelineLayout failed: %d", result); |
| return false; |
| } |
| |
| VkPipeline compute_pipeline; |
| |
| VkComputePipelineCreateInfo pipeline_info = { |
| .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, |
| .pNext = nullptr, |
| .flags = 0, |
| .stage = {.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, |
| .pNext = nullptr, |
| .flags = 0, |
| .stage = VK_SHADER_STAGE_COMPUTE_BIT, |
| .module = compute_shader_module_, |
| .pName = "main", |
| .pSpecializationInfo = nullptr}, |
| .layout = layout, |
| .basePipelineHandle = VK_NULL_HANDLE, |
| .basePipelineIndex = 0}; |
| |
| if ((result = vkCreateComputePipelines(vk_device_, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, |
| &compute_pipeline)) != VK_SUCCESS) { |
| PRINT_STDERR("vkCreateComputePipelines failed: %d", result); |
| return false; |
| } |
| |
| vkCmdBindPipeline(*command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute_pipeline); |
| vkCmdDispatch(*command_buffer, 1000, executions, 10); |
| |
| if ((result = vkEndCommandBuffer(*command_buffer)) != VK_SUCCESS) { |
| PRINT_STDERR("vkEndCommandBuffer failed: %d", result); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool VkPriorityTest::Exec() { |
| VkResult result; |
| result = vkQueueWaitIdle(low_prio_vk_queue_); |
| if (result != VK_SUCCESS) { |
| PRINT_STDERR("vkQueueWaitIdle failed with result %d", result); |
| return false; |
| } |
| result = vkQueueWaitIdle(high_prio_vk_queue_); |
| if (result != VK_SUCCESS) { |
| PRINT_STDERR("vkQueueWaitIdle failed with result %d", result); |
| return false; |
| } |
| |
| VkSubmitInfo submit_info = { |
| .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, |
| .pNext = nullptr, |
| .waitSemaphoreCount = 0, |
| .pWaitSemaphores = nullptr, |
| .pWaitDstStageMask = nullptr, |
| .commandBufferCount = 1, |
| .pCommandBuffers = &low_prio_vk_command_buffer_, |
| .signalSemaphoreCount = 0, |
| .pSignalSemaphores = nullptr, |
| }; |
| |
| auto low_prio_start_time = std::chrono::steady_clock::now(); |
| if ((result = vkQueueSubmit(low_prio_vk_queue_, 1, &submit_info, VK_NULL_HANDLE)) != VK_SUCCESS) { |
| PRINT_STDERR("vkQueueSubmit failed: %d", result); |
| return false; |
| } |
| |
| std::chrono::steady_clock::time_point low_prio_end_time; |
| auto low_priority_future = std::async(std::launch::async, [this, &low_prio_end_time]() { |
| VkResult result; |
| |
| if ((result = vkQueueWaitIdle(low_prio_vk_queue_)) != VK_SUCCESS) { |
| PRINT_STDERR("vkQueueWaitIdle failed: %d", result); |
| return false; |
| } |
| low_prio_end_time = std::chrono::steady_clock::now(); |
| return true; |
| }); |
| // Should be enough time for the first queue to start executing. |
| std::this_thread::sleep_for(std::chrono::milliseconds(10)); |
| VkSubmitInfo high_prio_submit_info = { |
| .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, |
| .pNext = nullptr, |
| .waitSemaphoreCount = 0, |
| .pWaitSemaphores = nullptr, |
| .pWaitDstStageMask = nullptr, |
| .commandBufferCount = 1, |
| .pCommandBuffers = &high_prio_vk_command_buffer_, |
| .signalSemaphoreCount = 0, |
| .pSignalSemaphores = nullptr, |
| }; |
| |
| auto high_prio_start_time = std::chrono::steady_clock::now(); |
| if ((result = vkQueueSubmit(high_prio_vk_queue_, 1, &high_prio_submit_info, VK_NULL_HANDLE)) != |
| VK_SUCCESS) { |
| PRINT_STDERR("vkQueueSubmit failed: %d", result); |
| return false; |
| } |
| |
| std::chrono::steady_clock::time_point high_prio_end_time; |
| auto high_priority_future = std::async(std::launch::async, [this, &high_prio_end_time]() { |
| VkResult result; |
| if ((result = vkQueueWaitIdle(high_prio_vk_queue_)) != VK_SUCCESS) { |
| PRINT_STDERR("vkQueueWaitIdle failed: %d", result); |
| return false; |
| } |
| high_prio_end_time = std::chrono::steady_clock::now(); |
| return true; |
| }); |
| |
| high_priority_future.wait(); |
| low_priority_future.wait(); |
| if (!high_priority_future.get() || !low_priority_future.get()) { |
| PRINT_STDERR("Queue wait failed"); |
| return false; |
| } |
| auto high_prio_duration = high_prio_end_time - high_prio_start_time; |
| printf("first vkQueueWaitIdle finished duration: %lld", |
| std::chrono::duration_cast<std::chrono::milliseconds>(high_prio_duration).count()); |
| auto low_prio_duration = low_prio_end_time - low_prio_start_time; |
| printf("second vkQueueWaitIdle finished duration: %lld", |
| std::chrono::duration_cast<std::chrono::milliseconds>(low_prio_duration).count()); |
| |
| if (different_priority_) { |
| // Depends on the precise scheduling, so may sometimes fail. |
| EXPECT_LE(high_prio_duration, low_prio_duration / 10); |
| } else { |
| // In this case they actually have equal priorities, but the "low priority" one has more |
| // work and should be context-switched away from. |
| EXPECT_LE(high_prio_duration, low_prio_duration); |
| } |
| |
| return true; |
| } |
| |
| TEST(Vulkan, Priority) { |
| VkPriorityTest test(true); |
| ASSERT_TRUE(test.Initialize()); |
| ASSERT_TRUE(test.Exec()); |
| } |
| |
| TEST(Vulkan, EqualPriority) { |
| VkPriorityTest test(false); |
| ASSERT_TRUE(test.Initialize()); |
| ASSERT_TRUE(test.Exec()); |
| } |
| |
| } // namespace |