chromium/media/gpu/android/pooled_shared_image_video_provider_unittest.cc

// 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/android/pooled_shared_image_video_provider.h"

#include <list>

#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "gpu/command_buffer/service/ref_counted_lock_for_test.h"
#include "gpu/config/gpu_finch_features.h"
#include "gpu/ipc/common/command_buffer_id.h"
#include "media/gpu/android/mock_shared_image_video_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::Mock;

namespace media {

class PooledSharedImageVideoProviderTest : public testing::Test {
 public:
  class MockGpuHelper : public PooledSharedImageVideoProvider::GpuHelper {
   public:
    MockGpuHelper(gpu::SyncToken* sync_token_out)
        : sync_token_out_(sync_token_out) {}

    // PooledSharedImageVideoProvider::GpuHelper
    void OnImageReturned(const gpu::SyncToken& sync_token,
                         scoped_refptr<CodecImageHolder> codec_image_holder,
                         base::OnceClosure cb,
                         scoped_refptr<gpu::RefCountedLock> lock) override {
      *sync_token_out_ = sync_token;

      // Run the output cb.
      std::move(cb).Run();
    }

   private:
    raw_ptr<gpu::SyncToken> sync_token_out_ = nullptr;
  };

  PooledSharedImageVideoProviderTest() = default;

  void SetUp() override {
    task_runner_ = base::SequencedTaskRunner::GetCurrentDefault();
    base::SequenceBound<MockGpuHelper> mock_gpu_helper(task_runner_,
                                                       &sync_token_);

    std::unique_ptr<MockSharedImageVideoProvider> mock_provider =
        std::make_unique<MockSharedImageVideoProvider>();
    mock_provider_raw_ = mock_provider.get();

    provider_ = base::WrapUnique(new PooledSharedImageVideoProvider(
        std::move(mock_gpu_helper), std::move(mock_provider),
        features::NeedThreadSafeAndroidMedia()
            ? base::MakeRefCounted<gpu::RefCountedLockForTest>()
            : nullptr));
  }

  // Return an ImageReadyCB that saves the ImageRecord in |image_records_|.
  SharedImageVideoProvider::ImageReadyCB SaveImageRecordCB() {
    return base::BindOnce(
        [](std::list<SharedImageVideoProvider::ImageRecord>* output_list,
           SharedImageVideoProvider::ImageRecord record) {
          output_list->push_back(std::move(record));
        },
        &image_records_);
  }

  // Request an image from |provier_|, which we expect will call through to
  // |mock_provider_raw_|.  Have |mock_provider_raw_| return an image, too.
  void RequestAndProvideImage(const SharedImageVideoProvider::ImageSpec& spec) {
    EXPECT_CALL(*mock_provider_raw_, MockRequestImage()).Times(1);
    provider_->RequestImage(SaveImageRecordCB(), spec);
    base::RunLoop().RunUntilIdle();
    Mock::VerifyAndClearExpectations(mock_provider_raw_);
    mock_provider_raw_->ProvideOneRequestedImage();
  }

  base::test::TaskEnvironment task_environment_;

  scoped_refptr<base::SequencedTaskRunner> task_runner_;

  // Provider under test.
  std::unique_ptr<PooledSharedImageVideoProvider> provider_;

  raw_ptr<MockSharedImageVideoProvider> mock_provider_raw_ = nullptr;
  gpu::SyncToken sync_token_;

  scoped_refptr<gpu::TextureOwner> texture_owner_;

