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

#include <list>
#include <memory>
#include <utility>
#include <vector>

#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "gpu/command_buffer/common/constants.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/dxgi_shared_handle_manager.h"
#include "gpu/command_buffer/service/shared_image/d3d_image_backing.h"
#include "gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h"
#include "gpu/ipc/service/gpu_channel_shared_image_interface.h"
#include "gpu/ipc/service/shared_image_stub.h"
#include "media/base/media_switches.h"
#include "media/base/win/mf_helpers.h"
#include "media/gpu/windows/d3d11_picture_buffer.h"
#include "media/gpu/windows/format_utils.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"

namespace media {

namespace {

bool SupportsFormat(DXGI_FORMAT dxgi_format) {
  switch (dxgi_format) {
    case DXGI_FORMAT_NV12:
    case DXGI_FORMAT_P010:
    case DXGI_FORMAT_Y210:
    case DXGI_FORMAT_Y410:
    case DXGI_FORMAT_P016:
    case DXGI_FORMAT_Y216:
    case DXGI_FORMAT_Y416:
    case DXGI_FORMAT_B8G8R8A8_UNORM:
    case DXGI_FORMAT_R10G10B10A2_UNORM:
    case DXGI_FORMAT_R16G16B16A16_FLOAT:
      return true;
    default:
      return false;
  }
}

viz::SharedImageFormat DXGIFormatToMultiPlanarSharedImageFormat(
    DXGI_FORMAT dxgi_format) {
  switch (dxgi_format) {
    case DXGI_FORMAT_NV12:
      return viz::MultiPlaneFormat::kNV12;
    case DXGI_FORMAT_P010:
      return viz::MultiPlaneFormat::kP010;
    case DXGI_FORMAT_B8G8R8A8_UNORM:
      return viz::SinglePlaneFormat::kBGRA_8888;
    case DXGI_FORMAT_R10G10B10A2_UNORM:
      return viz::SinglePlaneFormat::kRGBA_1010102;
    case DXGI_FORMAT_R16G16B16A16_FLOAT:
      return viz::SinglePlaneFormat::kRGBA_F16;
    default:
      NOTREACHED_IN_MIGRATION();
      return viz::SinglePlaneFormat::kBGRA_8888;
  }
}

}  // anonymous namespace

Texture2DWrapper::Texture2DWrapper() = default;

Texture2DWrapper::~Texture2DWrapper() = default;

DefaultTexture2DWrapper::DefaultTexture2DWrapper(
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    DXGI_FORMAT dxgi_format,
    ComD3D11Device device)
    : size_(size),
      color_space_(color_space),
      dxgi_format_(dxgi_format),
      video_device_(std::move(device)) {}

DefaultTexture2DWrapper::~DefaultTexture2DWrapper() = default;

D3D11Status DefaultTexture2DWrapper::BeginSharedImageAccess() {
  if (shared_image_access_) {
    return D3D11Status::Codes::kOk;
  }

  if (shared_image_rep_) {
    TRACE_EVENT0("gpu", "D3D11TextureWrapper::BeginScopedWriteAccess");
    shared_image_access_ = shared_image_rep_->BeginScopedWriteAccess();
    if (!shared_image_access_) {
      return {D3D11Status::Codes::
                  kVideoDecodeImageRepresentationBeginScopedWriteAccessFailed,
              "Failed to begin shared image access"};
    }
  }

  return D3D11Status::Codes::kOk;
}

D3D11Status DefaultTexture2DWrapper::ProcessTexture(
    const gfx::ColorSpace& input_color_space,
    ClientSharedImageOrMailboxHolder& shared_image_dest) {
  // If we've received an error, then return it to our caller.  This is probably
  // from some previous operation.
  // TODO(liberato): Return the error.
  if (shared_image_access_) {
    TRACE_EVENT0("gpu", "D3D11TextureWrapper::EndScopedWriteAccess");
    shared_image_access_.reset();
  }

  shared_image_dest = shared_image_;

  // TODO(hitawala): Possibly optimize this method as input and stored color
  // spaces should be same.
  CHECK_EQ(input_color_space, color_space_);

  return D3D11Status::Codes::kOk;
}

D3D11Status DefaultTexture2DWrapper::Init(
    scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
    GetCommandBufferHelperCB get_helper_cb,
    ComD3D11Texture2D texture,
    size_t array_slice,
    scoped_refptr<media::D3D11PictureBuffer> picture_buffer,
    Texture2DWrapper::PictureBufferGPUResourceInitDoneCB
        picture_buffer_gpu_resource_init_done_cb) {
  if (!SupportsFormat(dxgi_format_))
    return D3D11Status::Codes::kUnsupportedTextureFormatForBind;

  picture_buffer_gpu_resource_init_done_cb_ =
      std::move(picture_buffer_gpu_resource_init_done_cb);

  // Start construction of the GpuResources.
  // We send the texture itself, since we assume that we're using the angle
  // device for decoding.  Sharing seems not to work very well.  Otherwise, we
  // would create the texture with KEYED_MUTEX and NTHANDLE, then send along
  // a handle that we get from |texture| as an IDXGIResource1.
  auto on_error_cb = base::BindPostTaskToCurrentDefault(base::BindOnce(
      &DefaultTexture2DWrapper::OnError, weak_factory_.GetWeakPtr()));

  auto gpu_resource_init_cb = base::BindPostTaskToCurrentDefault(
      base::BindOnce(&DefaultTexture2DWrapper::OnGPUResourceInitDone,
                     weak_factory_.GetWeakPtr()));
  gpu_resources_ = base::SequenceBound<GpuResources>(
      std::move(gpu_task_runner), std::move(on_error_cb),
      std::move(get_helper_cb), size_, color_space_, dxgi_format_,
      video_device_, texture, array_slice, std::move(picture_buffer),
      std::move(gpu_resource_init_cb));
  return D3D11Status::Codes::kOk;
}

void DefaultTexture2DWrapper::OnError(D3D11Status status) {
  if (!received_error_)
    received_error_ = status;
}

void DefaultTexture2DWrapper::OnGPUResourceInitDone(
    scoped_refptr<media::D3D11PictureBuffer> picture_buffer,
    std::unique_ptr<gpu::VideoImageRepresentation> shared_image_rep,
    scoped_refptr<gpu::ClientSharedImage> client_shared_image) {
  DCHECK(shared_image_rep);
  shared_image_rep_ = std::move(shared_image_rep);
  if (client_shared_image) {
    shared_image_ = std::move(client_shared_image);
  }
  std::move(picture_buffer_gpu_resource_init_done_cb_)
      .Run(std::move(picture_buffer));
}

DefaultTexture2DWrapper::GpuResources::GpuResources(
    OnErrorCB on_error_cb,
    GetCommandBufferHelperCB get_helper_cb,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    DXGI_FORMAT dxgi_format,
    ComD3D11Device video_device,
    ComD3D11Texture2D texture,
    size_t array_slice,
    scoped_refptr<media::D3D11PictureBuffer> picture_buffer,
    GPUResourceInitCB gpu_resource_init_cb) {
  CHECK(texture);

  helper_ = get_helper_cb.Run();
  if (!helper_) {
    std::move(on_error_cb)
        .Run(std::move(D3D11Status::Codes::kGetCommandBufferHelperFailed));
    return;
  }

  auto* shared_image_manager = helper_->GetSharedImageManager();

  // Usage flags to allow the display compositor to draw from it, video to
  // decode from it, and webgl/canvas to read from it.
  gpu::SharedImageUsageSet usage =
      gpu::SHARED_IMAGE_USAGE_VIDEO_DECODE |
      gpu::SHARED_IMAGE_USAGE_GLES2_READ | gpu::SHARED_IMAGE_USAGE_RASTER_READ |
      gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | gpu::SHARED_IMAGE_USAGE_SCANOUT;

  scoped_refptr<gpu::DXGISharedHandleState> dxgi_shared_handle_state;
  D3D11_TEXTURE2D_DESC desc = {};
  texture->GetDesc(&desc);
  // Create shared handle for shareable output texture.
  if (desc.MiscFlags & D3D11_RESOURCE_MISC_SHARED_NTHANDLE) {
    ComDXGIResource1 dxgi_resource;
    HRESULT hr = texture.As(&dxgi_resource);
    if (FAILED(hr)) {
      DLOG(ERROR) << "QueryInterface for IDXGIResource failed with error "
                  << std::hex << hr;
      std::move(on_error_cb)
          .Run(std::move(D3D11Status::Codes::kCreateSharedHandleFailed));
      return;
    }

    // WebGPU will potentially read directly from this texture.
    usage |= gpu::SHARED_IMAGE_USAGE_WEBGPU_READ;

    HANDLE shared_handle = nullptr;
    hr = dxgi_resource->CreateSharedHandle(
        nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
        nullptr, &shared_handle);
    if (FAILED(hr)) {
      DLOG(ERROR) << "CreateSharedHandle failed with error " << std::hex << hr;
      std::move(on_error_cb)
          .Run(std::move(D3D11Status::Codes::kCreateSharedHandleFailed));
      return;
    }

    dxgi_shared_handle_state =
        shared_image_manager->dxgi_shared_handle_manager()
            ->CreateAnonymousSharedHandleState(
                base::win::ScopedHandle(shared_handle), texture);
  }
  const bool is_thread_safe =
      IsDedicatedMediaServiceThreadEnabled(gl::ANGLEImplementation::kD3D11);

  gpu::SharedImageInfo si_info{
      DXGIFormatToMultiPlanarSharedImageFormat(dxgi_format),
      size,
      color_space,
      kTopLeft_GrSurfaceOrigin,
      kPremul_SkAlphaType,
      usage,
      "VideoTexture"};
  scoped_refptr<gpu::GpuChannelSharedImageInterface>
      gpu_channel_shared_image_interface =
          helper_->GetSharedImageStub()->shared_image_interface();
  scoped_refptr<gpu::ClientSharedImage> shared_image =
      gpu_channel_shared_image_interface->CreateSharedImageForD3D11Video(
          si_info, texture, std::move(dxgi_shared_handle_state), array_slice,
          is_thread_safe);
  if (!shared_image) {
    std::move(on_error_cb)
        .Run(std::move(D3D11Status::Codes::kCreateSharedImageFailed));
    return;
  }

  auto* memory_type_tracker = helper_->GetMemoryTypeTracker();
  std::unique_ptr<gpu::VideoImageRepresentation> shared_image_rep =
      shared_image_manager->ProduceVideo(
          video_device.Get(), shared_image->mailbox(), memory_type_tracker);
  if (!shared_image_rep) {
    std::move(on_error_cb)
        .Run(D3D11Status::Codes::kProduceVideoDecodeImageRepresentationFailed);
    shared_image_ = nullptr;
    return;
  }

  std::move(gpu_resource_init_cb)
      .Run(std::move(picture_buffer), std::move(shared_image_rep),
           std::move(shared_image));
}

DefaultTexture2DWrapper::GpuResources::~GpuResources() = default;

}  // namespace media