chromium/media/capture/video/win/gpu_memory_buffer_tracker_win.cc

// Copyright 2021 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/capture/video/win/gpu_memory_buffer_tracker_win.h"

#include <dxgi1_2.h>

#include "base/check.h"
#include "base/logging.h"
#include "base/memory/raw_span.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/trace_event/trace_event.h"
#include "base/unguessable_token.h"
#include "base/win/scoped_handle.h"
#include "gpu/ipc/common/dxgi_helpers.h"
#include "media/base/win/mf_helpers.h"
#include "media/capture/video/video_capture_buffer_handle.h"
#include "ui/gfx/geometry/size.h"

namespace media {

namespace {

class DXGIGMBTrackerHandle : public media::VideoCaptureBufferHandle {
 public:
  explicit DXGIGMBTrackerHandle(base::span<uint8_t> data,
                                HANDLE dxgi_handle,
                                ID3D11Device* d3d11_device)
      : data_(data), dxgi_handle_(dxgi_handle), d3d11_device_(d3d11_device) {}

  size_t mapped_size() const final { return data_.size(); }
  uint8_t* data() const final { return data_.data(); }
  const uint8_t* const_data() const final { return data_.data(); }

  ~DXGIGMBTrackerHandle() override {
    gpu::CopyShMemToDXGIBuffer(data_, dxgi_handle_, d3d11_device_);
  }

 private:
  base::raw_span<uint8_t> data_;
  HANDLE dxgi_handle_;
  raw_ptr<ID3D11Device> d3d11_device_;
};

base::win::ScopedHandle CreateNV12Texture(ID3D11Device* d3d11_device,
                                          const gfx::Size& size) {
  const DXGI_FORMAT dxgi_format = DXGI_FORMAT_NV12;
  D3D11_TEXTURE2D_DESC desc = {
      .Width = static_cast<UINT>(size.width()),
      .Height = static_cast<UINT>(size.height()),
      .MipLevels = 1,
      .ArraySize = 1,
      .Format = dxgi_format,
      .SampleDesc = {1, 0},
      .Usage = D3D11_USAGE_DEFAULT,
      .BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET,
      .CPUAccessFlags = 0,
      .MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE |
                   D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX};

  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;

  HRESULT hr = d3d11_device->CreateTexture2D(&desc, nullptr, &d3d11_texture);
  if (FAILED(hr)) {
    DLOG(ERROR) << "Failed to create D3D11 texture: "
                << logging::SystemErrorCodeToString(hr);
    return base::win::ScopedHandle();
  }
  hr = SetDebugName(d3d11_texture.Get(), "Camera_MemoryBufferTracker");
  if (FAILED(hr)) {
    DLOG(ERROR) << "Failed to label D3D11 texture: "
                << logging::SystemErrorCodeToString(hr);
    return base::win::ScopedHandle();
  }

  Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource;
  hr = d3d11_texture.As(&dxgi_resource);
  CHECK(SUCCEEDED(hr));

  HANDLE texture_handle;
  hr = dxgi_resource->CreateSharedHandle(
      nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
      &texture_handle);
  if (FAILED(hr)) {
    DLOG(ERROR) << "Failed to create shared D3D11 texture handle: "
                << logging::SystemErrorCodeToString(hr);
    return base::win::ScopedHandle();
  }
  return base::win::ScopedHandle(texture_handle);
}

}  // namespace

GpuMemoryBufferTrackerWin::GpuMemoryBufferTrackerWin(
    scoped_refptr<DXGIDeviceManager> dxgi_device_manager)
    : dxgi_device_manager_(std::move(dxgi_device_manager)),
      d3d_device_(dxgi_device_manager_->GetDevice()) {}

GpuMemoryBufferTrackerWin::GpuMemoryBufferTrackerWin(
    gfx::GpuMemoryBufferHandle gmb_handle,
    scoped_refptr<DXGIDeviceManager> dxgi_device_manager)
    : dxgi_device_manager_(std::move(dxgi_device_manager)),
      d3d_device_(dxgi_device_manager_->GetDevice()),
      external_dxgi_handle_(std::move(gmb_handle)),
      is_external_dxgi_handle_(true) {}

GpuMemoryBufferTrackerWin::~GpuMemoryBufferTrackerWin() = default;

bool GpuMemoryBufferTrackerWin::Init(const gfx::Size& dimensions,
                                     VideoPixelFormat format,
                                     const mojom::PlaneStridesPtr& strides) {
  // Only support NV12
  if (format != PIXEL_FORMAT_NV12) {
    NOTREACHED_IN_MIGRATION() << "Unsupported VideoPixelFormat " << format;
    return false;
  }

  if (is_external_dxgi_handle_) {
    return CreateBufferInternal(std::move(external_dxgi_handle_),
                                std::move(dimensions));
  }

  gfx::GpuMemoryBufferHandle gmb_handle;
  gmb_handle.dxgi_handle = CreateNV12Texture(d3d_device_.Get(), dimensions);
  gmb_handle.dxgi_token = gfx::DXGIHandleToken();
  return CreateBufferInternal(std::move(gmb_handle), std::move(dimensions));
}

bool GpuMemoryBufferTrackerWin::IsSameGpuMemoryBuffer(
    const gfx::GpuMemoryBufferHandle& handle) const {
  // This function is used for reusing external buffer.
  if (!is_external_dxgi_handle_) {
    return false;
  }
  // On Windows, we need use 'dxgi_token' to decide whether the two handles
  // point to same gmb instead of handle directly since handle could be
  // duplicated, please see GpuMemoryBufferImplDXGI::CloneHandle.
  return buffer_->GetToken() == handle.dxgi_token;
}

bool GpuMemoryBufferTrackerWin::CreateBufferInternal(
    gfx::GpuMemoryBufferHandle buffer_handle,
    const gfx::Size& dimensions) {
  if (!buffer_handle.dxgi_handle.IsValid()) {
    LOG(ERROR) << "dxgi_handle is not valid.";
    return false;
  }

  buffer_ = gpu::GpuMemoryBufferImplDXGI::CreateFromHandle(
      std::move(buffer_handle), std::move(dimensions),
      gfx::BufferFormat::YUV_420_BIPLANAR, gfx::BufferUsage::GPU_READ,
      gpu::GpuMemoryBufferImpl::DestructionCallback(), nullptr, nullptr);
  if (!buffer_) {
    NOTREACHED_IN_MIGRATION() << "Failed to create GPU memory buffer";
    return false;
  }

  region_ = base::UnsafeSharedMemoryRegion::Create(GetMemorySizeInBytes());
  mapping_ = region_.Map();

  return true;
}

bool GpuMemoryBufferTrackerWin::IsD3DDeviceChanged() {
  // Check for and handle device loss by recreating the texture
  Microsoft::WRL::ComPtr<ID3D11Device> recreated_d3d_device;
  HRESULT hr = dxgi_device_manager_->CheckDeviceRemovedAndGetDevice(
      &recreated_d3d_device);
  if (FAILED(hr)) {
    LOG(ERROR) << "Detected device loss: "
               << logging::SystemErrorCodeToString(hr);
    base::UmaHistogramSparse("Media.VideoCapture.Win.D3DDeviceRemovedReason",
                             hr);
  }
  return recreated_d3d_device != d3d_device_;
}

bool GpuMemoryBufferTrackerWin::IsReusableForFormat(
    const gfx::Size& dimensions,
    VideoPixelFormat format,
    const mojom::PlaneStridesPtr& strides) {
  // External buffer is never reused.
  return !IsD3DDeviceChanged() && (format == PIXEL_FORMAT_NV12) &&
         (dimensions == buffer_->GetSize()) && !is_external_dxgi_handle_;
}

std::unique_ptr<VideoCaptureBufferHandle>
GpuMemoryBufferTrackerWin::GetMemoryMappedAccess() {
  return std::make_unique<DXGIGMBTrackerHandle>(
      mapping_.GetMemoryAsSpan<uint8_t>(), buffer_->GetHandle(),
      d3d_device_.Get());
}

base::UnsafeSharedMemoryRegion
GpuMemoryBufferTrackerWin::DuplicateAsUnsafeRegion() {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
               "GpuMemoryBufferTrackerWin::DuplicateAsUnsafeRegion");

