910e62b5创建于 1月15日历史提交
// Copyright 2019 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/vaapi/test_utils.h"

#include <sys/mman.h>

#include <memory>

#include "base/bits.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "media/base/video_types.h"
#include "media/gpu/vaapi/vaapi_utils.h"
#include "third_party/libdrm/src/include/drm/drm_fourcc.h"
#include "third_party/libyuv/include/libyuv.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "media/gpu/test/test_gbm_buffer_manager.h"
#endif

namespace media {
namespace vaapi_test_utils {

#if BUILDFLAG(IS_CHROMEOS)

namespace {

// Credit to the Mesa project for writing extensive documentation on the Tile4
// format. https://docs.mesa3d.org/isl/tiling.html#tile4
//
// Tile4 has 2 levels of tiling. The "main" tiles are 8x8 squares of subtiles.
// Subtiles are 16x4 rectangles of pixels. Pixels within subtiles are laid out
// in raster order. Subtiles within tiles are laid out in a repeating "Z"
// pattern. The "Z"s are 4 subtiles wide and 2 subtiles tall. Main tiles within
// a plane are laid out in raster order.
//
// Subtiles are conveniently the size of one cache line, and tiles are
// conveniently the size of one (4K) page.
//
// This detiling algorithm prioritizes linear writes at the expense of very
// non-linear reads so that we take advantage of the write combiner. One low
// hanging fruit optimization might be to experiment with prefetching to help
// with the unusual memory read pattern.

constexpr int kTile4TileWidth = 8;
constexpr int kTile4TileHeight = 8;
constexpr int kTile4SubtileWidth = 16;
constexpr int kTile4SubtileHeight = 4;
constexpr int kTile4SubtileSizeBytes = kTile4SubtileWidth * kTile4SubtileHeight;
constexpr int kTile4TileWidthBytes = kTile4TileWidth * kTile4SubtileWidth;
constexpr int kTile4TileHeightBytes = kTile4TileHeight * kTile4SubtileHeight;

void Detile4(uint8_t* linear_dest,
             const uint8_t* tiled_src,
             int width,
             int height) {
  constexpr int kTile4TileSizeBytes =
      kTile4TileWidthBytes * kTile4TileHeightBytes;
  constexpr int kTile4ZWidth = 4;

  width = base::bits::AlignDownDeprecatedDoNotUse(width, kTile4TileWidthBytes);
  height =
      base::bits::AlignDownDeprecatedDoNotUse(height, kTile4TileHeightBytes);

  for (int y = 0; y < height; y += kTile4TileHeight * kTile4SubtileHeight) {
    for (int tile_y = 0; tile_y < kTile4TileHeight; tile_y++) {
      for (int subtile_y = 0; subtile_y < kTile4SubtileHeight; subtile_y++) {
        const uint8_t* row_ptr = tiled_src;
        for (int x = 0; x < width; x += kTile4TileWidth * kTile4SubtileWidth) {
          int tile_x = 0;
          // Copy 1 row from 4 subtiles.
          for (; tile_x < kTile4ZWidth; tile_x++) {
            UNSAFE_TODO(memcpy(linear_dest, row_ptr, kTile4SubtileWidth));
            UNSAFE_TODO(linear_dest += kTile4SubtileWidth);
            UNSAFE_TODO(row_ptr += kTile4SubtileSizeBytes);
          }
          UNSAFE_TODO(row_ptr += kTile4ZWidth * kTile4SubtileSizeBytes);
          // Copy 1 row from another 4 subtiles.
          for (; tile_x < kTile4TileWidth; tile_x++) {
            UNSAFE_TODO(memcpy(linear_dest, row_ptr, kTile4SubtileWidth));
            UNSAFE_TODO(linear_dest += kTile4SubtileWidth);
            UNSAFE_TODO(row_ptr += kTile4SubtileSizeBytes);
          }

          // Advance to the tile to the right.
          UNSAFE_TODO(row_ptr += kTile4TileSizeBytes -
                                 (3 * kTile4ZWidth * kTile4SubtileSizeBytes));
        }

        // Advance to the next row in the subtile.
        UNSAFE_TODO(tiled_src += kTile4SubtileWidth);
      }

      // Advance to the next row in the tile.
      if (tile_y % 2 == 0) {
        UNSAFE_TODO(tiled_src += kTile4ZWidth * kTile4SubtileSizeBytes -
                                 kTile4SubtileSizeBytes);
      } else {
        UNSAFE_TODO(tiled_src += 3 * kTile4ZWidth * kTile4SubtileSizeBytes -
                                 kTile4SubtileSizeBytes);
      }
    }

    // Advance to the tile below.
    UNSAFE_TODO(tiled_src += width * kTile4TileHeight * kTile4SubtileHeight -
                             kTile4TileSizeBytes);
  }
}

}  // namespace

#endif

DecodedImage::~DecodedImage() = default;

std::string TestParamToString(
    const testing::TestParamInfo<TestParam>& param_info) {
  return param_info.param.test_name;
}

#if BUILDFLAG(IS_CHROMEOS)

DecodedImage ScopedVAImageToDecodedImage(const ScopedVAImage* scoped_va_image) {
  DecodedImage decoded_image{};

  decoded_image.fourcc = scoped_va_image->image()->format.fourcc;
  decoded_image.number_of_planes = scoped_va_image->image()->num_planes;
  decoded_image.size =
      gfx::Size(base::strict_cast<int>(scoped_va_image->image()->width),
                base::strict_cast<int>(scoped_va_image->image()->height));

  DCHECK_LE(base::strict_cast<size_t>(decoded_image.number_of_planes),
            kMaxNumberPlanes);

  // This is safe because |number_of_planes| is retrieved from the VA-API and it
  // can not be greater than 3, which is also the size of the |planes| array.
  for (uint32_t i = 0u; i < decoded_image.number_of_planes; ++i) {
    UNSAFE_TODO(
        decoded_image.planes[i].data =
            static_cast<uint8_t*>(scoped_va_image->va_buffer()->data()) +
            scoped_va_image->image()->offsets[i]);
    UNSAFE_TODO(decoded_image.planes[i].stride = base::checked_cast<int>(
                    scoped_va_image->image()->pitches[i]));
  }

  return decoded_image;
}

class NativePixmapMapping {
 public:
  virtual ~NativePixmapMapping() = default;
  virtual raw_ptr<uint8_t> GetData(size_t plane_idx) const = 0;
  virtual int GetStride(size_t plane_idx) const = 0;
  virtual gfx::Size GetSize() const = 0;
};

class GbmBufferMapping : public NativePixmapMapping {
 public:
  static std::unique_ptr<GbmBufferMapping> CreateGbmBufferMapping(
      gfx::NativePixmapHandle& handle,
      const gfx::Size& size,
      viz::SharedImageFormat format) {
    std::unique_ptr<TestGbmBufferManager> gbm_buffer_manager =
        std::make_unique<TestGbmBufferManager>();
    std::unique_ptr<TestGbmBuffer> gbm_buffer =
        gbm_buffer_manager->ImportDmaBuf(handle, size, format);

    if (!gbm_buffer->Map()) {
      LOG(ERROR) << "Failed to map GBM buffer!";
      return nullptr;
    }

    return std::make_unique<GbmBufferMapping>(std::move(gbm_buffer_manager),
                                              std::move(gbm_buffer));
  }

