chromium/media/gpu/windows/d3d11_video_decoder_wrapper.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "media/gpu/windows/d3d11_video_decoder_wrapper.h"

#include <d3d9.h>

#include "base/check_op.h"
#include "base/strings/string_number_conversions.h"
#include "media/gpu/windows/d3d11_picture_buffer.h"
#include "third_party/abseil-cpp/absl/container/inlined_vector.h"

namespace media {

namespace {

D3D11_VIDEO_DECODER_BUFFER_TYPE
BufferTypeToD3D11BufferType(D3DVideoDecoderWrapper::BufferType type) {
  switch (type) {
    case D3DVideoDecoderWrapper::BufferType::kPictureParameters:
      return D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS;
    case D3DVideoDecoderWrapper::BufferType::kInverseQuantizationMatrix:
      return D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX;
    case D3DVideoDecoderWrapper::BufferType::kSliceControl:
      return D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL;
    case D3DVideoDecoderWrapper::BufferType::kBitstream:
      return D3D11_VIDEO_DECODER_BUFFER_BITSTREAM;
  }
  NOTREACHED();
}

template <typename D3D11VideoContext, typename D3D11VideoDecoderBufferDesc>
class ScopedD3D11DecoderBuffer;

template <typename D3D11VideoContext, typename D3D11VideoDecoderBufferDesc>
class D3D11VideoDecoderWrapperImpl : public D3D11VideoDecoderWrapper {
 public:
  D3D11VideoDecoderWrapperImpl(
      MediaLog* media_log,
      ComD3D11VideoDevice video_device,
      Microsoft::WRL::ComPtr<D3D11VideoContext> video_context,
      ComD3D11VideoDecoder video_decoder)
      : D3D11VideoDecoderWrapper(media_log),
        video_device_(std::move(video_device)),
        video_context_(std::move(video_context)),
        video_decoder_(std::move(video_decoder)) {}
  ~D3D11VideoDecoderWrapperImpl() override {
    // Release the scoped buffer before video_context_ and video_decoder_
    // destructs.
    if (bitstream_buffer_) {
      bitstream_buffer_.reset();
    }
  }

  std::optional<bool> UseSingleTexture() const override {
    D3D11_VIDEO_DECODER_DESC desc;
    D3D11_VIDEO_DECODER_CONFIG config;
    HRESULT hr = video_decoder_->GetCreationParameters(&desc, &config);
    if (FAILED(hr)) {
      MEDIA_PLOG(ERROR, hr, media_log_)
          << "D3D11VideoDecoder GetCreationParameters failed";
      return std::nullopt;
    }
    // Prefer whatever the config tells us about whether to use one Texture2D
    // with multiple array slices, or multiple Texture2Ds with one slice each.
    // If bit 14 is clear, then it's the former, else it's the latter.
    //
    // Let the workaround override array texture mode, if enabled.
    // TODO(crbug.com/41463736): Ignore |use_single_video_decoder_texture_|
    // here, since it might be the case that it's not actually the right fix.
    // Instead, We use this workaround to force a copy later.  The workaround
    // will be renamed if this turns out to fix the issue, but we might need to
    // merge back and smaller changes are better.
    //
    // For more information, please see:
    // https://download.microsoft.com/download/9/2/A/92A4E198-67E0-4ABD-9DB7-635D711C2752/DXVA_VPx.pdf
    // https://download.microsoft.com/download/5/f/c/5fc4ec5c-bd8c-4624-8034-319c1bab7671/DXVA_H264.pdf
    // TODO(crbug.com/dawn/1932): Use array textures if preferred with shared
    // handles once Dawn supports importing those.
    return config.ConfigDecoderSpecific & (1 << 14);
  }

  void Reset() override {
    if (bitstream_buffer_) {
      CHECK(bitstream_buffer_->Commit());
      bitstream_buffer_.reset();
    }
  }

