chromium/media/gpu/chromeos/vulkan_overlay_adaptor_test.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/gpu/chromeos/vulkan_overlay_adaptor.h"

#include <linux/videodev2.h>
#include <sys/mman.h>
#include <sys/poll.h>

#include <cstdint>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "base/bits.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/shared_image_backing.h"
#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
#include "gpu/command_buffer/service/shared_image/shared_image_manager.h"
#include "gpu/command_buffer/service/shared_image/shared_image_representation.h"
#include "gpu/config/gpu_feature_info.h"
#include "media/base/video_frame.h"
#include "media/base/video_frame_layout.h"
#include "media/base/video_types.h"
#include "media/gpu/chromeos/chromeos_compressed_gpu_memory_buffer_video_frame_utils.h"
#include "media/gpu/chromeos/fourcc.h"
#include "media/gpu/chromeos/perf_test_util.h"
#include "media/gpu/chromeos/platform_video_frame_utils.h"
#include "media/gpu/test/image.h"
#include "media/gpu/test/image_quality_metrics.h"
#include "media/gpu/test/video_test_environment.h"
#include "media/gpu/video_frame_mapper_factory.h"
#include "third_party/libyuv/include/libyuv.h"
#include "ui/gfx/overlay_transform.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/gl_utils.h"
#include "ui/gl/init/gl_factory.h"
#include "ui/gl/test/gl_surface_test_support.h"
#include "ui/ozone/public/ozone_platform.h"

// Not all versions of the V4L2 headers define these FourCCs, so we add them
// here for backwards compatibility.
#ifndef V4L2_PIX_FMT_MT2T
#define V4L2_PIX_FMT_MT2T v4l2_fourcc('M', 'T', '2', 'T')
#endif
#ifndef V4L2_PIX_FMT_ARGB2101010
#define V4L2_PIX_FMT_ARGB2101010 v4l2_fourcc('A', 'R', '3', '0')
#endif
#ifndef V4L2_PIX_FMT_I010
#define V4L2_PIX_FMT_I010 v4l2_fourcc('I', '0', '1', '0')
#endif

namespace media {
namespace {

struct FrameState {
  uint32_t fourcc;
  bool is_scaled;
  bool is_rotated;

  friend constexpr bool operator==(const FrameState& lhs,
                                   const FrameState& rhs) = default;
};

}  // namespace
}  // namespace media

template <>
struct std::hash<media::FrameState> {
  size_t operator()(const media::FrameState& f) const {
    return static_cast<size_t>(f.fourcc) |
           (static_cast<size_t>(f.is_scaled) << 15) |
           (static_cast<size_t>(f.is_rotated) << 7);
  }
};

