chromium/media/gpu/windows/d3d12_video_processor_wrapper.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/windows/d3d12_video_processor_wrapper.h"

#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "media/gpu/windows/d3d12_helpers.h"
#include "ui/gfx/color_space_win.h"

namespace media {

D3D12VideoProcessorWrapper::D3D12VideoProcessorWrapper(
    ComD3D12VideoDevice video_device)
    : video_device_(video_device) {
  CHECK_EQ(video_device.As(&device_), S_OK);
}

D3D12VideoProcessorWrapper::~D3D12VideoProcessorWrapper() = default;

bool D3D12VideoProcessorWrapper::Init() {
  D3D12_COMMAND_QUEUE_DESC command_queue_desc{
      D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS};
  HRESULT hr = device_->CreateCommandQueue(&command_queue_desc,
                                           IID_PPV_ARGS(&command_queue_));
  if (FAILED(hr)) {
    DLOG(ERROR) << "Create video process command queue failed: "
                << logging::SystemErrorCodeToString(hr);
    return false;
  }

  hr = device_->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS,
                                       IID_PPV_ARGS(&command_allocator_));
  if (FAILED(hr)) {
    DLOG(ERROR) << "Create video process command allocator failed: "
                << logging::SystemErrorCodeToString(hr);
    return false;
  }

  hr = device_->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS,
                                  command_allocator_.Get(), nullptr,
                                  IID_PPV_ARGS(&command_list_));
  if (FAILED(hr)) {
    DLOG(ERROR) << "Create video process command list failed: "
                << logging::SystemErrorCodeToString(hr);
    return false;
  }

  hr = command_list_->Close();
  if (FAILED(hr)) {
    DLOG(ERROR) << "Close video process command list failed: "
                << logging::SystemErrorCodeToString(hr);
    return false;
  }

  fence_ = D3D12Fence::Create(device_.Get());
  if (!fence_) {
    DLOG(ERROR) << "Failed to create D3D12Fence";
    return false;
  }

  return true;
}

