blob: 28cb85b0545e2e3a81872db7616555859d983719 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/ui/scenic/lib/flatland/renderer/gpu_mem.h"
#include <lib/fdio/directory.h>
#include "src/ui/lib/escher/flatland/rectangle_compositor.h"
#include "src/ui/lib/escher/impl/vulkan_utils.h"
#include "src/ui/lib/escher/renderer/batch_gpu_downloader.h"
#include "src/ui/lib/escher/renderer/batch_gpu_uploader.h"
#include "src/ui/lib/escher/util/image_utils.h"
#include "src/ui/scenic/lib/flatland/buffers/util.h"
#include "src/ui/scenic/lib/flatland/renderer/tests/common.h"
namespace {
vk::BufferCollectionFUCHSIA CreateVulkanCollection(const vk::Device& device,
const vk::DispatchLoaderDynamic& vk_loader,
flatland::BufferCollectionHandle token) {
vk::BufferCollectionCreateInfoFUCHSIA buffer_collection_create_info;
buffer_collection_create_info.collectionToken = token.TakeChannel().release();
return escher::ESCHER_CHECKED_VK_RESULT(
device.createBufferCollectionFUCHSIA(buffer_collection_create_info, nullptr, vk_loader));
}
} // anonymous namespace
namespace escher {
namespace test {
const uint32_t kWidth = 32;
const uint32_t kHeight = 64;
using MemoryTest = flatland::RendererTest;
// Creates a buffer collection with multiple vmos and tries to import each of those
// vmos into GPU memory.
VK_TEST_F(MemoryTest, SimpleTest) {
SKIP_TEST_IF_ESCHER_USES_DEVICE(VirtualGpu);
const uint32_t kImageCount = 5U;
auto escher = GetEscher();
auto vk_device = escher->vk_device();
auto vk_loader = escher->device()->dispatch_loader();
vk::ImageUsageFlags usage = RectangleCompositor::kTextureUsageFlags;
auto image_create_info =
escher::RectangleCompositor::GetDefaultImageConstraints(vk::Format::eUndefined, usage);
auto tokens = flatland::SysmemTokens::Create(sysmem_allocator_.get());
auto result =
flatland::BufferCollectionInfo::New(sysmem_allocator_.get(), std::move(tokens.dup_token));
EXPECT_TRUE(result.is_ok());
auto collection = std::move(result.value());
// Set vulkan constraints.
fuchsia::sysmem::BufferCollectionTokenSyncPtr vulkan_token;
zx_status_t status = tokens.local_token->Duplicate(std::numeric_limits<uint32_t>::max(),
vulkan_token.NewRequest());
EXPECT_EQ(status, ZX_OK);
auto vk_collection = CreateVulkanCollection(vk_device, vk_loader, std::move(vulkan_token));
EXPECT_EQ(
vk_device.setBufferCollectionConstraintsFUCHSIA(vk_collection, image_create_info, vk_loader),
vk::Result::eSuccess);
// Set client constraints and wait for allocation.
flatland::SetClientConstraintsAndWaitForAllocated(
sysmem_allocator_.get(), std::move(tokens.local_token), kImageCount, kWidth, kHeight);
// Server collection should be allocated now.
EXPECT_TRUE(collection.BuffersAreAllocated());
for (uint32_t i = 0; i < kImageCount; i++) {
auto gpu_info = flatland::GpuImageInfo::New(vk_device, vk_loader, collection.GetSysmemInfo(),
vk_collection, i);
EXPECT_TRUE(gpu_info.GetGpuMem());
EXPECT_TRUE(gpu_info.p_extension());
auto vk_image_create_info = gpu_info.NewVkImageCreateInfo(kWidth, kHeight, usage);
EXPECT_EQ(vk_image_create_info.extent, vk::Extent3D(kWidth, kHeight, 1));
EXPECT_TRUE(vk_image_create_info.pNext);
}
// Cleanup.
vk_device.destroyBufferCollectionFUCHSIA(vk_collection, nullptr, vk_loader);
}
// Even if the BufferCollection is valid and allocated, no memory should be allocated if an
// invalid index that is outside of the range of Vmos the BufferCollection has is provided.
VK_TEST_F(MemoryTest, OutOfBoundsTest) {
const uint32_t kImageCount = 1U;
auto escher = GetEscher();
auto vk_device = escher->vk_device();
auto vk_loader = escher->device()->dispatch_loader();
vk::ImageUsageFlags usage = RectangleCompositor::kTextureUsageFlags;
auto image_create_info =
escher::RectangleCompositor::GetDefaultImageConstraints(vk::Format::eUndefined, usage);
auto tokens = flatland::SysmemTokens::Create(sysmem_allocator_.get());
auto result =
flatland::BufferCollectionInfo::New(sysmem_allocator_.get(), std::move(tokens.dup_token));
EXPECT_TRUE(result.is_ok());
auto collection = std::move(result.value());
fuchsia::sysmem::BufferCollectionTokenSyncPtr vulkan_token;
zx_status_t status = tokens.local_token->Duplicate(std::numeric_limits<uint32_t>::max(),
vulkan_token.NewRequest());
EXPECT_EQ(status, ZX_OK);
auto vk_collection = CreateVulkanCollection(vk_device, vk_loader, std::move(vulkan_token));
EXPECT_EQ(
vk_device.setBufferCollectionConstraintsFUCHSIA(vk_collection, image_create_info, vk_loader),
vk::Result::eSuccess);
flatland::SetClientConstraintsAndWaitForAllocated(
sysmem_allocator_.get(), std::move(tokens.local_token), kImageCount, kWidth, kHeight);
EXPECT_TRUE(collection.BuffersAreAllocated());
// This should fail however, as the index is beyond bounds.
auto gpu_info =
flatland::GpuImageInfo::New(vk_device, vk_loader, collection.GetSysmemInfo(), vk_collection,
/*index*/ 1);
EXPECT_FALSE(gpu_info.GetGpuMem());
// Cleanup.
vk_device.destroyBufferCollectionFUCHSIA(vk_collection, nullptr, vk_loader);
}
// This test checks the entire pipeline flow, which involves creating a buffer
// collection, writing to one of its Vmos, creating the GPUInfo object, creating
// an image from that gpu object, and then finally reading out the pixel value
// from the image using the GPUDownloader and making sure the value matches what
// was written to the initial buffer.
VK_TEST_F(MemoryTest, ImageReadWriteTest) {
SKIP_TEST_IF_ESCHER_USES_DEVICE(VirtualGpu);
const uint32_t kImageCount = 1U;
auto escher = GetEscher();
auto vk_device = escher->vk_device();
auto vk_loader = escher->device()->dispatch_loader();
auto resource_recycler = escher->resource_recycler();
vk::ImageUsageFlags usage = RectangleCompositor::kTextureUsageFlags;
auto image_create_info =
escher::RectangleCompositor::GetDefaultImageConstraints(vk::Format::eUndefined, usage);
// First create the pair of sysmem tokens, one for the client, one for the server.
auto tokens = flatland::SysmemTokens::Create(sysmem_allocator_.get());
// Create the buffer collection struct and set the server-side vulkan constraints.
flatland::BufferCollectionInfo server_collection;
vk::BufferCollectionFUCHSIA vk_collection;
{
auto result =
flatland::BufferCollectionInfo::New(sysmem_allocator_.get(), std::move(tokens.dup_token));
EXPECT_TRUE(result.is_ok());
server_collection = std::move(result.value());
fuchsia::sysmem::BufferCollectionTokenSyncPtr vulkan_token;
zx_status_t status = tokens.local_token->Duplicate(std::numeric_limits<uint32_t>::max(),
vulkan_token.NewRequest());
EXPECT_EQ(status, ZX_OK);
vk_collection = CreateVulkanCollection(vk_device, vk_loader, std::move(vulkan_token));
EXPECT_EQ(vk_device.setBufferCollectionConstraintsFUCHSIA(vk_collection, image_create_info,
vk_loader),
vk::Result::eSuccess);
}
// Create a client-side handle to the buffer collection and set the client constraints.
fuchsia::sysmem::BufferCollectionSyncPtr client_collection;
{
zx_status_t status = sysmem_allocator_->BindSharedCollection(std::move(tokens.local_token),
client_collection.NewRequest());
EXPECT_EQ(status, ZX_OK);
client_collection->SetName(100u, "FlatlandImageReadWriteTest");
fuchsia::sysmem::BufferCollectionConstraints constraints;
constraints.has_buffer_memory_constraints = true;
constraints.buffer_memory_constraints.cpu_domain_supported = true;
constraints.buffer_memory_constraints.ram_domain_supported = true;
constraints.usage.cpu = fuchsia::sysmem::cpuUsageWriteOften;
constraints.min_buffer_count = kImageCount;
constraints.image_format_constraints_count = 1;
auto& image_constraints = constraints.image_format_constraints[0];
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0] =
fuchsia::sysmem::ColorSpace{.type = fuchsia::sysmem::ColorSpaceType::SRGB};
image_constraints.pixel_format.type = fuchsia::sysmem::PixelFormatType::BGRA32;
image_constraints.pixel_format.has_format_modifier = true;
image_constraints.pixel_format.format_modifier.value = fuchsia::sysmem::FORMAT_MODIFIER_LINEAR;
image_constraints.required_min_coded_width = kWidth;
image_constraints.required_min_coded_height = kHeight;
image_constraints.required_max_coded_width = kWidth;
image_constraints.required_max_coded_height = kHeight;
image_constraints.max_coded_width = kWidth * 4;
image_constraints.max_coded_height = kHeight;
image_constraints.max_bytes_per_row = 0xffffffff;
status = client_collection->SetConstraints(true, constraints);
EXPECT_EQ(status, ZX_OK);
}
// Have the client wait for buffers allocated so it can populate its information
// struct with the vmo data.
fuchsia::sysmem::BufferCollectionInfo_2 client_collection_info = {};
{
zx_status_t allocation_status = ZX_OK;
auto status =
client_collection->WaitForBuffersAllocated(&allocation_status, &client_collection_info);
EXPECT_EQ(status, ZX_OK);
EXPECT_EQ(allocation_status, ZX_OK);
}
// Have the server also check allocation. Both client and server have set constraints, so this
// should be true.
EXPECT_TRUE(server_collection.BuffersAreAllocated());
// Get a raw pointer from the client collection's vmo and write several values to it.
const uint8_t kNumWrites = 10;
const uint8_t kWriteValues[] = {200U, 150U, 93U, 50U, 80U, 77U, 11U, 32U, 9U, 199U};
flatland::MapHostPointer(
client_collection_info, /*vmo_idx*/ 0, [kWriteValues](uint8_t* vmo_host, uint32_t num_bytes) {
memcpy(vmo_host, kWriteValues, sizeof(uint8_t) * kNumWrites);
// Flush the cache after writing to host VMO.
EXPECT_EQ(ZX_OK, zx_cache_flush(vmo_host, kNumWrites,
ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE));
});
// Create the GPU info from the server side collection.
auto gpu_info = flatland::GpuImageInfo::New(
vk_device, vk_loader, server_collection.GetSysmemInfo(), vk_collection, /*index*/ 0);
EXPECT_TRUE(gpu_info.GetGpuMem());
// Create an image from the server side collection.
auto image =
image_utils::NewImage(vk_device, gpu_info.NewVkImageCreateInfo(kWidth, kHeight, usage),
gpu_info.GetGpuMem(), resource_recycler);
// The returned image should not be null and should have the
// width and height specified above.
EXPECT_TRUE(image);
if (image) {
EXPECT_EQ(image->width(), kWidth);
EXPECT_EQ(image->height(), kHeight);
EXPECT_EQ(image->vk_format(), vk::Format::eB8G8R8A8Unorm);
EXPECT_EQ(image->size(), kWidth * kHeight * 4);
}
// Now we will read from the image and see if it matches what we wrote to it on the client side.
BatchGpuDownloader downloader(escher->GetWeakPtr(), CommandBuffer::Type::kGraphics, 0);
bool read_image_done = false;
downloader.ScheduleReadImage(
image, [&read_image_done, &kWriteValues](const void* host_ptr, size_t size) {
for (uint32_t i = 0; i < kNumWrites; i++) {
EXPECT_EQ(static_cast<const uint8_t*>(host_ptr)[i], kWriteValues[i]);
}
read_image_done = true;
});
bool batch_download_done = false;
downloader.Submit([&batch_download_done]() { batch_download_done = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(read_image_done);
EXPECT_TRUE(batch_download_done);
// Now we'll update the client side values one more time. We're going to check if the values in
// the VK image are also updated when we update the client values even though the image has
// already been created. Proving this works will mean that we can have the client continuously
// update the same image instead of having to create a new image for every new change.
const uint8_t kWriteValuesAgain[] = {231U, 188U, 19U, 75U, 13U, 45U, 47U, 98U, 05U, 214U};
flatland::MapHostPointer(
client_collection_info, /*vmo_idx*/ 0,
[kWriteValuesAgain](uint8_t* vmo_host, uint32_t num_bytes) {
memcpy(vmo_host, kWriteValuesAgain, sizeof(uint8_t) * kNumWrites);
// Flush the cache after writing to host VMO.
EXPECT_EQ(ZX_OK, zx_cache_flush(vmo_host, kNumWrites,
ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE));
});
bool read_image_again_done = false;
downloader.ScheduleReadImage(
image, [&read_image_again_done, &kWriteValuesAgain](const void* host_ptr, size_t size) {
for (uint32_t i = 0; i < kNumWrites; i++) {
uint8_t val = static_cast<const uint8_t*>(host_ptr)[i];
EXPECT_EQ(val, kWriteValuesAgain[i]) << val << ", " << kWriteValuesAgain[i];
}
read_image_again_done = true;
});
bool batch_download_again_done = false;
downloader.Submit([&batch_download_again_done]() { batch_download_again_done = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(read_image_again_done);
EXPECT_TRUE(batch_download_again_done);
vk_device.destroyBufferCollectionFUCHSIA(vk_collection, nullptr, vk_loader);
}
} // namespace test
} // namespace escher