| // 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 <lib/async-testing/test_loop.h> |
| #include <lib/async/cpp/wait.h> |
| #include <lib/async/default.h> |
| #include <lib/fdio/directory.h> |
| |
| #include <thread> |
| |
| #include "src/ui/scenic/lib/flatland/renderer/null_renderer.h" |
| #include "src/ui/scenic/lib/flatland/renderer/tests/common.h" |
| #include "src/ui/scenic/lib/flatland/renderer/vk_renderer.h" |
| |
| #include <glm/gtc/constants.hpp> |
| #include <glm/gtx/matrix_transform_2d.hpp> |
| |
| namespace flatland { |
| |
| using NullRendererTest = RendererTest; |
| using VulkanRendererTest = RendererTest; |
| |
| namespace { |
| static constexpr float kDegreesToRadians = glm::pi<float>() / 180.f; |
| |
| void GetVmoHostPtr(uint8_t** vmo_host, |
| const fuchsia::sysmem::BufferCollectionInfo_2& collection_info, uint32_t idx) { |
| const zx::vmo& image_vmo = collection_info.buffers[idx].vmo; |
| auto image_vmo_bytes = collection_info.settings.buffer_settings.size_bytes; |
| ASSERT_TRUE(image_vmo_bytes > 0); |
| auto status = zx::vmar::root_self()->map(0, image_vmo, 0, image_vmo_bytes, |
| ZX_VM_PERM_WRITE | ZX_VM_PERM_READ, |
| reinterpret_cast<uintptr_t*>(vmo_host)); |
| EXPECT_EQ(status, ZX_OK); |
| } |
| |
| glm::ivec4 GetPixel(uint8_t* vmo_host, uint32_t width, uint32_t x, uint32_t y) { |
| uint32_t r = vmo_host[y * width * 4 + x * 4]; |
| uint32_t g = vmo_host[y * width * 4 + x * 4 + 1]; |
| uint32_t b = vmo_host[y * width * 4 + x * 4 + 2]; |
| uint32_t a = vmo_host[y * width * 4 + x * 4 + 3]; |
| return glm::ivec4(r, g, b, a); |
| }; |
| |
| } // anonymous namespace |
| |
| // Make sure a valid token can be used to register a buffer collection. |
| void RegisterCollectionTest(Renderer* renderer, fuchsia::sysmem::Allocator_Sync* sysmem_allocator) { |
| auto tokens = CreateSysmemTokens(sysmem_allocator); |
| auto tokens2 = CreateSysmemTokens(sysmem_allocator); |
| |
| // First id should be valid. |
| auto bcid = sysmem_util::GenerateUniqueBufferCollectionId(); |
| auto result = renderer->RegisterRenderTargetCollection(bcid, sysmem_allocator, |
| std::move(tokens.local_token)); |
| EXPECT_TRUE(result); |
| } |
| |
| // Multiple clients may need to reference the same buffer collection in the renderer |
| // (for example if they both need access to a global camera feed). In this case, both |
| // clients will be passing their own duped tokens to the same collection to the renderer, |
| // and will each get back a different ID. The collection itself (which is just a pointer) |
| // will be in the renderer's map twice. So if all tokens are set, both server-side |
| // registered collections should be allocated (since they are just pointers that refer |
| // to the same collection). |
| void SameTokenTwiceTest(Renderer* renderer, fuchsia::sysmem::Allocator_Sync* sysmem_allocator) { |
| auto tokens = flatland::CreateSysmemTokens(sysmem_allocator); |
| |
| // Create a client token to represent a single client. |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr client_token; |
| auto status = tokens.local_token->Duplicate(std::numeric_limits<uint32_t>::max(), |
| client_token.NewRequest()); |
| EXPECT_EQ(status, ZX_OK); |
| |
| // First id should be valid. |
| auto bcid = sysmem_util::GenerateUniqueBufferCollectionId(); |
| auto result = renderer->RegisterRenderTargetCollection(bcid, sysmem_allocator, |
| std::move(tokens.local_token)); |
| EXPECT_TRUE(result); |
| |
| // Second id should be valid. |
| auto bcid2 = sysmem_util::GenerateUniqueBufferCollectionId(); |
| result = renderer->RegisterRenderTargetCollection(bcid2, sysmem_allocator, |
| std::move(tokens.dup_token)); |
| EXPECT_TRUE(result); |
| |
| // Set the client constraints. |
| SetClientConstraintsAndWaitForAllocated(sysmem_allocator, std::move(client_token)); |
| |
| // Now check that both server ids are allocated. |
| auto result_1 = renderer->Validate(bcid); |
| auto result_2 = renderer->Validate(bcid2); |
| |
| EXPECT_TRUE(result_1.has_value()); |
| EXPECT_TRUE(result_2.has_value()); |
| |
| // There should be 1 vmo each. |
| EXPECT_EQ(result_1->vmo_count, 1U); |
| EXPECT_EQ(result_2->vmo_count, 1U); |
| } |
| |
| // Make sure a bad token returns Renderer::sysmem_util::kInvalidId. A "bad token" here can |
| // either be a null token, or a token that's a valid channel but just not a |
| // valid buffer collection token. |
| void BadTokenTest(Renderer* renderer, fuchsia::sysmem::Allocator_Sync* sysmem_allocator) { |
| // Null token should fail. |
| auto bcid = sysmem_util::GenerateUniqueBufferCollectionId(); |
| auto result = renderer->RegisterRenderTargetCollection(bcid, sysmem_allocator, nullptr); |
| EXPECT_FALSE(result); |
| |
| // A valid channel that isn't a buffer collection should also fail. |
| zx::channel local_endpoint; |
| zx::channel remote_endpoint; |
| zx::channel::create(0, &local_endpoint, &remote_endpoint); |
| flatland::BufferCollectionHandle handle{std::move(remote_endpoint)}; |
| ASSERT_TRUE(handle.is_valid()); |
| |
| bcid = sysmem_util::GenerateUniqueBufferCollectionId(); |
| result = renderer->RegisterRenderTargetCollection(bcid, sysmem_allocator, std::move(handle)); |
| EXPECT_FALSE(result); |
| } |
| |
| // Test the Validate() function. First call Validate() without setting the client |
| // constraints, which should return std::nullopt, and then set the client constraints which |
| // should cause Validate() to return a valid BufferCollectionMetadata struct. |
| void ValidationTest(Renderer* renderer, fuchsia::sysmem::Allocator_Sync* sysmem_allocator) { |
| auto tokens = CreateSysmemTokens(sysmem_allocator); |
| |
| auto bcid = sysmem_util::GenerateUniqueBufferCollectionId(); |
| auto result = |
| renderer->RegisterRenderTargetCollection(bcid, sysmem_allocator, std::move(tokens.dup_token)); |
| EXPECT_TRUE(result); |
| |
| // The buffer collection should not be valid here. |
| EXPECT_FALSE(renderer->Validate(bcid).has_value()); |
| |
| SetClientConstraintsAndWaitForAllocated(sysmem_allocator, std::move(tokens.local_token)); |
| |
| // The buffer collection *should* be valid here. |
| auto validate_result = renderer->Validate(bcid); |
| EXPECT_TRUE(validate_result.has_value()); |
| |
| // There should be 1 vmo. |
| EXPECT_EQ(validate_result->vmo_count, 1U); |
| } |
| |
| // Simple deregistration test that calls ReleaseBufferCollection() directly without |
| // any zx::events just to make sure that the method's functionality itself is |
| // working as intented. |
| void DeregistrationTest(Renderer* renderer, fuchsia::sysmem::Allocator_Sync* sysmem_allocator) { |
| auto tokens = CreateSysmemTokens(sysmem_allocator); |
| |
| auto bcid = sysmem_util::GenerateUniqueBufferCollectionId(); |
| auto result = |
| renderer->RegisterRenderTargetCollection(bcid, sysmem_allocator, std::move(tokens.dup_token)); |
| EXPECT_TRUE(result); |
| |
| // The buffer collection should not be valid here. |
| EXPECT_FALSE(renderer->Validate(bcid).has_value()); |
| |
| SetClientConstraintsAndWaitForAllocated(sysmem_allocator, std::move(tokens.local_token)); |
| |
| // The buffer collection *should* be valid here. |
| auto validate_result = renderer->Validate(bcid); |
| EXPECT_TRUE(validate_result.has_value()); |
| |
| // There should be 1 vmo. |
| EXPECT_EQ(validate_result->vmo_count, 1U); |
| |
| // Now deregister the collection. |
| renderer->DeregisterRenderTargetCollection(bcid); |
| |
| // After deregistration, calling validate should return false. |
| EXPECT_FALSE(renderer->Validate(bcid)); |
| } |
| |
| // Test to make sure we can call the functions RegisterTextureCollection(), |
| // RegisterRenderTargetCollection() and Validate() simultaneously from |
| // multiple threads and have it work. |
| void MultithreadingTest(Renderer* renderer) { |
| const uint32_t kNumThreads = 50; |
| |
| std::set<sysmem_util::GlobalBufferCollectionId> bcid_set; |
| std::mutex lock; |
| |
| auto register_and_validate_function = [&renderer, &bcid_set, &lock]() { |
| // Make a test loop. |
| async::TestLoop loop; |
| |
| // Make an extra sysmem allocator for tokens. |
| fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator; |
| zx_status_t status = fdio_service_connect( |
| "/svc/fuchsia.sysmem.Allocator", sysmem_allocator.NewRequest().TakeChannel().release()); |
| |
| auto tokens = CreateSysmemTokens(sysmem_allocator.get()); |
| auto bcid = sysmem_util::GenerateUniqueBufferCollectionId(); |
| bool result = renderer->RegisterRenderTargetCollection(bcid, sysmem_allocator.get(), |
| std::move(tokens.local_token)); |
| |
| EXPECT_TRUE(result); |
| |
| SetClientConstraintsAndWaitForAllocated(sysmem_allocator.get(), std::move(tokens.dup_token)); |
| |
| // Add the bcid to the global vector in a thread-safe manner. |
| { |
| std::unique_lock<std::mutex> unique_lock(lock); |
| bcid_set.insert(bcid); |
| } |
| |
| // The buffer collection *should* be valid here. |
| auto validate_result = renderer->Validate(bcid); |
| EXPECT_TRUE(validate_result.has_value()); |
| |
| // There should be 1 vmo. |
| EXPECT_EQ(validate_result->vmo_count, 1U); |
| |
| loop.RunUntilIdle(); |
| }; |
| |
| // Run a bunch of threads, alternating between threads that register texture collections |
| // and threads that register render target collections. |
| std::vector<std::thread> threads; |
| for (uint32_t i = 0; i < kNumThreads; i++) { |
| threads.push_back(std::thread(register_and_validate_function)); |
| } |
| |
| for (auto&& thread : threads) { |
| thread.join(); |
| } |
| |
| // Validate the ids here one more time to make sure the renderer's internal |
| // state hasn't been corrupted. We use the values gathered in the bcid_vec |
| // to test with. |
| EXPECT_EQ(bcid_set.size(), kNumThreads); |
| for (const auto& bcid : bcid_set) { |
| // The buffer collection *should* be valid here. |
| auto result = renderer->Validate(bcid); |
| EXPECT_TRUE(result.has_value()); |
| |
| // There should be 1 vmo. |
| EXPECT_EQ(result->vmo_count, 1U); |
| } |
| } |
| |
| // This test checks to make sure that the Render() function properly signals |
| // a zx::event which can be used by an async::Wait object to asynchronously |
| // call a custom function. |
| void AsyncEventSignalTest(Renderer* renderer, fuchsia::sysmem::Allocator_Sync* sysmem_allocator, |
| bool use_vulkan) { |
| // First create a pairs of sysmem tokens for the render target. |
| auto target_tokens = CreateSysmemTokens(sysmem_allocator); |
| |
| // Register the render target with the renderer. |
| fuchsia::sysmem::BufferCollectionInfo_2 target_info = {}; |
| |
| auto target_id = sysmem_util::GenerateUniqueBufferCollectionId(); |
| |
| auto result = renderer->RegisterRenderTargetCollection(target_id, sysmem_allocator, |
| std::move(target_tokens.dup_token)); |
| EXPECT_TRUE(result); |
| |
| // Create a client-side handle to the buffer collection and set the client constraints. |
| auto client_target_collection = |
| CreateClientPointerWithConstraints(sysmem_allocator, std::move(target_tokens.local_token), |
| /*image_count*/ 1, |
| /*width*/ 60, |
| /*height*/ 40); |
| auto allocation_status = ZX_OK; |
| auto status = client_target_collection->WaitForBuffersAllocated(&allocation_status, &target_info); |
| EXPECT_EQ(status, ZX_OK); |
| EXPECT_EQ(allocation_status, ZX_OK); |
| |
| // Now that the renderer and client have set their contraints, we validate. |
| auto target_metadata = renderer->Validate(target_id); |
| EXPECT_TRUE(target_metadata.has_value()); |
| |
| // Create the render_target image meta_data. |
| ImageMetadata render_target = { |
| .collection_id = target_id, |
| .vmo_idx = 0, |
| .width = 16, |
| .height = 8, |
| }; |
| |
| // Create the release fence that will be passed along to the Render() |
| // function and be used to signal when we should deregister the collection. |
| zx::event release_fence; |
| status = zx::event::create(0, &release_fence); |
| EXPECT_EQ(status, ZX_OK); |
| |
| // Set up the async::Wait object to wait until the release_fence signals |
| // ZX_EVENT_SIGNALED. We make use of a test loop to access an async dispatcher. |
| async::TestLoop loop; |
| bool signaled = false; |
| auto dispatcher = loop.dispatcher(); |
| auto wait = std::make_unique<async::Wait>(release_fence.get(), ZX_EVENT_SIGNALED); |
| wait->set_handler([&signaled](async_dispatcher_t*, async::Wait*, zx_status_t /*status*/, |
| const zx_packet_signal_t* /*signal*/) mutable { signaled = true; }); |
| wait->Begin(dispatcher); |
| |
| // The call to Render() will signal the release fence, triggering the wait object to |
| // call its handler function. |
| std::vector<zx::event> fences; |
| fences.push_back(std::move(release_fence)); |
| renderer->Render(render_target, {}, {}, fences); |
| |
| if (use_vulkan) { |
| auto vk_renderer = static_cast<VkRenderer*>(renderer); |
| vk_renderer->WaitIdle(); |
| } |
| |
| // Close the test loop and test that our handler was called. |
| loop.RunUntilIdle(); |
| EXPECT_TRUE(signaled); |
| } |
| |
| TEST_F(NullRendererTest, RegisterCollectionTest) { |
| NullRenderer renderer; |
| RegisterCollectionTest(&renderer, sysmem_allocator_.get()); |
| } |
| |
| TEST_F(NullRendererTest, SameTokenTwiceTest) { |
| NullRenderer renderer; |
| SameTokenTwiceTest(&renderer, sysmem_allocator_.get()); |
| } |
| |
| TEST_F(NullRendererTest, BadTokenTest) { |
| NullRenderer renderer; |
| BadTokenTest(&renderer, sysmem_allocator_.get()); |
| } |
| |
| TEST_F(NullRendererTest, ValidationTest) { |
| NullRenderer renderer; |
| ValidationTest(&renderer, sysmem_allocator_.get()); |
| } |
| |
| TEST_F(NullRendererTest, DeregistrationTest) { |
| NullRenderer renderer; |
| DeregistrationTest(&renderer, sysmem_allocator_.get()); |
| } |
| |
| TEST_F(NullRendererTest, DISABLED_MultithreadingTest) { |
| NullRenderer renderer; |
| MultithreadingTest(&renderer); |
| } |
| |
| TEST_F(NullRendererTest, AsyncEventSignalTest) { |
| NullRenderer renderer; |
| AsyncEventSignalTest(&renderer, sysmem_allocator_.get(), /*use_vulkan*/ false); |
| } |
| |
| VK_TEST_F(VulkanRendererTest, RegisterCollectionTest) { |
| auto env = escher::test::EscherEnvironment::GetGlobalTestEnvironment(); |
| auto unique_escher = |
| std::make_unique<escher::Escher>(env->GetVulkanDevice(), env->GetFilesystem()); |
| VkRenderer renderer(std::move(unique_escher)); |
| RegisterCollectionTest(&renderer, sysmem_allocator_.get()); |
| } |
| |
| VK_TEST_F(VulkanRendererTest, SameTokenTwiceTest) { |
| auto env = escher::test::EscherEnvironment::GetGlobalTestEnvironment(); |
| auto unique_escher = |
| std::make_unique<escher::Escher>(env->GetVulkanDevice(), env->GetFilesystem()); |
| VkRenderer renderer(std::move(unique_escher)); |
| SameTokenTwiceTest(&renderer, sysmem_allocator_.get()); |
| } |
| |
| VK_TEST_F(VulkanRendererTest, BadTokenTest) { |
| auto env = escher::test::EscherEnvironment::GetGlobalTestEnvironment(); |
| auto unique_escher = |
| std::make_unique<escher::Escher>(env->GetVulkanDevice(), env->GetFilesystem()); |
| VkRenderer renderer(std::move(unique_escher)); |
| BadTokenTest(&renderer, sysmem_allocator_.get()); |
| } |
| |
| VK_TEST_F(VulkanRendererTest, ValidationTest) { |
| auto env = escher::test::EscherEnvironment::GetGlobalTestEnvironment(); |
| auto unique_escher = |
| std::make_unique<escher::Escher>(env->GetVulkanDevice(), env->GetFilesystem()); |
| VkRenderer renderer(std::move(unique_escher)); |
| ValidationTest(&renderer, sysmem_allocator_.get()); |
| } |
| |
| VK_TEST_F(VulkanRendererTest, DeregistrationTest) { |
| auto env = escher::test::EscherEnvironment::GetGlobalTestEnvironment(); |
| auto unique_escher = |
| std::make_unique<escher::Escher>(env->GetVulkanDevice(), env->GetFilesystem()); |
| VkRenderer renderer(std::move(unique_escher)); |
| DeregistrationTest(&renderer, sysmem_allocator_.get()); |
| } |
| |
| VK_TEST_F(VulkanRendererTest, DISABLED_MultithreadingTest) { |
| auto env = escher::test::EscherEnvironment::GetGlobalTestEnvironment(); |
| auto unique_escher = |
| std::make_unique<escher::Escher>(env->GetVulkanDevice(), env->GetFilesystem()); |
| VkRenderer renderer(std::move(unique_escher)); |
| MultithreadingTest(&renderer); |
| } |
| |
| VK_TEST_F(VulkanRendererTest, AsyncEventSignalTest) { |
| SKIP_TEST_IF_ESCHER_USES_DEVICE(VirtualGpu); |
| auto env = escher::test::EscherEnvironment::GetGlobalTestEnvironment(); |
| auto unique_escher = |
| std::make_unique<escher::Escher>(env->GetVulkanDevice(), env->GetFilesystem()); |
| VkRenderer renderer(std::move(unique_escher)); |
| AsyncEventSignalTest(&renderer, sysmem_allocator_.get(), /*use_vulkan*/ true); |
| } |
| |
| // This test actually renders a rectangle using the VKRenderer. We create a single rectangle, |
| // with a half-red, half-green texture, translate and scale it. The render target is 16x8 |
| // and the rectangle is 4x2. So in the end the result should look like this: |
| // |
| // ---------------- |
| // ---------------- |
| // ---------------- |
| // ------RRGG------ |
| // ------RRGG------ |
| // ---------------- |
| // ---------------- |
| // ---------------- |
| VK_TEST_F(VulkanRendererTest, RenderTest) { |
| SKIP_TEST_IF_ESCHER_USES_DEVICE(VirtualGpu); |
| auto env = escher::test::EscherEnvironment::GetGlobalTestEnvironment(); |
| auto unique_escher = |
| std::make_unique<escher::Escher>(env->GetVulkanDevice(), env->GetFilesystem()); |
| VkRenderer renderer(std::move(unique_escher)); |
| |
| // First create the pair of sysmem tokens, one for the client, one for the renderer. |
| auto tokens = flatland::CreateSysmemTokens(sysmem_allocator_.get()); |
| |
| auto target_tokens = flatland::CreateSysmemTokens(sysmem_allocator_.get()); |
| |
| // Register and validate the collection with the renderer. |
| auto collection_id = sysmem_util::GenerateUniqueBufferCollectionId(); |
| |
| auto result = renderer.ImportBufferCollection(collection_id, sysmem_allocator_.get(), |
| std::move(tokens.dup_token)); |
| EXPECT_TRUE(result); |
| |
| // Create a client-side handle to the buffer collection and set the client constraints. |
| auto [buffer_usage, memory_constraints] = GetUsageAndMemoryConstraintsForCpuWriteOften(); |
| auto client_collection = CreateClientPointerWithConstraints( |
| sysmem_allocator_.get(), std::move(tokens.local_token), |
| /*image_count*/ 1, |
| /*width*/ 60, |
| /*height*/ 40, buffer_usage, std::make_optional(memory_constraints)); |
| |
| auto target_id = sysmem_util::GenerateUniqueBufferCollectionId(); |
| |
| result = renderer.RegisterRenderTargetCollection(target_id, sysmem_allocator_.get(), |
| std::move(target_tokens.dup_token)); |
| EXPECT_TRUE(result); |
| |
| // Create a client-side handle to the buffer collection and set the client constraints. |
| auto client_target = CreateClientPointerWithConstraints( |
| sysmem_allocator_.get(), std::move(target_tokens.local_token), |
| /*image_count*/ 1, |
| /*width*/ 60, |
| /*height*/ 40, buffer_usage, std::make_optional(memory_constraints)); |
| |
| // 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 client wait for buffers allocated so it can populate its information |
| // struct with the vmo data. |
| fuchsia::sysmem::BufferCollectionInfo_2 client_target_info = {}; |
| { |
| zx_status_t allocation_status = ZX_OK; |
| auto status = client_target->WaitForBuffersAllocated(&allocation_status, &client_target_info); |
| EXPECT_EQ(status, ZX_OK); |
| EXPECT_EQ(allocation_status, ZX_OK); |
| } |
| |
| // Now that the renderer and client have set their contraints, we validate. |
| auto buffer_metadata = renderer.Validate(collection_id); |
| EXPECT_TRUE(buffer_metadata.has_value()); |
| |
| auto target_metadata = renderer.Validate(target_id); |
| EXPECT_TRUE(buffer_metadata.has_value()); |
| |
| const uint32_t kTargetWidth = 16; |
| const uint32_t kTargetHeight = 8; |
| |
| // Create the render_target image meta_data. |
| ImageMetadata render_target = { |
| .collection_id = target_id, |
| .vmo_idx = 0, |
| .width = kTargetWidth, |
| .height = kTargetHeight, |
| }; |
| |
| // Create the image meta data for the renderable. |
| ImageMetadata renderable_texture = { |
| .collection_id = collection_id, .vmo_idx = 0, .width = 2, .height = 1}; |
| |
| // Create a renderable where the upper-left hand corner should be at position (6,3) |
| // with a width/height of (4,2). |
| const uint32_t kRenderableWidth = 4; |
| const uint32_t kRenderableHeight = 2; |
| Rectangle2D renderable(glm::vec2(6, 3), glm::vec2(kRenderableWidth, kRenderableHeight)); |
| |
| // Have the client write pixel values to the renderable's texture. |
| uint8_t* vmo_host; |
| { |
| GetVmoHostPtr(&vmo_host, client_collection_info, renderable_texture.vmo_idx); |
| // The texture only has 2 pixels, so it needs 8 write values for 4 channels. We |
| // set the first pixel to red and the second pixel to green. |
| const uint8_t kNumWrites = 8; |
| const uint8_t kWriteValues[] = {/*red*/ 255U, 0, 0, 255U, /*green*/ 0, 255U, 0, 255U}; |
| memcpy(vmo_host, kWriteValues, sizeof(kWriteValues)); |
| |
| // 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)); |
| } |
| |
| // Render the renderable to the render target. |
| renderer.Render(render_target, {renderable}, {renderable_texture}); |
| renderer.WaitIdle(); |
| |
| // Get a raw pointer from the client collection's vmo that represents the render target |
| // and read its values. This should show that the renderable was rendered to the center |
| // of the render target, with its associated texture. |
| GetVmoHostPtr(&vmo_host, client_target_info, render_target.vmo_idx); |
| |
| // Flush the cache before reading back target image. |
| EXPECT_EQ(ZX_OK, zx_cache_flush(vmo_host, kTargetWidth * kTargetHeight * 4, |
| ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE)); |
| |
| // Make sure the pixels are in the right order give that we rotated |
| // the rectangle. |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 6, 3), glm::ivec4(255, 0, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 7, 3), glm::ivec4(255, 0, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 8, 3), glm::ivec4(0, 255, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 9, 3), glm::ivec4(0, 255, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 6, 4), glm::ivec4(255, 0, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 7, 4), glm::ivec4(255, 0, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 8, 4), glm::ivec4(0, 255, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 9, 4), glm::ivec4(0, 255, 0, 255)); |
| |
| // Make sure the remaining pixels are black. |
| uint32_t black_pixels = 0; |
| for (uint32_t y = 0; y < kTargetHeight; y++) { |
| for (uint32_t x = 0; x < kTargetWidth; x++) { |
| auto col = GetPixel(vmo_host, kTargetWidth, x, y); |
| if (col == glm::ivec4(0, 0, 0, 0)) |
| black_pixels++; |
| } |
| } |
| EXPECT_EQ(black_pixels, kTargetWidth * kTargetHeight - kRenderableWidth * kRenderableHeight); |
| } |
| |
| // Tests transparency. Render two overlapping rectangles, a red opaque one covered slightly by |
| // a green transparent one with an alpha of 0.5. The result should look like this: |
| // |
| // ---------------- |
| // ---------------- |
| // ---------------- |
| // ------RYYYG---- |
| // ------RYYYG---- |
| // ---------------- |
| // ---------------- |
| // ---------------- |
| // TODO(fxbug.dev/52632): Transparency is currently hardcoded in the renderer to be on. This test |
| // will break if that is changed to be hardcoded to false before we expose it in the API. |
| VK_TEST_F(VulkanRendererTest, TransparencyTest) { |
| SKIP_TEST_IF_ESCHER_USES_DEVICE(VirtualGpu); |
| auto env = escher::test::EscherEnvironment::GetGlobalTestEnvironment(); |
| auto unique_escher = |
| std::make_unique<escher::Escher>(env->GetVulkanDevice(), env->GetFilesystem()); |
| VkRenderer renderer(std::move(unique_escher)); |
| |
| // First create the pair of sysmem tokens, one for the client, one for the renderer. |
| auto tokens = flatland::CreateSysmemTokens(sysmem_allocator_.get()); |
| |
| auto target_tokens = flatland::CreateSysmemTokens(sysmem_allocator_.get()); |
| |
| // Register and validate the collection with the renderer. |
| auto collection_id = sysmem_util::GenerateUniqueBufferCollectionId(); |
| |
| auto result = renderer.ImportBufferCollection(collection_id, sysmem_allocator_.get(), |
| std::move(tokens.dup_token)); |
| EXPECT_TRUE(result); |
| |
| // Create a client-side handle to the buffer collection and set the client constraints. |
| auto [buffer_usage, memory_constraints] = GetUsageAndMemoryConstraintsForCpuWriteOften(); |
| auto client_collection = CreateClientPointerWithConstraints( |
| sysmem_allocator_.get(), std::move(tokens.local_token), |
| /*image_count*/ 2, |
| /*width*/ 60, |
| /*height*/ 40, buffer_usage, std::make_optional(memory_constraints)); |
| |
| auto target_id = sysmem_util::GenerateUniqueBufferCollectionId(); |
| result = renderer.RegisterRenderTargetCollection(target_id, sysmem_allocator_.get(), |
| std::move(target_tokens.dup_token)); |
| EXPECT_TRUE(result); |
| |
| // Create a client-side handle to the buffer collection and set the client constraints. |
| auto client_target = CreateClientPointerWithConstraints( |
| sysmem_allocator_.get(), std::move(target_tokens.local_token), |
| /*image_count*/ 1, |
| /*width*/ 60, |
| /*height*/ 40, buffer_usage, std::make_optional(memory_constraints)); |
| |
| // 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); |
| } |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 client_target_info = {}; |
| { |
| zx_status_t allocation_status = ZX_OK; |
| auto status = client_target->WaitForBuffersAllocated(&allocation_status, &client_target_info); |
| EXPECT_EQ(status, ZX_OK); |
| EXPECT_EQ(allocation_status, ZX_OK); |
| } |
| |
| // Now that the renderer and client have set their contraints, we validate. |
| auto buffer_metadata = renderer.Validate(collection_id); |
| EXPECT_TRUE(buffer_metadata.has_value()); |
| |
| auto target_metadata = renderer.Validate(target_id); |
| EXPECT_TRUE(buffer_metadata.has_value()); |
| |
| const uint32_t kTargetWidth = 16; |
| const uint32_t kTargetHeight = 8; |
| |
| // Create the render_target image meta_data. |
| ImageMetadata render_target = { |
| .collection_id = target_id, |
| .vmo_idx = 0, |
| .width = kTargetWidth, |
| .height = kTargetHeight, |
| }; |
| |
| // Create the image meta data for the renderable. |
| ImageMetadata renderable_texture = { |
| .collection_id = collection_id, .vmo_idx = 0, .width = 1, .height = 1}; |
| |
| // Create the texture that will go on the transparent renderable. |
| ImageMetadata transparent_texture = { |
| .collection_id = collection_id, .vmo_idx = 1, .width = 1, .height = 1}; |
| |
| // Create the two renderables. |
| const uint32_t kRenderableWidth = 4; |
| const uint32_t kRenderableHeight = 2; |
| Rectangle2D renderable(glm::vec2(6, 3), glm::vec2(kRenderableWidth, kRenderableHeight)); |
| Rectangle2D transparent_renderable(glm::vec2(7, 3), |
| glm::vec2(kRenderableWidth, kRenderableHeight)); |
| |
| // Have the client write pixel values to the renderable's texture. |
| uint8_t* vmo_host; |
| { |
| GetVmoHostPtr(&vmo_host, client_collection_info, renderable_texture.vmo_idx); |
| // Create a red opaque pixel. |
| const uint8_t kNumWrites = 4; |
| const uint8_t kWriteValues[] = {/*red*/ 255U, 0, 0, 255U}; |
| memcpy(vmo_host, kWriteValues, sizeof(kWriteValues)); |
| |
| // 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)); |
| } |
| |
| { |
| GetVmoHostPtr(&vmo_host, client_collection_info, transparent_texture.vmo_idx); |
| // Create a green pixel with an alpha of 0.5. |
| const uint8_t kNumWrites = 4; |
| const uint8_t kWriteValues[] = {/*red*/ 0, 255, 0, 128U}; |
| memcpy(vmo_host, kWriteValues, sizeof(kWriteValues)); |
| |
| // 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)); |
| } |
| |
| // Render the renderable to the render target. |
| renderer.Render(render_target, {renderable, transparent_renderable}, |
| {renderable_texture, transparent_texture}); |
| renderer.WaitIdle(); |
| |
| // Get a raw pointer from the client collection's vmo that represents the render target |
| // and read its values. This should show that the renderable was rendered to the center |
| // of the render target, with its associated texture. |
| GetVmoHostPtr(&vmo_host, client_target_info, render_target.vmo_idx); |
| |
| // Flush the cache before reading back target image. |
| EXPECT_EQ(ZX_OK, zx_cache_flush(vmo_host, kTargetWidth * kTargetHeight * 4, |
| ZX_CACHE_FLUSH_DATA | ZX_CACHE_FLUSH_INVALIDATE)); |
| |
| // Make sure the pixels are in the right order give that we rotated |
| // the rectangle. |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 6, 3), glm::ivec4(255, 0, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 6, 4), glm::ivec4(255, 0, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 7, 3), glm::ivec4(127, 255, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 7, 4), glm::ivec4(127, 255, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 8, 3), glm::ivec4(127, 255, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 8, 4), glm::ivec4(127, 255, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 9, 3), glm::ivec4(127, 255, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 9, 4), glm::ivec4(127, 255, 0, 255)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 10, 3), glm::ivec4(0, 255, 0, 128)); |
| EXPECT_EQ(GetPixel(vmo_host, kTargetWidth, 10, 4), glm::ivec4(0, 255, 0, 128)); |
| |
| // Make sure the remaining pixels are black. |
| uint32_t black_pixels = 0; |
| for (uint32_t y = 0; y < kTargetHeight; y++) { |
| for (uint32_t x = 0; x < kTargetWidth; x++) { |
| auto col = GetPixel(vmo_host, kTargetWidth, x, y); |
| if (col == glm::ivec4(0, 0, 0, 0)) |
| black_pixels++; |
| } |
| } |
| EXPECT_EQ(black_pixels, kTargetWidth * kTargetHeight - 10U); |
| } |
| |
| } // namespace flatland |