blob: 83dcc1635272e2937e969e74cd204b6f533bf6bc [file] [log] [blame]
// Copyright 2018 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_vp9_accelerator.h"
#include <windows.h>
#include <string>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "media/gpu/windows/d3d11_vp9_picture.h"
namespace media {
using DecodeStatus = VP9Decoder::VP9Accelerator::Status;
#define RETURN_ON_HR_FAILURE(expr_name, expr, code) \
do { \
HRESULT expr_value = (expr); \
if (FAILED(expr_value)) { \
RecordFailure(#expr_name, logging::SystemErrorCodeToString(expr_value), \
code); \
return false; \
} \
} while (0)
std::vector<D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK>
CreateSubsampleMappingBlock(const std::vector<SubsampleEntry>& from) {
std::vector<D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK> to(from.size());
for (const auto& entry : from) {
D3D11_VIDEO_DECODER_SUB_SAMPLE_MAPPING_BLOCK subsample = {
.ClearSize = entry.clear_bytes, .EncryptedSize = entry.cypher_bytes};
to.push_back(subsample);
}
return to;
}
D3D11VP9Accelerator::D3D11VP9Accelerator(
D3D11VideoDecoderClient* client,
MediaLog* media_log,
ComD3D11VideoDevice video_device,
std::unique_ptr<VideoContextWrapper> video_context)
: client_(client),
media_log_(media_log),
status_feedback_(0),
video_device_(std::move(video_device)),
video_context_(std::move(video_context)) {
DCHECK(client);
DCHECK(media_log_);
client->SetDecoderCB(base::BindRepeating(
&D3D11VP9Accelerator::SetVideoDecoder, base::Unretained(this)));
}
D3D11VP9Accelerator::~D3D11VP9Accelerator() {}
void D3D11VP9Accelerator::RecordFailure(const std::string& fail_type,
media::Status error) {
RecordFailure(fail_type, error.message(), error.code());
}
void D3D11VP9Accelerator::RecordFailure(const std::string& fail_type,
const std::string& reason,
StatusCode code) {
MEDIA_LOG(ERROR, media_log_)
<< "DX11VP9Failure(" << fail_type << ")=" << reason;
base::UmaHistogramSparse("Media.D3D11.VP9Status", static_cast<int>(code));
}
scoped_refptr<VP9Picture> D3D11VP9Accelerator::CreateVP9Picture() {
D3D11PictureBuffer* picture_buffer = client_->GetPicture();
if (!picture_buffer)
return nullptr;
return base::MakeRefCounted<D3D11VP9Picture>(picture_buffer, client_);
}
bool D3D11VP9Accelerator::BeginFrame(const D3D11VP9Picture& pic) {
const bool is_encrypted = pic.decrypt_config();
if (is_encrypted) {
RecordFailure("crypto_config",
"Cannot find the decrypt context for the frame.",
StatusCode::kCryptoConfigFailed);
return false;
}
HRESULT hr;
do {
ID3D11VideoDecoderOutputView* output_view = nullptr;
auto result = pic.picture_buffer()->AcquireOutputView();
if (result.has_value()) {
output_view = std::move(result).value();
} else {
media::Status error = std::move(result).error();
RecordFailure("AcquireOutputView", error.message(), error.code());
return false;
}
hr = video_context_->DecoderBeginFrame(video_decoder_.Get(), output_view, 0,
nullptr);
} while (hr == E_PENDING || hr == D3DERR_WASSTILLDRAWING);
if (FAILED(hr)) {
RecordFailure("DecoderBeginFrame", logging::SystemErrorCodeToString(hr),
StatusCode::kDecoderBeginFrameFailed);
return false;
}
return true;
}
void D3D11VP9Accelerator::CopyFrameParams(const D3D11VP9Picture& pic,
DXVA_PicParams_VP9* pic_params) {
#define SET_PARAM(a, b) pic_params->a = pic.frame_hdr->b
#define COPY_PARAM(a) SET_PARAM(a, a)
COPY_PARAM(profile);
COPY_PARAM(show_frame);
COPY_PARAM(error_resilient_mode);
COPY_PARAM(refresh_frame_context);
COPY_PARAM(frame_parallel_decoding_mode);
COPY_PARAM(intra_only);
COPY_PARAM(frame_context_idx);
COPY_PARAM(reset_frame_context);
COPY_PARAM(allow_high_precision_mv);
COPY_PARAM(frame_parallel_decoding_mode);
COPY_PARAM(intra_only);
COPY_PARAM(frame_context_idx);
COPY_PARAM(allow_high_precision_mv);
// extra_plane is initialized to zero.
pic_params->BitDepthMinus8Luma = pic_params->BitDepthMinus8Chroma =
pic.frame_hdr->bit_depth - 8;
pic_params->CurrPic.Index7Bits = pic.picture_index();
pic_params->frame_type = !pic.frame_hdr->IsKeyframe();
COPY_PARAM(subsampling_x);
COPY_PARAM(subsampling_y);
SET_PARAM(width, frame_width);
SET_PARAM(height, frame_height);
SET_PARAM(interp_filter, interpolation_filter);
SET_PARAM(log2_tile_cols, tile_cols_log2);
SET_PARAM(log2_tile_rows, tile_rows_log2);
#undef COPY_PARAM
#undef SET_PARAM
// This is taken, approximately, from libvpx.
gfx::Size this_frame_size(pic.frame_hdr->frame_width,
pic.frame_hdr->frame_height);
pic_params->use_prev_in_find_mv_refs = last_frame_size_ == this_frame_size &&
!pic.frame_hdr->error_resilient_mode &&
!pic.frame_hdr->intra_only &&
last_show_frame_;
// TODO(liberato): So, uh, do we ever need to reset this?
last_frame_size_ = this_frame_size;
last_show_frame_ = pic.frame_hdr->show_frame;
}
void D3D11VP9Accelerator::CopyReferenceFrames(
const D3D11VP9Picture& pic,
DXVA_PicParams_VP9* pic_params,
const Vp9ReferenceFrameVector& ref_frames) {
D3D11_TEXTURE2D_DESC texture_descriptor;
pic.picture_buffer()->Texture()->GetDesc(&texture_descriptor);
for (size_t i = 0; i < base::size(pic_params->ref_frame_map); i++) {
auto ref_pic = ref_frames.GetFrame(i);
if (ref_pic) {
scoped_refptr<D3D11VP9Picture> our_ref_pic(
static_cast<D3D11VP9Picture*>(ref_pic.get()));
pic_params->ref_frame_map[i].Index7Bits = our_ref_pic->picture_index();
pic_params->ref_frame_coded_width[i] = texture_descriptor.Width;
pic_params->ref_frame_coded_height[i] = texture_descriptor.Height;
} else {
pic_params->ref_frame_map[i].bPicEntry = 0xff;
pic_params->ref_frame_coded_width[i] = 0;
pic_params->ref_frame_coded_height[i] = 0;
}
}
}
void D3D11VP9Accelerator::CopyFrameRefs(DXVA_PicParams_VP9* pic_params,
const D3D11VP9Picture& pic) {
for (size_t i = 0; i < base::size(pic_params->frame_refs); i++) {
pic_params->frame_refs[i] =
pic_params->ref_frame_map[pic.frame_hdr->ref_frame_idx[i]];
}
for (size_t i = 0; i < base::size(pic_params->ref_frame_sign_bias); i++) {
pic_params->ref_frame_sign_bias[i] = pic.frame_hdr->ref_frame_sign_bias[i];
}
}
void D3D11VP9Accelerator::CopyLoopFilterParams(
DXVA_PicParams_VP9* pic_params,
const Vp9LoopFilterParams& loop_filter_params) {
#define SET_PARAM(a, b) pic_params->a = loop_filter_params.b
SET_PARAM(filter_level, level);
SET_PARAM(sharpness_level, sharpness);
SET_PARAM(mode_ref_delta_enabled, delta_enabled);
SET_PARAM(mode_ref_delta_update, delta_update);
#undef SET_PARAM
// base::size(...) doesn't work well in an array initializer.
DCHECK_EQ(4lu, base::size(pic_params->ref_deltas));
for (size_t i = 0; i < base::size(pic_params->ref_deltas); i++) {
// The update_ref_deltas[i] is _only_ for parsing! it allows omission of the
// 6 bytes that would otherwise be needed for a new value to overwrite the
// global one. It has nothing to do with setting the ref_deltas here.
pic_params->ref_deltas[i] = loop_filter_params.ref_deltas[i];
}
DCHECK_EQ(2lu, base::size(pic_params->mode_deltas));
for (size_t i = 0; i < base::size(pic_params->mode_deltas); i++) {
pic_params->mode_deltas[i] = loop_filter_params.mode_deltas[i];
}
}
void D3D11VP9Accelerator::CopyQuantParams(DXVA_PicParams_VP9* pic_params,
const D3D11VP9Picture& pic) {
#define SET_PARAM(a, b) pic_params->a = pic.frame_hdr->quant_params.b
SET_PARAM(base_qindex, base_q_idx);
SET_PARAM(y_dc_delta_q, delta_q_y_dc);
SET_PARAM(uv_dc_delta_q, delta_q_uv_dc);
SET_PARAM(uv_ac_delta_q, delta_q_uv_ac);
#undef SET_PARAM
}
void D3D11VP9Accelerator::CopySegmentationParams(
DXVA_PicParams_VP9* pic_params,
const Vp9SegmentationParams& segmentation_params) {
#define SET_PARAM(a, b) pic_params->stVP9Segments.a = segmentation_params.b
#define COPY_PARAM(a) SET_PARAM(a, a)
COPY_PARAM(enabled);
COPY_PARAM(update_map);
COPY_PARAM(temporal_update);
SET_PARAM(abs_delta, abs_or_delta_update);
for (size_t i = 0; i < base::size(segmentation_params.tree_probs); i++) {
COPY_PARAM(tree_probs[i]);
}
for (size_t i = 0; i < base::size(segmentation_params.pred_probs); i++) {
COPY_PARAM(pred_probs[i]);
}
for (size_t i = 0; i < 8; i++) {
for (size_t j = 0; j < 4; j++) {
COPY_PARAM(feature_data[i][j]);
if (segmentation_params.feature_enabled[i][j])
pic_params->stVP9Segments.feature_mask[i] |= (1 << j);
}
}
#undef COPY_PARAM
#undef SET_PARAM
}
void D3D11VP9Accelerator::CopyHeaderSizeAndID(DXVA_PicParams_VP9* pic_params,
const D3D11VP9Picture& pic) {
pic_params->uncompressed_header_size_byte_aligned =
static_cast<USHORT>(pic.frame_hdr->uncompressed_header_size);
pic_params->first_partition_size =
static_cast<USHORT>(pic.frame_hdr->header_size_in_bytes);
// StatusReportFeedbackNumber "should not be equal to 0".
pic_params->StatusReportFeedbackNumber = ++status_feedback_;
}
bool D3D11VP9Accelerator::SubmitDecoderBuffer(
const DXVA_PicParams_VP9& pic_params,
const D3D11VP9Picture& pic) {
#define GET_BUFFER(type, code) \
RETURN_ON_HR_FAILURE(GetDecoderBuffer, \
video_context_->GetDecoderBuffer( \
video_decoder_.Get(), type, &buffer_size, &buffer), \
code)
#define RELEASE_BUFFER(type, code) \
RETURN_ON_HR_FAILURE( \
ReleaseDecoderBuffer, \
video_context_->ReleaseDecoderBuffer(video_decoder_.Get(), type), code)
UINT buffer_size;
void* buffer;
GET_BUFFER(D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS,
StatusCode::kGetPicParamBufferFailed);
memcpy(buffer, &pic_params, sizeof(pic_params));
RELEASE_BUFFER(D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS,
StatusCode::kReleasePicParamBufferFailed);
size_t buffer_offset = 0;
while (buffer_offset < pic.frame_hdr->frame_size) {
GET_BUFFER(D3D11_VIDEO_DECODER_BUFFER_BITSTREAM,
StatusCode::kGetBitstreamBufferFailed);
size_t copy_size = pic.frame_hdr->frame_size - buffer_offset;
bool contains_end = true;
if (copy_size > buffer_size) {
copy_size = buffer_size;
contains_end = false;
}
memcpy(buffer, pic.frame_hdr->data + buffer_offset, copy_size);
RELEASE_BUFFER(D3D11_VIDEO_DECODER_BUFFER_BITSTREAM,
StatusCode::kReleaseBitstreamBufferFailed);
DXVA_Slice_VPx_Short slice_info;
GET_BUFFER(D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL,
StatusCode::kGetSliceControlBufferFailed);
slice_info.BSNALunitDataLocation = 0;
slice_info.SliceBytesInBuffer = (UINT)copy_size;
// See the DXVA header specification for values of wBadSliceChopping:
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/dxva/ns-dxva-_dxva_sliceinfo#wBadSliceChopping
if (buffer_offset == 0 && contains_end)
slice_info.wBadSliceChopping = 0;
else if (buffer_offset == 0 && !contains_end)
slice_info.wBadSliceChopping = 1;
else if (buffer_offset != 0 && contains_end)
slice_info.wBadSliceChopping = 2;
else if (buffer_offset != 0 && !contains_end)
slice_info.wBadSliceChopping = 3;
memcpy(buffer, &slice_info, sizeof(slice_info));
RELEASE_BUFFER(D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL,
StatusCode::kReleaseSliceControlBufferFailed);
constexpr int buffers_count = 3;
VideoContextWrapper::VideoBufferWrapper buffers[buffers_count] = {};
buffers[0].BufferType = D3D11_VIDEO_DECODER_BUFFER_PICTURE_PARAMETERS;
buffers[0].DataOffset = 0;
buffers[0].DataSize = sizeof(pic_params);
buffers[1].BufferType = D3D11_VIDEO_DECODER_BUFFER_SLICE_CONTROL;
buffers[1].DataOffset = 0;
buffers[1].DataSize = sizeof(slice_info);
buffers[2].BufferType = D3D11_VIDEO_DECODER_BUFFER_BITSTREAM;
buffers[2].DataOffset = 0;
buffers[2].DataSize = copy_size;
const DecryptConfig* config = pic.decrypt_config();
if (config) {
buffers[2].pIV = const_cast<char*>(config->iv().data());
buffers[2].IVSize = config->iv().size();
// Subsamples matter iff there is IV, for decryption.
if (!config->subsamples().empty()) {
buffers[2].pSubSampleMappingBlock =
CreateSubsampleMappingBlock(config->subsamples()).data();
buffers[2].SubSampleMappingCount = config->subsamples().size();
}
}
RETURN_ON_HR_FAILURE(SubmitDecoderBuffers,
video_context_->SubmitDecoderBuffers(
video_decoder_.Get(), buffers_count, buffers),
StatusCode::kSubmitDecoderBuffersFailed);
buffer_offset += copy_size;
}
return true;
#undef GET_BUFFER
#undef RELEASE_BUFFER
}
DecodeStatus D3D11VP9Accelerator::SubmitDecode(
scoped_refptr<VP9Picture> picture,
const Vp9SegmentationParams& segmentation_params,
const Vp9LoopFilterParams& loop_filter_params,
const Vp9ReferenceFrameVector& reference_frames,
base::OnceClosure on_finished_cb) {
D3D11VP9Picture* pic = static_cast<D3D11VP9Picture*>(picture.get());
if (!BeginFrame(*pic))
return DecodeStatus::kFail;
DXVA_PicParams_VP9 pic_params = {};
CopyFrameParams(*pic, &pic_params);
CopyReferenceFrames(*pic, &pic_params, reference_frames);
CopyFrameRefs(&pic_params, *pic);
CopyLoopFilterParams(&pic_params, loop_filter_params);
CopyQuantParams(&pic_params, *pic);
CopySegmentationParams(&pic_params, segmentation_params);
CopyHeaderSizeAndID(&pic_params, *pic);
if (!SubmitDecoderBuffer(pic_params, *pic))
return DecodeStatus::kFail;
HRESULT hr = video_context_->DecoderEndFrame(video_decoder_.Get());
if (FAILED(hr)) {
RecordFailure("DecoderEndFrame", logging::SystemErrorCodeToString(hr),
StatusCode::kDecoderEndFrameFailed);
return DecodeStatus::kFail;
}
if (on_finished_cb)
std::move(on_finished_cb).Run();
return DecodeStatus::kOk;
}
bool D3D11VP9Accelerator::OutputPicture(scoped_refptr<VP9Picture> picture) {
D3D11VP9Picture* pic = static_cast<D3D11VP9Picture*>(picture.get());
return client_->OutputResult(picture.get(), pic->picture_buffer());
}
bool D3D11VP9Accelerator::IsFrameContextRequired() const {
return false;
}
bool D3D11VP9Accelerator::GetFrameContext(scoped_refptr<VP9Picture> picture,
Vp9FrameContext* frame_context) {
return false;
}
void D3D11VP9Accelerator::SetVideoDecoder(ComD3D11VideoDecoder video_decoder) {
video_decoder_ = std::move(video_decoder);
}
} // namespace media