| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/gfx/android/android_surface_control_compat.h" |
| |
| #include <android/data_space.h> |
| |
| #include "base/android/android_info.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/task_environment.h" |
| #include "skia/ext/skcolorspace_trfn.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/color_space.h" |
| |
| namespace gfx { |
| namespace { |
| |
| class SurfaceControlTransactionTest : public testing::Test { |
| public: |
| SurfaceControlTransactionTest() { |
| gfx::SurfaceControl::SetStubImplementationForTesting(); |
| } |
| |
| protected: |
| struct CallbackContext { |
| CallbackContext(bool* called, bool* destroyed) |
| : called(called), destroyed(destroyed) {} |
| ~CallbackContext() { *destroyed = true; } |
| raw_ptr<bool> called; |
| raw_ptr<bool> destroyed; |
| }; |
| |
| SurfaceControl::Transaction::OnCompleteCb CreateOnCompleteCb( |
| bool* called, |
| bool* destroyed) { |
| return base::BindOnce( |
| [](std::unique_ptr<CallbackContext> context, |
| SurfaceControl::TransactionStats stats) { |
| DCHECK(!*context->called); |
| *context->called = true; |
| }, |
| std::make_unique<CallbackContext>(called, destroyed)); |
| } |
| |
| SurfaceControl::Transaction::OnCommitCb CreateOnCommitCb(bool* called, |
| bool* destroyed) { |
| return base::BindOnce( |
| [](std::unique_ptr<CallbackContext> context) { |
| DCHECK(!*context->called); |
| *context->called = true; |
| }, |
| std::make_unique<CallbackContext>(called, destroyed)); |
| } |
| |
| void RunRemainingTasks() { |
| base::RunLoop runloop; |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, runloop.QuitClosure()); |
| runloop.Run(); |
| } |
| |
| base::test::SingleThreadTaskEnvironment task_environment; |
| }; |
| |
| TEST_F(SurfaceControlTransactionTest, CallbackCalledAfterApply) { |
| bool on_complete_called = false; |
| bool on_commit_called = false; |
| bool on_commit_destroyed = false; |
| bool on_complete_destroyed = false; |
| |
| gfx::SurfaceControl::Transaction transaction; |
| transaction.SetOnCompleteCb( |
| CreateOnCompleteCb(&on_complete_called, &on_complete_destroyed), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| transaction.SetOnCommitCb( |
| CreateOnCommitCb(&on_commit_called, &on_commit_destroyed), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| |
| // Nothing should have been called yet. |
| EXPECT_FALSE(on_complete_called); |
| EXPECT_FALSE(on_commit_called); |
| |
| transaction.Apply(); |
| RunRemainingTasks(); |
| |
| // After apply callbacks should be called. |
| EXPECT_TRUE(on_complete_called); |
| EXPECT_TRUE(on_commit_called); |
| |
| // As this is Once callback naturally it's context should have been destroyed. |
| EXPECT_TRUE(on_complete_destroyed); |
| EXPECT_TRUE(on_commit_destroyed); |
| } |
| |
| TEST_F(SurfaceControlTransactionTest, CallbackDestroyedWithoutApply) { |
| bool on_complete_called = false; |
| bool on_commit_called = false; |
| bool on_commit_destroyed = false; |
| bool on_complete_destroyed = false; |
| |
| { |
| SurfaceControl::Transaction transaction; |
| transaction.SetOnCompleteCb( |
| CreateOnCompleteCb(&on_complete_called, &on_complete_destroyed), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| transaction.SetOnCommitCb( |
| CreateOnCommitCb(&on_commit_called, &on_commit_destroyed), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| |
| // Nothing should have been called yet. |
| EXPECT_FALSE(on_complete_called); |
| EXPECT_FALSE(on_commit_called); |
| } |
| |
| RunRemainingTasks(); |
| |
| // Apply wasn't called, but transaction left the scope, so the callback |
| // contexts should have been destroyed. |
| EXPECT_TRUE(on_complete_destroyed); |
| EXPECT_TRUE(on_commit_destroyed); |
| } |
| |
| TEST_F(SurfaceControlTransactionTest, CallbackSetupAfterGetTransaction) { |
| bool on_complete_called = false; |
| bool on_commit_called = false; |
| bool on_commit_destroyed = false; |
| bool on_complete_destroyed = false; |
| |
| gfx::SurfaceControl::Transaction transaction; |
| transaction.SetOnCompleteCb( |
| CreateOnCompleteCb(&on_complete_called, &on_complete_destroyed), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| transaction.SetOnCommitCb( |
| CreateOnCommitCb(&on_commit_called, &on_commit_destroyed), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| |
| // Nothing should have been called yet. |
| EXPECT_FALSE(on_complete_called); |
| EXPECT_FALSE(on_commit_called); |
| |
| auto* asurfacetransaction = transaction.GetTransaction(); |
| |
| // Should be no task to run, but calling this to make sure nothing is |
| // scheduled that can call callbacks. |
| RunRemainingTasks(); |
| |
| // And not yet. |
| EXPECT_FALSE(on_complete_called); |
| EXPECT_FALSE(on_commit_called); |
| |
| // This is usually called by framework. |
| SurfaceControl::ApplyTransaction(asurfacetransaction); |
| RunRemainingTasks(); |
| |
| // After apply callbacks should be called. |
| EXPECT_TRUE(on_complete_called); |
| EXPECT_TRUE(on_commit_called); |
| |
| // As this is Once callback naturally it's context should have been destroyed. |
| EXPECT_TRUE(on_complete_destroyed); |
| EXPECT_TRUE(on_commit_destroyed); |
| } |
| |
| TEST(SurfaceControl, ColorSpaceToADataSpace) { |
| // Invalid color spaces are mapped to sRGB. |
| { |
| ADataSpace dataspace = ADATASPACE_UNKNOWN; |
| float extended_range_brightness_ratio = 0.f; |
| EXPECT_TRUE(SurfaceControl::ColorSpaceToADataSpace( |
| gfx::ColorSpace(), 1.f, dataspace, extended_range_brightness_ratio)); |
| EXPECT_EQ(dataspace, ADATASPACE_SRGB); |
| EXPECT_EQ(extended_range_brightness_ratio, 1.f); |
| } |
| |
| // sRGB. |
| { |
| ADataSpace dataspace = ADATASPACE_UNKNOWN; |
| float extended_range_brightness_ratio = 0.f; |
| EXPECT_TRUE(SurfaceControl::ColorSpaceToADataSpace( |
| gfx::ColorSpace::CreateSRGB(), 1.f, dataspace, |
| extended_range_brightness_ratio)); |
| EXPECT_EQ(dataspace, ADATASPACE_SRGB); |
| EXPECT_EQ(extended_range_brightness_ratio, 1.f); |
| } |
| |
| // Display P3. |
| { |
| ADataSpace dataspace = ADATASPACE_UNKNOWN; |
| float extended_range_brightness_ratio = 0.f; |
| EXPECT_TRUE(SurfaceControl::ColorSpaceToADataSpace( |
| gfx::ColorSpace::CreateDisplayP3D65(), 1.f, dataspace, |
| extended_range_brightness_ratio)); |
| EXPECT_EQ(dataspace, ADATASPACE_DISPLAY_P3); |
| EXPECT_EQ(extended_range_brightness_ratio, 1.f); |
| } |
| |
| // Before S, only sRGB and P3 are supported. |
| if (base::android::android_info::sdk_int() < |
| base::android::android_info::SDK_VERSION_S) { |
| return; |
| } |
| |
| // Rec2020 with an sRGB transfer function. |
| { |
| gfx::ColorSpace rec2020(gfx::ColorSpace::PrimaryID::BT2020, |
| gfx::ColorSpace::TransferID::SRGB); |
| ADataSpace dataspace = ADATASPACE_UNKNOWN; |
| float extended_range_brightness_ratio = 0.f; |
| EXPECT_TRUE(SurfaceControl::ColorSpaceToADataSpace( |
| rec2020, 1.f, dataspace, extended_range_brightness_ratio)); |
| EXPECT_EQ(dataspace, ADATASPACE_STANDARD_BT2020 | ADATASPACE_TRANSFER_SRGB | |
| ADATASPACE_RANGE_FULL); |
| EXPECT_EQ(extended_range_brightness_ratio, 1.f); |
| } |
| |
| // sRGB, but it will come out as extended because there is a >1 desired |
| // brightness ratio. |
| { |
| ADataSpace dataspace = ADATASPACE_UNKNOWN; |
| float extended_range_brightness_ratio = 0.f; |
| EXPECT_TRUE(SurfaceControl::ColorSpaceToADataSpace( |
| gfx::ColorSpace::CreateSRGB(), 4.f, dataspace, |
| extended_range_brightness_ratio)); |
| EXPECT_EQ(dataspace, ADATASPACE_STANDARD_BT709 | ADATASPACE_TRANSFER_SRGB | |
| ADATASPACE_RANGE_EXTENDED); |
| EXPECT_EQ(extended_range_brightness_ratio, 1.f); |
| } |
| |
| // P3, extended by 2x. |
| { |
| skcms_TransferFunction trfn_srgb_scaled = |
| skia::ScaleTransferFunction(SkNamedTransferFn::kSRGB, 2.f); |
| gfx::ColorSpace p3_scaled( |
| gfx::ColorSpace::PrimaryID::P3, gfx::ColorSpace::TransferID::CUSTOM_HDR, |
| gfx::ColorSpace::MatrixID::RGB, gfx::ColorSpace::RangeID::FULL, nullptr, |
| &trfn_srgb_scaled); |
| ADataSpace dataspace = ADATASPACE_UNKNOWN; |
| float extended_range_brightness_ratio = 0.f; |
| EXPECT_TRUE(SurfaceControl::ColorSpaceToADataSpace( |
| p3_scaled, 1.f, dataspace, extended_range_brightness_ratio)); |
| EXPECT_EQ(dataspace, ADATASPACE_STANDARD_DCI_P3 | ADATASPACE_TRANSFER_SRGB | |
| ADATASPACE_RANGE_EXTENDED); |
| EXPECT_NEAR(extended_range_brightness_ratio, 2.f, 0.0001f); |
| } |
| } |
| |
| } // namespace |
| } // namespace gfx |