| // 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/common/gtest_escher.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 |