| // Copyright 2021 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 <fuchsia/ui/composition/cpp/fidl.h> |
| #include <lib/sys/component/cpp/testing/realm_builder.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/ui/scenic/cpp/view_creation_tokens.h> |
| #include <lib/ui/scenic/cpp/view_identity.h> |
| #include <sys/types.h> |
| #include <zircon/status.h> |
| |
| #include <cstdint> |
| #include <iostream> |
| #include <utility> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/lib/testing/loop_fixture/real_loop_fixture.h" |
| #include "src/ui/lib/escher/geometry/types.h" |
| #include "src/ui/scenic/lib/allocation/allocator.h" |
| #include "src/ui/scenic/lib/allocation/buffer_collection_import_export_tokens.h" |
| #include "src/ui/scenic/lib/allocation/mock_buffer_collection_importer.h" |
| #include "src/ui/scenic/lib/flatland/buffers/util.h" |
| #include "src/ui/scenic/lib/screen_capture/screen_capture.h" |
| #include "src/ui/scenic/lib/utils/helpers.h" |
| #include "src/ui/scenic/tests/utils/scenic_realm_builder.h" |
| #include "src/ui/scenic/tests/utils/utils.h" |
| #include "zircon/system/ulib/fbl/include/fbl/algorithm.h" |
| |
| namespace integration_tests { |
| |
| using flatland::MapHostPointer; |
| using fuchsia::math::SizeU; |
| using fuchsia::math::Vec; |
| using fuchsia::ui::composition::ChildViewWatcher; |
| using fuchsia::ui::composition::ContentId; |
| using fuchsia::ui::composition::Flatland; |
| using fuchsia::ui::composition::FlatlandDisplay; |
| using fuchsia::ui::composition::FrameInfo; |
| using fuchsia::ui::composition::GetNextFrameArgs; |
| using fuchsia::ui::composition::ParentViewportWatcher; |
| using fuchsia::ui::composition::RegisterBufferCollectionArgs; |
| using fuchsia::ui::composition::RegisterBufferCollectionUsage; |
| using fuchsia::ui::composition::ScreenCapture; |
| using fuchsia::ui::composition::ScreenCaptureConfig; |
| using fuchsia::ui::composition::ScreenCaptureError; |
| using fuchsia::ui::composition::TransformId; |
| using fuchsia::ui::composition::ViewportProperties; |
| using fuchsia::ui::views::ViewRef; |
| using RealmRoot = component_testing::RealmRoot; |
| |
| class ScreenCaptureIntegrationTest : public gtest::RealLoopFixture { |
| public: |
| ScreenCaptureIntegrationTest() |
| : realm_(ScenicRealmBuilder() |
| .AddRealmProtocol(fuchsia::ui::composition::Flatland::Name_) |
| .AddRealmProtocol(fuchsia::ui::composition::FlatlandDisplay::Name_) |
| .AddRealmProtocol(fuchsia::ui::composition::Allocator::Name_) |
| .AddRealmProtocol(fuchsia::ui::composition::ScreenCapture::Name_) |
| .Build()) { |
| auto context = sys::ComponentContext::Create(); |
| context->svc()->Connect(sysmem_allocator_.NewRequest()); |
| |
| flatland_display_ = realm_.Connect<fuchsia::ui::composition::FlatlandDisplay>(); |
| flatland_display_.set_error_handler([](zx_status_t status) { |
| FAIL() << "Lost connection to Scenic: " << zx_status_get_string(status); |
| }); |
| |
| flatland_allocator_ = realm_.ConnectSync<fuchsia::ui::composition::Allocator>(); |
| |
| // Set up root view. |
| root_session_ = realm_.Connect<fuchsia::ui::composition::Flatland>(); |
| root_session_.set_error_handler([](zx_status_t status) { |
| FAIL() << "Lost connection to Scenic: " << zx_status_get_string(status); |
| }); |
| |
| fidl::InterfacePtr<ChildViewWatcher> child_view_watcher; |
| fidl::InterfacePtr<ParentViewportWatcher> parent_viewport_watcher; |
| { |
| auto [child_token, parent_token] = scenic::ViewCreationTokenPair::New(); |
| flatland_display_->SetContent(std::move(parent_token), child_view_watcher.NewRequest()); |
| |
| auto identity = scenic::NewViewIdentityOnCreation(); |
| root_view_ref_ = fidl::Clone(identity.view_ref); |
| root_session_->CreateView2(std::move(child_token), std::move(identity), {}, |
| parent_viewport_watcher.NewRequest()); |
| parent_viewport_watcher->GetLayout([this](auto layout_info) { |
| ASSERT_TRUE(layout_info.has_logical_size()); |
| const auto [width, height] = layout_info.logical_size(); |
| display_width_ = width; |
| display_height_ = height; |
| num_pixels_ = display_width_ * display_height_; |
| }); |
| } |
| BlockingPresent(root_session_); |
| |
| // Wait until we get the display size. |
| RunLoopUntil([this] { return display_width_ != 0 && display_height_ != 0; }); |
| |
| // Set up the root graph. |
| fidl::InterfacePtr<ChildViewWatcher> child_view_watcher2; |
| auto [child_token, parent_token] = scenic::ViewCreationTokenPair::New(); |
| ViewportProperties properties; |
| properties.set_logical_size({display_width_, display_height_}); |
| const TransformId kRootTransform{.value = 1}; |
| const ContentId kRootContent{.value = 1}; |
| root_session_->CreateTransform(kRootTransform); |
| root_session_->CreateViewport(kRootContent, std::move(parent_token), std::move(properties), |
| child_view_watcher2.NewRequest()); |
| root_session_->SetRootTransform(kRootTransform); |
| root_session_->SetContent(kRootTransform, kRootContent); |
| BlockingPresent(root_session_); |
| |
| // Set up the child view. |
| child_session_ = realm_.Connect<fuchsia::ui::composition::Flatland>(); |
| fidl::InterfacePtr<ParentViewportWatcher> parent_viewport_watcher2; |
| auto identity = scenic::NewViewIdentityOnCreation(); |
| auto child_view_ref = fidl::Clone(identity.view_ref); |
| fuchsia::ui::composition::ViewBoundProtocols protocols; |
| child_session_->CreateView2(std::move(child_token), std::move(identity), std::move(protocols), |
| parent_viewport_watcher2.NewRequest()); |
| child_session_->CreateTransform(kChildRootTransform); |
| child_session_->SetRootTransform(kChildRootTransform); |
| BlockingPresent(child_session_); |
| |
| // Create ScreenCapture client. |
| screen_capture_ = realm_.Connect<fuchsia::ui::composition::ScreenCapture>(); |
| screen_capture_.set_error_handler( |
| [](zx_status_t status) { FAIL() << "Lost connection to ScreenCapture"; }); |
| } |
| |
| static flatland::SysmemTokens CreateSysmemTokens( |
| fuchsia::sysmem::Allocator_Sync* sysmem_allocator) { |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr local_token; |
| zx_status_t status = sysmem_allocator->AllocateSharedCollection(local_token.NewRequest()); |
| EXPECT_EQ(status, ZX_OK); |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr dup_token; |
| status = local_token->Duplicate(std::numeric_limits<uint32_t>::max(), dup_token.NewRequest()); |
| EXPECT_EQ(status, ZX_OK); |
| status = local_token->Sync(); |
| EXPECT_EQ(status, ZX_OK); |
| |
| return {std::move(local_token), std::move(dup_token)}; |
| } |
| |
| void BlockingPresent(fuchsia::ui::composition::FlatlandPtr& flatland) { |
| bool presented = false; |
| flatland.events().OnFramePresented = [&presented](auto) { presented = true; }; |
| flatland->Present({}); |
| RunLoopUntil([&presented] { return presented; }); |
| flatland.events().OnFramePresented = nullptr; |
| } |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 CreateBufferCollectionInfoWithConstraints( |
| fuchsia::sysmem::BufferCollectionConstraints constraints, |
| allocation::BufferCollectionExportToken export_token, |
| RegisterBufferCollectionUsage usage) const { |
| // Create Buffer Collection for image to add to scene graph. |
| RegisterBufferCollectionArgs args = {}; |
| |
| auto [local_token, dup_token] = CreateSysmemTokens(sysmem_allocator_.get()); |
| |
| args.set_export_token(std::move(export_token)); |
| args.set_buffer_collection_token(std::move(dup_token)); |
| args.set_usage(usage); |
| |
| fuchsia::sysmem::BufferCollectionSyncPtr buffer_collection; |
| zx_status_t status = sysmem_allocator_->BindSharedCollection(std::move(local_token), |
| buffer_collection.NewRequest()); |
| EXPECT_EQ(status, ZX_OK); |
| |
| buffer_collection->SetConstraints(true, constraints); |
| |
| fuchsia::ui::composition::Allocator_RegisterBufferCollection_Result result; |
| |
| flatland_allocator_->RegisterBufferCollection(std::move(args), &result); |
| EXPECT_FALSE(result.is_err()); |
| |
| zx_status_t allocation_status; |
| fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info{}; |
| EXPECT_EQ(ZX_OK, buffer_collection->WaitForBuffersAllocated(&allocation_status, |
| &buffer_collection_info)); |
| EXPECT_EQ(ZX_OK, allocation_status); |
| EXPECT_EQ(ZX_OK, buffer_collection->Close()); |
| |
| return buffer_collection_info; |
| } |
| |
| // This function calls GetNextFrame(). |
| fpromise::result<FrameInfo, ScreenCaptureError> CaptureScreen( |
| fuchsia::ui::composition::ScreenCapturePtr& screencapturer) { |
| zx::event event; |
| zx::event dup; |
| zx_status_t status = zx::event::create(0, &event); |
| EXPECT_EQ(status, ZX_OK); |
| event.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup); |
| |
| GetNextFrameArgs gnf_args; |
| gnf_args.set_event(std::move(dup)); |
| |
| fpromise::result<FrameInfo, ScreenCaptureError> response; |
| bool alloc_result = false; |
| screencapturer->GetNextFrame( |
| std::move(gnf_args), |
| [&response, &alloc_result](fpromise::result<FrameInfo, ScreenCaptureError> result) { |
| response = std::move(result); |
| alloc_result = true; |
| }); |
| |
| RunLoopUntil([&alloc_result] { return alloc_result; }); |
| |
| if (response.is_ok()) { |
| zx::duration kEventDelay = zx::msec(5000); |
| status = event.wait_one(ZX_EVENT_SIGNALED, zx::deadline_after(kEventDelay), nullptr); |
| EXPECT_EQ(status, ZX_OK); |
| } |
| |
| return response; |
| } |
| |
| const TransformId kChildRootTransform{.value = 1}; |
| static constexpr uint32_t kBytesPerPixel = 4; |
| static constexpr zx::duration kEventDelay = zx::msec(5000); |
| |
| static constexpr uint32_t red = (255U << 24) | (255U); |
| static constexpr uint32_t green = (255U << 16) | (255U); |
| static constexpr uint32_t blue = (255U << 8) | (255U); |
| static constexpr uint32_t yellow = green | blue; |
| |
| RealmRoot realm_; |
| |
| fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator_; |
| fuchsia::ui::composition::AllocatorSyncPtr flatland_allocator_; |
| fuchsia::ui::composition::FlatlandDisplayPtr flatland_display_; |
| fuchsia::ui::composition::FlatlandPtr root_session_; |
| fuchsia::ui::composition::FlatlandPtr child_session_; |
| fuchsia::ui::composition::ScreenCapturePtr screen_capture_; |
| fuchsia::ui::views::ViewRef root_view_ref_; |
| |
| uint32_t display_width_ = 0; |
| uint32_t display_height_ = 0; |
| uint32_t num_pixels_ = 0; |
| }; |
| |
| struct SysmemTokens { |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr local_token; |
| fuchsia::sysmem::BufferCollectionTokenSyncPtr dup_token; |
| }; |
| |
| void GenerateImageForFlatlandInstance(uint32_t buffer_collection_index, |
| fuchsia::ui::composition::FlatlandPtr& flatland, |
| TransformId parent_transform, |
| allocation::BufferCollectionImportToken import_token, |
| SizeU size, Vec translation, uint32_t image_id, |
| uint32_t transform_id) { |
| // Create the image in the Flatland instance. |
| fuchsia::ui::composition::ImageProperties image_properties = {}; |
| image_properties.set_size(size); |
| fuchsia::ui::composition::ContentId content_id = {.value = image_id}; |
| flatland->CreateImage(content_id, std::move(import_token), buffer_collection_index, |
| std::move(image_properties)); |
| |
| // Add the created image as a child of the parent transform specified. Apply the right size and |
| // orientation commands. |
| const TransformId kTransform{.value = transform_id}; |
| flatland->CreateTransform(kTransform); |
| |
| flatland->SetContent(kTransform, content_id); |
| flatland->SetImageDestinationSize(content_id, {size.width, size.height}); |
| flatland->SetTranslation(kTransform, translation); |
| |
| flatland->AddChild(parent_transform, kTransform); |
| } |
| |
| inline uint32_t GetPixelsPerRow(const fuchsia::sysmem::SingleBufferSettings& settings, |
| uint32_t bytes_per_pixel, uint32_t image_width) { |
| uint32_t bytes_per_row_divisor = settings.image_format_constraints.bytes_per_row_divisor; |
| uint32_t min_bytes_per_row = settings.image_format_constraints.min_bytes_per_row; |
| uint32_t bytes_per_row = fbl::round_up(std::max(image_width * bytes_per_pixel, min_bytes_per_row), |
| bytes_per_row_divisor); |
| uint32_t pixels_per_row = bytes_per_row / bytes_per_pixel; |
| |
| return pixels_per_row; |
| } |
| |
| // This method writes to a sysmem buffer, taking into account any potential stride width |
| // differences. The method also flushes the cache if the buffer is in RAM domain. |
| void WriteToSysmemBuffer(const std::vector<uint32_t>& write_values, |
| fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info, |
| uint32_t buffer_collection_idx, uint32_t kBytesPerPixel, |
| uint32_t image_width, uint32_t image_height) { |
| uint32_t pixels_per_row = |
| GetPixelsPerRow(buffer_collection_info.settings, kBytesPerPixel, image_width); |
| |
| MapHostPointer(buffer_collection_info, buffer_collection_idx, |
| [&write_values, pixels_per_row, kBytesPerPixel, image_width, image_height]( |
| uint8_t* vmo_host, uint32_t num_bytes) { |
| uint32_t bytes_per_row = pixels_per_row * kBytesPerPixel; |
| uint32_t valid_bytes_per_row = image_width * kBytesPerPixel; |
| |
| EXPECT_GE(bytes_per_row, valid_bytes_per_row); |
| EXPECT_GE(num_bytes, bytes_per_row * image_height); |
| |
| if (bytes_per_row == valid_bytes_per_row) { |
| // Fast path. |
| memcpy(vmo_host, write_values.data(), sizeof(uint32_t) * write_values.size()); |
| } else { |
| // Copy over row-by-row. |
| for (size_t i = 0; i < image_height; ++i) { |
| memcpy(vmo_host + (i * bytes_per_row), &write_values[i * image_width], |
| valid_bytes_per_row); |
| } |
| } |
| }); |
| |
| // Flush the cache if we are operating in RAM. |
| if (buffer_collection_info.settings.buffer_settings.coherency_domain == |
| fuchsia::sysmem::CoherencyDomain::RAM) { |
| EXPECT_EQ(ZX_OK, buffer_collection_info.buffers[buffer_collection_idx].vmo.op_range( |
| ZX_VMO_OP_CACHE_CLEAN, 0, |
| buffer_collection_info.settings.buffer_settings.size_bytes, nullptr, 0)); |
| } |
| } |
| |
| // This function returns a linear buffer of pixels of size width * height. |
| std::vector<uint32_t> ExtractScreenCapture( |
| const fpromise::result<FrameInfo, ScreenCaptureError>& frame_result, |
| fuchsia::sysmem::BufferCollectionInfo_2& buffer_collection_info, uint32_t kBytesPerPixel, |
| uint32_t render_target_width, uint32_t render_target_height) { |
| EXPECT_FALSE(frame_result.is_error()); |
| uint32_t buffer_id = frame_result.value().buffer_id(); |
| // Copy ScreenCapture output for inspection. Note that the stride of the buffer may be different |
| // than the width of the image, if the width of the image is not a multiple of 64. |
| // |
| // For instance, is the original image were 1024x600, the new width is 600. 600*4=2400 bytes, |
| // which is not a multiple of 64. The next multiple would be 2432, which would mean the buffer |
| // is actually a 608x1024 "pixel" buffer, since 2432/4=608. We must account for that 8 byte |
| // padding when copying the bytes over to be inspected. |
| EXPECT_EQ(ZX_OK, buffer_collection_info.buffers[buffer_id].vmo.op_range( |
| ZX_CACHE_FLUSH_DATA | ZX_VMO_OP_CACHE_INVALIDATE, 0, |
| buffer_collection_info.settings.buffer_settings.size_bytes, nullptr, 0)); |
| |
| uint32_t pixels_per_row = |
| GetPixelsPerRow(buffer_collection_info.settings, kBytesPerPixel, render_target_width); |
| std::vector<uint32_t> read_values; |
| read_values.resize(static_cast<size_t>(render_target_width) * render_target_height); |
| |
| MapHostPointer(buffer_collection_info, buffer_id, |
| [&read_values, kBytesPerPixel, pixels_per_row, render_target_width, |
| render_target_height](uint8_t* vmo_host, uint32_t num_bytes) { |
| uint32_t bytes_per_row = pixels_per_row * kBytesPerPixel; |
| uint32_t valid_bytes_per_row = render_target_width * kBytesPerPixel; |
| |
| EXPECT_GE(bytes_per_row, valid_bytes_per_row); |
| |
| if (bytes_per_row == valid_bytes_per_row) { |
| // Fast path. |
| memcpy(read_values.data(), vmo_host, |
| static_cast<size_t>(bytes_per_row) * render_target_height); |
| } else { |
| for (size_t i = 0; i < render_target_height; ++i) { |
| memcpy(&read_values[i * render_target_width], vmo_host + (i * bytes_per_row), |
| valid_bytes_per_row); |
| } |
| } |
| }); |
| |
| return read_values; |
| } |
| |
| TEST_F(ScreenCaptureIntegrationTest, SingleColorUnrotatedScreenshot) { |
| const uint32_t image_width = display_width_; |
| const uint32_t image_height = display_height_; |
| const uint32_t render_target_width = display_width_; |
| const uint32_t render_target_height = display_height_; |
| |
| // Create Buffer Collection for image to add to scene graph. |
| allocation::BufferCollectionImportExportTokens ref_pair = |
| allocation::BufferCollectionImportExportTokens::New(); |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info = |
| CreateBufferCollectionInfoWithConstraints( |
| utils::CreateDefaultConstraints(/*buffer_count=*/1, image_width, image_height), |
| std::move(ref_pair.export_token), RegisterBufferCollectionUsage::DEFAULT); |
| |
| std::vector<uint32_t> write_values; |
| write_values.assign(num_pixels_, green); |
| |
| WriteToSysmemBuffer(write_values, buffer_collection_info, 0, kBytesPerPixel, image_width, |
| image_height); |
| |
| GenerateImageForFlatlandInstance(0, child_session_, kChildRootTransform, |
| std::move(ref_pair.import_token), {image_width, image_height}, |
| {0, 0}, 2, 2); |
| BlockingPresent(child_session_); |
| |
| // The scene graph is now ready for screencapturing! |
| |
| // Create buffer collection to render into for GetNextFrame(). |
| allocation::BufferCollectionImportExportTokens scr_ref_pair = |
| allocation::BufferCollectionImportExportTokens::New(); |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 sc_buffer_collection_info = |
| CreateBufferCollectionInfoWithConstraints( |
| utils::CreateDefaultConstraints(/*buffer_count=*/1, render_target_width, |
| render_target_height), |
| std::move(scr_ref_pair.export_token), RegisterBufferCollectionUsage::SCREENSHOT); |
| |
| // Configure buffers in ScreenCapture client. |
| ScreenCaptureConfig sc_args; |
| sc_args.set_import_token(std::move(scr_ref_pair.import_token)); |
| sc_args.set_buffer_count(sc_buffer_collection_info.buffer_count); |
| sc_args.set_size({render_target_width, render_target_height}); |
| |
| bool alloc_result = false; |
| screen_capture_->Configure(std::move(sc_args), |
| [&alloc_result](fpromise::result<void, ScreenCaptureError> result) { |
| EXPECT_FALSE(result.is_error()); |
| alloc_result = true; |
| }); |
| |
| RunLoopUntil([&alloc_result] { return alloc_result; }); |
| |
| // Take Screenshot! |
| const auto& cs_result = CaptureScreen(screen_capture_); |
| const auto& read_values = |
| ExtractScreenCapture(cs_result, sc_buffer_collection_info, kBytesPerPixel, |
| render_target_width, render_target_height); |
| |
| EXPECT_EQ(read_values.size(), write_values.size()); |
| |
| // Compare read and write values. |
| uint32_t num_green = 0; |
| |
| for (unsigned int read_value : read_values) { |
| if (read_value == green) |
| num_green++; |
| } |
| |
| EXPECT_EQ(num_green, num_pixels_); |
| } |
| |
| // Creates this image: |
| // RRRRRRRR |
| // RRRRRRRR |
| // GGGGGGGG |
| // GGGGGGGG |
| // |
| // Rotates into this image: |
| // GGGGGGGG |
| // GGGGGGGG |
| // RRRRRRRR |
| // RRRRRRRR |
| TEST_F(ScreenCaptureIntegrationTest, MultiColor180DegreeRotationScreenshot) { |
| const uint32_t image_width = display_width_; |
| const uint32_t image_height = display_height_; |
| const uint32_t render_target_width = display_width_; |
| const uint32_t render_target_height = display_height_; |
| |
| // Create Buffer Collection for image#1 to add to scene graph. |
| allocation::BufferCollectionImportExportTokens ref_pair = |
| allocation::BufferCollectionImportExportTokens::New(); |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info = |
| CreateBufferCollectionInfoWithConstraints( |
| utils::CreateDefaultConstraints(/*buffer_count=*/1, display_width_, display_height_), |
| std::move(ref_pair.export_token), RegisterBufferCollectionUsage::DEFAULT); |
| |
| // Write the image with half green, half red |
| std::vector<uint32_t> write_values; |
| const uint32_t pixel_color_count = num_pixels_ / 2; |
| |
| for (uint32_t i = 0; i < pixel_color_count; ++i) { |
| write_values.push_back(red); |
| } |
| for (uint32_t i = 0; i < pixel_color_count; ++i) { |
| write_values.push_back(green); |
| } |
| WriteToSysmemBuffer(write_values, buffer_collection_info, 0, kBytesPerPixel, image_width, |
| image_height); |
| |
| GenerateImageForFlatlandInstance(0, child_session_, kChildRootTransform, |
| std::move(ref_pair.import_token), {image_width, image_height}, |
| {0, 0}, 2, 2); |
| |
| BlockingPresent(child_session_); |
| |
| // The scene graph is now ready for screenshotting! |
| |
| // Create buffer collection to render into for GetNextFrame(). |
| allocation::BufferCollectionImportExportTokens scr_ref_pair = |
| allocation::BufferCollectionImportExportTokens::New(); |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 sc_buffer_collection_info = |
| CreateBufferCollectionInfoWithConstraints( |
| utils::CreateDefaultConstraints(/*buffer_count=*/1, render_target_width, |
| render_target_height), |
| std::move(scr_ref_pair.export_token), RegisterBufferCollectionUsage::SCREENSHOT); |
| |
| // Configure buffers in ScreenCapture client. |
| ScreenCaptureConfig sc_args; |
| sc_args.set_import_token(std::move(scr_ref_pair.import_token)); |
| sc_args.set_buffer_count(sc_buffer_collection_info.buffer_count); |
| sc_args.set_size({render_target_width, render_target_height}); |
| sc_args.set_rotation(fuchsia::ui::composition::Rotation::CW_180_DEGREES); |
| |
| bool alloc_result = false; |
| screen_capture_->Configure(std::move(sc_args), |
| [&alloc_result](fpromise::result<void, ScreenCaptureError> result) { |
| EXPECT_FALSE(result.is_error()); |
| alloc_result = true; |
| }); |
| |
| RunLoopUntil([&alloc_result] { return alloc_result; }); |
| |
| // Take Screenshot! |
| const auto& cs_result = CaptureScreen(screen_capture_); |
| const auto& read_values = |
| ExtractScreenCapture(cs_result, sc_buffer_collection_info, kBytesPerPixel, |
| render_target_width, render_target_height); |
| |
| EXPECT_EQ(read_values.size(), write_values.size()); |
| |
| // Compare read and write values. |
| uint32_t num_green = 0; |
| uint32_t num_red = 0; |
| |
| for (size_t i = 0; i < read_values.size(); i++) { |
| if (read_values[i] == green) { |
| num_green++; |
| EXPECT_EQ(write_values[i], red); |
| } else if (read_values[i] == red) { |
| num_red++; |
| EXPECT_EQ(write_values[i], green); |
| } |
| } |
| |
| EXPECT_EQ(num_green, pixel_color_count); |
| EXPECT_EQ(num_red, pixel_color_count); |
| } |
| |
| // Creates this image: |
| // RRRRRGGGGG |
| // RRRRRGGGGG |
| // YYYYYBBBBB |
| // YYYYYBBBBB |
| // |
| // Rotates into this image: |
| // YYRR |
| // YYRR |
| // YYRR |
| // YYRR |
| // YYRR |
| // BBGG |
| // BBGG |
| // BBGG |
| // BBGG |
| // BBGG |
| TEST_F(ScreenCaptureIntegrationTest, MultiColor90DegreeRotationScreenshot) { |
| const uint32_t image_width = display_width_; |
| const uint32_t image_height = display_height_; |
| const uint32_t render_target_width = display_height_; |
| const uint32_t render_target_height = display_width_; |
| |
| // Create Buffer Collection for image#1 to add to scene graph. |
| allocation::BufferCollectionImportExportTokens ref_pair = |
| allocation::BufferCollectionImportExportTokens::New(); |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info = |
| CreateBufferCollectionInfoWithConstraints( |
| utils::CreateDefaultConstraints(/*buffer_count=*/1, image_width, image_height), |
| std::move(ref_pair.export_token), RegisterBufferCollectionUsage::DEFAULT); |
| |
| // Write the image with the color scheme displayed in ASCII above. |
| std::vector<uint32_t> write_values; |
| |
| uint32_t red_pixel_count = 0; |
| uint32_t green_pixel_count = 0; |
| uint32_t blue_pixel_count = 0; |
| uint32_t yellow_pixel_count = 0; |
| const uint32_t pixel_color_count = num_pixels_ / 4; |
| |
| for (uint32_t i = 0; i < num_pixels_; ++i) { |
| uint32_t row = i / image_width; |
| uint32_t col = i % image_width; |
| |
| // Top-left quadrant |
| if (row < image_height / 2 && col < image_width / 2) { |
| write_values.push_back(red); |
| ++red_pixel_count; |
| } |
| // Top-right quadrant |
| else if (row < image_height / 2 && col >= image_width / 2) { |
| write_values.push_back(green); |
| ++green_pixel_count; |
| } |
| // Bottom-right quadrant |
| else if (row >= image_height / 2 && col >= image_width / 2) { |
| write_values.push_back(blue); |
| ++blue_pixel_count; |
| } |
| // Bottom-left quadrant |
| else if (row >= image_height / 2 && col < image_width / 2) { |
| write_values.push_back(yellow); |
| ++yellow_pixel_count; |
| } |
| } |
| |
| EXPECT_EQ(red_pixel_count, pixel_color_count); |
| EXPECT_EQ(green_pixel_count, pixel_color_count); |
| EXPECT_EQ(blue_pixel_count, pixel_color_count); |
| EXPECT_EQ(yellow_pixel_count, pixel_color_count); |
| |
| WriteToSysmemBuffer(write_values, buffer_collection_info, 0, kBytesPerPixel, image_width, |
| image_height); |
| |
| GenerateImageForFlatlandInstance(0, child_session_, kChildRootTransform, |
| std::move(ref_pair.import_token), {image_width, image_height}, |
| {0, 0}, 2, 2); |
| BlockingPresent(child_session_); |
| |
| // The scene graph is now ready for screenshotting! |
| |
| // Create buffer collection to render into for GetNextFrame(). |
| allocation::BufferCollectionImportExportTokens scr_ref_pair = |
| allocation::BufferCollectionImportExportTokens::New(); |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 sc_buffer_collection_info = |
| CreateBufferCollectionInfoWithConstraints( |
| utils::CreateDefaultConstraints(/*buffer_count=*/1, render_target_width, |
| render_target_height), |
| std::move(scr_ref_pair.export_token), RegisterBufferCollectionUsage::SCREENSHOT); |
| |
| // Configure buffers in ScreenCapture client. |
| ScreenCaptureConfig sc_args; |
| sc_args.set_import_token(std::move(scr_ref_pair.import_token)); |
| sc_args.set_buffer_count(sc_buffer_collection_info.buffer_count); |
| sc_args.set_size({render_target_width, render_target_height}); |
| sc_args.set_rotation(fuchsia::ui::composition::Rotation::CW_90_DEGREES); |
| |
| bool alloc_result = false; |
| screen_capture_->Configure(std::move(sc_args), |
| [&alloc_result](fpromise::result<void, ScreenCaptureError> result) { |
| EXPECT_FALSE(result.is_error()); |
| alloc_result = true; |
| }); |
| |
| RunLoopUntil([&alloc_result] { return alloc_result; }); |
| |
| // Take Screenshot! |
| const auto& cs_result = CaptureScreen(screen_capture_); |
| const auto& read_values = |
| ExtractScreenCapture(cs_result, sc_buffer_collection_info, kBytesPerPixel, |
| render_target_width, render_target_height); |
| |
| EXPECT_EQ(read_values.size(), write_values.size()); |
| |
| // Compare read and write values for each quadrant. |
| uint32_t top_left_correct = 0; |
| uint32_t top_right_correct = 0; |
| uint32_t bottom_right_correct = 0; |
| uint32_t bottom_left_correct = 0; |
| |
| for (uint32_t i = 0; i < read_values.size(); i++) { |
| uint32_t row = i / render_target_width; |
| uint32_t col = i % render_target_width; |
| |
| // Top-left quadrant |
| if (row < render_target_height / 2 && col < render_target_width / 2) { |
| if (read_values[i] == yellow) |
| top_left_correct++; |
| } |
| // Top-right quadrant |
| else if (row < render_target_height / 2 && col >= render_target_width / 2) { |
| if (read_values[i] == red) |
| top_right_correct++; |
| } |
| // Bottom-right quadrant |
| else if (row >= render_target_height / 2 && col >= render_target_width / 2) { |
| if (read_values[i] == green) |
| bottom_right_correct++; |
| } |
| // Bottom-left quadrant |
| else if (row >= render_target_height / 2 && col < render_target_width / 2) { |
| if (read_values[i] == blue) |
| bottom_left_correct++; |
| } |
| } |
| |
| EXPECT_EQ(top_left_correct, pixel_color_count); |
| EXPECT_EQ(top_right_correct, pixel_color_count); |
| EXPECT_EQ(bottom_right_correct, pixel_color_count); |
| EXPECT_EQ(bottom_left_correct, pixel_color_count); |
| } |
| |
| // Creates this image: |
| // RRRRRGGGGG |
| // RRRRRGGGGG |
| // YYYYYBBBBB |
| // YYYYYBBBBB |
| // |
| // Rotates into this image: |
| // GGBB |
| // GGBB |
| // GGBB |
| // GGBB |
| // GGBB |
| // RRYY |
| // RRYY |
| // RRYY |
| // RRYY |
| // RRYY |
| TEST_F(ScreenCaptureIntegrationTest, MultiColor270DegreeRotationScreenshot) { |
| const uint32_t image_width = display_width_; |
| const uint32_t image_height = display_height_; |
| const uint32_t render_target_width = display_height_; |
| const uint32_t render_target_height = display_width_; |
| |
| // Create Buffer Collection for image#1 to add to scene graph. |
| allocation::BufferCollectionImportExportTokens ref_pair = |
| allocation::BufferCollectionImportExportTokens::New(); |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info = |
| CreateBufferCollectionInfoWithConstraints( |
| utils::CreateDefaultConstraints(/*buffer_count=*/1, image_width, image_height), |
| std::move(ref_pair.export_token), RegisterBufferCollectionUsage::DEFAULT); |
| |
| // Write the image with the color scheme displayed in ASCII above. |
| std::vector<uint32_t> write_values; |
| |
| uint32_t red_pixel_count = 0; |
| uint32_t green_pixel_count = 0; |
| uint32_t blue_pixel_count = 0; |
| uint32_t yellow_pixel_count = 0; |
| const uint32_t pixel_color_count = num_pixels_ / 4; |
| |
| for (uint32_t i = 0; i < num_pixels_; ++i) { |
| uint32_t row = i / image_width; |
| uint32_t col = i % image_width; |
| |
| // Top-left quadrant |
| if (row < image_height / 2 && col < image_width / 2) { |
| write_values.push_back(red); |
| ++red_pixel_count; |
| } |
| // Top-right quadrant |
| else if (row < image_height / 2 && col >= image_width / 2) { |
| write_values.push_back(green); |
| ++green_pixel_count; |
| } |
| // Bottom-right quadrant |
| else if (row >= image_height / 2 && col >= image_width / 2) { |
| write_values.push_back(blue); |
| ++blue_pixel_count; |
| } |
| // Bottom-left quadrant |
| else if (row >= image_height / 2 && col < image_width / 2) { |
| write_values.push_back(yellow); |
| ++yellow_pixel_count; |
| } |
| } |
| |
| EXPECT_EQ(red_pixel_count, pixel_color_count); |
| EXPECT_EQ(green_pixel_count, pixel_color_count); |
| EXPECT_EQ(blue_pixel_count, pixel_color_count); |
| EXPECT_EQ(yellow_pixel_count, pixel_color_count); |
| |
| WriteToSysmemBuffer(write_values, buffer_collection_info, 0, kBytesPerPixel, image_width, |
| image_height); |
| |
| GenerateImageForFlatlandInstance(0, child_session_, kChildRootTransform, |
| std::move(ref_pair.import_token), {image_width, image_height}, |
| {0, 0}, 2, 2); |
| BlockingPresent(child_session_); |
| |
| // The scene graph is now ready for screenshotting! |
| |
| // Create buffer collection to render into for GetNextFrame(). |
| allocation::BufferCollectionImportExportTokens scr_ref_pair = |
| allocation::BufferCollectionImportExportTokens::New(); |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 sc_buffer_collection_info = |
| CreateBufferCollectionInfoWithConstraints( |
| utils::CreateDefaultConstraints(/*buffer_count=*/1, render_target_width, |
| render_target_height), |
| std::move(scr_ref_pair.export_token), RegisterBufferCollectionUsage::SCREENSHOT); |
| |
| // Configure buffers in ScreenCapture client. |
| ScreenCaptureConfig sc_args; |
| sc_args.set_import_token(std::move(scr_ref_pair.import_token)); |
| sc_args.set_buffer_count(sc_buffer_collection_info.buffer_count); |
| sc_args.set_size({render_target_width, render_target_height}); |
| sc_args.set_rotation(fuchsia::ui::composition::Rotation::CW_270_DEGREES); |
| |
| bool alloc_result = false; |
| screen_capture_->Configure(std::move(sc_args), |
| [&alloc_result](fpromise::result<void, ScreenCaptureError> result) { |
| EXPECT_FALSE(result.is_error()); |
| alloc_result = true; |
| }); |
| |
| RunLoopUntil([&alloc_result] { return alloc_result; }); |
| |
| // Take Screenshot! |
| const auto& cs_result = CaptureScreen(screen_capture_); |
| const auto& read_values = |
| ExtractScreenCapture(cs_result, sc_buffer_collection_info, kBytesPerPixel, |
| render_target_width, render_target_height); |
| |
| EXPECT_EQ(read_values.size(), write_values.size()); |
| |
| // Compare read and write values for each quadrant. |
| uint32_t top_left_correct = 0; |
| uint32_t top_right_correct = 0; |
| uint32_t bottom_right_correct = 0; |
| uint32_t bottom_left_correct = 0; |
| |
| for (uint32_t i = 0; i < read_values.size(); i++) { |
| uint32_t row = i / render_target_width; |
| uint32_t col = i % render_target_width; |
| |
| // Top-left quadrant |
| if (row < render_target_height / 2 && col < render_target_width / 2) { |
| if (read_values[i] == green) |
| top_left_correct++; |
| } |
| // Top-right quadrant |
| else if (row < render_target_height / 2 && col >= render_target_width / 2) { |
| if (read_values[i] == blue) |
| top_right_correct++; |
| } |
| // Bottom-right quadrant |
| else if (row >= render_target_height / 2 && col >= render_target_width / 2) { |
| if (read_values[i] == yellow) |
| bottom_right_correct++; |
| } |
| // Bottom-left quadrant |
| else if (row >= render_target_height / 2 && col < render_target_width / 2) { |
| if (read_values[i] == red) |
| bottom_left_correct++; |
| } |
| } |
| |
| EXPECT_EQ(top_left_correct, pixel_color_count); |
| EXPECT_EQ(top_right_correct, pixel_color_count); |
| EXPECT_EQ(bottom_right_correct, pixel_color_count); |
| EXPECT_EQ(bottom_left_correct, pixel_color_count); |
| } |
| |
| TEST_F(ScreenCaptureIntegrationTest, FilledRectScreenshot) { |
| const uint32_t image_width = display_width_; |
| const uint32_t image_height = display_height_; |
| const uint32_t render_target_width = display_width_; |
| const uint32_t render_target_height = display_height_; |
| |
| const ContentId kFilledRectId = {1}; |
| const TransformId kTransformId = {2}; |
| |
| // Create a fuchsia colored rectangle. |
| child_session_->CreateFilledRect(kFilledRectId); |
| child_session_->SetSolidFill(kFilledRectId, {1, 0, 1, 1}, {image_width, image_height}); |
| |
| // Associate the rect with a transform. |
| child_session_->CreateTransform(kTransformId); |
| child_session_->SetContent(kTransformId, kFilledRectId); |
| |
| // Attach the transform to the scene |
| child_session_->AddChild(kChildRootTransform, kTransformId); |
| BlockingPresent(child_session_); |
| |
| // The scene graph is now ready for screencapturing! |
| |
| // Create buffer collection to render into for GetNextFrame(). |
| allocation::BufferCollectionImportExportTokens scr_ref_pair = |
| allocation::BufferCollectionImportExportTokens::New(); |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 sc_buffer_collection_info = |
| CreateBufferCollectionInfoWithConstraints( |
| utils::CreateDefaultConstraints(/*buffer_count=*/1, render_target_width, |
| render_target_height), |
| std::move(scr_ref_pair.export_token), RegisterBufferCollectionUsage::SCREENSHOT); |
| |
| // Configure buffers in ScreenCapture client. |
| ScreenCaptureConfig sc_args; |
| sc_args.set_import_token(std::move(scr_ref_pair.import_token)); |
| sc_args.set_size({render_target_width, render_target_height}); |
| sc_args.set_buffer_count(sc_buffer_collection_info.buffer_count); |
| |
| bool alloc_result = false; |
| screen_capture_->Configure(std::move(sc_args), |
| [&alloc_result](fpromise::result<void, ScreenCaptureError> result) { |
| EXPECT_FALSE(result.is_error()); |
| alloc_result = true; |
| }); |
| |
| RunLoopUntil([&alloc_result] { return alloc_result; }); |
| |
| // Take Screenshot! |
| const auto& cs_result = CaptureScreen(screen_capture_); |
| const auto& read_values = |
| ExtractScreenCapture(cs_result, sc_buffer_collection_info, kBytesPerPixel, |
| render_target_width, render_target_height); |
| |
| EXPECT_EQ(read_values.size(), num_pixels_); |
| |
| // Compare read and write values. |
| uint32_t num_fuchsia_count = 0; |
| |
| for (unsigned int read_value : read_values) { |
| if (read_value == 0xFFFF00FF) |
| num_fuchsia_count++; |
| } |
| |
| EXPECT_EQ(num_fuchsia_count, num_pixels_); |
| } |
| |
| TEST_F(ScreenCaptureIntegrationTest, ChangeFilledRectScreenshots) { |
| const uint32_t image_width = display_width_; |
| const uint32_t image_height = display_height_; |
| const uint32_t render_target_width = display_width_; |
| const uint32_t render_target_height = display_height_; |
| |
| const ContentId kFilledRectId = {1}; |
| const TransformId kTransformId = {2}; |
| |
| // Create a red rectangle. |
| child_session_->CreateFilledRect(kFilledRectId); |
| child_session_->SetSolidFill(kFilledRectId, {1, 0, 0, 1}, {image_width, image_height}); |
| |
| // Associate the rect with a transform. |
| child_session_->CreateTransform(kTransformId); |
| child_session_->SetContent(kTransformId, kFilledRectId); |
| |
| // Attach the transform to the scene |
| child_session_->AddChild(kChildRootTransform, kTransformId); |
| BlockingPresent(child_session_); |
| |
| // The scene graph is now ready for screencapturing! |
| |
| // Create buffer collection to render into for GetNextFrame(). |
| allocation::BufferCollectionImportExportTokens scr_ref_pair = |
| allocation::BufferCollectionImportExportTokens::New(); |
| |
| fuchsia::sysmem::BufferCollectionInfo_2 sc_buffer_collection_info = |
| CreateBufferCollectionInfoWithConstraints( |
| utils::CreateDefaultConstraints(/*buffer_count=*/2, render_target_width, |
| render_target_height), |
| std::move(scr_ref_pair.export_token), RegisterBufferCollectionUsage::SCREENSHOT); |
| |
| // Configure buffers in ScreenCapture client. |
| ScreenCaptureConfig sc_args; |
| sc_args.set_import_token(std::move(scr_ref_pair.import_token)); |
| sc_args.set_size({render_target_width, render_target_height}); |
| sc_args.set_buffer_count(sc_buffer_collection_info.buffer_count); |
| |
| bool alloc_result = false; |
| screen_capture_->Configure(std::move(sc_args), |
| [&alloc_result](fpromise::result<void, ScreenCaptureError> result) { |
| EXPECT_FALSE(result.is_error()); |
| alloc_result = true; |
| }); |
| |
| RunLoopUntil([&alloc_result] { return alloc_result; }); |
| |
| // Take Screenshot! |
| const auto& cs_result = CaptureScreen(screen_capture_); |
| const auto& read_values = |
| ExtractScreenCapture(cs_result, sc_buffer_collection_info, kBytesPerPixel, |
| render_target_width, render_target_height); |
| |
| EXPECT_EQ(read_values.size(), num_pixels_); |
| |
| // Compare read and write values. |
| uint32_t num_red_count = 0; |
| |
| for (unsigned int read_value : read_values) { |
| // Output is ARGB |
| if (read_value == 0xFFFF0000) |
| num_red_count++; |
| } |
| |
| EXPECT_EQ(num_red_count, num_pixels_); |
| |
| // Now change the color of the screen. |
| |
| const ContentId kFilledRectId2 = {2}; |
| const TransformId kTransformId2 = {3}; |
| |
| // Create a blue rectangle. |
| child_session_->CreateFilledRect(kFilledRectId2); |
| child_session_->SetSolidFill(kFilledRectId2, {0, 0, 1, 1}, {image_width, image_height}); |
| |
| // Associate the rect with a transform. |
| child_session_->CreateTransform(kTransformId2); |
| child_session_->SetContent(kTransformId2, kFilledRectId2); |
| |
| // Attach the transform to the scene |
| child_session_->AddChild(kChildRootTransform, kTransformId2); |
| BlockingPresent(child_session_); |
| |
| // The scene graph is now ready for screencapturing! |
| |
| // Take Screenshot! |
| const auto& cs_result2 = CaptureScreen(screen_capture_); |
| const auto& read_values2 = |
| ExtractScreenCapture(cs_result2, sc_buffer_collection_info, kBytesPerPixel, |
| render_target_width, render_target_height); |
| |
| EXPECT_EQ(read_values2.size(), num_pixels_); |
| |
| // Compare read and write values. |
| uint32_t num_blue_count = 0; |
| |
| for (unsigned int read_value : read_values2) { |
| if (read_value == 0xFF0000FF) |
| num_blue_count++; |
| } |
| |
| EXPECT_EQ(num_blue_count, num_pixels_); |
| } |
| |
| } // namespace integration_tests |