  if (!buffer_) {
    return base::UnsafeSharedMemoryRegion();
  }

  CHECK(region_.IsValid());
  CHECK(mapping_.IsValid());

  if (!gpu::CopyDXGIBufferToShMem(buffer_->GetHandle(),
                                  mapping_.GetMemoryAsSpan<uint8_t>(),
                                  d3d_device_.Get(), &staging_texture_)) {
    DLOG(ERROR) << "Couldn't copy DXGI buffer to shmem";
    return base::UnsafeSharedMemoryRegion();
  }

  return region_.Duplicate();
}

gfx::GpuMemoryBufferHandle
GpuMemoryBufferTrackerWin::GetGpuMemoryBufferHandle() {
  if (IsD3DDeviceChanged()) {
    return gfx::GpuMemoryBufferHandle();
  }
  auto handle = buffer_->CloneHandle();
  handle.region = region_.Duplicate();
  return handle;
}

VideoCaptureBufferType GpuMemoryBufferTrackerWin::GetBufferType() {
  return VideoCaptureBufferType::kGpuMemoryBuffer;
}

void GpuMemoryBufferTrackerWin::OnHeldByConsumersChanged(
    bool is_held_by_consumers) {
  if (!is_held_by_consumers) {
    imf_buffer_ = nullptr;
  }
}

void GpuMemoryBufferTrackerWin::UpdateExternalData(
    media::CapturedExternalVideoBuffer buffer) {
  imf_buffer_ = std::move(buffer.imf_buffer);
}

uint32_t GpuMemoryBufferTrackerWin::GetMemorySizeInBytes() {
  DCHECK(buffer_);
  return (buffer_->GetSize().width() * buffer_->GetSize().height() * 3) / 2;
}

}  // namespace media