namespace media {
namespace {

static constexpr size_t kMM21TileWidth = 16;
static constexpr size_t kMM21TileHeight = 32;
static constexpr int kRandomFrameSeed = 1000;

const char* usage_msg =
    "usage: vulkan_overlay_adaptor_test\n"
    "[--gtest_help] [--help] [-v=<level>] [--vmodule=<config>] \n";

const char* help_msg =
    "Run the vulkan image processor perf tests.\n\n"
    "The following arguments are supported:\n"
    "  --gtest_help          display the gtest help and exit.\n"
    "  --help                display this help and exit.\n"
    "  --source_directory    specify test input files location.\n"
    "  --output_directory    specify test output file location. Defaults to "
    "                        execution directory if not specified.\n"
    "   -v                   enable verbose mode, e.g. -v=2.\n"
    "  --vmodule             enable verbose mode for the specified module.\n";

// File for MM21 detile and scaling test.
const base::FilePath::CharType* kMM21Image =
    FILE_PATH_LITERAL("puppets-480x270.mm21.yuv");
// Files for MT2T Vulkan detile test.
const base::FilePath::CharType* kMT2TImage =
    FILE_PATH_LITERAL("crowd_run_1080x512.mt2t");

constexpr int kLibYUVSuccess = 0;

scoped_refptr<VideoFrame> ConvMM21ToI420(const VideoFrame& in_frame) {
  CHECK_EQ(in_frame.format(), VideoPixelFormat::PIXEL_FORMAT_NV12);

  scoped_refptr<VideoFrame> out_frame = VideoFrame::CreateFrame(
      VideoPixelFormat::PIXEL_FORMAT_I420, in_frame.coded_size(),
      in_frame.visible_rect(), in_frame.coded_size(), base::TimeDelta());
  CHECK_EQ(
      libyuv::MM21ToI420(
          in_frame.visible_data(VideoFrame::Plane::kY),
          in_frame.stride(VideoFrame::Plane::kY),
          in_frame.visible_data(VideoFrame::Plane::kUV),
          in_frame.stride(VideoFrame::Plane::kUV),
          out_frame->GetWritableVisibleData(VideoFrame::Plane::kY),
          out_frame->stride(VideoFrame::Plane::kY),
          out_frame->GetWritableVisibleData(VideoFrame::Plane::kU),
          out_frame->stride(VideoFrame::Plane::kU),
          out_frame->GetWritableVisibleData(VideoFrame::Plane::kV),
          out_frame->stride(VideoFrame::Plane::kV),
          out_frame->coded_size().width(), out_frame->coded_size().height()),
      kLibYUVSuccess);

  return out_frame;
}

scoped_refptr<VideoFrame> ConvMT2TToP010(const VideoFrame& in_frame) {
  constexpr size_t bpp_numerator = 5;
  constexpr size_t bpp_denom = 4;

  CHECK_EQ(in_frame.format(), VideoPixelFormat::PIXEL_FORMAT_NV12);

  gfx::Size out_size =
      gfx::Size(in_frame.coded_size().width(),
                in_frame.coded_size().height() / bpp_numerator * bpp_denom);

  scoped_refptr<VideoFrame> out_frame = VideoFrame::CreateFrame(
      VideoPixelFormat::PIXEL_FORMAT_P010LE, out_size, in_frame.visible_rect(),
      out_size, base::TimeDelta());
  CHECK_EQ(
      libyuv::MT2TToP010(
          in_frame.visible_data(VideoFrame::Plane::kY),
          in_frame.stride(VideoFrame::Plane::kY) * bpp_numerator / bpp_denom,
          in_frame.visible_data(VideoFrame::Plane::kUV),
          in_frame.stride(VideoFrame::Plane::kUV) * bpp_numerator / bpp_denom,
          reinterpret_cast<uint16_t*>(
              out_frame->GetWritableVisibleData(VideoFrame::Plane::kY)),
          out_frame->stride(VideoFrame::Plane::kY) / 2,
          reinterpret_cast<uint16_t*>(
              out_frame->GetWritableVisibleData(VideoFrame::Plane::kUV)),
          out_frame->stride(VideoFrame::Plane::kUV) / 2,
          out_frame->coded_size().width(), out_frame->coded_size().height()),
      kLibYUVSuccess);

  return out_frame;
}

scoped_refptr<VideoFrame> ConvP010ToI010(const VideoFrame& in_frame) {
  CHECK_EQ(in_frame.format(), VideoPixelFormat::PIXEL_FORMAT_P010LE);

  scoped_refptr<VideoFrame> out_frame = VideoFrame::CreateFrame(
      VideoPixelFormat::PIXEL_FORMAT_YUV420P10, in_frame.coded_size(),
      in_frame.visible_rect(), in_frame.coded_size(), base::TimeDelta());
  CHECK_EQ(libyuv::P010ToI010(
               reinterpret_cast<const uint16_t*>(
                   in_frame.visible_data(VideoFrame::Plane::kY)),
               in_frame.stride(VideoFrame::Plane::kY) / 2,
               reinterpret_cast<const uint16_t*>(
                   in_frame.visible_data(VideoFrame::Plane::kUV)),
               in_frame.stride(VideoFrame::Plane::kUV) / 2,
               reinterpret_cast<uint16_t*>(
                   out_frame->GetWritableVisibleData(VideoFrame::Plane::kY)),
               out_frame->stride(VideoFrame::Plane::kY) / 2,
               reinterpret_cast<uint16_t*>(
                   out_frame->GetWritableVisibleData(VideoFrame::Plane::kU)),
               out_frame->stride(VideoFrame::Plane::kU) / 2,
               reinterpret_cast<uint16_t*>(
                   out_frame->GetWritableVisibleData(VideoFrame::Plane::kV)),
               out_frame->stride(VideoFrame::Plane::kV) / 2,
               out_frame->visible_rect().width(),
               out_frame->visible_rect().height()),
           kLibYUVSuccess);

  return out_frame;
}

scoped_refptr<VideoFrame> ConvI420ToARGB(const VideoFrame& in_frame) {
  CHECK_EQ(in_frame.format(), VideoPixelFormat::PIXEL_FORMAT_I420);

  scoped_refptr<VideoFrame> out_frame = VideoFrame::CreateFrame(
      VideoPixelFormat::PIXEL_FORMAT_ARGB, in_frame.coded_size(),
      in_frame.visible_rect(), in_frame.coded_size(), base::TimeDelta());

  CHECK_EQ(libyuv::I420ToARGB(
               in_frame.visible_data(VideoFrame::Plane::kY),
               in_frame.stride(VideoFrame::Plane::kY),
               in_frame.visible_data(VideoFrame::Plane::kU),
               in_frame.stride(VideoFrame::Plane::kU),
               in_frame.visible_data(VideoFrame::Plane::kV),
               in_frame.stride(VideoFrame::Plane::kV),
               out_frame->GetWritableVisibleData(VideoFrame::Plane::kARGB),
               out_frame->stride(VideoFrame::Plane::kARGB),
               out_frame->visible_rect().width(),
               out_frame->visible_rect().height()),
           kLibYUVSuccess);

  return out_frame;
}

scoped_refptr<VideoFrame> ConvI010ToAR30(const VideoFrame& in_frame) {
  CHECK_EQ(in_frame.format(), VideoPixelFormat::PIXEL_FORMAT_YUV420P10);

  scoped_refptr<VideoFrame> out_frame = VideoFrame::CreateFrame(
      VideoPixelFormat::PIXEL_FORMAT_XR30, in_frame.coded_size(),
      in_frame.visible_rect(), in_frame.coded_size(), base::TimeDelta());

  CHECK_EQ(libyuv::I010ToAR30(
               reinterpret_cast<const uint16_t*>(
                   in_frame.visible_data(VideoFrame::Plane::kY)),
               in_frame.stride(VideoFrame::Plane::kY) / 2,
               reinterpret_cast<const uint16_t*>(
                   in_frame.visible_data(VideoFrame::Plane::kU)),
               in_frame.stride(VideoFrame::Plane::kU) / 2,
               reinterpret_cast<const uint16_t*>(
                   in_frame.visible_data(VideoFrame::Plane::kV)),
               in_frame.stride(VideoFrame::Plane::kV) / 2,
               out_frame->GetWritableVisibleData(VideoFrame::Plane::kARGB),
               out_frame->stride(VideoFrame::Plane::kARGB),
               out_frame->visible_rect().width(),
               out_frame->visible_rect().height()),
           kLibYUVSuccess);

  return out_frame;
}

scoped_refptr<VideoFrame> ScaleI420(const gfx::Size* dst_size,
                                    const VideoFrame& in_frame) {
  CHECK_EQ(in_frame.format(), VideoPixelFormat::PIXEL_FORMAT_I420);

  scoped_refptr<VideoFrame> out_frame = VideoFrame::CreateFrame(
      VideoPixelFormat::PIXEL_FORMAT_I420, *dst_size, gfx::Rect(*dst_size),
      *dst_size, base::TimeDelta());

  CHECK_EQ(
      libyuv::I420Scale(
          in_frame.visible_data(VideoFrame::Plane::kY),
          in_frame.stride(VideoFrame::Plane::kY),
          in_frame.visible_data(VideoFrame::Plane::kU),
          in_frame.stride(VideoFrame::Plane::kU),
          in_frame.visible_data(VideoFrame::Plane::kV),
          in_frame.stride(VideoFrame::Plane::kV),
          in_frame.visible_rect().width(), in_frame.visible_rect().height(),
          out_frame->GetWritableVisibleData(VideoFrame::Plane::kY),
          out_frame->stride(VideoFrame::Plane::kY),
          out_frame->GetWritableVisibleData(VideoFrame::Plane::kU),
          out_frame->stride(VideoFrame::Plane::kU),
          out_frame->GetWritableVisibleData(VideoFrame::Plane::kV),
          out_frame->stride(VideoFrame::Plane::kV),
          out_frame->visible_rect().width(), out_frame->visible_rect().height(),
          libyuv::kFilterBilinear),
      kLibYUVSuccess);

  return out_frame;
}

scoped_refptr<VideoFrame> ScaleI010(const gfx::Size* dst_size,
                                    const VideoFrame& in_frame) {
  CHECK_EQ(in_frame.format(), VideoPixelFormat::PIXEL_FORMAT_YUV420P10);

  scoped_refptr<VideoFrame> out_frame = VideoFrame::CreateFrame(
      VideoPixelFormat::PIXEL_FORMAT_YUV420P10, *dst_size, gfx::Rect(*dst_size),
      *dst_size, base::TimeDelta());

  CHECK_EQ(
      libyuv::I420Scale_16(
          reinterpret_cast<const uint16_t*>(
              in_frame.visible_data(VideoFrame::Plane::kY)),
          in_frame.stride(VideoFrame::Plane::kY) / 2,
          reinterpret_cast<const uint16_t*>(
              in_frame.visible_data(VideoFrame::Plane::kU)),
          in_frame.stride(VideoFrame::Plane::kU) / 2,
          reinterpret_cast<const uint16_t*>(
              in_frame.visible_data(VideoFrame::Plane::kV)),
          in_frame.stride(VideoFrame::Plane::kV) / 2,
          in_frame.visible_rect().width(), in_frame.visible_rect().height(),
          reinterpret_cast<uint16_t*>(
              out_frame->GetWritableVisibleData(VideoFrame::Plane::kY)),
          out_frame->stride(VideoFrame::Plane::kY) / 2,
          reinterpret_cast<uint16_t*>(
              out_frame->GetWritableVisibleData(VideoFrame::Plane::kU)),
          out_frame->stride(VideoFrame::Plane::kU) / 2,
          reinterpret_cast<uint16_t*>(
              out_frame->GetWritableVisibleData(VideoFrame::Plane::kV)),
          out_frame->stride(VideoFrame::Plane::kV) / 2,
          out_frame->visible_rect().width(), out_frame->visible_rect().height(),
          libyuv::kFilterBilinear),
      kLibYUVSuccess);

  return out_frame;
}

scoped_refptr<VideoFrame> RotateI420(libyuv::RotationMode rotation,
                                     gfx::Size* final_out_size,
                                     const VideoFrame& in_frame) {
  CHECK_EQ(in_frame.format(), VideoPixelFormat::PIXEL_FORMAT_I420);

  gfx::Size out_size = in_frame.coded_size();
  if (rotation == libyuv::kRotate90 || rotation == libyuv::kRotate270) {
    out_size.Transpose();

    // If we need to do a scale and a rotate, we may need to transpose the scale
    // operation's output dimensions if it comes before the rotation. To handle
    // this, we transpose the final output size initially, and then here, in the
    // rotation operation, we transpose it back to the original dimensions in
    // case the scale operation comes later.
    final_out_size->Transpose();
  }
  scoped_refptr<VideoFrame> out_frame =
      VideoFrame::CreateFrame(VideoPixelFormat::PIXEL_FORMAT_I420, out_size,
                              gfx::Rect(out_size), out_size, base::TimeDelta());

  CHECK_EQ(libyuv::I420Rotate(
               in_frame.visible_data(VideoFrame::Plane::kY),
               in_frame.stride(VideoFrame::Plane::kY),
               in_frame.visible_data(VideoFrame::Plane::kU),
               in_frame.stride(VideoFrame::Plane::kU),
               in_frame.visible_data(VideoFrame::Plane::kV),
               in_frame.stride(VideoFrame::Plane::kV),
               out_frame->GetWritableVisibleData(VideoFrame::Plane::kY),
               out_frame->stride(VideoFrame::Plane::kY),
               out_frame->GetWritableVisibleData(VideoFrame::Plane::kU),
               out_frame->stride(VideoFrame::Plane::kU),
               out_frame->GetWritableVisibleData(VideoFrame::Plane::kV),
               out_frame->stride(VideoFrame::Plane::kV),
               in_frame.visible_rect().width(),
               in_frame.visible_rect().height(), rotation),
           kLibYUVSuccess);

  return out_frame;
}

scoped_refptr<VideoFrame> RotateI010(libyuv::RotationMode rotation,
                                     gfx::Size* final_out_size,
                                     const VideoFrame& in_frame) {
  CHECK_EQ(in_frame.format(), VideoPixelFormat::PIXEL_FORMAT_YUV420P10);

  gfx::Size out_size = in_frame.coded_size();
  if (rotation == libyuv::kRotate90 || rotation == libyuv::kRotate270) {
    out_size.Transpose();

    final_out_size->Transpose();
  }
  scoped_refptr<VideoFrame> out_frame = VideoFrame::CreateFrame(
      VideoPixelFormat::PIXEL_FORMAT_YUV420P10, out_size, gfx::Rect(out_size),
      out_size, base::TimeDelta());

  CHECK_EQ(libyuv::I010Rotate(
               reinterpret_cast<const uint16_t*>(
                   in_frame.visible_data(VideoFrame::Plane::kY)),
               in_frame.stride(VideoFrame::Plane::kY) / 2,
               reinterpret_cast<const uint16_t*>(
                   in_frame.visible_data(VideoFrame::Plane::kU)),
               in_frame.stride(VideoFrame::Plane::kU) / 2,
               reinterpret_cast<const uint16_t*>(
                   in_frame.visible_data(VideoFrame::Plane::kV)),
               in_frame.stride(VideoFrame::Plane::kV) / 2,
               reinterpret_cast<uint16_t*>(
                   out_frame->GetWritableVisibleData(VideoFrame::Plane::kY)),
               out_frame->stride(VideoFrame::Plane::kY) / 2,
               reinterpret_cast<uint16_t*>(
                   out_frame->GetWritableVisibleData(VideoFrame::Plane::kU)),
               out_frame->stride(VideoFrame::Plane::kU) / 2,
               reinterpret_cast<uint16_t*>(
                   out_frame->GetWritableVisibleData(VideoFrame::Plane::kV)),
               out_frame->stride(VideoFrame::Plane::kV) / 2,
               in_frame.visible_rect().width(),
               in_frame.visible_rect().height(), rotation),
           kLibYUVSuccess);

  return out_frame;
}

// Convenience function for handling multi-step LibYUV conversions with pivots.
scoped_refptr<VideoFrame> ProcessFrameLibyuv(scoped_refptr<VideoFrame> in_frame,
                                             uint32_t in_fourcc,
                                             const gfx::Size& in_size,
                                             uint32_t out_fourcc,
                                             gfx::Size out_size,
                                             gfx::OverlayTransform transform) {
  libyuv::RotationMode rotation;
  switch (transform) {
    case gfx::OVERLAY_TRANSFORM_NONE:
      rotation = libyuv::kRotate0;
      break;
    case gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90:
      rotation = libyuv::kRotate90;
      break;
    case gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180:
      rotation = libyuv::kRotate180;
      break;
    case gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270:
      rotation = libyuv::kRotate270;
      break;
    default:
      NOTREACHED() << "Invalid overlay transform: " << transform;
      return nullptr;
  }

  // Assemble a graph of the available LibYUV conversion functions.
  std::unordered_multimap<
      uint32_t, std::pair<base::RepeatingCallback<scoped_refptr<VideoFrame>(
                              const VideoFrame&)>,
                          FrameState>>
      frame_process_graph = {
          {V4L2_PIX_FMT_MM21,
           std::make_pair(base::BindRepeating(&ConvMM21ToI420),
                          FrameState(V4L2_PIX_FMT_YUV420, false, false))},
          {V4L2_PIX_FMT_MT2T,
           std::make_pair(base::BindRepeating(&ConvMT2TToP010),
                          FrameState(V4L2_PIX_FMT_P010, false, false))},
          {V4L2_PIX_FMT_P010,
           std::make_pair(base::BindRepeating(&ConvP010ToI010),
                          FrameState(V4L2_PIX_FMT_I010, false, false))},
          {V4L2_PIX_FMT_YUV420,
           std::make_pair(base::BindRepeating(&ConvI420ToARGB),
                          FrameState(V4L2_PIX_FMT_ARGB32, false, false))},
          {V4L2_PIX_FMT_I010,
           std::make_pair(base::BindRepeating(&ConvI010ToAR30),
                          FrameState(V4L2_PIX_FMT_ARGB2101010, false, false))},
          {V4L2_PIX_FMT_YUV420,
           std::make_pair(base::BindRepeating(&ScaleI420, &out_size),
                          FrameState(V4L2_PIX_FMT_YUV420, true, false))},
          {V4L2_PIX_FMT_I010,
           std::make_pair(base::BindRepeating(&ScaleI010, &out_size),
                          FrameState(V4L2_PIX_FMT_I010, true, false))},
          {V4L2_PIX_FMT_YUV420,
           std::make_pair(base::BindRepeating(&RotateI420, rotation, &out_size),
                          FrameState(V4L2_PIX_FMT_YUV420, false, true))},
          {V4L2_PIX_FMT_I010,
           std::make_pair(base::BindRepeating(&RotateI010, rotation, &out_size),
                          FrameState(V4L2_PIX_FMT_I010, false, true))}};

  FrameState target_state = {out_fourcc, in_size != out_size,
                             rotation != libyuv::kRotate0};
  std::vector<
      base::RepeatingCallback<scoped_refptr<VideoFrame>(const VideoFrame&)>>
      path;
  bool found_path = false;

  // Simple BFS implementation for finding the minimal number of processing
  // steps for a given frame. Some of these conversions are not completely
  // lossless, so we want to minimize distortion.
  std::vector<FrameState> frame_states = {FrameState(in_fourcc, false)};
  std::unordered_set<FrameState> seen_states;
  std::vector<std::vector<
      base::RepeatingCallback<scoped_refptr<VideoFrame>(const VideoFrame&)>>>
      paths = {{}};
  constexpr int kMaxPivots = 10;
  int search_radius;
  for (search_radius = 0; search_radius < kMaxPivots; search_radius++) {
    if (found_path) {
      break;
    }

    std::vector<FrameState> updated_states;
    std::vector<std::vector<
        base::RepeatingCallback<scoped_refptr<VideoFrame>(const VideoFrame&)>>>
        updated_paths;

    for (size_t i = 0; i < frame_states.size(); i++) {
      if (found_path) {
        break;
      }

      const auto& curr_state = frame_states[i];
      const auto& curr_path = paths[i];
      auto range = frame_process_graph.equal_range(curr_state.fourcc);
      for (auto itr = range.first; itr != range.second; itr++) {
        FrameState candidate_state(
            itr->second.second.fourcc,
            itr->second.second.is_scaled | curr_state.is_scaled,
            itr->second.second.is_rotated | curr_state.is_rotated);
        if (seen_states.contains(candidate_state)) {
          continue;
        }

        seen_states.insert(candidate_state);
        updated_states.push_back(candidate_state);
        updated_paths.push_back(curr_path);
        updated_paths.back().push_back(itr->second.first);

        if (candidate_state == target_state) {
          path = updated_paths.back();
          found_path = true;
          break;
        }
      }
    }

    frame_states = std::move(updated_states);
    paths = std::move(updated_paths);
  }
  CHECK_NE(search_radius, kMaxPivots);

  auto frame = in_frame;
  if (rotation == libyuv::kRotate90 || rotation == libyuv::kRotate270) {
    out_size.Transpose();
  }
  for (auto& process : path) {
    frame = process.Run(*frame);
  }

  return frame;
}

void InitWithImage(const uint8_t* img_data,
                   const gfx::Size size,
                   uint8_t* y_plane,
                   size_t y_stride,
                   uint8_t* uv_plane,
                   size_t uv_stride) {
  libyuv::NV12Copy(img_data, size.width(), img_data + size.GetArea(),
                   size.width(), y_plane, y_stride, uv_plane, uv_stride,
                   size.width(), size.height());
}

void InitWithRandom(const gfx::Size size,
                    uint8_t* y_plane,
                    size_t y_stride,
                    uint8_t* uv_plane,
                    size_t uv_stride) {
  base::span<uint8_t> y_plane_span = UNSAFE_TODO(base::span(
      y_plane, y_stride * base::checked_cast<size_t>(size.height())));
  base::span<uint8_t> uv_plane_span = UNSAFE_TODO(base::span(
      uv_plane, uv_stride * base::checked_cast<size_t>(size.height()) / 2u));
  const auto width = base::checked_cast<size_t>(size.width());

  for (int row = 0; row < size.height(); row++) {
    auto [row_bytes, rem] = y_plane_span.split_at(y_stride);
    base::RandBytes(row_bytes.first(width));
    y_plane_span = rem;
  }
  for (int row = 0; row < size.height() / 2; row++) {
    auto [row_bytes, rem] = uv_plane_span.split_at(uv_stride);
    base::RandBytes(row_bytes.first(width));
    uv_plane_span = rem;
  }
}

struct VulkanOverlayAdaptorTestParam {
  TiledImageFormat tiling;
  gfx::Size size;
  gfx::OverlayTransform transform;
};

class VulkanOverlayAdaptorTest
    : public testing::Test,
      public testing::WithParamInterface<VulkanOverlayAdaptorTestParam> {
 public:
  VulkanOverlayAdaptorTest();
  ~VulkanOverlayAdaptorTest() = default;

  struct PrintToStringParamName {
    template <class ParamType>
    std::string operator()(
        const testing::TestParamInfo<ParamType>& info) const {
      std::string ret = info.param.tiling == kMM21 ? "MM21" : "MT2T";
      ret += "_" + info.param.size.ToString();
      switch (info.param.transform) {
        case gfx::OVERLAY_TRANSFORM_NONE:
          ret += "_rot0";
          break;
        case gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90:
          ret += "_rot90";
          break;
        case gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180:
          ret += "_rot180";
          break;
        case gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270:
          ret += "_rot270";
          break;
        default:
          NOTREACHED() << "Invalid transform: " << info.param.transform;
      }
      return ret;
    }
  };

  void ProcessMailboxes(gpu::Mailbox in_mailbox,
                        const gfx::Size& size,
                        gpu::Mailbox out_mailbox,
                        const gfx::RectF& display_rect,
                        const gfx::RectF& crop_rect,
                        gfx::OverlayTransform transform,
                        VulkanOverlayAdaptor& processor);

  scoped_refptr<VideoFrame> CreateVideoFrame(
      gpu::Mailbox mailbox,
      const gfx::Size& coded_size,
      const gfx::Rect& visible_rect,
      base::OnceCallback<void(gfx::Size, uint8_t*, size_t, uint8_t*, size_t)>
          frame_init_cb,
      bool is_10bit);

  scoped_refptr<VideoFrame> CreateFramebuffer(gpu::Mailbox mailbox,
                                              const gfx::Size& coded_size,
                                              bool is_10bit);

 private:
  scoped_refptr<gl::GLShareGroup> share_group_;
  scoped_refptr<gl::GLSurface> surface_;
  scoped_refptr<gl::GLContext> context_;
  scoped_refptr<gpu::SharedContextState> context_state_;
  gpu::SharedImageManager shared_image_manager_;
  gpu::GpuPreferences gpu_preferences_;
  gpu::GpuDriverBugWorkarounds gpu_workarounds_;
  gpu::GpuFeatureInfo gpu_info_;
  std::unique_ptr<gpu::SharedImageFactory> shared_image_factory_;
};

VulkanOverlayAdaptorTest::VulkanOverlayAdaptorTest()
    : share_group_(base::MakeRefCounted<gl::GLShareGroup>()),
      surface_(gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(),
                                                  gfx::Size())),
      context_(gl::init::CreateGLContext(share_group_.get(),
                                         surface_.get(),
                                         gl::GLContextAttribs())) {
  context_->MakeCurrent(surface_.get());
  context_state_ = base::MakeRefCounted<gpu::SharedContextState>(
      share_group_, surface_, context_, false, base::DoNothing(),
      gpu::GpuPreferences().gr_context_type);
  shared_image_factory_ = std::make_unique<gpu::SharedImageFactory>(
      gpu_preferences_, gpu_workarounds_, gpu_info_, context_state_.get(),
      &shared_image_manager_, nullptr, false);
}

void VulkanOverlayAdaptorTest::ProcessMailboxes(
    gpu::Mailbox in_mailbox,
    const gfx::Size& size,
    gpu::Mailbox out_mailbox,
    const gfx::RectF& display_rect,
    const gfx::RectF& crop_rect,
    gfx::OverlayTransform transform,
    VulkanOverlayAdaptor& processor) {
  auto in_vulkan_representation = shared_image_manager_.ProduceVulkan(
      in_mailbox, nullptr, processor.GetVulkanDeviceQueue(),
      processor.GetVulkanImplementation(), /*needs_detiling=*/true);
  auto out_vulkan_representation = shared_image_manager_.ProduceVulkan(
      out_mailbox, nullptr, processor.GetVulkanDeviceQueue(),
      processor.GetVulkanImplementation(), /*needs_detiling=*/true);
  {
    std::vector<VkSemaphore> begin_semaphores;
    std::vector<VkSemaphore> end_semaphores;
    auto in_access = in_vulkan_representation->BeginScopedAccess(
        gpu::RepresentationAccessMode::kRead, begin_semaphores, end_semaphores);
    auto out_access = out_vulkan_representation->BeginScopedAccess(
        gpu::RepresentationAccessMode::kWrite, begin_semaphores,
        end_semaphores);

    processor.Process(in_access->GetVulkanImage(), size,
                      out_access->GetVulkanImage(), display_rect, crop_rect,
                      transform, begin_semaphores, end_semaphores);
  }
}

scoped_refptr<VideoFrame> VulkanOverlayAdaptorTest::CreateVideoFrame(
    gpu::Mailbox mailbox,
    const gfx::Size& coded_size,
    const gfx::Rect& visible_rect,
    base::OnceCallback<
        void(const gfx::Size, uint8_t*, size_t, uint8_t*, size_t)>
        frame_init_cb,
    bool is_10bit) {
  constexpr base::TimeDelta kNullTimestamp;
  const size_t bpp_numerator = is_10bit ? 5 : 1;
  const size_t bpp_denom = is_10bit ? 4 : 1;
  const gfx::Size alloc_size(
      base::bits::AlignUp(base::checked_cast<size_t>(coded_size.width()),
                          kMM21TileWidth),
      base::bits::AlignUp(base::checked_cast<size_t>(coded_size.height()),
                          kMM21TileHeight) *
          bpp_numerator / bpp_denom);

  scoped_refptr<VideoFrame> frame = CreateGpuMemoryBufferVideoFrame(
      VideoPixelFormat::PIXEL_FORMAT_NV12, alloc_size, visible_rect, alloc_size,
      kNullTimestamp, gfx::BufferUsage::SCANOUT_CPU_READ_WRITE);

  std::unique_ptr<VideoFrameMapper> frame_mapper =
      VideoFrameMapperFactory::CreateMapper(
          VideoPixelFormat::PIXEL_FORMAT_NV12,
          VideoFrame::STORAGE_GPU_MEMORY_BUFFER,
          /*force_linear_buffer_mapper=*/true,
          /*must_support_intel_media_compressed_buffers=*/false);
  scoped_refptr<VideoFrame> mapped_frame =
      frame_mapper->Map(frame, PROT_READ | PROT_WRITE);

  std::move(frame_init_cb)
      .Run(alloc_size,
           mapped_frame->GetWritableVisibleData(VideoFrame::Plane::kY),
           mapped_frame->stride(VideoFrame::Plane::kY),
           mapped_frame->GetWritableVisibleData(VideoFrame::Plane::kUV),
           mapped_frame->stride(VideoFrame::Plane::kUV));

  auto gmb = CreateGpuMemoryBufferHandle(frame.get());
  viz::SharedImageFormat format_nv12 = viz::SharedImageFormat::MultiPlane(
      viz::SharedImageFormat::PlaneConfig::kY_UV,
      viz::SharedImageFormat::Subsampling::k420,
      viz::SharedImageFormat::ChannelFormat::k8);
  format_nv12.SetPrefersExternalSampler();
  shared_image_factory_->CreateSharedImage(
      mailbox, format_nv12, frame->coded_size(), gfx::ColorSpace::CreateSRGB(),
      kTopLeft_GrSurfaceOrigin, kOpaque_SkAlphaType,
      gpu::SharedImageUsage::SHARED_IMAGE_USAGE_DISPLAY_READ, "TestLabel",
      std::move(gmb));

  return mapped_frame;
}

scoped_refptr<VideoFrame> VulkanOverlayAdaptorTest::CreateFramebuffer(
    gpu::Mailbox mailbox,
    const gfx::Size& coded_size,
    bool is_10bit) {
  constexpr base::TimeDelta kNullTimestamp;

  scoped_refptr<VideoFrame> frame = CreateGpuMemoryBufferVideoFrame(
      is_10bit ? VideoPixelFormat::PIXEL_FORMAT_XR30
               : VideoPixelFormat::PIXEL_FORMAT_ARGB,
      coded_size, gfx::Rect(coded_size), coded_size, kNullTimestamp,
      gfx::BufferUsage::SCANOUT_CPU_READ_WRITE);

  auto gmb = CreateGpuMemoryBufferHandle(frame.get());
  shared_image_factory_->CreateSharedImage(
      mailbox,
      is_10bit ? viz::SinglePlaneFormat::kBGRA_1010102
               : viz::SinglePlaneFormat::kBGRA_8888,
      coded_size, gfx::ColorSpace::CreateSRGB(), kTopLeft_GrSurfaceOrigin,
      kUnpremul_SkAlphaType,
      gpu::SharedImageUsage::SHARED_IMAGE_USAGE_DISPLAY_WRITE |
          gpu::SharedImageUsage::SHARED_IMAGE_USAGE_SCANOUT,
      "TestLabel", std::move(gmb));

  std::unique_ptr<VideoFrameMapper> frame_mapper =
      VideoFrameMapperFactory::CreateMapper(
          is_10bit ? VideoPixelFormat::PIXEL_FORMAT_XR30
                   : VideoPixelFormat::PIXEL_FORMAT_ARGB,
          VideoFrame::STORAGE_GPU_MEMORY_BUFFER,
          /*force_linear_buffer_mapper=*/true,
          /*must_support_intel_media_compressed_buffers=*/false);
  scoped_refptr<VideoFrame> mapped_frame =
      frame_mapper->Map(frame, PROT_READ | PROT_WRITE);

  return mapped_frame;
}

TEST_P(VulkanOverlayAdaptorTest, Correctness) {
  bool is_10bit = GetParam().tiling == kMT2T;
  const gfx::Size& output_size = GetParam().size;
  const gfx::OverlayTransform transform = GetParam().transform;

  auto in_mailbox = gpu::Mailbox::Generate();
  auto out_mailbox = gpu::Mailbox::Generate();

  test::Image image(media::g_source_directory.Append(
      base::FilePath(is_10bit ? kMT2TImage : kMM21Image)));
  ASSERT_TRUE(image.Load());
  gfx::Size size(
      base::bits::AlignUp(base::checked_cast<size_t>(image.Size().width()),
                          kMM21TileWidth),
      base::bits::AlignUp(base::checked_cast<size_t>(image.Size().height()),
                          kMM21TileHeight));
  auto init_cb = base::BindOnce(&InitWithImage, image.Data());
  auto in_frame =
      CreateVideoFrame(in_mailbox, image.Size(), image.VisibleRect(),
                       std::move(init_cb), is_10bit);

  auto out_frame = CreateFramebuffer(out_mailbox, output_size, is_10bit);

  auto vulkan_overlay_adaptor =
      VulkanOverlayAdaptor::Create(/*is_protected=*/false, GetParam().tiling);

  bool performed_cleanup = false;
  auto fence_helper =
      vulkan_overlay_adaptor->GetVulkanDeviceQueue()->GetFenceHelper();
  fence_helper->EnqueueCleanupTaskForSubmittedWork(base::BindOnce(
      [](bool* cleanup_flag, gpu::VulkanDeviceQueue* device_queue,
         bool device_lost) { *cleanup_flag = true; },
      &performed_cleanup));
  auto cleanup_fence = fence_helper->GenerateCleanupFence();
  fence_helper->Wait(cleanup_fence, UINT64_MAX);

  ProcessMailboxes(in_mailbox, image.VisibleRect().size(), out_mailbox,
                   gfx::RectF(base::checked_cast<float>(output_size.width()),
                              base::checked_cast<float>(output_size.height())),
                   gfx::RectF(1.0f, 1.0f), transform, *vulkan_overlay_adaptor);
  ASSERT_TRUE(performed_cleanup);

  // This implicitly waits for all semaphores to signal.
  vulkan_overlay_adaptor->GetVulkanDeviceQueue()
      ->GetFenceHelper()
      ->PerformImmediateCleanup();

  const uint32_t in_fourcc = is_10bit ? V4L2_PIX_FMT_MT2T : V4L2_PIX_FMT_MM21;
  const uint32_t out_fourcc =
      is_10bit ? V4L2_PIX_FMT_ARGB2101010 : V4L2_PIX_FMT_ARGB32;
  // TODO(b/328227651): We have to keep the 10-bit PSNR threshold pretty low
  // because LibYUV produces inaccurate results in the 10-bit YUV->ARGB
  // conversion. We should try to fix this discrepancy though.
  const double psnr_threshold = is_10bit ? 25.0 : 35.0;
  double psnr = 0.0;
  // GPU memory buffers have some unusual stride requirements that don't play
  // nicely with the libyuv implementation of some functions. So instead of
  // using the regular input VideoFrame, we create a new one that just wraps the
  // raw, packed image data.
  auto packed_in_frame = VideoFrame::WrapExternalData(
      VideoPixelFormat::PIXEL_FORMAT_NV12, in_frame->coded_size(),
      in_frame->visible_rect(), in_frame->coded_size(), image.Data(),
      in_frame->coded_size().GetArea() * 3 / 2, base::TimeDelta());

  auto libyuv_out_frame =
      ProcessFrameLibyuv(packed_in_frame, in_fourcc, image.Size(), out_fourcc,
                         output_size, transform);
  if (is_10bit) {
    psnr = test::ComputeAR30PSNR(
        reinterpret_cast<const uint32_t*>(
            out_frame->visible_data(VideoFrame::Plane::kARGB)),
        out_frame->stride(VideoFrame::Plane::kARGB) / 4,
        reinterpret_cast<const uint32_t*>(
            libyuv_out_frame->visible_data(VideoFrame::Plane::kARGB)),
        libyuv_out_frame->stride(VideoFrame::Plane::kARGB) / 4,
        output_size.width(), output_size.height());
  } else {
    psnr = libyuv::CalcFramePsnr(
        out_frame->visible_data(VideoFrame::Plane::kARGB),
        out_frame->stride(VideoFrame::Plane::kARGB),
        libyuv_out_frame->visible_data(VideoFrame::Plane::kARGB),
        libyuv_out_frame->stride(VideoFrame::Plane::kARGB), output_size.width(),
        output_size.height());
  }
  ASSERT_TRUE(psnr >= psnr_threshold);
}

TEST_P(VulkanOverlayAdaptorTest, Performance) {
  constexpr size_t kNumberOfTestFrames = 10;
  constexpr size_t kNumberOfTestCycles = 200;

  const bool is_10bit = GetParam().tiling == kMT2T;
  const gfx::Size& test_image_size = GetParam().size;
  const gfx::OverlayTransform transform = GetParam().transform;
  gfx::Size output_size = test_image_size;
  if (transform == gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90 ||
      transform == gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270) {
    output_size.Transpose();
  }

  gfx::Size test_coded_size(
      base::bits::AlignUp(base::checked_cast<size_t>(test_image_size.width()),
                          kMM21TileWidth),
      base::bits::AlignUp(base::checked_cast<size_t>(test_image_size.height()),
                          kMM21TileHeight));
  std::array<gpu::Mailbox, kNumberOfTestFrames> in_mailboxes;
  std::array<scoped_refptr<VideoFrame>, kNumberOfTestFrames> in_frames;
  std::array<gpu::Mailbox, kNumberOfTestFrames> out_mailboxes;
  std::array<scoped_refptr<VideoFrame>, kNumberOfTestFrames> out_frames;

  for (size_t i = 0; i < kNumberOfTestFrames; i++) {
    auto init_cb = base::BindOnce(&InitWithRandom);
    in_mailboxes[i] = gpu::Mailbox::Generate();
    in_frames[i] = CreateVideoFrame(in_mailboxes[i], test_coded_size,
                                    gfx::Rect(test_coded_size),
                                    std::move(init_cb), is_10bit);

    out_mailboxes[i] = gpu::Mailbox::Generate();
    out_frames[i] = CreateFramebuffer(out_mailboxes[i], output_size, is_10bit);
  }

  auto vulkan_overlay_adaptor =
      VulkanOverlayAdaptor::Create(/*is_protected=*/false, GetParam().tiling);

  auto start_time = base::TimeTicks::Now();
  for (size_t i = 0; i < kNumberOfTestCycles; i++) {
    ProcessMailboxes(
        in_mailboxes[i % kNumberOfTestFrames], test_image_size,
        out_mailboxes[i % kNumberOfTestFrames],
        gfx::RectF(base::checked_cast<float>(output_size.width()),
                   base::checked_cast<float>(output_size.height())),
        gfx::RectF(1.0f, 1.0f), transform, *vulkan_overlay_adaptor);
  }
  auto end_time = base::TimeTicks::Now();

  base::TimeDelta delta_time = end_time - start_time;
  const double fps = (kNumberOfTestCycles / delta_time.InSecondsF());
  WriteJsonResult({{"FramesDecoded", kNumberOfTestCycles},
                   {"TotalDurationMs", delta_time.InMicrosecondsF()},
                   {"FramesPerSecond", fps}});
}

INSTANTIATE_TEST_SUITE_P(
    ,
    VulkanOverlayAdaptorTest,
    testing::Values(
        VulkanOverlayAdaptorTestParam({kMM21, gfx::Size(320, 240),
                                       gfx::OVERLAY_TRANSFORM_NONE}),
        VulkanOverlayAdaptorTestParam({kMM21, gfx::Size(1280, 720),
                                       gfx::OVERLAY_TRANSFORM_NONE}),
        VulkanOverlayAdaptorTestParam({kMM21, gfx::Size(1920, 1080),
                                       gfx::OVERLAY_TRANSFORM_NONE}),
        VulkanOverlayAdaptorTestParam(
            {kMM21, gfx::Size(240, 320),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90}),
        VulkanOverlayAdaptorTestParam(
            {kMM21, gfx::Size(720, 1280),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90}),
        VulkanOverlayAdaptorTestParam(
            {kMM21, gfx::Size(1080, 1920),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90}),
        VulkanOverlayAdaptorTestParam(
            {kMM21, gfx::Size(320, 240),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180}),
        VulkanOverlayAdaptorTestParam(
            {kMM21, gfx::Size(1280, 720),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180}),
        VulkanOverlayAdaptorTestParam(
            {kMM21, gfx::Size(1920, 1080),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180}),
        VulkanOverlayAdaptorTestParam(
            {kMM21, gfx::Size(240, 320),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270}),
        VulkanOverlayAdaptorTestParam(
            {kMM21, gfx::Size(720, 1280),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270}),
        VulkanOverlayAdaptorTestParam(
            {kMM21, gfx::Size(1080, 1920),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270}),
        VulkanOverlayAdaptorTestParam({kMT2T, gfx::Size(320, 240),
                                       gfx::OVERLAY_TRANSFORM_NONE}),
        VulkanOverlayAdaptorTestParam({kMT2T, gfx::Size(1280, 720),
                                       gfx::OVERLAY_TRANSFORM_NONE}),
        VulkanOverlayAdaptorTestParam({kMT2T, gfx::Size(1920, 1080),
                                       gfx::OVERLAY_TRANSFORM_NONE}),
        VulkanOverlayAdaptorTestParam(
            {kMT2T, gfx::Size(240, 320),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90}),
        VulkanOverlayAdaptorTestParam(
            {kMT2T, gfx::Size(720, 1280),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90}),
        VulkanOverlayAdaptorTestParam(
            {kMT2T, gfx::Size(1080, 1920),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90}),
        VulkanOverlayAdaptorTestParam(
            {kMT2T, gfx::Size(320, 240),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180}),
        VulkanOverlayAdaptorTestParam(
            {kMT2T, gfx::Size(1280, 720),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180}),
        VulkanOverlayAdaptorTestParam(
            {kMT2T, gfx::Size(1920, 1080),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180}),
        VulkanOverlayAdaptorTestParam(
            {kMT2T, gfx::Size(240, 320),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270}),
        VulkanOverlayAdaptorTestParam(
            {kMT2T, gfx::Size(720, 1280),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270}),
        VulkanOverlayAdaptorTestParam(
            {kMT2T, gfx::Size(1080, 1920),
             gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270})),
    VulkanOverlayAdaptorTest::PrintToStringParamName());

}  // namespace
}  // namespace media

