| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // 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_video_decoder.h" |
| |
| #include <d3d11_4.h> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/debug/dump_without_crashing.h" |
| #include "base/feature_list.h" |
| #include "base/memory/ref_counted_delete_on_sequence.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "base/trace_event/trace_event.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/base/decoder_buffer.h" |
| #include "media/base/media_log.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/video_aspect_ratio.h" |
| #include "media/base/video_codecs.h" |
| #include "media/base/video_decoder_config.h" |
| #include "media/base/video_frame.h" |
| #include "media/base/video_util.h" |
| #include "media/base/win/hresult_status_helper.h" |
| #include "media/gpu/windows/d3d11_av1_accelerator.h" |
| #include "media/gpu/windows/d3d11_picture_buffer.h" |
| #include "media/gpu/windows/d3d11_video_context_wrapper.h" |
| #include "media/gpu/windows/d3d11_video_decoder_impl.h" |
| #include "media/gpu/windows/d3d11_video_device_format_support.h" |
| #include "media/gpu/windows/supported_profile_helpers.h" |
| #include "media/media_buildflags.h" |
| #include "ui/gl/gl_angle_util_win.h" |
| #include "ui/gl/gl_switches.h" |
| #include "ui/gl/hdr_metadata_helper_win.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // Holder class, so that we don't keep creating CommandBufferHelpers every time |
| // somebody calls a callback. We can't actually create it until we're on the |
| // right thread. |
| struct CommandBufferHelperHolder |
| : base::RefCountedDeleteOnSequence<CommandBufferHelperHolder> { |
| CommandBufferHelperHolder( |
| scoped_refptr<base::SequencedTaskRunner> task_runner) |
| : base::RefCountedDeleteOnSequence<CommandBufferHelperHolder>( |
| std::move(task_runner)) {} |
| scoped_refptr<CommandBufferHelper> helper; |
| |
| private: |
| ~CommandBufferHelperHolder() = default; |
| friend class base::RefCountedDeleteOnSequence<CommandBufferHelperHolder>; |
| friend class base::DeleteHelper<CommandBufferHelperHolder>; |
| |
| DISALLOW_COPY_AND_ASSIGN(CommandBufferHelperHolder); |
| }; |
| |
| scoped_refptr<CommandBufferHelper> CreateCommandBufferHelper( |
| base::RepeatingCallback<gpu::CommandBufferStub*()> get_stub_cb, |
| scoped_refptr<CommandBufferHelperHolder> holder) { |
| gpu::CommandBufferStub* stub = get_stub_cb.Run(); |
| if (!stub) |
| return nullptr; |
| |
| DCHECK(holder); |
| if (!holder->helper) |
| holder->helper = CommandBufferHelper::Create(stub); |
| |
| return holder->helper; |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<VideoDecoder> D3D11VideoDecoder::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, |
| D3D11VideoDecoder::GetD3D11DeviceCB get_d3d11_device_cb, |
| SupportedConfigs supported_configs, |
| bool is_hdr_supported) { |
| // We create |impl_| on the wrong thread, but we never use it here. |
| // Note that the output callback will hop to our thread, post the video |
| // frame, and along with a callback that will hop back to the impl thread |
| // when it's released. |
| // Note that we WrapUnique<VideoDecoder> rather than D3D11VideoDecoder to make |
| // this castable; the deleters have to match. |
| std::unique_ptr<MediaLog> cloned_media_log = media_log->Clone(); |
| auto get_helper_cb = |
| base::BindRepeating(CreateCommandBufferHelper, std::move(get_stub_cb), |
| scoped_refptr<CommandBufferHelperHolder>( |
| new CommandBufferHelperHolder(gpu_task_runner))); |
| return base::WrapUnique<VideoDecoder>(new D3D11VideoDecoder( |
| gpu_task_runner, std::move(media_log), gpu_preferences, gpu_workarounds, |
| base::SequenceBound<D3D11VideoDecoderImpl>( |
| gpu_task_runner, std::move(cloned_media_log), get_helper_cb), |
| get_helper_cb, std::move(get_d3d11_device_cb), |
| std::move(supported_configs), is_hdr_supported)); |
| } |
| |
| D3D11VideoDecoder::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::SequenceBound<D3D11VideoDecoderImpl> impl, |
| base::RepeatingCallback<scoped_refptr<CommandBufferHelper>()> get_helper_cb, |
| GetD3D11DeviceCB get_d3d11_device_cb, |
| SupportedConfigs supported_configs, |
| bool is_hdr_supported) |
| : media_log_(std::move(media_log)), |
| impl_(std::move(impl)), |
| gpu_task_runner_(std::move(gpu_task_runner)), |
| decoder_task_runner_(base::SequencedTaskRunnerHandle::Get()), |
| already_initialized_(false), |
| gpu_preferences_(gpu_preferences), |
| gpu_workarounds_(gpu_workarounds), |
| get_d3d11_device_cb_(std::move(get_d3d11_device_cb)), |
| get_helper_cb_(std::move(get_helper_cb)), |
| supported_configs_(std::move(supported_configs)), |
| is_hdr_supported_(is_hdr_supported) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(media_log_); |
| } |
| |
| D3D11VideoDecoder::~D3D11VideoDecoder() { |
| // Post destruction to the main thread. When this executes, it will also |
| // cancel pending callbacks into |impl_| via |impl_weak_|. Callbacks out |
| // from |impl_| will be cancelled by |weak_factory_| when we return. |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| impl_.Reset(); |
| |
| // Explicitly destroy the decoder, since it can reference picture buffers. |
| accelerated_video_decoder_.reset(); |
| |
| if (already_initialized_) |
| AddLifetimeProgressionStage(D3D11LifetimeProgression::kPlaybackSucceeded); |
| } |
| |
| VideoDecoderType D3D11VideoDecoder::GetDecoderType() const { |
| return VideoDecoderType::kD3D11; |
| } |
| |
| HRESULT D3D11VideoDecoder::InitializeAcceleratedDecoder( |
| const VideoDecoderConfig& config, |
| ComD3D11VideoDecoder video_decoder) { |
| TRACE_EVENT0("gpu", "D3D11VideoDecoder::InitializeAcceleratedDecoder"); |
| // If we got an 11.1 D3D11 Device, we can use a |ID3D11VideoContext1|, |
| // otherwise we have to make sure we only use a |ID3D11VideoContext|. |
| HRESULT hr; |
| |
| // |device_context_| is the primary display context, but currently |
| // we share it for decoding purposes. |
| auto video_context = VideoContextWrapper::CreateWrapper(usable_feature_level_, |
| device_context_, &hr); |
| |
| if (!SUCCEEDED(hr)) |
| return hr; |
| |
| profile_ = config.profile(); |
| if (config.codec() == VideoCodec::kVP9) { |
| accelerated_video_decoder_ = std::make_unique<VP9Decoder>( |
| std::make_unique<D3D11VP9Accelerator>( |
| this, media_log_.get(), video_device_, std::move(video_context)), |
| profile_, config.color_space_info()); |
| } else if (config.codec() == VideoCodec::kH264) { |
| accelerated_video_decoder_ = std::make_unique<H264Decoder>( |
| std::make_unique<D3D11H264Accelerator>( |
| this, media_log_.get(), video_device_, std::move(video_context)), |
| profile_, config.color_space_info()); |
| } else if (config.codec() == VideoCodec::kAV1) { |
| accelerated_video_decoder_ = std::make_unique<AV1Decoder>( |
| std::make_unique<D3D11AV1Accelerator>( |
| this, media_log_.get(), video_device_, std::move(video_context)), |
| profile_, config.color_space_info()); |
| } else { |
| return E_FAIL; |
| } |
| |
| // Provide the initial video decoder object. |
| DCHECK(set_accelerator_decoder_cb_); |
| set_accelerator_decoder_cb_.Run(std::move(video_decoder)); |
| |
| return hr; |
| } |
| |
| StatusOr<ComD3D11VideoDecoder> D3D11VideoDecoder::CreateD3D11Decoder() { |
| // By default we assume outputs are 8-bit for SDR color spaces and 10 bit for |
| // HDR color spaces (or VP9.2). We'll get a config change once we know the |
| // real bit depth if this turns out to be wrong. |
| bit_depth_ = |
| accelerated_video_decoder_ |
| ? accelerated_video_decoder_->GetBitDepth() |
| : (config_.profile() == VP9PROFILE_PROFILE2 || |
| config_.color_space_info().ToGfxColorSpace().IsHDR() |
| ? 10 |
| : 8); |
| |
| // OS prevent read any content from encrypted video frame. No need to support |
| // shared handle and keyed_mutex system for the encrypted frame. |
| const bool use_shared_handle = |
| base::FeatureList::IsEnabled(kD3D11VideoDecoderUseSharedHandle) && |
| !config_.is_encrypted(); |
| |
| // TODO: supported check? |
| decoder_configurator_ = D3D11DecoderConfigurator::Create( |
| gpu_preferences_, gpu_workarounds_, config_, bit_depth_, media_log_.get(), |
| use_shared_handle); |
| if (!decoder_configurator_) |
| return StatusCode::kDecoderUnsupportedProfile; |
| |
| if (!decoder_configurator_->SupportsDevice(video_device_)) |
| return StatusCode::kDecoderUnsupportedCodec; |
| |
| FormatSupportChecker format_checker(device_); |
| if (!format_checker.Initialize()) { |
| // Don't fail; it'll just return no support a lot. |
| MEDIA_LOG(WARNING, media_log_) |
| << "Could not create format checker, continuing"; |
| } |
| |
| // Use IsHDRSupported to guess whether the compositor can output HDR textures. |
| // See TextureSelector for notes about why the decoder should not care. |
| texture_selector_ = TextureSelector::Create( |
| gpu_preferences_, gpu_workarounds_, |
| decoder_configurator_->TextureFormat(), |
| is_hdr_supported_ ? TextureSelector::HDRMode::kSDROrHDR |
| : TextureSelector::HDRMode::kSDROnly, |
| &format_checker, video_device_, device_context_, media_log_.get(), |
| use_shared_handle); |
| if (!texture_selector_) |
| return StatusCode::kCreateTextureSelectorFailed; |
| |
| UINT config_count = 0; |
| auto hr = video_device_->GetVideoDecoderConfigCount( |
| decoder_configurator_->DecoderDescriptor(), &config_count); |
| if (FAILED(hr)) { |
| return Status(StatusCode::kGetDecoderConfigCountFailed) |
| .AddCause(HresultToStatus(hr)); |
| } |
| |
| if (config_count == 0) |
| return Status(StatusCode::kGetDecoderConfigCountFailed); |
| |
| 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)) { |
| return Status(StatusCode::kGetDecoderConfigFailed) |
| .AddCause(HresultToStatus(hr)); |
| } |
| |
| if ((config_.codec() == VideoCodec::kVP9 || |
| config_.codec() == VideoCodec::kAV1) && |
| dec_config.ConfigBitstreamRaw == 1) { |
| // DXVA VP9 and AV1 specifications say ConfigBitstreamRaw "shall be 1". |
| found = true; |
| break; |
| } |
| |
| if (config_.codec() == VideoCodec::kH264 && |
| dec_config.ConfigBitstreamRaw == 2) { |
| // ConfigBitstreamRaw == 2 means the decoder uses DXVA_Slice_H264_Short. |
| found = true; |
| break; |
| } |
| } |
| if (!found) |
| return StatusCode::kDecoderUnsupportedConfig; |
| |
| // 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/971952): 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 |
| // |
| // When creating output texture with shared handle supports, we can't use a |
| // texture array. Because the keyed mutex applies on the entire texture array |
| // causing a deadlock when multiple threads try to use different slots of the |
| // array. More info here: https://crbug.com/1238943 |
| use_single_video_decoder_texture_ = |
| !!(dec_config.ConfigDecoderSpecific & (1 << 14)) || use_shared_handle; |
| if (use_single_video_decoder_texture_) |
| MEDIA_LOG(INFO, media_log_) << "D3D11VideoDecoder is using single textures"; |
| else |
| MEDIA_LOG(INFO, media_log_) << "D3D11VideoDecoder is using array texture"; |
| |
| Microsoft::WRL::ComPtr<ID3D11VideoDecoder> video_decoder; |
| hr = video_device_->CreateVideoDecoder( |
| decoder_configurator_->DecoderDescriptor(), &dec_config, &video_decoder); |
| |
| if (!video_decoder.Get()) |
| return Status(StatusCode::kDecoderCreationFailed); |
| |
| if (FAILED(hr)) { |
| return Status(StatusCode::kDecoderCreationFailed) |
| .AddCause(HresultToStatus(hr)); |
| } |
| |
| return {std::move(video_decoder)}; |
| } |
| |
| void D3D11VideoDecoder::Initialize(const VideoDecoderConfig& config, |
| bool low_delay, |
| CdmContext* /* cdm_context */, |
| InitCB init_cb, |
| const OutputCB& output_cb, |
| const WaitingCB& /* waiting_cb */) { |
| TRACE_EVENT0("gpu", "D3D11VideoDecoder::Initialize"); |
| if (already_initialized_) |
| AddLifetimeProgressionStage(D3D11LifetimeProgression::kPlaybackSucceeded); |
| AddLifetimeProgressionStage(D3D11LifetimeProgression::kInitializeStarted); |
| |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(output_cb); |
| |
| state_ = State::kInitializing; |
| |
| config_ = config; |
| init_cb_ = std::move(init_cb); |
| output_cb_ = output_cb; |
| |
| // Verify that |config| matches one of the supported configurations. This |
| // helps us skip configs that are supported by the VDA but not us, since |
| // GpuMojoMediaClient merges them. This is not hacky, even in the tiniest |
| // little bit, nope. Definitely not. Convinced? |
| bool is_supported = false; |
| for (const auto& supported_config : supported_configs_) { |
| if (supported_config.Matches(config)) { |
| is_supported = true; |
| break; |
| } |
| } |
| |
| if (!is_supported) { |
| NotifyError("D3D11VideoDecoder does not support this config"); |
| return; |
| } |
| |
| if (config.is_encrypted()) { |
| NotifyError("D3D11VideoDecoder does not support encrypted stream"); |
| return; |
| } |
| |
| // Initialize the video decoder. |
| |
| // Note that we assume that this is the ANGLE device, since we don't implement |
| // texture sharing properly. That also implies that this is the GPU main |
| // thread, since we use non-threadsafe properties of the device (e.g., we get |
| // the immediate context). |
| // |
| // Also note that we don't technically have a guarantee that the ANGLE device |
| // will use the most recent version of D3D11; it might be configured to use |
| // D3D9. In practice, though, it seems to use 11.1 if it's available, unless |
| // it's been specifically configured via switch to avoid d3d11. |
| // |
| // TODO(liberato): On re-init, we can probably re-use the device. |
| // TODO(liberato): This isn't allowed off the main thread, since the callback |
| // does who-knows-what. Either we should be given the angle device, or we |
| // should thread-hop to get it. |
| device_ = get_d3d11_device_cb_.Run(); |
| if (!device_) { |
| // This happens if, for example, if chrome is configured to use |
| // D3D9 for ANGLE. |
| NotifyError("ANGLE did not provide D3D11 device"); |
| return; |
| } |
| |
| if (!GetD3D11FeatureLevel(device_, gpu_workarounds_, |
| &usable_feature_level_)) { |
| NotifyError("D3D11 feature level not supported"); |
| return; |
| } |
| |
| device_->GetImmediateContext(&device_context_); |
| |
| HRESULT hr; |
| |
| // TODO(liberato): Handle cleanup better. Also consider being less chatty in |
| // the logs, since this will fall back. |
| |
| ComD3D11Multithread multi_threaded; |
| hr = device_->QueryInterface(IID_PPV_ARGS(&multi_threaded)); |
| if (FAILED(hr)) { |
| return NotifyError(Status(StatusCode::kQueryID3D11MultithreadFailed) |
| .AddCause(HresultToStatus(hr))); |
| } |
| |
| multi_threaded->SetMultithreadProtected(TRUE); |
| |
| hr = device_.As(&video_device_); |
| if (!SUCCEEDED(hr)) { |
| NotifyError("Failed to get video device"); |
| return; |
| } |
| |
| auto video_decoder_or_error = CreateD3D11Decoder(); |
| if (video_decoder_or_error.has_error()) { |
| NotifyError(std::move(video_decoder_or_error).error()); |
| return; |
| } |
| |
| hr = InitializeAcceleratedDecoder(config, |
| std::move(video_decoder_or_error).value()); |
| |
| if (!SUCCEEDED(hr)) { |
| NotifyError("Failed to get device context"); |
| return; |
| } |
| |
| // At this point, playback is supported so add a line in the media log to help |
| // us figure that out. |
| MEDIA_LOG(INFO, media_log_) << "Video is supported by D3D11VideoDecoder"; |
| |
| if (base::FeatureList::IsEnabled(kD3D11PrintCodecOnCrash)) { |
| static base::debug::CrashKeyString* codec_name = |
| base::debug::AllocateCrashKeyString("d3d11_playback_video_codec", |
| base::debug::CrashKeySize::Size32); |
| base::debug::SetCrashKeyString(codec_name, |
| config.GetHumanReadableCodecName()); |
| } |
| |
| auto impl_init_cb = base::BindOnce(&D3D11VideoDecoder::OnGpuInitComplete, |
| weak_factory_.GetWeakPtr()); |
| |
| auto get_picture_buffer_cb = |
| base::BindRepeating(&D3D11VideoDecoder::ReceivePictureBufferFromClient, |
| weak_factory_.GetWeakPtr()); |
| |
| AddLifetimeProgressionStage(D3D11LifetimeProgression::kInitializeSucceeded); |
| |
| // Initialize the gpu side. It would be nice if we could ask SB<> to elide |
| // the post if we're already on that thread, but it can't. |
| // Bind our own init / output cb that hop to this thread, so we don't call |
| // the originals on some other thread. |
| // Important but subtle note: base::Bind will copy |config_| since it's a |
| // const ref. |
| impl_.AsyncCall(&D3D11VideoDecoderImpl::Initialize) |
| .WithArgs(BindToCurrentLoop(std::move(impl_init_cb))); |
| } |
| |
| void D3D11VideoDecoder::AddLifetimeProgressionStage( |
| D3D11LifetimeProgression stage) { |
| already_initialized_ = |
| (stage == D3D11LifetimeProgression::kInitializeSucceeded); |
| const std::string uma_name("Media.D3D11.DecoderLifetimeProgression"); |
| base::UmaHistogramEnumeration(uma_name, stage); |
| } |
| |
| void D3D11VideoDecoder::ReceivePictureBufferFromClient( |
| scoped_refptr<D3D11PictureBuffer> buffer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| TRACE_EVENT0("gpu", "D3D11VideoDecoder::ReceivePictureBufferFromClient"); |
| |
| // We may decode into this buffer again. |
| // Note that |buffer| might no longer be in |picture_buffers_| if we've |
| // replaced them. That's okay. |
| buffer->remove_client_use(); |
| |
| // Also re-start decoding in case it was waiting for more pictures. |
| DoDecode(); |
| } |
| |
| void D3D11VideoDecoder::OnGpuInitComplete( |
| bool success, |
| D3D11VideoDecoderImpl::ReleaseMailboxCB release_mailbox_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| TRACE_EVENT0("gpu", "D3D11VideoDecoder::OnGpuInitComplete"); |
| |
| if (!init_cb_) { |
| // We already failed, so just do nothing. |
| DCHECK_EQ(state_, State::kError); |
| return; |
| } |
| |
| DCHECK_EQ(state_, State::kInitializing); |
| |
| if (!success) { |
| NotifyError("Gpu init failed"); |
| return; |
| } |
| |
| release_mailbox_cb_ = std::move(release_mailbox_cb); |
| |
| state_ = State::kRunning; |
| std::move(init_cb_).Run(OkStatus()); |
| } |
| |
| void D3D11VideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer, |
| DecodeCB decode_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| TRACE_EVENT0("gpu", "D3D11VideoDecoder::Decode"); |
| |
| // If we aren't given a decode cb, then record that. |
| // crbug.com/1012464 . |
| if (!decode_cb) |
| base::debug::DumpWithoutCrashing(); |
| |
| if (state_ == State::kError) { |
| // TODO(liberato): consider posting, though it likely doesn't matter. |
| std::move(decode_cb).Run(DecodeStatus::DECODE_ERROR); |
| return; |
| } |
| |
| input_buffer_queue_.push_back( |
| std::make_pair(std::move(buffer), std::move(decode_cb))); |
| |
| // Post, since we're not supposed to call back before this returns. It |
| // probably doesn't matter since we're in the gpu process anyway. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&D3D11VideoDecoder::DoDecode, weak_factory_.GetWeakPtr())); |
| } |
| |
| void D3D11VideoDecoder::DoDecode() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| TRACE_EVENT0("gpu", "D3D11VideoDecoder::DoDecode"); |
| |
| if (state_ != State::kRunning) { |
| DVLOG(2) << __func__ << ": Do nothing in " << static_cast<int>(state_) |
| << " state."; |
| return; |
| } |
| |
| if (!current_buffer_) { |
| if (input_buffer_queue_.empty()) { |
| return; |
| } |
| current_buffer_ = std::move(input_buffer_queue_.front().first); |
| current_decode_cb_ = std::move(input_buffer_queue_.front().second); |
| // If we pop a null decode cb off the stack, record it so we can see if this |
| // is from a top-level call, or through Decode. |
| // crbug.com/1012464 . |
| if (!current_decode_cb_) |
| base::debug::DumpWithoutCrashing(); |
| input_buffer_queue_.pop_front(); |
| if (current_buffer_->end_of_stream()) { |
| // Flush, then signal the decode cb once all pictures have been output. |
| current_buffer_ = nullptr; |
| if (!accelerated_video_decoder_->Flush()) { |
| // This will also signal error |current_decode_cb_|. |
| NotifyError(StatusCode::kAcceleratorFlushFailed); |
| return; |
| } |
| // Pictures out output synchronously during Flush. Signal the decode |
| // cb now. |
| std::move(current_decode_cb_).Run(DecodeStatus::OK); |
| return; |
| } |
| // This must be after checking for EOS because there is no timestamp for an |
| // EOS buffer. |
| current_timestamp_ = current_buffer_->timestamp(); |
| |
| accelerated_video_decoder_->SetStream(-1, *current_buffer_); |
| } |
| |
| while (true) { |
| // If we transition to the error state, then stop here. |
| if (state_ == State::kError) |
| return; |
| |
| // If somebody cleared the buffer, then stop and post. |
| // TODO(liberato): It's unclear to me how this might happen. If it does |
| // fix the crash, then more investigation is required. Please see |
| // crbug.com/1012464 for more information. |
| if (!current_buffer_) |
| break; |
| |
| // Record if we get here with a buffer, but without a decode cb. This |
| // shouldn't happen, but does. This will prevent the crash, and record how |
| // we got here. |
| // crbug.com/1012464 . |
| if (!current_decode_cb_) { |
| base::debug::DumpWithoutCrashing(); |
| current_buffer_ = nullptr; |
| break; |
| } |
| |
| media::AcceleratedVideoDecoder::DecodeResult result = |
| accelerated_video_decoder_->Decode(); |
| if (state_ == State::kError) { |
| // Transitioned to an error at some point. The h264 accelerator can do |
| // this if picture output fails, at least. Until that's fixed, check |
| // here and exit if so. |
| return; |
| } |
| // TODO(liberato): switch + class enum. |
| if (result == media::AcceleratedVideoDecoder::kRanOutOfStreamData) { |
| current_buffer_ = nullptr; |
| std::move(current_decode_cb_).Run(DecodeStatus::OK); |
| break; |
| } else if (result == media::AcceleratedVideoDecoder::kRanOutOfSurfaces) { |
| // At this point, we know the picture size. |
| // If we haven't allocated picture buffers yet, then allocate some now. |
| // Otherwise, stop here. We'll restart when a picture comes back. |
| if (picture_buffers_.size()) |
| return; |
| |
| CreatePictureBuffers(); |
| } else if (result == media::AcceleratedVideoDecoder::kConfigChange) { |
| // Before the first frame, we get a config change that we should ignore. |
| // We only want to take action if this is a mid-stream config change. We |
| // could wait until now to allocate the first D3D11VideoDecoder, but we |
| // don't, so that init can fail rather than decoding if there's a problem |
| // creating it. We could also unconditionally re-allocate the decoder, |
| // but we keep it if it's ready to go. |
| const auto new_bit_depth = accelerated_video_decoder_->GetBitDepth(); |
| const auto new_profile = accelerated_video_decoder_->GetProfile(); |
| const auto new_coded_size = accelerated_video_decoder_->GetPicSize(); |
| if (new_profile == config_.profile() && |
| new_coded_size == config_.coded_size() && |
| new_bit_depth == bit_depth_ && !picture_buffers_.size()) { |
| continue; |
| } |
| |
| // Update the config. |
| MEDIA_LOG(INFO, media_log_) |
| << "D3D11VideoDecoder config change: profile: " |
| << static_cast<int>(new_profile) << " coded_size: (" |
| << new_coded_size.width() << ", " << new_coded_size.height() << ")"; |
| profile_ = new_profile; |
| config_.set_profile(profile_); |
| config_.set_coded_size(new_coded_size); |
| |
| // Replace the decoder, and clear any picture buffers we have. It's okay |
| // if we don't have any picture buffer yet; this might be before the |
| // accelerated decoder asked for any. |
| auto video_decoder_or_error = CreateD3D11Decoder(); |
| if (video_decoder_or_error.has_error()) { |
| NotifyError(std::move(video_decoder_or_error).error()); |
| return; |
| } |
| DCHECK(set_accelerator_decoder_cb_); |
| set_accelerator_decoder_cb_.Run( |
| std::move(video_decoder_or_error).value()); |
| picture_buffers_.clear(); |
| } else if (result == media::AcceleratedVideoDecoder::kTryAgain) { |
| LOG(ERROR) << "Try again is not supported"; |
| NotifyError(StatusCode::kTryAgainNotSupported); |
| return; |
| } else { |
| std::ostringstream message; |
| message << "VDA Error " << result; |
| NotifyError(Status(StatusCode::kDecoderFailedDecode, message.str())); |
| return; |
| } |
| } |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&D3D11VideoDecoder::DoDecode, weak_factory_.GetWeakPtr())); |
| } |
| |
| void D3D11VideoDecoder::Reset(base::OnceClosure closure) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK_NE(state_, State::kInitializing); |
| TRACE_EVENT0("gpu", "D3D11VideoDecoder::Reset"); |
| |
| current_buffer_ = nullptr; |
| if (current_decode_cb_) |
| std::move(current_decode_cb_).Run(DecodeStatus::ABORTED); |
| |
| for (auto& queue_pair : input_buffer_queue_) |
| std::move(queue_pair.second).Run(DecodeStatus::ABORTED); |
| input_buffer_queue_.clear(); |
| |
| // TODO(liberato): how do we signal an error? |
| accelerated_video_decoder_->Reset(); |
| |
| std::move(closure).Run(); |
| } |
| |
| bool D3D11VideoDecoder::NeedsBitstreamConversion() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| return true; |
| } |
| |
| bool D3D11VideoDecoder::CanReadWithoutStalling() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| return false; |
| } |
| |
| int D3D11VideoDecoder::GetMaxDecodeRequests() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| return 4; |
| } |
| |
| void D3D11VideoDecoder::CreatePictureBuffers() { |
| // TODO(liberato): When we run off the gpu main thread, this call will need |
| // to signal success / failure asynchronously. We'll need to transition into |
| // a "waiting for pictures" state, since D3D11PictureBuffer will post the gpu |
| // thread work. |
| TRACE_EVENT0("gpu", "D3D11VideoDecoder::CreatePictureBuffers"); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(decoder_configurator_); |
| DCHECK(texture_selector_); |
| gfx::Size size = accelerated_video_decoder_->GetPicSize(); |
| |
| gfx::HDRMetadata stream_metadata; |
| if (config_.hdr_metadata()) |
| stream_metadata = *config_.hdr_metadata(); |
| // else leave |stream_metadata| default-initialized. We might use it anyway. |
| |
| absl::optional<DXGI_HDR_METADATA_HDR10> display_metadata; |
| if (decoder_configurator_->TextureFormat() == DXGI_FORMAT_P010) { |
| // For HDR formats, try to get the display metadata. This may fail, which |
| // is okay. We'll just skip sending the metadata. |
| gl::HDRMetadataHelperWin hdr_metadata_helper(device_); |
| display_metadata = hdr_metadata_helper.GetDisplayMetadata(); |
| } |
| |
| // Drop any old pictures. |
| for (auto& buffer : picture_buffers_) |
| DCHECK(!buffer->in_picture_use()); |
| picture_buffers_.clear(); |
| |
| ComD3D11Texture2D in_texture; |
| |
| // Create each picture buffer. |
| for (size_t i = 0; i < D3D11DecoderConfigurator::BUFFER_COUNT; i++) { |
| // Create an input texture / texture array if we haven't already. |
| if (!in_texture) { |
| auto result = decoder_configurator_->CreateOutputTexture( |
| device_, size, |
| use_single_video_decoder_texture_ |
| ? 1 |
| : D3D11DecoderConfigurator::BUFFER_COUNT, |
| texture_selector_->DoesDecoderOutputUseSharedHandle()); |
| if (result.has_value()) { |
| in_texture = std::move(result).value(); |
| } else { |
| NotifyError(std::move(result).error().AddHere()); |
| return; |
| } |
| } |
| |
| DCHECK(!!in_texture); |
| |
| auto tex_wrapper = texture_selector_->CreateTextureWrapper(device_, size); |
| if (!tex_wrapper) { |
| NotifyError(StatusCode::kAllocateTextureForCopyingWrapperFailed); |
| return; |
| } |
| |
| const size_t array_slice = use_single_video_decoder_texture_ ? 0 : i; |
| picture_buffers_.push_back( |
| new D3D11PictureBuffer(decoder_task_runner_, in_texture, array_slice, |
| std::move(tex_wrapper), size, i /* level */)); |
| Status result = picture_buffers_[i]->Init( |
| gpu_task_runner_, get_helper_cb_, video_device_, |
| decoder_configurator_->DecoderGuid(), media_log_->Clone()); |
| if (!result.is_ok()) { |
| NotifyError(std::move(result).AddHere()); |
| return; |
| } |
| |
| // If we're using one texture per buffer, rather than an array, then clear |
| // the ref to it so that we allocate a new one above. |
| if (use_single_video_decoder_texture_) |
| in_texture = nullptr; |
| |
| // If we have display metadata, then tell the processor. Note that the |
| // order of these calls is important, and we must set the display metadata |
| // if we set the stream metadata, else it can crash on some AMD cards. |
| if (display_metadata) { |
| if (config_.hdr_metadata() || |
| gpu_workarounds_.use_empty_video_hdr_metadata) { |
| // It's okay if this has an empty-initialized metadata. |
| picture_buffers_[i]->texture_wrapper()->SetStreamHDRMetadata( |
| stream_metadata); |
| } |
| picture_buffers_[i]->texture_wrapper()->SetDisplayHDRMetadata( |
| *display_metadata); |
| } |
| } |
| } |
| |
| D3D11PictureBuffer* D3D11VideoDecoder::GetPicture() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| for (auto& buffer : picture_buffers_) { |
| if (!buffer->in_client_use() && !buffer->in_picture_use()) { |
| buffer->timestamp_ = current_timestamp_; |
| return buffer.get(); |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| void D3D11VideoDecoder::UpdateTimestamp(D3D11PictureBuffer* picture_buffer) { |
| // A picture is being reused with a different timestamp; since we've already |
| // generated a VideoFrame from the previous picture buffer, we can just stamp |
| // the new timestamp directly onto the buffer. |
| picture_buffer->timestamp_ = current_timestamp_; |
| } |
| |
| bool D3D11VideoDecoder::OutputResult(const CodecPicture* picture, |
| D3D11PictureBuffer* picture_buffer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(texture_selector_); |
| TRACE_EVENT0("gpu", "D3D11VideoDecoder::OutputResult"); |
| |
| picture_buffer->add_client_use(); |
| |
| // Note: The pixel format doesn't matter. |
| gfx::Rect visible_rect = picture->visible_rect(); |
| if (visible_rect.IsEmpty()) |
| visible_rect = config_.visible_rect(); |
| |
| // TODO(https://crbug.com/843150): Use aspect ratio from decoder (SPS) if |
| // the config's aspect ratio isn't valid. |
| gfx::Size natural_size = config_.aspect_ratio().GetNaturalSize(visible_rect); |
| |
| base::TimeDelta timestamp = picture_buffer->timestamp_; |
| |
| MailboxHolderArray mailbox_holders; |
| gfx::ColorSpace output_color_space; |
| Status result = picture_buffer->ProcessTexture( |
| picture->get_colorspace().ToGfxColorSpace(), &mailbox_holders, |
| &output_color_space); |
| if (!result.is_ok()) { |
| NotifyError(std::move(result).AddHere()); |
| return false; |
| } |
| |
| scoped_refptr<VideoFrame> frame = VideoFrame::WrapNativeTextures( |
| texture_selector_->PixelFormat(), mailbox_holders, |
| VideoFrame::ReleaseMailboxCB(), picture_buffer->size(), visible_rect, |
| natural_size, timestamp); |
| |
| if (!frame) { |
| // This can happen if, somehow, we get an unsupported combination of |
| // pixel format, etc. |
| NotifyError(StatusCode::kDecoderVideoFrameConstructionFailed); |
| return false; |
| } |
| |
| // Remember that this will likely thread-hop to the GPU main thread. Note |
| // that |picture_buffer| will delete on sequence, so it's okay even if |
| // |wait_complete_cb| doesn't ever run. |
| auto wait_complete_cb = BindToCurrentLoop( |
| base::BindOnce(&D3D11VideoDecoder::ReceivePictureBufferFromClient, |
| weak_factory_.GetWeakPtr(), |
| scoped_refptr<D3D11PictureBuffer>(picture_buffer))); |
| frame->SetReleaseMailboxCB( |
| base::BindOnce(release_mailbox_cb_, std::move(wait_complete_cb))); |
| |
| // For NV12, overlay is allowed by default. If the decoder is going to support |
| // non-NV12 textures, then this may have to be conditionally set. Also note |
| // that ALLOW_OVERLAY is required for encrypted video path. |
| // |
| // Since all of our picture buffers allow overlay, we just set this to true. |
| // However, we may choose to set ALLOW_OVERLAY to false even if |
| // the finch flag is enabled. We may not choose to set ALLOW_OVERLAY if the |
| // flag is off, however. |
| // |
| // Also note that, since we end up binding textures with GLImageDXGI, it's |
| // probably okay just to allow overlay always, and let the swap chain |
| // presenter decide if it wants to. |
| frame->metadata().allow_overlay = true; |
| |
| frame->metadata().power_efficient = true; |
| frame->set_color_space(output_color_space); |
| frame->set_hdr_metadata(config_.hdr_metadata()); |
| output_cb_.Run(frame); |
| return true; |
| } |
| |
| void D3D11VideoDecoder::SetDecoderCB(const SetAcceleratorDecoderCB& cb) { |
| set_accelerator_decoder_cb_ = cb; |
| } |
| |
| // TODO(tmathmeyer): Please don't add new uses of this overload. |
| void D3D11VideoDecoder::NotifyError(const char* reason) { |
| NotifyError(Status(StatusCode::kDecoderInitializeNeverCompleted, reason)); |
| } |
| |
| void D3D11VideoDecoder::NotifyError(const Status& reason) { |
| TRACE_EVENT0("gpu", "D3D11VideoDecoder::NotifyError"); |
| state_ = State::kError; |
| |
| // Log why this failed. |
| base::UmaHistogramSparse("Media.D3D11.NotifyErrorStatus", |
| static_cast<int>(reason.code())); |
| |
| if (init_cb_) { |
| std::move(init_cb_).Run(reason); |
| } else { |
| // TODO(tmathmeyer) - Remove this after plumbing Status through the |
| // decode_cb and input_buffer_queue cb's. |
| // Let the init handler set the error string if this is an init failure. |
| MEDIA_LOG(ERROR, media_log_) << "D3D11VideoDecoder error: 0x" << std::hex |
| << reason.code() << " " << reason.message(); |
| } |
| |
| current_buffer_ = nullptr; |
| if (current_decode_cb_) |
| std::move(current_decode_cb_).Run(DecodeStatus::DECODE_ERROR); |
| |
| for (auto& queue_pair : input_buffer_queue_) |
| std::move(queue_pair.second).Run(DecodeStatus::DECODE_ERROR); |
| input_buffer_queue_.clear(); |
| } |
| |
| // static |
| bool D3D11VideoDecoder::GetD3D11FeatureLevel( |
| ComD3D11Device dev, |
| const gpu::GpuDriverBugWorkarounds& gpu_workarounds, |
| D3D_FEATURE_LEVEL* feature_level) { |
| if (!dev || !feature_level) |
| return false; |
| |
| *feature_level = dev->GetFeatureLevel(); |
| if (*feature_level < D3D_FEATURE_LEVEL_11_0) |
| return false; |
| |
| // TODO(tmathmeyer) should we log this to UMA? |
| if (gpu_workarounds.limit_d3d11_video_decoder_to_11_0 && |
| !base::FeatureList::IsEnabled(kD3D11VideoDecoderIgnoreWorkarounds)) { |
| *feature_level = D3D_FEATURE_LEVEL_11_0; |
| } |
| |
| return true; |
| } |
| |
| // static |
| std::vector<SupportedVideoDecoderConfig> |
| D3D11VideoDecoder::GetSupportedVideoDecoderConfigs( |
| const gpu::GpuPreferences& gpu_preferences, |
| const gpu::GpuDriverBugWorkarounds& gpu_workarounds, |
| GetD3D11DeviceCB get_d3d11_device_cb) { |
| const std::string uma_name("Media.D3D11.WasVideoSupported"); |
| |
| if (!base::FeatureList::IsEnabled(kD3D11VideoDecoderIgnoreWorkarounds)) { |
| // Allow all of d3d11 to be turned off by workaround. |
| if (gpu_workarounds.disable_d3d11_video_decoder) { |
| UMA_HISTOGRAM_ENUMERATION(uma_name, NotSupportedReason::kOffByWorkaround); |
| return {}; |
| } |
| } |
| |
| // Remember that this might query the angle device, so this won't work if |
| // we're not on the GPU main thread. Also remember that devices are thread |
| // safe (contexts are not), so we could use the angle device from any thread |
| // as long as we're not calling into possible not-thread-safe things to get |
| // it. I.e., if this cached it, then it'd be fine. It's up to our caller |
| // to guarantee that, though. |
| // |
| // Note also that, currently, we are called from the GPU main thread only. |
| auto d3d11_device = get_d3d11_device_cb.Run(); |
| if (!d3d11_device) { |
| UMA_HISTOGRAM_ENUMERATION(uma_name, |
| NotSupportedReason::kCouldNotGetD3D11Device); |
| return {}; |
| } |
| |
| D3D_FEATURE_LEVEL usable_feature_level; |
| if (!GetD3D11FeatureLevel(d3d11_device, gpu_workarounds, |
| &usable_feature_level)) { |
| UMA_HISTOGRAM_ENUMERATION( |
| uma_name, NotSupportedReason::kInsufficientD3D11FeatureLevel); |
| return {}; |
| } |
| |
| const auto supported_resolutions = GetSupportedD3D11VideoDecoderResolutions( |
| d3d11_device, gpu_workarounds, |
| base::FeatureList::IsEnabled(kD3D11VideoDecoderAV1) && |
| !gpu_workarounds.disable_accelerated_av1_decode_d3d11); |
| |
| std::vector<SupportedVideoDecoderConfig> configs; |
| for (const auto& kv : supported_resolutions) { |
| const auto profile = kv.first; |
| if (profile == VP9PROFILE_PROFILE2 && |
| !base::FeatureList::IsEnabled(kD3D11VideoDecoderVP9Profile2)) { |
| continue; |
| } |
| |
| // TODO(liberato): Add VP8 support to D3D11VideoDecoder. |
| if (profile == VP8PROFILE_ANY) |
| continue; |
| |
| const auto& resolution_range = kv.second; |
| configs.emplace_back(profile, profile, resolution_range.min_resolution, |
| resolution_range.max_landscape_resolution, |
| /*allow_encrypted=*/false, |
| /*require_encrypted=*/false); |
| if (!resolution_range.max_portrait_resolution.IsEmpty() && |
| resolution_range.max_portrait_resolution != |
| resolution_range.max_landscape_resolution) { |
| configs.emplace_back(profile, profile, resolution_range.min_resolution, |
| resolution_range.max_portrait_resolution, |
| /*allow_encrypted=*/false, |
| /*require_encrypted=*/false); |
| } |
| } |
| |
| // TODO(liberato): Should we separate out h264 and vp9? |
| UMA_HISTOGRAM_ENUMERATION(uma_name, NotSupportedReason::kVideoIsSupported); |
| |
| return configs; |
| } |
| |
| } // namespace media |