chromium/media/gpu/windows/d3d11_video_decoder.h

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

#ifndef MEDIA_GPU_WINDOWS_D3D11_VIDEO_DECODER_H_
#define MEDIA_GPU_WINDOWS_D3D11_VIDEO_DECODER_H_

#include <list>
#include <vector>

#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/sequence_bound.h"
#include "base/time/time.h"
#include "gpu/config/gpu_driver_bug_workarounds.h"
#include "gpu/config/gpu_info.h"
#include "gpu/config/gpu_preferences.h"
#include "media/base/callback_registry.h"
#include "media/base/status.h"
#include "media/base/supported_video_decoder_config.h"
#include "media/base/video_decoder.h"
#include "media/base/video_types.h"
#include "media/gpu/command_buffer_helper.h"
#include "media/gpu/media_gpu_export.h"
#include "media/gpu/windows/d3d11_decoder_configurator.h"
#include "media/gpu/windows/d3d11_h264_accelerator.h"
#include "media/gpu/windows/d3d11_status.h"
#include "media/gpu/windows/d3d11_texture_selector.h"
#include "media/gpu/windows/d3d11_video_decoder_client.h"
#include "media/gpu/windows/d3d11_video_decoder_wrapper.h"
#include "media/gpu/windows/d3d11_video_frame_mailbox_release_helper.h"
#include "media/gpu/windows/d3d11_vp9_accelerator.h"
#include "media/gpu/windows/d3d_com_defs.h"

namespace gpu {
class CommandBufferStub;
}  // namespace gpu

