blob: 6ca907fad765624e5aefc5386b00a81b0638c26d [file] [log] [blame]
// Copyright 2018 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/lib/escher/renderer/batch_gpu_uploader.h"
#include <thread>
#include "gtest/gtest.h"
#include "src/ui/lib/escher/renderer/batch_gpu_downloader.h"
#include "src/ui/lib/escher/test/gtest_escher.h"
#include "src/ui/lib/escher/test/vk/vulkan_tester.h"
#include "src/ui/lib/escher/util/image_utils.h"
#include "src/ui/lib/escher/vk/buffer_factory.h"
#include "src/ui/lib/escher/vk/command_buffer.h"
#include "src/ui/lib/escher/vk/image_factory.h"
namespace escher {
namespace {
std::pair<ImagePtr, vk::BufferImageCopy> Create1x1ImageAndRegion(EscherWeakPtr escher) {
// Create a 1x1 RGBA (8-bit channels) image to write to.
ImageFactoryAdapter image_factory(escher->gpu_allocator(), escher->resource_recycler());
ImagePtr image = image_utils::NewImage(&image_factory, vk::Format::eR8G8B8A8Unorm, 1, 1);
vk::BufferImageCopy region;
region.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageExtent.width = 1;
region.imageExtent.height = 1;
region.imageExtent.depth = 1;
region.bufferOffset = 0;
return {std::move(image), std::move(region)};
}
} // namespace
using BatchGpuUploaderTest = test::TestWithVkValidationLayer;
VK_TEST_F(BatchGpuUploaderTest, CreateDestroyUploader) {
auto escher = test::GetEscher()->GetWeakPtr();
bool batch_upload_done = false;
{
std::unique_ptr<BatchGpuUploader> uploader = BatchGpuUploader::New(escher);
// BatchGpuUploader must be submitted before it is destroyed.
uploader->Submit([&batch_upload_done]() { batch_upload_done = true; });
}
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_upload_done);
}
VK_TEST_F(BatchGpuUploaderTest, InvalidUploader) {
// A BatchGpuUploader without an escher should not be created.
auto uploader = BatchGpuUploader::New(EscherWeakPtr());
EXPECT_FALSE(uploader);
}
VK_TEST_F(BatchGpuUploaderTest, CallbackTriggeredOnEmptyUploader) {
auto escher = test::GetEscher()->GetWeakPtr();
std::unique_ptr<BatchGpuUploader> uploader = BatchGpuUploader::New(escher);
EXPECT_FALSE(uploader->HasContentToUpload());
bool callback_executed = false;
uploader->Submit([&callback_executed] { callback_executed = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(callback_executed);
}
VK_TEST_F(BatchGpuUploaderTest, WriteBufferUsingWriteFunction) {
auto escher = test::GetEscher()->GetWeakPtr();
auto uploader = BatchGpuUploader::New(escher);
const size_t buffer_size = 3 * sizeof(vec3);
// Create buffer to write to.
BufferFactoryAdapter buffer_factory(escher->gpu_allocator(), escher->resource_recycler());
BufferPtr vertex_buffer = buffer_factory.NewBuffer(
buffer_size, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst,
vk::MemoryPropertyFlagBits::eDeviceLocal);
// Do write.
bool write_finished = false;
uploader->ScheduleWriteBuffer(
vertex_buffer,
[&write_finished](uint8_t* host_ptr, size_t size) {
EXPECT_TRUE(size >= sizeof(vec3) * 3);
vec3* verts = reinterpret_cast<vec3*>(host_ptr);
verts[0] = vec3(0.f, 0.f, 0.f);
verts[1] = vec3(0.f, 1.f, 0.f);
verts[2] = vec3(1.f, 0.f, 0.f);
write_finished = true;
},
/* target_offset */ 0, /* copy_size */ buffer_size);
// The write is deferred until we generate the commands.
EXPECT_FALSE(write_finished);
// Submit the work.
bool batch_upload_done = false;
uploader->Submit([&batch_upload_done]() { batch_upload_done = true; });
EXPECT_TRUE(write_finished);
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_upload_done);
}
VK_TEST_F(BatchGpuUploaderTest, WriteBufferUsingVectorOfUint8) {
auto escher = test::GetEscher()->GetWeakPtr();
auto uploader = BatchGpuUploader::New(escher);
const size_t buffer_size = 3 * sizeof(vec3);
// Create buffer to write to.
BufferFactoryAdapter buffer_factory(escher->gpu_allocator(), escher->resource_recycler());
BufferPtr vertex_buffer = buffer_factory.NewBuffer(
buffer_size, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst,
vk::MemoryPropertyFlagBits::eDeviceLocal);
// Do write.
std::vector<uint8_t> host_data(buffer_size);
vec3* verts = reinterpret_cast<vec3*>(host_data.data());
verts[0] = vec3(0.f, 0.f, 0.f);
verts[1] = vec3(0.f, 1.f, 0.f);
verts[2] = vec3(1.f, 0.f, 0.f);
uploader->ScheduleWriteBuffer(vertex_buffer, std::move(host_data));
// Submit the work.
bool batch_upload_done = false;
uploader->Submit([&batch_upload_done]() { batch_upload_done = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_upload_done);
}
VK_TEST_F(BatchGpuUploaderTest, WriteBufferUsingVectorOfAnyType) {
auto escher = test::GetEscher()->GetWeakPtr();
auto uploader = BatchGpuUploader::New(escher);
const size_t buffer_size = 3 * sizeof(vec3);
// Create buffer to write to.
BufferFactoryAdapter buffer_factory(escher->gpu_allocator(), escher->resource_recycler());
BufferPtr vertex_buffer = buffer_factory.NewBuffer(
buffer_size, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst,
vk::MemoryPropertyFlagBits::eDeviceLocal);
// Do write.
std::vector<vec3> verts(3);
verts[0] = vec3(0.f, 0.f, 0.f);
verts[1] = vec3(0.f, 1.f, 0.f);
verts[2] = vec3(1.f, 0.f, 0.f);
uploader->ScheduleWriteBuffer(vertex_buffer, verts);
// Submit the work.
bool batch_upload_done = false;
uploader->Submit([&batch_upload_done]() { batch_upload_done = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_upload_done);
}
VK_TEST_F(BatchGpuUploaderTest, LazyInitializationTest) {
auto escher = test::GetEscher()->GetWeakPtr();
std::unique_ptr<BatchGpuUploader> uploader = BatchGpuUploader::New(escher);
constexpr vk::DeviceSize kBufferSize = 1024;
BufferFactoryAdapter buffer_factory(escher->gpu_allocator(), escher->resource_recycler());
BufferPtr buffer = buffer_factory.NewBuffer(
kBufferSize, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst,
vk::MemoryPropertyFlagBits::eDeviceLocal);
// BatchGpuUploader will not be initialized until instantiation of Writers.
EXPECT_FALSE(uploader->HasContentToUpload());
std::vector<uint8_t> host_data(kBufferSize, 0x7f);
uploader->ScheduleWriteBuffer(buffer, std::move(host_data));
EXPECT_TRUE(uploader->HasContentToUpload());
// BatchGpuUploader must be submitted before it is destroyed.
bool batch_upload_done = false;
uploader->Submit([&batch_upload_done]() { batch_upload_done = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_upload_done);
}
VK_TEST_F(BatchGpuUploaderTest, WriteImageUsingWriteFunction) {
auto escher = test::GetEscher()->GetWeakPtr();
auto uploader = BatchGpuUploader::New(escher);
const size_t image_size = sizeof(uint8_t) * 4;
// Create a 1x1 RGBA (8-bit channels) image to write to.
auto [image, region] = Create1x1ImageAndRegion(escher);
// Do write.
constexpr vk::ImageLayout kTargetImageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
bool write_finished = false;
uploader->ScheduleWriteImage(
image,
[&write_finished](uint8_t* host_ptr, size_t data_size) {
EXPECT_TRUE(data_size >= 4);
host_ptr[0] = 150;
host_ptr[1] = 88;
host_ptr[2] = 121;
host_ptr[3] = 255;
write_finished = true;
},
kTargetImageLayout);
// The write is deferred until we generate the commands.
EXPECT_FALSE(write_finished);
// Submit the work.
bool batch_upload_done = false;
uploader->Submit([&batch_upload_done]() { batch_upload_done = true; });
EXPECT_TRUE(write_finished);
escher->vk_device().waitIdle();
// Verify that the image layout was set correctly.
EXPECT_TRUE(image->layout() == kTargetImageLayout);
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_upload_done);
}
VK_TEST_F(BatchGpuUploaderTest, WriteImageUsingVectorOfUint8) {
auto escher = test::GetEscher()->GetWeakPtr();
auto uploader = BatchGpuUploader::New(escher);
const size_t image_size = sizeof(uint8_t) * 4;
// Create a 1x1 RGBA (8-bit channels) image to write to.
auto [image, region] = Create1x1ImageAndRegion(escher);
// Do write.
constexpr vk::ImageLayout kTargetImageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
std::vector<uint8_t> pixels(image_size);
pixels[0] = 150;
pixels[1] = 88;
pixels[2] = 121;
pixels[3] = 255;
uploader->ScheduleWriteImage(image, std::move(pixels), kTargetImageLayout, region);
// Submit the work.
bool batch_upload_done = false;
uploader->Submit([&batch_upload_done]() { batch_upload_done = true; });
escher->vk_device().waitIdle();
// Verify that the image layout was set correctly.
EXPECT_TRUE(image->layout() == kTargetImageLayout);
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_upload_done);
}
VK_TEST_F(BatchGpuUploaderTest, WriteImageUsingVectorOfAnyType) {
struct RGBA {
uint8_t r, g, b, a;
};
auto escher = test::GetEscher()->GetWeakPtr();
auto uploader = BatchGpuUploader::New(escher);
// Create a 1x1 RGBA (8-bit channels) image to write to.
auto [image, region] = Create1x1ImageAndRegion(escher);
// Do write.
constexpr vk::ImageLayout kTargetImageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
std::vector<RGBA> pixels;
pixels.push_back({150, 88, 121, 255});
uploader->ScheduleWriteImage(image, std::move(pixels), kTargetImageLayout, region);
// Submit the work.
bool batch_upload_done = false;
uploader->Submit([&batch_upload_done]() { batch_upload_done = true; });
escher->vk_device().waitIdle();
// Verify that the image layout was set correctly.
EXPECT_TRUE(image->layout() == kTargetImageLayout);
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_upload_done);
}
// This unit tests if we can upload to the same image multiple times and if
// the image layout can be set correctly every time we upload the image.
VK_TEST_F(BatchGpuUploaderTest, ChangeLayout) {
auto escher = test::GetEscher()->GetWeakPtr();
// Create a 1x1 RGBA (8-bit channels) image to write to.
std::vector<uint8_t> pixel_1 = {150, 88, 121, 255};
auto [image, region] = Create1x1ImageAndRegion(escher);
// Do write.
auto uploader = BatchGpuUploader::New(escher);
constexpr vk::ImageLayout kTargetImageLayout = vk::ImageLayout::eGeneral;
uploader->ScheduleWriteImage(image, std::move(pixel_1), kTargetImageLayout, region);
// Submit the work.
bool batch_upload_done = false;
uploader->Submit([&batch_upload_done]() { batch_upload_done = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(image->layout() == kTargetImageLayout);
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_upload_done);
// Write the image again and change the image layout to another layout.
auto uploader_2 = BatchGpuUploader::New(escher);
constexpr vk::ImageLayout kTargetImageLayout_2 = vk::ImageLayout::eShaderReadOnlyOptimal;
std::vector<uint8_t> pixel_2 = {130, 120, 110, 255};
uploader_2->ScheduleWriteImage(image, std::move(pixel_2), kTargetImageLayout_2, region);
// Submit the work.
bool batch_upload_done_2 = false;
uploader_2->Submit([&batch_upload_done_2]() { batch_upload_done_2 = true; });
escher->vk_device().waitIdle();
// Verify that the image layout was set correctly.
EXPECT_TRUE(image->layout() == kTargetImageLayout_2);
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_upload_done_2);
}
VK_TEST_F(BatchGpuUploaderTest, SubmitImageToCommandBuffer) {
auto escher = test::GetEscher()->GetWeakPtr();
// Create a 1x1 RGBA (8-bit channels) image to write to.
std::vector<uint8_t> pixel_1 = {150, 88, 121, 255};
auto [image, region] = Create1x1ImageAndRegion(escher);
// Do write.
auto uploader = BatchGpuUploader::New(escher);
constexpr vk::ImageLayout kTargetImageLayout = vk::ImageLayout::eGeneral;
uploader->ScheduleWriteImage(image, std::move(pixel_1), kTargetImageLayout, region);
auto cmds = CommandBuffer::NewForTransfer(escher.get());
uploader->GenerateCommands(cmds.get());
bool uploaded = false;
cmds->Submit([&uploaded]() { uploaded = true; });
EXPECT_FALSE(uploader->HasContentToUpload());
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(uploaded);
// Check if the uploaded content is correct.
auto downloader = BatchGpuDownloader::New(escher);
bool pixel_correct = false;
downloader->ScheduleReadImage(
image,
[&pixel_correct](const void* host_ptr, size_t size) {
const auto* pixel_downloaded = reinterpret_cast<const uint8_t*>(host_ptr);
pixel_correct = pixel_downloaded[0] == 150u && pixel_downloaded[1] == 88u &&
pixel_downloaded[2] == 121u && pixel_downloaded[3] == 255u;
},
region);
bool downloaded = false;
downloader->Submit([&downloaded]() { downloaded = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(downloaded && pixel_correct);
}
VK_TEST_F(BatchGpuUploaderTest, ReuseAfterSubmission) {
auto escher = test::GetEscher()->GetWeakPtr();
// Create a 1x1 RGBA (8-bit channels) image to write to.
std::vector<uint8_t> pixel_1 = {150, 88, 121, 255};
std::vector<uint8_t> pixel_2 = {130, 120, 110, 255};
auto [image_1, region_1] = Create1x1ImageAndRegion(escher);
auto [image_2, region_2] = Create1x1ImageAndRegion(escher);
// Do write.
auto uploader = BatchGpuUploader::New(escher);
constexpr vk::ImageLayout kTargetImageLayout = vk::ImageLayout::eGeneral;
uploader->ScheduleWriteImage(image_1, std::move(pixel_1), kTargetImageLayout, region_1);
EXPECT_TRUE(uploader->HasContentToUpload());
bool uploaded_1 = false;
uploader->Submit([&uploaded_1]() { uploaded_1 = true; });
EXPECT_FALSE(uploader->HasContentToUpload());
// Schedule another write after submission.
uploader->ScheduleWriteImage(image_2, std::move(pixel_2), kTargetImageLayout, region_2);
EXPECT_TRUE(uploader->HasContentToUpload());
bool uploaded_2 = false;
uploader->Submit([&uploaded_2]() { uploaded_2 = true; });
EXPECT_FALSE(uploader->HasContentToUpload());
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(uploaded_1 && uploaded_2);
}
VK_TEST_F(BatchGpuUploaderTest, UnfinishedWorkDeathTest) {
auto escher = test::GetEscher()->GetWeakPtr();
auto [image, region] = Create1x1ImageAndRegion(escher);
std::vector<uint8_t> pixel_1 = {150, 88, 121, 255};
constexpr vk::ImageLayout kTargetImageLayout = vk::ImageLayout::eGeneral;
// Submit should fail since we scheduled image write but didn't submit that.
EXPECT_DEATH(
{
auto uploader = BatchGpuUploader::New(escher);
uploader->ScheduleWriteImage(image, std::move(pixel_1), kTargetImageLayout, region);
uploader = nullptr;
},
"");
}
} // namespace escher