blob: 7d749a1ed8c1d50211005c39a93f26d453b7869e [file] [log] [blame]
// Copyright (C) 2023 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 <log/log.h>
#include <atomic>
#include <thread>
#include "GfxstreamEnd2EndTestUtils.h"
#include "GfxstreamEnd2EndTests.h"
#include "gfxstream/Expected.h"
#include "shaders/blit_sampler2d_frag.h"
#include "shaders/fullscreen_triangle_with_uv_vert.h"
namespace gfxstream {
namespace tests {
namespace {
using namespace std::chrono_literals;
using testing::Eq;
using testing::Ge;
using testing::IsEmpty;
using testing::IsNull;
using testing::IsTrue;
using testing::Ne;
using testing::Not;
using testing::NotNull;
template <typename DurationType>
constexpr uint64_t AsVkTimeout(DurationType duration) {
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count());
}
class GfxstreamEnd2EndVkTest : public GfxstreamEnd2EndTest {
protected:
// Gfxstream uses a vkQueueSubmit() to signal the VkFence and VkSemaphore used
// in vkAcquireImageANDROID() calls. The guest is not aware of this and may try
// to vkDestroyFence() and vkDestroySemaphore() (because the VkImage, VkFence,
// and VkSemaphore may have been unused from the guest point of view) while the
// host's command buffer is running. Gfxstream needs to ensure that it performs
// the necessary tracking to not delete the VkFence and VkSemaphore while they
// are in use on the host.
void DoAcquireImageAndroidWithSync(bool withFence, bool withSemaphore) {
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
const uint32_t width = 32;
const uint32_t height = 32;
auto ahb = GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate(
*mGralloc, width, height, GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM));
const VkNativeBufferANDROID imageNativeBufferInfo = {
.sType = VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID,
.handle = mGralloc->getNativeHandle(ahb),
};
auto vkAcquireImageANDROID =
PFN_vkAcquireImageANDROID(device->getProcAddr("vkAcquireImageANDROID"));
ASSERT_THAT(vkAcquireImageANDROID, NotNull());
const vkhpp::ImageCreateInfo imageCreateInfo = {
.pNext = &imageNativeBufferInfo,
.imageType = vkhpp::ImageType::e2D,
.extent.width = width,
.extent.height = height,
.extent.depth = 1,
.mipLevels = 1,
.arrayLayers = 1,
.format = vkhpp::Format::eR8G8B8A8Unorm,
.tiling = vkhpp::ImageTiling::eOptimal,
.initialLayout = vkhpp::ImageLayout::eUndefined,
.usage = vkhpp::ImageUsageFlagBits::eSampled | vkhpp::ImageUsageFlagBits::eTransferDst |
vkhpp::ImageUsageFlagBits::eTransferSrc,
.sharingMode = vkhpp::SharingMode::eExclusive,
.samples = vkhpp::SampleCountFlagBits::e1,
};
auto image = device->createImageUnique(imageCreateInfo).value;
vkhpp::MemoryRequirements imageMemoryRequirements{};
device->getImageMemoryRequirements(*image, &imageMemoryRequirements);
const uint32_t imageMemoryIndex = utils::getMemoryType(
physicalDevice, imageMemoryRequirements, vkhpp::MemoryPropertyFlagBits::eDeviceLocal);
ASSERT_THAT(imageMemoryIndex, Not(Eq(-1)));
const vkhpp::MemoryAllocateInfo imageMemoryAllocateInfo = {
.allocationSize = imageMemoryRequirements.size,
.memoryTypeIndex = imageMemoryIndex,
};
auto imageMemory = device->allocateMemoryUnique(imageMemoryAllocateInfo).value;
ASSERT_THAT(imageMemory, IsValidHandle());
ASSERT_THAT(device->bindImageMemory(*image, *imageMemory, 0), IsVkSuccess());
vkhpp::UniqueFence fence;
if (withFence) {
fence = device->createFenceUnique(vkhpp::FenceCreateInfo()).value;
}
vkhpp::UniqueSemaphore semaphore;
if (withSemaphore) {
semaphore = device->createSemaphoreUnique(vkhpp::SemaphoreCreateInfo()).value;
}
auto result = vkAcquireImageANDROID(*device, *image, -1, *semaphore, *fence);
ASSERT_THAT(result, Eq(VK_SUCCESS));
if (withFence) {
fence.reset();
}
if (withSemaphore) {
semaphore.reset();
}
}
Result<Ok> DoCommandsImmediate(
TypicalVkTestEnvironment& vk,
const std::function<Result<Ok>(vkhpp::UniqueCommandBuffer&)>& func,
const std::vector<vkhpp::UniqueSemaphore>& semaphores_wait = {},
const std::vector<vkhpp::UniqueSemaphore>& semaphores_signal = {}) {
const vkhpp::CommandPoolCreateInfo commandPoolCreateInfo = {
.queueFamilyIndex = vk.queueFamilyIndex,
};
auto commandPool =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->createCommandPoolUnique(commandPoolCreateInfo));
const vkhpp::CommandBufferAllocateInfo commandBufferAllocateInfo = {
.commandPool = *commandPool,
.level = vkhpp::CommandBufferLevel::ePrimary,
.commandBufferCount = 1,
};
auto commandBuffers = GFXSTREAM_EXPECT_VKHPP_RV(
vk.device->allocateCommandBuffersUnique(commandBufferAllocateInfo));
auto commandBuffer = std::move(commandBuffers[0]);
const vkhpp::CommandBufferBeginInfo commandBufferBeginInfo = {
.flags = vkhpp::CommandBufferUsageFlagBits::eOneTimeSubmit,
};
commandBuffer->begin(commandBufferBeginInfo);
GFXSTREAM_EXPECT(func(commandBuffer));
commandBuffer->end();
std::vector<vkhpp::CommandBuffer> commandBufferHandles;
commandBufferHandles.push_back(*commandBuffer);
std::vector<vkhpp::Semaphore> semaphoreHandlesWait;
semaphoreHandlesWait.reserve(semaphores_wait.size());
for (const auto& s : semaphores_wait) {
semaphoreHandlesWait.emplace_back(*s);
}
std::vector<vkhpp::Semaphore> semaphoreHandlesSignal;
semaphoreHandlesSignal.reserve(semaphores_signal.size());
for (const auto& s : semaphores_signal) {
semaphoreHandlesSignal.emplace_back(*s);
}
vkhpp::SubmitInfo submitInfo = {
.commandBufferCount = static_cast<uint32_t>(commandBufferHandles.size()),
.pCommandBuffers = commandBufferHandles.data(),
};
if (!semaphoreHandlesWait.empty()) {
submitInfo.waitSemaphoreCount = static_cast<uint32_t>(semaphoreHandlesWait.size());
submitInfo.pWaitSemaphores = semaphoreHandlesWait.data();
}
if (!semaphoreHandlesSignal.empty()) {
submitInfo.signalSemaphoreCount = static_cast<uint32_t>(semaphoreHandlesSignal.size());
submitInfo.pSignalSemaphores = semaphoreHandlesSignal.data();
}
vk.queue.submit(submitInfo);
vk.queue.waitIdle();
return Ok{};
}
struct BufferWithMemory {
vkhpp::UniqueBuffer buffer;
vkhpp::UniqueDeviceMemory bufferMemory;
};
Result<BufferWithMemory> CreateBuffer(TypicalVkTestEnvironment& vk,
vkhpp::DeviceSize bufferSize,
vkhpp::BufferUsageFlags bufferUsages,
vkhpp::MemoryPropertyFlags bufferMemoryProperties,
const uint8_t* data = nullptr,
vkhpp::DeviceSize dataSize = 0) {
const vkhpp::BufferCreateInfo bufferCreateInfo = {
.size = static_cast<VkDeviceSize>(bufferSize),
.usage = bufferUsages,
.sharingMode = vkhpp::SharingMode::eExclusive,
};
auto buffer = GFXSTREAM_EXPECT_VKHPP_RV(vk.device->createBufferUnique(bufferCreateInfo));
vkhpp::MemoryRequirements bufferMemoryRequirements{};
vk.device->getBufferMemoryRequirements(*buffer, &bufferMemoryRequirements);
const auto bufferMemoryTypeIndex = utils::getMemoryType(
vk.physicalDevice, bufferMemoryRequirements, bufferMemoryProperties);
const vkhpp::MemoryAllocateInfo bufferMemoryAllocateInfo = {
.allocationSize = bufferMemoryRequirements.size,
.memoryTypeIndex = bufferMemoryTypeIndex,
};
auto bufferMemory =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->allocateMemoryUnique(bufferMemoryAllocateInfo));
GFXSTREAM_EXPECT_VKHPP_RESULT(vk.device->bindBufferMemory(*buffer, *bufferMemory, 0));
if (data != nullptr) {
if (!(bufferUsages & vkhpp::BufferUsageFlagBits::eTransferDst)) {
return gfxstream::unexpected(
"Must request transfer dst usage when creating buffer with data");
}
if (!(bufferMemoryProperties & vkhpp::MemoryPropertyFlagBits::eHostVisible)) {
return gfxstream::unexpected(
"Must request host visible mem property when creating buffer with data");
}
void* mapped =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->mapMemory(*bufferMemory, 0, bufferSize));
std::memcpy(mapped, data, dataSize);
if (!(bufferMemoryProperties & vkhpp::MemoryPropertyFlagBits::eHostVisible)) {
vk.device->flushMappedMemoryRanges(vkhpp::MappedMemoryRange{
.memory = *bufferMemory,
.offset = 0,
.size = VK_WHOLE_SIZE,
});
}
vk.device->unmapMemory(*bufferMemory);
}
return BufferWithMemory{
.buffer = std::move(buffer),
.bufferMemory = std::move(bufferMemory),
};
}
struct ImageWithMemory {
std::optional<vkhpp::UniqueSamplerYcbcrConversion> imageSamplerConversion;
vkhpp::UniqueSampler imageSampler;
vkhpp::UniqueDeviceMemory imageMemory;
vkhpp::UniqueImage image;
vkhpp::UniqueImageView imageView;
};
Result<ImageWithMemory> CreateImageWithAhb(TypicalVkTestEnvironment& vk,
const ScopedAHardwareBuffer& ahb,
const vkhpp::ImageUsageFlags usages,
const vkhpp::ImageLayout layout) {
const auto ahbHandle = mGralloc->getNativeHandle(ahb);
if (ahbHandle == nullptr) {
return gfxstream::unexpected("Failed to query native handle.");
}
const auto ahbFormat = mGralloc->getFormat(ahb);
const bool ahbIsYuv = ahbFormat == GFXSTREAM_AHB_FORMAT_YV12 ||
ahbFormat == GFXSTREAM_AHB_FORMAT_Y8Cb8Cr8_420;
auto vkGetAndroidHardwareBufferPropertiesANDROID =
reinterpret_cast<PFN_vkGetAndroidHardwareBufferPropertiesANDROID>(
vk.device->getProcAddr("vkGetAndroidHardwareBufferPropertiesANDROID"));
if (vkGetAndroidHardwareBufferPropertiesANDROID == nullptr) {
return gfxstream::unexpected(
"Failed to query vkGetAndroidHardwareBufferPropertiesANDROID().");
}
VkAndroidHardwareBufferFormatPropertiesANDROID ahbFormatProperties = {
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID,
.pNext = nullptr,
};
VkAndroidHardwareBufferPropertiesANDROID ahbProperties = {
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
.pNext = &ahbFormatProperties,
};
if (vkGetAndroidHardwareBufferPropertiesANDROID(*vk.device, ahb, &ahbProperties) !=
VK_SUCCESS) {
return gfxstream::unexpected("Failed to query ahb properties.");
}
const VkExternalFormatANDROID externalFormat = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
.externalFormat = ahbFormatProperties.externalFormat,
};
std::optional<vkhpp::UniqueSamplerYcbcrConversion> imageSamplerConversion;
std::optional<vkhpp::SamplerYcbcrConversionInfo> samplerConversionInfo;
if (ahbIsYuv) {
const vkhpp::SamplerYcbcrConversionCreateInfo conversionCreateInfo = {
.pNext = &externalFormat,
.format = static_cast<vkhpp::Format>(ahbFormatProperties.format),
.ycbcrModel = static_cast<vkhpp::SamplerYcbcrModelConversion>(
ahbFormatProperties.suggestedYcbcrModel),
.ycbcrRange =
static_cast<vkhpp::SamplerYcbcrRange>(ahbFormatProperties.suggestedYcbcrRange),
.components =
{
.r = static_cast<vkhpp::ComponentSwizzle>(
ahbFormatProperties.samplerYcbcrConversionComponents.r),
.g = static_cast<vkhpp::ComponentSwizzle>(
ahbFormatProperties.samplerYcbcrConversionComponents.g),
.b = static_cast<vkhpp::ComponentSwizzle>(
ahbFormatProperties.samplerYcbcrConversionComponents.b),
.a = static_cast<vkhpp::ComponentSwizzle>(
ahbFormatProperties.samplerYcbcrConversionComponents.a),
},
.xChromaOffset =
static_cast<vkhpp::ChromaLocation>(ahbFormatProperties.suggestedXChromaOffset),
.yChromaOffset =
static_cast<vkhpp::ChromaLocation>(ahbFormatProperties.suggestedYChromaOffset),
.chromaFilter = vkhpp::Filter::eNearest,
.forceExplicitReconstruction = VK_FALSE,
};
imageSamplerConversion = GFXSTREAM_EXPECT_VKHPP_RV(
vk.device->createSamplerYcbcrConversionUnique(conversionCreateInfo));
samplerConversionInfo = vkhpp::SamplerYcbcrConversionInfo{
.conversion = **imageSamplerConversion,
};
}
const vkhpp::SamplerCreateInfo samplerCreateInfo = {
.pNext = ahbIsYuv ? &samplerConversionInfo : nullptr,
.magFilter = vkhpp::Filter::eNearest,
.minFilter = vkhpp::Filter::eNearest,
.mipmapMode = vkhpp::SamplerMipmapMode::eNearest,
.addressModeU = vkhpp::SamplerAddressMode::eClampToEdge,
.addressModeV = vkhpp::SamplerAddressMode::eClampToEdge,
.addressModeW = vkhpp::SamplerAddressMode::eClampToEdge,
.mipLodBias = 0.0f,
.anisotropyEnable = VK_FALSE,
.maxAnisotropy = 1.0f,
.compareEnable = VK_FALSE,
.compareOp = vkhpp::CompareOp::eLessOrEqual,
.minLod = 0.0f,
.maxLod = 0.0f,
.borderColor = vkhpp::BorderColor::eIntTransparentBlack,
.unnormalizedCoordinates = VK_FALSE,
};
auto imageSampler =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->createSamplerUnique(samplerCreateInfo));
const VkExternalMemoryImageCreateInfo externalMemoryImageCreateInfo = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
.pNext = &externalFormat,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
};
const vkhpp::ImageCreateInfo imageCreateInfo = {
.pNext = &externalMemoryImageCreateInfo,
.imageType = vkhpp::ImageType::e2D,
.format = static_cast<vkhpp::Format>(ahbFormatProperties.format),
.extent =
{
.width = mGralloc->getWidth(ahb),
.height = mGralloc->getHeight(ahb),
.depth = 1,
},
.mipLevels = 1,
.arrayLayers = 1,
.samples = vkhpp::SampleCountFlagBits::e1,
.tiling = vkhpp::ImageTiling::eOptimal,
.usage = usages,
.sharingMode = vkhpp::SharingMode::eExclusive,
.initialLayout = vkhpp::ImageLayout::eUndefined,
};
auto image = GFXSTREAM_EXPECT_VKHPP_RV(vk.device->createImageUnique(imageCreateInfo));
const vkhpp::MemoryRequirements imageMemoryRequirements = {
.size = ahbProperties.allocationSize,
.alignment = 0,
.memoryTypeBits = ahbProperties.memoryTypeBits,
};
const uint32_t imageMemoryIndex =
utils::getMemoryType(vk.physicalDevice, imageMemoryRequirements,
vkhpp::MemoryPropertyFlagBits::eDeviceLocal);
const vkhpp::ImportAndroidHardwareBufferInfoANDROID importAhbInfo = {
.buffer = ahb,
};
const vkhpp::MemoryDedicatedAllocateInfo importMemoryDedicatedInfo = {
.pNext = &importAhbInfo,
.image = *image,
};
const vkhpp::MemoryAllocateInfo imageMemoryAllocateInfo = {
.pNext = &importMemoryDedicatedInfo,
.allocationSize = imageMemoryRequirements.size,
.memoryTypeIndex = imageMemoryIndex,
};
auto imageMemory =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->allocateMemoryUnique(imageMemoryAllocateInfo));
vk.device->bindImageMemory(*image, *imageMemory, 0);
const vkhpp::ImageViewCreateInfo imageViewCreateInfo = {
.pNext = &samplerConversionInfo,
.image = *image,
.viewType = vkhpp::ImageViewType::e2D,
.format = static_cast<vkhpp::Format>(ahbFormatProperties.format),
.components =
{
.r = vkhpp::ComponentSwizzle::eIdentity,
.g = vkhpp::ComponentSwizzle::eIdentity,
.b = vkhpp::ComponentSwizzle::eIdentity,
.a = vkhpp::ComponentSwizzle::eIdentity,
},
.subresourceRange =
{
.aspectMask = vkhpp::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
auto imageView =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->createImageViewUnique(imageViewCreateInfo));
GFXSTREAM_EXPECT(DoCommandsImmediate(vk, [&](vkhpp::UniqueCommandBuffer& cmd) {
const std::vector<vkhpp::ImageMemoryBarrier> imageMemoryBarriers = {
vkhpp::ImageMemoryBarrier{
.srcAccessMask = {},
.dstAccessMask = vkhpp::AccessFlagBits::eTransferWrite,
.oldLayout = vkhpp::ImageLayout::eUndefined,
.newLayout = layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = *image,
.subresourceRange =
{
.aspectMask = vkhpp::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
},
};
cmd->pipelineBarrier(
/*srcStageMask=*/vkhpp::PipelineStageFlagBits::eAllCommands,
/*dstStageMask=*/vkhpp::PipelineStageFlagBits::eAllCommands,
/*dependencyFlags=*/{},
/*memoryBarriers=*/{},
/*bufferMemoryBarriers=*/{},
/*imageMemoryBarriers=*/imageMemoryBarriers);
return Ok{};
}));
return ImageWithMemory{
.imageSamplerConversion = std::move(imageSamplerConversion),
.imageSampler = std::move(imageSampler),
.imageMemory = std::move(imageMemory),
.image = std::move(image),
.imageView = std::move(imageView),
};
}
Result<ImageWithMemory> CreateImage(TypicalVkTestEnvironment& vk, uint32_t width,
uint32_t height, vkhpp::Format format,
vkhpp::ImageUsageFlags usages,
vkhpp::MemoryPropertyFlags memoryProperties,
vkhpp::ImageLayout returnedLayout) {
const vkhpp::ImageCreateInfo imageCreateInfo = {
.imageType = vkhpp::ImageType::e2D,
.format = format,
.extent =
{
.width = width,
.height = height,
.depth = 1,
},
.mipLevels = 1,
.arrayLayers = 1,
.samples = vkhpp::SampleCountFlagBits::e1,
.tiling = vkhpp::ImageTiling::eOptimal,
.usage = usages,
.sharingMode = vkhpp::SharingMode::eExclusive,
.initialLayout = vkhpp::ImageLayout::eUndefined,
};
auto image = GFXSTREAM_EXPECT_VKHPP_RV(vk.device->createImageUnique(imageCreateInfo));
const auto memoryRequirements = vk.device->getImageMemoryRequirements(*image);
const uint32_t memoryIndex =
utils::getMemoryType(vk.physicalDevice, memoryRequirements, memoryProperties);
const vkhpp::MemoryAllocateInfo imageMemoryAllocateInfo = {
.allocationSize = memoryRequirements.size,
.memoryTypeIndex = memoryIndex,
};
auto imageMemory =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->allocateMemoryUnique(imageMemoryAllocateInfo));
vk.device->bindImageMemory(*image, *imageMemory, 0);
const vkhpp::ImageViewCreateInfo imageViewCreateInfo = {
.image = *image,
.viewType = vkhpp::ImageViewType::e2D,
.format = format,
.components =
{
.r = vkhpp::ComponentSwizzle::eIdentity,
.g = vkhpp::ComponentSwizzle::eIdentity,
.b = vkhpp::ComponentSwizzle::eIdentity,
.a = vkhpp::ComponentSwizzle::eIdentity,
},
.subresourceRange =
{
.aspectMask = vkhpp::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
auto imageView =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->createImageViewUnique(imageViewCreateInfo));
GFXSTREAM_EXPECT(DoCommandsImmediate(vk, [&](vkhpp::UniqueCommandBuffer& cmd) {
const std::vector<vkhpp::ImageMemoryBarrier> imageMemoryBarriers = {
vkhpp::ImageMemoryBarrier{
.srcAccessMask = {},
.dstAccessMask = vkhpp::AccessFlagBits::eTransferWrite,
.oldLayout = vkhpp::ImageLayout::eUndefined,
.newLayout = returnedLayout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = *image,
.subresourceRange =
{
.aspectMask = vkhpp::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
},
};
cmd->pipelineBarrier(
/*srcStageMask=*/vkhpp::PipelineStageFlagBits::eAllCommands,
/*dstStageMask=*/vkhpp::PipelineStageFlagBits::eAllCommands,
/*dependencyFlags=*/{},
/*memoryBarriers=*/{},
/*bufferMemoryBarriers=*/{},
/*imageMemoryBarriers=*/imageMemoryBarriers);
return Ok{};
}));
return ImageWithMemory{
.image = std::move(image),
.imageMemory = std::move(imageMemory),
.imageView = std::move(imageView),
};
}
struct FramebufferWithAttachments {
std::optional<ImageWithMemory> colorAttachment;
std::optional<ImageWithMemory> depthAttachment;
vkhpp::UniqueRenderPass renderpass;
vkhpp::UniqueFramebuffer framebuffer;
};
Result<FramebufferWithAttachments> CreateFramebuffer(
TypicalVkTestEnvironment& vk, uint32_t width, uint32_t height,
vkhpp::Format colorAttachmentFormat = vkhpp::Format::eUndefined,
vkhpp::Format depthAttachmentFormat = vkhpp::Format::eUndefined) {
std::optional<ImageWithMemory> colorAttachment;
if (colorAttachmentFormat != vkhpp::Format::eUndefined) {
colorAttachment =
GFXSTREAM_EXPECT(CreateImage(vk, width, height, colorAttachmentFormat,
vkhpp::ImageUsageFlagBits::eColorAttachment |
vkhpp::ImageUsageFlagBits::eTransferSrc,
vkhpp::MemoryPropertyFlagBits::eDeviceLocal,
vkhpp::ImageLayout::eColorAttachmentOptimal));
}
std::optional<ImageWithMemory> depthAttachment;
if (depthAttachmentFormat != vkhpp::Format::eUndefined) {
depthAttachment =
GFXSTREAM_EXPECT(CreateImage(vk, width, height, depthAttachmentFormat,
vkhpp::ImageUsageFlagBits::eDepthStencilAttachment |
vkhpp::ImageUsageFlagBits::eTransferSrc,
vkhpp::MemoryPropertyFlagBits::eDeviceLocal,
vkhpp::ImageLayout::eDepthStencilAttachmentOptimal));
}
std::vector<vkhpp::AttachmentDescription> attachments;
std::optional<vkhpp::AttachmentReference> colorAttachmentReference;
if (colorAttachmentFormat != vkhpp::Format::eUndefined) {
attachments.push_back(vkhpp::AttachmentDescription{
.format = colorAttachmentFormat,
.samples = vkhpp::SampleCountFlagBits::e1,
.loadOp = vkhpp::AttachmentLoadOp::eClear,
.storeOp = vkhpp::AttachmentStoreOp::eStore,
.stencilLoadOp = vkhpp::AttachmentLoadOp::eClear,
.stencilStoreOp = vkhpp::AttachmentStoreOp::eStore,
.initialLayout = vkhpp::ImageLayout::eColorAttachmentOptimal,
.finalLayout = vkhpp::ImageLayout::eColorAttachmentOptimal,
});
colorAttachmentReference = vkhpp::AttachmentReference{
.attachment = static_cast<uint32_t>(attachments.size() - 1),
.layout = vkhpp::ImageLayout::eColorAttachmentOptimal,
};
}
std::optional<vkhpp::AttachmentReference> depthAttachmentReference;
if (depthAttachmentFormat != vkhpp::Format::eUndefined) {
attachments.push_back(vkhpp::AttachmentDescription{
.format = depthAttachmentFormat,
.samples = vkhpp::SampleCountFlagBits::e1,
.loadOp = vkhpp::AttachmentLoadOp::eClear,
.storeOp = vkhpp::AttachmentStoreOp::eStore,
.stencilLoadOp = vkhpp::AttachmentLoadOp::eClear,
.stencilStoreOp = vkhpp::AttachmentStoreOp::eStore,
.initialLayout = vkhpp::ImageLayout::eColorAttachmentOptimal,
.finalLayout = vkhpp::ImageLayout::eColorAttachmentOptimal,
});
depthAttachmentReference = vkhpp::AttachmentReference{
.attachment = static_cast<uint32_t>(attachments.size() - 1),
.layout = vkhpp::ImageLayout::eDepthStencilAttachmentOptimal,
};
}
vkhpp::SubpassDependency dependency = {
.srcSubpass = 0,
.dstSubpass = 0,
.srcStageMask = {},
.dstStageMask = vkhpp::PipelineStageFlagBits::eFragmentShader,
.srcAccessMask = {},
.dstAccessMask = vkhpp::AccessFlagBits::eInputAttachmentRead,
.dependencyFlags = vkhpp::DependencyFlagBits::eByRegion,
};
if (colorAttachmentFormat != vkhpp::Format::eUndefined) {
dependency.srcStageMask |= vkhpp::PipelineStageFlagBits::eColorAttachmentOutput;
dependency.dstStageMask |= vkhpp::PipelineStageFlagBits::eColorAttachmentOutput;
dependency.srcAccessMask |= vkhpp::AccessFlagBits::eColorAttachmentWrite;
}
if (depthAttachmentFormat != vkhpp::Format::eUndefined) {
dependency.srcStageMask |= vkhpp::PipelineStageFlagBits::eColorAttachmentOutput;
dependency.dstStageMask |= vkhpp::PipelineStageFlagBits::eColorAttachmentOutput;
dependency.srcAccessMask |= vkhpp::AccessFlagBits::eColorAttachmentWrite;
}
vkhpp::SubpassDescription subpass = {
.pipelineBindPoint = vkhpp::PipelineBindPoint::eGraphics,
.inputAttachmentCount = 0,
.pInputAttachments = nullptr,
.colorAttachmentCount = 0,
.pColorAttachments = nullptr,
.pResolveAttachments = nullptr,
.pDepthStencilAttachment = nullptr,
.pPreserveAttachments = nullptr,
};
if (colorAttachmentFormat != vkhpp::Format::eUndefined) {
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &*colorAttachmentReference;
}
if (depthAttachmentFormat != vkhpp::Format::eUndefined) {
subpass.pDepthStencilAttachment = &*depthAttachmentReference;
}
const vkhpp::RenderPassCreateInfo renderpassCreateInfo = {
.attachmentCount = static_cast<uint32_t>(attachments.size()),
.pAttachments = attachments.data(),
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = &dependency,
};
auto renderpass =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->createRenderPassUnique(renderpassCreateInfo));
std::vector<vkhpp::ImageView> framebufferAttachments;
if (colorAttachment) {
framebufferAttachments.push_back(*colorAttachment->imageView);
}
if (depthAttachment) {
framebufferAttachments.push_back(*depthAttachment->imageView);
}
const vkhpp::FramebufferCreateInfo framebufferCreateInfo = {
.renderPass = *renderpass,
.attachmentCount = static_cast<uint32_t>(framebufferAttachments.size()),
.pAttachments = framebufferAttachments.data(),
.width = width,
.height = height,
.layers = 1,
};
auto framebuffer =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->createFramebufferUnique(framebufferCreateInfo));
return FramebufferWithAttachments{
.colorAttachment = std::move(colorAttachment),
.depthAttachment = std::move(depthAttachment),
.renderpass = std::move(renderpass),
.framebuffer = std::move(framebuffer),
};
}
struct DescriptorContents {
uint32_t binding = 0;
struct Image {
vkhpp::ImageView imageView;
vkhpp::ImageLayout imageLayout;
vkhpp::Sampler imageSampler;
};
std::optional<Image> image;
};
struct DescriptorSetBundle {
vkhpp::UniqueDescriptorPool pool;
vkhpp::UniqueDescriptorSetLayout layout;
vkhpp::UniqueDescriptorSet ds;
};
Result<DescriptorSetBundle> CreateDescriptorSet(
TypicalVkTestEnvironment& vk,
const std::vector<vkhpp::DescriptorSetLayoutBinding>& bindings,
const std::vector<DescriptorContents> contents) {
std::unordered_map<vkhpp::DescriptorType, uint32_t> descriptorTypeToSizes;
for (const auto& binding : bindings) {
descriptorTypeToSizes[binding.descriptorType] += binding.descriptorCount;
}
std::vector<vkhpp::DescriptorPoolSize> descriptorPoolSizes;
for (const auto& [descriptorType, descriptorCount] : descriptorTypeToSizes) {
descriptorPoolSizes.push_back(vkhpp::DescriptorPoolSize{
.type = descriptorType,
.descriptorCount = descriptorCount,
});
}
const vkhpp::DescriptorPoolCreateInfo descriptorPoolCreateInfo = {
.flags = vkhpp::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
.maxSets = 1,
.poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size()),
.pPoolSizes = descriptorPoolSizes.data(),
};
auto descriptorSetPool = GFXSTREAM_EXPECT_VKHPP_RV(
vk.device->createDescriptorPoolUnique(descriptorPoolCreateInfo));
const vkhpp::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = {
.bindingCount = static_cast<uint32_t>(bindings.size()),
.pBindings = bindings.data(),
};
auto descriptorSetLayout = GFXSTREAM_EXPECT_VKHPP_RV(
vk.device->createDescriptorSetLayoutUnique(descriptorSetLayoutCreateInfo));
const vkhpp::DescriptorSetLayout descriptorSetLayoutHandle = *descriptorSetLayout;
const vkhpp::DescriptorSetAllocateInfo descriptorSetAllocateInfo = {
.descriptorPool = *descriptorSetPool,
.descriptorSetCount = 1,
.pSetLayouts = &descriptorSetLayoutHandle,
};
auto descriptorSets = GFXSTREAM_EXPECT_VKHPP_RV(
vk.device->allocateDescriptorSetsUnique(descriptorSetAllocateInfo));
auto descriptorSet(std::move(descriptorSets[0]));
std::vector<std::unique_ptr<vkhpp::DescriptorImageInfo>> descriptorImageInfos;
std::vector<vkhpp::WriteDescriptorSet> descriptorSetWrites;
for (const auto& content : contents) {
if (content.image) {
descriptorImageInfos.emplace_back(new vkhpp::DescriptorImageInfo{
.sampler = content.image->imageSampler,
.imageView = content.image->imageView,
.imageLayout = content.image->imageLayout,
});
descriptorSetWrites.emplace_back(vkhpp::WriteDescriptorSet{
.dstSet = *descriptorSet,
.dstBinding = content.binding,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vkhpp::DescriptorType::eCombinedImageSampler,
.pImageInfo = descriptorImageInfos.back().get(),
});
} else {
return gfxstream::unexpected("Unhandled descriptor type");
;
}
}
vk.device->updateDescriptorSets(descriptorSetWrites, {});
return DescriptorSetBundle{
.pool = std::move(descriptorSetPool),
.layout = std::move(descriptorSetLayout),
.ds = std::move(descriptorSet),
};
}
struct PipelineParams {
std::vector<uint32_t> vert;
std::vector<uint32_t> frag;
std::vector<DescriptorSetBundle*> descriptorSets;
const FramebufferWithAttachments* framebuffer = nullptr;
};
struct PipelineBundle {
vkhpp::UniqueShaderModule vert;
vkhpp::UniqueShaderModule frag;
vkhpp::UniquePipelineLayout pipelineLayout;
vkhpp::UniquePipeline pipeline;
};
Result<PipelineBundle> CreatePipeline(TypicalVkTestEnvironment& vk,
const PipelineParams& params) {
const vkhpp::ShaderModuleCreateInfo vertShaderCreateInfo = {
.codeSize = params.vert.size() * sizeof(uint32_t),
.pCode = params.vert.data(),
};
auto vertShaderModule =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->createShaderModuleUnique(vertShaderCreateInfo));
const vkhpp::ShaderModuleCreateInfo fragShaderCreateInfo = {
.codeSize = params.frag.size() * sizeof(uint32_t),
.pCode = params.frag.data(),
};
auto fragShaderModule =
GFXSTREAM_EXPECT_VKHPP_RV(vk.device->createShaderModuleUnique(fragShaderCreateInfo));
const std::vector<vkhpp::PipelineShaderStageCreateInfo> pipelineStages = {
vkhpp::PipelineShaderStageCreateInfo{
.stage = vkhpp::ShaderStageFlagBits::eVertex,
.module = *vertShaderModule,
.pName = "main",
},
vkhpp::PipelineShaderStageCreateInfo{
.stage = vkhpp::ShaderStageFlagBits::eFragment,
.module = *fragShaderModule,
.pName = "main",
},
};
std::vector<vkhpp::DescriptorSetLayout> descriptorSetLayoutHandles;
for (const auto* descriptorSet : params.descriptorSets) {
descriptorSetLayoutHandles.push_back(*descriptorSet->layout);
}
const vkhpp::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = {
.setLayoutCount = static_cast<uint32_t>(descriptorSetLayoutHandles.size()),
.pSetLayouts = descriptorSetLayoutHandles.data(),
};
auto pipelineLayout = GFXSTREAM_EXPECT_VKHPP_RV(
vk.device->createPipelineLayoutUnique(pipelineLayoutCreateInfo));
const vkhpp::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo = {};
const vkhpp::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo = {
.topology = vkhpp::PrimitiveTopology::eTriangleList,
};
const vkhpp::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo = {
.viewportCount = 1,
.pViewports = nullptr,
.scissorCount = 1,
.pScissors = nullptr,
};
const vkhpp::PipelineRasterizationStateCreateInfo pipelineRasterStateCreateInfo = {
.depthClampEnable = VK_FALSE,
.rasterizerDiscardEnable = VK_FALSE,
.polygonMode = vkhpp::PolygonMode::eFill,
.cullMode = {},
.frontFace = vkhpp::FrontFace::eCounterClockwise,
.depthBiasEnable = VK_FALSE,
.depthBiasConstantFactor = 0.0f,
.depthBiasClamp = 0.0f,
.depthBiasSlopeFactor = 0.0f,
.lineWidth = 1.0f,
};
const vkhpp::SampleMask pipelineSampleMask = 65535;
const vkhpp::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo = {
.rasterizationSamples = vkhpp::SampleCountFlagBits::e1,
.sampleShadingEnable = VK_FALSE,
.minSampleShading = 1.0f,
.pSampleMask = &pipelineSampleMask,
.alphaToCoverageEnable = VK_FALSE,
.alphaToOneEnable = VK_FALSE,
};
const vkhpp::PipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo = {
.depthTestEnable = VK_FALSE,
.depthWriteEnable = VK_FALSE,
.depthCompareOp = vkhpp::CompareOp::eLess,
.depthBoundsTestEnable = VK_FALSE,
.stencilTestEnable = VK_FALSE,
.front =
{
.failOp = vkhpp::StencilOp::eKeep,
.passOp = vkhpp::StencilOp::eKeep,
.depthFailOp = vkhpp::StencilOp::eKeep,
.compareOp = vkhpp::CompareOp::eAlways,
.compareMask = 0,
.writeMask = 0,
.reference = 0,
},
.back =
{
.failOp = vkhpp::StencilOp::eKeep,
.passOp = vkhpp::StencilOp::eKeep,
.depthFailOp = vkhpp::StencilOp::eKeep,
.compareOp = vkhpp::CompareOp::eAlways,
.compareMask = 0,
.writeMask = 0,
.reference = 0,
},
.minDepthBounds = 0.0f,
.maxDepthBounds = 0.0f,
};
const std::vector<vkhpp::PipelineColorBlendAttachmentState> pipelineColorBlendAttachments =
{
vkhpp::PipelineColorBlendAttachmentState{
.blendEnable = VK_FALSE,
.srcColorBlendFactor = vkhpp::BlendFactor::eOne,
.dstColorBlendFactor = vkhpp::BlendFactor::eOneMinusSrcAlpha,
.colorBlendOp = vkhpp::BlendOp::eAdd,
.srcAlphaBlendFactor = vkhpp::BlendFactor::eOne,
.dstAlphaBlendFactor = vkhpp::BlendFactor::eOneMinusSrcAlpha,
.alphaBlendOp = vkhpp::BlendOp::eAdd,
.colorWriteMask =
vkhpp::ColorComponentFlagBits::eR | vkhpp::ColorComponentFlagBits::eG |
vkhpp::ColorComponentFlagBits::eB | vkhpp::ColorComponentFlagBits::eA,
},
};
const vkhpp::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo = {
.logicOpEnable = VK_FALSE,
.logicOp = vkhpp::LogicOp::eCopy,
.attachmentCount = static_cast<uint32_t>(pipelineColorBlendAttachments.size()),
.pAttachments = pipelineColorBlendAttachments.data(),
.blendConstants = {{
0.0f,
0.0f,
0.0f,
0.0f,
}},
};
const std::vector<vkhpp::DynamicState> pipelineDynamicStates = {
vkhpp::DynamicState::eViewport,
vkhpp::DynamicState::eScissor,
};
const vkhpp::PipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo = {
.dynamicStateCount = static_cast<uint32_t>(pipelineDynamicStates.size()),
.pDynamicStates = pipelineDynamicStates.data(),
};
const vkhpp::GraphicsPipelineCreateInfo pipelineCreateInfo = {
.stageCount = static_cast<uint32_t>(pipelineStages.size()),
.pStages = pipelineStages.data(),
.pVertexInputState = &pipelineVertexInputStateCreateInfo,
.pInputAssemblyState = &pipelineInputAssemblyStateCreateInfo,
.pTessellationState = nullptr,
.pViewportState = &pipelineViewportStateCreateInfo,
.pRasterizationState = &pipelineRasterStateCreateInfo,
.pMultisampleState = &pipelineMultisampleStateCreateInfo,
.pDepthStencilState = &pipelineDepthStencilStateCreateInfo,
.pColorBlendState = &pipelineColorBlendStateCreateInfo,
.pDynamicState = &pipelineDynamicStateCreateInfo,
.layout = *pipelineLayout,
.renderPass = *params.framebuffer->renderpass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = 0,
};
auto pipeline = GFXSTREAM_EXPECT_VKHPP_RV(
vk.device->createGraphicsPipelineUnique({}, pipelineCreateInfo));
return PipelineBundle{
.vert = std::move(vertShaderModule),
.frag = std::move(fragShaderModule),
.pipelineLayout = std::move(pipelineLayout),
.pipeline = std::move(pipeline),
};
}
Result<Image> DownloadImage(TypicalVkTestEnvironment& vk, uint32_t width, uint32_t height,
const vkhpp::UniqueImage& image, vkhpp::ImageLayout currentLayout,
vkhpp::ImageLayout returnedLayout) {
static constexpr const VkDeviceSize kStagingBufferSize = 32 * 1024 * 1024;
auto stagingBuffer = GFXSTREAM_EXPECT(CreateBuffer(
vk, kStagingBufferSize,
vkhpp::BufferUsageFlagBits::eTransferDst | vkhpp::BufferUsageFlagBits::eTransferSrc,
vkhpp::MemoryPropertyFlagBits::eHostVisible |
vkhpp::MemoryPropertyFlagBits::eHostCoherent));
GFXSTREAM_EXPECT(DoCommandsImmediate(vk, [&](vkhpp::UniqueCommandBuffer& cmd) {
if (currentLayout != vkhpp::ImageLayout::eTransferSrcOptimal) {
const std::vector<vkhpp::ImageMemoryBarrier> imageMemoryBarriers = {
vkhpp::ImageMemoryBarrier{
.srcAccessMask = vkhpp::AccessFlagBits::eMemoryRead |
vkhpp::AccessFlagBits::eMemoryWrite,
.dstAccessMask = vkhpp::AccessFlagBits::eTransferRead,
.oldLayout = currentLayout,
.newLayout = vkhpp::ImageLayout::eTransferSrcOptimal,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = *image,
.subresourceRange =
{
.aspectMask = vkhpp::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
},
};
cmd->pipelineBarrier(
/*srcStageMask=*/vkhpp::PipelineStageFlagBits::eAllCommands,
/*dstStageMask=*/vkhpp::PipelineStageFlagBits::eAllCommands,
/*dependencyFlags=*/{},
/*memoryBarriers=*/{},
/*bufferMemoryBarriers=*/{},
/*imageMemoryBarriers=*/imageMemoryBarriers);
}
const std::vector<vkhpp::BufferImageCopy> regions = {
vkhpp::BufferImageCopy{
.bufferOffset = 0,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource =
{
.aspectMask = vkhpp::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.imageOffset =
{
.x = 0,
.y = 0,
.z = 0,
},
.imageExtent =
{
.width = width,
.height = height,
.depth = 1,
},
},
};
cmd->copyImageToBuffer(*image, vkhpp::ImageLayout::eTransferSrcOptimal,
*stagingBuffer.buffer, regions);
if (returnedLayout != vkhpp::ImageLayout::eTransferSrcOptimal) {
const std::vector<vkhpp::ImageMemoryBarrier> imageMemoryBarriers = {
vkhpp::ImageMemoryBarrier{
.srcAccessMask = vkhpp::AccessFlagBits::eTransferRead,
.dstAccessMask = vkhpp::AccessFlagBits::eMemoryRead |
vkhpp::AccessFlagBits::eMemoryWrite,
.oldLayout = vkhpp::ImageLayout::eTransferSrcOptimal,
.newLayout = returnedLayout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = *image,
.subresourceRange =
{
.aspectMask = vkhpp::ImageAspectFlagBits::eColor,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
},
};
cmd->pipelineBarrier(
/*srcStageMask=*/vkhpp::PipelineStageFlagBits::eAllCommands,
/*dstStageMask=*/vkhpp::PipelineStageFlagBits::eAllCommands,
/*dependencyFlags=*/{},
/*memoryBarriers=*/{},
/*bufferMemoryBarriers=*/{},
/*imageMemoryBarriers=*/imageMemoryBarriers);
}
return Ok{};
}));
std::vector<uint32_t> outPixels;
outPixels.resize(width * height);
auto* mapped = GFXSTREAM_EXPECT_VKHPP_RV(
vk.device->mapMemory(*stagingBuffer.bufferMemory, 0, VK_WHOLE_SIZE));
std::memcpy(outPixels.data(), mapped, sizeof(uint32_t) * outPixels.size());
vk.device->unmapMemory(*stagingBuffer.bufferMemory);
return Image{
.width = width,
.height = height,
.pixels = outPixels,
};
}
void DoFillAndRenderFromAhb(uint32_t ahbFormat) {
const uint32_t width = 1920;
const uint32_t height = 1080;
const auto goldenPixel = PixelR8G8B8A8(0, 255, 255, 255);
const auto badPixel = PixelR8G8B8A8(0, 0, 0, 255);
// Bind to a placeholder ahb before rebinding to the real one.
// This is to test the behavior of descriptors and make sure
// it removes the references to the old one when overwritten.
auto deletedAhb =
GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate(*mGralloc, width, height, ahbFormat));
GFXSTREAM_ASSERT(FillAhb(deletedAhb, badPixel));
auto ahb =
GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate(*mGralloc, width, height, ahbFormat));
GFXSTREAM_ASSERT(FillAhb(ahb, goldenPixel));
const vkhpp::PhysicalDeviceVulkan11Features deviceFeatures = {
.samplerYcbcrConversion = VK_TRUE,
};
auto vk = GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment({
.deviceExtensions = {{VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME}},
.deviceCreateInfoPNext = &deviceFeatures,
}));
auto deletedAhbImage =
GFXSTREAM_ASSERT(CreateImageWithAhb(vk, deletedAhb, vkhpp::ImageUsageFlagBits::eSampled,
vkhpp::ImageLayout::eShaderReadOnlyOptimal));
auto ahbImage =
GFXSTREAM_ASSERT(CreateImageWithAhb(vk, ahb, vkhpp::ImageUsageFlagBits::eSampled,
vkhpp::ImageLayout::eShaderReadOnlyOptimal));
auto framebuffer = GFXSTREAM_ASSERT(CreateFramebuffer(
vk, width, height, /*colorAttachmentFormat=*/vkhpp::Format::eR8G8B8A8Unorm));
const vkhpp::Sampler ahbSamplerHandle = *ahbImage.imageSampler;
auto descriptorSet0 = GFXSTREAM_ASSERT(
CreateDescriptorSet(vk,
/*bindings=*/
{{
.binding = 0,
.descriptorType = vkhpp::DescriptorType::eCombinedImageSampler,
.descriptorCount = 1,
.stageFlags = vkhpp::ShaderStageFlagBits::eFragment,
.pImmutableSamplers = &ahbSamplerHandle,
}},
/*writes=*/
{{
.binding = 0,
.image = {{
.imageView = *deletedAhbImage.imageView,
.imageLayout = vkhpp::ImageLayout::eShaderReadOnlyOptimal,
.imageSampler = *deletedAhbImage.imageSampler,
}},
}}));
auto pipeline =
GFXSTREAM_ASSERT(CreatePipeline(vk, {
.vert = kFullscreenTriangleWithUVVert,
.frag = kBlitSampler2dFrag,
.descriptorSets = {&descriptorSet0},
.framebuffer = &framebuffer,
}));
std::vector<vkhpp::WriteDescriptorSet> descriptorSetWrites;
vkhpp::DescriptorImageInfo descriptorImageInfo = {
.imageView = *ahbImage.imageView,
.imageLayout = vkhpp::ImageLayout::eShaderReadOnlyOptimal,
.sampler = *ahbImage.imageSampler,
};
descriptorSetWrites.emplace_back(vkhpp::WriteDescriptorSet{
.dstSet = *descriptorSet0.ds,
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vkhpp::DescriptorType::eCombinedImageSampler,
.pImageInfo = &descriptorImageInfo,
});
vk.device->updateDescriptorSets(descriptorSetWrites, {});
deletedAhbImage = {};
deletedAhb = {};
GFXSTREAM_ASSERT(DoCommandsImmediate(vk, [&](vkhpp::UniqueCommandBuffer& cmd) {
const std::vector<vkhpp::ClearValue> renderPassBeginClearValues = {
vkhpp::ClearValue{
.color =
{
.float32 = {{
1.0f,
0.0f,
0.0f,
1.0f,
}},
},
},
};
const vkhpp::RenderPassBeginInfo renderPassBeginInfo = {
.renderPass = *framebuffer.renderpass,
.framebuffer = *framebuffer.framebuffer,
.renderArea =
{
.offset =
{
.x = 0,
.y = 0,
},
.extent =
{
.width = width,
.height = height,
},
},
.clearValueCount = static_cast<uint32_t>(renderPassBeginClearValues.size()),
.pClearValues = renderPassBeginClearValues.data(),
};
cmd->beginRenderPass(renderPassBeginInfo, vkhpp::SubpassContents::eInline);
cmd->bindPipeline(vkhpp::PipelineBindPoint::eGraphics, *pipeline.pipeline);
cmd->bindDescriptorSets(vkhpp::PipelineBindPoint::eGraphics, *pipeline.pipelineLayout,
/*firstSet=*/0, {*descriptorSet0.ds},
/*dynamicOffsets=*/{});
const vkhpp::Viewport viewport = {
.x = 0.0f,
.y = 0.0f,
.width = static_cast<float>(width),
.height = static_cast<float>(height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
cmd->setViewport(0, {viewport});
const vkhpp::Rect2D scissor = {
.offset =
{
.x = 0,
.y = 0,
},
.extent =
{
.width = width,
.height = height,
},
};
cmd->setScissor(0, {scissor});
cmd->draw(3, 1, 0, 0);
cmd->endRenderPass();
return Ok{};
}));
const auto actualImage = GFXSTREAM_ASSERT(
DownloadImage(vk, width, height, framebuffer.colorAttachment->image,
/*currentLayout=*/vkhpp::ImageLayout::eColorAttachmentOptimal,
/*returnedLayout=*/vkhpp::ImageLayout::eColorAttachmentOptimal));
const auto expectedImage = ImageFromColor(width, height, goldenPixel);
EXPECT_THAT(AreImagesSimilar(expectedImage, actualImage), IsTrue());
}
};
TEST_P(GfxstreamEnd2EndVkTest, Basic) {
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
}
TEST_P(GfxstreamEnd2EndVkTest, ImportAHB) {
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
const uint32_t width = 32;
const uint32_t height = 32;
auto ahb = GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate(
*mGralloc, width, height, GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM));
const VkNativeBufferANDROID imageNativeBufferInfo = {
.sType = VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID,
.handle = mGralloc->getNativeHandle(ahb),
};
auto vkQueueSignalReleaseImageANDROID = PFN_vkQueueSignalReleaseImageANDROID(
device->getProcAddr("vkQueueSignalReleaseImageANDROID"));
ASSERT_THAT(vkQueueSignalReleaseImageANDROID, NotNull());
const vkhpp::ImageCreateInfo imageCreateInfo = {
.pNext = &imageNativeBufferInfo,
.imageType = vkhpp::ImageType::e2D,
.extent.width = width,
.extent.height = height,
.extent.depth = 1,
.mipLevels = 1,
.arrayLayers = 1,
.format = vkhpp::Format::eR8G8B8A8Unorm,
.tiling = vkhpp::ImageTiling::eOptimal,
.initialLayout = vkhpp::ImageLayout::eUndefined,
.usage = vkhpp::ImageUsageFlagBits::eSampled |
vkhpp::ImageUsageFlagBits::eTransferDst |
vkhpp::ImageUsageFlagBits::eTransferSrc,
.sharingMode = vkhpp::SharingMode::eExclusive,
.samples = vkhpp::SampleCountFlagBits::e1,
};
auto image = device->createImageUnique(imageCreateInfo).value;
vkhpp::MemoryRequirements imageMemoryRequirements{};
device->getImageMemoryRequirements(*image, &imageMemoryRequirements);
const uint32_t imageMemoryIndex = utils::getMemoryType(
physicalDevice, imageMemoryRequirements, vkhpp::MemoryPropertyFlagBits::eDeviceLocal);
ASSERT_THAT(imageMemoryIndex, Not(Eq(-1)));
const vkhpp::MemoryAllocateInfo imageMemoryAllocateInfo = {
.allocationSize = imageMemoryRequirements.size,
.memoryTypeIndex = imageMemoryIndex,
};
auto imageMemory = device->allocateMemoryUnique(imageMemoryAllocateInfo).value;
ASSERT_THAT(imageMemory, IsValidHandle());
ASSERT_THAT(device->bindImageMemory(*image, *imageMemory, 0), IsVkSuccess());
const vkhpp::BufferCreateInfo bufferCreateInfo = {
.size = static_cast<VkDeviceSize>(12 * 1024 * 1024),
.usage = vkhpp::BufferUsageFlagBits::eTransferDst |
vkhpp::BufferUsageFlagBits::eTransferSrc,
.sharingMode = vkhpp::SharingMode::eExclusive,
};
auto stagingBuffer = device->createBufferUnique(bufferCreateInfo).value;
ASSERT_THAT(stagingBuffer, IsValidHandle());
vkhpp::MemoryRequirements stagingBufferMemoryRequirements{};
device->getBufferMemoryRequirements(*stagingBuffer, &stagingBufferMemoryRequirements);
const auto stagingBufferMemoryType = utils::getMemoryType(
physicalDevice, stagingBufferMemoryRequirements,
vkhpp::MemoryPropertyFlagBits::eHostVisible | vkhpp::MemoryPropertyFlagBits::eHostCoherent);
const vkhpp::MemoryAllocateInfo stagingBufferMemoryAllocateInfo = {
.allocationSize = stagingBufferMemoryRequirements.size,
.memoryTypeIndex = stagingBufferMemoryType,
};
auto stagingBufferMemory = device->allocateMemoryUnique(stagingBufferMemoryAllocateInfo).value;
ASSERT_THAT(stagingBufferMemory, IsValidHandle());
ASSERT_THAT(device->bindBufferMemory(*stagingBuffer, *stagingBufferMemory, 0), IsVkSuccess());
const vkhpp::CommandPoolCreateInfo commandPoolCreateInfo = {
.queueFamilyIndex = queueFamilyIndex,
};
auto commandPool = device->createCommandPoolUnique(commandPoolCreateInfo).value;
ASSERT_THAT(stagingBufferMemory, IsValidHandle());
const vkhpp::CommandBufferAllocateInfo commandBufferAllocateInfo = {
.level = vkhpp::CommandBufferLevel::ePrimary,
.commandPool = *commandPool,
.commandBufferCount = 1,
};
auto commandBuffers = device->allocateCommandBuffersUnique(commandBufferAllocateInfo).value;
ASSERT_THAT(commandBuffers, Not(IsEmpty()));
auto commandBuffer = std::move(commandBuffers[0]);
ASSERT_THAT(commandBuffer, IsValidHandle());
const vkhpp::CommandBufferBeginInfo commandBufferBeginInfo = {
.flags = vkhpp::CommandBufferUsageFlagBits::eOneTimeSubmit,
};
commandBuffer->begin(commandBufferBeginInfo);
commandBuffer->end();
std::vector<vkhpp::CommandBuffer> commandBufferHandles;
commandBufferHandles.push_back(*commandBuffer);
auto transferFence = device->createFenceUnique(vkhpp::FenceCreateInfo()).value;
ASSERT_THAT(commandBuffer, IsValidHandle());
const vkhpp::SubmitInfo submitInfo = {
.commandBufferCount = static_cast<uint32_t>(commandBufferHandles.size()),
.pCommandBuffers = commandBufferHandles.data(),
};
queue.submit(submitInfo, *transferFence);
auto waitResult = device->waitForFences(*transferFence, VK_TRUE, AsVkTimeout(3s));
ASSERT_THAT(waitResult, IsVkSuccess());
int fence;
auto result = vkQueueSignalReleaseImageANDROID(queue, 0, nullptr, *image, &fence);
ASSERT_THAT(result, Eq(VK_SUCCESS));
ASSERT_THAT(fence, Not(Eq(-1)));
ASSERT_THAT(mSync->wait(fence, 3000), Eq(0));
}
TEST_P(GfxstreamEnd2EndVkTest, DeferredImportAHB) {
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
const uint32_t width = 32;
const uint32_t height = 32;
auto ahb = GFXSTREAM_ASSERT(ScopedAHardwareBuffer::Allocate(
*mGralloc, width, height, GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM));
auto vkQueueSignalReleaseImageANDROID = PFN_vkQueueSignalReleaseImageANDROID(
device->getProcAddr("vkQueueSignalReleaseImageANDROID"));
ASSERT_THAT(vkQueueSignalReleaseImageANDROID, NotNull());
const vkhpp::ImageCreateInfo imageCreateInfo = {
.pNext = nullptr,
.imageType = vkhpp::ImageType::e2D,
.extent.width = width,
.extent.height = height,
.extent.depth = 1,
.mipLevels = 1,
.arrayLayers = 1,
.format = vkhpp::Format::eR8G8B8A8Unorm,
.tiling = vkhpp::ImageTiling::eOptimal,
.initialLayout = vkhpp::ImageLayout::eUndefined,
.usage = vkhpp::ImageUsageFlagBits::eSampled |
vkhpp::ImageUsageFlagBits::eTransferDst |
vkhpp::ImageUsageFlagBits::eTransferSrc,
.sharingMode = vkhpp::SharingMode::eExclusive,
.samples = vkhpp::SampleCountFlagBits::e1,
};
auto image = device->createImageUnique(imageCreateInfo).value;
// NOTE: Binding the VkImage to the AHB happens after the VkImage is created.
const VkNativeBufferANDROID imageNativeBufferInfo = {
.sType = VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID,
.handle = mGralloc->getNativeHandle(ahb),
};
const vkhpp::BindImageMemoryInfo imageBindMemoryInfo = {
.pNext = &imageNativeBufferInfo,
.image = *image,
.memory = VK_NULL_HANDLE,
.memoryOffset = 0,
};
ASSERT_THAT(device->bindImageMemory2({imageBindMemoryInfo}), IsVkSuccess());
std::vector<vkhpp::Semaphore> semaphores;
int fence;
auto result = vkQueueSignalReleaseImageANDROID(queue, 0, nullptr, *image, &fence);
ASSERT_THAT(result, Eq(VK_SUCCESS));
ASSERT_THAT(fence, Not(Eq(-1)));
ASSERT_THAT(mSync->wait(fence, 3000), Eq(0));
}
TEST_P(GfxstreamEnd2EndVkTest, BlobAHBIsNotMapable) {
if (GetParam().with_gl) {
GTEST_SKIP()
<< "Skipping test, data buffers are currently only supported in Vulkan only mode.";
}
if (GetParam().with_features.count("VulkanUseDedicatedAhbMemoryType") == 0) {
GTEST_SKIP()
<< "Skipping test, AHB test only makes sense with VulkanUseDedicatedAhbMemoryType.";
}
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
const uint32_t width = 32;
const uint32_t height = 1;
auto ahb = GFXSTREAM_ASSERT(
ScopedAHardwareBuffer::Allocate(*mGralloc, width, height, GFXSTREAM_AHB_FORMAT_BLOB));
const vkhpp::ExternalMemoryBufferCreateInfo externalMemoryBufferCreateInfo = {
.handleTypes = vkhpp::ExternalMemoryHandleTypeFlagBits::eAndroidHardwareBufferANDROID,
};
const vkhpp::BufferCreateInfo bufferCreateInfo = {
.pNext = &externalMemoryBufferCreateInfo,
.size = width,
.usage = vkhpp::BufferUsageFlagBits::eTransferDst |
vkhpp::BufferUsageFlagBits::eTransferSrc |
vkhpp::BufferUsageFlagBits::eVertexBuffer,
.sharingMode = vkhpp::SharingMode::eExclusive,
};
auto buffer = device->createBufferUnique(bufferCreateInfo).value;
ASSERT_THAT(buffer, IsValidHandle());
auto vkGetAndroidHardwareBufferPropertiesANDROID =
reinterpret_cast<PFN_vkGetAndroidHardwareBufferPropertiesANDROID>(
device->getProcAddr("vkGetAndroidHardwareBufferPropertiesANDROID"));
ASSERT_THAT(vkGetAndroidHardwareBufferPropertiesANDROID, NotNull());
VkAndroidHardwareBufferPropertiesANDROID bufferProperties = {
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
.pNext = nullptr,
};
ASSERT_THAT(vkGetAndroidHardwareBufferPropertiesANDROID(*device, ahb, &bufferProperties),
Eq(VK_SUCCESS));
const vkhpp::MemoryRequirements bufferMemoryRequirements{
.size = bufferProperties.allocationSize,
.alignment = 0,
.memoryTypeBits = bufferProperties.memoryTypeBits,
};
const auto memoryProperties = physicalDevice.getMemoryProperties();
for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++) {
if (!(bufferMemoryRequirements.memoryTypeBits & (1 << i))) {
continue;
}
const auto memoryPropertyFlags = memoryProperties.memoryTypes[i].propertyFlags;
EXPECT_THAT(memoryPropertyFlags & vkhpp::MemoryPropertyFlagBits::eHostVisible,
Ne(vkhpp::MemoryPropertyFlagBits::eHostVisible));
}
const auto bufferMemoryType = utils::getMemoryType(physicalDevice, bufferMemoryRequirements,
vkhpp::MemoryPropertyFlagBits::eDeviceLocal);
ASSERT_THAT(bufferMemoryType, Ne(-1));
const vkhpp::ImportAndroidHardwareBufferInfoANDROID importHardwareBufferInfo = {
.buffer = ahb,
};
const vkhpp::MemoryAllocateInfo bufferMemoryAllocateInfo = {
.pNext = &importHardwareBufferInfo,
.allocationSize = bufferMemoryRequirements.size,
.memoryTypeIndex = bufferMemoryType,
};
auto bufferMemory = device->allocateMemoryUnique(bufferMemoryAllocateInfo).value;
ASSERT_THAT(bufferMemory, IsValidHandle());
ASSERT_THAT(device->bindBufferMemory(*buffer, *bufferMemory, 0), IsVkSuccess());
}
TEST_P(GfxstreamEnd2EndVkTest, HostMemory) {
static constexpr const vkhpp::DeviceSize kSize = 16 * 1024;
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
uint32_t hostMemoryTypeIndex = -1;
const auto memoryProperties = physicalDevice.getMemoryProperties();
for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++) {
const vkhpp::MemoryType& memoryType = memoryProperties.memoryTypes[i];
if (memoryType.propertyFlags & vkhpp::MemoryPropertyFlagBits::eHostVisible) {
hostMemoryTypeIndex = i;
}
}
if (hostMemoryTypeIndex == -1) {
GTEST_SKIP() << "Skipping test due to no host visible memory type.";
return;
}
const vkhpp::MemoryAllocateInfo memoryAllocateInfo = {
.allocationSize = kSize,
.memoryTypeIndex = hostMemoryTypeIndex,
};
auto memory = device->allocateMemoryUnique(memoryAllocateInfo).value;
ASSERT_THAT(memory, IsValidHandle());
void* mapped = nullptr;
auto mapResult = device->mapMemory(*memory, 0, VK_WHOLE_SIZE, vkhpp::MemoryMapFlags{}, &mapped);
ASSERT_THAT(mapResult, IsVkSuccess());
ASSERT_THAT(mapped, NotNull());
auto* bytes = reinterpret_cast<uint8_t*>(mapped);
std::memset(bytes, 0xFF, kSize);
const vkhpp::MappedMemoryRange range = {
.memory = *memory,
.offset = 0,
.size = kSize,
};
device->flushMappedMemoryRanges({range});
device->invalidateMappedMemoryRanges({range});
for (uint32_t i = 0; i < kSize; ++i) {
EXPECT_THAT(bytes[i], Eq(0xFF));
}
}
TEST_P(GfxstreamEnd2EndVkTest, GetPhysicalDeviceProperties2) {
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
auto props1 = physicalDevice.getProperties();
auto props2 = physicalDevice.getProperties2();
EXPECT_THAT(props1.vendorID, Eq(props2.properties.vendorID));
EXPECT_THAT(props1.deviceID, Eq(props2.properties.deviceID));
}
TEST_P(GfxstreamEnd2EndVkTest, GetPhysicalDeviceFeatures2KHR) {
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
auto features1 = physicalDevice.getFeatures();
auto features2 = physicalDevice.getFeatures2();
EXPECT_THAT(features1.robustBufferAccess, Eq(features2.features.robustBufferAccess));
}
TEST_P(GfxstreamEnd2EndVkTest, GetPhysicalDeviceImageFormatProperties2KHR) {
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
const vkhpp::PhysicalDeviceImageFormatInfo2 imageFormatInfo = {
.format = vkhpp::Format::eR8G8B8A8Unorm,
.type = vkhpp::ImageType::e2D,
.tiling = vkhpp::ImageTiling::eOptimal,
.usage = vkhpp::ImageUsageFlagBits::eSampled,
};
const auto properties =
GFXSTREAM_ASSERT_VKHPP_RV(physicalDevice.getImageFormatProperties2(imageFormatInfo));
EXPECT_THAT(properties.imageFormatProperties.maxExtent.width, Ge(1));
EXPECT_THAT(properties.imageFormatProperties.maxExtent.height, Ge(1));
EXPECT_THAT(properties.imageFormatProperties.maxExtent.depth, Ge(1));
}
template <typename VkhppUniqueHandleType,
typename VkhppHandleType = typename VkhppUniqueHandleType::element_type>
std::vector<VkhppHandleType> AsHandles(const std::vector<VkhppUniqueHandleType>& elements) {
std::vector<VkhppHandleType> ret;
ret.reserve(elements.size());
for (const auto& e : elements) {
ret.push_back(*e);
}
return ret;
}
struct DescriptorBundle {
vkhpp::UniqueDescriptorPool descriptorPool;
vkhpp::UniqueDescriptorSetLayout descriptorSetLayout;
std::vector<vkhpp::UniqueDescriptorSet> descriptorSets;
};
Result<Ok> ReallocateDescriptorBundleSets(vkhpp::Device device, uint32_t count,
DescriptorBundle* bundle) {
if (!bundle->descriptorSetLayout) {
return gfxstream::unexpected("Invalid descriptor set layout");
}
const std::vector<vkhpp::DescriptorSetLayout> descriptorSetLayouts(count, *bundle->descriptorSetLayout);
const vkhpp::DescriptorSetAllocateInfo descriptorSetAllocateInfo = {
.descriptorPool = *bundle->descriptorPool,
.descriptorSetCount = count,
.pSetLayouts = descriptorSetLayouts.data(),
};
auto descriptorSets =
GFXSTREAM_EXPECT_VKHPP_RV(device.allocateDescriptorSetsUnique(descriptorSetAllocateInfo));
bundle->descriptorSets = std::move(descriptorSets);
return Ok{};
}
Result<DescriptorBundle> AllocateDescriptorBundle(vkhpp::Device device, uint32_t count) {
const vkhpp::DescriptorPoolSize descriptorPoolSize = {
.type = vkhpp::DescriptorType::eUniformBuffer,
.descriptorCount = 1 * count,
};
const vkhpp::DescriptorPoolCreateInfo descriptorPoolCreateInfo = {
.flags = vkhpp::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
.maxSets = count,
.poolSizeCount = 1,
.pPoolSizes = &descriptorPoolSize,
};
auto descriptorPool =
GFXSTREAM_EXPECT_VKHPP_RV(device.createDescriptorPoolUnique(descriptorPoolCreateInfo));
const vkhpp::DescriptorSetLayoutBinding descriptorSetBinding = {
.binding = 0,
.descriptorType = vkhpp::DescriptorType::eUniformBuffer,
.descriptorCount = 1,
.stageFlags = vkhpp::ShaderStageFlagBits::eVertex,
};
const vkhpp::DescriptorSetLayoutCreateInfo descriptorSetLayoutInfo = {
.bindingCount = 1,
.pBindings = &descriptorSetBinding,
};
auto descriptorSetLayout =
GFXSTREAM_EXPECT_VKHPP_RV(device.createDescriptorSetLayoutUnique(descriptorSetLayoutInfo));
DescriptorBundle bundle = {
.descriptorPool = std::move(descriptorPool),
.descriptorSetLayout = std::move(descriptorSetLayout),
};
GFXSTREAM_EXPECT(ReallocateDescriptorBundleSets(device, count, &bundle));
return std::move(bundle);
}
// 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(GfxstreamEnd2EndVkTest, DescriptorSetAllocFree) {
constexpr const uint32_t kNumSets = 4;
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
auto bundle = GFXSTREAM_ASSERT(AllocateDescriptorBundle(*device, kNumSets));
auto descriptorSetHandles = AsHandles(bundle.descriptorSets);
EXPECT_THAT(device->freeDescriptorSets(*bundle.descriptorPool, kNumSets, descriptorSetHandles.data()), IsVkSuccess());
// The double free should also work
EXPECT_THAT(device->freeDescriptorSets(*bundle.descriptorPool, kNumSets, descriptorSetHandles.data()), IsVkSuccess());
// Alloc/free again should also work
GFXSTREAM_ASSERT(ReallocateDescriptorBundleSets(*device, kNumSets, &bundle));
descriptorSetHandles = AsHandles(bundle.descriptorSets);
EXPECT_THAT(device->freeDescriptorSets(*bundle.descriptorPool, kNumSets, descriptorSetHandles.data()), IsVkSuccess());
}
TEST_P(GfxstreamEnd2EndVkTest, DescriptorSetAllocFreeReset) {
constexpr const uint32_t kNumSets = 4;
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
auto bundle = GFXSTREAM_ASSERT(AllocateDescriptorBundle(*device, kNumSets));
device->resetDescriptorPool(*bundle.descriptorPool);
// The double free should also work
auto descriptorSetHandles = AsHandles(bundle.descriptorSets);
EXPECT_THAT(device->freeDescriptorSets(*bundle.descriptorPool, kNumSets, descriptorSetHandles.data()), IsVkSuccess());
// Alloc/reset/free again should also work
GFXSTREAM_ASSERT(ReallocateDescriptorBundleSets(*device, kNumSets, &bundle));
device->resetDescriptorPool(*bundle.descriptorPool);
descriptorSetHandles = AsHandles(bundle.descriptorSets);
EXPECT_THAT(device->freeDescriptorSets(*bundle.descriptorPool, kNumSets, descriptorSetHandles.data()), IsVkSuccess());
}
TEST_P(GfxstreamEnd2EndVkTest, DISABLED_DescriptorSetAllocFreeDestroy) {
constexpr const uint32_t kNumSets = 4;
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
auto bundle = GFXSTREAM_ASSERT(AllocateDescriptorBundle(*device, kNumSets));
device->destroyDescriptorPool(*bundle.descriptorPool);
// The double free should also work
auto descriptorSetHandles = AsHandles(bundle.descriptorSets);
EXPECT_THAT(device->freeDescriptorSets(*bundle.descriptorPool, kNumSets, descriptorSetHandles.data()), IsVkSuccess());
}
TEST_P(GfxstreamEnd2EndVkTest, MultiThreadedShutdown) {
constexpr const int kNumIterations = 20;
for (int i = 0; i < kNumIterations; i++) {
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
const vkhpp::BufferCreateInfo bufferCreateInfo = {
.size = 1024,
.usage = vkhpp::BufferUsageFlagBits::eTransferSrc,
};
// TODO: switch to std::barrier with arrive_and_wait().
std::atomic_int threadsReady{0};
std::vector<std::thread> threads;
constexpr const int kNumThreads = 5;
for (int t = 0; t < kNumThreads; t++) {
threads.emplace_back([&, this]() {
// Perform some work to ensure host RenderThread started.
auto buffer1 = device->createBufferUnique(bufferCreateInfo).value;
++threadsReady;
while (threadsReady.load() != kNumThreads) {
}
// Sleep a little which is hopefully enough time to potentially get
// the corresponding host ASG RenderThreads to go sleep waiting for
// a WAKEUP via a GFXSTREAM_CONTEXT_PING.
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto buffer2 = device->createBufferUnique(bufferCreateInfo).value;
// 2 vkDestroyBuffer() calls happen here with the destruction of `buffer1`
// and `buffer2`. vkDestroy*() calls are async (return `void`) and the
// guest thread continues execution without waiting for the command to
// complete on the host.
//
// The guest ASG and corresponding virtio gpu resource will also be
// destructed here as a part of the thread_local HostConnection being
// destructed.
//
// Note: Vulkan commands are given a sequence number in order to ensure that
// commands from multi-threaded guest Vulkan apps are executed in order on the
// host. Gfxstream's host Vulkan decoders will spin loop waiting for their turn to
// process their next command.
//
// With all of the above, a deadlock would previouly occur with the following
// sequence:
//
// T1: Host-RenderThread-1: <sleeping waiting for wakeup>
//
// T2: Host-RenderThread-2: <sleeping waiting for wakeup>
//
// T3: Guest-Thread-1: vkDestroyBuffer() called,
// VkEncoder grabs sequence-number-10,
// writes sequence-number-10 into ASG-1 via resource-1
//
// T4: Guest-Thread-2: vkDestroyBuffer() called,
// VkEncoder grabs sequence-number-11,
// writes into ASG-2 via resource-2
//
// T5: Guest-Thread-2: ASG-2 sends a VIRTIO_GPU_CMD_SUBMIT_3D with
// GFXSTREAM_CONTEXT_PING on ASG-resource-2
//
// T6: Guest-Thread-2: guest thread finishes,
// ASG-2 destructor destroys the virtio-gpu resource used,
// destruction sends VIRTIO_GPU_CMD_RESOURCE_UNREF on
// resource-2
//
// T7: Guest-Thread-1: ASG-1 sends VIRTIO_GPU_CMD_SUBMIT_3D with
// GFXSTREAM_CONTEXT_PING on ASG-resource-1
//
// T8: Host-Virtio-Gpu-Thread: performs VIRTIO_GPU_CMD_SUBMIT_3D from T5,
// pings ASG-2 which wakes up Host-RenderThread-2
//
// T9: Host-RenderThread-2: woken from T8,
// reads sequence-number-11 from ASG-2,
// spin looping waiting for sequence-number-10 to execute
//
// T10: Host-Virtio-Gpu-Thread: performs VIRTIO_GPU_CMD_RESOURCE_UNREF for
// resource-2 from T6,
// resource-2 is used by ASG-2 / Host-RenderThread-2
// waits for Host-RenderThread-2 to finish
//
// DEADLOCKED HERE:
//
// * Host-Virtio-GpuThread is waiting for Host-RenderThread-2 to finish before
// it can finish destroying resource-2
//
// * Host-RenderThread-2 is waiting for Host-RenderThread-1 to execute
// sequence-number-10
//
// * Host-RenderThread-1 is asleep waiting for a GFXSTREAM_CONTEXT_PING
// from Host-Virtio-GpuThread
});
}
for (auto& thread : threads) {
thread.join();
}
}
}
TEST_P(GfxstreamEnd2EndVkTest, DeviceCreateWithDeviceGroup) {
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
const vkhpp::DeviceGroupDeviceCreateInfo deviceGroupDeviceCreateInfo = {
.physicalDeviceCount = 1,
.pPhysicalDevices = &physicalDevice,
};
const float queuePriority = 1.0f;
const vkhpp::DeviceQueueCreateInfo deviceQueueCreateInfo = {
.queueFamilyIndex = 0,
.queueCount = 1,
.pQueuePriorities = &queuePriority,
};
const vkhpp::DeviceCreateInfo deviceCreateInfo = {
.pNext = &deviceGroupDeviceCreateInfo,
.pQueueCreateInfos = &deviceQueueCreateInfo,
.queueCreateInfoCount = 1,
};
auto device2 = GFXSTREAM_ASSERT_VKHPP_RV(physicalDevice.createDeviceUnique(deviceCreateInfo));
ASSERT_THAT(device2, IsValidHandle());
}
TEST_P(GfxstreamEnd2EndVkTest, AcquireImageAndroidWithFence) {
DoAcquireImageAndroidWithSync(/*withFence=*/true, /*withSemaphore=*/false);
}
TEST_P(GfxstreamEnd2EndVkTest, AcquireImageAndroidWithSemaphore) {
DoAcquireImageAndroidWithSync(/*withFence=*/false, /*withSemaphore=*/true);
}
TEST_P(GfxstreamEnd2EndVkTest, AcquireImageAndroidWithFenceAndSemaphore) {
DoAcquireImageAndroidWithSync(/*withFence=*/true, /*withSemaphore=*/true);
}
VKAPI_ATTR void VKAPI_CALL MemoryReportCallback(const VkDeviceMemoryReportCallbackDataEXT*, void*) {
// Unused
}
TEST_P(GfxstreamEnd2EndVkTest, DeviceMemoryReport) {
int userdata = 1;
vkhpp::DeviceDeviceMemoryReportCreateInfoEXT deviceDeviceMemoryReportInfo = {
.pfnUserCallback = &MemoryReportCallback,
.pUserData = &userdata,
};
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment({
.deviceExtensions = {{
VK_EXT_DEVICE_MEMORY_REPORT_EXTENSION_NAME,
}},
.deviceCreateInfoPNext = &deviceDeviceMemoryReportInfo,
}));
const vkhpp::MemoryAllocateInfo memoryAllocateInfo = {
.allocationSize = 1024,
.memoryTypeIndex = 0,
};
auto memory = device->allocateMemoryUnique(memoryAllocateInfo).value;
ASSERT_THAT(memory, IsValidHandle());
}
TEST_P(GfxstreamEnd2EndVkTest, DescriptorUpdateTemplateWithWrapping) {
auto vk = GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
auto& [instance, physicalDevice, device, queue, queueFamilyIndex] = vk;
const VkDeviceSize kBufferSize = 1024;
auto buffer = GFXSTREAM_ASSERT(CreateBuffer(
vk, kBufferSize,
vkhpp::BufferUsageFlagBits::eTransferDst |
vkhpp::BufferUsageFlagBits::eTransferSrc |
vkhpp::BufferUsageFlagBits::eUniformBuffer,
vkhpp::MemoryPropertyFlagBits::eHostVisible |
vkhpp::MemoryPropertyFlagBits::eHostCoherent));
const std::vector<VkDescriptorBufferInfo> descriptorInfo = {
VkDescriptorBufferInfo{
.buffer = *buffer.buffer,
.offset = 0,
.range = kBufferSize,
},
VkDescriptorBufferInfo{
.buffer = *buffer.buffer,
.offset = 0,
.range = kBufferSize,
},
VkDescriptorBufferInfo{
.buffer = *buffer.buffer,
.offset = 0,
.range = kBufferSize,
},
VkDescriptorBufferInfo{
.buffer = *buffer.buffer,
.offset = 0,
.range = kBufferSize,
},
};
const std::vector<vkhpp::DescriptorPoolSize> descriptorPoolSizes = {
{
.type = vkhpp::DescriptorType::eUniformBuffer,
.descriptorCount = 4,
},
};
const vkhpp::DescriptorPoolCreateInfo descriptorPoolCreateInfo = {
.flags = vkhpp::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
.maxSets = 1,
.poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size()),
.pPoolSizes = descriptorPoolSizes.data(),
};
auto descriptorPool =
GFXSTREAM_ASSERT_VKHPP_RV(device->createDescriptorPoolUnique(descriptorPoolCreateInfo));
const std::vector<vkhpp::DescriptorSetLayoutBinding> descriptorSetBindings = {
{
.binding = 0,
.descriptorType = vkhpp::DescriptorType::eUniformBuffer,
.descriptorCount = 1,
.stageFlags = vkhpp::ShaderStageFlagBits::eVertex,
},
{
.binding = 1,
.descriptorType = vkhpp::DescriptorType::eUniformBuffer,
.descriptorCount = 1,
.stageFlags = vkhpp::ShaderStageFlagBits::eVertex,
},
{
.binding = 2,
.descriptorType = vkhpp::DescriptorType::eUniformBuffer,
.descriptorCount = 1,
.stageFlags = vkhpp::ShaderStageFlagBits::eVertex,
},
{
.binding = 3,
.descriptorType = vkhpp::DescriptorType::eUniformBuffer,
.descriptorCount = 1,
.stageFlags = vkhpp::ShaderStageFlagBits::eVertex,
},
};
const vkhpp::DescriptorSetLayoutCreateInfo descriptorSetLayoutInfo = {
.bindingCount = static_cast<uint32_t>(descriptorSetBindings.size()),
.pBindings = descriptorSetBindings.data(),
};
auto descriptorSetLayout =
GFXSTREAM_ASSERT_VKHPP_RV(device->createDescriptorSetLayoutUnique(descriptorSetLayoutInfo));
const std::vector<vkhpp::DescriptorSetLayout> descriptorSetLayouts = {*descriptorSetLayout};
const vkhpp::DescriptorSetAllocateInfo descriptorSetAllocateInfo = {
.descriptorPool = *descriptorPool,
.descriptorSetCount = static_cast<uint32_t>(descriptorSetLayouts.size()),
.pSetLayouts = descriptorSetLayouts.data(),
};
auto descriptorSets =
GFXSTREAM_ASSERT_VKHPP_RV(device->allocateDescriptorSetsUnique(descriptorSetAllocateInfo));
auto descriptorSet = std::move(descriptorSets[0]);
const vkhpp::PipelineLayoutCreateInfo pipelineLayoutCreateInfo = {
.setLayoutCount = static_cast<uint32_t>(descriptorSetLayouts.size()),
.pSetLayouts = descriptorSetLayouts.data(),
};
auto pipelineLayout =
GFXSTREAM_ASSERT_VKHPP_RV(device->createPipelineLayoutUnique(pipelineLayoutCreateInfo));
const std::vector<vkhpp::DescriptorUpdateTemplateEntry> descriptorUpdateEntries = {
{
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 4,
.descriptorType = vkhpp::DescriptorType::eUniformBuffer,
.offset = 0,
.stride = sizeof(VkDescriptorBufferInfo),
},
};
const vkhpp::DescriptorUpdateTemplateCreateInfo descriptorUpdateTemplateCreateInfo = {
.descriptorUpdateEntryCount = static_cast<uint32_t>(descriptorUpdateEntries.size()),
.pDescriptorUpdateEntries = descriptorUpdateEntries.data(),
.descriptorSetLayout = *descriptorSetLayout,
.pipelineBindPoint = vkhpp::PipelineBindPoint::eGraphics,
.pipelineLayout = *pipelineLayout,
.set = 0,
};
auto descriptorUpdateTemplate = GFXSTREAM_ASSERT_VKHPP_RV(
device->createDescriptorUpdateTemplateUnique(descriptorUpdateTemplateCreateInfo));
device->updateDescriptorSetWithTemplate(*descriptorSet, *descriptorUpdateTemplate,
(const void*)descriptorInfo.data());
// Gfxstream optimizes descriptor set updates by batching updates until there is an
// actual use in a command buffer. Try to force that flush by binding the descriptor
// set here:
GFXSTREAM_ASSERT(DoCommandsImmediate(vk,
[&](vkhpp::UniqueCommandBuffer& cmd) {
cmd->bindDescriptorSets(vkhpp::PipelineBindPoint::eGraphics, *pipelineLayout,
/*firstSet=*/0, {*descriptorSet},
/*dynamicOffsets=*/{});
return Ok{};
}));
}
TEST_P(GfxstreamEnd2EndVkTest, MultiThreadedVkMapMemory) {
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
static constexpr const vkhpp::DeviceSize kSize = 1024;
const vkhpp::BufferCreateInfo bufferCreateInfo = {
.size = kSize,
.usage = vkhpp::BufferUsageFlagBits::eTransferSrc,
};
auto buffer = device->createBufferUnique(bufferCreateInfo).value;
vkhpp::MemoryRequirements bufferMemoryRequirements{};
device->getBufferMemoryRequirements(*buffer, &bufferMemoryRequirements);
const uint32_t bufferMemoryIndex = utils::getMemoryType(
physicalDevice, bufferMemoryRequirements,
vkhpp::MemoryPropertyFlagBits::eHostVisible | vkhpp::MemoryPropertyFlagBits::eHostCoherent);
if (bufferMemoryIndex == -1) {
GTEST_SKIP() << "Skipping test due to no memory type with HOST_VISIBLE | HOST_COHERENT.";
}
std::vector<std::thread> threads;
std::atomic_int threadsReady{0};
constexpr const int kNumThreads = 2;
for (int t = 0; t < kNumThreads; t++) {
threads.emplace_back([&, this]() {
// Perform some work to ensure host RenderThread started.
auto buffer2 = device->createBufferUnique(bufferCreateInfo).value;
ASSERT_THAT(buffer2, IsValidHandle());
++threadsReady;
while (threadsReady.load() != kNumThreads) {
}
constexpr const int kNumIterations = 100;
for (int i = 0; i < kNumIterations; i++) {
auto buffer3 = device->createBufferUnique(bufferCreateInfo).value;
ASSERT_THAT(buffer3, IsValidHandle());
const vkhpp::MemoryAllocateInfo buffer3MemoryAllocateInfo = {
.allocationSize = bufferMemoryRequirements.size,
.memoryTypeIndex = bufferMemoryIndex,
};
auto buffer3Memory = device->allocateMemoryUnique(buffer3MemoryAllocateInfo).value;
ASSERT_THAT(buffer3Memory, IsValidHandle());
ASSERT_THAT(device->bindBufferMemory(*buffer3, *buffer3Memory, 0), IsVkSuccess());
void* mapped = nullptr;
ASSERT_THAT(device->mapMemory(*buffer3Memory, 0, VK_WHOLE_SIZE,
vkhpp::MemoryMapFlags{}, &mapped),
IsVkSuccess());
ASSERT_THAT(mapped, NotNull());
device->unmapMemory(*buffer3Memory);
}
});
}
for (auto& thread : threads) {
thread.join();
}
}
TEST_P(GfxstreamEnd2EndVkTest, MultiThreadedResetCommandBuffer) {
auto [instance, physicalDevice, device, queue, queueFamilyIndex] =
GFXSTREAM_ASSERT(SetUpTypicalVkTestEnvironment());
static constexpr const vkhpp::DeviceSize kSize = 1024;
const vkhpp::BufferCreateInfo bufferCreateInfo = {
.size = kSize,
.usage = vkhpp::BufferUsageFlagBits::eTransferSrc,
};
static std::mutex queue_mutex;
std::vector<std::thread> threads;
std::atomic_int threadsReady{0};
constexpr const int kNumThreads = 10;
for (int t = 0; t < kNumThreads; t++) {
threads.emplace_back([&, this]() {
// Perform some work to ensure host RenderThread started.
auto buffer2 = device->createBufferUnique(bufferCreateInfo).value;
ASSERT_THAT(buffer2, IsValidHandle());
++threadsReady;
while (threadsReady.load() != kNumThreads) {
}
const vkhpp::CommandPoolCreateInfo commandPoolCreateInfo = {
.queueFamilyIndex = queueFamilyIndex,
};
auto commandPool = device->createCommandPoolUnique(commandPoolCreateInfo).value;
const vkhpp::CommandBufferAllocateInfo commandBufferAllocateInfo = {
.level = vkhpp::CommandBufferLevel::ePrimary,
.commandPool = *commandPool,
.commandBufferCount = 1,
};
auto commandBuffers = device->allocateCommandBuffersUnique(commandBufferAllocateInfo).value;
ASSERT_THAT(commandBuffers, Not(IsEmpty()));
auto commandBuffer = std::move(commandBuffers[0]);
ASSERT_THAT(commandBuffer, IsValidHandle());
auto transferFence = device->createFenceUnique(vkhpp::FenceCreateInfo()).value;
ASSERT_THAT(commandBuffer, IsValidHandle());
constexpr const int kNumIterations = 1000;
for (int i = 0; i < kNumIterations; i++) {
commandBuffer->reset();
const vkhpp::CommandBufferBeginInfo commandBufferBeginInfo = {
.flags = vkhpp::CommandBufferUsageFlagBits::eOneTimeSubmit,
};
commandBuffer->begin(commandBufferBeginInfo);
commandBuffer->end();
std::vector<vkhpp::CommandBuffer> commandBufferHandles;
commandBufferHandles.push_back(*commandBuffer);
const vkhpp::SubmitInfo submitInfo = {
.commandBufferCount = static_cast<uint32_t>(commandBufferHandles.size()),
.pCommandBuffers = commandBufferHandles.data(),
};
{
std::lock_guard<std::mutex> qm(queue_mutex);
queue.submit(submitInfo, *transferFence);
}
auto waitResult = device->waitForFences(*transferFence, VK_TRUE, AsVkTimeout(3s));
ASSERT_THAT(waitResult, IsVkSuccess());
}
});
}
for (auto& thread : threads) {
thread.join();
}
}
TEST_P(GfxstreamEnd2EndVkTest, ImportAndBlitFromR8G8B8A8Ahb) {
DoFillAndRenderFromAhb(GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM);
}
TEST_P(GfxstreamEnd2EndVkTest, ImportAndBlitFromYCbCr888420Ahb) {
DoFillAndRenderFromAhb(GFXSTREAM_AHB_FORMAT_Y8Cb8Cr8_420);
}
TEST_P(GfxstreamEnd2EndVkTest, ImportAndBlitFromYv12Ahb) {
DoFillAndRenderFromAhb(GFXSTREAM_AHB_FORMAT_YV12);
}
std::vector<TestParams> GenerateTestCases() {
std::vector<TestParams> cases = {TestParams{
.with_gl = false,
.with_vk = true,
.with_transport = GfxstreamTransport::kVirtioGpuAsg,
},
TestParams{
.with_gl = true,
.with_vk = true,
.with_transport = GfxstreamTransport::kVirtioGpuAsg,
},
TestParams{
.with_gl = false,
.with_vk = true,
.with_transport = GfxstreamTransport::kVirtioGpuPipe,
},
TestParams{
.with_gl = true,
.with_vk = true,
.with_transport = GfxstreamTransport::kVirtioGpuPipe,
}};
cases = WithAndWithoutFeatures(cases, {"VulkanSnapshots"});
cases = WithAndWithoutFeatures(cases, {"VulkanUseDedicatedAhbMemoryType"});
return cases;
}
INSTANTIATE_TEST_CASE_P(GfxstreamEnd2EndTests, GfxstreamEnd2EndVkTest,
::testing::ValuesIn(GenerateTestCases()), &GetTestName);
} // namespace
} // namespace tests
} // namespace gfxstream