  GbmBufferMapping(std::unique_ptr<TestGbmBufferManager> gbm_buffer_manager,
                   std::unique_ptr<TestGbmBuffer> gbm_buffer)
      : gbm_buffer_manager_(std::move(gbm_buffer_manager)),
        gbm_buffer_(std::move(gbm_buffer)) {}

  ~GbmBufferMapping() override { gbm_buffer_->Unmap(); }

  raw_ptr<uint8_t> GetData(size_t plane_idx) const override {
    return static_cast<uint8_t*>(gbm_buffer_->memory(plane_idx));
  }

  int GetStride(size_t plane_idx) const override {
    return gbm_buffer_->stride(plane_idx);
  }

  gfx::Size GetSize() const override { return gbm_buffer_->GetSize(); }

 private:
  // It's very important these two objects are initialized in this order,
  // because C++ guarantees they will be destroyed in the reverse order.
  // Unfortunately, the destructor for TestGbmBuffer calls the GBM
  // device that gets destroyed by the TestGbmBufferManager destructor,
  // so there is an order we need to do this in to prevent a segfault.
  const std::unique_ptr<TestGbmBufferManager> gbm_buffer_manager_;
  const std::unique_ptr<TestGbmBuffer> gbm_buffer_;
};

class Tile4Mapping : public NativePixmapMapping {
 public:
  static std::unique_ptr<Tile4Mapping> CreateTile4Mapping(
      gfx::NativePixmapHandle& handle,
      const gfx::Size& size,
      viz::SharedImageFormat format) {
    size_t plane_strides[2];
    size_t plane_sizes[2];
    uint8_t* plane_addrs[2];

    int aligned_width = base::bits::AlignUp(
        handle.planes[0].stride, static_cast<uint32_t>(kTile4TileWidthBytes));
    int aligned_height = base::bits::AlignUpDeprecatedDoNotUse(
        size.height(), kTile4TileHeightBytes);
    plane_strides[0] = aligned_width;
    plane_sizes[0] = aligned_height * aligned_width;

    aligned_width = base::bits::AlignUp(
        handle.planes[1].stride, static_cast<uint32_t>(kTile4TileWidthBytes));
    aligned_height = base::bits::AlignUpDeprecatedDoNotUse(
        size.height() / 2, kTile4TileHeightBytes);
    plane_strides[1] = aligned_width;
    plane_sizes[1] = aligned_height * aligned_width;

    // minigbm doesn't support Tile4 mappings, so we tell it to perform the
    // mapping as if the buffer were linear to work around this limitation.
    CHECK_EQ(handle.modifier, I915_FORMAT_MOD_4_TILED);
    handle.modifier = gfx::NativePixmapHandle::kNoModifier;

    TestGbmBufferManager gbm_buffer_manager;
    std::unique_ptr<TestGbmBuffer> gbm_buffer =
        gbm_buffer_manager.ImportDmaBuf(handle, size, format);

    if (!gbm_buffer->Map()) {
      LOG(ERROR) << "Failed to map GBM buffer!";
      return nullptr;
    }

    CHECK_EQ(handle.planes.size(), 2u);
    for (size_t plane_idx = 0; plane_idx < handle.planes.size(); plane_idx++) {
      int width = UNSAFE_TODO(plane_strides[plane_idx]);
      int height = UNSAFE_TODO(plane_sizes[plane_idx]) / width;
      const uint8_t* src = static_cast<uint8_t*>(gbm_buffer->memory(plane_idx));
      uint8_t* dest = static_cast<uint8_t*>(
          mmap(nullptr, UNSAFE_TODO(plane_sizes[plane_idx]),
               PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
      if (dest == MAP_FAILED) {
        PLOG(ERROR) << "Failed to create detiled mapping!";
        return nullptr;
      }

      Detile4(dest, src, width, height);

      UNSAFE_TODO(plane_addrs[plane_idx]) = dest;

      // We don't want to give the user the impression that this mapping is
      // bidirectional. We are performing a one-off detile operation to allow
      // this Tile4 buffer to be read, but we have no way of propagating writes
      // from our temporary linear buffer to the underlying Tile4 buffer. So, we
      // mark these pages as read only.
      if (mprotect(dest, UNSAFE_TODO(plane_sizes[plane_idx]), PROT_READ)) {
        PLOG(ERROR) << "Failed to mark detiled mapping read only!";
        return nullptr;
      }
    }

    gbm_buffer->Unmap();

    handle.modifier = I915_FORMAT_MOD_4_TILED;

    return std::make_unique<Tile4Mapping>(size, plane_strides, plane_sizes,
                                          plane_addrs);
  }

  Tile4Mapping(gfx::Size size,
               size_t plane_strides[2],
               size_t plane_sizes[2],
               uint8_t* plane_addrs[2])
      : size_(size),
        plane_strides_{UNSAFE_TODO(plane_strides[0], plane_strides[1])},
        plane_sizes_{UNSAFE_TODO(plane_sizes[0], plane_sizes[1])},
        plane_addrs_{UNSAFE_TODO(plane_addrs[0], plane_addrs[1])} {}

  ~Tile4Mapping() override {
    for (size_t plane_idx = 0; plane_idx < std::size(plane_addrs_);
         plane_idx++) {
      munmap(static_cast<void*>(UNSAFE_TODO(plane_addrs_[plane_idx])),
             UNSAFE_TODO(plane_sizes_[plane_idx]));
    }
  }

  raw_ptr<uint8_t> GetData(size_t plane_idx) const override {
    if (plane_idx >= std::size(plane_addrs_)) {
      return nullptr;
    }

    return UNSAFE_TODO(plane_addrs_[plane_idx]);
  }

  int GetStride(size_t plane_idx) const override {
    if (plane_idx >= std::size(plane_strides_)) {
      return -1;
    }

    return UNSAFE_TODO(plane_strides_[plane_idx]);
  }

  gfx::Size GetSize() const override { return size_; }

 private:
  const gfx::Size size_;
  const size_t plane_strides_[2];
  const size_t plane_sizes_[2];
  uint8_t* const plane_addrs_[2];
};

std::unique_ptr<NativePixmapMapping> CreateNativePixmapMapping(
    gfx::NativePixmapHandle& handle,
    const gfx::Size& size,
    viz::SharedImageFormat format) {
  if (handle.modifier == I915_FORMAT_MOD_4_TILED) {
    return Tile4Mapping::CreateTile4Mapping(handle, size, format);
  }

  return GbmBufferMapping::CreateGbmBufferMapping(handle, size, format);
}

struct NativePixmapDecodedImage : public DecodedImage {
 public:
  NativePixmapDecodedImage(const uint32_t fourcc,
                           const uint32_t number_of_planes,
                           const gfx::Size& size,
                           std::unique_ptr<NativePixmapMapping> mapping)
      : mapping_(std::move(mapping)) {
    this->fourcc = fourcc;
    this->number_of_planes = number_of_planes;
    this->size = size;

    for (size_t plane_idx = 0; plane_idx < number_of_planes; plane_idx++) {
      UNSAFE_TODO(planes[plane_idx].data) = mapping_->GetData(plane_idx);
      UNSAFE_TODO(planes[plane_idx].stride) = mapping_->GetStride(plane_idx);
    }
  }

 private:
  const std::unique_ptr<NativePixmapMapping> mapping_;
};

std::unique_ptr<DecodedImage> NativePixmapToDecodedImage(
    gfx::NativePixmapHandle& handle,
    const gfx::Size& size,
    const viz::SharedImageFormat& format) {
  uint32_t fourcc;
  uint32_t number_of_planes;
  if (format == viz::MultiPlaneFormat::kYV12) {
    fourcc = VA_FOURCC_I420;
    number_of_planes = 3;
  } else if (format == viz::MultiPlaneFormat::kNV12) {
    fourcc = VA_FOURCC_NV12;
    number_of_planes = 2;
  } else {
    LOG(ERROR) << "Unsupported format " << format.ToString();
    return nullptr;
  }

  std::unique_ptr<NativePixmapMapping> mapping =
      CreateNativePixmapMapping(handle, size, format);

  if (!mapping) {
    LOG(ERROR) << "Failed to create NativePixmapMapping";
    return nullptr;
  }

  return std::make_unique<NativePixmapDecodedImage>(fourcc, number_of_planes,
                                                    size, std::move(mapping));
}

#endif

bool CompareImages(const DecodedImage& reference_image,
                   const DecodedImage& hw_decoded_image,
                   double min_ssim) {
  if (reference_image.fourcc != VA_FOURCC_I420)
    return false;

  // Uses the reference image's size as the ground truth.
  const gfx::Size image_size = reference_image.size;
  if (image_size != hw_decoded_image.size) {
    LOG(ERROR) << "Wrong expected software decoded image size, "
               << image_size.ToString() << " versus VaAPI provided "
               << hw_decoded_image.size.ToString();
    return false;
  }

  double ssim = 0;
  const uint32_t hw_fourcc = hw_decoded_image.fourcc;
  if (hw_fourcc == VA_FOURCC_I420) {
    ssim = libyuv::I420Ssim(
        reference_image.planes[0].data, reference_image.planes[0].stride,
        reference_image.planes[1].data, reference_image.planes[1].stride,
        reference_image.planes[2].data, reference_image.planes[2].stride,
        hw_decoded_image.planes[0].data, hw_decoded_image.planes[0].stride,
        hw_decoded_image.planes[1].data, hw_decoded_image.planes[1].stride,
        hw_decoded_image.planes[2].data, hw_decoded_image.planes[2].stride,
        image_size.width(), image_size.height());
  } else if (hw_fourcc == VA_FOURCC_NV12 || hw_fourcc == VA_FOURCC_YUY2 ||
             hw_fourcc == VA_FOURCC('Y', 'U', 'Y', 'V')) {
    // Calculate the stride for the chroma planes.
    const gfx::Size half_image_size((image_size.width() + 1) / 2,
                                    (image_size.height() + 1) / 2);
    // Temporary planes to hold intermediate conversions to I420 (i.e. NV12 to
    // I420 or YUYV/2 to I420).
    auto temp_y = std::make_unique<uint8_t[]>(image_size.GetArea());
    auto temp_u = std::make_unique<uint8_t[]>(half_image_size.GetArea());
    auto temp_v = std::make_unique<uint8_t[]>(half_image_size.GetArea());
    int conversion_result = -1;

    if (hw_fourcc == VA_FOURCC_NV12) {
      conversion_result = libyuv::NV12ToI420(
          hw_decoded_image.planes[0].data, hw_decoded_image.planes[0].stride,
          hw_decoded_image.planes[1].data, hw_decoded_image.planes[1].stride,
          temp_y.get(), image_size.width(), temp_u.get(),
          half_image_size.width(), temp_v.get(), half_image_size.width(),
          image_size.width(), image_size.height());
    } else {
      // |hw_fourcc| is YUY2 or YUYV, which are handled the same.
      // TODO(crbug.com/40586948): support other formats/planarities/pitches.
      conversion_result = libyuv::YUY2ToI420(
          hw_decoded_image.planes[0].data, hw_decoded_image.planes[0].stride,
          temp_y.get(), image_size.width(), temp_u.get(),
          half_image_size.width(), temp_v.get(), half_image_size.width(),
          image_size.width(), image_size.height());
    }
    if (conversion_result != 0) {
      LOG(ERROR) << "libyuv conversion error";
      return false;
    }

    ssim = libyuv::I420Ssim(
        reference_image.planes[0].data, reference_image.planes[0].stride,
        reference_image.planes[1].data, reference_image.planes[1].stride,
        reference_image.planes[2].data, reference_image.planes[2].stride,
        temp_y.get(), image_size.width(), temp_u.get(), half_image_size.width(),
        temp_v.get(), half_image_size.width(), image_size.width(),
        image_size.height());
  } else {
    LOG(ERROR) << "HW FourCC not supported: " << FourccToString(hw_fourcc);
    return false;
  }

  if (ssim < min_ssim) {
    LOG(ERROR) << "SSIM too low: " << ssim << " < " << min_ssim;
    return false;
  }

  return true;
}

}  // namespace vaapi_test_utils
}  // namespace media