text: // Copyright 2021 The Chromium 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 "third_party/blink/renderer/platform/graphics/video_frame_image_util.h"
  
  #include "base/callback_helpers.h"
  #include "base/test/task_environment.h"
  #include "build/build_config.h"
  #include "components/viz/common/gpu/raster_context_provider.h"
  #include "components/viz/test/test_context_provider.h"
  #include "gpu/command_buffer/common/capabilities.h"
  #include "media/base/video_frame.h"
  #include "media/renderers/shared_image_video_frame_test_utils.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
  #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
  #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
  #include "third_party/blink/renderer/platform/graphics/test/gpu_test_utils.h"
  #include "third_party/blink/renderer/platform/testing/video_frame_utils.h"
  
  namespace blink {
  
  namespace {
  
  constexpr auto kTestSize = gfx::Size(64, 64);
  
  class ScopedFakeGpuContext {
   public:
    explicit ScopedFakeGpuContext(bool disable_imagebitmap) {
      SharedGpuContext::ResetForTesting();
      test_context_provider_ = viz::TestContextProvider::Create();
  
      if (disable_imagebitmap) {
        // Disable CanvasResourceProvider using GPU.
        auto& feature_info = test_context_provider_->GetWritableGpuFeatureInfo();
        feature_info.enabled_gpu_driver_bug_workarounds.push_back(
            DISABLE_IMAGEBITMAP_FROM_VIDEO_USING_GPU);
      }
  
      InitializeSharedGpuContext(test_context_provider_.get());
    }
  
    scoped_refptr<viz::ContextProvider> context_provider() const {
      return test_context_provider_;
    }
  
    viz::RasterContextProvider* raster_context_provider() const {
      return test_context_provider_.get();
    }
  
    ~ScopedFakeGpuContext() {
      task_environment_.RunUntilIdle();
      SharedGpuContext::ResetForTesting();
    }
  
   private:
    base::test::SingleThreadTaskEnvironment task_environment_;
    scoped_refptr<viz::TestContextProvider> test_context_provider_;
  };
  
  // TODO(crbug.com/1186864): Remove |expect_broken_tagging| when fixed.
  void TestOrientation(scoped_refptr<media::VideoFrame> frame,
                       bool expect_broken_tagging = false) {
    constexpr auto kTestTransform =
        media::VideoTransformation(media::VIDEO_ROTATION_90, /*mirrored=*/true);
    constexpr auto kTestOrientation = ImageOrientationEnum::kOriginLeftTop;
  
    frame->metadata().transformation = kTestTransform;
    auto image =
        CreateImageFromVideoFrame(frame, true, nullptr, nullptr, gfx::Rect(),
                                  /*prefer_tagged_orientation=*/true);
    if (expect_broken_tagging)
      EXPECT_EQ(image->CurrentFrameOrientation(), ImageOrientationEnum::kDefault);
    else
      EXPECT_EQ(image->CurrentFrameOrientation(), kTestOrientation);
  
    image = CreateImageFromVideoFrame(frame, true, nullptr, nullptr, gfx::Rect(),
                                      /*prefer_tagged_orientation=*/false);
    EXPECT_EQ(image->CurrentFrameOrientation(), ImageOrientationEnum::kDefault);
  }
  
  }  // namespace
  
  TEST(VideoFrameImageUtilTest, VideoTransformationToFromImageOrientation) {
    for (int i = 0; i < static_cast<int>(ImageOrientationEnum::kMaxValue); ++i) {
      auto blink_orientation = ImageOrientation::FromEXIFValue(i).Orientation();
      auto media_transform =
          ImageOrientationToVideoTransformation(blink_orientation);
      EXPECT_EQ(blink_orientation,
                VideoTransformationToImageOrientation(media_transform));
    }
  }
  
  TEST(VideoFrameImageUtilTest, WillCreateAcceleratedImagesFromVideoFrame) {
    // I420A isn't a supported zero copy format.
    {
      auto alpha_frame = media::VideoFrame::CreateTransparentFrame(kTestSize);
      EXPECT_FALSE(WillCreateAcceleratedImagesFromVideoFrame(alpha_frame.get()));
    }
  
    // Software RGB frames aren't supported.
    {
      auto cpu_frame = CreateTestFrame(kTestSize, gfx::Rect(kTestSize), kTestSize,
                                       media::VideoFrame::STORAGE_OWNED_MEMORY,
                                       media::PIXEL_FORMAT_XRGB);
      EXPECT_FALSE(WillCreateAcceleratedImagesFromVideoFrame(cpu_frame.get()));
    }
  
    // GpuMemoryBuffer frames aren't supported.
    {
      auto cpu_frame = CreateTestFrame(
          kTestSize, gfx::Rect(kTestSize), kTestSize,
          media::VideoFrame::STORAGE_GPU_MEMORY_BUFFER, media::PIXEL_FORMAT_XRGB);
      EXPECT_FALSE(WillCreateAcceleratedImagesFromVideoFrame(cpu_frame.get()));
    }
  
    // Single mailbox shared images should be supported except on Android.
    {
      auto shared_image_frame = CreateTestFrame(
          kTestSize, gfx::Rect(kTestSize), kTestSize,
          media::VideoFrame::STORAGE_OPAQUE, media::PIXEL_FORMAT_XRGB);
      EXPECT_EQ(shared_image_frame->NumTextures(), 1u);
      EXPECT_TRUE(shared_image_frame->mailbox_holder(0).mailbox.IsSharedImage());
  #if defined(OS_ANDROID)
      EXPECT_FALSE(
          WillCreateAcceleratedImagesFromVideoFrame(shared_image_frame.get()));
  #else
      EXPECT_TRUE(
          WillCreateAcceleratedImagesFromVideoFrame(shared_image_frame.get()));
  #endif
    }
  }
  
  // Android doesn't support zero copy images.
  #if !defined(OS_ANDROID)
  TEST(VideoFrameImageUtilTest, CreateImageFromVideoFrameZeroCopy) {
    ScopedFakeGpuContext fake_context(/*disable_imagebitmap=*/false);
    auto shared_image_frame = CreateTestFrame(
        kTestSize, gfx::Rect(kTestSize), kTestSize,
        media::VideoFrame::STORAGE_OPAQUE, media::PIXEL_FORMAT_XRGB);
    EXPECT_EQ(shared_image_frame->NumTextures(), 1u);
    EXPECT_TRUE(shared_image_frame->mailbox_holder(0).mailbox.IsSharedImage());
  
    auto image = CreateImageFromVideoFrame(shared_image_frame);
    ASSERT_TRUE(image->IsTextureBacked());
    EXPECT_EQ(memcmp(image->GetMailboxHolder().mailbox.name,
                     shared_image_frame->mailbox_holder(0).mailbox.name,
                     sizeof(gpu::Mailbox::Name)),
              0);
  }
  #endif
  
  TEST(VideoFrameImageUtilTest, CreateImageFromVideoFrameSoftwareFrame) {
    base::test::SingleThreadTaskEnvironment task_environment_;
    auto cpu_frame = CreateTestFrame(kTestSize, gfx::Rect(kTestSize), kTestSize,
                                     media::VideoFrame::STORAGE_OWNED_MEMORY,
                                     media::PIXEL_FORMAT_XRGB);
    auto image = CreateImageFromVideoFrame(cpu_frame);
    EXPECT_FALSE(image->IsTextureBacked());
  
    TestOrientation(cpu_frame);
    task_environment_.RunUntilIdle();
  }
  
  TEST(VideoFrameImageUtilTest, CreateImageFromVideoFrameGpuMemoryBufferFrame) {
    base::test::SingleThreadTaskEnvironment task_environment_;
    auto cpu_frame = CreateTestFrame(kTestSize, gfx::Rect(kTestSize), kTestSize,
                                     media::VideoFrame::STORAGE_GPU_MEMORY_BUFFER,
                                     media::PIXEL_FORMAT_NV12);
    auto image = CreateImageFromVideoFrame(cpu_frame);
    ASSERT_FALSE(image->IsTextureBacked());
    task_environment_.RunUntilIdle();
  }
  
  TEST(VideoFrameImageUtilTest, CreateImageFromVideoFrameTextureFrame) {
    base::test::SingleThreadTaskEnvironment task_environment_;
    auto cpu_frame = CreateTestFrame(kTestSize, gfx::Rect(kTestSize), kTestSize,
                                     media::VideoFrame::STORAGE_OPAQUE,
                                     media::PIXEL_FORMAT_NV12);
    auto image = CreateImageFromVideoFrame(cpu_frame);
  
    // An unaccelerated image can't be created from a texture based VideoFrame
    // without a viz::RasterContextProvider.
    ASSERT_FALSE(image);
    task_environment_.RunUntilIdle();
  }
  
  TEST(VideoFrameImageUtilTest,
       CreateAcceleratedImageFromVideoFrameBasicSoftwareFrame) {
    ScopedFakeGpuContext fake_context(/*disable_imagebitmap=*/false);
    auto cpu_frame = CreateTestFrame(kTestSize, gfx::Rect(kTestSize), kTestSize,
                                     media::VideoFrame::STORAGE_OWNED_MEMORY,
                                     media::PIXEL_FORMAT_XRGB);
    auto image = CreateImageFromVideoFrame(cpu_frame);
    ASSERT_TRUE(image->IsTextureBacked());
  }
  
  TEST(VideoFrameImageUtilTest, CreateAcceleratedImageFromGpuMemoryBufferFrame) {
    ScopedFakeGpuContext fake_context(/*disable_imagebitmap=*/false);
    auto gmb_frame = CreateTestFrame(kTestSize, gfx::Rect(kTestSize), kTestSize,
                                     media::VideoFrame::STORAGE_GPU_MEMORY_BUFFER,
                                     media::PIXEL_FORMAT_NV12);
    auto image = CreateImageFromVideoFrame(gmb_frame);
    ASSERT_TRUE(image->IsTextureBacked());
    TestOrientation(gmb_frame, /*expect_broken_tagging=*/true);
  }
  
  TEST(VideoFrameImageUtilTest, CreateAcceleratedImageFromTextureFrame) {
    ScopedFakeGpuContext fake_context(/*disable_imagebitmap=*/false);
  
    auto texture_frame = media::CreateSharedImageRGBAFrame(
        fake_context.context_provider(), kTestSize, gfx::Rect(kTestSize),
        base::DoNothing::Once());
    auto image = CreateImageFromVideoFrame(texture_frame,
                                           /*allow_zero_copy_images=*/false);
    ASSERT_TRUE(image->IsTextureBacked());
    TestOrientation(texture_frame, /*expect_broken_tagging=*/true);
  }
  
  TEST(VideoFrameImageUtilTest, FlushedAcceleratedImage) {
    ScopedFakeGpuContext fake_context(/*disable_imagebitmap=*/false);
  
    auto texture_frame = media::CreateSharedImageRGBAFrame(
        fake_context.context_provider(), kTestSize, gfx::Rect(kTestSize),
        base::DoNothing::Once());
  
    auto* raster_context_provider = fake_context.raster_context_provider();
    ASSERT_TRUE(raster_context_provider);
  
    auto provider = CreateResourceProviderForVideoFrame(IntSize(kTestSize),
                                                        raster_context_provider);
    ASSERT_TRUE(provider);
    EXPECT_TRUE(provider->IsAccelerated());
  
    auto image = CreateImageFromVideoFrame(texture_frame,
                                           /*allow_zero_copy_images=*/false,
                                           provider.get());
    EXPECT_TRUE(image->IsTextureBacked());
  
    image = CreateImageFromVideoFrame(texture_frame,
                                      /*allow_zero_copy_images=*/false,
                                      provider.get());
    EXPECT_TRUE(image->IsTextureBacked());
  
    ASSERT_FALSE(provider->needs_flush());
    ASSERT_FALSE(provider->HasRecordedDrawOps());
  }
  
  TEST(VideoFrameImageUtilTest, SoftwareCreateResourceProviderForVideoFrame) {
    // Creating a provider with a null viz::RasterContextProvider should result in
    // a non-accelerated provider being created.
    auto provider =
        CreateResourceProviderForVideoFrame(IntSize(kTestSize), nullptr);
    ASSERT_TRUE(provider);
    EXPECT_FALSE(provider->IsAccelerated());
  }
  
  TEST(VideoFrameImageUtilTest, AcceleratedCreateResourceProviderForVideoFrame) {
    ScopedFakeGpuContext fake_context(/*disable_imagebitmap=*/false);
    ASSERT_TRUE(SharedGpuContext::IsGpuCompositingEnabled());
  
    auto* raster_context_provider = fake_context.raster_context_provider();
    ASSERT_TRUE(raster_context_provider);
  
    // Creating a provider with a null viz::RasterContextProvider should result in
    // a non-accelerated provider being created.
    {
      auto provider =
          CreateResourceProviderForVideoFrame(IntSize(kTestSize), nullptr);
      ASSERT_TRUE(provider);
      EXPECT_FALSE(provider->IsAccelerated());
    }
  
    // Creating a provider with a real raster context provider should result in
    // an accelerated provider being created.
    {
      auto provider = CreateResourceProviderForVideoFrame(
          IntSize(kTestSize), raster_context_provider);
      ASSERT_TRUE(provider);
      EXPECT_TRUE(provider->IsAccelerated());
    }
  }
  
  TEST(VideoFrameImageUtilTest, WorkaroundCreateResourceProviderForVideoFrame) {
    ScopedFakeGpuContext fake_context(/*disable_imagebitmap=*/true);
    ASSERT_TRUE(SharedGpuContext::IsGpuCompositingEnabled());
  
    auto* raster_context_provider = fake_context.raster_context_provider();
    ASSERT_TRUE(raster_context_provider);
  
    // Creating a provider with a real raster context provider should result in
    // an unaccelerated provider being created due to the workaround.
    {
      auto provider = CreateResourceProviderForVideoFrame(
          IntSize(kTestSize), raster_context_provider);
      ASSERT_TRUE(provider);
      EXPECT_FALSE(provider->IsAccelerated());
    }
  }
  
  TEST(VideoFrameImageUtilTest, DestRectWithoutCanvasResourceProvider) {
    base::test::SingleThreadTaskEnvironment task_environment_;
    auto cpu_frame = CreateTestFrame(kTestSize, gfx::Rect(kTestSize), kTestSize,
                                     media::VideoFrame::STORAGE_OWNED_MEMORY,
                                     media::PIXEL_FORMAT_XRGB);
  
    // A CanvasResourceProvider must be provided with a custom destination rect.
    auto image = CreateImageFromVideoFrame(cpu_frame, true, nullptr, nullptr,
                                           gfx::Rect(0, 0, 10, 10));
    ASSERT_FALSE(image);
    task_environment_.RunUntilIdle();
  }
  
  TEST(VideoFrameImageUtilTest, CanvasResourceProviderTooSmallForDestRect) {
    base::test::SingleThreadTaskEnvironment task_environment_;
    auto cpu_frame = CreateTestFrame(kTestSize, gfx::Rect(kTestSize), kTestSize,
                                     media::VideoFrame::STORAGE_OWNED_MEMORY,
                                     media::PIXEL_FORMAT_XRGB);
  
    auto provider =
        CreateResourceProviderForVideoFrame(IntSize(gfx::Size(16, 16)), nullptr);
    ASSERT_TRUE(provider);
    EXPECT_FALSE(provider->IsAccelerated());
  
    auto image = CreateImageFromVideoFrame(cpu_frame, true, provider.get(),
                                           nullptr, gfx::Rect(kTestSize));
    ASSERT_FALSE(image);
    task_environment_.RunUntilIdle();
  }
  
  TEST(VideoFrameImageUtilTest, CanvasResourceProviderDestRect) {
    base::test::SingleThreadTaskEnvironment task_environment_;
    auto cpu_frame = CreateTestFrame(kTestSize, gfx::Rect(kTestSize), kTestSize,
                                     media::VideoFrame::STORAGE_OWNED_MEMORY,
                                     media::PIXEL_FORMAT_XRGB);
  
    auto provider = CreateResourceProviderForVideoFrame(
        IntSize(gfx::Size(128, 128)), nullptr);
    ASSERT_TRUE(provider);
    EXPECT_FALSE(provider->IsAccelerated());
  
    auto image = CreateImageFromVideoFrame(cpu_frame, true, provider.get(),
                                           nullptr, gfx::Rect(16, 16, 64, 64));
    ASSERT_TRUE(image);
    task_environment_.RunUntilIdle();
  }
  
  }  // namespace blink
  