// 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.
#include "media/gpu/windows/d3d11_video_decoder.h"
#include <memory>
#include <utility>
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "media/base/decoder_buffer.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/supported_types.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/gpu/windows/d3d11_av1_accelerator.h"
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
#include "media/gpu/windows/d3d11_h265_accelerator.h"
#endif // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
#include "media/gpu/windows/d3d11_picture_buffer.h"
#include "media/gpu/windows/d3d11_status.h"
#include "media/gpu/windows/d3d11_video_device_format_support.h"
#include "media/gpu/windows/d3d11_video_frame_mailbox_release_helper.h"
#include "media/gpu/windows/d3d12_video_decoder_wrapper.h"
#include "media/gpu/windows/supported_profile_helpers.h"
#include "media/media_buildflags.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/hdr_metadata.h"
#include "ui/gl/gl_angle_util_win.h"
#include "ui/gl/gl_switches.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)) {}
CommandBufferHelperHolder(const CommandBufferHelperHolder&) = delete;
CommandBufferHelperHolder& operator=(const CommandBufferHelperHolder&) =
delete;
scoped_refptr<CommandBufferHelper> helper;
private:
~CommandBufferHelperHolder() = default;
friend class base::RefCountedDeleteOnSequence<CommandBufferHelperHolder>;
friend class base::DeleteHelper<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::GetD3DDeviceCB get_d3d_device_cb,
SupportedConfigs supported_configs) {
// 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.
auto get_helper_cb = base::BindRepeating(
CreateCommandBufferHelper, std::move(get_stub_cb),
base::MakeRefCounted<CommandBufferHelperHolder>(gpu_task_runner));
return base::WrapUnique<VideoDecoder>(new D3D11VideoDecoder(
gpu_task_runner, std::move(media_log), gpu_preferences, gpu_workarounds,
get_helper_cb, std::move(get_d3d_device_cb),
std::move(supported_configs)));
}
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::RepeatingCallback<scoped_refptr<CommandBufferHelper>()> get_helper_cb,
GetD3DDeviceCB get_d3d_device_cb,
SupportedConfigs supported_configs)
: media_log_(std::move(media_log)),
mailbox_release_helper_(
base::MakeRefCounted<D3D11VideoFrameMailboxReleaseHelper>(
media_log_->Clone(),
get_helper_cb)),
gpu_task_runner_(std::move(gpu_task_runner)),
decoder_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
gpu_preferences_(gpu_preferences),
gpu_workarounds_(gpu_workarounds),
get_d3d_device_cb_(std::move(get_d3d_device_cb)),
get_helper_cb_(std::move(get_helper_cb)),
supported_configs_(std::move(supported_configs)),
use_shared_handle_(
base::FeatureList::IsEnabled(kD3D12VideoDecoder) ||
base::FeatureList::IsEnabled(kD3D11VideoDecoderUseSharedHandle)) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(media_log_);
}
D3D11VideoDecoder::~D3D11VideoDecoder() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Log whatever usage we measured, if any.
LogPictureBufferUsage();
// Explicitly destroy the decoder, since it can reference picture buffers.
accelerated_video_decoder_.reset();
}
VideoDecoderType D3D11VideoDecoder::GetDecoderType() const {
return VideoDecoderType::kD3D11;
}
bool D3D11VideoDecoder::InitializeAcceleratedDecoder(
const VideoDecoderConfig& config) {
TRACE_EVENT0("gpu", "D3D11VideoDecoder::InitializeAcceleratedDecoder");
profile_ = config.profile();
if (config.codec() == VideoCodec::kVP9) {
accelerated_video_decoder_ = std::make_unique<VP9Decoder>(
std::make_unique<D3D11VP9Accelerator>(this, media_log_.get()), 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()),
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()), profile_,
config.color_space_info());
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
} else if (config.codec() == VideoCodec::kHEVC) {
DCHECK(base::FeatureList::IsEnabled(kPlatformHEVCDecoderSupport));
accelerated_video_decoder_ = std::make_unique<H265Decoder>(
std::make_unique<D3D11H265Accelerator>(this, media_log_.get()),
profile_, config.color_space_info());
#endif // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
} else {
NotifyError(D3D11Status::Codes::kDecoderUnsupportedCodec);
return false;
}
return true;
}
bool D3D11VideoDecoder::ResetD3DVideoDecoder() {
// By default we assume outputs are 8-bit for SDR color spaces and 10 bit for
// HDR color spaces (or VP9.2, or HEVC Main10, or HEVC Rext) with HBD capable
// codecs (the decoder doesn't support H264PROFILE_HIGH10PROFILE). We'll get
// a config change once we know the real bit depth if this turns out to be
// wrong.
uint8_t bit_depth = 0;
if (accelerated_video_decoder_) {
bit_depth = accelerated_video_decoder_->GetBitDepth();
}
if (!bit_depth) {
bit_depth =
(config_.profile() == VP9PROFILE_PROFILE2 ||
config_.profile() == HEVCPROFILE_REXT ||
config_.profile() == HEVCPROFILE_MAIN10 ||
(config_.color_space_info().GuessGfxColorSpace().IsHDR() &&
config_.codec() != VideoCodec::kH264 &&
config_.profile() != HEVCPROFILE_MAIN &&
config_.profile() != HEVCPROFILE_MAIN_STILL_PICTURE)
? 10
: 8);
}
auto decoder_configurator = D3D11DecoderConfigurator::Create(
gpu_preferences_, gpu_workarounds_, config_, bit_depth, chroma_sampling_,
media_log_.get(), use_shared_handle_);
if (!decoder_configurator) {
NotifyError(D3D11StatusCode::kDecoderUnsupportedProfile);
return false;
}
if (!decoder_configurator->SupportsDevice(video_device_)) {
NotifyError(D3D11StatusCode::kDecoderUnsupportedCodec);
return false;
}
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";
}
auto texture_selector = TextureSelector::Create(
gpu_preferences_, gpu_workarounds_, decoder_configurator->TextureFormat(),
&format_checker, video_device_, device_context_, media_log_.get(),
config_.color_space_info().ToGfxColorSpace(), use_shared_handle_);
if (!texture_selector) {
NotifyError(D3D11StatusCode::kCreateTextureSelectorFailed);
return false;
}
auto video_decoder_wrapper =
CreateD3DVideoDecoderWrapper(decoder_configurator.get(), bit_depth);
if (!video_decoder_wrapper) {
return false;
}
// Replace the re-created members after all error-checking passes.
bit_depth_ = bit_depth;
decoder_configurator_ = std::move(decoder_configurator);
texture_selector_ = std::move(texture_selector);
d3d_video_decoder_wrapper_ = std::move(video_decoder_wrapper);
return true;
}
std::unique_ptr<D3DVideoDecoderWrapper>
D3D11VideoDecoder::CreateD3DVideoDecoderWrapper(
D3D11DecoderConfigurator* decoder_configurator,
uint8_t bit_depth) {
CHECK(decoder_configurator);
std::unique_ptr<D3DVideoDecoderWrapper> video_decoder_wrapper;
if (base::FeatureList::IsEnabled(kD3D12VideoDecoder)) {
MEDIA_LOG(INFO, media_log_) << "D3D11VideoDecoder is using D3D12 backend";
ComUnknown d3d_device = get_d3d_device_cb_.Run(D3DVersion::kD3D12);
if (!d3d_device) {
NotifyError({D3D11StatusCode::kUnsupportedFeatureLevel,
"Cannot create D3D12Device"});
return nullptr;
}
ComD3D12Device device;
CHECK_EQ(d3d_device.As(&device), S_OK);
ComD3D12VideoDevice video_device;
HRESULT hr = device.As(&video_device);
if (FAILED(hr)) {
NotifyError({D3D11StatusCode::kFailedToGetVideoDevice,
"Cannot create D3D12VideoDevice", hr});
return nullptr;
}
video_decoder_wrapper = D3D12VideoDecoderWrapper::Create(
media_log_.get(), video_device, config_, bit_depth, chroma_sampling_);
} else {
MEDIA_LOG(INFO, media_log_) << "D3D11VideoDecoder is using D3D11 backend";
ComD3D11VideoContext video_context;
CHECK_EQ(device_context_.As(&video_context), S_OK);
video_decoder_wrapper = D3D11VideoDecoderWrapper::Create(
media_log_.get(), video_device_, std::move(video_context),
decoder_configurator, usable_feature_level_, config_);
}
if (!video_decoder_wrapper) {
NotifyError({D3D11StatusCode::kDecoderCreationFailed,
"D3DVideoDecoderWrapper is not created"});
return nullptr;
}
auto use_single_texture = video_decoder_wrapper->UseSingleTexture();
if (!use_single_texture.has_value()) {
NotifyError({D3D11StatusCode::kGetDecoderConfigFailed,
"GetSingleTextureRecommended failed"});
return nullptr;
}
use_single_video_decoder_texture_ =
use_single_texture.value() | 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";
}
return video_decoder_wrapper;
}
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");
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 we don't have support support for a given codec, try to initialize
// anyways -- otherwise we're certain to fail playback.
if (gpu_workarounds_.disable_d3d11_video_decoder ||
(!is_supported && IsBuiltInVideoCodec(config.codec()))) {
return PostDecoderStatus(
DecoderStatus(DecoderStatus::Codes::kUnsupportedConfig)
.WithData("config", config));
}
if (config.is_encrypted()) {
return PostDecoderStatus(DecoderStatus::Codes::kUnsupportedEncryptionMode);
}
// 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.
ComUnknown d3d_device = get_d3d_device_cb_.Run(D3DVersion::kD3D11);
if (!d3d_device) {
// This happens if, for example, if chrome is configured to use
// D3D9 for ANGLE.
return NotifyError(D3D11Status::Codes::kFailedToGetAngleDevice);
}
CHECK_EQ(d3d_device.As(&device_), S_OK);
if (!GetD3D11FeatureLevel(device_, gpu_workarounds_,
&usable_feature_level_)) {
return NotifyError(D3D11Status::Codes::kUnsupportedFeatureLevel);
}
device_->GetImmediateContext(&device_context_);
// TODO(liberato): Handle cleanup better. Also consider being less chatty in
// the logs, since this will fall back.
auto hr = device_.As(&video_device_);
if (FAILED(hr))
return NotifyError({D3D11Status::Codes::kFailedToGetVideoDevice, hr});
if (!InitializeAcceleratedDecoder(config_)) {
return;
}
if (!ResetD3DVideoDecoder()) {
return;
}
LogDecoderAdapterLUID();
// 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";
// Initialize `mailbox_release_helper_` so we have a ReleaseMailboxCB which
// knows how to wait for SyncToken resolution. No need to reinitialize if
// we've done it once.
if (release_mailbox_cb_) {
OnGpuInitComplete(true, release_mailbox_cb_);
return;
}
auto mailbox_helper_init_cb = base::BindOnce(
&D3D11VideoDecoder::OnGpuInitComplete, weak_factory_.GetWeakPtr());
if (gpu_task_runner_->BelongsToCurrentThread()) {
mailbox_release_helper_->Initialize(std::move(mailbox_helper_init_cb));
} else {
gpu_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&D3D11VideoFrameMailboxReleaseHelper::Initialize,
mailbox_release_helper_,
base::BindPostTaskToCurrentDefault(
std::move(mailbox_helper_init_cb))));
}
}
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::PictureBufferGPUResourceInitDone(
scoped_refptr<D3D11PictureBuffer> buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT0("gpu", "D3D11VideoDecoder::PictureBufferGPUResourceInitDone");
buffer->remove_client_use();
// Picture buffer gpu resource may not be ready when D3D11VideoDecoder
// initialization finished. In that case, use PictureBuffer::in_client_use()
// to pause decoder through media::AcceleratedVideoDecoder::kRanOutOfSurfaces
// state. Then restart decoding after picture buffer gpu resource
// initialization finished.
if (state_ == State::kRunning) {
DoDecode();
}
}
void D3D11VideoDecoder::OnGpuInitComplete(
bool success,
D3D11VideoFrameMailboxReleaseHelper::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) {
return NotifyError(D3D11Status::Codes::kFailedToInitializeGPUProcess);
}
release_mailbox_cb_ = std::move(release_mailbox_cb);
state_ = State::kRunning;
std::move(init_cb_).Run(DecoderStatus::Codes::kOk);
}
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(DecoderStatus::Codes::kInterrupted);
return;
}
const bool is_spatial_layer_buffer =
buffer->has_side_data() && !buffer->side_data()->spatial_layers.empty();
input_buffer_queue_.push_back(
std::make_pair(std::move(buffer), std::move(decode_cb)));
if (config_.codec() == VideoCodec::kVP9 && is_spatial_layer_buffer &&
gpu_workarounds_.disable_d3d11_vp9_ksvc_decoding) {
PostDecoderStatus(DecoderStatus::Codes::kPlatformDecodeFailure);
return;
}
// 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::SingleThreadTaskRunner::GetCurrentDefault()->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;
}
// Periodically measure picture buffer usage. We could do this on every free,
// but it's not that important that we should run it so often.
if (picture_buffers_.size() > 0) {
if (!decode_count_until_picture_buffer_measurement_) {
MeasurePictureBufferUsage();
decode_count_until_picture_buffer_measurement_ = picture_buffers_.size();
} else {
decode_count_until_picture_buffer_measurement_--;
}
}
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(D3D11Status::Codes::kAcceleratorFlushFailed);
return;
}
// Pictures out output synchronously during Flush. Signal the decode
// cb now.
std::move(current_decode_cb_).Run(DecoderStatus::Codes::kOk);
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(DecoderStatus::Codes::kOk);
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();
const auto new_chroma_sampling =
accelerated_video_decoder_->GetChromaSampling();
const auto new_color_space =
accelerated_video_decoder_->GetVideoColorSpace();
if (new_profile == config_.profile() &&
new_coded_size == config_.coded_size() &&
new_bit_depth == bit_depth_ && !picture_buffers_.size() &&
new_chroma_sampling == chroma_sampling_ &&
new_color_space == color_space_) {
continue;
}
// Update the config.
MEDIA_LOG(INFO, media_log_)
<< "D3D11VideoDecoder config change: profile: "
<< GetProfileName(new_profile) << ", chroma_sampling_format: "
<< VideoChromaSamplingToString(new_chroma_sampling)
<< ", coded_size: " << new_coded_size.ToString()
<< ", bit_depth: " << base::strict_cast<int>(new_bit_depth)
<< ", color_space: " << new_color_space.ToString();
profile_ = new_profile;
config_.set_profile(profile_);
config_.set_coded_size(new_coded_size);
chroma_sampling_ = new_chroma_sampling;
color_space_ = new_color_space;
// 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.
if (!ResetD3DVideoDecoder()) {
return;
}
picture_buffers_.clear();
} else if (result == media::AcceleratedVideoDecoder::kTryAgain) {
LOG(ERROR) << "Try again is not supported";
NotifyError(D3D11Status::Codes::kTryAgainNotSupported);
return;
} else {
return NotifyError(D3D11Status(D3D11Status::Codes::kDecoderFailedDecode)
.WithData("VDA Error", result));
}
}
base::SingleThreadTaskRunner::GetCurrentDefault()->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(DecoderStatus::Codes::kAborted);
for (auto& queue_pair : input_buffer_queue_)
std::move(queue_pair.second).Run(DecoderStatus::Codes::kAborted);
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_);
// If picture buffers haven't been created yet, the client can read.
if (picture_buffers_.empty()) {
return true;
}
// If we haven't given all our picture buffers to the client, it can read.
for (const auto& buffer : picture_buffers_) {
if (!buffer->in_client_use()) {
return true;
}
}
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::ColorSpace color_space =
accelerated_video_decoder_->GetVideoColorSpace().ToGfxColorSpace();
if (!color_space.IsValid()) {
color_space = config_.color_space_info().ToGfxColorSpace();
}
// Since we are about to allocate new picture buffers, record whatever usage
// we had for the outgoing ones, if any.
LogPictureBufferUsage();
// Drop any old pictures.
for (auto& buffer : picture_buffers_)
DCHECK(!buffer->in_picture_use());
picture_buffers_.clear();
ComD3D11Texture2D in_texture;
// In addition to what the decoder needs, add one picture buffer
// for overlay weirdness, just to be safe. We may need to track
// actual used buffers for all use cases and decide an optimal
// number of picture buffers.
size_t pic_buffers_required =
accelerated_video_decoder_->GetRequiredNumOfPictures() + 1;
// Create each picture buffer.
for (size_t i = 0; i < pic_buffers_required; 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 : pic_buffers_required,
texture_selector_->DoesDecoderOutputUseSharedHandle());
if (result.has_value()) {
in_texture = std::move(result).value();
} else {
return NotifyError(std::move(result).error().AddHere());
}
}
DCHECK(!!in_texture);
auto tex_wrapper =
texture_selector_->CreateTextureWrapper(device_, color_space, size);
if (!tex_wrapper) {
return NotifyError(
D3D11Status::Codes::kAllocateTextureForCopyingWrapperFailed);
}
const size_t array_slice = use_single_video_decoder_texture_ ? 0 : i;
picture_buffers_.push_back(base::MakeRefCounted<D3D11PictureBuffer>(
decoder_task_runner_, in_texture, array_slice, std::move(tex_wrapper),
size, /*level=*/i));
base::OnceCallback<void(scoped_refptr<media::D3D11PictureBuffer>)>
picture_buffer_gpu_resource_init_done_cb = base::DoNothing();
// WebGPU requires interop on the picture buffer to achieve zero copy.
// This requires a picture buffer to produce a shared image representation
// during initialization. Add picture buffer in_client_use count to idle
// the decoder until picture buffer finished gpu resource initialization
// in gpu thread.
picture_buffers_[i]->add_client_use();
picture_buffer_gpu_resource_init_done_cb =
base::BindPostTaskToCurrentDefault(
base::BindOnce(&D3D11VideoDecoder::PictureBufferGPUResourceInitDone,
weak_factory_.GetWeakPtr()));
D3D11Status result = picture_buffers_[i]->Init(
gpu_task_runner_, get_helper_cb_, video_device_,
decoder_configurator_->DecoderGuid(), media_log_->Clone(),
std::move(picture_buffer_gpu_resource_init_done_cb));
if (!result.is_ok()) {
return NotifyError(std::move(result).AddHere());
}
// 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;
}
}
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(crbug.com/41389060): 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_;
// Prefer the frame color space over what's in the config.
auto picture_color_space = picture->get_colorspace().ToGfxColorSpace();
if (!picture_color_space.IsValid()) {
picture_color_space = config_.color_space_info().ToGfxColorSpace();
}
media::ClientSharedImageOrMailboxHolder shared_image;
D3D11Status result =
picture_buffer->ProcessTexture(picture_color_space, shared_image);
if (!result.is_ok()) {
NotifyError(std::move(result).AddHere());
return false;
}
scoped_refptr<VideoFrame> frame;
if (absl::holds_alternative<scoped_refptr<gpu::ClientSharedImage>>(
shared_image)) {
scoped_refptr<gpu::ClientSharedImage> client_shared_image =
absl::get<scoped_refptr<gpu::ClientSharedImage>>(
std::move(shared_image));
frame = VideoFrame::WrapSharedImage(
texture_selector_->PixelFormat(), client_shared_image,
client_shared_image->creation_sync_token(), GL_TEXTURE_EXTERNAL_OES,
VideoFrame::ReleaseMailboxCB(), picture_buffer->size(), visible_rect,
natural_size, timestamp);
} else {
gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes] = {
absl::get<gpu::MailboxHolder>(std::move(shared_image))};
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.
PostDecoderStatus(DecoderStatus::Codes::kFailedToGetVideoFrame);
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 = base::BindPostTaskToCurrentDefault(
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.
frame->metadata().allow_overlay = true;
// The swapchain presenter currently only supports NV12 and P010 overlay,
// with BGRA only as fallback when DWM continuously fails to overlay submitted
// video. As a result, we should not allow overlay for non-NV12/P010 formats
// which may cause chroma downsampling when blitting into the back buffer.
// See https://crbugs.com/331679628 for more details.
if (!config_.is_encrypted()) {
frame->metadata().allow_overlay =
texture_selector_->OutputDXGIFormat() == DXGI_FORMAT_P010 ||
texture_selector_->OutputDXGIFormat() == DXGI_FORMAT_NV12;
}
frame->metadata().power_efficient = true;
frame->set_color_space(picture_color_space);
if (picture_color_space.IsHDR()) {
// Some streams may have varying metadata, so bitstream metadata should be
// preferred over metadata provide by the configuration.
frame->set_hdr_metadata(picture->hdr_metadata() ? picture->hdr_metadata()
: config_.hdr_metadata());
}
frame->set_shared_image_format_type(
SharedImageFormatType::kSharedImageFormat);
frame->metadata().is_webgpu_compatible = use_shared_handle_;
output_cb_.Run(frame);
return true;
}
D3DVideoDecoderWrapper* D3D11VideoDecoder::GetWrapper() {
return d3d_video_decoder_wrapper_.get();
}
void D3D11VideoDecoder::NotifyError(D3D11Status reason,
DecoderStatus::Codes opt_decoder_code) {
TRACE_EVENT0("gpu", "D3D11VideoDecoder::NotifyError");
if (!reason.is_ok() && !reason.message().empty()) {
MEDIA_LOG(ERROR, media_log_) << "D3D11VideoDecoder: " << reason.message();
}
PostDecoderStatus(
DecoderStatus(opt_decoder_code).AddCause(std::move(reason)));
}
void D3D11VideoDecoder::PostDecoderStatus(DecoderStatus status) {
TRACE_EVENT0("gpu", "D3D11VideoDecoder::PostDecoderStatus");
state_ = State::kError;
current_buffer_ = nullptr;
if (init_cb_) {
std::move(init_cb_).Run(status);
}
if (current_decode_cb_) {
std::move(current_decode_cb_).Run(status);
}
for (auto& queue_pair : input_buffer_queue_) {
std::move(queue_pair.second).Run(status);
}
// Also clear |input_buffer_queue_| since the callbacks have been consumed.
input_buffer_queue_.clear();
}
void D3D11VideoDecoder::MeasurePictureBufferUsage() {
// Count the total number of buffers that are currently unused by either the
// client or the decoder. These are buffers that we didn't need to allocate.
int unused_buffers = 0;
for (const auto& buffer : picture_buffers_) {
if (!buffer->in_client_use() && !buffer->in_picture_use())
unused_buffers++;
}
if (!min_unused_buffers_ || unused_buffers < *min_unused_buffers_) {
min_unused_buffers_ = unused_buffers;
}
}
void D3D11VideoDecoder::LogPictureBufferUsage() {
if (!min_unused_buffers_)
return;
// Record these separately because (a) we could potentially fix the
// MultiTexture case pretty easily, and (b) we have no idea how often we're in
// one mode vs the other. This will let us know if there is enough usage of
// MultiTexture and also enough unused textures that it's worth fixing. Note
// that this assumes that we would lazily allocate buffers but not free them,
// and is a lower bound on savings.
if (use_single_video_decoder_texture_) {
UMA_HISTOGRAM_COUNTS_100(
"Media.D3D11VideoDecoder.UnusedPictureBufferCount.SingleTexture",
*min_unused_buffers_);
} else {
UMA_HISTOGRAM_COUNTS_100(
"Media.D3D11VideoDecoder.UnusedPictureBufferCount.MultiTexture",
*min_unused_buffers_);
}
min_unused_buffers_.reset();
}
void D3D11VideoDecoder::LogDecoderAdapterLUID() {
if (!device_)
return;
ComDXGIDevice dxgi_device;
HRESULT hr = device_.As(&dxgi_device);
if (FAILED(hr))
return;
ComDXGIAdapter dxgi_adapter;
hr = dxgi_device->GetAdapter(&dxgi_adapter);
if (FAILED(hr))
return;
DXGI_ADAPTER_DESC adapter_desc{};
hr = dxgi_adapter->GetDesc(&adapter_desc);
if (FAILED(hr))
return;
MEDIA_LOG(INFO, media_log_) << "Selected D3D11VideoDecoder adapter LUID:{"
<< adapter_desc.AdapterLuid.HighPart << ", "
<< adapter_desc.AdapterLuid.LowPart << "}";
}
// 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;
if (gpu_workarounds.limit_d3d11_video_decoder_to_11_0)
*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,
GetD3DDeviceCB get_d3d_device_cb) {
// Allow all of d3d11 to be turned off by workaround.
if (gpu_workarounds.disable_d3d11_video_decoder)
return {};
SupportedResolutionRangeMap supported_resolutions;
if (base::FeatureList::IsEnabled(kD3D12VideoDecoder)) {
auto d3d_device = get_d3d_device_cb.Run(D3DVersion::kD3D12);
if (!d3d_device) {
return {};
}
ComD3D12Device d3d12_device;
CHECK_EQ(d3d_device.As(&d3d12_device), S_OK);
supported_resolutions =
GetSupportedD3D12VideoDecoderResolutions(d3d12_device, gpu_workarounds);
} else {
// 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 d3d_device = get_d3d_device_cb.Run(D3DVersion::kD3D11);
if (!d3d_device) {
return {};
}
ComD3D11Device d3d11_device;
CHECK_EQ(d3d_device.As(&d3d11_device), S_OK);
D3D_FEATURE_LEVEL usable_feature_level;
if (!GetD3D11FeatureLevel(d3d11_device, gpu_workarounds,
&usable_feature_level)) {
return {};
}
supported_resolutions =
GetSupportedD3D11VideoDecoderResolutions(d3d11_device, gpu_workarounds);
}
std::vector<SupportedVideoDecoderConfig> configs;
for (const auto& kv : supported_resolutions) {
const auto profile = kv.first;
// 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);
}
}
return configs;
}
} // namespace media