chromium/ui/gfx/android/android_surface_control_compat_unittest.cc

// 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/build_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::BuildInfo::GetInstance()->sdk_int() <
      base::android::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, STANDARD_BT2020 | TRANSFER_SRGB | 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, STANDARD_BT709 | TRANSFER_SRGB | RANGE_EXTENDED);
    EXPECT_EQ(extended_range_brightness_ratio, 1.f);
  }

  // P3, extended by 2x.
  {
    skcms_TransferFunction trfn_srgb_scaled =
        skia::ScaleTransferFunction(SkNamedTransferFnExt::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, STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED);
    EXPECT_NEAR(extended_range_brightness_ratio, 2.f, 0.0001f);
  }
}

}  // namespace
}  // namespace gfx