blob: a8031c71315b51492e286f7283970effe640fea2 [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_downloader.h"
#include <thread>
#include "gtest/gtest.h"
#include "src/ui/lib/escher/renderer/batch_gpu_uploader.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/image_factory.h"
#include "test/test_with_vk_validation_layer_macros.h"
#include "third_party/granite/vk/command_buffer.h"
namespace escher {
using BatchGpuDownloaderTest = test::TestWithVkValidationLayer;
VK_TEST_F(BatchGpuDownloaderTest, CreateDestroyDownloader) {
auto escher = test::GetEscher()->GetWeakPtr();
bool batch_download_done = false;
{
std::unique_ptr<BatchGpuDownloader> downloader = BatchGpuDownloader::New(escher);
// BatchGpuDownloader must be submitted before it is destroyed.
downloader->Submit([&batch_download_done]() { batch_download_done = true; });
}
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_download_done);
}
VK_TEST_F(BatchGpuDownloaderTest, InvalidDownloader) {
// A BatchGpuDownloader without an escher should not be created.
auto downloader = BatchGpuDownloader::New(EscherWeakPtr());
EXPECT_FALSE(downloader);
}
VK_TEST_F(BatchGpuDownloaderTest, CallbackTriggeredOnEmptyDownloader) {
auto escher = test::GetEscher()->GetWeakPtr();
std::unique_ptr<BatchGpuDownloader> downloader = BatchGpuDownloader::New(escher);
EXPECT_FALSE(downloader->HasContentToDownload());
bool callback_executed = false;
downloader->Submit([&callback_executed] { callback_executed = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(callback_executed);
}
VK_TEST_F(BatchGpuDownloaderTest, SubmitEmptyDownloader) {
auto escher = test::GetEscher()->GetWeakPtr();
std::unique_ptr<BatchGpuDownloader> downloader = BatchGpuDownloader::New(escher);
// BatchGpuDownloader must be submitted before it is destroyed.
bool batch_download_done = false;
downloader->Submit([&batch_download_done]() { batch_download_done = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_download_done);
}
VK_TEST_F(BatchGpuDownloaderTest, LazyInitializationTest) {
auto escher = test::GetEscher()->GetWeakPtr();
// Create buffer to read from.
const size_t buffer_size = 3 * sizeof(vec3);
BufferFactoryAdapter buffer_factory(escher->gpu_allocator(), escher->resource_recycler());
BufferPtr vertex_buffer = buffer_factory.NewBuffer(buffer_size,
vk::BufferUsageFlagBits::eVertexBuffer |
vk::BufferUsageFlagBits::eTransferSrc |
vk::BufferUsageFlagBits::eTransferDst,
vk::MemoryPropertyFlagBits::eDeviceLocal);
ASSERT_TRUE(vertex_buffer) << "Fatal: Cannot allocate device-local vertex buffer.";
std::unique_ptr<BatchGpuDownloader> downloader = BatchGpuDownloader::New(escher);
// BatchGpuDownloader will not be initialized until we schedule a read.
EXPECT_FALSE(downloader->HasContentToDownload());
// Read the vertex buffer.
bool read_done = false;
downloader->ScheduleReadBuffer(
vertex_buffer, [&read_done](const void* host_ptr, size_t size) { read_done = true; });
EXPECT_TRUE(downloader->HasContentToDownload());
// BatchGpuDownloader must be submitted before it is destroyed.
bool batch_download_done = false;
downloader->Submit([&batch_download_done]() { batch_download_done = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_download_done);
EXPECT_TRUE(read_done);
}
VK_TEST_F(BatchGpuDownloaderTest, SupportAllCommandBufferTypes) {
const std::vector<CommandBuffer::Type> kCommandBufferTypes = {CommandBuffer::Type::kTransfer,
CommandBuffer::Type::kCompute,
CommandBuffer::Type::kGraphics};
std::vector<bool> downloaders_done = {false, false, false};
auto escher = test::GetEscher()->GetWeakPtr();
for (size_t i = 0; i < downloaders_done.size(); ++i) {
const auto command_buffer_type = kCommandBufferTypes[i];
std::unique_ptr<BatchGpuDownloader> downloader =
BatchGpuDownloader::New(escher, command_buffer_type);
downloader->Submit([done_ptr = &downloaders_done[i]]() { *done_ptr = true; });
}
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
for (size_t i = 0; i < downloaders_done.size(); ++i) {
EXPECT_TRUE(downloaders_done[i]);
}
}
VK_TEST_F(BatchGpuDownloaderTest, InitializeUploaderAndDownloader) {
// This test case tests if BatchGpuUploader and BatchGpuDownloader can
// coexist with each other.
auto escher = test::GetEscher()->GetWeakPtr();
auto uploader = BatchGpuUploader::New(escher);
auto downloader = BatchGpuDownloader::New(escher);
bool uploader_finished = false;
bool batch_download_done = false;
uploader->Submit([&uploader_finished]() { uploader_finished = true; });
downloader->Submit([&batch_download_done]() { batch_download_done = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(uploader_finished);
EXPECT_TRUE(batch_download_done);
}
VK_TEST_F(BatchGpuDownloaderTest, ReadImageTest) {
auto escher = test::GetEscher()->GetWeakPtr();
// Upload an image to read back.
constexpr uint32_t kWidth = 512;
constexpr uint32_t kHeight = 256;
auto pixels = image_utils::NewNoisePixels(kWidth, kHeight);
auto image = image_utils::NewImage(escher->image_cache(), vk::Format::eR8Unorm, kWidth, kHeight);
BatchGpuUploader uploader(escher, 0);
image_utils::WritePixelsToImage(&uploader, pixels.get(), image,
vk::ImageLayout::eTransferSrcOptimal);
auto sema = escher::Semaphore::New(escher->vk_device());
uploader.AddSignalSemaphore(sema);
uploader.Submit();
BatchGpuDownloader downloader(escher, CommandBuffer::Type::kGraphics, 0);
downloader.AddWaitSemaphore(sema, vk::PipelineStageFlagBits::eTransfer);
bool read_image_done = false;
downloader.ScheduleReadImage(
image, [&read_image_done, original = std::move(pixels), num_bytes = kWidth * kHeight](
const void* host_ptr, size_t size) {
bool pixels_match = !memcmp(original.get(), host_ptr, num_bytes);
EXPECT_TRUE(pixels_match);
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);
}
VK_TEST_F(BatchGpuDownloaderTest, SubmitToCommandBuffer) {
auto escher = test::GetEscher()->GetWeakPtr();
// Upload an image to read back.
constexpr uint32_t kWidth = 512;
constexpr uint32_t kHeight = 256;
auto pixels = image_utils::NewNoisePixels(kWidth, kHeight);
auto image = image_utils::NewImage(escher->image_cache(), vk::Format::eR8Unorm, kWidth, kHeight);
BatchGpuUploader uploader(escher, 0);
image_utils::WritePixelsToImage(&uploader, pixels.get(), image,
vk::ImageLayout::eTransferSrcOptimal);
auto sema = escher::Semaphore::New(escher->vk_device());
uploader.AddSignalSemaphore(sema);
uploader.Submit();
BatchGpuDownloader downloader(escher, CommandBuffer::Type::kGraphics, 0);
downloader.AddWaitSemaphore(sema, vk::PipelineStageFlagBits::eTransfer);
bool read_image_done = false;
downloader.ScheduleReadImage(
image, [&read_image_done, original = std::move(pixels), num_bytes = kWidth * kHeight](
const void* host_ptr, size_t size) {
bool pixels_match = !memcmp(original.get(), host_ptr, num_bytes);
EXPECT_TRUE(pixels_match);
read_image_done = true;
});
auto cmds = CommandBuffer::NewForTransfer(escher.get());
auto downloader_callback = downloader.GenerateCommands(cmds.get());
bool command_buffer_done = false;
cmds->Submit([downloader_callback = std::move(downloader_callback), &command_buffer_done]() {
downloader_callback();
command_buffer_done = true;
});
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(read_image_done);
EXPECT_TRUE(command_buffer_done);
}
// For each Read() operation of BatchGpuDownloader, BatchGpuDownloader will
// keep the image layout if image layout is initialized; otherwise it will set
// image layout to eShaderReadOnlyOptimal.
//
// This test is created to make sure that the image layout is always set
// correctly, and we can do GPU image download no matter which layout the image
// has.
//
// In this test we first read the image and then submit the BatchGpuDownloader,
// then we read this image again using another BatchGpuDownloader to see if it
// works correctly.
VK_TEST_F(BatchGpuDownloaderTest, ReadTheSameImageTwice) {
auto escher = test::GetEscher()->GetWeakPtr();
// Upload an image to read back.
constexpr uint32_t kWidth = 512;
constexpr uint32_t kHeight = 256;
auto pixels = image_utils::NewNoisePixels(kWidth, kHeight);
auto image = image_utils::NewImage(escher->image_cache(), vk::Format::eR8Unorm, kWidth, kHeight);
BatchGpuUploader uploader(escher, 0);
image_utils::WritePixelsToImage(&uploader, pixels.get(), image,
vk::ImageLayout::eTransferSrcOptimal);
auto uploader_signal_sem = escher::Semaphore::New(escher->vk_device());
uploader.AddSignalSemaphore(uploader_signal_sem);
uploader.Submit();
// We first read the image to a buffer.
BatchGpuDownloader downloader(escher, CommandBuffer::Type::kTransfer, 0);
downloader.AddWaitSemaphore(uploader_signal_sem, vk::PipelineStageFlagBits::eTransfer);
SemaphorePtr downloader_signal_sem = Semaphore::New(escher->vk_device());
downloader.AddSignalSemaphore(downloader_signal_sem);
bool read_image_done = false;
downloader.ScheduleReadImage(
image, [&read_image_done](const void* host_ptr, size_t size) { read_image_done = true; });
bool batch_download_done = false;
downloader.Submit([&batch_download_done]() { batch_download_done = true; });
// After submitting the downloader command queue, read the same image again.
// In this case BatchGpuDownloader will do eShaderReadOnlyOptimal ->
// eTransferSrc and eTransferSrc -> eShaderReadOnlyOptimal conversion.
BatchGpuDownloader downloader2(escher, CommandBuffer::Type::kTransfer, 0);
downloader2.AddWaitSemaphore(downloader_signal_sem, vk::PipelineStageFlagBits::eTransfer);
bool read_image_done_2 = false;
downloader2.ScheduleReadImage(
image, [&read_image_done_2](const void* host_ptr, size_t size) { read_image_done_2 = true; });
bool batch_download_done_2 = false;
downloader2.Submit([&batch_download_done_2]() { batch_download_done_2 = true; });
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(read_image_done && read_image_done_2);
EXPECT_TRUE(batch_download_done && batch_download_done_2);
}
VK_TEST_F(BatchGpuDownloaderTest, ReadBufferTest) {
auto escher = test::GetEscher()->GetWeakPtr();
// Create buffer to read from.
const size_t buffer_size = 3 * sizeof(vec3);
BufferFactoryAdapter buffer_factory(escher->gpu_allocator(), escher->resource_recycler());
BufferPtr vertex_buffer = buffer_factory.NewBuffer(
buffer_size,
vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferSrc |
vk::BufferUsageFlagBits::eTransferDst,
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
// If VmaAllocator cannot allocate a buffer with both HostVisible and
// HostCoherent memory property (required for a host buffer which is
// able to be modified by the host), we will terminate the test.
if (!vertex_buffer) {
FXL_LOG(ERROR) << "Memory property not supported, test terminated.";
EXPECT_TRUE(escher->Cleanup());
return;
}
void* host_ptr = vertex_buffer->host_ptr();
vec3* const verts = static_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);
// Do read.
std::unique_ptr<BatchGpuDownloader> downloader =
BatchGpuDownloader::New(escher, CommandBuffer::Type::kTransfer);
bool read_buffer_done = false;
downloader->ScheduleReadBuffer(vertex_buffer,
[&read_buffer_done](const void* host_ptr, size_t size) {
const vec3* verts = static_cast<const vec3*>(host_ptr);
EXPECT_EQ(verts[0], vec3(0.f, 0.f, 0.f));
EXPECT_EQ(verts[1], vec3(0.f, 1.f, 0.f));
EXPECT_EQ(verts[2], vec3(1.f, 0.f, 0.f));
read_buffer_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_buffer_done);
EXPECT_TRUE(batch_download_done);
}
// Test to make sure that multiple downloader can access the same buffer
// multiple times and still succesfully submit work to the GPU and have it
// finish. This is to make sure that the command buffer does not get stuck
// waiting on itself.
VK_TEST_F(BatchGpuDownloaderTest, MultipleReadToSameBuffer) {
auto escher = test::GetEscher()->GetWeakPtr();
std::unique_ptr<BatchGpuDownloader> downloader = BatchGpuDownloader::New(escher);
// Create buffer to read from.
const size_t buffer_size = 3 * sizeof(vec3);
BufferFactoryAdapter buffer_factory(escher->gpu_allocator(), escher->resource_recycler());
BufferPtr vertex_buffer = buffer_factory.NewBuffer(buffer_size,
vk::BufferUsageFlagBits::eVertexBuffer |
vk::BufferUsageFlagBits::eTransferSrc |
vk::BufferUsageFlagBits::eTransferDst,
vk::MemoryPropertyFlagBits::eDeviceLocal);
ASSERT_TRUE(vertex_buffer) << "Fatal: Cannot allocate device-local vertex buffer.";
// Read the vertex buffer.
bool read_callback_executed = false;
downloader->ScheduleReadBuffer(vertex_buffer,
[&read_callback_executed](const void* host_ptr, size_t size) {
read_callback_executed = true;
});
// Read the *same* vertex buffer again.
bool read2_callback_executed = false;
downloader->ScheduleReadBuffer(vertex_buffer,
[&read2_callback_executed](const void* host_ptr, size_t size) {
read2_callback_executed = true;
});
// Read the *same* vertex buffer once again.
bool read3_callback_executed = false;
downloader->ScheduleReadBuffer(vertex_buffer,
[&read3_callback_executed](const void* host_ptr, size_t size) {
read3_callback_executed = true;
});
bool batch_download_done = false;
downloader->Submit([&batch_download_done]() { batch_download_done = true; });
// Trigger Cleanup, which triggers the callback on the submitted command
// buffer.
escher->vk_device().waitIdle();
EXPECT_TRUE(escher->Cleanup());
EXPECT_TRUE(batch_download_done);
EXPECT_TRUE(read_callback_executed);
EXPECT_TRUE(read2_callback_executed);
EXPECT_TRUE(read3_callback_executed);
}
VK_TEST_F(BatchGpuDownloaderTest, DISABLED_ReadAfterWriteSucceeds) {
// TODO(SCN-1197) Enable once memory barriers are added to the
// BatchGpuDownloader and it can be used for reads and writes on the same
// resource.
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::eTransferSrc |
vk::BufferUsageFlagBits::eTransferDst,
vk::MemoryPropertyFlagBits::eDeviceLocal);
ASSERT_TRUE(vertex_buffer) << "Fatal: Cannot allocate device-local vertex buffer.";
// Do write.
std::vector<uint8_t> host_data(buffer_size);
vec3* write_verts = reinterpret_cast<vec3*>(host_data.data());
write_verts[0] = vec3(0.f, 0.f, 1.f);
write_verts[1] = vec3(0.f, 1.f, 0.f);
write_verts[2] = vec3(1.f, 0.f, 0.f);
uploader->ScheduleWriteBuffer(vertex_buffer, host_data);
uploader->Submit();
// Create a BatchGpuDownloader to read from the buffer pending a write.
auto downloader = BatchGpuDownloader::New(escher, CommandBuffer::Type::kTransfer);
bool read_buffer_done = false;
downloader->ScheduleReadBuffer(
vertex_buffer, [&read_buffer_done, &write_verts](const void* host_ptr, size_t size) {
const vec3* read_verts = static_cast<const vec3*>(host_ptr);
EXPECT_EQ(read_verts[0], write_verts[0]);
EXPECT_EQ(read_verts[1], write_verts[1]);
EXPECT_EQ(read_verts[2], write_verts[2]);
read_buffer_done = true;
});
// Submit all the work.
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_buffer_done);
EXPECT_TRUE(batch_download_done);
}
} // namespace escher