namespace media {

class D3D11PictureBuffer;
class D3D11VideoDecoderTest;
class MediaLog;

// Video decoder that uses D3D11 directly.  It is intended that this class will
// run the decoder on whatever thread it lives on.  However, at the moment, it
// only works if it's on the gpu main thread.
class MEDIA_GPU_EXPORT D3D11VideoDecoder : public VideoDecoder,
                                           public D3D11VideoDecoderClient {
 public:
  enum class D3DVersion { kD3D11, kD3D12 };

  // Callback to get a D3D11/12 device.
  using GetD3DDeviceCB = base::RepeatingCallback<ComUnknown(D3DVersion)>;

  // List of configs that we'll check against when initializing.  This is only
  // needed since GpuMojoMediaClient merges our supported configs with the VDA
  // supported configs.
  using SupportedConfigs = std::vector<SupportedVideoDecoderConfig>;

  // |helper| must be called from |gpu_task_runner|.
  static std::unique_ptr<VideoDecoder> Create(
      scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
      std::unique_ptr<MediaLog> media_log,
      const gpu::GpuPreferences& gpu_preferences,
      const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
      base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb,
      GetD3DDeviceCB get_d3d_device_cb,
      SupportedConfigs supported_configs);

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

  // VideoDecoder implementation:
  VideoDecoderType GetDecoderType() const override;
  void Initialize(const VideoDecoderConfig& config,
                  bool low_delay,
                  CdmContext* cdm_context,
                  InitCB init_cb,
                  const OutputCB& output_cb,
                  const WaitingCB& waiting_cb) override;
  void Decode(scoped_refptr<DecoderBuffer> buffer, DecodeCB decode_cb) override;
  void Reset(base::OnceClosure closure) override;
  bool NeedsBitstreamConversion() const override;
  bool CanReadWithoutStalling() const override;
  int GetMaxDecodeRequests() const override;

  // D3D11VideoDecoderClient implementation.
  D3D11PictureBuffer* GetPicture() override;
  void UpdateTimestamp(D3D11PictureBuffer* picture_buffer) override;
  bool OutputResult(const CodecPicture* picture,
                    D3D11PictureBuffer* picture_buffer) override;
  D3DVideoDecoderWrapper* GetWrapper() override;

  bool ResetD3DVideoDecoder();

  static bool GetD3D11FeatureLevel(
      ComD3D11Device dev,
      const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
      D3D_FEATURE_LEVEL* feature_level);

  // Return the set of video decoder configs that we support.
  static std::vector<SupportedVideoDecoderConfig>
  GetSupportedVideoDecoderConfigs(
      const gpu::GpuPreferences& gpu_preferences,
      const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
      GetD3DDeviceCB get_d3d_device_cb);

 protected:
  // Owners should call Destroy(). This is automatic via
  // std::default_delete<media::VideoDecoder> when held by a
  // std::unique_ptr<media::VideoDecoder>.
  ~D3D11VideoDecoder() override;

 private:
  friend class D3D11VideoDecoderTest;

  D3D11VideoDecoder(
      scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner,
      std::unique_ptr<MediaLog> media_log,
      const gpu::GpuPreferences& gpu_preferences,
      const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
      base::RepeatingCallback<scoped_refptr<CommandBufferHelper>()>
          get_helper_cb,
      GetD3DDeviceCB get_d3d_device_cb,
      SupportedConfigs supported_configs);

  // Receive |buffer|, that is now unused by the client.
  void ReceivePictureBufferFromClient(scoped_refptr<D3D11PictureBuffer> buffer);

  // Picture buffer and related gpu resource initialization done.
  void PictureBufferGPUResourceInitDone(
      scoped_refptr<D3D11PictureBuffer> buffer);

  // Called when the gpu side of initialization is complete.
  void OnGpuInitComplete(
      bool success,
      D3D11VideoFrameMailboxReleaseHelper::ReleaseMailboxCB release_mailbox_cb);

  // Run the decoder loop.
  void DoDecode();

  // Instantiate |accelerated_video_decoder_| based on the video profile.
  // Returns false if the codec is unsupported.
  bool InitializeAcceleratedDecoder(const VideoDecoderConfig& config);

  // Query the video device for a specific decoder ID.
  bool DeviceHasDecoderID(GUID decoder_guid);

  // Create new PictureBuffers.  Currently, this completes synchronously, but
  // really should have an async interface since it must do some work on the
  // gpu main thread.
  void CreatePictureBuffers();

  // Measure the number of picture buffers that are currently unused, and see if
  // it's smaller than the minimum we've seen since we've allocated them.
  void MeasurePictureBufferUsage();

  // Log the current measurement of picture buffer usage, as measured by
  // `MeasurePictureBufferUsage()`, and clear the measurement.  Do nothing,
  // successfully, if no measurement has been made.
  void LogPictureBufferUsage();

  // Log the LUID of the adapter used for decoding.
  void LogDecoderAdapterLUID();

  // Create the D3DVideoDecoderWrapper according to the version level.
  std::unique_ptr<D3DVideoDecoderWrapper> CreateD3DVideoDecoderWrapper(
      D3D11DecoderConfigurator* decoder_configurator,
      uint8_t bit_depth);

  std::unique_ptr<MediaLog> media_log_;

  enum class State {
    // Initializing resources required to create a codec.
    kInitializing,

    // Initialization has completed and we're running. This is the only state
    // in which |codec_| might be non-null. If |codec_| is null, a codec
    // creation is pending.
    kRunning,

    // A fatal error occurred. A terminal state.
    kError,
  };

  // Enter the kError state. This will fail any pending |init_cb_| and/or
  // pending decodes as well. |opt_decoder_code| can be optionally provided for
  // a more descriptive reason passed back up to the decoder stream rather than
  // just kFailed.
  void NotifyError(
      D3D11Status reason,
      DecoderStatus::Codes opt_decoder_code = DecoderStatus::Codes::kFailed);

  // Posts |status| to any pending initialization or decode callbacks.
  void PostDecoderStatus(DecoderStatus status);

  // Mailbox release helper; which lives on the GPU main thread. Note: This must
  // be ref counted to outlive D3D11VideoDecoder since each output VideoFrame
  // uses it to wait on a SyncToken during mailbox release.
  scoped_refptr<D3D11VideoFrameMailboxReleaseHelper> mailbox_release_helper_;

  // GPU main thread task runner.
  scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner_;

  // Task runner on which |this| lives.
  scoped_refptr<base::SequencedTaskRunner> decoder_task_runner_;

  gpu::GpuPreferences gpu_preferences_;
  gpu::GpuDriverBugWorkarounds gpu_workarounds_;

  // During init, these will be set.
  VideoDecoderConfig config_;
  InitCB init_cb_;
  OutputCB output_cb_;

  // Callback to be used as a release CB for VideoFrames.  Be sure to
  // base::BindPostTaskToCurrentDefault the closure that it takes.
  D3D11VideoFrameMailboxReleaseHelper::ReleaseMailboxCB release_mailbox_cb_;

  // Right now, this is used both for the video decoder and for display.  In
  // the future, this should only be for the video decoder.  We should use
  // the ANGLE device for display (plus texture sharing, if needed).
  GetD3DDeviceCB get_d3d_device_cb_;

  // These may be accessed from |decoder_task_runner_|, since the angle device
  // is in multi-threaded mode.  Just be sure not to set any global state.
  ComD3D11Device device_;
  ComD3D11DeviceContext device_context_;
  ComD3D11VideoDevice video_device_;

  // D3D11 version on this device.
  D3D_FEATURE_LEVEL usable_feature_level_;

  std::unique_ptr<AcceleratedVideoDecoder> accelerated_video_decoder_;

  std::unique_ptr<D3D11DecoderConfigurator> decoder_configurator_;

  std::unique_ptr<TextureSelector> texture_selector_;

  std::list<std::pair<scoped_refptr<DecoderBuffer>, DecodeCB>>
      input_buffer_queue_;
  scoped_refptr<DecoderBuffer> current_buffer_;
  DecodeCB current_decode_cb_;
  base::TimeDelta current_timestamp_;

  // Must be called on the gpu main thread.  So, don't call it from here,
  // since we don't know what thread we're on.
  base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb_;

  // It would be nice to unique_ptr these, but we give a ref to the VideoFrame
  // so that the texture is retained until the mailbox is opened.
  std::vector<scoped_refptr<D3D11PictureBuffer>> picture_buffers_;

  State state_ = State::kInitializing;

  // Profile of the video being decoded.
  VideoCodecProfile profile_ = VIDEO_CODEC_PROFILE_UNKNOWN;

  // Callback to get a command buffer helper.  Must be called from the gpu
  // main thread only.
  base::RepeatingCallback<scoped_refptr<CommandBufferHelper>()> get_helper_cb_;

  // Entire class should be single-sequence.
  SEQUENCE_CHECKER(sequence_checker_);

  SupportedConfigs supported_configs_;

  // Should we use shared handles for WebGPU interop or if using Graphite.
  bool use_shared_handle_ = false;

  // Should we use multiple single textures for the decoder output (true) or one
  // texture with multiple array slices (false)?
  bool use_single_video_decoder_texture_ = false;

  std::unique_ptr<D3DVideoDecoderWrapper> d3d_video_decoder_wrapper_;

  // The currently configured bit depth for the decoder. When this changes we
  // need to recreate the decoder.
  uint8_t bit_depth_ = 8u;

  // The currently configured color space for the decoder. When this changes we
  // need to recreate the decoder.
  VideoColorSpace color_space_;

  // The currently configured chroma sampling format on the accelerator. When
  // this changes we need to recreate the decoder.
  VideoChromaSampling chroma_sampling_ = VideoChromaSampling::k420;

  // If set, this is the minimum number of picture buffers that we've seen
  // since the last time it was logged to UMA that are unused by both the
  // client and the decoder.  If unset, then no measurement has been made.
  std::optional<int> min_unused_buffers_;

  // Picture buffer usage is measured periodically after some number of decodes.
  // This tracks how many until the next measurement.  It's used strictly to
  // rate limit the measurements, so we don't spent too much time counting
  // unused picture buffers.
  int decode_count_until_picture_buffer_measurement_ = 0;

  base::WeakPtrFactory<D3D11VideoDecoder> weak_factory_{this};
};

}  // namespace media

#endif  // MEDIA_GPU_WINDOWS_D3D11_VIDEO_DECODER_H_