int main(int argc, char** argv) {
  base::CommandLine::Init(argc, argv);

  // Print the help message if requested. This needs to be done before
  // initializing gtest, to overwrite the default gtest help message.
  const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
  LOG_ASSERT(cmd_line);
  if (cmd_line->HasSwitch("help")) {
    std::cout << media::usage_msg << "\n" << media::help_msg;
    return 0;
  }

  base::CommandLine::SwitchMap switches = cmd_line->GetSwitches();
  for (base::CommandLine::SwitchMap::const_iterator it = switches.begin();
       it != switches.end(); ++it) {
    if (it->first.find("gtest_") == 0 ||               // Handled by GoogleTest
        it->first == "v" || it->first == "vmodule") {  // Handled by Chrome
      continue;
    }

    if (it->first == "source_directory") {
      media::g_source_directory = base::FilePath(it->second);
    } else if (it->first == "output_directory") {
      media::g_output_directory = base::FilePath(it->second);
    } else {
      std::cout << "unknown option: --" << it->first << "\n"
                << media::usage_msg;
      return EXIT_FAILURE;
    }
  }
  srand(media::kRandomFrameSeed);
  testing::InitGoogleTest(&argc, argv);

  auto* const test_environment = new media::test::VideoTestEnvironment;
  media::g_env = reinterpret_cast<media::test::VideoTestEnvironment*>(
      testing::AddGlobalTestEnvironment(test_environment));

  // TODO(b/316374371) Try to remove Ozone and replace with EGL and GL.
  ui::OzonePlatform::InitParams ozone_param;
  ozone_param.single_process = true;
  ui::OzonePlatform::InitializeForUI(ozone_param);
  ui::OzonePlatform::InitializeForGPU(ozone_param);
  gl::GLSurfaceTestSupport::InitializeOneOffImplementation(
      gl::GLImplementationParts(gl::kGLImplementationEGLGLES2));

  return RUN_ALL_TESTS();
}