  bool WaitForFrameBegins(D3D11PictureBuffer* output_picture) override {
    auto result = output_picture->AcquireOutputView();
    if (!result.has_value()) {
      media_log_->NotifyError(std::move(result).error().AddHere());
      return false;
    }
    ID3D11VideoDecoderOutputView* output_view = std::move(result).value();
    HRESULT hr;
    do {
      hr = video_context_->DecoderBeginFrame(video_decoder_.Get(), output_view,
                                             0, nullptr);
      if (hr == E_PENDING || hr == D3DERR_WASSTILLDRAWING) {
        // Hardware is busy.  We should make the call again.
        base::PlatformThread::YieldCurrentThread();
      }
    } while (hr == E_PENDING || hr == D3DERR_WASSTILLDRAWING);

    if (FAILED(hr)) {
      MEDIA_PLOG(ERROR, hr, media_log_) << "DecoderBeginFrame failed";
      return false;
    }

    return true;
  }

  bool HasPendingBuffer(BufferType type) override {
    D3D11_VIDEO_DECODER_BUFFER_TYPE d3d_buffer_type =
        BufferTypeToD3D11BufferType(type);
    for (auto video_buffer : video_buffers_) {
      if (video_buffer.BufferType == d3d_buffer_type) {
        return true;
      }
    }

    return false;
  }

  bool SubmitSlice() override {
    if (!slice_info_bytes_.empty()) {
      auto buffer = GetSliceControlBuffer(slice_info_bytes_.size());
      if (buffer.size() < slice_info_bytes_.size()) {
        MEDIA_LOG(ERROR, media_log_) << "Insufficient slice info buffer size";
        return false;
      }

      memcpy(buffer.data(), slice_info_bytes_.data(), slice_info_bytes_.size());
      slice_info_bytes_.clear();

      if (!buffer.Commit()) {
        return false;
      }
    }

    return SubmitBitstreamBuffer() && SubmitDecoderBuffers();
  }

  bool SubmitDecode() override {
    HRESULT hr = video_context_->DecoderEndFrame(video_decoder_.Get());
    if (FAILED(hr)) {
      MEDIA_PLOG(ERROR, hr, media_log_) << "SubmitDecode failed";
      return false;
    }
    return true;
  }

 private:
  friend class ScopedD3D11DecoderBuffer<D3D11VideoContext,
                                        D3D11VideoDecoderBufferDesc>;

  std::unique_ptr<ScopedD3DBuffer> GetBuffer(BufferType type,
                                             uint32_t desired_size) override {
    return std::make_unique<ScopedD3D11DecoderBuffer<
        D3D11VideoContext, D3D11VideoDecoderBufferDesc>>(
        this, BufferTypeToD3D11BufferType(type), desired_size, media_log_);
  }

  bool SubmitBitstreamBuffer() {
    DCHECK(bitstream_buffer_);
    bool ok = bitstream_buffer_->Commit();
    bitstream_buffer_.reset();
    return ok;
  }

  bool SubmitDecoderBuffers();

