chromium/media/gpu/windows/d3d11_copying_texture_wrapper_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 <string.h>

#include <utility>

#include "base/functional/callback_helpers.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "media/gpu/windows/d3d11_copying_texture_wrapper.h"
#include "media/gpu/windows/d3d11_picture_buffer.h"
#include "media/gpu/windows/d3d11_texture_wrapper.h"
#include "media/gpu/windows/d3d11_video_processor_proxy.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::Bool;
using ::testing::Combine;
using ::testing::Return;
using ::testing::Values;

namespace media {

class MockVideoProcessorProxy : public VideoProcessorProxy {
 public:
  MockVideoProcessorProxy() : VideoProcessorProxy(nullptr, nullptr) {}

  D3D11Status Init(uint32_t width, uint32_t height) override {
    return MockInit(width, height);
  }

  HRESULT CreateVideoProcessorOutputView(
      ID3D11Texture2D* output_texture,
      D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC* output_view_descriptor,
      ID3D11VideoProcessorOutputView** output_view) override {
    return MockCreateVideoProcessorOutputView();
  }

  HRESULT CreateVideoProcessorInputView(
      ID3D11Texture2D* input_texture,
      D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC* input_view_descriptor,
      ID3D11VideoProcessorInputView** input_view) override {
    return MockCreateVideoProcessorInputView();
  }

  void SetStreamColorSpace(const gfx::ColorSpace& color_space) override {
    last_stream_color_space_ = color_space;
  }

  void SetOutputColorSpace(const gfx::ColorSpace& color_space) override {
    last_output_color_space_ = color_space;
  }

  HRESULT VideoProcessorBlt(ID3D11VideoProcessorOutputView* output_view,
                            UINT output_frameno,
                            UINT stream_count,
                            D3D11_VIDEO_PROCESSOR_STREAM* streams) override {
    return MockVideoProcessorBlt();
  }

  MOCK_METHOD2(MockInit, D3D11Status(uint32_t, uint32_t));
  MOCK_METHOD0(MockCreateVideoProcessorOutputView, HRESULT());
  MOCK_METHOD0(MockCreateVideoProcessorInputView, HRESULT());
  MOCK_METHOD0(MockVideoProcessorBlt, HRESULT());

  // Most recent arguments to SetStream/OutputColorSpace()/etc.
  std::optional<gfx::ColorSpace> last_stream_color_space_;
  std::optional<gfx::ColorSpace> last_output_color_space_;

 private:
  ~MockVideoProcessorProxy() override = default;
};

class MockTexture2DWrapper : public Texture2DWrapper {
 public:
  MockTexture2DWrapper() {}

  D3D11Status ProcessTexture(
      const gfx::ColorSpace& input_color_space,
      ClientSharedImageOrMailboxHolder& shared_image_dest) override {
    return MockProcessTexture();
  }

  D3D11Status Init(scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
                   GetCommandBufferHelperCB get_helper_cb,
                   ComD3D11Texture2D in_texture,
                   size_t array_slice,
                   scoped_refptr<media::D3D11PictureBuffer> picture_buffer,
                   PictureBufferGPUResourceInitDoneCB
                       picture_buffer_gpu_resource_init_done_cb) override {
    gpu_task_runner_ = std::move(gpu_task_runner);
    return MockInit();
  }

  D3D11Status BeginSharedImageAccess() override {
    return MockBeginSharedImageAccess();
  }

  MOCK_METHOD0(MockInit, D3D11Status());
  MOCK_METHOD0(MockProcessTexture, D3D11Status());
  MOCK_METHOD0(MockBeginSharedImageAccess, D3D11Status());

  scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner_;
};

CommandBufferHelperPtr UselessHelper() {
  return nullptr;
}

class D3D11CopyingTexture2DWrapperTest
    : public ::testing::TestWithParam<
          std::tuple<HRESULT, HRESULT, HRESULT, bool, bool, bool, bool>> {
 public:
#define FIELD(TYPE, NAME, INDEX) \
  TYPE Get##NAME() { return std::get<INDEX>(GetParam()); }
  FIELD(HRESULT, CreateVideoProcessorOutputView, 0)
  FIELD(HRESULT, CreateVideoProcessorInputView, 1)
  FIELD(HRESULT, VideoProcessorBlt, 2)
  FIELD(bool, ProcessorProxyInit, 3)
  FIELD(bool, TextureWrapperInit, 4)
  FIELD(bool, ProcessTexture, 5)
  FIELD(bool, PassthroughColorSpace, 6)
#undef FIELD

  void SetUp() override {
    gpu_task_runner_ = task_environment_.GetMainThreadTaskRunner();
  }

  scoped_refptr<MockVideoProcessorProxy> ExpectProcessorProxy() {
    auto result = base::MakeRefCounted<MockVideoProcessorProxy>();
    ON_CALL(*result.get(), MockInit(_, _))
        .WillByDefault(
            Return(GetProcessorProxyInit()
                       ? D3D11Status::Codes::kOk
                       : D3D11Status::Codes::kCreateVideoProcessorFailed));

    ON_CALL(*result.get(), MockCreateVideoProcessorOutputView())
        .WillByDefault(Return(GetCreateVideoProcessorOutputView()));

    ON_CALL(*result.get(), MockCreateVideoProcessorInputView())
        .WillByDefault(Return(GetCreateVideoProcessorInputView()));

    ON_CALL(*result.get(), MockVideoProcessorBlt())
        .WillByDefault(Return(GetVideoProcessorBlt()));

    return result;
  }

  std::unique_ptr<MockTexture2DWrapper> ExpectTextureWrapper() {
    auto result = std::make_unique<MockTexture2DWrapper>();

    ON_CALL(*result.get(), MockInit())
        .WillByDefault(
            Return(GetTextureWrapperInit()
                       ? D3D11Status::Codes::kOk
                       : D3D11Status::Codes::kCreateVideoProcessorFailed));

    ON_CALL(*result.get(), MockProcessTexture())
        .WillByDefault(Return(
            GetProcessTexture()
                ? D3D11Status::Codes::kOk
                : D3D11Status::Codes::kCreateVideoProcessorOutputViewFailed));

    return result;
  }

  GetCommandBufferHelperCB CreateMockHelperCB() {
    return base::BindRepeating(&UselessHelper);
  }

  bool InitSucceeds() {
    return GetProcessorProxyInit() && GetTextureWrapperInit();
  }

  bool ProcessTextureSucceeds() {
    return GetProcessTexture() &&
           SUCCEEDED(GetCreateVideoProcessorOutputView()) &&
           SUCCEEDED(GetCreateVideoProcessorInputView()) &&
           SUCCEEDED(GetVideoProcessorBlt());
  }

  base::test::TaskEnvironment task_environment_;
  scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner_;
};

INSTANTIATE_TEST_SUITE_P(CopyingTexture2DWrapperTest,
                         D3D11CopyingTexture2DWrapperTest,
                         Combine(Values(S_OK, E_FAIL),
                                 Values(S_OK, E_FAIL),
                                 Values(S_OK, E_FAIL),
                                 Bool(),
                                 Bool(),
                                 Bool(),
                                 Bool()));

// For ever potential return value combination for the D3D11VideoProcessor,
// make sure that any failures result in a total failure.
TEST_P(D3D11CopyingTexture2DWrapperTest,
       CopyingTextureWrapperProcessesCorrectly) {
  gfx::Size size;
  auto processor = ExpectProcessorProxy();
  MockVideoProcessorProxy* processor_raw = processor.get();
  auto texture_wrapper = ExpectTextureWrapper();
  MockTexture2DWrapper* texture_wrapper_raw = texture_wrapper.get();
  auto wrapper = std::make_unique<CopyingTexture2DWrapper>(
      size, std::move(texture_wrapper), processor, nullptr);

  // TODO: check |gpu_task_runner_|.

  ClientSharedImageOrMailboxHolder shared_image;
  gfx::ColorSpace input_color_space = gfx::ColorSpace::CreateSRGBLinear();
  EXPECT_EQ(
      wrapper
          ->Init(gpu_task_runner_, CreateMockHelperCB(),
                 /*texture=*/nullptr, /*array_slice=*/0,
                 /*picture_buffer=*/nullptr,
                 /*picture_buffer_gpu_resource_init_done_cb=*/base::DoNothing())
          .is_ok(),
      InitSucceeds());
  task_environment_.RunUntilIdle();
  if (GetProcessorProxyInit()) {
    EXPECT_EQ(texture_wrapper_raw->gpu_task_runner_, gpu_task_runner_);
  }
  EXPECT_EQ(wrapper->ProcessTexture(input_color_space, shared_image).is_ok(),
            ProcessTextureSucceeds());

  if (ProcessTextureSucceeds()) {
    // Also expect that the input and copy spaces were provided to the video
    // processor as the stream and output color spaces, respectively.
    EXPECT_TRUE(processor_raw->last_stream_color_space_);
    EXPECT_EQ(*processor_raw->last_stream_color_space_, input_color_space);
    EXPECT_TRUE(processor_raw->last_output_color_space_);
    EXPECT_EQ(*processor_raw->last_output_color_space_, input_color_space);
  }

  // TODO: verify that these aren't sent multiple times, unless they change.
}

}  // namespace media