blob: a9e0fd0725c9aa03e9bc7b9a454ffaedf77fc8fb [file] [log] [blame]
// Copyright (C) 2018 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 <gtest/gtest.h>
#include "GoldfishOpenglTestEnv.h"
#include "GrallocDispatch.h"
#include "GrallocUsageConversion.h"
#include "AndroidVulkanDispatch.h"
#include <vulkan/vulkan.h>
#include <vulkan/vulkan_android.h>
#include <vulkan/vk_android_native_buffer.h>
#include "android/base/ArraySize.h"
#include "android/base/files/MemStream.h"
#include "android/base/files/Stream.h"
#include "android/base/files/PathUtils.h"
#include "android/base/memory/ScopedPtr.h"
#include "android/base/synchronization/ConditionVariable.h"
#include "android/base/synchronization/Lock.h"
#include "android/base/system/System.h"
#include "android/base/threads/FunctorThread.h"
#include "android/opengles.h"
#include "android/snapshot/interface.h"
#include "OpenglSystemCommon/HostConnection.h"
#include "OpenglSystemCommon/ProcessPipe.h"
#include <android/hardware_buffer.h>
#include <cutils/properties.h>
#include <atomic>
#include <random>
#include <memory>
#include <vector>
using android::base::AutoLock;
using android::base::ConditionVariable;
using android::base::FunctorThread;
using android::base::Lock;
using android::base::pj;
using android::base::System;
namespace aemu {
static constexpr int kWindowSize = 256;
static android_vulkan_dispatch* vk = nullptr;
class VulkanHalTest :
public ::testing::Test,
public ::testing::WithParamInterface<const char*> {
protected:
static GoldfishOpenglTestEnv* testEnv;
static void SetUpTestCase() {
testEnv = new GoldfishOpenglTestEnv;
#ifdef _WIN32
const char* libFilename = "vulkan_android.dll";
#elif defined(__APPLE__)
const char* libFilename = "libvulkan_android.dylib";
#else
const char* libFilename = "libvulkan_android.so";
#endif
auto path =
pj(System::get()->getProgramDirectory(),
"lib64", libFilename);
vk = load_android_vulkan_dispatch(path.c_str());
}
static void TearDownTestCase() {
// Cancel all host threads as well
android_finishOpenglesRenderer();
delete testEnv;
testEnv = nullptr;
delete vk;
}
static constexpr kGltransportPropName = "ro.boot.qemu.gltransport";
bool usingAddressSpaceGraphics() {
char value[PROPERTY_VALUE_MAX];
if (property_get(
kGltransportPropName, value, "pipe") > 0) {
return !strcmp("asg", value);
}
return false;
}
void SetUp() override {
mProcessPipeRestarted = false;
property_set(kGltransportPropName, GetParam());
printf("%s: using transport: %s\n", __func__, GetParam());
setupGralloc();
setupVulkan();
}
void TearDown() override {
teardownVulkan();
teardownGralloc();
}
void restartProcessPipeAndHostConnection() {
processPipeRestart();
cutHostConnectionUnclean();
refreshHostConnection();
}
void cutHostConnectionUnclean() {
HostConnection::exitUnclean();
}
void setupGralloc() {
auto grallocPath = pj(System::get()->getProgramDirectory(), "lib64",
"gralloc.ranchu" LIBSUFFIX);
load_gralloc_module(grallocPath.c_str(), &mGralloc);
set_global_gralloc_module(&mGralloc);
EXPECT_NE(nullptr, mGralloc.alloc_dev);
EXPECT_NE(nullptr, mGralloc.alloc_module);
}
void teardownGralloc() { unload_gralloc_module(&mGralloc); }
buffer_handle_t createTestGrallocBuffer(
int usage, int format,
int width, int height, int* stride_out) {
buffer_handle_t buffer;
int res;
res = mGralloc.alloc(width, height, format, usage, &buffer, stride_out);
if (res) {
fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer);
::abort();
}
res = mGralloc.registerBuffer(buffer);
if (res) {
fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer);
::abort();
}
return buffer;
}
void destroyTestGrallocBuffer(buffer_handle_t buffer) {
int res;
res = mGralloc.unregisterBuffer(buffer);
if (res) {
fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer);
::abort();
}
res = mGralloc.free(buffer);
if (res) {
fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer);
::abort();
}
}
void setupVulkan() {
uint32_t extCount = 0;
std::vector<VkExtensionProperties> exts;
EXPECT_EQ(VK_SUCCESS, vk->vkEnumerateInstanceExtensionProperties(
nullptr, &extCount, nullptr));
exts.resize(extCount);
EXPECT_EQ(VK_SUCCESS, vk->vkEnumerateInstanceExtensionProperties(
nullptr, &extCount, exts.data()));
bool hasGetPhysicalDeviceProperties2 = false;
bool hasExternalMemoryCapabilities = false;
for (const auto& prop : exts) {
if (!strcmp("VK_KHR_get_physical_device_properties2", prop.extensionName)) {
hasGetPhysicalDeviceProperties2 = true;
}
if (!strcmp("VK_KHR_external_memory_capabilities", prop.extensionName)) {
hasExternalMemoryCapabilities = true;
}
}
std::vector<const char*> enabledExtensions;
if (hasGetPhysicalDeviceProperties2) {
enabledExtensions.push_back("VK_KHR_get_physical_device_properties2");
mInstanceHasGetPhysicalDeviceProperties2Support = true;
}
if (hasExternalMemoryCapabilities) {
enabledExtensions.push_back("VK_KHR_external_memory_capabilities");
mInstanceHasExternalMemorySupport = true;
}
const char* const* enabledExtensionNames =
enabledExtensions.size() > 0 ? enabledExtensions.data()
: nullptr;
VkApplicationInfo appInfo = {
VK_STRUCTURE_TYPE_APPLICATION_INFO, 0,
"someAppName", 1,
"someEngineName", 1,
VK_VERSION_1_0,
};
VkInstanceCreateInfo instCi = {
VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
0, 0, &appInfo,
0, nullptr,
(uint32_t)enabledExtensions.size(),
enabledExtensionNames,
};
EXPECT_EQ(VK_SUCCESS, vk->vkCreateInstance(&instCi, nullptr, &mInstance));
uint32_t physdevCount = 0;
std::vector<VkPhysicalDevice> physdevs;
EXPECT_EQ(VK_SUCCESS,
vk->vkEnumeratePhysicalDevices(mInstance, &physdevCount, nullptr));
physdevs.resize(physdevCount);
EXPECT_EQ(VK_SUCCESS,
vk->vkEnumeratePhysicalDevices(mInstance, &physdevCount,
physdevs.data()));
std::vector<VkPhysicalDevice> physdevsSecond(physdevCount);
EXPECT_EQ(VK_SUCCESS,
vk->vkEnumeratePhysicalDevices(mInstance, &physdevCount,
physdevsSecond.data()));
// Check that a second call to vkEnumeratePhysicalDevices
// retrieves the same physical device handles.
EXPECT_EQ(physdevs, physdevsSecond);
uint32_t bestPhysicalDevice = 0;
bool queuesGood = false;
bool hasAndroidNativeBuffer = false;
bool hasExternalMemorySupport = false;
for (uint32_t i = 0; i < physdevCount; ++i) {
queuesGood = false;
hasAndroidNativeBuffer = false;
hasExternalMemorySupport = false;
bool hasGetMemoryRequirements2 = false;
bool hasDedicatedAllocation = false;
bool hasExternalMemoryBaseExtension = false;
bool hasExternalMemoryPlatformExtension = false;
uint32_t queueFamilyCount = 0;
std::vector<VkQueueFamilyProperties> queueFamilyProps;
vk->vkGetPhysicalDeviceQueueFamilyProperties(
physdevs[i], &queueFamilyCount, nullptr);
queueFamilyProps.resize(queueFamilyCount);
vk->vkGetPhysicalDeviceQueueFamilyProperties(
physdevs[i], &queueFamilyCount, queueFamilyProps.data());
for (uint32_t j = 0; j < queueFamilyCount; ++j) {
auto count = queueFamilyProps[j].queueCount;
auto flags = queueFamilyProps[j].queueFlags;
if (count > 0 && (flags & VK_QUEUE_GRAPHICS_BIT)) {
bestPhysicalDevice = i;
mGraphicsQueueFamily = j;
queuesGood = true;
break;
}
}
uint32_t devExtCount = 0;
std::vector<VkExtensionProperties> availableDeviceExtensions;
vk->vkEnumerateDeviceExtensionProperties(physdevs[i], nullptr,
&devExtCount, nullptr);
availableDeviceExtensions.resize(devExtCount);
vk->vkEnumerateDeviceExtensionProperties(
physdevs[i], nullptr, &devExtCount, availableDeviceExtensions.data());
for (uint32_t j = 0; j < devExtCount; ++j) {
if (!strcmp("VK_KHR_swapchain",
availableDeviceExtensions[j].extensionName)) {
hasAndroidNativeBuffer = true;
}
if (!strcmp("VK_KHR_get_memory_requirements2",
availableDeviceExtensions[j].extensionName)) {
hasGetMemoryRequirements2 = true;
}
if (!strcmp("VK_KHR_dedicated_allocation",
availableDeviceExtensions[j].extensionName)) {
hasDedicatedAllocation = true;
}
if (!strcmp("VK_KHR_external_memory",
availableDeviceExtensions[j].extensionName)) {
hasExternalMemoryBaseExtension = true;
}
static const char* externalMemoryPlatformExtension =
"VK_ANDROID_external_memory_android_hardware_buffer";
if (!strcmp(externalMemoryPlatformExtension,
availableDeviceExtensions[j].extensionName)) {
hasExternalMemoryPlatformExtension = true;
}
}
hasExternalMemorySupport =
(hasGetMemoryRequirements2 &&
hasDedicatedAllocation &&
hasExternalMemoryBaseExtension &&
hasExternalMemoryPlatformExtension);
if (queuesGood && hasExternalMemorySupport) {
bestPhysicalDevice = i;
break;
}
}
EXPECT_TRUE(queuesGood);
EXPECT_TRUE(hasAndroidNativeBuffer);
mDeviceHasExternalMemorySupport =
hasExternalMemorySupport;
mPhysicalDevice = physdevs[bestPhysicalDevice];
VkPhysicalDeviceMemoryProperties memProps;
vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps);
bool foundHostVisibleMemoryTypeIndex = false;
bool foundDeviceLocalMemoryTypeIndex = false;
for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
if (memProps.memoryTypes[i].propertyFlags &
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
mHostVisibleMemoryTypeIndex = i;
foundHostVisibleMemoryTypeIndex = true;
}
if (memProps.memoryTypes[i].propertyFlags &
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
mDeviceLocalMemoryTypeIndex = i;
foundDeviceLocalMemoryTypeIndex = true;
}
if (foundHostVisibleMemoryTypeIndex &&
foundDeviceLocalMemoryTypeIndex) {
break;
}
}
EXPECT_TRUE(
foundHostVisibleMemoryTypeIndex &&
foundDeviceLocalMemoryTypeIndex);
EXPECT_TRUE(foundHostVisibleMemoryTypeIndex);
float priority = 1.0f;
VkDeviceQueueCreateInfo dqCi = {
VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
0, 0,
mGraphicsQueueFamily, 1,
&priority,
};
VkDeviceCreateInfo dCi = {
VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, 0, 0,
1, &dqCi,
0, nullptr, // no layers
0, nullptr, // no extensions
nullptr, // no features
};
std::vector<const char*> externalMemoryExtensions = {
"VK_KHR_get_memory_requirements2",
"VK_KHR_dedicated_allocation",
"VK_KHR_external_memory",
"VK_ANDROID_external_memory_android_hardware_buffer",
};
// Mostly for MoltenVK or any other driver that doesn't support
// external memory.
std::vector<const char*> usefulExtensions = {
"VK_KHR_get_memory_requirements2",
"VK_KHR_dedicated_allocation",
};
if (mDeviceHasExternalMemorySupport) {
dCi.enabledExtensionCount =
(uint32_t)externalMemoryExtensions.size();
dCi.ppEnabledExtensionNames =
externalMemoryExtensions.data();
} else {
dCi.enabledExtensionCount =
(uint32_t)usefulExtensions.size();
dCi.ppEnabledExtensionNames =
usefulExtensions.data();
}
EXPECT_EQ(VK_SUCCESS, vk->vkCreateDevice(physdevs[bestPhysicalDevice], &dCi,
nullptr, &mDevice));
vk->vkGetDeviceQueue(mDevice, mGraphicsQueueFamily, 0, &mQueue);
}
void teardownVulkan() {
vk->vkDestroyDevice(mDevice, nullptr);
vk->vkDestroyInstance(mInstance, nullptr);
}
void createAndroidNativeImage(buffer_handle_t* buffer_out, VkImage* image_out) {
int usage = GRALLOC_USAGE_HW_RENDER;
int format = HAL_PIXEL_FORMAT_RGBA_8888;
int stride;
buffer_handle_t buffer =
createTestGrallocBuffer(
usage, format, kWindowSize, kWindowSize, &stride);
uint64_t producerUsage, consumerUsage;
android_convertGralloc0To1Usage(usage, &producerUsage, &consumerUsage);
VkNativeBufferANDROID nativeBufferInfo = {
VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID, nullptr,
buffer, stride,
format,
usage,
{
consumerUsage,
producerUsage,
},
};
VkImageCreateInfo testImageCi = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, (const void*)&nativeBufferInfo,
0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R8G8B8A8_UNORM,
{ kWindowSize, kWindowSize, 1, },
1, 1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0, nullptr /* shared queue families */,
VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImage testAndroidNativeImage;
EXPECT_EQ(VK_SUCCESS, vk->vkCreateImage(mDevice, &testImageCi, nullptr,
&testAndroidNativeImage));
*buffer_out = buffer;
*image_out = testAndroidNativeImage;
}
void destroyAndroidNativeImage(buffer_handle_t buffer, VkImage image) {
vk->vkDestroyImage(mDevice, image, nullptr);
destroyTestGrallocBuffer(buffer);
}
AHardwareBuffer* allocateAndroidHardwareBuffer(
int width = kWindowSize,
int height = kWindowSize,
AHardwareBuffer_Format format =
AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
uint64_t usage =
AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN) {
AHardwareBuffer_Desc desc = {
kWindowSize, kWindowSize, 1,
AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
usage,
4, // stride ignored for allocate; don't check this
};
AHardwareBuffer* buf = nullptr;
AHardwareBuffer_allocate(&desc, &buf);
EXPECT_NE(nullptr, buf);
return buf;
}
void exportAllocateAndroidHardwareBuffer(
VkMemoryDedicatedAllocateInfo* dedicated,
VkDeviceSize allocSize,
uint32_t memoryTypeIndex,
VkDeviceMemory* pMemory,
AHardwareBuffer** ahw) {
EXPECT_TRUE(mDeviceHasExternalMemorySupport);
VkExportMemoryAllocateInfo exportAi = {
VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, dedicated,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
};
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &exportAi,
allocSize, memoryTypeIndex,
};
VkResult res = vk->vkAllocateMemory(mDevice, &allocInfo, nullptr, pMemory);
EXPECT_EQ(VK_SUCCESS, res);
if (ahw) {
VkMemoryGetAndroidHardwareBufferInfoANDROID getAhbInfo = {
VK_STRUCTURE_TYPE_MEMORY_GET_ANDROID_HARDWARE_BUFFER_INFO_ANDROID, 0, *pMemory,
};
EXPECT_EQ(VK_SUCCESS,
vk->vkGetMemoryAndroidHardwareBufferANDROID(
mDevice, &getAhbInfo, ahw));
}
}
void importAllocateAndroidHardwareBuffer(
VkMemoryDedicatedAllocateInfo* dedicated,
VkDeviceSize allocSize,
uint32_t memoryTypeIndex,
AHardwareBuffer* ahw,
VkDeviceMemory* pMemory) {
VkImportAndroidHardwareBufferInfoANDROID importInfo = {
VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
dedicated, ahw,
};
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &importInfo,
allocSize, memoryTypeIndex,
};
VkDeviceMemory memory;
VkResult res = vk->vkAllocateMemory(mDevice, &allocInfo, nullptr, pMemory);
EXPECT_EQ(VK_SUCCESS, res);
}
void createExternalImage(
VkImage* pImage,
uint32_t width = kWindowSize,
uint32_t height = kWindowSize,
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM,
VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL) {
VkExternalMemoryImageCreateInfo extMemImgCi = {
VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, 0,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
};
VkImageCreateInfo testImageCi = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
(const void*)&extMemImgCi, 0,
VK_IMAGE_TYPE_2D, format,
{ width, height, 1, }, 1, 1,
VK_SAMPLE_COUNT_1_BIT,
tiling,
VK_IMAGE_USAGE_SAMPLED_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0, nullptr /* shared queue families */,
VK_IMAGE_LAYOUT_UNDEFINED,
};
EXPECT_EQ(VK_SUCCESS,
vk->vkCreateImage(
mDevice, &testImageCi, nullptr, pImage));
}
uint32_t getFirstMemoryTypeIndexForImage(VkImage image) {
VkMemoryRequirements memReqs;
vk->vkGetImageMemoryRequirements(
mDevice, image, &memReqs);
uint32_t memoryTypeIndex = 0;
EXPECT_NE(0, memReqs.memoryTypeBits);
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i) {
if (memReqs.memoryTypeBits & (1 << i)) {
memoryTypeIndex = i;
break;
}
}
return memoryTypeIndex;
}
VkDeviceSize getNeededMemorySizeForImage(VkImage image) {
VkMemoryRequirements memReqs;
vk->vkGetImageMemoryRequirements(
mDevice, image, &memReqs);
return memReqs.size;
}
VkResult allocateTestDescriptorSetsFromExistingPool(
uint32_t setsToAllocate,
VkDescriptorPool pool,
VkDescriptorSetLayout setLayout,
VkDescriptorSet* sets_out) {
std::vector<VkDescriptorSetLayout> setLayouts;
for (uint32_t i = 0; i < setsToAllocate; ++i) {
setLayouts.push_back(setLayout);
}
VkDescriptorSetAllocateInfo setAi = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, 0,
pool, setsToAllocate, setLayouts.data(),
};
return vk->vkAllocateDescriptorSets(
mDevice, &setAi, sets_out);
}
VkResult allocateTestDescriptorSets(
uint32_t maxSetCount,
uint32_t setsToAllocate,
VkDescriptorType descriptorType,
VkDescriptorPoolCreateFlags poolCreateFlags,
VkDescriptorPool* pool_out,
VkDescriptorSetLayout* setLayout_out,
VkDescriptorSet* sets_out) {
VkDescriptorPoolSize poolSize = {
descriptorType,
maxSetCount,
};
VkDescriptorPoolCreateInfo poolCi = {
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, 0,
VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
maxSetCount /* maxSets */,
1 /* poolSizeCount */,
&poolSize,
};
EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorPool(mDevice, &poolCi, nullptr, pool_out));
VkDescriptorSetLayoutBinding binding = {
0,
descriptorType,
1,
VK_SHADER_STAGE_VERTEX_BIT,
nullptr,
};
VkDescriptorSetLayoutCreateInfo setLayoutCi = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 0, 0,
1,
&binding,
};
EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorSetLayout(
mDevice, &setLayoutCi, nullptr, setLayout_out));
return allocateTestDescriptorSetsFromExistingPool(
setsToAllocate, *pool_out, *setLayout_out, sets_out);
}
VkResult allocateImmutableSamplerDescriptorSets(
uint32_t maxSetCount,
uint32_t setsToAllocate,
std::vector<bool> bindingImmutabilities,
VkSampler* sampler_out,
VkDescriptorPool* pool_out,
VkDescriptorSetLayout* setLayout_out,
VkDescriptorSet* sets_out) {
VkSamplerCreateInfo samplerCi = {
VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, 0, 0,
VK_FILTER_NEAREST,
VK_FILTER_NEAREST,
VK_SAMPLER_MIPMAP_MODE_NEAREST,
VK_SAMPLER_ADDRESS_MODE_REPEAT,
VK_SAMPLER_ADDRESS_MODE_REPEAT,
VK_SAMPLER_ADDRESS_MODE_REPEAT,
0.0f,
VK_FALSE,
1.0f,
VK_FALSE,
VK_COMPARE_OP_NEVER,
0.0f,
1.0f,
VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
VK_FALSE,
};
EXPECT_EQ(VK_SUCCESS,
vk->vkCreateSampler(
mDevice, &samplerCi, nullptr, sampler_out));
VkDescriptorPoolSize poolSize = {
VK_DESCRIPTOR_TYPE_SAMPLER,
maxSetCount,
};
VkDescriptorPoolCreateInfo poolCi = {
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, 0,
VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
maxSetCount /* maxSets */,
1 /* poolSizeCount */,
&poolSize,
};
EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorPool(mDevice, &poolCi, nullptr, pool_out));
std::vector<VkDescriptorSetLayoutBinding> samplerBindings;
for (size_t i = 0; i < bindingImmutabilities.size(); ++i) {
VkDescriptorSetLayoutBinding samplerBinding = {
(uint32_t)i, VK_DESCRIPTOR_TYPE_SAMPLER,
1, VK_SHADER_STAGE_FRAGMENT_BIT,
bindingImmutabilities[i] ? sampler_out : nullptr,
};
samplerBindings.push_back(samplerBinding);
}
VkDescriptorSetLayoutCreateInfo setLayoutCi = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 0, 0,
(uint32_t)samplerBindings.size(),
samplerBindings.data(),
};
EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorSetLayout(
mDevice, &setLayoutCi, nullptr, setLayout_out));
return allocateTestDescriptorSetsFromExistingPool(
setsToAllocate, *pool_out, *setLayout_out, sets_out);
}
struct gralloc_implementation mGralloc;
bool mInstanceHasGetPhysicalDeviceProperties2Support = false;
bool mInstanceHasExternalMemorySupport = false;
bool mDeviceHasExternalMemorySupport = false;
bool mProcessPipeRestarted = false;
VkInstance mInstance;
VkPhysicalDevice mPhysicalDevice;
VkDevice mDevice;
VkQueue mQueue;
uint32_t mHostVisibleMemoryTypeIndex;
uint32_t mDeviceLocalMemoryTypeIndex;
uint32_t mGraphicsQueueFamily;
};
// static
GoldfishOpenglTestEnv* VulkanHalTest::testEnv = nullptr;
// A basic test of Vulkan HAL:
// - Touch the Android loader at global, instance, and device level.
TEST_P(VulkanHalTest, Basic) { }
// Test: Allocate, map, flush, invalidate some host visible memory.
TEST_P(VulkanHalTest, MemoryMapping) {
static constexpr VkDeviceSize kTestAlloc = 16 * 1024;
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 0,
kTestAlloc,
mHostVisibleMemoryTypeIndex,
};
VkDeviceMemory mem;
EXPECT_EQ(VK_SUCCESS, vk->vkAllocateMemory(mDevice, &allocInfo, nullptr, &mem));
void* hostPtr;
EXPECT_EQ(VK_SUCCESS, vk->vkMapMemory(mDevice, mem, 0, VK_WHOLE_SIZE, 0, &hostPtr));
memset(hostPtr, 0xff, kTestAlloc);
VkMappedMemoryRange toFlush = {
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 0,
mem, 0, kTestAlloc,
};
EXPECT_EQ(VK_SUCCESS, vk->vkFlushMappedMemoryRanges(mDevice, 1, &toFlush));
EXPECT_EQ(VK_SUCCESS, vk->vkInvalidateMappedMemoryRanges(mDevice, 1, &toFlush));
for (uint32_t i = 0; i < kTestAlloc; ++i) {
EXPECT_EQ(0xff, *((uint8_t*)hostPtr + i));
}
int usage = GRALLOC_USAGE_HW_RENDER;
int format = HAL_PIXEL_FORMAT_RGBA_8888;
int stride;
buffer_handle_t buffer =
createTestGrallocBuffer(
usage, format, kWindowSize, kWindowSize, &stride);
uint64_t producerUsage, consumerUsage;
android_convertGralloc0To1Usage(usage, &producerUsage, &consumerUsage);
VkNativeBufferANDROID nativeBufferInfo = {
VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID, nullptr,
buffer, stride,
format,
usage,
{
consumerUsage,
producerUsage,
},
};
VkImageCreateInfo testImageCi = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, (const void*)&nativeBufferInfo,
0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R8G8B8A8_UNORM,
{ kWindowSize, kWindowSize, 1, },
1, 1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0, nullptr /* shared queue families */,
VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImage testAndroidNativeImage;
EXPECT_EQ(VK_SUCCESS, vk->vkCreateImage(mDevice, &testImageCi, nullptr,
&testAndroidNativeImage));
vk->vkDestroyImage(mDevice, testAndroidNativeImage, nullptr);
destroyTestGrallocBuffer(buffer);
vk->vkUnmapMemory(mDevice, mem);
vk->vkFreeMemory(mDevice, mem, nullptr);
}
// Tests creation of VkImages backed by gralloc buffers.
TEST_P(VulkanHalTest, AndroidNativeImageCreation) {
VkImage image;
buffer_handle_t buffer;
createAndroidNativeImage(&buffer, &image);
destroyAndroidNativeImage(buffer, image);
}
// Tests the path to sync Android native buffers with Gralloc buffers.
TEST_P(VulkanHalTest, AndroidNativeImageQueueSignal) {
VkImage image;
buffer_handle_t buffer;
int fenceFd;
createAndroidNativeImage(&buffer, &image);
PFN_vkQueueSignalReleaseImageANDROID func =
(PFN_vkQueueSignalReleaseImageANDROID)
vk->vkGetDeviceProcAddr(mDevice, "vkQueueSignalReleaseImageANDROID");
if (func) {
fprintf(stderr, "%s: qsig\n", __func__);
func(mQueue, 0, nullptr, image, &fenceFd);
}
destroyAndroidNativeImage(buffer, image);
}
// Tests VK_KHR_get_physical_device_properties2:
// new API: vkGetPhysicalDeviceProperties2KHR
TEST_P(VulkanHalTest, GetPhysicalDeviceProperties2) {
if (!mInstanceHasGetPhysicalDeviceProperties2Support) {
printf("Warning: Not testing VK_KHR_physical_device_properties2, not "
"supported\n");
return;
}
PFN_vkGetPhysicalDeviceProperties2KHR physProps2KHRFunc =
(PFN_vkGetPhysicalDeviceProperties2KHR)vk->vkGetInstanceProcAddr(
mInstance, "vkGetPhysicalDeviceProperties2KHR");
EXPECT_NE(nullptr, physProps2KHRFunc);
VkPhysicalDeviceProperties2KHR props2 = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, 0,
};
physProps2KHRFunc(mPhysicalDevice, &props2);
VkPhysicalDeviceProperties props;
vk->vkGetPhysicalDeviceProperties(mPhysicalDevice, &props);
EXPECT_EQ(props.vendorID, props2.properties.vendorID);
EXPECT_EQ(props.deviceID, props2.properties.deviceID);
}
// Tests VK_KHR_get_physical_device_properties2:
// new API: vkGetPhysicalDeviceFeatures2KHR
TEST_P(VulkanHalTest, GetPhysicalDeviceFeatures2KHR) {
if (!mInstanceHasGetPhysicalDeviceProperties2Support) {
printf("Warning: Not testing VK_KHR_physical_device_properties2, not "
"supported\n");
return;
}
PFN_vkGetPhysicalDeviceFeatures2KHR physDeviceFeatures =
(PFN_vkGetPhysicalDeviceFeatures2KHR)vk->vkGetInstanceProcAddr(
mInstance, "vkGetPhysicalDeviceFeatures2KHR");
EXPECT_NE(nullptr, physDeviceFeatures);
VkPhysicalDeviceFeatures2 features2 = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, 0,
};
physDeviceFeatures(mPhysicalDevice, &features2);
}
// Tests VK_KHR_get_physical_device_properties2:
// new API: vkGetPhysicalDeviceImageFormatProperties2KHR
TEST_P(VulkanHalTest, GetPhysicalDeviceImageFormatProperties2KHR) {
if (!mInstanceHasGetPhysicalDeviceProperties2Support) {
printf("Warning: Not testing VK_KHR_physical_device_properties2, not "
"supported\n");
return;
}
PFN_vkGetPhysicalDeviceImageFormatProperties2KHR
physDeviceImageFormatPropertiesFunc =
(PFN_vkGetPhysicalDeviceImageFormatProperties2KHR)
vk->vkGetInstanceProcAddr(mInstance,
"vkGetPhysicalDeviceImageForm"
"atProperties2KHR");
EXPECT_NE(nullptr, physDeviceImageFormatPropertiesFunc);
VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, 0,
VK_FORMAT_R8G8B8A8_UNORM,
VK_IMAGE_TYPE_2D,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_SAMPLED_BIT,
0,
};
VkImageFormatProperties2 res = {
VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, 0,
};
EXPECT_EQ(VK_SUCCESS, physDeviceImageFormatPropertiesFunc(
mPhysicalDevice, &imageFormatInfo, &res));
}
// Tests that if we create an instance and the API version is less than 1.1,
// we return null for 1.1 core API calls.
TEST_P(VulkanHalTest, Hide1_1FunctionPointers) {
VkPhysicalDeviceProperties props;
vk->vkGetPhysicalDeviceProperties(mPhysicalDevice, &props);
if (props.apiVersion < VK_API_VERSION_1_1) {
EXPECT_EQ(nullptr,
vk->vkGetDeviceProcAddr(mDevice, "vkTrimCommandPool"));
} else {
EXPECT_NE(nullptr,
vk->vkGetDeviceProcAddr(mDevice, "vkTrimCommandPool"));
}
}
// Tests VK_ANDROID_external_memory_android_hardware_buffer's allocation API.
// The simplest: export allocate device local memory.
// Disabled for now: currently goes down invalid paths in the GL side
TEST_P(VulkanHalTest, DISABLED_AndroidHardwareBufferAllocate_ExportDeviceLocal) {
if (!mDeviceHasExternalMemorySupport) return;
VkDeviceMemory memory;
AHardwareBuffer* ahw;
exportAllocateAndroidHardwareBuffer(
nullptr, 4096, mDeviceLocalMemoryTypeIndex,
&memory, &ahw);
vk->vkFreeMemory(mDevice, memory, nullptr);
}
// Test AHB allocation via import.
// Disabled for now: currently goes down invalid paths in the GL side
TEST_P(VulkanHalTest, DISABLED_AndroidHardwareBufferAllocate_ImportDeviceLocal) {
if (!mDeviceHasExternalMemorySupport) return;
AHardwareBuffer* testBuf = allocateAndroidHardwareBuffer();
VkDeviceMemory memory;
importAllocateAndroidHardwareBuffer(
nullptr,
4096, // also checks that the top-level allocation size is ignored
mDeviceLocalMemoryTypeIndex,
testBuf,
&memory);
vk->vkFreeMemory(mDevice, memory, nullptr);
AHardwareBuffer_release(testBuf);
}
// Test AHB allocation via export, but with a dedicated allocation (image).
// Disabled for now: currently goes down invalid paths in the GL side
TEST_P(VulkanHalTest, DISABLED_AndroidHardwareBufferAllocate_Dedicated_Export) {
if (!mDeviceHasExternalMemorySupport) return;
VkImage testAhbImage;
createExternalImage(&testAhbImage);
VkMemoryDedicatedAllocateInfo dedicatedAi = {
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, 0,
testAhbImage, VK_NULL_HANDLE,
};
VkDeviceMemory memory;
AHardwareBuffer* buffer;
exportAllocateAndroidHardwareBuffer(
&dedicatedAi,
4096,
getFirstMemoryTypeIndexForImage(testAhbImage),
&memory, &buffer);
EXPECT_EQ(VK_SUCCESS, vk->vkBindImageMemory(mDevice, testAhbImage, memory, 0));
vk->vkFreeMemory(mDevice, memory, nullptr);
vk->vkDestroyImage(mDevice, testAhbImage, nullptr);
}
// Test AHB allocation via import, but with a dedicated allocation (image).
// Disabled for now: currently goes down invalid paths in the GL side
TEST_P(VulkanHalTest, DISABLED_AndroidHardwareBufferAllocate_Dedicated_Import) {
if (!mDeviceHasExternalMemorySupport) return;
AHardwareBuffer* testBuf =
allocateAndroidHardwareBuffer();
VkImage testAhbImage;
createExternalImage(&testAhbImage);
VkMemoryDedicatedAllocateInfo dedicatedAi = {
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, 0,
testAhbImage, VK_NULL_HANDLE,
};
VkDeviceMemory memory;
importAllocateAndroidHardwareBuffer(
&dedicatedAi,
4096, // also checks that the top-level allocation size is ignored
getFirstMemoryTypeIndexForImage(testAhbImage),
testBuf,
&memory);
EXPECT_EQ(VK_SUCCESS,
vk->vkBindImageMemory(mDevice, testAhbImage, memory, 0));
vk->vkFreeMemory(mDevice, memory, nullptr);
vk->vkDestroyImage(mDevice, testAhbImage, nullptr);
AHardwareBuffer_release(testBuf);
}
// Test many host visible allocations.
TEST_P(VulkanHalTest, HostVisibleAllocations) {
static constexpr VkDeviceSize kTestAllocSizesSmall[] =
{ 4, 5, 6, 16, 32, 37, 64, 255, 256, 267,
1024, 1023, 1025, 4095, 4096,
4097, 16333, };
static constexpr size_t kNumSmallAllocSizes = android::base::arraySize(kTestAllocSizesSmall);
static constexpr size_t kNumTrialsSmall = 1000;
static constexpr VkDeviceSize kTestAllocSizesLarge[] =
{ 1048576, 1048577, 1048575 };
static constexpr size_t kNumLargeAllocSizes = android::base::arraySize(kTestAllocSizesLarge);
static constexpr size_t kNumTrialsLarge = 20;
static constexpr float kLargeAllocChance = 0.05;
std::default_random_engine generator;
// Use a consistent seed value to avoid flakes
generator.seed(0);
std::uniform_int_distribution<size_t>
smallAllocIndexDistribution(0, kNumSmallAllocSizes - 1);
std::uniform_int_distribution<size_t>
largeAllocIndexDistribution(0, kNumLargeAllocSizes - 1);
std::bernoulli_distribution largeAllocDistribution(kLargeAllocChance);
size_t smallAllocCount = 0;
size_t largeAllocCount = 0;
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 0,
0,
mHostVisibleMemoryTypeIndex,
};
std::vector<VkDeviceMemory> allocs;
while (smallAllocCount < kNumTrialsSmall ||
largeAllocCount < kNumTrialsLarge) {
VkDeviceMemory mem = VK_NULL_HANDLE;
void* hostPtr = nullptr;
if (largeAllocDistribution(generator)) {
if (largeAllocCount < kNumTrialsLarge) {
fprintf(stderr, "%s: large alloc\n", __func__);
allocInfo.allocationSize =
kTestAllocSizesLarge[
largeAllocIndexDistribution(generator)];
++largeAllocCount;
}
} else {
if (smallAllocCount < kNumTrialsSmall) {
allocInfo.allocationSize =
kTestAllocSizesSmall[
smallAllocIndexDistribution(generator)];
++smallAllocCount;
}
}
EXPECT_EQ(VK_SUCCESS,
vk->vkAllocateMemory(mDevice, &allocInfo, nullptr, &mem));
if (!mem) continue;
allocs.push_back(mem);
EXPECT_EQ(VK_SUCCESS,
vk->vkMapMemory(mDevice, mem, 0, VK_WHOLE_SIZE, 0, &hostPtr));
memset(hostPtr, 0xff, 4);
VkMappedMemoryRange toFlush = {
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 0,
mem, 0, 4,
};
EXPECT_EQ(VK_SUCCESS, vk->vkFlushMappedMemoryRanges(mDevice, 1, &toFlush));
EXPECT_EQ(VK_SUCCESS, vk->vkInvalidateMappedMemoryRanges(mDevice, 1, &toFlush));
for (uint32_t i = 0; i < 4; ++i) {
EXPECT_EQ(0xff, *((uint8_t*)hostPtr + i));
}
}
for (auto mem : allocs) {
vk->vkUnmapMemory(mDevice, mem);
vk->vkFreeMemory(mDevice, mem, nullptr);
}
}
TEST_P(VulkanHalTest, BufferCreate) {
VkBufferCreateInfo bufCi = {
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 0, 0,
4096,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0, nullptr,
};
VkBuffer buffer;
vk->vkCreateBuffer(mDevice, &bufCi, nullptr, &buffer);
VkMemoryRequirements memReqs;
vk->vkGetBufferMemoryRequirements(mDevice, buffer, &memReqs);
vk->vkDestroyBuffer(mDevice, buffer, nullptr);
}
TEST_P(VulkanHalTest, SnapshotSaveLoad) {
// TODO: Skip if using address space graphics
if (usingAddressSpaceGraphics()) {
printf("%s: skipping, ASG does not yet support snapshots\n", __func__);
return;
}
androidSnapshot_save("test_snapshot");
androidSnapshot_load("test_snapshot");
}
TEST_P(VulkanHalTest, SnapshotSaveLoadSimpleNonDispatchable) {
// TODO: Skip if using address space graphics
if (usingAddressSpaceGraphics()) {
printf("%s: skipping, ASG does not yet support snapshots\n", __func__);
return;
}
VkBufferCreateInfo bufCi = {
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 0, 0,
4096,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0, nullptr,
};
VkFence fence;
VkFenceCreateInfo fenceCi = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, 0, };
vk->vkCreateFence(mDevice, &fenceCi, nullptr, &fence);
fprintf(stderr, "%s: guest fence: %p\n", __func__, fence);
VkBuffer buffer;
vk->vkCreateBuffer(mDevice, &bufCi, nullptr, &buffer);
fprintf(stderr, "%s: guest buffer: %p\n", __func__, buffer);
androidSnapshot_save("test_snapshot");
androidSnapshot_load("test_snapshot");
VkMemoryRequirements memReqs;
vk->vkGetBufferMemoryRequirements(mDevice, buffer, &memReqs);
vk->vkDestroyBuffer(mDevice, buffer, nullptr);
vk->vkDestroyFence(mDevice, fence, nullptr);
}
// Tests save/load of host visible memory. This is not yet a viable host-only
// test because, the only way to really test it is to be able to preserve a
// host visible address for the simulated guest while the backing memory under
// it changes due to the new snapshot. In other words, this is arbitrary
// remapping of virtual addrs and is functionality that does not exist on
// Linux/macOS. It would ironically require a hypervisor (or an OS that
// supports freer ways of mapping memory) in order to test properly.
// Disabled for now: currently goes down invalid paths in the GL side
TEST_P(VulkanHalTest, DISABLED_SnapshotSaveLoadHostVisibleMemory) {
// TODO: Skip if using address space graphics
if (usingAddressSpaceGraphics()) {
printf("%s: skipping, ASG does not yet support snapshots\n", __func__);
return;
}
static constexpr VkDeviceSize kTestAlloc = 16 * 1024;
VkMemoryAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 0,
kTestAlloc,
mHostVisibleMemoryTypeIndex,
};
VkDeviceMemory mem;
EXPECT_EQ(VK_SUCCESS, vk->vkAllocateMemory(mDevice, &allocInfo, nullptr, &mem));
void* hostPtr;
EXPECT_EQ(VK_SUCCESS, vk->vkMapMemory(mDevice, mem, 0, VK_WHOLE_SIZE, 0, &hostPtr));
androidSnapshot_save("test_snapshot");
androidSnapshot_load("test_snapshot");
memset(hostPtr, 0xff, kTestAlloc);
VkMappedMemoryRange toFlush = {
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 0,
mem, 0, kTestAlloc,
};
EXPECT_EQ(VK_SUCCESS, vk->vkFlushMappedMemoryRanges(mDevice, 1, &toFlush));
EXPECT_EQ(VK_SUCCESS, vk->vkInvalidateMappedMemoryRanges(mDevice, 1, &toFlush));
vk->vkUnmapMemory(mDevice, mem);
vk->vkFreeMemory(mDevice, mem, nullptr);
}
// Tests save/load of a dispatchable handle, such as VkCommandBuffer.
// Note that the internal state of the command buffer is not snapshotted yet.
TEST_P(VulkanHalTest, SnapshotSaveLoadSimpleDispatchable) {
// TODO: Skip if using address space graphics
if (usingAddressSpaceGraphics()) {
printf("%s: skipping, ASG does not yet support snapshots\n", __func__);
return;
}
VkCommandPoolCreateInfo poolCi = {
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily,
};
VkCommandPool pool;
vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &pool);
VkCommandBuffer cb;
VkCommandBufferAllocateInfo cbAi = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0,
pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1,
};
vk->vkAllocateCommandBuffers(mDevice, &cbAi, &cb);
androidSnapshot_save("test_snapshot");
androidSnapshot_load("test_snapshot");
vk->vkFreeCommandBuffers(mDevice, pool, 1, &cb);
vk->vkDestroyCommandPool(mDevice, pool, nullptr);
}
// Tests that dependencies are respected between different handle types,
// such as VkImage and VkImageView.
TEST_P(VulkanHalTest, SnapshotSaveLoadDependentHandlesImageView) {
// TODO: Skip if using address space graphics
if (usingAddressSpaceGraphics()) {
printf("%s: skipping, ASG does not yet support snapshots\n", __func__);
return;
}
VkImage image;
VkImageCreateInfo imageCi = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, 0, 0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R8G8B8A8_UNORM,
{ 1, 1, 1, },
1, 1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_LINEAR,
VK_IMAGE_USAGE_TRANSFER_DST_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0, nullptr,
VK_IMAGE_LAYOUT_UNDEFINED,
};
vk->vkCreateImage(mDevice, &imageCi, nullptr, &image);
VkImageView imageView;
VkImageViewCreateInfo imageViewCi = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 0, 0,
image,
VK_IMAGE_VIEW_TYPE_2D,
VK_FORMAT_R8G8B8A8_UNORM,
{
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
},
{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1, },
};
vk->vkCreateImageView(mDevice, &imageViewCi, nullptr, &imageView);
androidSnapshot_save("test_snapshot");
androidSnapshot_load("test_snapshot");
vk->vkDestroyImageView(mDevice, imageView, nullptr);
vk->vkCreateImageView(mDevice, &imageViewCi, nullptr, &imageView);
vk->vkDestroyImageView(mDevice, imageView, nullptr);
vk->vkDestroyImage(mDevice, image, nullptr);
}
// Tests beginning and ending command buffers from separate threads.
TEST_P(VulkanHalTest, SeparateThreadCommandBufferBeginEnd) {
Lock lock;
ConditionVariable cvSequence;
uint32_t begins = 0;
uint32_t ends = 0;
constexpr uint32_t kTrials = 1000;
VkCommandPoolCreateInfo poolCi = {
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily,
};
VkCommandPool pool;
vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &pool);
VkCommandBuffer cb;
VkCommandBufferAllocateInfo cbAi = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0,
pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1,
};
vk->vkAllocateCommandBuffers(mDevice, &cbAi, &cb);
auto timeoutDeadline = []() {
return System::get()->getUnixTimeUs() + 5000000; // 5 s
};
FunctorThread beginThread([this, cb, &lock, &cvSequence, &begins, &ends, timeoutDeadline]() {
while (begins < kTrials) {
AutoLock a(lock);
while (ends != begins) {
if (!cvSequence.timedWait(&lock, timeoutDeadline())) {
EXPECT_TRUE(false) << "Error: begin thread timed out!";
return 0;
}
}
VkCommandBufferBeginInfo beginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 0,
};
vk->vkBeginCommandBuffer(cb, &beginInfo);
++begins;
cvSequence.signal();
}
vk->vkDeviceWaitIdle(mDevice);
return 0;
});
FunctorThread endThread([this, cb, &lock, &cvSequence, &begins, &ends, timeoutDeadline]() {
while (ends < kTrials) {
AutoLock a(lock);
while (begins - ends != 1) {
if (!cvSequence.timedWait(&lock, timeoutDeadline())) {
EXPECT_TRUE(false) << "Error: end thread timed out!";
return 0;
}
}
vk->vkEndCommandBuffer(cb);
++ends;
cvSequence.signal();
}
vk->vkDeviceWaitIdle(mDevice);
return 0;
});
beginThread.start();
endThread.start();
beginThread.wait();
endThread.wait();
vk->vkFreeCommandBuffers(mDevice, pool, 1, &cb);
vk->vkDestroyCommandPool(mDevice, pool, nullptr);
}
// Tests creating a bunch of descriptor sets and freeing them via vkFreeDescriptorSet.
// 1. Via vkFreeDescriptorSet directly
// 2. Via vkResetDescriptorPool
// 3. Via vkDestroyDescriptorPool
// 4. Via vkResetDescriptorPool and double frees in vkFreeDescriptorSet
// 5. Via vkResetDescriptorPool and double frees in vkFreeDescriptorSet
// 4. Via vkResetDescriptorPool, creating more, and freeing vai vkFreeDescriptorSet
// (because vkFree* APIs are expected to never fail)
// https://github.com/KhronosGroup/Vulkan-Docs/issues/1070
TEST_P(VulkanHalTest, DescriptorSetAllocFreeBasic) {
const uint32_t kSetCount = 4;
VkDescriptorPool pool;
VkDescriptorSetLayout setLayout;
std::vector<VkDescriptorSet> sets(kSetCount);
EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets(
kSetCount, kSetCount,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
&pool, &setLayout, sets.data()));
EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets(
mDevice, pool, kSetCount, sets.data()));
// The double free should also work
EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets(
mDevice, pool, kSetCount, sets.data()));
// Alloc/free again should also work
EXPECT_EQ(VK_SUCCESS,
allocateTestDescriptorSetsFromExistingPool(
kSetCount, pool, setLayout, sets.data()));
EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets(
mDevice, pool, kSetCount, sets.data()));
vk->vkDestroyDescriptorPool(mDevice, pool, nullptr);
}
// Tests creating a bunch of descriptor sets and freeing them via
// vkResetDescriptorPool, and that vkFreeDescriptorSets still succeeds.
TEST_P(VulkanHalTest, DescriptorSetAllocFreeReset) {
const uint32_t kSetCount = 4;
VkDescriptorPool pool;
VkDescriptorSetLayout setLayout;
std::vector<VkDescriptorSet> sets(kSetCount);
EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets(
kSetCount, kSetCount,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
&pool, &setLayout, sets.data()));
EXPECT_EQ(VK_SUCCESS, vk->vkResetDescriptorPool(
mDevice, pool, 0));
// The double free should also work
EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets(
mDevice, pool, kSetCount, sets.data()));
// Alloc/reset/free again should also work
EXPECT_EQ(VK_SUCCESS,
allocateTestDescriptorSetsFromExistingPool(
kSetCount, pool, setLayout, sets.data()));
EXPECT_EQ(VK_SUCCESS, vk->vkResetDescriptorPool(
mDevice, pool, 0));
EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets(
mDevice, pool, kSetCount, sets.data()));
vk->vkDestroyDescriptorPool(mDevice, pool, nullptr);
}
// Tests creating a bunch of descriptor sets and freeing them via vkDestroyDescriptorPool, and that vkFreeDescriptorSets still succeeds.
TEST_P(VulkanHalTest, DescriptorSetAllocFreeDestroy) {
const uint32_t kSetCount = 4;
VkDescriptorPool pool;
VkDescriptorSetLayout setLayout;
std::vector<VkDescriptorSet> sets(kSetCount);
EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets(
kSetCount, kSetCount,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
&pool, &setLayout, sets.data()));
vk->vkDestroyDescriptorPool(mDevice, pool, nullptr);
EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets(
mDevice, pool, kSetCount, sets.data()));
}
// Tests that immutable sampler descriptors properly cause
// the |sampler| field of VkWriteDescriptorSet's descriptor image info
// to be ignored.
TEST_P(VulkanHalTest, ImmutableSamplersSuppressVkWriteDescriptorSetSampler) {
const uint32_t kSetCount = 4;
std::vector<bool> bindingImmutabilities = {
false,
true,
false,
false,
};
VkSampler sampler;
VkDescriptorPool pool;
VkDescriptorSetLayout setLayout;
std::vector<VkDescriptorSet> sets(kSetCount);
EXPECT_EQ(VK_SUCCESS,
allocateImmutableSamplerDescriptorSets(
kSetCount * bindingImmutabilities.size(),
kSetCount,
bindingImmutabilities,
&sampler,
&pool,
&setLayout,
sets.data()));
for (uint32_t i = 0; i < bindingImmutabilities.size(); ++i) {
VkDescriptorImageInfo imageInfo = {
bindingImmutabilities[i] ? (VkSampler)0xdeadbeef : sampler,
0,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
};
VkWriteDescriptorSet write = {
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0,
sets[0],
1,
0,
1,
VK_DESCRIPTOR_TYPE_SAMPLER,
&imageInfo,
nullptr,
nullptr,
};
vk->vkUpdateDescriptorSets(mDevice, 1, &write, 0, nullptr);
}
vk->vkDestroyDescriptorPool(mDevice, pool, nullptr);
vk->vkDestroyDescriptorSetLayout(mDevice, setLayout, nullptr);
vk->vkDestroySampler(mDevice, sampler, nullptr);
}
// Tests vkGetImageMemoryRequirements2
TEST_P(VulkanHalTest, GetImageMemoryRequirements2) {
VkImageCreateInfo testImageCi = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, nullptr,
0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R8G8B8A8_UNORM,
{ kWindowSize, kWindowSize, 1, },
1, 1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0, nullptr /* shared queue families */,
VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImage testImage;
EXPECT_EQ(VK_SUCCESS, vk->vkCreateImage(mDevice, &testImageCi, nullptr,
&testImage));
VkImageMemoryRequirementsInfo2 info2 = {
VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR, 0,
testImage,
};
VkMemoryDedicatedRequirements dedicatedReqs {
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR, 0,
};
VkMemoryRequirements2 reqs2 = {
VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR, 0,
};
reqs2.pNext = &dedicatedReqs;
PFN_vkGetImageMemoryRequirements2KHR func =
(PFN_vkGetImageMemoryRequirements2KHR)
vk->vkGetDeviceProcAddr(mDevice, "vkGetImageMemoryRequirements2KHR");
EXPECT_NE(nullptr, func);
func(mDevice, &info2, &reqs2);
vk->vkDestroyImage(mDevice, testImage, nullptr);
}
TEST_P(VulkanHalTest, ProcessCleanup) {
static constexpr uint32_t kNumTrials = 10;
static constexpr uint32_t kNumImagesPerTrial = 100;
for (uint32_t i = 0; i < kNumTrials; ++i) {
VkImage images[kNumImagesPerTrial];
for (uint32_t j = 0; j < kNumImagesPerTrial; ++j) {
VkImageCreateInfo imageCi = {
VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, 0, 0,
VK_IMAGE_TYPE_2D,
VK_FORMAT_R8G8B8A8_UNORM,
{ 1, 1, 1, },
1, 1,
VK_SAMPLE_COUNT_1_BIT,
VK_IMAGE_TILING_LINEAR,
VK_IMAGE_USAGE_TRANSFER_DST_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0, nullptr,
VK_IMAGE_LAYOUT_UNDEFINED,
};
vk->vkCreateImage(mDevice, &imageCi, nullptr, &images[j]);
}
for (uint32_t j = 0; j < kNumImagesPerTrial; ++j) {
vk->vkDestroyImage(mDevice, images[j], nullptr);
}
restartProcessPipeAndHostConnection();
setupVulkan();
}
}
// Multithreaded benchmarks: Speed of light with simple vkCmd's.
//
// Currently disabled until we land
// VulkanQueueSubmitWithCommands---syncEncodersFor** interferes with rc
// encoders
TEST_P(VulkanHalTest, DISABLED_MultithreadedSimpleCommand) {
Lock lock;
constexpr uint32_t kThreadCount = 4;
VkDescriptorPool pool;
VkDescriptorSetLayout setLayout;
std::vector<VkDescriptorSet> sets(kThreadCount);
// Setup descriptor sets
EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets(
kThreadCount, kThreadCount,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
&pool, &setLayout, sets.data()));
VkPipelineLayout pipelineLayout;
VkPipelineLayoutCreateInfo pipelineLayoutCi = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 0, 0,
1, &setLayout,
0, nullptr,
};
vk->vkCreatePipelineLayout(mDevice, &pipelineLayoutCi, nullptr, &pipelineLayout);
// Setup command buffers
VkCommandPoolCreateInfo poolCi = {
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily,
};
VkCommandPool commandPool;
vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &commandPool);
VkCommandBuffer cbs[kThreadCount];
VkCommandBufferAllocateInfo cbAi = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0,
commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount,
};
vk->vkAllocateCommandBuffers(mDevice, &cbAi, cbs);
std::vector<FunctorThread*> threads;
constexpr uint32_t kRecordsPerThread = 20000;
constexpr uint32_t kRepeatSubmits = 1;
constexpr uint32_t kTotalRecords = kThreadCount * kRecordsPerThread * kRepeatSubmits;
for (uint32_t i = 0; i < kThreadCount; ++i) {
FunctorThread* thread = new FunctorThread([this, &lock, cbs, sets, pipelineLayout, i]() {
VkCommandBufferBeginInfo beginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 0,
};
for (uint32_t k = 0; k < kRepeatSubmits; ++k) {
vk->vkBeginCommandBuffer(cbs[i], &beginInfo);
VkRect2D scissor = {
{ 0, 0, },
{ 256, 256, },
};
for (uint32_t j = 0; j < kRecordsPerThread; ++j) {
vk->vkCmdBindDescriptorSets(
cbs[i],
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, 1, &sets[i], 0, nullptr);
}
vk->vkEndCommandBuffer(cbs[i]);
VkSubmitInfo si = {
VK_STRUCTURE_TYPE_SUBMIT_INFO, 0,
0, 0,
0,
1, &cbs[i],
0, 0,
};
{
AutoLock queueLock(lock);
vk->vkQueueSubmit(mQueue, 1, &si, 0);
}
}
VkPhysicalDeviceMemoryProperties memProps;
vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps);
return 0;
});
threads.push_back(thread);
}
auto cpuTimeStart = System::cpuTime();
for (uint32_t i = 0; i < kThreadCount; ++i) {
threads[i]->start();
}
for (uint32_t i = 0; i < kThreadCount; ++i) {
threads[i]->wait();
delete threads[i];
}
vk->vkQueueWaitIdle(mQueue);
vk->vkDeviceWaitIdle(mDevice);
auto cpuTime = System::cpuTime() - cpuTimeStart;
uint64_t duration_us = cpuTime.wall_time_us;
uint64_t duration_cpu_us = cpuTime.usageUs();
float ms = duration_us / 1000.0f;
float sec = duration_us / 1000000.0f;
float submitHz = (float)kTotalRecords / sec;
printf("Record %u times in %f ms. Rate: %f Hz per thread: %f Hz\n", kTotalRecords, ms, submitHz, (float)submitHz / (float)kThreadCount);
vk->vkFreeCommandBuffers(mDevice, commandPool, 1, cbs);
vk->vkDestroyCommandPool(mDevice, commandPool, nullptr);
EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets(
mDevice, pool, kThreadCount, sets.data()));
vk->vkDestroyDescriptorPool(mDevice, pool, nullptr);
}
// Multithreaded benchmarks: Round trip speed of light.
// Currently disabled until we land
// VulkanQueueSubmitWithCommands---syncEncodersFor** interferes with rc
// encoders
TEST_P(VulkanHalTest, DISABLED_MultithreadedRoundTrip) {
android::base::enableTracing();
constexpr uint32_t kThreadCount = 6;
std::vector<FunctorThread*> threads;
constexpr uint32_t kRecordsPerThread = 50;
constexpr uint32_t kTotalRecords = kThreadCount * kRecordsPerThread;
for (uint32_t i = 0; i < kThreadCount; ++i) {
FunctorThread* thread = new FunctorThread([this]() {
for (uint32_t j = 0; j < kRecordsPerThread; ++j) {
VkPhysicalDeviceMemoryProperties memProps;
vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps);
}
return 0;
});
threads.push_back(thread);
}
auto cpuTimeStart = System::cpuTime();
for (uint32_t i = 0; i < kThreadCount; ++i) {
threads[i]->start();
}
for (uint32_t i = 0; i < kThreadCount; ++i) {
threads[i]->wait();
delete threads[i];
}
vk->vkDeviceWaitIdle(mDevice);
auto cpuTime = System::cpuTime() - cpuTimeStart;
uint64_t duration_us = cpuTime.wall_time_us;
uint64_t duration_cpu_us = cpuTime.usageUs();
float ms = duration_us / 1000.0f;
float sec = duration_us / 1000000.0f;
float submitHz = (float)kTotalRecords / sec;
printf("Round trip %u times in %f ms. Rate: %f Hz per thread: %f Hz\n", kTotalRecords, ms, submitHz, (float)submitHz / (float)kThreadCount);
android::base::disableTracing();
usleep(1000000);
}
// Secondary command buffers.
TEST_P(VulkanHalTest, SecondaryCommandBuffers) {
constexpr uint32_t kThreadCount = 1;
VkDescriptorPool pool;
VkDescriptorSetLayout setLayout;
std::vector<VkDescriptorSet> sets(kThreadCount);
// Setup descriptor sets
EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets(
kThreadCount, kThreadCount,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
&pool, &setLayout, sets.data()));
VkPipelineLayout pipelineLayout;
VkPipelineLayoutCreateInfo pipelineLayoutCi = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 0, 0,
1, &setLayout,
0, nullptr,
};
vk->vkCreatePipelineLayout(mDevice, &pipelineLayoutCi, nullptr, &pipelineLayout);
// Setup command buffers
VkCommandPoolCreateInfo poolCi = {
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily,
};
VkCommandPool commandPool;
vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &commandPool);
VkCommandBuffer cbs[kThreadCount];
VkCommandBuffer cbs2[kThreadCount * 2];
VkCommandBufferAllocateInfo cbAi = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0,
commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount,
};
vk->vkAllocateCommandBuffers(mDevice, &cbAi, cbs);
VkCommandBufferAllocateInfo cbAi2 = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0,
commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount * 2,
};
vk->vkAllocateCommandBuffers(mDevice, &cbAi2, cbs2);
std::vector<FunctorThread*> threads;
constexpr uint32_t kRecordsPerThread = 20000;
constexpr uint32_t kRepeatSubmits = 1;
constexpr uint32_t kTotalRecords = kThreadCount * kRecordsPerThread * kRepeatSubmits;
for (uint32_t i = 0; i < kThreadCount; ++i) {
FunctorThread* thread = new FunctorThread([this, cbs, cbs2, sets, pipelineLayout, i]() {
VkCommandBufferInheritanceInfo inheritanceInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO, 0,
VK_NULL_HANDLE, 0,
VK_NULL_HANDLE,
VK_FALSE,
0,
0,
};
VkCommandBufferBeginInfo secondaryBeginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, &inheritanceInfo,
};
VkCommandBufferBeginInfo primaryBeginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 0,
};
for (uint32_t k = 0; k < kRepeatSubmits; ++k) {
// Secondaries
{
VkCommandBuffer first = cbs2[2 * i];
VkCommandBuffer second = cbs2[2 * i + 1];
vk->vkBeginCommandBuffer(first, &secondaryBeginInfo);
for (uint32_t j = 0; j < kRecordsPerThread / 2; ++j) {
vk->vkCmdBindDescriptorSets(
first,
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, 1, &sets[i], 0, nullptr);
}
vk->vkEndCommandBuffer(first);
vk->vkBeginCommandBuffer(second, &secondaryBeginInfo);
for (uint32_t j = 0; j < kRecordsPerThread / 2; ++j) {
vk->vkCmdBindDescriptorSets(
second,
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, 1, &sets[i], 0, nullptr);
}
vk->vkEndCommandBuffer(second);
}
vk->vkBeginCommandBuffer(cbs[i], &primaryBeginInfo);
vk->vkCmdExecuteCommands(cbs[i], 2, cbs2 + 2 * i);
vk->vkEndCommandBuffer(cbs[i]);
VkSubmitInfo si = {
VK_STRUCTURE_TYPE_SUBMIT_INFO, 0,
0, 0,
0,
1, &cbs[i],
0, 0,
};
vk->vkQueueSubmit(mQueue, 1, &si, 0);
}
VkPhysicalDeviceMemoryProperties memProps;
vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps);
return 0;
});
threads.push_back(thread);
}
auto cpuTimeStart = System::cpuTime();
for (uint32_t i = 0; i < kThreadCount; ++i) {
threads[i]->start();
}
for (uint32_t i = 0; i < kThreadCount; ++i) {
threads[i]->wait();
delete threads[i];
}
vk->vkQueueWaitIdle(mQueue);
vk->vkDeviceWaitIdle(mDevice);
auto cpuTime = System::cpuTime() - cpuTimeStart;
uint64_t duration_us = cpuTime.wall_time_us;
uint64_t duration_cpu_us = cpuTime.usageUs();
float ms = duration_us / 1000.0f;
float sec = duration_us / 1000000.0f;
float submitHz = (float)kTotalRecords / sec;
printf("Record %u times in %f ms. Rate: %f Hz per thread: %f Hz\n", kTotalRecords, ms, submitHz, (float)submitHz / (float)kThreadCount);
vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount * 2, cbs2);
vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount, cbs);
vk->vkDestroyCommandPool(mDevice, commandPool, nullptr);
EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets(
mDevice, pool, kThreadCount, sets.data()));
vk->vkDestroyDescriptorPool(mDevice, pool, nullptr);
}
// Secondary command buffers.
TEST_P(VulkanHalTest, SecondaryCommandBuffersWithDescriptorSetUpdate) {
constexpr uint32_t kThreadCount = 1;
VkDescriptorPool pool;
VkDescriptorSetLayout setLayout;
// Setup descriptor sets
uint32_t maxSetsTotal = 4;
std::vector<VkDescriptorSet> sets(maxSetsTotal);
VkSampler sampler;
VkBuffer buffers[2];
VkSamplerCreateInfo samplerCi = {
VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, 0, 0,
VK_FILTER_NEAREST,
VK_FILTER_NEAREST,
VK_SAMPLER_MIPMAP_MODE_NEAREST,
VK_SAMPLER_ADDRESS_MODE_REPEAT,
VK_SAMPLER_ADDRESS_MODE_REPEAT,
VK_SAMPLER_ADDRESS_MODE_REPEAT,
0.0f,
VK_FALSE,
1.0f,
VK_FALSE,
VK_COMPARE_OP_NEVER,
0.0f,
1.0f,
VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
VK_FALSE,
};
VkBufferCreateInfo bufCi = {
VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 0, 0,
4096,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_SHARING_MODE_EXCLUSIVE,
0, nullptr,
};
EXPECT_EQ(VK_SUCCESS,
vk->vkCreateSampler(
mDevice, &samplerCi, nullptr, &sampler));
EXPECT_EQ(VK_SUCCESS,
vk->vkCreateBuffer(mDevice, &bufCi, nullptr, &buffers[0]));
EXPECT_EQ(VK_SUCCESS,
vk->vkCreateBuffer(mDevice, &bufCi, nullptr, &buffers[1]));
VkDescriptorPoolSize poolSizes[] = {
{ VK_DESCRIPTOR_TYPE_SAMPLER, 1 * maxSetsTotal, },
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 8 * maxSetsTotal, },
};
VkDescriptorPoolCreateInfo dpCi = {
VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, 0,
VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
maxSetsTotal /* maxSets */,
2 /* poolSizeCount */,
poolSizes,
};
EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorPool(mDevice, &dpCi, nullptr, &pool));
VkDescriptorSetLayoutBinding bindings[] = {
{ 0, VK_DESCRIPTOR_TYPE_SAMPLER, 1, VK_SHADER_STAGE_VERTEX_BIT, &sampler, }, /* immutable sampler */
{ 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 6, VK_SHADER_STAGE_VERTEX_BIT, nullptr, },
{ 10, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, VK_SHADER_STAGE_VERTEX_BIT, nullptr, },
};
VkDescriptorSetLayoutCreateInfo setLayoutCi = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 0, 0,
sizeof(bindings) / sizeof(VkDescriptorSetLayoutBinding),
bindings,
};
EXPECT_EQ(VK_SUCCESS, vk->vkCreateDescriptorSetLayout(
mDevice, &setLayoutCi, nullptr, &setLayout));
std::vector<VkDescriptorSetLayout> setLayouts(maxSetsTotal, setLayout);
VkDescriptorSetAllocateInfo setAi = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, 0,
pool, maxSetsTotal, setLayouts.data(),
};
EXPECT_EQ(VK_SUCCESS, vk->vkAllocateDescriptorSets(
mDevice, &setAi, sets.data()));
EXPECT_EQ(VK_SUCCESS, vk->vkResetDescriptorPool(
mDevice, pool, 0));
EXPECT_EQ(VK_SUCCESS, vk->vkAllocateDescriptorSets(
mDevice, &setAi, sets.data()));
VkPipelineLayout pipelineLayout;
VkPipelineLayoutCreateInfo pipelineLayoutCi = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 0, 0,
1, &setLayout,
0, nullptr,
};
vk->vkCreatePipelineLayout(mDevice, &pipelineLayoutCi, nullptr, &pipelineLayout);
// Setup command buffers
VkCommandPoolCreateInfo poolCi = {
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily,
};
VkCommandPool commandPool;
vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &commandPool);
VkCommandBuffer cbs[kThreadCount];
VkCommandBuffer cbs2[kThreadCount * 2];
VkCommandBufferAllocateInfo cbAi = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0,
commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount,
};
vk->vkAllocateCommandBuffers(mDevice, &cbAi, cbs);
VkCommandBufferAllocateInfo cbAi2 = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0,
commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount * 2,
};
vk->vkAllocateCommandBuffers(mDevice, &cbAi2, cbs2);
std::vector<FunctorThread*> threads;
constexpr uint32_t kRecordsPerThread = 4;
constexpr uint32_t kRepeatSubmits = 2;
constexpr uint32_t kTotalRecords = kThreadCount * kRecordsPerThread * kRepeatSubmits;
for (uint32_t i = 0; i < kThreadCount; ++i) {
FunctorThread* thread = new FunctorThread([this, maxSetsTotal, buffers, cbs, cbs2, sets, pipelineLayout, i]() {
VkCommandBufferInheritanceInfo inheritanceInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO, 0,
VK_NULL_HANDLE, 0,
VK_NULL_HANDLE,
VK_FALSE,
0,
0,
};
VkCommandBufferBeginInfo secondaryBeginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, &inheritanceInfo,
};
VkCommandBufferBeginInfo primaryBeginInfo = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 0,
};
for (uint32_t k = 0; k < kRepeatSubmits; ++k) {
// Secondaries
{
VkCommandBuffer first = cbs2[2 * i];
VkCommandBuffer second = cbs2[2 * i + 1];
vk->vkBeginCommandBuffer(first, &secondaryBeginInfo);
for (uint32_t j = 0; j < kRecordsPerThread / 2; ++j) {
for (uint32_t l = 0; l < maxSetsTotal; ++l) {
VkDescriptorImageInfo immutableSamplerImageInfos[] = {
{ (VkSampler)0xdeadbeef, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL, },
{ (VkSampler)0xdeadbeef, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_GENERAL, },
};
VkDescriptorBufferInfo bufferInfos[] = {
{ buffers[0], 0, 16, },
{ buffers[1], 16, 32, },
{ buffers[0], 1024, 4096, },
{ buffers[1], 256, 512, },
{ buffers[0], 0, 512, },
{ buffers[1], 8, 48, },
};
VkWriteDescriptorSet descWrites[] = {
{
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0, sets[l],
0, 0, 1,
VK_DESCRIPTOR_TYPE_SAMPLER,
immutableSamplerImageInfos,
bufferInfos,
nullptr,
},
{
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0, sets[l],
1, 0, 2,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
immutableSamplerImageInfos,
bufferInfos,
nullptr,
},
{
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0, sets[l],
1, 4, 2,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
immutableSamplerImageInfos,
bufferInfos,
nullptr,
},
{
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 0, sets[l],
10, 0, 2,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
immutableSamplerImageInfos,
bufferInfos + 2,
nullptr,
},
};
VkCopyDescriptorSet descCopies[] = {
{
VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET, 0,
sets[l], 10, 0,
sets[l], 1, 2,
2,
},
{
VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET, 0,
sets[l], 1, 0,
sets[l], 10, 0,
2,
},
};
vk->vkUpdateDescriptorSets(
mDevice,
sizeof(descWrites) / sizeof(VkWriteDescriptorSet), descWrites,
sizeof(descCopies) / sizeof(VkCopyDescriptorSet), descCopies);
}
vk->vkCmdBindDescriptorSets(
first,
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, maxSetsTotal, &sets[0], 0, nullptr);
}
vk->vkEndCommandBuffer(first);
vk->vkBeginCommandBuffer(second, &secondaryBeginInfo);
for (uint32_t j = 0; j < kRecordsPerThread / 2; ++j) {
vk->vkCmdBindDescriptorSets(
second,
VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayout, 0, 1, &sets[i], 0, nullptr);
}
vk->vkEndCommandBuffer(second);
}
vk->vkBeginCommandBuffer(cbs[i], &primaryBeginInfo);
vk->vkCmdExecuteCommands(cbs[i], 2, cbs2 + 2 * i);
vk->vkEndCommandBuffer(cbs[i]);
VkSubmitInfo si = {
VK_STRUCTURE_TYPE_SUBMIT_INFO, 0,
0, 0,
0,
1, &cbs[i],
0, 0,
};
vk->vkQueueSubmit(mQueue, 1, &si, 0);
vk->vkQueueWaitIdle(mQueue);
}
VkPhysicalDeviceMemoryProperties memProps;
vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps);
return 0;
});
threads.push_back(thread);
}
auto cpuTimeStart = System::cpuTime();
for (uint32_t i = 0; i < kThreadCount; ++i) {
threads[i]->start();
}
for (uint32_t i = 0; i < kThreadCount; ++i) {
threads[i]->wait();
delete threads[i];
}
vk->vkQueueWaitIdle(mQueue);
vk->vkDeviceWaitIdle(mDevice);
auto cpuTime = System::cpuTime() - cpuTimeStart;
uint64_t duration_us = cpuTime.wall_time_us;
uint64_t duration_cpu_us = cpuTime.usageUs();
float ms = duration_us / 1000.0f;
float sec = duration_us / 1000000.0f;
float submitHz = (float)kTotalRecords / sec;
printf("Record %u times in %f ms. Rate: %f Hz per thread: %f Hz\n", kTotalRecords, ms, submitHz, (float)submitHz / (float)kThreadCount);
vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount * 2, cbs2);
vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount, cbs);
vk->vkDestroyCommandPool(mDevice, commandPool, nullptr);
EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets(
mDevice, pool, maxSetsTotal, sets.data()));
vk->vkDestroyDescriptorPool(mDevice, pool, nullptr);
}
// Flush
TEST_P(VulkanHalTest, Flush) {
constexpr uint32_t kThreadCount = 1;
VkDescriptorPool pool;
VkDescriptorSetLayout setLayout;
std::vector<VkDescriptorSet> sets(kThreadCount);
// Setup descriptor sets
EXPECT_EQ(VK_SUCCESS, allocateTestDescriptorSets(
kThreadCount, kThreadCount,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
&pool, &setLayout, sets.data()));
VkPipelineLayout pipelineLayout;
VkPipelineLayoutCreateInfo pipelineLayoutCi = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 0, 0,
1, &setLayout,
0, nullptr,
};
vk->vkCreatePipelineLayout(mDevice, &pipelineLayoutCi, nullptr, &pipelineLayout);
// Setup command buffers
VkCommandPoolCreateInfo poolCi = {
VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 0, 0, mGraphicsQueueFamily,
};
VkCommandPool commandPool;
vk->vkCreateCommandPool(mDevice, &poolCi, nullptr, &commandPool);
VkCommandBuffer cbs[kThreadCount];
VkCommandBuffer cbs2[kThreadCount * 2];
VkCommandBufferAllocateInfo cbAi = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0,
commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount,
};
vk->vkAllocateCommandBuffers(mDevice, &cbAi, cbs);
VkCommandBufferAllocateInfo cbAi2 = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 0,
commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, kThreadCount * 2,
};
vk->vkAllocateCommandBuffers(mDevice, &cbAi2, cbs2);
std::vector<FunctorThread*> threads;
constexpr uint32_t kRecordsPerThread = 800000;
constexpr uint32_t kTotalRecords = kThreadCount * kRecordsPerThread;
for (uint32_t i = 0; i < kThreadCount; ++i) {
FunctorThread* thread = new FunctorThread([this, cbs, cbs2, sets, pipelineLayout, i]() {
for (uint32_t k = 0; k < kRecordsPerThread; ++k) {
VkSubmitInfo si = {
VK_STRUCTURE_TYPE_SUBMIT_INFO, 0,
0, 0,
0,
0, nullptr,
0, 0,
};
vk->vkQueueSubmit(mQueue, 1, &si, 0);
}
VkPhysicalDeviceMemoryProperties memProps;
vk->vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps);
return 0;
});
threads.push_back(thread);
}
auto cpuTimeStart = System::cpuTime();
for (uint32_t i = 0; i < kThreadCount; ++i) {
threads[i]->start();
}
for (uint32_t i = 0; i < kThreadCount; ++i) {
threads[i]->wait();
delete threads[i];
}
vk->vkQueueWaitIdle(mQueue);
vk->vkDeviceWaitIdle(mDevice);
auto cpuTime = System::cpuTime() - cpuTimeStart;
uint64_t duration_us = cpuTime.wall_time_us;
uint64_t duration_cpu_us = cpuTime.usageUs();
float ms = duration_us / 1000.0f;
float sec = duration_us / 1000000.0f;
float submitHz = (float)kTotalRecords / sec;
printf("Record %u times in %f ms. Rate: %f Hz per thread: %f Hz\n", kTotalRecords, ms, submitHz, (float)submitHz / (float)kThreadCount);
vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount * 2, cbs2);
vk->vkFreeCommandBuffers(mDevice, commandPool, kThreadCount, cbs);
vk->vkDestroyCommandPool(mDevice, commandPool, nullptr);
EXPECT_EQ(VK_SUCCESS, vk->vkFreeDescriptorSets(
mDevice, pool, kThreadCount, sets.data()));
vk->vkDestroyDescriptorPool(mDevice, pool, nullptr);
}
INSTANTIATE_TEST_SUITE_P(
MultipleTransports,
VulkanHalTest,
testing::ValuesIn(GoldfishOpenglTestEnv::getTransportsToTest()));
} // namespace aemu