  ComD3D11VideoDevice video_device_;
  Microsoft::WRL::ComPtr<D3D11VideoContext> video_context_;
  ComD3D11VideoDecoder video_decoder_;
  absl::InlinedVector<D3D11VideoDecoderBufferDesc, 4> video_buffers_;
};

template <>
bool D3D11VideoDecoderWrapperImpl<
    ID3D11VideoContext,
    D3D11_VIDEO_DECODER_BUFFER_DESC>::SubmitDecoderBuffers() {
  DCHECK_LE(video_buffers_.size(), 4ull);
  HRESULT hr = video_context_->SubmitDecoderBuffers(
      video_decoder_.Get(), video_buffers_.size(), video_buffers_.data());
  video_buffers_.clear();
  if (FAILED(hr)) {
    MEDIA_PLOG(ERROR, hr, media_log_) << "SubmitDecoderBuffers failed";
    return false;
  }

  return true;
}

template <>
bool D3D11VideoDecoderWrapperImpl<
    ID3D11VideoContext1,
    D3D11_VIDEO_DECODER_BUFFER_DESC1>::SubmitDecoderBuffers() {
  DCHECK_LE(video_buffers_.size(), 4ull);
  HRESULT hr = video_context_->SubmitDecoderBuffers1(
      video_decoder_.Get(), video_buffers_.size(), video_buffers_.data());
  video_buffers_.clear();
  if (FAILED(hr)) {
    MEDIA_PLOG(ERROR, hr, media_log_) << "SubmitDecoderBuffers failed";
    return false;
  }

  return true;
}

template <typename D3D11VideoContext, typename D3D11VideoDecoderBufferDesc>
class ScopedD3D11DecoderBuffer : public ScopedD3DBuffer {
 public:
  ScopedD3D11DecoderBuffer(
      D3D11VideoDecoderWrapperImpl<D3D11VideoContext,
                                   D3D11VideoDecoderBufferDesc>* decoder,
      D3D11_VIDEO_DECODER_BUFFER_TYPE type,
      uint32_t desired_size,
      MediaLog* media_log)
      : decoder_(decoder),
        type_(type),
        desired_size_(desired_size),
        media_log_(media_log->Clone()) {
    UINT size;
    uint8_t* buffer;
    HRESULT hr = decoder_->video_context_->GetDecoderBuffer(
        decoder_->video_decoder_.Get(), type_, &size,
        reinterpret_cast<void**>(&buffer));
    if (FAILED(hr)) {
      D3D11StatusCode status_code = D3D11StatusCode::kOk;
      switch (type_) {
        case D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS:
          status_code = D3D11StatusCode::kGetPicParamBufferFailed;
          break;
        case D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX:
          status_code = D3D11StatusCode::kGetQuantBufferFailed;
          break;
        case D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL:
          status_code = D3D11StatusCode::kGetSliceControlBufferFailed;
          break;
        case D3D11_VIDEO_DECODER_BUFFER_BITSTREAM:
          status_code = D3D11StatusCode::kGetBitstreamBufferFailed;
          break;
        default:
          NOTREACHED();
      }
      media_log_->NotifyError(
          D3D11Status{status_code, "D3D11 GetDecoderBuffer failed", hr});
      return;
    }

    data_ = base::span<uint8_t>(buffer, size);
  }

  ~ScopedD3D11DecoderBuffer() override { Commit(); }

  ScopedD3D11DecoderBuffer(const ScopedD3D11DecoderBuffer&) = delete;
  ScopedD3D11DecoderBuffer& operator=(const ScopedD3D11DecoderBuffer&) = delete;

  bool Commit() override {
    return Commit(std::min(static_cast<size_t>(desired_size_), data_.size()));
  }

  bool Commit(uint32_t written_size) override {
    if (data_.empty()) {
      return false;
    }

    HRESULT hr = decoder_->video_context_->ReleaseDecoderBuffer(
        decoder_->video_decoder_.Get(), type_);
    if (FAILED(hr)) {
      D3D11StatusCode status_code = D3D11StatusCode::kOk;
      switch (type_) {
        case D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS:
          status_code = D3D11StatusCode::kReleasePicParamBufferFailed;
          break;
        case D3D11_VIDEO_DECODER_BUFFER_INVERSE_QUANTIZATION_MATRIX:
          status_code = D3D11StatusCode::kReleaseQuantBufferFailed;
          break;
        case D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL:
          status_code = D3D11StatusCode::kReleaseSliceControlBufferFailed;
          break;
        case D3D11_VIDEO_DECODER_BUFFER_BITSTREAM:
          status_code = D3D11StatusCode::kReleaseBitstreamBufferFailed;
          break;
        default:
          NOTREACHED();
      }
      media_log_->NotifyError(
          D3D11Status{status_code, "D3D11 ReleaseDecoderBuffer failed", hr});
      return false;
    }

    decoder_->video_buffers_.emplace_back(D3D11VideoDecoderBufferDesc{
        .BufferType = type_,
        .DataOffset = 0,
        .DataSize = written_size,
    });
    data_ = base::span<uint8_t>();
    return true;
  }

