blob: 702c07a786836c285c3c0ae043c5faa15fa62f11 [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/vaapi/vaapi_utils.h"
#include <type_traits>
#include <utility>
#include "base/cxx17_backports.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/synchronization/lock.h"
#include "build/chromeos_buildflags.h"
#include "media/gpu/vaapi/vaapi_common.h"
#include "media/gpu/vaapi/vaapi_wrapper.h"
#include "media/gpu/vp8_picture.h"
#include "media/gpu/vp8_reference_frame_vector.h"
#include "third_party/libva_protected_content/va_protected_content.h"
namespace media {
namespace {
template <typename To, typename From>
void CheckedMemcpy(To& to, From& from) {
static_assert(std::is_array<To>::value, "First parameter must be an array");
static_assert(std::is_array<From>::value,
"Second parameter must be an array");
static_assert(sizeof(to) == sizeof(from), "arrays must be of same size");
memcpy(&to, &from, sizeof(to));
}
} // namespace
ScopedVABufferMapping::ScopedVABufferMapping(
const base::Lock* lock,
VADisplay va_display,
VABufferID buffer_id,
base::OnceCallback<void(VABufferID)> release_callback)
: lock_(lock), va_display_(va_display), buffer_id_(buffer_id) {
DCHECK(lock_);
lock_->AssertAcquired();
DCHECK_NE(buffer_id, VA_INVALID_ID);
const VAStatus result =
vaMapBuffer(va_display_, buffer_id_, &va_buffer_data_);
const bool success = result == VA_STATUS_SUCCESS;
LOG_IF(ERROR, !success) << "vaMapBuffer failed: " << vaErrorStr(result);
DCHECK(success == (va_buffer_data_ != nullptr))
<< "|va_buffer_data| should be null if vaMapBuffer() fails";
if (!success && release_callback)
std::move(release_callback).Run(buffer_id_);
}
ScopedVABufferMapping::~ScopedVABufferMapping() {
if (va_buffer_data_) {
lock_->AssertAcquired();
Unmap();
}
}
VAStatus ScopedVABufferMapping::Unmap() {
lock_->AssertAcquired();
const VAStatus result = vaUnmapBuffer(va_display_, buffer_id_);
if (result == VA_STATUS_SUCCESS)
va_buffer_data_ = nullptr;
else
LOG(ERROR) << "vaUnmapBuffer failed: " << vaErrorStr(result);
return result;
}
// static
std::unique_ptr<ScopedVABuffer> ScopedVABuffer::Create(
base::Lock* lock,
VADisplay va_display,
VAContextID va_context_id,
VABufferType va_buffer_type,
size_t size) {
DCHECK(lock);
DCHECK(va_display);
DCHECK_NE(va_context_id, VA_INVALID_ID);
DCHECK(IsValidVABufferType(va_buffer_type));
DCHECK_NE(size, 0u);
lock->AssertAcquired();
unsigned int va_buffer_size;
if (!base::CheckedNumeric<size_t>(size).AssignIfValid(&va_buffer_size)) {
LOG(ERROR) << "Invalid size, " << size;
return nullptr;
}
VABufferID buffer_id = VA_INVALID_ID;
const VAStatus va_res =
vaCreateBuffer(va_display, va_context_id, va_buffer_type, va_buffer_size,
1, nullptr, &buffer_id);
if (va_res != VA_STATUS_SUCCESS) {
LOG(ERROR) << "Failed to create a VA buffer: " << vaErrorStr(va_res);
return nullptr;
}
DCHECK_NE(buffer_id, VA_INVALID_ID);
return base::WrapUnique(
new ScopedVABuffer(lock, va_display, buffer_id, va_buffer_type, size));
}
// static
std::unique_ptr<ScopedVABuffer> ScopedVABuffer::CreateForTesting(
VABufferID va_buffer_id,
VABufferType va_buffer_type,
size_t size) {
return base::WrapUnique(
new ScopedVABuffer(nullptr, nullptr, va_buffer_id, va_buffer_type, size));
}
ScopedVABuffer::ScopedVABuffer(base::Lock* lock,
VADisplay va_display,
VABufferID va_buffer_id,
VABufferType va_buffer_type,
size_t size)
: lock_(lock),
va_display_(va_display),
va_buffer_id_(va_buffer_id),
va_buffer_type_(va_buffer_type),
size_(size) {}
ScopedVABuffer::~ScopedVABuffer() {
DCHECK_NE(va_buffer_id_, VA_INVALID_ID);
if (!lock_)
return; // Don't call VA-API function in test.
base::AutoLock auto_lock(*lock_);
VAStatus va_res = vaDestroyBuffer(va_display_, va_buffer_id_);
LOG_IF(ERROR, va_res != VA_STATUS_SUCCESS)
<< "Failed to destroy a VA buffer: " << vaErrorStr(va_res);
}
ScopedVAImage::ScopedVAImage(base::Lock* lock,
VADisplay va_display,
VASurfaceID va_surface_id,
VAImageFormat* format,
const gfx::Size& size)
: lock_(lock), va_display_(va_display), image_(new VAImage{}) {
DCHECK(lock_);
lock_->AssertAcquired();
VAStatus result = vaCreateImage(va_display_, format, size.width(),
size.height(), image_.get());
if (result != VA_STATUS_SUCCESS) {
DCHECK_EQ(image_->image_id, VA_INVALID_ID);
LOG(ERROR) << "vaCreateImage failed: " << vaErrorStr(result);
return;
}
DCHECK_NE(image_->image_id, VA_INVALID_ID);
result = vaGetImage(va_display_, va_surface_id, 0, 0, size.width(),
size.height(), image_->image_id);
if (result != VA_STATUS_SUCCESS) {
LOG(ERROR) << "vaGetImage failed: " << vaErrorStr(result);
return;
}
va_buffer_ =
std::make_unique<ScopedVABufferMapping>(lock_, va_display, image_->buf);
}
ScopedVAImage::~ScopedVAImage() {
if (image_->image_id != VA_INVALID_ID) {
base::AutoLock auto_lock(*lock_);
// |va_buffer_| has to be deleted before vaDestroyImage().
va_buffer_.reset();
vaDestroyImage(va_display_, image_->image_id);
}
}
ScopedVASurface::ScopedVASurface(scoped_refptr<VaapiWrapper> vaapi_wrapper,
VASurfaceID va_surface_id,
const gfx::Size& size,
unsigned int va_rt_format)
: vaapi_wrapper_(std::move(vaapi_wrapper)),
va_surface_id_(va_surface_id),
size_(size),
va_rt_format_(va_rt_format) {
DCHECK(vaapi_wrapper_);
}
ScopedVASurface::~ScopedVASurface() {
if (va_surface_id_ != VA_INVALID_ID)
vaapi_wrapper_->DestroySurface(va_surface_id_);
}
bool ScopedVASurface::IsValid() const {
return va_surface_id_ != VA_INVALID_ID && !size_.IsEmpty() &&
va_rt_format_ != kInvalidVaRtFormat;
}
void FillVP8DataStructures(const Vp8FrameHeader& frame_header,
const Vp8ReferenceFrameVector& reference_frames,
VAIQMatrixBufferVP8* iq_matrix_buf,
VAProbabilityDataBufferVP8* prob_buf,
VAPictureParameterBufferVP8* pic_param,
VASliceParameterBufferVP8* slice_param) {
const Vp8SegmentationHeader& sgmnt_hdr = frame_header.segmentation_hdr;
const Vp8QuantizationHeader& quant_hdr = frame_header.quantization_hdr;
static_assert(base::size(decltype(iq_matrix_buf->quantization_index){}) ==
kMaxMBSegments,
"incorrect quantization matrix segment size");
static_assert(
base::size(decltype(iq_matrix_buf->quantization_index){}[0]) == 6,
"incorrect quantization matrix Q index size");
for (size_t i = 0; i < kMaxMBSegments; ++i) {
int q = quant_hdr.y_ac_qi;
if (sgmnt_hdr.segmentation_enabled) {
if (sgmnt_hdr.segment_feature_mode ==
Vp8SegmentationHeader::FEATURE_MODE_ABSOLUTE) {
q = sgmnt_hdr.quantizer_update_value[i];
} else {
q += sgmnt_hdr.quantizer_update_value[i];
}
}
#define CLAMP_Q(q) base::clamp(q, 0, 127)
iq_matrix_buf->quantization_index[i][0] = CLAMP_Q(q);
iq_matrix_buf->quantization_index[i][1] = CLAMP_Q(q + quant_hdr.y_dc_delta);
iq_matrix_buf->quantization_index[i][2] =
CLAMP_Q(q + quant_hdr.y2_dc_delta);
iq_matrix_buf->quantization_index[i][3] =
CLAMP_Q(q + quant_hdr.y2_ac_delta);
iq_matrix_buf->quantization_index[i][4] =
CLAMP_Q(q + quant_hdr.uv_dc_delta);
iq_matrix_buf->quantization_index[i][5] =
CLAMP_Q(q + quant_hdr.uv_ac_delta);
#undef CLAMP_Q
}
const Vp8EntropyHeader& entr_hdr = frame_header.entropy_hdr;
CheckedMemcpy(prob_buf->dct_coeff_probs, entr_hdr.coeff_probs);
pic_param->frame_width = frame_header.width;
pic_param->frame_height = frame_header.height;
const auto last_frame = reference_frames.GetFrame(Vp8RefType::VP8_FRAME_LAST);
if (last_frame) {
pic_param->last_ref_frame =
last_frame->AsVaapiVP8Picture()->GetVASurfaceID();
} else {
pic_param->last_ref_frame = VA_INVALID_SURFACE;
}
const auto golden_frame =
reference_frames.GetFrame(Vp8RefType::VP8_FRAME_GOLDEN);
if (golden_frame) {
pic_param->golden_ref_frame =
golden_frame->AsVaapiVP8Picture()->GetVASurfaceID();
} else {
pic_param->golden_ref_frame = VA_INVALID_SURFACE;
}
const auto alt_frame =
reference_frames.GetFrame(Vp8RefType::VP8_FRAME_ALTREF);
if (alt_frame)
pic_param->alt_ref_frame = alt_frame->AsVaapiVP8Picture()->GetVASurfaceID();
else
pic_param->alt_ref_frame = VA_INVALID_SURFACE;
pic_param->out_of_loop_frame = VA_INVALID_SURFACE;
const Vp8LoopFilterHeader& lf_hdr = frame_header.loopfilter_hdr;
#define FHDR_TO_PP_PF(a, b) pic_param->pic_fields.bits.a = (b)
FHDR_TO_PP_PF(key_frame, frame_header.IsKeyframe() ? 0 : 1);
FHDR_TO_PP_PF(version, frame_header.version);
FHDR_TO_PP_PF(segmentation_enabled, sgmnt_hdr.segmentation_enabled);
FHDR_TO_PP_PF(update_mb_segmentation_map,
sgmnt_hdr.update_mb_segmentation_map);
FHDR_TO_PP_PF(update_segment_feature_data,
sgmnt_hdr.update_segment_feature_data);
FHDR_TO_PP_PF(filter_type, lf_hdr.type);
FHDR_TO_PP_PF(sharpness_level, lf_hdr.sharpness_level);
FHDR_TO_PP_PF(loop_filter_adj_enable, lf_hdr.loop_filter_adj_enable);
FHDR_TO_PP_PF(mode_ref_lf_delta_update, lf_hdr.mode_ref_lf_delta_update);
FHDR_TO_PP_PF(sign_bias_golden, frame_header.sign_bias_golden);
FHDR_TO_PP_PF(sign_bias_alternate, frame_header.sign_bias_alternate);
FHDR_TO_PP_PF(mb_no_coeff_skip, frame_header.mb_no_skip_coeff);
FHDR_TO_PP_PF(loop_filter_disable, lf_hdr.level == 0);
#undef FHDR_TO_PP_PF
CheckedMemcpy(pic_param->mb_segment_tree_probs, sgmnt_hdr.segment_prob);
static_assert(std::extent<decltype(sgmnt_hdr.lf_update_value)>() ==
std::extent<decltype(pic_param->loop_filter_level)>(),
"loop filter level arrays mismatch");
for (size_t i = 0; i < base::size(sgmnt_hdr.lf_update_value); ++i) {
int lf_level = lf_hdr.level;
if (sgmnt_hdr.segmentation_enabled) {
if (sgmnt_hdr.segment_feature_mode ==
Vp8SegmentationHeader::FEATURE_MODE_ABSOLUTE) {
lf_level = sgmnt_hdr.lf_update_value[i];
} else {
lf_level += sgmnt_hdr.lf_update_value[i];
}
}
pic_param->loop_filter_level[i] = base::clamp(lf_level, 0, 63);
}
static_assert(
std::extent<decltype(lf_hdr.ref_frame_delta)>() ==
std::extent<decltype(pic_param->loop_filter_deltas_ref_frame)>(),
"loop filter deltas arrays size mismatch");
static_assert(std::extent<decltype(lf_hdr.mb_mode_delta)>() ==
std::extent<decltype(pic_param->loop_filter_deltas_mode)>(),
"loop filter deltas arrays size mismatch");
static_assert(std::extent<decltype(lf_hdr.ref_frame_delta)>() ==
std::extent<decltype(lf_hdr.mb_mode_delta)>(),
"loop filter deltas arrays size mismatch");
for (size_t i = 0; i < base::size(lf_hdr.ref_frame_delta); ++i) {
pic_param->loop_filter_deltas_ref_frame[i] = lf_hdr.ref_frame_delta[i];
pic_param->loop_filter_deltas_mode[i] = lf_hdr.mb_mode_delta[i];
}
#define FHDR_TO_PP(a) pic_param->a = frame_header.a
FHDR_TO_PP(prob_skip_false);
FHDR_TO_PP(prob_intra);
FHDR_TO_PP(prob_last);
FHDR_TO_PP(prob_gf);
#undef FHDR_TO_PP
CheckedMemcpy(pic_param->y_mode_probs, entr_hdr.y_mode_probs);
CheckedMemcpy(pic_param->uv_mode_probs, entr_hdr.uv_mode_probs);
CheckedMemcpy(pic_param->mv_probs, entr_hdr.mv_probs);
pic_param->bool_coder_ctx.range = frame_header.bool_dec_range;
pic_param->bool_coder_ctx.value = frame_header.bool_dec_value;
pic_param->bool_coder_ctx.count = frame_header.bool_dec_count;
slice_param->slice_data_size = frame_header.frame_size;
slice_param->slice_data_offset = frame_header.first_part_offset;
slice_param->slice_data_flag = VA_SLICE_DATA_FLAG_ALL;
slice_param->macroblock_offset = frame_header.macroblock_bit_offset;
// Number of DCT partitions plus control partition.
slice_param->num_of_partitions = frame_header.num_of_dct_partitions + 1;
// Per VAAPI, this size only includes the size of the macroblock data in
// the first partition (in bytes), so we have to subtract the header size.
slice_param->partition_size[0] =
frame_header.first_part_size -
((frame_header.macroblock_bit_offset + 7) / 8);
for (size_t i = 0; i < frame_header.num_of_dct_partitions; ++i)
slice_param->partition_size[i + 1] = frame_header.dct_partition_sizes[i];
}
bool IsValidVABufferType(VABufferType type) {
return type < VABufferTypeMax ||
#if BUILDFLAG(IS_CHROMEOS_ASH)
// TODO(jkardatzke): Remove this once we update to libva 2.0.10 in
// ChromeOS.
type == VAEncryptionParameterBufferType ||
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
type == VACencStatusParameterBufferType;
}
} // namespace media