348 lines
13 KiB
C++
348 lines
13 KiB
C++
|
|
// copyright (c) 2022 the android open source project
|
||
|
|
//
|
||
|
|
// 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 "VulkanTestHelper.h"
|
||
|
|
|
||
|
|
#include "host-common/emugl_vm_operations.h"
|
||
|
|
#include "host-common/feature_control.h"
|
||
|
|
#include "host-common/logging.h"
|
||
|
|
#include "host-common/vm_operations.h"
|
||
|
|
|
||
|
|
namespace gfxstream {
|
||
|
|
namespace vk {
|
||
|
|
namespace testing {
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
using ::android::base::BumpPool;
|
||
|
|
|
||
|
|
bool validationErrorsFound = false;
|
||
|
|
|
||
|
|
// Called back by the Vulkan validation layer in case of a validation error
|
||
|
|
VKAPI_ATTR VkBool32 VKAPI_CALL validationCallback(
|
||
|
|
VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type,
|
||
|
|
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) {
|
||
|
|
if (severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
|
||
|
|
ERR("Validation Layer: \"%s\"", pCallbackData->pMessage);
|
||
|
|
validationErrorsFound = true;
|
||
|
|
}
|
||
|
|
return VK_FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
std::mutex VulkanTestHelper::mMutex;
|
||
|
|
|
||
|
|
VulkanTestHelper::VulkanTestHelper()
|
||
|
|
: mLock(mMutex),
|
||
|
|
mVk(vkDispatch(/*forTesting=*/true)),
|
||
|
|
mLogger(),
|
||
|
|
mMetricsLogger(android::base::CreateMetricsLogger()),
|
||
|
|
mHealthMonitor(*mMetricsLogger),
|
||
|
|
mVkEmu(createGlobalVkEmulation(mVk)),
|
||
|
|
mBp(std::make_unique<BumpPool>()),
|
||
|
|
mDecoderContext(VkDecoderContext{.processName = "vulkan_test",
|
||
|
|
.gfxApiLogger = &mLogger,
|
||
|
|
.healthMonitor = &mHealthMonitor,
|
||
|
|
.metricsLogger = mMetricsLogger.get()}),
|
||
|
|
mTestDispatch(mVk, mBp.get(), &mDecoderContext) {
|
||
|
|
// Enable so that we can have VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
|
||
|
|
feature_set_enabled_override(kFeature_GLDirectMem, true);
|
||
|
|
|
||
|
|
// This is used by VkDecoderGlobalState::on_vkCreateInstance()
|
||
|
|
QAndroidVmOperations vmOps;
|
||
|
|
vmOps.setSkipSnapshotSave = [](bool) {};
|
||
|
|
set_emugl_vm_operations(vmOps);
|
||
|
|
|
||
|
|
validationErrorsFound = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
void VulkanTestHelper::destroy() {
|
||
|
|
if (mDevice) {
|
||
|
|
vk().vkDeviceWaitIdle(mDevice);
|
||
|
|
if (mCommandPool) vk().vkDestroyCommandPool(mDevice, mCommandPool, nullptr);
|
||
|
|
vk().vkDestroyDevice(mDevice, nullptr);
|
||
|
|
}
|
||
|
|
if (mInstance) {
|
||
|
|
if (mDebugMessenger) {
|
||
|
|
vk().vkDestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, nullptr);
|
||
|
|
}
|
||
|
|
vk().vkDestroyInstance(mInstance, nullptr);
|
||
|
|
}
|
||
|
|
|
||
|
|
mCommandPool = VK_NULL_HANDLE;
|
||
|
|
mDevice = VK_NULL_HANDLE;
|
||
|
|
mInstance = VK_NULL_HANDLE;
|
||
|
|
mDebugMessenger = VK_NULL_HANDLE;
|
||
|
|
|
||
|
|
VkDecoderGlobalState::reset();
|
||
|
|
teardownGlobalVkEmulation();
|
||
|
|
}
|
||
|
|
|
||
|
|
VulkanTestHelper::~VulkanTestHelper() {
|
||
|
|
destroy();
|
||
|
|
if (mFailOnValidationErrors && validationErrorsFound) {
|
||
|
|
FATAL() << "Validation errors found. Aborting.";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void VulkanTestHelper::initialize(const InitializationOptions& options) {
|
||
|
|
initVkEmulationFeatures(std::make_unique<VkEmulationFeatures>(VkEmulationFeatures{
|
||
|
|
.astcLdrEmulationMode = options.astcLdrEmulationMode,
|
||
|
|
}));
|
||
|
|
|
||
|
|
// Check that the validation layer is present
|
||
|
|
const char* validationLayer = "VK_LAYER_KHRONOS_validation";
|
||
|
|
uint32_t layerCount;
|
||
|
|
vk().vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
|
||
|
|
std::vector<VkLayerProperties> availableLayers(layerCount);
|
||
|
|
vk().vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
|
||
|
|
|
||
|
|
bool layerFound = false;
|
||
|
|
for (const auto& layerProperties : availableLayers) {
|
||
|
|
if (strcmp(validationLayer, layerProperties.layerName) == 0) {
|
||
|
|
layerFound = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!layerFound) FATAL() << "Vulkan Validation Layer not found";
|
||
|
|
|
||
|
|
// Create the instance
|
||
|
|
VkApplicationInfo defaultAppInfo = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||
|
|
.pApplicationName = "vulkan_test",
|
||
|
|
.pEngineName = "vulkan_test",
|
||
|
|
.apiVersion = VK_API_VERSION_1_1,
|
||
|
|
};
|
||
|
|
|
||
|
|
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
||
|
|
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
|
||
|
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
|
||
|
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
|
||
|
|
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
|
||
|
|
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
|
||
|
|
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
|
||
|
|
.pfnUserCallback = validationCallback,
|
||
|
|
};
|
||
|
|
|
||
|
|
std::vector<const char*> extensions = {VK_EXT_DEBUG_UTILS_EXTENSION_NAME};
|
||
|
|
for (const auto& extName : options.enabledExtensions) {
|
||
|
|
extensions.push_back(extName.c_str());
|
||
|
|
}
|
||
|
|
|
||
|
|
VkInstanceCreateInfo createInfo = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||
|
|
.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debugCreateInfo,
|
||
|
|
.pApplicationInfo = options.appInfo ? &options.appInfo.value() : &defaultAppInfo,
|
||
|
|
.enabledLayerCount = 1,
|
||
|
|
.ppEnabledLayerNames = &validationLayer,
|
||
|
|
.enabledExtensionCount = static_cast<uint32_t>(extensions.size()),
|
||
|
|
.ppEnabledExtensionNames = extensions.data(),
|
||
|
|
};
|
||
|
|
VK_CHECK(vk().vkCreateInstance(&createInfo, nullptr, &mInstance));
|
||
|
|
|
||
|
|
// Setup validation layer callbacks
|
||
|
|
VK_CHECK(vk().vkCreateDebugUtilsMessengerEXT(mInstance, &debugCreateInfo, nullptr,
|
||
|
|
&mDebugMessenger));
|
||
|
|
|
||
|
|
// Pick a physical device
|
||
|
|
uint32_t deviceCount = 0;
|
||
|
|
vk().vkEnumeratePhysicalDevices(mInstance, &deviceCount, nullptr);
|
||
|
|
if (deviceCount == 0) FATAL() << "No Vulkan device found.";
|
||
|
|
std::vector<VkPhysicalDevice> devices(deviceCount);
|
||
|
|
VK_CHECK(vk().vkEnumeratePhysicalDevices(mInstance, &deviceCount, devices.data()));
|
||
|
|
|
||
|
|
mPhysicalDevice = devices[0];
|
||
|
|
assert(mPhysicalDevice != VK_NULL_HANDLE);
|
||
|
|
|
||
|
|
// Create the logical device
|
||
|
|
float queuePriority = 1.0f;
|
||
|
|
VkDeviceQueueCreateInfo queueCreateInfo = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||
|
|
.queueFamilyIndex = getQueueFamilyIndex(VK_QUEUE_GRAPHICS_BIT),
|
||
|
|
.queueCount = 1,
|
||
|
|
.pQueuePriorities = &queuePriority,
|
||
|
|
};
|
||
|
|
|
||
|
|
VkDeviceCreateInfo deviceCreateInfo = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||
|
|
.queueCreateInfoCount = 1,
|
||
|
|
.pQueueCreateInfos = &queueCreateInfo,
|
||
|
|
.enabledLayerCount = 1,
|
||
|
|
.ppEnabledLayerNames = &validationLayer,
|
||
|
|
.pEnabledFeatures = &options.deviceFeatures,
|
||
|
|
};
|
||
|
|
VK_CHECK(vk().vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice));
|
||
|
|
|
||
|
|
// Get a graphics queue
|
||
|
|
vk().vkGetDeviceQueue(mDevice, queueCreateInfo.queueFamilyIndex, 0, &mGraphicsQueue);
|
||
|
|
assert(mGraphicsQueue != VK_NULL_HANDLE);
|
||
|
|
|
||
|
|
// Create command pool
|
||
|
|
VkCommandPoolCreateInfo poolInfo{
|
||
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||
|
|
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||
|
|
.queueFamilyIndex = queueCreateInfo.queueFamilyIndex,
|
||
|
|
};
|
||
|
|
VK_CHECK(vk().vkCreateCommandPool(mDevice, &poolInfo, nullptr, &mCommandPool));
|
||
|
|
}
|
||
|
|
|
||
|
|
bool VulkanTestHelper::hasValidationErrors() const { return validationErrorsFound; }
|
||
|
|
|
||
|
|
VkCommandBuffer VulkanTestHelper::beginCommandBuffer() {
|
||
|
|
VkCommandBufferAllocateInfo allocInfo = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||
|
|
.commandPool = unbox_VkCommandPool(mCommandPool),
|
||
|
|
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||
|
|
.commandBufferCount = 1,
|
||
|
|
};
|
||
|
|
VkCommandBuffer commandBuffer;
|
||
|
|
vk().vkAllocateCommandBuffers(mDevice, &allocInfo, &commandBuffer);
|
||
|
|
|
||
|
|
VkCommandBufferBeginInfo beginInfo = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||
|
|
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||
|
|
};
|
||
|
|
vk().vkBeginCommandBuffer(commandBuffer, &beginInfo);
|
||
|
|
return commandBuffer;
|
||
|
|
}
|
||
|
|
|
||
|
|
void VulkanTestHelper::submitCommandBuffer(VkCommandBuffer commandBuffer) {
|
||
|
|
vk().vkEndCommandBuffer(commandBuffer);
|
||
|
|
auto cmdBuf = unbox_VkCommandBuffer(commandBuffer);
|
||
|
|
|
||
|
|
VkSubmitInfo submitInfo = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||
|
|
.commandBufferCount = 1,
|
||
|
|
.pCommandBuffers = &cmdBuf,
|
||
|
|
};
|
||
|
|
vk().vkQueueSubmit(mGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
|
||
|
|
vk().vkQueueWaitIdle(mGraphicsQueue);
|
||
|
|
vk().vkFreeCommandBuffers(mDevice, mCommandPool, 1, &cmdBuf);
|
||
|
|
}
|
||
|
|
|
||
|
|
uint32_t VulkanTestHelper::getQueueFamilyIndex(VkQueueFlagBits queueFlags) {
|
||
|
|
uint32_t queueFamilyCount = 0;
|
||
|
|
vk().vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyCount, nullptr);
|
||
|
|
|
||
|
|
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
||
|
|
vk().vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyCount,
|
||
|
|
queueFamilies.data());
|
||
|
|
for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilies.size()); i++) {
|
||
|
|
if (queueFamilies[i].queueFlags & queueFlags) {
|
||
|
|
return i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
FATAL() << "No queue family found matching the requested flags";
|
||
|
|
}
|
||
|
|
|
||
|
|
uint32_t VulkanTestHelper::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
|
||
|
|
VkPhysicalDeviceMemoryProperties memProperties;
|
||
|
|
vk().vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProperties);
|
||
|
|
|
||
|
|
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
|
||
|
|
if ((typeFilter & (1 << i)) &&
|
||
|
|
(memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
|
||
|
|
return i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
FATAL() << "failed to find suitable memory type!";
|
||
|
|
}
|
||
|
|
|
||
|
|
void VulkanTestHelper::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
|
||
|
|
VkMemoryPropertyFlags properties, VkBuffer& buffer,
|
||
|
|
VkDeviceMemory& bufferMemory) {
|
||
|
|
VkBufferCreateInfo bufferInfo = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||
|
|
.size = size,
|
||
|
|
.usage = usage,
|
||
|
|
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||
|
|
};
|
||
|
|
VK_CHECK(vk().vkCreateBuffer(mDevice, &bufferInfo, nullptr, &buffer));
|
||
|
|
|
||
|
|
VkMemoryRequirements memRequirements;
|
||
|
|
vk().vkGetBufferMemoryRequirements(mDevice, buffer, &memRequirements);
|
||
|
|
|
||
|
|
VkMemoryAllocateInfo allocInfo = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||
|
|
.allocationSize = memRequirements.size,
|
||
|
|
.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties),
|
||
|
|
};
|
||
|
|
VK_CHECK(vk().vkAllocateMemory(mDevice, &allocInfo, nullptr, &bufferMemory));
|
||
|
|
|
||
|
|
vk().vkBindBufferMemory(mDevice, buffer, bufferMemory, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void VulkanTestHelper::transitionImageLayout(VkCommandBuffer cmdBuf, VkImage image,
|
||
|
|
VkImageLayout oldLayout, VkImageLayout newLayout) {
|
||
|
|
VkImageMemoryBarrier barrier = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||
|
|
.oldLayout = oldLayout,
|
||
|
|
.newLayout = newLayout,
|
||
|
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||
|
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||
|
|
.image = unbox_VkImage(image),
|
||
|
|
.subresourceRange =
|
||
|
|
{
|
||
|
|
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||
|
|
.baseMipLevel = 0,
|
||
|
|
.levelCount = 1,
|
||
|
|
.baseArrayLayer = 0,
|
||
|
|
.layerCount = 1,
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
switch (oldLayout) {
|
||
|
|
case VK_IMAGE_LAYOUT_UNDEFINED:
|
||
|
|
barrier.srcAccessMask = 0;
|
||
|
|
break;
|
||
|
|
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||
|
|
break;
|
||
|
|
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||
|
|
break;
|
||
|
|
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||
|
|
barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
FATAL() << "Unsupported layout transition!";
|
||
|
|
}
|
||
|
|
|
||
|
|
switch (newLayout) {
|
||
|
|
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||
|
|
break;
|
||
|
|
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||
|
|
break;
|
||
|
|
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||
|
|
if (barrier.srcAccessMask == 0) {
|
||
|
|
barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||
|
|
}
|
||
|
|
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
FATAL() << "Unsupported layout transition!";
|
||
|
|
}
|
||
|
|
vk().vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||
|
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1,
|
||
|
|
&barrier);
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace testing
|
||
|
|
} // namespace vk
|
||
|
|
} // namespace gfxstream
|