  // Image records that we've received from |provider|, via SaveImageRecordCB().
  std::list<SharedImageVideoProvider::ImageRecord> image_records_;
};

TEST_F(PooledSharedImageVideoProviderTest, InitializeForwardsGpuCallback) {
  bool was_called = false;
  auto gpu_init_cb = base::BindOnce(
      [](bool* flag, scoped_refptr<gpu::SharedContextState>) { *flag = true; },
      &was_called);
  provider_->Initialize(std::move(gpu_init_cb));
  std::move(mock_provider_raw_->gpu_init_cb_).Run(nullptr);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(was_called);
}

TEST_F(PooledSharedImageVideoProviderTest, RequestImageRequestsMultipleImages) {
  // Test the RequestImage will keep requesting images from the underlying
  // provider as long as we don't return any.
  SharedImageVideoProvider::ImageSpec spec(gfx::Size(1, 1), 0u);
  RequestAndProvideImage(spec);
  RequestAndProvideImage(spec);
  RequestAndProvideImage(spec);
  EXPECT_EQ(image_records_.size(), 3u);
}

TEST_F(PooledSharedImageVideoProviderTest, ReleasingAnImageForwardsSyncToken) {
  // Calling the release callback on an image should forward the sync token to
  // our gpu helper.
  SharedImageVideoProvider::ImageSpec spec(gfx::Size(1, 1), 0u);
  RequestAndProvideImage(spec);

  gpu::SyncToken sync_token(gpu::CommandBufferNamespace::GPU_IO,
                            gpu::CommandBufferIdFromChannelAndRoute(2, 3), 4);
  std::move(image_records_.back().release_cb).Run(sync_token);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(sync_token, sync_token_);
}

TEST_F(PooledSharedImageVideoProviderTest,
       ReleasingAnImageDoesntRunUnderlyingReleaseCallback) {
  // Verify that releasing an image doesn't call the underlying release callback
  // on it.  Presumably, it should be sent back to the pool instead.
  SharedImageVideoProvider::ImageSpec spec(gfx::Size(1, 1), 0u);
  RequestAndProvideImage(spec);

  // Release the image.
  image_records_.pop_back();
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(mock_provider_raw_->num_release_callbacks_, 0);
}

TEST_F(PooledSharedImageVideoProviderTest, RequestImageReusesReturnedImages) {
  // Test the RequestImage will return images without requesting new ones, if
  // some have been returned to the pool.
  SharedImageVideoProvider::ImageSpec spec(gfx::Size(1, 1), 0u);
  // Request two images.
  RequestAndProvideImage(spec);
  RequestAndProvideImage(spec);
  EXPECT_EQ(image_records_.size(), 2u);
  // Now return one, and request another.
  image_records_.pop_back();
  // Let the release CB run.
  base::RunLoop().RunUntilIdle();

  // Shouldn't call MockRequestImage a third time.
  EXPECT_CALL(*mock_provider_raw_, MockRequestImage()).Times(0);
  provider_->RequestImage(SaveImageRecordCB(), spec);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(image_records_.size(), 2u);
}

TEST_F(PooledSharedImageVideoProviderTest,
       DeletingProviderWithOutstandingImagesDoesntCrash) {
  // Destroying |provider_| with outstanding images shouldn't break anything.
  SharedImageVideoProvider::ImageSpec spec(gfx::Size(1, 1), 0u);
  provider_->RequestImage(SaveImageRecordCB(), spec);
  base::RunLoop().RunUntilIdle();
  provider_.reset();
  base::RunLoop().RunUntilIdle();
  // Shouldn't crash.
}

TEST_F(PooledSharedImageVideoProviderTest,
       ReturnedImagesAreReleasedAfterSpecChange) {
  // When we change the ImageSpec, old images should be released on the
  // underlying provider as they are returned.
  SharedImageVideoProvider::ImageSpec spec_1(gfx::Size(1, 1), 0u);
  SharedImageVideoProvider::ImageSpec spec_2(gfx::Size(1, 2), 0u);
  RequestAndProvideImage(spec_1);
  RequestAndProvideImage(spec_2);

  EXPECT_EQ(mock_provider_raw_->num_release_callbacks_, 0);

  // Return image 1, and it should run the underlying release callback since it
  // doesn't match the pool spec.
  image_records_.pop_front();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mock_provider_raw_->num_release_callbacks_, 1);

  // Returning image 2 should not, since it should be put into the pool.
  image_records_.pop_front();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mock_provider_raw_->num_release_callbacks_, 1);
}

TEST_F(PooledSharedImageVideoProviderTest, SizeChangeEmptiesPool) {
  // Verify that a size change in the ImageSpec causes the pool to be emptied.
  SharedImageVideoProvider::ImageSpec spec_1(gfx::Size(1, 1), 0u);
  SharedImageVideoProvider::ImageSpec spec_2(gfx::Size(1, 2), 0u);

  // Request an image with |spec_1| and release it, to send it to the pool.
  RequestAndProvideImage(spec_1);
  image_records_.pop_front();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mock_provider_raw_->num_release_callbacks_, 0);

  // Request an image with |spec_2|, which should release the first one.
  RequestAndProvideImage(spec_2);
  EXPECT_EQ(mock_provider_raw_->num_release_callbacks_, 1);
}

TEST_F(PooledSharedImageVideoProviderTest, GenerationIdChangeEmptiesPool) {
  // Verify that a change in the generation id causes the pool to be emptied.
  SharedImageVideoProvider::ImageSpec spec_1(gfx::Size(1, 1), 0u);
  SharedImageVideoProvider::ImageSpec spec_2(gfx::Size(1, 1), 1u);
  RequestAndProvideImage(spec_1);
  image_records_.pop_front();
  base::RunLoop().RunUntilIdle();
  RequestAndProvideImage(spec_2);
  EXPECT_EQ(mock_provider_raw_->num_release_callbacks_, 1);
}

TEST_F(PooledSharedImageVideoProviderTest, InFlightSpecChangeProvidesImage) {
  // If we change the ImageSpec between requesting and receiving an image from
  // the provider, it should still provide the image to the requestor.
  SharedImageVideoProvider::ImageSpec spec_1(gfx::Size(1, 1), 0u);
  SharedImageVideoProvider::ImageSpec spec_2(gfx::Size(1, 1), 1u);

  // Request both images before providing either.
  EXPECT_CALL(*mock_provider_raw_, MockRequestImage()).Times(2);
  provider_->RequestImage(SaveImageRecordCB(), spec_1);
  provider_->RequestImage(SaveImageRecordCB(), spec_2);
  base::RunLoop().RunUntilIdle();

  // Provide the |spec_1| image.  Nothing should be released since it should
  // fulfill the first request.
  mock_provider_raw_->ProvideOneRequestedImage();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mock_provider_raw_->num_release_callbacks_, 0);
  EXPECT_EQ(image_records_.size(), 1u);

  // Provide the |spec_2| image, which should also be provided to us.
  mock_provider_raw_->ProvideOneRequestedImage();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mock_provider_raw_->num_release_callbacks_, 0);
  EXPECT_EQ(image_records_.size(), 2u);

  // Drop the |spec_1| image, which should be released rather than added back to
  // the pool.
  image_records_.pop_front();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mock_provider_raw_->num_release_callbacks_, 1);

  // Drop the |spec_2| image, which should be pooled rather than released.
  image_records_.pop_front();
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(mock_provider_raw_->num_release_callbacks_, 1);
}

}  // namespace media