|  | // Copyright 2019 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. | 
|  |  | 
|  | // This has to be included first. | 
|  | // See http://code.google.com/p/googletest/issues/detail?id=371 | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | #include <drm_fourcc.h> | 
|  | #include <gbm.h> | 
|  | #include <unistd.h> | 
|  | #include <map> | 
|  | #include <vector> | 
|  |  | 
|  | #include <va/va.h> | 
|  | #include <va/va_drmcommon.h> | 
|  | #include <va/va_str.h> | 
|  |  | 
|  | #include "base/bits.h" | 
|  | #include "base/callback_helpers.h" | 
|  | #include "base/containers/contains.h" | 
|  | #include "base/cpu.h" | 
|  | #include "base/files/file.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/files/scoped_file.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/process/launch.h" | 
|  | #include "base/strings/pattern.h" | 
|  | #include "base/strings/string_split.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/test/launcher/unit_test_launcher.h" | 
|  | #include "base/test/scoped_feature_list.h" | 
|  | #include "base/test/test_suite.h" | 
|  | #include "build/chromeos_buildflags.h" | 
|  | #include "media/base/media_switches.h" | 
|  | #include "media/gpu/vaapi/vaapi_wrapper.h" | 
|  | #include "media/media_buildflags.h" | 
|  | #include "third_party/abseil-cpp/absl/types/optional.h" | 
|  | #include "ui/gfx/linux/gbm_defines.h" | 
|  |  | 
|  | namespace media { | 
|  | namespace { | 
|  |  | 
|  | absl::optional<VAProfile> ConvertToVAProfile(VideoCodecProfile profile) { | 
|  | // A map between VideoCodecProfile and VAProfile. | 
|  | const std::map<VideoCodecProfile, VAProfile> kProfileMap = { | 
|  | // VAProfileH264Baseline is deprecated in <va/va.h> from libva 2.0.0. | 
|  | {H264PROFILE_BASELINE, VAProfileH264ConstrainedBaseline}, | 
|  | {H264PROFILE_MAIN, VAProfileH264Main}, | 
|  | {H264PROFILE_HIGH, VAProfileH264High}, | 
|  | {VP8PROFILE_ANY, VAProfileVP8Version0_3}, | 
|  | {VP9PROFILE_PROFILE0, VAProfileVP9Profile0}, | 
|  | {VP9PROFILE_PROFILE2, VAProfileVP9Profile2}, | 
|  | {AV1PROFILE_PROFILE_MAIN, VAProfileAV1Profile0}, | 
|  | #if BUILDFLAG(ENABLE_PLATFORM_HEVC_DECODING) | 
|  | {HEVCPROFILE_MAIN, VAProfileHEVCMain}, | 
|  | {HEVCPROFILE_MAIN10, VAProfileHEVCMain10}, | 
|  | #endif | 
|  | }; | 
|  | auto it = kProfileMap.find(profile); | 
|  | return it != kProfileMap.end() ? absl::make_optional<VAProfile>(it->second) | 
|  | : absl::nullopt; | 
|  | } | 
|  |  | 
|  | // Converts the given string to VAProfile | 
|  | absl::optional<VAProfile> StringToVAProfile(const std::string& va_profile) { | 
|  | const std::map<std::string, VAProfile> kStringToVAProfile = { | 
|  | {"VAProfileNone", VAProfileNone}, | 
|  | {"VAProfileH264ConstrainedBaseline", VAProfileH264ConstrainedBaseline}, | 
|  | // Even though it's deprecated, we leave VAProfileH264Baseline's | 
|  | // translation here to assert we never encounter it. | 
|  | {"VAProfileH264Baseline", VAProfileH264Baseline}, | 
|  | {"VAProfileH264Main", VAProfileH264Main}, | 
|  | {"VAProfileH264High", VAProfileH264High}, | 
|  | {"VAProfileJPEGBaseline", VAProfileJPEGBaseline}, | 
|  | {"VAProfileVP8Version0_3", VAProfileVP8Version0_3}, | 
|  | {"VAProfileVP9Profile0", VAProfileVP9Profile0}, | 
|  | {"VAProfileVP9Profile2", VAProfileVP9Profile2}, | 
|  | {"VAProfileAV1Profile0", VAProfileAV1Profile0}, | 
|  | #if BUILDFLAG(ENABLE_PLATFORM_HEVC_DECODING) | 
|  | {"VAProfileHEVCMain", VAProfileHEVCMain}, | 
|  | {"VAProfileHEVCMain10", VAProfileHEVCMain10}, | 
|  | #endif | 
|  | #if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) | 
|  | {"VAProfileProtected", VAProfileProtected}, | 
|  | #endif  // BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) | 
|  | }; | 
|  |  | 
|  | auto it = kStringToVAProfile.find(va_profile); | 
|  | return it != kStringToVAProfile.end() | 
|  | ? absl::make_optional<VAProfile>(it->second) | 
|  | : absl::nullopt; | 
|  | } | 
|  |  | 
|  | // Converts the given string to VAEntrypoint | 
|  | absl::optional<VAEntrypoint> StringToVAEntrypoint( | 
|  | const std::string& va_entrypoint) { | 
|  | const std::map<std::string, VAEntrypoint> kStringToVAEntrypoint = { | 
|  | {"VAEntrypointVLD", VAEntrypointVLD}, | 
|  | {"VAEntrypointEncSlice", VAEntrypointEncSlice}, | 
|  | {"VAEntrypointEncPicture", VAEntrypointEncPicture}, | 
|  | {"VAEntrypointEncSliceLP", VAEntrypointEncSliceLP}, | 
|  | {"VAEntrypointVideoProc", VAEntrypointVideoProc}, | 
|  | #if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) | 
|  | {"VAEntrypointProtectedContent", VAEntrypointProtectedContent}, | 
|  | #endif  // BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) | 
|  | }; | 
|  |  | 
|  | auto it = kStringToVAEntrypoint.find(va_entrypoint); | 
|  | return it != kStringToVAEntrypoint.end() | 
|  | ? absl::make_optional<VAEntrypoint>(it->second) | 
|  | : absl::nullopt; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::test::ScopedFeatureList> CreateScopedFeatureList() { | 
|  | auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>(); | 
|  | scoped_feature_list->InitWithFeatures( | 
|  | /*enabled_features=*/{media::kVaapiAV1Decoder}, | 
|  | /*disabled_features=*/{}); | 
|  | return scoped_feature_list; | 
|  | } | 
|  |  | 
|  | unsigned int ToVaRTFormat(uint32_t va_fourcc) { | 
|  | switch (va_fourcc) { | 
|  | case VA_FOURCC_I420: | 
|  | case VA_FOURCC_NV12: | 
|  | return VA_RT_FORMAT_YUV420; | 
|  | case VA_FOURCC_YUY2: | 
|  | return VA_RT_FORMAT_YUV422; | 
|  | case VA_FOURCC_RGBA: | 
|  | return VA_RT_FORMAT_RGB32; | 
|  | case VA_FOURCC_P010: | 
|  | return VA_RT_FORMAT_YUV420_10; | 
|  | } | 
|  | return kInvalidVaRtFormat; | 
|  | } | 
|  |  | 
|  | uint32_t ToVaFourcc(unsigned int va_rt_format) { | 
|  | switch (va_rt_format) { | 
|  | case VA_RT_FORMAT_YUV420: | 
|  | return VA_FOURCC_NV12; | 
|  | case VA_RT_FORMAT_YUV420_10: | 
|  | return VA_FOURCC_P010; | 
|  | } | 
|  | return DRM_FORMAT_INVALID; | 
|  | } | 
|  |  | 
|  | int ToGBMFormat(unsigned int va_rt_format) { | 
|  | switch (va_rt_format) { | 
|  | case VA_RT_FORMAT_YUV420: | 
|  | return DRM_FORMAT_NV12; | 
|  | case VA_RT_FORMAT_YUV420_10: | 
|  | return DRM_FORMAT_P010; | 
|  | } | 
|  | return DRM_FORMAT_INVALID; | 
|  | } | 
|  |  | 
|  | const std::string VARTFormatToString(unsigned int va_rt_format) { | 
|  | switch (va_rt_format) { | 
|  | case VA_RT_FORMAT_YUV420: | 
|  | return "VA_RT_FORMAT_YUV420"; | 
|  | case VA_RT_FORMAT_YUV420_10: | 
|  | return "VA_RT_FORMAT_YUV420_10"; | 
|  | } | 
|  | NOTREACHED() << "Unknown VA_RT_FORMAT 0x" << std::hex << va_rt_format; | 
|  | return "Unknown VA_RT_FORMAT"; | 
|  | } | 
|  |  | 
|  | #define TOSTR(enumCase) \ | 
|  | case enumCase:        \ | 
|  | return #enumCase | 
|  |  | 
|  | const char* VAProfileToString(VAProfile profile) { | 
|  | // clang-format off | 
|  | switch (profile) { | 
|  | TOSTR(VAProfileNone); | 
|  | TOSTR(VAProfileMPEG2Simple); | 
|  | TOSTR(VAProfileMPEG2Main); | 
|  | TOSTR(VAProfileMPEG4Simple); | 
|  | TOSTR(VAProfileMPEG4AdvancedSimple); | 
|  | TOSTR(VAProfileMPEG4Main); | 
|  | case VAProfileH264Baseline: | 
|  | NOTREACHED() << "VAProfileH264Baseline is deprecated"; | 
|  | return "Deprecated VAProfileH264Baseline"; | 
|  | TOSTR(VAProfileH264Main); | 
|  | TOSTR(VAProfileH264High); | 
|  | TOSTR(VAProfileVC1Simple); | 
|  | TOSTR(VAProfileVC1Main); | 
|  | TOSTR(VAProfileVC1Advanced); | 
|  | TOSTR(VAProfileH263Baseline); | 
|  | TOSTR(VAProfileH264ConstrainedBaseline); | 
|  | TOSTR(VAProfileJPEGBaseline); | 
|  | TOSTR(VAProfileVP8Version0_3); | 
|  | TOSTR(VAProfileH264MultiviewHigh); | 
|  | TOSTR(VAProfileH264StereoHigh); | 
|  | TOSTR(VAProfileHEVCMain); | 
|  | TOSTR(VAProfileHEVCMain10); | 
|  | TOSTR(VAProfileVP9Profile0); | 
|  | TOSTR(VAProfileVP9Profile1); | 
|  | TOSTR(VAProfileVP9Profile2); | 
|  | TOSTR(VAProfileVP9Profile3); | 
|  | TOSTR(VAProfileHEVCMain12); | 
|  | TOSTR(VAProfileHEVCMain422_10); | 
|  | TOSTR(VAProfileHEVCMain422_12); | 
|  | TOSTR(VAProfileHEVCMain444); | 
|  | TOSTR(VAProfileHEVCMain444_10); | 
|  | TOSTR(VAProfileHEVCMain444_12); | 
|  | TOSTR(VAProfileHEVCSccMain); | 
|  | TOSTR(VAProfileHEVCSccMain10); | 
|  | TOSTR(VAProfileHEVCSccMain444); | 
|  | TOSTR(VAProfileAV1Profile0); | 
|  | TOSTR(VAProfileAV1Profile1); | 
|  | TOSTR(VAProfileHEVCSccMain444_10); | 
|  | #if VA_MAJOR_VERSION >= 2 || VA_MINOR_VERSION >= 11 | 
|  | TOSTR(VAProfileProtected); | 
|  | #endif | 
|  | } | 
|  | // clang-format on | 
|  | return "<unknown profile>"; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class VaapiTest : public testing::Test { | 
|  | public: | 
|  | VaapiTest() : scoped_feature_list_(CreateScopedFeatureList()) {} | 
|  | ~VaapiTest() override = default; | 
|  |  | 
|  | private: | 
|  | std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_; | 
|  | }; | 
|  |  | 
|  | std::map<VAProfile, std::vector<VAEntrypoint>> ParseVainfo( | 
|  | const std::string& output) { | 
|  | const std::vector<std::string> lines = | 
|  | base::SplitString(output, "\n", base::WhitespaceHandling::TRIM_WHITESPACE, | 
|  | base::SplitResult::SPLIT_WANT_ALL); | 
|  | std::map<VAProfile, std::vector<VAEntrypoint>> info; | 
|  | for (const std::string& line : lines) { | 
|  | if (!base::StartsWith(line, "VAProfile", | 
|  | base::CompareCase::INSENSITIVE_ASCII)) { | 
|  | continue; | 
|  | } | 
|  | std::vector<std::string> res = | 
|  | base::SplitString(line, ":", base::WhitespaceHandling::TRIM_WHITESPACE, | 
|  | base::SplitResult::SPLIT_WANT_ALL); | 
|  | if (res.size() != 2) { | 
|  | LOG(ERROR) << "Unexpected line: " << line; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | auto va_profile = StringToVAProfile(res[0]); | 
|  | if (!va_profile) | 
|  | continue; | 
|  | auto va_entrypoint = StringToVAEntrypoint(res[1]); | 
|  | if (!va_entrypoint) | 
|  | continue; | 
|  | info[*va_profile].push_back(*va_entrypoint); | 
|  | DVLOG(3) << line; | 
|  | } | 
|  | return info; | 
|  | } | 
|  |  | 
|  | std::map<VAProfile, std::vector<VAEntrypoint>> RetrieveVAInfoOutput() { | 
|  | int fds[2]; | 
|  | PCHECK(pipe(fds) == 0); | 
|  | base::File read_pipe(fds[0]); | 
|  | base::ScopedFD write_pipe_fd(fds[1]); | 
|  |  | 
|  | base::LaunchOptions options; | 
|  | options.fds_to_remap.emplace_back(write_pipe_fd.get(), STDOUT_FILENO); | 
|  | std::vector<std::string> argv = {"vainfo"}; | 
|  | EXPECT_TRUE(LaunchProcess(argv, options).IsValid()); | 
|  | write_pipe_fd.reset(); | 
|  |  | 
|  | char buf[4096] = {}; | 
|  | int n = read_pipe.ReadAtCurrentPos(buf, sizeof(buf)); | 
|  | PCHECK(n >= 0); | 
|  | EXPECT_LT(n, 4096); | 
|  | std::string output(buf, n); | 
|  | DVLOG(4) << output; | 
|  | return ParseVainfo(output); | 
|  | } | 
|  |  | 
|  | TEST_F(VaapiTest, VaapiSandboxInitialization) { | 
|  | // Here we just test that the PreSandboxInitialization() in SetUp() worked | 
|  | // fine. Said initialization is buried in internal singletons, but we can | 
|  | // verify that at least the implementation type has been filled in. | 
|  | EXPECT_NE(VaapiWrapper::GetImplementationType(), VAImplementation::kInvalid); | 
|  | } | 
|  |  | 
|  | // Commit [1] deprecated VAProfileH264Baseline from libva in 2017 (release | 
|  | // 2.0.0). This test verifies that such profile is never seen in the lab. | 
|  | // [1] https://github.com/intel/libva/commit/6f69256f8ccc9a73c0b196ab77ac69ab1f4f33c2 | 
|  | TEST_F(VaapiTest, VerifyNoVAProfileH264Baseline) { | 
|  | const auto va_info = RetrieveVAInfoOutput(); | 
|  | EXPECT_FALSE(base::Contains(va_info, VAProfileH264Baseline)); | 
|  | } | 
|  |  | 
|  | // Verifies that every VAProfile from VaapiWrapper::GetSupportedDecodeProfiles() | 
|  | // is indeed supported by the command line vainfo utility and by | 
|  | // VaapiWrapper::IsDecodeSupported(). | 
|  | TEST_F(VaapiTest, GetSupportedDecodeProfiles) { | 
|  | const auto va_info = RetrieveVAInfoOutput(); | 
|  |  | 
|  | for (const auto& profile : VaapiWrapper::GetSupportedDecodeProfiles()) { | 
|  | const auto va_profile = ConvertToVAProfile(profile.profile); | 
|  | ASSERT_TRUE(va_profile.has_value()); | 
|  |  | 
|  | EXPECT_TRUE(base::Contains(va_info.at(*va_profile), VAEntrypointVLD)) | 
|  | << " profile: " << GetProfileName(profile.profile) | 
|  | << ", va profile: " << vaProfileStr(*va_profile); | 
|  | EXPECT_TRUE(VaapiWrapper::IsDecodeSupported(*va_profile)) | 
|  | << " profile: " << GetProfileName(profile.profile) | 
|  | << ", va profile: " << vaProfileStr(*va_profile); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Verifies that every VAProfile from VaapiWrapper::GetSupportedEncodeProfiles() | 
|  | // is indeed supported by the command line vainfo utility. | 
|  | TEST_F(VaapiTest, GetSupportedEncodeProfiles) { | 
|  | const auto va_info = RetrieveVAInfoOutput(); | 
|  |  | 
|  | for (const auto& profile : VaapiWrapper::GetSupportedEncodeProfiles()) { | 
|  | const auto va_profile = ConvertToVAProfile(profile.profile); | 
|  | ASSERT_TRUE(va_profile.has_value()); | 
|  |  | 
|  | EXPECT_TRUE(base::Contains(va_info.at(*va_profile), VAEntrypointEncSlice) || | 
|  | base::Contains(va_info.at(*va_profile), VAEntrypointEncSliceLP)) | 
|  | << " profile: " << GetProfileName(profile.profile) | 
|  | << ", va profile: " << vaProfileStr(*va_profile); | 
|  | } | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) | 
|  | // Verifies that VAProfileProtected is indeed supported by the command line | 
|  | // vainfo utility. | 
|  | TEST_F(VaapiTest, VaapiProfileProtected) { | 
|  | const auto va_info = RetrieveVAInfoOutput(); | 
|  |  | 
|  | EXPECT_TRUE(base::Contains(va_info.at(VAProfileProtected), | 
|  | VAEntrypointProtectedContent)) | 
|  | << ", va profile: " << vaProfileStr(VAProfileProtected); | 
|  | } | 
|  | #endif  // BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) | 
|  |  | 
|  | // Verifies that if JPEG decoding and encoding are supported by VaapiWrapper, | 
|  | // they are also supported by by the command line vainfo utility. | 
|  | TEST_F(VaapiTest, VaapiProfilesJPEG) { | 
|  | const auto va_info = RetrieveVAInfoOutput(); | 
|  |  | 
|  | EXPECT_EQ(VaapiWrapper::IsDecodeSupported(VAProfileJPEGBaseline), | 
|  | base::Contains(va_info.at(VAProfileJPEGBaseline), VAEntrypointVLD)); | 
|  | EXPECT_EQ(VaapiWrapper::IsJpegEncodeSupported(), | 
|  | base::Contains(va_info.at(VAProfileJPEGBaseline), | 
|  | VAEntrypointEncPicture)); | 
|  | } | 
|  |  | 
|  | // Verifies that the default VAEntrypoint as per VaapiWrapper is indeed among | 
|  | // the supported ones. | 
|  | TEST_F(VaapiTest, DefaultEntrypointIsSupported) { | 
|  | for (size_t i = 0; i < VaapiWrapper::kCodecModeMax; ++i) { | 
|  | const auto wrapper_mode = static_cast<VaapiWrapper::CodecMode>(i); | 
|  | std::map<VAProfile, std::vector<VAEntrypoint>> configurations = | 
|  | VaapiWrapper::GetSupportedConfigurationsForCodecModeForTesting( | 
|  | wrapper_mode); | 
|  | for (const auto& profile_and_entrypoints : configurations) { | 
|  | const VAEntrypoint default_entrypoint = | 
|  | VaapiWrapper::GetDefaultVaEntryPoint(wrapper_mode, | 
|  | profile_and_entrypoints.first); | 
|  | const auto& supported_entrypoints = profile_and_entrypoints.second; | 
|  | EXPECT_TRUE(base::Contains(supported_entrypoints, default_entrypoint)) | 
|  | << "Default VAEntrypoint " << vaEntrypointStr(default_entrypoint) | 
|  | << " (VaapiWrapper mode = " << wrapper_mode | 
|  | << ") is not supported for " | 
|  | << vaProfileStr(profile_and_entrypoints.first); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Verifies that VaapiWrapper::CreateContext() will queue up a buffer to set the | 
|  | // encoder to its lowest quality setting if a given VAProfile and VAEntrypoint | 
|  | // claims to support configuring it. | 
|  | TEST_F(VaapiTest, LowQualityEncodingSetting) { | 
|  | // This test only applies to low powered Intel processors. | 
|  | constexpr int kPentiumAndLaterFamily = 0x06; | 
|  | const base::CPU cpuid; | 
|  | const bool is_core_y_processor = | 
|  | base::MatchPattern(cpuid.cpu_brand(), "Intel(R) Core(TM) *Y CPU*"); | 
|  | const bool is_low_power_intel = | 
|  | cpuid.family() == kPentiumAndLaterFamily && | 
|  | (base::Contains(cpuid.cpu_brand(), "Pentium") || | 
|  | base::Contains(cpuid.cpu_brand(), "Celeron") || is_core_y_processor); | 
|  | if (!is_low_power_intel) | 
|  | GTEST_SKIP() << "Not an Intel low power processor"; | 
|  |  | 
|  | std::map<VAProfile, std::vector<VAEntrypoint>> configurations = | 
|  | VaapiWrapper::GetSupportedConfigurationsForCodecModeForTesting( | 
|  | VaapiWrapper::kEncodeConstantBitrate); | 
|  |  | 
|  | for (const auto& codec_mode : | 
|  | {VaapiWrapper::kEncodeConstantBitrate, | 
|  | VaapiWrapper::kEncodeConstantQuantizationParameter}) { | 
|  | std::map<VAProfile, std::vector<VAEntrypoint>> configurations = | 
|  | VaapiWrapper::GetSupportedConfigurationsForCodecModeForTesting( | 
|  | codec_mode); | 
|  |  | 
|  | for (const auto& profile_and_entrypoints : configurations) { | 
|  | const VAProfile va_profile = profile_and_entrypoints.first; | 
|  | scoped_refptr<VaapiWrapper> wrapper = VaapiWrapper::Create( | 
|  | VaapiWrapper::kEncodeConstantBitrate, va_profile, | 
|  | EncryptionScheme::kUnencrypted, base::DoNothing()); | 
|  |  | 
|  | // Depending on the GPU Gen, flags and policies, we may or may not utilize | 
|  | // all entrypoints (e.g. we might always want VAEntrypointEncSliceLP if | 
|  | // supported and enabled). Query VaapiWrapper's mandated entry point. | 
|  | const VAEntrypoint entrypoint = | 
|  | VaapiWrapper::GetDefaultVaEntryPoint(codec_mode, va_profile); | 
|  | ASSERT_TRUE(base::Contains(profile_and_entrypoints.second, entrypoint)); | 
|  |  | 
|  | VAConfigAttrib attrib{}; | 
|  | attrib.type = VAConfigAttribEncQualityRange; | 
|  | { | 
|  | base::AutoLock auto_lock(*wrapper->va_lock_); | 
|  | VAStatus va_res = vaGetConfigAttributes( | 
|  | wrapper->va_display_, va_profile, entrypoint, &attrib, 1); | 
|  | ASSERT_EQ(va_res, VA_STATUS_SUCCESS); | 
|  | } | 
|  | const auto quality_level = attrib.value; | 
|  | if (quality_level == VA_ATTRIB_NOT_SUPPORTED || quality_level <= 1u) | 
|  | continue; | 
|  | DLOG(INFO) << vaProfileStr(va_profile) | 
|  | << " supports encoding quality setting, with max value " | 
|  | << quality_level; | 
|  |  | 
|  | // If we get here it means the |va_profile| and |entrypoint| support | 
|  | // the quality setting. We cannot inspect what the driver does with this | 
|  | // number (it could ignore it), so instead just make sure there's a | 
|  | // |pending_va_buffers_| that, when mapped, looks correct. That buffer | 
|  | // should be created by CreateContext(). | 
|  | ASSERT_TRUE(wrapper->CreateContext(gfx::Size(640, 368))); | 
|  | ASSERT_EQ(wrapper->pending_va_buffers_.size(), 1u); | 
|  | { | 
|  | base::AutoLock auto_lock(*wrapper->va_lock_); | 
|  | ScopedVABufferMapping mapping(wrapper->va_lock_, wrapper->va_display_, | 
|  | wrapper->pending_va_buffers_.front()); | 
|  | ASSERT_TRUE(mapping.IsValid()); | 
|  |  | 
|  | auto* const va_buffer = | 
|  | reinterpret_cast<VAEncMiscParameterBuffer*>(mapping.data()); | 
|  | EXPECT_EQ(va_buffer->type, VAEncMiscParameterTypeQualityLevel); | 
|  |  | 
|  | auto* const enc_quality = | 
|  | reinterpret_cast<VAEncMiscParameterBufferQualityLevel*>( | 
|  | va_buffer->data); | 
|  | EXPECT_EQ(enc_quality->quality_level, quality_level) | 
|  | << vaProfileStr(va_profile) << " " << vaEntrypointStr(entrypoint); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // This test checks the supported SVC scalability mode. | 
|  | TEST_F(VaapiTest, CheckSupportedSVCScalabilityModes) { | 
|  | #if BUILDFLAG(IS_CHROMEOS_ASH) | 
|  | const std::vector<SVCScalabilityMode> kSupportedTemporalSVC = { | 
|  | SVCScalabilityMode::kL1T2, SVCScalabilityMode::kL1T3}; | 
|  | const std::vector<SVCScalabilityMode> kSupportedTemporalAndKeySVC = { | 
|  | SVCScalabilityMode::kL1T2,    SVCScalabilityMode::kL1T3, | 
|  | SVCScalabilityMode::kL2T2Key, SVCScalabilityMode::kL2T3Key, | 
|  | SVCScalabilityMode::kL3T2Key, SVCScalabilityMode::kL3T3Key}; | 
|  | #endif | 
|  |  | 
|  | const auto scalability_modes_vp9_profile0 = | 
|  | VaapiWrapper::GetSupportedScalabilityModes(VP9PROFILE_PROFILE0, | 
|  | VAProfileVP9Profile0); | 
|  | #if BUILDFLAG(IS_CHROMEOS_ASH) | 
|  | if (base::FeatureList::IsEnabled(kVaapiVp9kSVCHWEncoding) && | 
|  | VaapiWrapper::GetDefaultVaEntryPoint( | 
|  | VaapiWrapper::kEncodeConstantQuantizationParameter, | 
|  | VAProfileVP9Profile0) == VAEntrypointEncSliceLP) { | 
|  | EXPECT_EQ(scalability_modes_vp9_profile0, kSupportedTemporalAndKeySVC); | 
|  | } else { | 
|  | EXPECT_EQ(scalability_modes_vp9_profile0, kSupportedTemporalSVC); | 
|  | } | 
|  | #else | 
|  | EXPECT_TRUE(scalability_modes_vp9_profile0.empty()); | 
|  | #endif | 
|  |  | 
|  | const auto scalability_modes_vp9_profile2 = | 
|  | VaapiWrapper::GetSupportedScalabilityModes(VP9PROFILE_PROFILE2, | 
|  | VAProfileVP9Profile2); | 
|  | EXPECT_TRUE(scalability_modes_vp9_profile2.empty()); | 
|  |  | 
|  | const auto scalability_modes_h264_baseline = | 
|  | VaapiWrapper::GetSupportedScalabilityModes( | 
|  | H264PROFILE_BASELINE, VAProfileH264ConstrainedBaseline); | 
|  | #if BUILDFLAG(IS_CHROMEOS_ASH) | 
|  | // TODO(b/199487660): Enable H.264 temporal layer encoding on AMD once their | 
|  | // drivers support them. | 
|  | const auto implementation = VaapiWrapper::GetImplementationType(); | 
|  | if (base::FeatureList::IsEnabled(kVaapiH264TemporalLayerHWEncoding) && | 
|  | (implementation == VAImplementation::kIntelI965 || | 
|  | implementation == VAImplementation::kIntelIHD)) { | 
|  | EXPECT_EQ(scalability_modes_h264_baseline, kSupportedTemporalSVC); | 
|  | } else { | 
|  | EXPECT_TRUE(scalability_modes_h264_baseline.empty()); | 
|  | } | 
|  | #else | 
|  | EXPECT_TRUE(scalability_modes_h264_baseline.empty()); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | class VaapiVppTest | 
|  | : public VaapiTest, | 
|  | public testing::WithParamInterface<std::tuple<uint32_t, uint32_t>> { | 
|  | public: | 
|  | VaapiVppTest() = default; | 
|  | ~VaapiVppTest() override = default; | 
|  |  | 
|  | // Populate meaningful test suffixes instead of /0, /1, etc. | 
|  | struct PrintToStringParamName { | 
|  | template <class ParamType> | 
|  | std::string operator()( | 
|  | const testing::TestParamInfo<ParamType>& info) const { | 
|  | std::stringstream ss; | 
|  | ss << FourccToString(std::get<0>(info.param)) << "_to_" | 
|  | << FourccToString(std::get<1>(info.param)); | 
|  | return ss.str(); | 
|  | } | 
|  | }; | 
|  | }; | 
|  |  | 
|  | TEST_P(VaapiVppTest, BlitWithVAAllocatedSurfaces) { | 
|  | const uint32_t va_fourcc_in = std::get<0>(GetParam()); | 
|  | const uint32_t va_fourcc_out = std::get<1>(GetParam()); | 
|  |  | 
|  | // TODO(b/187852384): enable the other two backends. | 
|  | if (VaapiWrapper::GetImplementationType() != VAImplementation::kIntelIHD) | 
|  | GTEST_SKIP() << "backend not supported"; | 
|  |  | 
|  | if (!VaapiWrapper::IsVppFormatSupported(va_fourcc_in) || | 
|  | !VaapiWrapper::IsVppFormatSupported(va_fourcc_out)) { | 
|  | GTEST_SKIP() << FourccToString(va_fourcc_in) << " -> " | 
|  | << FourccToString(va_fourcc_out) << " not supported"; | 
|  | } | 
|  | constexpr gfx::Size kInputSize(640, 320); | 
|  | constexpr gfx::Size kOutputSize(320, 180); | 
|  | ASSERT_TRUE(VaapiWrapper::IsVppResolutionAllowed(kInputSize)); | 
|  | ASSERT_TRUE(VaapiWrapper::IsVppResolutionAllowed(kOutputSize)); | 
|  |  | 
|  | auto wrapper = | 
|  | VaapiWrapper::Create(VaapiWrapper::kVideoProcess, VAProfileNone, | 
|  | EncryptionScheme::kUnencrypted, base::DoNothing()); | 
|  | ASSERT_TRUE(!!wrapper); | 
|  | // Size is unnecessary for a VPP context. | 
|  | ASSERT_TRUE(wrapper->CreateContext(gfx::Size())); | 
|  |  | 
|  | const unsigned int va_rt_format_in = ToVaRTFormat(va_fourcc_in); | 
|  | ASSERT_NE(va_rt_format_in, kInvalidVaRtFormat); | 
|  | const unsigned int va_rt_format_out = ToVaRTFormat(va_fourcc_out); | 
|  | ASSERT_NE(va_rt_format_out, kInvalidVaRtFormat); | 
|  |  | 
|  | auto scoped_surfaces = wrapper->CreateScopedVASurfaces( | 
|  | va_rt_format_in, kInputSize, {VaapiWrapper::SurfaceUsageHint::kGeneric}, | 
|  | 1u, /*visible_size=*/absl::nullopt, /*va_fourcc=*/absl::nullopt); | 
|  | ASSERT_FALSE(scoped_surfaces.empty()); | 
|  | std::unique_ptr<ScopedVASurface> scoped_surface_in = | 
|  | std::move(scoped_surfaces[0]); | 
|  |  | 
|  | scoped_surfaces = wrapper->CreateScopedVASurfaces( | 
|  | va_rt_format_out, kOutputSize, {VaapiWrapper::SurfaceUsageHint::kGeneric}, | 
|  | 1u, /*visible_size=*/absl::nullopt, /*va_fourcc=*/absl::nullopt); | 
|  | ASSERT_FALSE(scoped_surfaces.empty()); | 
|  | std::unique_ptr<ScopedVASurface> scoped_surface_out = | 
|  | std::move(scoped_surfaces[0]); | 
|  |  | 
|  | scoped_refptr<VASurface> surface_in = base::MakeRefCounted<VASurface>( | 
|  | scoped_surface_in->id(), kInputSize, va_rt_format_in, base::DoNothing()); | 
|  | scoped_refptr<VASurface> surface_out = | 
|  | base::MakeRefCounted<VASurface>(scoped_surface_out->id(), kOutputSize, | 
|  | va_rt_format_out, base::DoNothing()); | 
|  |  | 
|  | ASSERT_TRUE(wrapper->BlitSurface(*surface_in, *surface_out, | 
|  | gfx::Rect(kInputSize), | 
|  | gfx::Rect(kOutputSize), VIDEO_ROTATION_0)); | 
|  | ASSERT_TRUE(wrapper->SyncSurface(scoped_surface_out->id())); | 
|  | wrapper->DestroyContext(); | 
|  | } | 
|  |  | 
|  | // TODO(b/187852384): Consider adding more VaapiVppTest cases, e.g. crops. | 
|  |  | 
|  | // Note: vaCreateSurfaces() uses the RT version of the Four CC, so we don't need | 
|  | // to consider swizzlings, since they'll end up mapped to the same RT format. | 
|  | constexpr uint32_t kVAFourCCs[] = {VA_FOURCC_I420, VA_FOURCC_YUY2, | 
|  | VA_FOURCC_RGBA, VA_FOURCC_P010}; | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(, | 
|  | VaapiVppTest, | 
|  | ::testing::Combine(::testing::ValuesIn(kVAFourCCs), | 
|  | ::testing::ValuesIn(kVAFourCCs)), | 
|  | VaapiVppTest::PrintToStringParamName()); | 
|  |  | 
|  | class VaapiMinigbmTest | 
|  | : public VaapiTest, | 
|  | public testing::WithParamInterface< | 
|  | std::tuple<VAProfile, unsigned int /*va_rt_format*/, gfx::Size>> { | 
|  | public: | 
|  | VaapiMinigbmTest() = default; | 
|  | ~VaapiMinigbmTest() override = default; | 
|  |  | 
|  | // Populate meaningful test suffixes instead of /0, /1, etc. | 
|  | struct PrintToStringParamName { | 
|  | template <class ParamType> | 
|  | std::string operator()( | 
|  | const testing::TestParamInfo<ParamType>& info) const { | 
|  | // Using here vaProfileStr(std::get<0>(info.param)) crashes the binary. | 
|  | // TODO(mcasas): investigate why and use it instead of codec%d. | 
|  | return base::StringPrintf( | 
|  | "%s__%s__%s", VAProfileToString(std::get<0>(info.param)), | 
|  | VARTFormatToString(std::get<1>(info.param)).c_str(), | 
|  | std::get<2>(info.param).ToString().c_str()); | 
|  | } | 
|  | }; | 
|  | }; | 
|  |  | 
|  | // This test allocates a VASurface (via VaapiWrapper) for the given VAProfile, | 
|  | // VA RT Format and resolution (as per the test parameters). It then verifies | 
|  | // that said VASurface's metadata (e.g. width, height, number of planes, pitch) | 
|  | // are the same as those we would allocate via minigbm. | 
|  | TEST_P(VaapiMinigbmTest, AllocateAndCompareWithMinigbm) { | 
|  | const VAProfile va_profile = std::get<0>(GetParam()); | 
|  | const unsigned int va_rt_format = std::get<1>(GetParam()); | 
|  | const gfx::Size resolution = std::get<2>(GetParam()); | 
|  |  | 
|  | // TODO(b/187852384): enable the other backends. | 
|  | if (VaapiWrapper::GetImplementationType() != VAImplementation::kIntelIHD) | 
|  | GTEST_SKIP() << "backend not supported"; | 
|  |  | 
|  | ASSERT_NE(va_rt_format, kInvalidVaRtFormat); | 
|  | if (!VaapiWrapper::IsDecodeSupported(va_profile)) | 
|  | GTEST_SKIP() << vaProfileStr(va_profile) << " not supported."; | 
|  |  | 
|  | if (!VaapiWrapper::IsDecodingSupportedForInternalFormat(va_profile, | 
|  | va_rt_format)) { | 
|  | GTEST_SKIP() << VARTFormatToString(va_rt_format) << " not supported."; | 
|  | } | 
|  |  | 
|  | gfx::Size minimum_supported_size; | 
|  | ASSERT_TRUE(VaapiWrapper::GetDecodeMinResolution(va_profile, | 
|  | &minimum_supported_size)); | 
|  | gfx::Size maximum_supported_size; | 
|  | ASSERT_TRUE(VaapiWrapper::GetDecodeMaxResolution(va_profile, | 
|  | &maximum_supported_size)); | 
|  |  | 
|  | if (resolution.width() < minimum_supported_size.width() || | 
|  | resolution.height() < minimum_supported_size.height() || | 
|  | resolution.width() > maximum_supported_size.width() || | 
|  | resolution.height() > maximum_supported_size.height()) { | 
|  | GTEST_SKIP() << resolution.ToString() | 
|  | << " not supported (min: " << minimum_supported_size.ToString() | 
|  | << ", max: " << maximum_supported_size.ToString() << ")"; | 
|  | } | 
|  |  | 
|  | auto wrapper = | 
|  | VaapiWrapper::Create(VaapiWrapper::kDecode, va_profile, | 
|  | EncryptionScheme::kUnencrypted, base::DoNothing()); | 
|  | ASSERT_TRUE(!!wrapper); | 
|  | ASSERT_TRUE(wrapper->CreateContext(resolution)); | 
|  |  | 
|  | auto scoped_surfaces = wrapper->CreateScopedVASurfaces( | 
|  | va_rt_format, resolution, {VaapiWrapper::SurfaceUsageHint::kVideoDecoder}, | 
|  | 1u, | 
|  | /*visible_size=*/absl::nullopt, /*va_fourcc=*/absl::nullopt); | 
|  | ASSERT_FALSE(scoped_surfaces.empty()); | 
|  | const auto scoped_va_surface = std::move(scoped_surfaces[0]); | 
|  | wrapper->DestroyContext(); | 
|  |  | 
|  | ASSERT_TRUE(scoped_va_surface->IsValid()); | 
|  | EXPECT_EQ(scoped_va_surface->format(), va_rt_format); | 
|  |  | 
|  | // Request the underlying DRM metadata for |scoped_va_surface|. | 
|  | VADRMPRIMESurfaceDescriptor va_descriptor{}; | 
|  | { | 
|  | base::AutoLock auto_lock(*wrapper->va_lock_); | 
|  | VAStatus va_res = | 
|  | vaSyncSurface(wrapper->va_display_, scoped_va_surface->id()); | 
|  | ASSERT_EQ(va_res, VA_STATUS_SUCCESS); | 
|  | va_res = vaExportSurfaceHandle( | 
|  | wrapper->va_display_, scoped_va_surface->id(), | 
|  | VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, | 
|  | VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS, | 
|  | &va_descriptor); | 
|  | ASSERT_EQ(va_res, VA_STATUS_SUCCESS); | 
|  | } | 
|  |  | 
|  | // Verify some expected properties of the allocated VASurface. We expect a | 
|  | // single |object|, with a number of |layers| of the same |pitch|. | 
|  | EXPECT_EQ(scoped_va_surface->size(), | 
|  | gfx::Size(base::checked_cast<int>(va_descriptor.width), | 
|  | base::checked_cast<int>(va_descriptor.height))); | 
|  |  | 
|  | const auto va_fourcc = ToVaFourcc(va_rt_format); | 
|  | ASSERT_NE(va_fourcc, base::checked_cast<unsigned int>(DRM_FORMAT_INVALID)); | 
|  | EXPECT_EQ(va_descriptor.fourcc, va_fourcc) | 
|  | << FourccToString(va_descriptor.fourcc) | 
|  | << " != " << FourccToString(va_fourcc); | 
|  | EXPECT_EQ(va_descriptor.num_objects, 1u); | 
|  | // TODO(mcasas): consider comparing |size| with a better estimate of the | 
|  | // |scoped_va_surface| memory footprint (e.g. including planes and format). | 
|  | EXPECT_GE(va_descriptor.objects[0].size, | 
|  | base::checked_cast<uint32_t>(scoped_va_surface->size().GetArea())); | 
|  | EXPECT_EQ(va_descriptor.objects[0].drm_format_modifier, | 
|  | I915_FORMAT_MOD_Y_TILED); | 
|  | // TODO(mcasas): |num_layers| actually depends on |va_descriptor.va_fourcc|. | 
|  | EXPECT_EQ(va_descriptor.num_layers, 2u); | 
|  | for (uint32_t i = 0; i < va_descriptor.num_layers; ++i) { | 
|  | EXPECT_EQ(va_descriptor.layers[i].num_planes, 1u); | 
|  | EXPECT_EQ(va_descriptor.layers[i].object_index[0], 0u); | 
|  |  | 
|  | DVLOG(2) << "plane " << i | 
|  | << ", pitch: " << va_descriptor.layers[i].pitch[0]; | 
|  | // Luma and chroma planes have different |pitch| expectations. | 
|  | // TODO(mcasas): consider bitdepth for pitch lower thresholds. | 
|  | if (i == 0) { | 
|  | EXPECT_GE( | 
|  | va_descriptor.layers[i].pitch[0], | 
|  | base::checked_cast<uint32_t>(scoped_va_surface->size().width())); | 
|  | } else { | 
|  | const auto expected_rounded_up_pitch = | 
|  | base::bits::AlignUp(scoped_va_surface->size().width(), 2); | 
|  | EXPECT_GE(va_descriptor.layers[i].pitch[0], | 
|  | base::checked_cast<uint32_t>(expected_rounded_up_pitch)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Now open minigbm pointing to the DRM primary node, allocate a gbm_bo, and | 
|  | // compare its width/height/stride/etc with the |va_descriptor|s. | 
|  | base::File drm_fd( | 
|  | base::FilePath("/dev/dri/card0"), | 
|  | base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE); | 
|  |  | 
|  | ASSERT_TRUE(drm_fd.IsValid()); | 
|  | struct gbm_device* gbm = gbm_create_device(drm_fd.GetPlatformFile()); | 
|  | ASSERT_TRUE(gbm); | 
|  |  | 
|  | const auto gbm_format = ToGBMFormat(va_rt_format); | 
|  | ASSERT_NE(gbm_format, DRM_FORMAT_INVALID); | 
|  | const auto bo_use_flags = GBM_BO_USE_TEXTURING | GBM_BO_USE_HW_VIDEO_DECODER; | 
|  | struct gbm_bo* bo = | 
|  | gbm_bo_create(gbm, resolution.width(), resolution.height(), gbm_format, | 
|  | bo_use_flags | GBM_BO_USE_SCANOUT); | 
|  | if (!bo) { | 
|  | // Try again without the scanout flag. This reproduces Chrome's behaviour. | 
|  | bo = gbm_bo_create(gbm, resolution.width(), resolution.height(), gbm_format, | 
|  | bo_use_flags); | 
|  | } | 
|  | ASSERT_TRUE(bo); | 
|  | EXPECT_EQ(scoped_va_surface->size(), | 
|  | gfx::Size(base::checked_cast<int>(gbm_bo_get_width(bo)), | 
|  | base::checked_cast<int>(gbm_bo_get_height(bo)))); | 
|  |  | 
|  | const int bo_num_planes = gbm_bo_get_plane_count(bo); | 
|  | ASSERT_EQ(va_descriptor.num_layers, | 
|  | base::checked_cast<uint32_t>(bo_num_planes)); | 
|  | for (int i = 0; i < bo_num_planes; ++i) { | 
|  | EXPECT_EQ(va_descriptor.layers[i].pitch[0], | 
|  | gbm_bo_get_stride_for_plane(bo, i)); | 
|  | } | 
|  |  | 
|  | // TODO(mcasas): consider comparing |va_descriptor.objects[0].size| with |bo|s | 
|  | // size (as returned by lseek()ing it). | 
|  |  | 
|  | gbm_bo_destroy(bo); | 
|  | gbm_device_destroy(gbm); | 
|  | } | 
|  |  | 
|  | constexpr VAProfile kVACodecProfiles[] = { | 
|  | VAProfileVP8Version0_3, VAProfileH264ConstrainedBaseline, | 
|  | VAProfileVP9Profile0,   VAProfileVP9Profile2, | 
|  | VAProfileAV1Profile0,   VAProfileJPEGBaseline}; | 
|  | constexpr uint32_t kVARTFormatsForGBM[] = {VA_RT_FORMAT_YUV420, | 
|  | VA_RT_FORMAT_YUV420_10}; | 
|  | constexpr gfx::Size kResolutions[] = { | 
|  | // clang-format off | 
|  | gfx::Size(127, 127), | 
|  | gfx::Size(128, 128), | 
|  | gfx::Size(129, 129), | 
|  | gfx::Size(320, 180), | 
|  | gfx::Size(320, 240),  // QVGA | 
|  | gfx::Size(323, 243), | 
|  | gfx::Size(480, 320),  // 3/4 VGA | 
|  | gfx::Size(640, 360),  // VGA | 
|  | gfx::Size(640, 480), | 
|  | gfx::Size(1280, 720)}; | 
|  | // clang-format on | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P( | 
|  | , | 
|  | VaapiMinigbmTest, | 
|  | ::testing::Combine(::testing::ValuesIn(kVACodecProfiles), | 
|  | ::testing::ValuesIn(kVARTFormatsForGBM), | 
|  | ::testing::ValuesIn(kResolutions)), | 
|  | VaapiMinigbmTest::PrintToStringParamName()); | 
|  |  | 
|  | }  // namespace media | 
|  |  | 
|  | int main(int argc, char** argv) { | 
|  | base::TestSuite test_suite(argc, argv); | 
|  | { | 
|  | // Enables/Disables features during PreSandboxInitialization(). We have to | 
|  | // destruct ScopedFeatureList after it because base::TestSuite::Run() | 
|  | // creates a ScopedFeatureList and multiple concurrent ScopedFeatureLists | 
|  | // are not allowed. | 
|  | auto scoped_feature_list = media::CreateScopedFeatureList(); | 
|  | // PreSandboxInitialization() loads and opens the driver, queries its | 
|  | // capabilities and fills in the VASupportedProfiles. | 
|  | media::VaapiWrapper::PreSandboxInitialization(); | 
|  | } | 
|  |  | 
|  | return base::LaunchUnitTests( | 
|  | argc, argv, | 
|  | base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite))); | 
|  | } |