#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
#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 {
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;
}
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
bool ShouldUseDXVADeviceForHEVCRangeExtension(const VideoDecoderConfig& config,
ComD3D11Device device) {
return config.profile() == HEVCPROFILE_REXT &&
(base::FeatureList::IsEnabled(kD3D12VideoDecoder) ||
SupportsHEVCRangeExtensionDXVAProfile(device));
}
#endif
}
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) {
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_);
LogPictureBufferUsage();
if (device_context_) {
device_context_->Flush();
}
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(),
gpu_workarounds_.use_first_valid_ref_for_av1_invalid_ref),
profile_, config.color_space_info());
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
} else if (config.codec() == VideoCodec::kHEVC) {
DCHECK(base::FeatureList::IsEnabled(kPlatformHEVCDecoderSupport));
bool use_dxva_device_for_hevc_rext =
ShouldUseDXVADeviceForHEVCRangeExtension(config, device_);
accelerated_video_decoder_ = std::make_unique<H265Decoder>(
std::make_unique<D3D11H265Accelerator>(this, media_log_.get(),
use_dxva_device_for_hevc_rext),
profile_, config.color_space_info());
#endif
} else {
NotifyError(D3D11Status::Codes::kDecoderUnsupportedCodec);
return false;
}
return true;
}
bool D3D11VideoDecoder::ResetD3DVideoDecoder() {
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_, device_);
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()) {
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;
}
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_ ||
gpu_workarounds_.disable_decode_into_array_texture;
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* ,
InitCB init_cb,
const OutputCB& output_cb,
const WaitingCB& ) {
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;
bool is_supported = false;
for (const auto& supported_config : supported_configs_) {
if (supported_config.Matches(config)) {
is_supported = true;
break;
}
}
if (gpu_workarounds_.disable_d3d11_video_decoder ||
(!is_supported && IsDecoderBuiltInVideoCodec(config.codec()))) {
return PostDecoderStatus(
DecoderStatus(DecoderStatus::Codes::kUnsupportedConfig)
.WithData("config", config));
}
if (config.is_encrypted()) {
return PostDecoderStatus(DecoderStatus::Codes::kUnsupportedEncryptionMode);
}
ComUnknown d3d_device = get_d3d_device_cb_.Run(D3DVersion::kD3D11);
if (!d3d_device) {
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_);
auto hr = device_.As(&video_device_);
if (FAILED(hr))
return NotifyError({D3D11Status::Codes::kFailedToGetVideoDevice, hr});
if (!InitializeAcceleratedDecoder(config_)) {
return;
}
if (!ResetD3DVideoDecoder()) {
return;
}
LogDecoderAdapterLUID();
MEDIA_LOG(INFO, media_log_) << "Video is supported by D3D11VideoDecoder";
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");
buffer->remove_client_use();
DoDecode();
}
void D3D11VideoDecoder::PictureBufferGPUResourceInitDone(
scoped_refptr<D3D11PictureBuffer> buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT0("gpu", "D3D11VideoDecoder::PictureBufferGPUResourceInitDone");
buffer->remove_client_use();
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_) {
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 (!decode_cb)
base::debug::DumpWithoutCrashing();
if (state_ == State::kError) {
std::move(decode_cb).Run(DecoderStatus::Codes::kInterrupted);
return;
}
const bool is_spatial_layer_buffer =
!buffer->end_of_stream() && buffer->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;
}
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;
}
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 (!current_decode_cb_)
base::debug::DumpWithoutCrashing();
input_buffer_queue_.pop_front();
if (current_buffer_->end_of_stream()) {
current_buffer_ = nullptr;
if (!accelerated_video_decoder_->Flush()) {
NotifyError(D3D11Status::Codes::kAcceleratorFlushFailed);
return;
}
std::move(current_decode_cb_).Run(DecoderStatus::Codes::kOk);
return;
} else if (current_buffer_->empty()) {
current_buffer_ = nullptr;
std::move(current_decode_cb_).Run(DecoderStatus::Codes::kOk);
return;
}
current_timestamp_ = current_buffer_->timestamp();
accelerated_video_decoder_->SetStream(-1, current_buffer_);
}
while (true) {
if (state_ == State::kError)
return;
if (!current_buffer_)
break;
if (!current_decode_cb_) {
base::debug::DumpWithoutCrashing();
current_buffer_ = nullptr;
break;
}
media::AcceleratedVideoDecoder::DecodeResult result =
accelerated_video_decoder_->Decode();
if (state_ == State::kError) {
return;
}
if (result == media::AcceleratedVideoDecoder::kRanOutOfStreamData) {
current_buffer_ = nullptr;
std::move(current_decode_cb_).Run(DecoderStatus::Codes::kOk);
break;
} else if (result == media::AcceleratedVideoDecoder::kRanOutOfSurfaces) {
if (picture_buffers_.size())
return;
CreatePictureBuffers();
} else if (result == media::AcceleratedVideoDecoder::kConfigChange) {
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;
}
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;
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();
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_.empty()) {
return true;
}
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() {
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();
}
LogPictureBufferUsage();
for (auto& buffer : picture_buffers_)
DCHECK(!buffer->in_picture_use());
picture_buffers_.clear();
ComD3D11Texture2D in_texture;
size_t pic_buffers_required =
accelerated_video_decoder_->GetRequiredNumOfPictures() + 1;
for (size_t i = 0; i < pic_buffers_required; i++) {
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, i));
base::OnceCallback<void(scoped_refptr<media::D3D11PictureBuffer>)>
picture_buffer_gpu_resource_init_done_cb = base::DoNothing();
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 (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) {
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();
gfx::Rect visible_rect = picture->visible_rect();
if (visible_rect.IsEmpty())
visible_rect = config_.visible_rect();
gfx::Size natural_size = config_.aspect_ratio().GetNaturalSize(visible_rect);
base::TimeDelta timestamp = picture_buffer->timestamp_;
auto picture_color_space = picture->get_colorspace().ToGfxColorSpace();
if (!picture_color_space.IsValid()) {
picture_color_space = config_.color_space_info().ToGfxColorSpace();
}
scoped_refptr<gpu::ClientSharedImage> shared_image;
D3D11Status result =
picture_buffer->ProcessTexture(picture_color_space, shared_image);
if (!result.is_ok()) {
NotifyError(std::move(result).AddHere());
return false;
}
picture_color_space = shared_image->color_space();
scoped_refptr<VideoFrame> frame = VideoFrame::WrapSharedImage(
texture_selector_->PixelFormat(), shared_image,
shared_image->creation_sync_token(), VideoFrame::ReleaseMailboxCB(),
picture_buffer->size(), visible_rect, natural_size, timestamp);
if (!frame) {
PostDecoderStatus(DecoderStatus::Codes::kFailedToGetVideoFrame);
return false;
}
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)));
frame->metadata().allow_overlay = true;
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()) {
frame->set_hdr_metadata(picture->hdr_metadata() ? picture->hdr_metadata()
: config_.hdr_metadata());
}
frame->metadata().is_webgpu_compatible =
!(gpu_workarounds_.disable_sharing_nv12_from_d3d11_to_d3d12 &&
texture_selector_->OutputDXGIFormat() == DXGI_FORMAT_NV12) &&
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);
}
input_buffer_queue_.clear();
}
void D3D11VideoDecoder::MeasurePictureBufferUsage() {
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;
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 << "}";
}
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;
}
std::vector<SupportedVideoDecoderConfig>
D3D11VideoDecoder::GetSupportedVideoDecoderConfigs(
const gpu::GpuPreferences& gpu_preferences,
const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
GetD3DDeviceCB get_d3d_device_cb) {
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 {
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;
const auto& resolution_range = kv.second;
DCHECK(!resolution_range.min_resolution.IsEmpty());
DCHECK(!resolution_range.max_landscape_resolution.IsEmpty());
configs.emplace_back(profile, profile, resolution_range.min_resolution,
resolution_range.max_landscape_resolution,
false,
false);
if (resolution_range.max_portrait_resolution) {
configs.emplace_back(profile, profile, resolution_range.min_resolution,
*resolution_range.max_portrait_resolution,
false,
false);
}
}
return configs;
}
}