blob: 5d3e5cb6a3a8a36318f3dd40138bc0ef050863ca [file] [log] [blame]
// Copyright 2019 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 <gtest/gtest.h>
// TODO(https://fxbug.dev/42101772): support shaders as a first-class target type
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnewline-eof"
#include "src/graphics/magma/tests_linux/unit_tests/basic_compute.h"
#pragma clang diagnostic pop
#include <sstream>
#include <string>
#include <vector>
#include <vulkan/vulkan.h>
namespace {
// Allows googletest to print VkResult values.
class VkResultPrintable {
public:
VkResultPrintable(VkResult result) : result_(result) {}
VkResultPrintable& operator=(VkResult result) {
result_ = result;
return *this;
}
VkResultPrintable& operator=(const VkResultPrintable& other) {
result_ = other.result_;
return *this;
}
bool operator==(VkResult result) const { return result == result_; }
bool operator==(VkResultPrintable& other) const { return other.result_ == result_; }
friend std::ostream& operator<<(std::ostream& os, const VkResultPrintable& result) {
switch (result.result_) {
case VK_SUCCESS:
return os << "VK_SUCCESS";
case VK_NOT_READY:
return os << "VK_NOT_READY";
case VK_TIMEOUT:
return os << "VK_TIMEOUT";
case VK_EVENT_SET:
return os << "VK_EVENT_SET";
case VK_EVENT_RESET:
return os << "VK_EVENT_RESET";
case VK_INCOMPLETE:
return os << "VK_INCOMPLETE";
case VK_ERROR_OUT_OF_HOST_MEMORY:
return os << "VK_ERROR_OUT_OF_HOST_MEMORY";
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
return os << "VK_ERROR_OUT_OF_DEVICE_MEMORY";
case VK_ERROR_INITIALIZATION_FAILED:
return os << "VK_ERROR_INITIALIZATION_FAILED";
case VK_ERROR_DEVICE_LOST:
return os << "VK_ERROR_DEVICE_LOST";
case VK_ERROR_MEMORY_MAP_FAILED:
return os << "VK_ERROR_MEMORY_MAP_FAILED";
case VK_ERROR_LAYER_NOT_PRESENT:
return os << "VK_ERROR_LAYER_NOT_PRESENT";
case VK_ERROR_EXTENSION_NOT_PRESENT:
return os << "VK_ERROR_EXTENSION_NOT_PRESENT";
case VK_ERROR_FEATURE_NOT_PRESENT:
return os << "VK_ERROR_FEATURE_NOT_PRESENT";
case VK_ERROR_INCOMPATIBLE_DRIVER:
return os << "VK_ERROR_INCOMPATIBLE_DRIVER";
case VK_ERROR_TOO_MANY_OBJECTS:
return os << "VK_ERROR_TOO_MANY_OBJECTS";
case VK_ERROR_FORMAT_NOT_SUPPORTED:
return os << "VK_ERROR_FORMAT_NOT_SUPPORTED";
case VK_ERROR_FRAGMENTED_POOL:
return os << "VK_ERROR_FRAGMENTED_POOL";
case VK_ERROR_OUT_OF_POOL_MEMORY:
return os << "VK_ERROR_OUT_OF_POOL_MEMORY";
case VK_ERROR_INVALID_EXTERNAL_HANDLE:
return os << "VK_ERROR_INVALID_EXTERNAL_HANDLE";
case VK_ERROR_SURFACE_LOST_KHR:
return os << "VK_ERROR_SURFACE_LOST_KHR";
case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
return os << "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
case VK_SUBOPTIMAL_KHR:
return os << "VK_SUBOPTIMAL_KHR";
case VK_ERROR_OUT_OF_DATE_KHR:
return os << "VK_ERROR_OUT_OF_DATE_KHR";
case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:
return os << "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR";
case VK_ERROR_VALIDATION_FAILED_EXT:
return os << "VK_ERROR_VALIDATION_FAILED_EXT";
case VK_ERROR_INVALID_SHADER_NV:
return os << "VK_ERROR_INVALID_SHADER_NV";
case VK_ERROR_FRAGMENTATION_EXT:
return os << "VK_ERROR_FRAGMENTATION_EXT";
case VK_ERROR_NOT_PERMITTED_EXT:
return os << "VK_ERROR_NOT_PERMITTED_EXT";
default:
return os << "UNKNOWN (" << static_cast<int32_t>(result.result_) << ")";
}
}
private:
VkResult result_;
};
struct VulkanPhysicalDevice {
VkPhysicalDevice device;
VkPhysicalDeviceProperties properties;
std::vector<VkQueueFamilyProperties> queues;
};
class VirtMagmaTest : public ::testing::Test {
protected:
VirtMagmaTest() {}
~VirtMagmaTest() override {}
void SetUp() override {
CreateInstance();
EnumeratePhysicalDevices();
GetQueues();
}
void TearDown() override {
physical_devices_.clear(); // Implicitly destroyed with instance.
vkDestroyInstance(instance_, nullptr);
}
void CreateInstance() {
VkApplicationInfo application_info{};
application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
application_info.pApplicationName = "fuchsia-test";
application_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
application_info.pEngineName = "no-engine";
application_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
application_info.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo instance_create_info{};
instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_create_info.pApplicationInfo = &application_info;
VkResultPrintable result = vkCreateInstance(&instance_create_info, nullptr, &instance_);
ASSERT_EQ(result, VK_SUCCESS) << "vkCreateInstance failed";
}
void EnumeratePhysicalDevices() {
uint32_t physical_device_count = 0;
std::vector<VkPhysicalDevice> physical_devices;
VkResultPrintable result =
vkEnumeratePhysicalDevices(instance_, &physical_device_count, nullptr);
ASSERT_EQ(result, VK_SUCCESS) << "vkEnumeratePhysicalDevices failed";
physical_devices.resize(physical_device_count);
while ((result = vkEnumeratePhysicalDevices(instance_, &physical_device_count,
physical_devices.data())) == VK_INCOMPLETE) {
physical_devices.resize(++physical_device_count);
}
ASSERT_EQ(result, VK_SUCCESS) << "vkEnumeratePhysicalDevices failed";
ASSERT_GT(physical_devices.size(), 0u) << "No physical devices found";
physical_devices_.resize(physical_device_count);
for (uint32_t i = 0; i < physical_device_count; ++i) {
VkPhysicalDeviceProperties properties{};
vkGetPhysicalDeviceProperties(physical_devices[i], &properties);
EXPECT_NE(properties.vendorID, 0u) << "Missing vendor ID";
EXPECT_NE(properties.deviceID, 0u) << "Missing device ID";
EXPECT_LE(properties.vendorID, 0xFFFFu) << "Invalid vendor ID";
EXPECT_LE(properties.deviceID, 0xFFFFu) << "Invalid device ID";
physical_devices_[i].device = physical_devices[i];
physical_devices_[i].properties = properties;
}
}
void GetQueues() {
for (auto& device : physical_devices_) {
uint32_t queue_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device.device, &queue_count, nullptr);
ASSERT_GT(queue_count, 0u) << "No queue families found";
device.queues.resize(queue_count);
vkGetPhysicalDeviceQueueFamilyProperties(device.device, &queue_count, device.queues.data());
uint32_t queue_flags_union = 0;
for (auto& queue : device.queues) {
ASSERT_GT(queue.queueCount, 0u) << "Empty queue family";
queue_flags_union |= queue.queueFlags;
}
ASSERT_TRUE(queue_flags_union & VK_QUEUE_GRAPHICS_BIT)
<< "Device missing graphics capability";
ASSERT_TRUE(queue_flags_union & VK_QUEUE_COMPUTE_BIT) << "Device missing compute capability";
}
}
VkInstance instance_;
std::vector<VulkanPhysicalDevice> physical_devices_;
};
// Tests that a device can be created on the first reported graphics queue.
TEST_F(VirtMagmaTest, CreateGraphicsDevice) {
// TODO(https://fxbug.dev/42082331): support per-device gtests
for (auto& physical_device : physical_devices_) {
VkDeviceQueueCreateInfo device_queue_create_info{};
device_queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
for (uint32_t i = 0; i < physical_device.queues.size(); ++i) {
if (physical_device.queues[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
device_queue_create_info.queueFamilyIndex = i;
break;
}
}
device_queue_create_info.queueCount = 1;
float priority = 1.0f;
device_queue_create_info.pQueuePriorities = &priority;
VkDeviceCreateInfo device_create_info{};
device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_create_info.queueCreateInfoCount = 1;
device_create_info.pQueueCreateInfos = &device_queue_create_info;
VkDevice device{};
VkResultPrintable result =
vkCreateDevice(physical_device.device, &device_create_info, nullptr, &device);
ASSERT_EQ(result, VK_SUCCESS) << "vkCreateDevice failed";
vkDestroyDevice(device, nullptr);
}
}
// Tests that the device can run a basic compute shader.
TEST_F(VirtMagmaTest, BasicCompute) {
static const size_t kBufferSize = 65536;
static const uint32_t kGroupSize = 32; // This must match basic_compute.glsl
const uint32_t num_elements = kBufferSize / sizeof(uint32_t);
const uint32_t num_groups = num_elements / kGroupSize;
// TODO(https://fxbug.dev/42082331): support per-device gtests
for (auto& physical_device : physical_devices_) {
VkDeviceQueueCreateInfo device_queue_create_info{};
device_queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
for (uint32_t i = 0; i < physical_device.queues.size(); ++i) {
if (physical_device.queues[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
device_queue_create_info.queueFamilyIndex = i;
break;
}
}
device_queue_create_info.queueCount = 1;
float priority = 1.0f;
device_queue_create_info.pQueuePriorities = &priority;
VkDeviceCreateInfo device_create_info{};
device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_create_info.queueCreateInfoCount = 1;
device_create_info.pQueueCreateInfos = &device_queue_create_info;
VkDevice device{};
VkResultPrintable result =
vkCreateDevice(physical_device.device, &device_create_info, nullptr, &device);
ASSERT_EQ(result, VK_SUCCESS) << "vkCreateDevice failed";
VkQueue queue{};
vkGetDeviceQueue(device, device_queue_create_info.queueFamilyIndex, 0, &queue);
VkPhysicalDeviceMemoryProperties physical_device_memory_properties{};
vkGetPhysicalDeviceMemoryProperties(physical_device.device, &physical_device_memory_properties);
int32_t memory_type_index = -1;
for (uint32_t i = 0; i < physical_device_memory_properties.memoryTypeCount; ++i) {
const auto& memory_type = physical_device_memory_properties.memoryTypes[i];
if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) &&
(memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) &&
(physical_device_memory_properties.memoryHeaps[memory_type.heapIndex].size >=
kBufferSize)) {
memory_type_index = i;
break;
}
}
ASSERT_NE(memory_type_index, -1) << "Suitable memory heap not found";
VkMemoryAllocateInfo memory_allocate_info{};
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocate_info.allocationSize = kBufferSize;
memory_allocate_info.memoryTypeIndex = memory_type_index;
VkDeviceMemory device_memory{};
result = vkAllocateMemory(device, &memory_allocate_info, nullptr, &device_memory);
ASSERT_EQ(result, VK_SUCCESS) << "vkAllocateMemory failed";
VkBufferCreateInfo buffer_create_info{};
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_create_info.size = kBufferSize;
buffer_create_info.usage =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
VkBuffer buffer{};
result = vkCreateBuffer(device, &buffer_create_info, nullptr, &buffer);
ASSERT_EQ(result, VK_SUCCESS) << "vkCreateBuffer failed";
result = vkBindBufferMemory(device, buffer, device_memory, 0);
ASSERT_EQ(result, VK_SUCCESS) << "vkBindBufferMemory failed";
VkShaderModuleCreateInfo shader_module_create_info{};
shader_module_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shader_module_create_info.codeSize = sizeof(basic_compute_spirv);
shader_module_create_info.pCode = basic_compute_spirv;
VkShaderModule shader_module{};
result = vkCreateShaderModule(device, &shader_module_create_info, nullptr, &shader_module);
ASSERT_EQ(result, VK_SUCCESS) << "vkCreateShaderModule failed";
VkDescriptorSetLayoutBinding descriptor_set_layout_binding{};
descriptor_set_layout_binding.binding = 0;
descriptor_set_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptor_set_layout_binding.descriptorCount = 1;
descriptor_set_layout_binding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info{};
descriptor_set_layout_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptor_set_layout_create_info.bindingCount = 1;
descriptor_set_layout_create_info.pBindings = &descriptor_set_layout_binding;
VkDescriptorSetLayout descriptor_set_layout{};
result = vkCreateDescriptorSetLayout(device, &descriptor_set_layout_create_info, nullptr,
&descriptor_set_layout);
ASSERT_EQ(result, VK_SUCCESS) << "vkCreateDescriptorSetLayout failed";
VkDescriptorPoolSize pool_size{};
pool_size.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
pool_size.descriptorCount = 1;
VkDescriptorPoolCreateInfo descriptor_pool_create_info{};
descriptor_pool_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptor_pool_create_info.maxSets = 1;
descriptor_pool_create_info.poolSizeCount = 1;
descriptor_pool_create_info.pPoolSizes = &pool_size;
VkDescriptorPool descriptor_pool{};
result = vkCreateDescriptorPool(device, &descriptor_pool_create_info, 0, &descriptor_pool);
ASSERT_EQ(result, VK_SUCCESS) << "vkCreateDescriptorPool failed";
VkDescriptorSetAllocateInfo descriptor_set_allocate_info{};
descriptor_set_allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptor_set_allocate_info.descriptorPool = descriptor_pool;
descriptor_set_allocate_info.descriptorSetCount = 1;
descriptor_set_allocate_info.pSetLayouts = &descriptor_set_layout;
VkDescriptorSet descriptor_set{};
result = vkAllocateDescriptorSets(device, &descriptor_set_allocate_info, &descriptor_set);
ASSERT_EQ(result, VK_SUCCESS) << "vkAllocateDescriptorSets failed";
VkDescriptorBufferInfo descriptor_buffer_info{};
descriptor_buffer_info.buffer = buffer;
descriptor_buffer_info.range = VK_WHOLE_SIZE;
VkWriteDescriptorSet write_descriptor_set{};
write_descriptor_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write_descriptor_set.dstSet = descriptor_set;
write_descriptor_set.descriptorCount = 1;
write_descriptor_set.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
write_descriptor_set.pBufferInfo = &descriptor_buffer_info;
vkUpdateDescriptorSets(device, 1, &write_descriptor_set, 0, nullptr);
VkPipelineLayoutCreateInfo pipeline_layout_create_info{};
pipeline_layout_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipeline_layout_create_info.setLayoutCount = 1;
pipeline_layout_create_info.pSetLayouts = &descriptor_set_layout;
VkPipelineLayout pipeline_layout{};
result =
vkCreatePipelineLayout(device, &pipeline_layout_create_info, nullptr, &pipeline_layout);
ASSERT_EQ(result, VK_SUCCESS) << "vkCreatePipelineLayout failed";
VkComputePipelineCreateInfo compute_pipeline_create_info{};
compute_pipeline_create_info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
compute_pipeline_create_info.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
compute_pipeline_create_info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
compute_pipeline_create_info.stage.module = shader_module;
compute_pipeline_create_info.stage.pName = "main";
compute_pipeline_create_info.layout = pipeline_layout;
VkPipeline pipeline{};
result = vkCreateComputePipelines(device, nullptr, 1, &compute_pipeline_create_info, nullptr,
&pipeline);
ASSERT_EQ(result, VK_SUCCESS) << "vkCreateComputePipelines failed";
VkCommandPoolCreateInfo command_pool_create_info{};
command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
command_pool_create_info.queueFamilyIndex = device_queue_create_info.queueFamilyIndex;
VkCommandPool command_pool{};
result = vkCreateCommandPool(device, &command_pool_create_info, nullptr, &command_pool);
ASSERT_EQ(result, VK_SUCCESS) << "vkCreateCommandPool failed";
VkCommandBufferAllocateInfo command_buffer_allocate_info{};
command_buffer_allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
command_buffer_allocate_info.commandPool = command_pool;
command_buffer_allocate_info.commandBufferCount = 1;
VkCommandBuffer command_buffer{};
result = vkAllocateCommandBuffers(device, &command_buffer_allocate_info, &command_buffer);
ASSERT_EQ(result, VK_SUCCESS) << "vkAllocateCommandBuffers failed";
VkCommandBufferBeginInfo command_buffer_begin_info{};
command_buffer_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
command_buffer_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
result = vkBeginCommandBuffer(command_buffer, &command_buffer_begin_info);
ASSERT_EQ(result, VK_SUCCESS) << "vkBeginCommandBuffer failed";
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1,
&descriptor_set, 0, nullptr);
vkCmdDispatch(command_buffer, num_groups, 1, 1);
result = vkEndCommandBuffer(command_buffer);
ASSERT_EQ(result, VK_SUCCESS) << "vkEndCommandBuffer failed";
VkSubmitInfo submit_info{};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &command_buffer;
result = vkQueueSubmit(queue, 1, &submit_info, nullptr);
ASSERT_EQ(result, VK_SUCCESS) << "vkQueueSubmit failed";
result = vkQueueWaitIdle(queue);
ASSERT_EQ(result, VK_SUCCESS) << "vkQueueWaitIdle failed";
void* mapped_data = nullptr;
result = vkMapMemory(device, device_memory, 0, kBufferSize, 0, &mapped_data);
ASSERT_EQ(result, VK_SUCCESS) << "vkMapMemory failed";
auto buffer_data = reinterpret_cast<uint32_t*>(mapped_data);
uint32_t correct_data_count = 0;
for (uint32_t i = 0; i < num_elements; ++i) {
uint32_t expected_value = i;
if (buffer_data[i] == expected_value) {
++correct_data_count;
}
}
EXPECT_EQ(correct_data_count, num_elements) << "Buffer does not contain the correct data";
vkUnmapMemory(device, device_memory);
vkDestroyCommandPool(device, command_pool, nullptr);
vkDestroyPipeline(device, pipeline, nullptr);
vkDestroyPipelineLayout(device, pipeline_layout, nullptr);
vkDestroyDescriptorPool(device, descriptor_pool, nullptr);
vkDestroyDescriptorSetLayout(device, descriptor_set_layout, nullptr);
vkDestroyShaderModule(device, shader_module, nullptr);
vkDestroyBuffer(device, buffer, nullptr);
vkFreeMemory(device, device_memory, nullptr);
vkDestroyDevice(device, nullptr);
}
}
} // namespace