293 lines
14 KiB
C++
293 lines
14 KiB
C++
|
|
// Note: needs to be included before DisplayVk to avoid conflicts
|
||
|
|
// between gtest and x11 headers.
|
||
|
|
#include <gtest/gtest.h>
|
||
|
|
|
||
|
|
#include "DisplayVk.h"
|
||
|
|
|
||
|
|
#include "BorrowedImageVk.h"
|
||
|
|
#include "Standalone.h"
|
||
|
|
#include "aemu/base/synchronization/Lock.h"
|
||
|
|
#include "tests/VkTestUtils.h"
|
||
|
|
#include "vulkan/VulkanDispatch.h"
|
||
|
|
|
||
|
|
using gfxstream::DisplaySurface;
|
||
|
|
|
||
|
|
namespace gfxstream {
|
||
|
|
namespace vk {
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
class DisplayVkTest : public ::testing::Test {
|
||
|
|
protected:
|
||
|
|
using RenderTexture = RenderTextureVk;
|
||
|
|
|
||
|
|
static void SetUpTestCase() { k_vk = vkDispatch(false); }
|
||
|
|
|
||
|
|
void SetUp() override {
|
||
|
|
// skip the test when testing without a window
|
||
|
|
if (!shouldUseWindow()) {
|
||
|
|
GTEST_SKIP();
|
||
|
|
}
|
||
|
|
ASSERT_NE(k_vk, nullptr);
|
||
|
|
|
||
|
|
createInstance();
|
||
|
|
createWindowAndSurface();
|
||
|
|
m_window = createOrGetTestWindow(0, 0, k_width, k_height);
|
||
|
|
pickPhysicalDevice();
|
||
|
|
createLogicalDevice();
|
||
|
|
k_vk->vkGetDeviceQueue(m_vkDevice, m_compositorQueueFamilyIndex, 0, &m_compositorVkQueue);
|
||
|
|
m_compositorVkQueueLock = std::make_shared<android::base::Lock>();
|
||
|
|
k_vk->vkGetDeviceQueue(m_vkDevice, m_swapChainQueueFamilyIndex, 0, &m_swapChainVkQueue);
|
||
|
|
m_swapChainVkQueueLock = std::make_shared<android::base::Lock>();
|
||
|
|
VkCommandPoolCreateInfo commandPoolCi = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||
|
|
.queueFamilyIndex = m_compositorQueueFamilyIndex};
|
||
|
|
ASSERT_EQ(k_vk->vkCreateCommandPool(m_vkDevice, &commandPoolCi, nullptr, &m_vkCommandPool),
|
||
|
|
VK_SUCCESS);
|
||
|
|
m_displayVk = std::make_unique<DisplayVk>(
|
||
|
|
*k_vk, m_vkPhysicalDevice, m_swapChainQueueFamilyIndex, m_compositorQueueFamilyIndex,
|
||
|
|
m_vkDevice, m_compositorVkQueue, m_compositorVkQueueLock, m_swapChainVkQueue,
|
||
|
|
m_swapChainVkQueueLock);
|
||
|
|
m_displaySurface = std::make_unique<gfxstream::DisplaySurface>(
|
||
|
|
k_width, k_height,
|
||
|
|
DisplaySurfaceVk::create(*k_vk, m_vkInstance, m_window->getNativeWindow()));
|
||
|
|
ASSERT_NE(m_displaySurface, nullptr);
|
||
|
|
m_displayVk->bindToSurface(m_displaySurface.get());
|
||
|
|
}
|
||
|
|
|
||
|
|
void TearDown() override {
|
||
|
|
if (shouldUseWindow()) {
|
||
|
|
ASSERT_EQ(k_vk->vkQueueWaitIdle(m_compositorVkQueue), VK_SUCCESS);
|
||
|
|
ASSERT_EQ(k_vk->vkQueueWaitIdle(m_swapChainVkQueue), VK_SUCCESS);
|
||
|
|
|
||
|
|
m_displayVk.reset();
|
||
|
|
k_vk->vkDestroyCommandPool(m_vkDevice, m_vkCommandPool, nullptr);
|
||
|
|
k_vk->vkDestroyDevice(m_vkDevice, nullptr);
|
||
|
|
k_vk->vkDestroySurfaceKHR(m_vkInstance, m_vkSurface, nullptr);
|
||
|
|
k_vk->vkDestroyInstance(m_vkInstance, nullptr);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
std::unique_ptr<BorrowedImageInfoVk> createBorrowedImageInfo(
|
||
|
|
const std::unique_ptr<const RenderTexture>& texture) {
|
||
|
|
static uint32_t sTextureId = 0;
|
||
|
|
|
||
|
|
auto info = std::make_unique<BorrowedImageInfoVk>();
|
||
|
|
info->id = sTextureId++;
|
||
|
|
info->width = texture->m_vkImageCreateInfo.extent.width;
|
||
|
|
info->height = texture->m_vkImageCreateInfo.extent.height;
|
||
|
|
info->image = texture->m_vkImage;
|
||
|
|
info->imageCreateInfo = texture->m_vkImageCreateInfo;
|
||
|
|
info->preBorrowLayout = RenderTexture::k_vkImageLayout;
|
||
|
|
info->preBorrowQueueFamilyIndex = m_compositorQueueFamilyIndex;
|
||
|
|
info->postBorrowLayout = RenderTexture::k_vkImageLayout;
|
||
|
|
info->postBorrowQueueFamilyIndex = m_compositorQueueFamilyIndex;
|
||
|
|
return info;
|
||
|
|
}
|
||
|
|
|
||
|
|
static const VulkanDispatch* k_vk;
|
||
|
|
static constexpr uint32_t k_width = 0x100;
|
||
|
|
static constexpr uint32_t k_height = 0x100;
|
||
|
|
|
||
|
|
OSWindow *m_window;
|
||
|
|
VkInstance m_vkInstance = VK_NULL_HANDLE;
|
||
|
|
VkSurfaceKHR m_vkSurface = VK_NULL_HANDLE;
|
||
|
|
VkPhysicalDevice m_vkPhysicalDevice = VK_NULL_HANDLE;
|
||
|
|
uint32_t m_swapChainQueueFamilyIndex = 0;
|
||
|
|
uint32_t m_compositorQueueFamilyIndex = 0;
|
||
|
|
VkDevice m_vkDevice = VK_NULL_HANDLE;
|
||
|
|
VkQueue m_compositorVkQueue = VK_NULL_HANDLE;
|
||
|
|
std::shared_ptr<android::base::Lock> m_compositorVkQueueLock;
|
||
|
|
VkQueue m_swapChainVkQueue = VK_NULL_HANDLE;
|
||
|
|
std::shared_ptr<android::base::Lock> m_swapChainVkQueueLock;
|
||
|
|
VkCommandPool m_vkCommandPool = VK_NULL_HANDLE;
|
||
|
|
std::unique_ptr<DisplayVk> m_displayVk = nullptr;
|
||
|
|
std::unique_ptr<DisplaySurface> m_displaySurface = nullptr;
|
||
|
|
|
||
|
|
private:
|
||
|
|
void createInstance() {
|
||
|
|
VkApplicationInfo appInfo = {.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||
|
|
.pNext = nullptr,
|
||
|
|
.pApplicationName = "emulator SwapChainStateVk unittest",
|
||
|
|
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
|
||
|
|
.pEngineName = "No Engine",
|
||
|
|
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
|
||
|
|
.apiVersion = VK_API_VERSION_1_1};
|
||
|
|
auto extensions = SwapChainStateVk::getRequiredInstanceExtensions();
|
||
|
|
VkInstanceCreateInfo instanceCi = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||
|
|
.pApplicationInfo = &appInfo,
|
||
|
|
.enabledExtensionCount = static_cast<uint32_t>(extensions.size()),
|
||
|
|
.ppEnabledExtensionNames = extensions.data()};
|
||
|
|
ASSERT_EQ(k_vk->vkCreateInstance(&instanceCi, nullptr, &m_vkInstance), VK_SUCCESS);
|
||
|
|
ASSERT_TRUE(m_vkInstance != VK_NULL_HANDLE);
|
||
|
|
}
|
||
|
|
|
||
|
|
void createWindowAndSurface() {
|
||
|
|
m_window = createOrGetTestWindow(0, 0, k_width, k_height);
|
||
|
|
ASSERT_NE(m_window, nullptr);
|
||
|
|
// TODO(kaiyili, b/179477624): add support for other platforms
|
||
|
|
#ifdef _WIN32
|
||
|
|
VkWin32SurfaceCreateInfoKHR surfaceCi = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
|
||
|
|
.hinstance = GetModuleHandle(nullptr),
|
||
|
|
.hwnd = m_window->getNativeWindow()};
|
||
|
|
ASSERT_EQ(k_vk->vkCreateWin32SurfaceKHR(m_vkInstance, &surfaceCi, nullptr, &m_vkSurface),
|
||
|
|
VK_SUCCESS);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
void pickPhysicalDevice() {
|
||
|
|
uint32_t physicalDeviceCount = 0;
|
||
|
|
ASSERT_EQ(k_vk->vkEnumeratePhysicalDevices(m_vkInstance, &physicalDeviceCount, nullptr),
|
||
|
|
VK_SUCCESS);
|
||
|
|
ASSERT_GT(physicalDeviceCount, 0);
|
||
|
|
std::vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
|
||
|
|
ASSERT_EQ(k_vk->vkEnumeratePhysicalDevices(m_vkInstance, &physicalDeviceCount,
|
||
|
|
physicalDevices.data()),
|
||
|
|
VK_SUCCESS);
|
||
|
|
for (const auto &device : physicalDevices) {
|
||
|
|
uint32_t queueFamilyCount = 0;
|
||
|
|
k_vk->vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
|
||
|
|
ASSERT_GT(queueFamilyCount, 0);
|
||
|
|
std::vector<VkQueueFamilyProperties> queueProps(queueFamilyCount);
|
||
|
|
k_vk->vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount,
|
||
|
|
queueProps.data());
|
||
|
|
std::optional<uint32_t> maybeSwapChainQueueFamilyIndex = std::nullopt;
|
||
|
|
std::optional<uint32_t> maybeCompositorQueueFamilyIndex = std::nullopt;
|
||
|
|
for (uint32_t queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount;
|
||
|
|
queueFamilyIndex++) {
|
||
|
|
if (!maybeSwapChainQueueFamilyIndex.has_value() &&
|
||
|
|
SwapChainStateVk::validateQueueFamilyProperties(*k_vk, device, m_vkSurface,
|
||
|
|
queueFamilyIndex) &&
|
||
|
|
SwapChainStateVk::createSwapChainCi(*k_vk, m_vkSurface, device, k_width,
|
||
|
|
k_height, {queueFamilyIndex})) {
|
||
|
|
maybeSwapChainQueueFamilyIndex = queueFamilyIndex;
|
||
|
|
}
|
||
|
|
if (!maybeCompositorQueueFamilyIndex.has_value() &&
|
||
|
|
CompositorVk::queueSupportsComposition(queueProps[queueFamilyIndex])) {
|
||
|
|
maybeCompositorQueueFamilyIndex = queueFamilyIndex;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!maybeSwapChainQueueFamilyIndex.has_value() ||
|
||
|
|
!maybeCompositorQueueFamilyIndex.has_value()) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
m_swapChainQueueFamilyIndex = maybeSwapChainQueueFamilyIndex.value();
|
||
|
|
m_compositorQueueFamilyIndex = maybeCompositorQueueFamilyIndex.value();
|
||
|
|
m_vkPhysicalDevice = device;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
FAIL() << "Can't find a suitable VkPhysicalDevice.";
|
||
|
|
}
|
||
|
|
|
||
|
|
void createLogicalDevice() {
|
||
|
|
const float queuePriority = 1.0f;
|
||
|
|
std::vector<VkDeviceQueueCreateInfo> queueCis(0);
|
||
|
|
for (auto queueFamilyIndex : std::unordered_set<uint32_t>(
|
||
|
|
{m_swapChainQueueFamilyIndex, m_compositorQueueFamilyIndex})) {
|
||
|
|
VkDeviceQueueCreateInfo queueCi = {.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||
|
|
.queueFamilyIndex = queueFamilyIndex,
|
||
|
|
.queueCount = 1,
|
||
|
|
.pQueuePriorities = &queuePriority};
|
||
|
|
queueCis.push_back(queueCi);
|
||
|
|
}
|
||
|
|
VkPhysicalDeviceFeatures2 features = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||
|
|
.pNext = nullptr};
|
||
|
|
auto extensions = SwapChainStateVk::getRequiredDeviceExtensions();
|
||
|
|
VkDeviceCreateInfo deviceCi = {
|
||
|
|
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||
|
|
.pNext = &features,
|
||
|
|
.queueCreateInfoCount = static_cast<uint32_t>(queueCis.size()),
|
||
|
|
.pQueueCreateInfos = queueCis.data(),
|
||
|
|
.enabledLayerCount = 0,
|
||
|
|
.enabledExtensionCount = static_cast<uint32_t>(extensions.size()),
|
||
|
|
.ppEnabledExtensionNames = extensions.data(),
|
||
|
|
.pEnabledFeatures = nullptr};
|
||
|
|
ASSERT_EQ(k_vk->vkCreateDevice(m_vkPhysicalDevice, &deviceCi, nullptr, &m_vkDevice),
|
||
|
|
VK_SUCCESS);
|
||
|
|
ASSERT_TRUE(m_vkDevice != VK_NULL_HANDLE);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const VulkanDispatch* DisplayVkTest::k_vk = nullptr;
|
||
|
|
|
||
|
|
TEST_F(DisplayVkTest, Init) {}
|
||
|
|
|
||
|
|
TEST_F(DisplayVkTest, PostWithoutSurfaceShouldntCrash) {
|
||
|
|
uint32_t textureWidth = 20;
|
||
|
|
uint32_t textureHeight = 40;
|
||
|
|
DisplayVk displayVk(*k_vk, m_vkPhysicalDevice, m_swapChainQueueFamilyIndex,
|
||
|
|
m_compositorQueueFamilyIndex, m_vkDevice, m_compositorVkQueue,
|
||
|
|
m_compositorVkQueueLock, m_swapChainVkQueue, m_swapChainVkQueueLock);
|
||
|
|
auto texture = RenderTexture::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
|
||
|
|
m_vkCommandPool, textureWidth, textureHeight);
|
||
|
|
std::vector<uint32_t> pixels(textureWidth * textureHeight, 0);
|
||
|
|
ASSERT_TRUE(texture->write(pixels));
|
||
|
|
const auto imageInfo = createBorrowedImageInfo(texture);
|
||
|
|
displayVk.post(imageInfo.get());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(DisplayVkTest, SimplePost) {
|
||
|
|
uint32_t textureWidth = 20;
|
||
|
|
uint32_t textureHeight = 40;
|
||
|
|
auto texture = RenderTexture::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
|
||
|
|
m_vkCommandPool, textureWidth, textureHeight);
|
||
|
|
std::vector<uint32_t> pixels(textureWidth * textureHeight);
|
||
|
|
for (int i = 0; i < textureHeight; i++) {
|
||
|
|
for (int j = 0; j < textureWidth; j++) {
|
||
|
|
uint8_t *pixel = reinterpret_cast<uint8_t *>(&pixels[i * textureWidth + j]);
|
||
|
|
pixel[0] = static_cast<uint8_t>((i * 0xff / textureHeight) & 0xff);
|
||
|
|
pixel[1] = static_cast<uint8_t>((j * 0xff / textureWidth) & 0xff);
|
||
|
|
pixel[2] = 0;
|
||
|
|
pixel[3] = 0xff;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ASSERT_TRUE(texture->write(pixels));
|
||
|
|
std::vector<std::shared_future<void>> waitForGpuFutures;
|
||
|
|
for (uint32_t i = 0; i < 10; i++) {
|
||
|
|
const auto imageInfo = createBorrowedImageInfo(texture);
|
||
|
|
auto postResult = m_displayVk->post(imageInfo.get());
|
||
|
|
ASSERT_TRUE(postResult.success);
|
||
|
|
waitForGpuFutures.emplace_back(std::move(postResult.postCompletedWaitable));
|
||
|
|
}
|
||
|
|
for (auto &waitForGpuFuture : waitForGpuFutures) {
|
||
|
|
waitForGpuFuture.wait();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_F(DisplayVkTest, PostTwoColorBuffers) {
|
||
|
|
uint32_t textureWidth = 20;
|
||
|
|
uint32_t textureHeight = 40;
|
||
|
|
auto redTexture =
|
||
|
|
RenderTexture::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
|
||
|
|
m_vkCommandPool, textureWidth, textureHeight);
|
||
|
|
auto greenTexture =
|
||
|
|
RenderTexture::create(*k_vk, m_vkDevice, m_vkPhysicalDevice, m_compositorVkQueue,
|
||
|
|
m_vkCommandPool, textureWidth, textureHeight);
|
||
|
|
uint32_t red = 0xff0000ff;
|
||
|
|
uint32_t green = 0xff00ff00;
|
||
|
|
std::vector<uint32_t> redPixels(textureWidth * textureHeight, red);
|
||
|
|
std::vector<uint32_t> greenPixels(textureWidth * textureHeight, green);
|
||
|
|
ASSERT_TRUE(redTexture->write(redPixels));
|
||
|
|
ASSERT_TRUE(greenTexture->write(greenPixels));
|
||
|
|
std::vector<std::shared_future<void>> waitForGpuFutures;
|
||
|
|
for (uint32_t i = 0; i < 10; i++) {
|
||
|
|
const auto redImageInfo = createBorrowedImageInfo(redTexture);
|
||
|
|
const auto greenImageInfo = createBorrowedImageInfo(greenTexture);
|
||
|
|
auto redPostResult = m_displayVk->post(redImageInfo.get());
|
||
|
|
ASSERT_TRUE(redPostResult.success);
|
||
|
|
waitForGpuFutures.emplace_back(std::move(redPostResult.postCompletedWaitable));
|
||
|
|
|
||
|
|
auto greenPostResult = m_displayVk->post(greenImageInfo.get());
|
||
|
|
ASSERT_TRUE(greenPostResult.success);
|
||
|
|
waitForGpuFutures.emplace_back(std::move(greenPostResult.postCompletedWaitable));
|
||
|
|
}
|
||
|
|
for (auto &waitForGpuFuture : waitForGpuFutures) {
|
||
|
|
waitForGpuFuture.wait();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
} // namespace vk
|
||
|
|
} // namespace gfxstream
|