 private:
  const raw_ptr<D3D11VideoDecoderWrapperImpl<D3D11VideoContext,
                                             D3D11VideoDecoderBufferDesc>>
      decoder_;
  const D3D11_VIDEO_DECODER_BUFFER_TYPE type_;
  const uint32_t desired_size_;
  const std::unique_ptr<MediaLog> media_log_;
};

}  // namespace

// static
std::unique_ptr<D3D11VideoDecoderWrapper> D3D11VideoDecoderWrapper::Create(
    MediaLog* media_log,
    ComD3D11VideoDevice video_device,
    ComD3D11VideoContext video_context,
    const D3D11DecoderConfigurator* decoder_configurator,
    D3D_FEATURE_LEVEL supported_d3d11_version,
    VideoDecoderConfig config) {
  UINT config_count = 0;
  HRESULT hr = video_device->GetVideoDecoderConfigCount(
      decoder_configurator->DecoderDescriptor(), &config_count);
  if (FAILED(hr) || config_count == 0) {
    MEDIA_PLOG(ERROR, hr, media_log) << "GetVideoDecoderConfigCount failed";
    return nullptr;
  }

  D3D11_VIDEO_DECODER_CONFIG dec_config{};
  bool found = false;
  for (UINT i = 0; i < config_count; i++) {
    hr = video_device->GetVideoDecoderConfig(
        decoder_configurator->DecoderDescriptor(), i, &dec_config);
    if (FAILED(hr)) {
      MEDIA_PLOG(ERROR, hr, media_log) << "GetVideoDecoderConfig failed";
      return nullptr;
    }

    // DXVA HEVC, VP9, and AV1 specifications say ConfigBitstreamRaw
    // "shall be 1".
    if (dec_config.ConfigBitstreamRaw == 1 &&
        (config.codec() == VideoCodec::kVP9 ||
         config.codec() == VideoCodec::kAV1 ||
         config.codec() == VideoCodec::kHEVC)) {
      found = true;
      break;
    }

    // ConfigBitstreamRaw == 2 means the decoder uses DXVA_Slice_H264_Short.
    if (dec_config.ConfigBitstreamRaw == 2 &&
        config.codec() == VideoCodec::kH264) {
      found = true;
      break;
    }
  }

  if (!found) {
    MEDIA_LOG(ERROR, media_log) << "VideoDecoderConfig is unsupported";
    return nullptr;
  }

  ComD3D11VideoDecoder video_decoder;
  hr = video_device->CreateVideoDecoder(
      decoder_configurator->DecoderDescriptor(), &dec_config, &video_decoder);
  if (FAILED(hr)) {
    MEDIA_PLOG(ERROR, hr, media_log) << "CreateVideoDecoder failed";
    return nullptr;
  }

  // If we got an 11.1 D3D11 Device, we can use a |ID3D11VideoContext1|,
  // otherwise we have to make sure we only use a |ID3D11VideoContext|.
  if (supported_d3d11_version == D3D_FEATURE_LEVEL_11_0) {
    return std::make_unique<D3D11VideoDecoderWrapperImpl<
        ID3D11VideoContext, D3D11_VIDEO_DECODER_BUFFER_DESC>>(
        media_log, std::move(video_device), std::move(video_context),
        std::move(video_decoder));
  }

  if (supported_d3d11_version == D3D_FEATURE_LEVEL_11_1) {
    ComD3D11VideoContext1 video_context1;
    hr = video_context.As(&video_context1);
    CHECK_EQ(hr, S_OK);
    return std::make_unique<D3D11VideoDecoderWrapperImpl<
        ID3D11VideoContext1, D3D11_VIDEO_DECODER_BUFFER_DESC1>>(
        media_log, std::move(video_device), std::move(video_context1),
        std::move(video_decoder));
  }

  return nullptr;
}

D3D11VideoDecoderWrapper::~D3D11VideoDecoderWrapper() = default;

D3D11VideoDecoderWrapper::D3D11VideoDecoderWrapper(MediaLog* media_log)
    : D3DVideoDecoderWrapper(media_log) {}

}  // namespace media