bool D3D12VideoProcessorWrapper::ProcessFrames(
    ID3D12Resource* input_texture,
    UINT input_subresource,
    const gfx::ColorSpace& input_color_space,
    const gfx::Rect& input_rectangle,
    ID3D12Resource* output_texture,
    UINT output_subresource,
    const gfx::ColorSpace& output_color_space,
    const gfx::Rect& output_rectangle) {
  DCHECK(command_queue_ && command_allocator_ && command_list_ && fence_);
  D3D12_RESOURCE_DESC input_texture_desc = input_texture->GetDesc();
  D3D12_RESOURCE_DESC output_texture_desc = output_texture->GetDesc();
  D3D12_VIDEO_SIZE_RANGE source_size_range{
      static_cast<UINT>(input_texture_desc.Width), input_texture_desc.Height,
      static_cast<UINT>(input_texture_desc.Width), input_texture_desc.Height};
  D3D12_VIDEO_SIZE_RANGE destination_size_range{
      static_cast<UINT>(output_texture_desc.Width), output_texture_desc.Height,
      static_cast<UINT>(output_texture_desc.Width), output_texture_desc.Height};
  D3D12_VIDEO_PROCESS_INPUT_STREAM_DESC input_stream_desc{
      .Format = input_texture_desc.Format,
      .ColorSpace = gfx::ColorSpaceWin::GetDXGIColorSpace(input_color_space),
      .FrameRate = {30, 1},
      .SourceSizeRange = source_size_range,
      .DestinationSizeRange = destination_size_range,
  };
  D3D12_VIDEO_PROCESS_OUTPUT_STREAM_DESC output_stream_desc{
      .Format = output_texture_desc.Format,
      .ColorSpace = gfx::ColorSpaceWin::GetDXGIColorSpace(output_color_space),
      .FrameRate = {30, 1},
  };

  HRESULT hr;
  if (memcmp(&input_stream_desc, &input_stream_desc_,
             sizeof(D3D12_VIDEO_PROCESS_INPUT_STREAM_DESC)) != 0 ||
      memcmp(&output_stream_desc, &output_stream_desc_,
             sizeof(D3D12_VIDEO_PROCESS_OUTPUT_STREAM_DESC)) != 0) {
    D3D12_FEATURE_DATA_VIDEO_PROCESS_SUPPORT support{
        .InputSample = {.Width = static_cast<UINT>(input_texture_desc.Width),
                        .Height = input_texture_desc.Height,
                        .Format = {.Format = input_texture_desc.Format,
                                   .ColorSpace = input_stream_desc.ColorSpace}},
        .InputFrameRate = {30, 1},
        .OutputFormat = {.Format = output_texture_desc.Format,
                         .ColorSpace = output_stream_desc.ColorSpace},
        .OutputFrameRate = {30, 1},
    };
    hr = video_device_->CheckFeatureSupport(D3D12_FEATURE_VIDEO_PROCESS_SUPPORT,
                                            &support, sizeof(support));
    if (FAILED(hr)) {
      DLOG(ERROR) << "CheckFeatureSupport for "
                     "D3D12_FEATURE_VIDEO_PROCESS_SUPPORT failed: "
                  << logging::SystemErrorCodeToString(hr);
    }
    if (support.SupportFlags != D3D12_VIDEO_PROCESS_SUPPORT_FLAG_SUPPORTED) {
      DLOG(ERROR) << "D3D12 cannot support video processing.";
      return false;
    }

    hr = video_device_->CreateVideoProcessor(0, &output_stream_desc, 1,
                                             &input_stream_desc,
                                             IID_PPV_ARGS(&video_processor_));
    if (FAILED(hr)) {
      DLOG(ERROR) << "Create video processor failed: "
                  << logging::SystemErrorCodeToString(hr);
      return false;
    }
    input_stream_desc_ = input_stream_desc;
    output_stream_desc_ = output_stream_desc;
  }

  hr = command_allocator_->Reset();
  if (FAILED(hr)) {
    DLOG(ERROR) << "Reset video process command allocator failed:"
                << logging::SystemErrorCodeToString(hr);
    return false;
  }

  hr = command_list_->Reset(command_allocator_.Get());
  if (FAILED(hr)) {
    DLOG(ERROR) << "Reset video process command list failed:"
                << logging::SystemErrorCodeToString(hr);
    return false;
  }

  std::vector<D3D12_RESOURCE_BARRIER> resource_barriers;
  auto barriers1 = CreateD3D12TransitionBarriersForAllPlanes(
      input_texture, input_subresource, D3D12_RESOURCE_STATE_COMMON,
      D3D12_RESOURCE_STATE_VIDEO_PROCESS_READ);
  auto barriers2 = CreateD3D12TransitionBarriersForAllPlanes(
      output_texture, output_subresource, D3D12_RESOURCE_STATE_COMMON,
      D3D12_RESOURCE_STATE_VIDEO_PROCESS_WRITE);
  resource_barriers.insert(resource_barriers.end(), barriers1.begin(),
                           barriers1.end());
  resource_barriers.insert(resource_barriers.end(), barriers2.begin(),
                           barriers2.end());
  command_list_->ResourceBarrier(resource_barriers.size(),
                                 resource_barriers.data());

  D3D12_VIDEO_PROCESS_INPUT_STREAM_ARGUMENTS input_stream_arguments{
      .InputStream = {{.pTexture2D = input_texture,
                       .Subresource = input_subresource}},
      .Transform = {.SourceRectangle = input_rectangle.ToRECT(),
                    .DestinationRectangle = output_rectangle.ToRECT()}};
  D3D12_VIDEO_PROCESS_OUTPUT_STREAM_ARGUMENTS output_stream_arguments{
      .OutputStream = {
          {.pTexture2D = output_texture, .Subresource = output_subresource}}};
  command_list_->ProcessFrames(video_processor_.Get(), &output_stream_arguments,
                               1, &input_stream_arguments);

  for (auto& barrier : resource_barriers) {
    std::swap(barrier.Transition.StateBefore, barrier.Transition.StateAfter);
  }
  command_list_->ResourceBarrier(resource_barriers.size(),
                                 resource_barriers.data());

  hr = command_list_->Close();
  if (FAILED(hr)) {
    DLOG(ERROR) << "Close video process command list failed:"
                << logging::SystemErrorCodeToString(hr);
    return false;
  }

  ID3D12CommandList* command_list = command_list_.Get();
  command_queue_->ExecuteCommandLists(1, &command_list);

  return fence_->SignalAndWait(*command_queue_.Get()) == D3D11StatusCode::kOk;
}

}  // namespace media