// Copyright 2021 The Chromium Authors and Alex313031. 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_wrapper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base/bind.h" #include "base/bits.h" #include "base/callback_helpers.h" #include "base/containers/contains.h" #include "base/containers/cxx20_erase.h" #include "base/cpu.h" #include "base/cxx17_backports.h" #include "base/environment.h" #include "base/files/scoped_file.h" #include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "base/no_destructor.h" #include "base/numerics/checked_math.h" #include "base/numerics/safe_conversions.h" #include "base/posix/eintr_wrapper.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" #include "base/system/sys_info.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "media/base/media_switches.h" #include "media/base/video_frame.h" #include "media/base/video_types.h" #include "media/gpu/macros.h" #include "media/media_buildflags.h" // Auto-generated for dlopen libva libraries #include "media/gpu/vaapi/va_stubs.h" #include "third_party/libva_protected_content/va_protected_content.h" #include "third_party/libyuv/include/libyuv.h" #include "ui/gfx/buffer_format_util.h" #include "ui/gfx/buffer_types.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/linux/native_pixmap_dmabuf.h" #include "ui/gfx/native_pixmap.h" #include "ui/gfx/native_pixmap_handle.h" #include "ui/gl/gl_bindings.h" #include "ui/gl/gl_implementation.h" #if BUILDFLAG(USE_VAAPI_X11) typedef XID Drawable; extern "C" { #include "media/gpu/vaapi/va_x11.sigs" } #include "ui/gfx/x/connection.h" // nogncheck #endif // BUILDFLAG(USE_VAAPI_X11) #if defined(USE_OZONE) #include "ui/ozone/public/ozone_platform.h" #include "ui/ozone/public/surface_factory_ozone.h" #endif #if BUILDFLAG(IS_CHROMEOS_ASH) #include using media_gpu_vaapi::kModuleVa_prot; #endif using media_gpu_vaapi::kModuleVa; using media_gpu_vaapi::kModuleVa_drm; #if BUILDFLAG(USE_VAAPI_X11) using media_gpu_vaapi::kModuleVa_x11; #endif // BUILDFLAG(USE_VAAPI_X11) using media_gpu_vaapi::InitializeStubs; using media_gpu_vaapi::IsVaInitialized; #if BUILDFLAG(USE_VAAPI_X11) using media_gpu_vaapi::IsVa_x11Initialized; #endif // BUILDFLAG(USE_VAAPI_X11) using media_gpu_vaapi::IsVa_drmInitialized; using media_gpu_vaapi::StubPathMap; namespace media { // These values are logged to UMA. Entries should not be renumbered and numeric // values should never be reused. Please keep in sync with // "VaapiFunctions" in src/tools/metrics/histograms/enums.xml. enum class VaapiFunctions { kVABeginPicture = 0, kVACreateBuffer = 1, kVACreateConfig = 2, kVACreateContext = 3, kVACreateImage = 4, kVACreateSurfaces_Allocating = 5, kVACreateSurfaces_Importing = 6, kVADestroyBuffer = 7, kVADestroyConfig = 8, kVADestroyContext = 9, kVADestroySurfaces = 10, kVAEndPicture = 11, kVAExportSurfaceHandle = 12, kVAGetConfigAttributes = 13, kVAPutImage = 14, kVAPutSurface = 15, kVAQueryConfigAttributes = 16, kVAQueryImageFormats = 17, kVAQuerySurfaceAttributes = 18, kVAQueryVideoProcPipelineCaps = 19, kVARenderPicture_VABuffers = 20, kVARenderPicture_Vpp = 21, kVASyncSurface = 22, kVATerminate = 23, kVAUnmapBuffer = 24, // Protected mode functions below. kVACreateProtectedSession = 25, kVADestroyProtectedSession = 26, kVAAttachProtectedSession = 27, kVADetachProtectedSession = 28, kVAProtectedSessionHwUpdate_Deprecated = 29, kVAProtectedSessionExecute = 30, // Anything else is captured in this last entry. kOtherVAFunction = 31, kMaxValue = kOtherVAFunction, }; void ReportVaapiErrorToUMA(const std::string& histogram_name, VaapiFunctions value) { UMA_HISTOGRAM_ENUMERATION(histogram_name, value); } constexpr std::array(VaapiFunctions::kMaxValue) + 1> kVaapiFunctionNames = {"vaBeginPicture", "vaCreateBuffer", "vaCreateConfig", "vaCreateContext", "vaCreateImage", "vaCreateSurfaces (allocate mode)", "vaCreateSurfaces (import mode)", "vaDestroyBuffer", "vaDestroyConfig", "vaDestroyContext", "vaDestroySurfaces", "vaEndPicture", "vaExportSurfaceHandle", "vaGetConfigAttributes", "vaPutImage", "vaPutSurface", "vaQueryConfigAttributes", "vaQueryImageFormats", "vaQuerySurfaceAttributes", "vaQueryVideoProcPipelineCaps", "vaRenderPicture (|pending_va_buffers_|)", "vaRenderPicture using Vpp", "vaSyncSurface", "vaTerminate", "vaUnmapBuffer", "vaCreateProtectedSession", "vaDestroyProtectedSession", "vaAttachProtectedSession", "vaDetachProtectedSession", "vaProtectedSessionHwUpdate (Deprecated)", "vaProtectedSessionExecute", "Other VA function"}; // Translates |function| into a human readable string for logging. const char* VaapiFunctionName(VaapiFunctions function) { DCHECK(function <= VaapiFunctions::kMaxValue); return kVaapiFunctionNames[static_cast(function)]; } } // namespace media #define LOG_VA_ERROR_AND_REPORT(va_error, function) \ do { \ LOG(ERROR) << VaapiFunctionName(function) \ << " failed, VA error: " << vaErrorStr(va_error); \ report_error_to_uma_cb_.Run(function); \ } while (0) #define VA_LOG_ON_ERROR(va_res, function) \ do { \ const VAStatus va_res_va_log_on_error = (va_res); \ if (va_res_va_log_on_error != VA_STATUS_SUCCESS) \ LOG_VA_ERROR_AND_REPORT(va_res_va_log_on_error, function); \ } while (0) #define VA_SUCCESS_OR_RETURN(va_res, function, ret) \ do { \ const VAStatus va_res_va_sucess_or_return = (va_res); \ if (va_res_va_sucess_or_return != VA_STATUS_SUCCESS) { \ LOG_VA_ERROR_AND_REPORT(va_res_va_sucess_or_return, function); \ return (ret); \ } \ DVLOG(3) << VaapiFunctionName(function); \ } while (0) namespace { uint32_t BufferFormatToVAFourCC(gfx::BufferFormat fmt) { switch (fmt) { case gfx::BufferFormat::BGRX_8888: return VA_FOURCC_BGRX; case gfx::BufferFormat::BGRA_8888: return VA_FOURCC_BGRA; case gfx::BufferFormat::RGBX_8888: return VA_FOURCC_RGBX; case gfx::BufferFormat::RGBA_8888: return VA_FOURCC_RGBA; case gfx::BufferFormat::YVU_420: return VA_FOURCC_YV12; case gfx::BufferFormat::YUV_420_BIPLANAR: return VA_FOURCC_NV12; case gfx::BufferFormat::P010: return VA_FOURCC_P010; default: NOTREACHED() << gfx::BufferFormatToString(fmt); return 0; } } media::VAImplementation VendorStringToImplementationType( const std::string& va_vendor_string) { if (base::StartsWith(va_vendor_string, "Mesa Gallium driver", base::CompareCase::SENSITIVE)) { return media::VAImplementation::kMesaGallium; } else if (base::StartsWith(va_vendor_string, "Intel i965 driver", base::CompareCase::SENSITIVE)) { return media::VAImplementation::kIntelI965; } else if (base::StartsWith(va_vendor_string, "Intel iHD driver", base::CompareCase::SENSITIVE)) { return media::VAImplementation::kIntelIHD; } else if (base::StartsWith(va_vendor_string, "Splitted-Desktop Systems VDPAU", base::CompareCase::SENSITIVE)) { return media::VAImplementation::kNVIDIAVDPAU; } return media::VAImplementation::kOther; } bool UseGlobalVaapiLock(media::VAImplementation implementation_type) { // Only iHD is known to be thread safe at the moment, see crbug.com/1123429. return implementation_type != media::VAImplementation::kIntelIHD || base::FeatureList::IsEnabled(media::kGlobalVaapiLock); } } // namespace namespace media { namespace { // VAEntrypoint is an enumeration starting from 1, but has no "invalid" value. constexpr VAEntrypoint kVAEntrypointInvalid = static_cast(0); // Returns true if the SoC has a Gen8 GPU. CPU model ID's are referenced from // the following file in the kernel source: arch/x86/include/asm/intel-family.h. bool IsGen8Gpu() { constexpr int kPentiumAndLaterFamily = 0x06; constexpr int kBroadwellCoreModelId = 0x3D; constexpr int kBroadwellGT3EModelId = 0x47; constexpr int kBroadwellXModelId = 0x4F; constexpr int kBroadwellXeonDModelId = 0x56; constexpr int kBraswellModelId = 0x4C; static const base::NoDestructor cpuid; static const bool is_gen8_gpu = cpuid->family() == kPentiumAndLaterFamily && (cpuid->model() == kBroadwellCoreModelId || cpuid->model() == kBroadwellGT3EModelId || cpuid->model() == kBroadwellXModelId || cpuid->model() == kBroadwellXeonDModelId || cpuid->model() == kBraswellModelId); return is_gen8_gpu; } // Returns true if the SoC has a Gen9 GPU. CPU model ID's are referenced from // the following file in the kernel source: arch/x86/include/asm/intel-family.h. bool IsGen9Gpu() { constexpr int kPentiumAndLaterFamily = 0x06; constexpr int kSkyLakeModelId = 0x5E; constexpr int kSkyLake_LModelId = 0x4E; constexpr int kApolloLakeModelId = 0x5c; static const base::NoDestructor cpuid; static const bool is_gen9_gpu = cpuid->family() == kPentiumAndLaterFamily && (cpuid->model() == kSkyLakeModelId || cpuid->model() == kSkyLake_LModelId || cpuid->model() == kApolloLakeModelId); return is_gen9_gpu; } // Returns true if the SoC has a 9.5 GPU. CPU model IDs are referenced from the // following file in the kernel source: arch/x86/include/asm/intel-family.h. bool IsGen95Gpu() { constexpr int kPentiumAndLaterFamily = 0x06; constexpr int kKabyLakeModelId = 0x9E; // Amber Lake, Whiskey Lake and some Comet Lake CPU IDs are the same as KBL L. constexpr int kKabyLake_LModelId = 0x8E; constexpr int kGeminiLakeModelId = 0x7A; constexpr int kCometLakeModelId = 0xA5; constexpr int kCometLake_LModelId = 0xA6; static const base::NoDestructor cpuid; static const bool is_gen95_gpu = cpuid->family() == kPentiumAndLaterFamily && (cpuid->model() == kKabyLakeModelId || cpuid->model() == kKabyLake_LModelId || cpuid->model() == kGeminiLakeModelId || cpuid->model() == kCometLakeModelId || cpuid->model() == kCometLake_LModelId); return is_gen95_gpu; } // Returns true if the intel hybrid driver is used for decoding |va_profile|. // https://github.com/intel/intel-hybrid-driver // Note that since the hybrid driver runs as a part of the i965 driver, // vaQueryVendorString() returns "Intel i965 driver". bool IsUsingHybridDriverForDecoding(VAProfile va_profile) { // Note that Skylake (not gen8) also needs the hybrid decoder for VP9 // decoding. However, it is disabled today on ChromeOS // (see crrev.com/c/390511). return va_profile == VAProfileVP9Profile0 && IsGen8Gpu(); } // Returns true if the SoC is considered a low power one, i.e. it's an Intel // Pentium, Celeron, or a Core Y-series. See go/intel-socs-101 or // https://www.intel.com/content/www/us/en/processors/processor-numbers.html. bool IsLowPowerIntelProcessor() { constexpr int kPentiumAndLaterFamily = 0x06; static const base::NoDestructor cpuid; static const bool is_core_y_processor = base::MatchPattern(cpuid->cpu_brand(), "Intel(R) Core(TM) *Y CPU*"); static 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); return is_low_power_intel; } bool IsModeEncoding(VaapiWrapper::CodecMode mode) { return mode == VaapiWrapper::CodecMode::kEncodeConstantBitrate || mode == VaapiWrapper::CodecMode::kEncodeConstantQuantizationParameter; } bool GetNV12VisibleWidthBytes(int visible_width, uint32_t plane, size_t* bytes) { if (plane == 0) { *bytes = base::checked_cast(visible_width); return true; } *bytes = base::checked_cast(visible_width); return visible_width % 2 == 0 || base::CheckAdd(visible_width, 1).AssignIfValid(bytes); } // Fill 0 on VAImage's non visible area. bool ClearNV12Padding(const VAImage& image, const gfx::Size& visible_size, uint8_t* data) { DCHECK_EQ(2u, image.num_planes); DCHECK_EQ(image.format.fourcc, static_cast(VA_FOURCC_NV12)); size_t visible_width_bytes[2] = {}; if (!GetNV12VisibleWidthBytes(visible_size.width(), 0u, &visible_width_bytes[0]) || !GetNV12VisibleWidthBytes(visible_size.width(), 1u, &visible_width_bytes[1])) { return false; } for (uint32_t plane = 0; plane < image.num_planes; plane++) { size_t row_bytes = base::strict_cast(image.pitches[plane]); if (row_bytes == visible_width_bytes[plane]) continue; CHECK_GT(row_bytes, visible_width_bytes[plane]); int visible_height = visible_size.height(); if (plane == 1 && !(base::CheckAdd(visible_size.height(), 1) / 2) .AssignIfValid(&visible_height)) { return false; } const size_t padding_bytes = row_bytes - visible_width_bytes[plane]; uint8_t* plane_data = data + image.offsets[plane]; for (int row = 0; row < visible_height; row++, plane_data += row_bytes) memset(plane_data + visible_width_bytes[plane], 0, padding_bytes); CHECK_GE(base::strict_cast(image.height), visible_height); size_t image_height = base::strict_cast(image.height); if (plane == 1 && !(base::CheckAdd(image.height, 1) / 2) .AssignIfValid(&image_height)) { return false; } base::CheckedNumeric remaining_area(image_height); remaining_area -= base::checked_cast(visible_height); remaining_area *= row_bytes; if (!remaining_area.IsValid()) return false; memset(plane_data, 0, remaining_area.ValueOrDie()); } return true; } // Can't statically initialize the profile map: // https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables using ProfileCodecMap = std::map; const ProfileCodecMap& GetProfileCodecMap() { static const base::NoDestructor kMediaToVAProfileMap({ // VAProfileH264Baseline is deprecated in since libva 2.0.0. {H264PROFILE_BASELINE, VAProfileH264ConstrainedBaseline}, {H264PROFILE_MAIN, VAProfileH264Main}, // TODO(posciak): See if we can/want to support other variants of // H264PROFILE_HIGH*. {H264PROFILE_HIGH, VAProfileH264High}, {VP8PROFILE_ANY, VAProfileVP8Version0_3}, {VP9PROFILE_PROFILE0, VAProfileVP9Profile0}, // VaapiWrapper does not support VP9 Profile 1, see b/153680337. // {VP9PROFILE_PROFILE1, VAProfileVP9Profile1}, {VP9PROFILE_PROFILE2, VAProfileVP9Profile2}, // VaapiWrapper does not support Profile 3. //{VP9PROFILE_PROFILE3, VAProfileVP9Profile3}, {AV1PROFILE_PROFILE_MAIN, VAProfileAV1Profile0}, // VaapiWrapper does not support AV1 Profile 1. // {AV1PROFILE_PROFILE_HIGH, VAProfileAV1Profile1}, #if BUILDFLAG(ENABLE_PLATFORM_HEVC_DECODING) {HEVCPROFILE_MAIN, VAProfileHEVCMain}, {HEVCPROFILE_MAIN10, VAProfileHEVCMain10}, #endif }); return *kMediaToVAProfileMap; } // Maps a VideoCodecProfile |profile| to a VAProfile, or VAProfileNone. VAProfile ProfileToVAProfile(VideoCodecProfile profile, VaapiWrapper::CodecMode mode) { const auto& profiles = GetProfileCodecMap(); const auto& maybe_profile = profiles.find(profile); if (maybe_profile == profiles.end()) return VAProfileNone; return maybe_profile->second; } bool IsVAProfileSupported(VAProfile va_profile) { const auto& profiles = GetProfileCodecMap(); // VAProfileJPEGBaseline and VAProfileProtected are always recognized but are // not video codecs per se. return va_profile == VAProfileJPEGBaseline || #if BUILDFLAG(IS_CHROMEOS_ASH) va_profile == VAProfileProtected || #endif std::find_if(profiles.begin(), profiles.end(), [va_profile](const auto& entry) { return entry.second == va_profile; }) != profiles.end(); } bool IsBlockedDriver(VaapiWrapper::CodecMode mode, VAProfile va_profile) { if (!IsModeEncoding(mode)) { return va_profile == VAProfileAV1Profile0 && !base::FeatureList::IsEnabled(kVaapiAV1Decoder); } // TODO(posciak): Remove once VP8 encoding is to be enabled by default. if (va_profile == VAProfileVP8Version0_3 && !base::FeatureList::IsEnabled(kVaapiVP8Encoder)) { return true; } // TODO(crbug.com/811912): Remove once VP9 encoding is enabled by default. if (va_profile == VAProfileVP9Profile0 && !base::FeatureList::IsEnabled(kVaapiVP9Encoder)) { return true; } return false; } // This class is a wrapper around its |va_display_| (and its associated // |va_lock_|) to guarantee mutual exclusion and singleton behaviour. class VADisplayState { public: static VADisplayState* Get(); VADisplayState(const VADisplayState&) = delete; VADisplayState& operator=(const VADisplayState&) = delete; // Initialize static data before sandbox is enabled. static void PreSandboxInitialization(); bool Initialize(); VAStatus Deinitialize(); base::Lock* va_lock() { return &va_lock_; } VADisplay va_display() const { return va_display_; } VAImplementation implementation_type() const { return implementation_type_; } void SetDrmFd(base::PlatformFile fd) { drm_fd_.reset(HANDLE_EINTR(dup(fd))); } private: friend class base::NoDestructor; VADisplayState(); ~VADisplayState() = default; // Implementation of Initialize() called only once. bool InitializeOnce() EXCLUSIVE_LOCKS_REQUIRED(va_lock_); bool InitializeVaDisplay_Locked() EXCLUSIVE_LOCKS_REQUIRED(va_lock_); bool InitializeVaDriver_Locked() EXCLUSIVE_LOCKS_REQUIRED(va_lock_); int refcount_ GUARDED_BY(va_lock_); // Libva may or may not be thread safe depending on the backend. If not thread // safe, we have to do locking for it ourselves. Therefore, this lock may need // to be taken for the duration of all VA-API calls and for the entire job // submission sequence in ExecuteAndDestroyPendingBuffers(). base::Lock va_lock_; // Drm fd used to obtain access to the driver interface by VA. base::ScopedFD drm_fd_; // The VADisplay handle. Valid between Initialize() and Deinitialize(). VADisplay va_display_; // True if vaInitialize() has been called successfully, until Deinitialize(). bool va_initialized_; // Enumerated version of vaQueryVendorString(). Valid after Initialize(). VAImplementation implementation_type_ = VAImplementation::kInvalid; }; // static VADisplayState* VADisplayState::Get() { static base::NoDestructor display_state; return display_state.get(); } // static void VADisplayState::PreSandboxInitialization() { const char kDriRenderNode0Path[] = "/dev/dri/renderD128"; base::File drm_file = base::File( base::FilePath::FromUTF8Unsafe(kDriRenderNode0Path), base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE); if (drm_file.IsValid()) VADisplayState::Get()->SetDrmFd(drm_file.GetPlatformFile()); const char kNvidiaPath[] = "/dev/dri/nvidiactl"; base::File nvidia_file = base::File( base::FilePath::FromUTF8Unsafe(kNvidiaPath), base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE); } VADisplayState::VADisplayState() : refcount_(0), va_display_(nullptr), va_initialized_(false) {} bool VADisplayState::Initialize() { base::AutoLock auto_lock(va_lock_); #if defined(USE_OZONE) && defined(OS_LINUX) // TODO(crbug.com/1116701): add vaapi support for other Ozone platforms on // Linux. See comment in OzonePlatform::PlatformProperties::supports_vaapi // for more details. This will also require revisiting everything that's // guarded by USE_VAAPI_X11. For example, if USE_VAAPI_X11 is true, but the // user chooses the Wayland backend for Ozone at runtime, then many things (if // not all) that we do for X11 won't apply. if (!ui::OzonePlatform::GetInstance()->GetPlatformProperties().supports_vaapi) return false; #endif bool libraries_initialized = IsVaInitialized() && IsVa_drmInitialized(); #if BUILDFLAG(USE_VAAPI_X11) libraries_initialized = libraries_initialized && IsVa_x11Initialized(); #endif if (!libraries_initialized) return false; // Manual refcounting to ensure the rest of the method is called only once. if (refcount_++ > 0) return true; const bool success = InitializeOnce(); UMA_HISTOGRAM_BOOLEAN("Media.VaapiWrapper.VADisplayStateInitializeSuccess", success); return success; } #if BUILDFLAG(USE_VAAPI_X11) absl::optional GetVADisplayStateX11(const base::ScopedFD& drm_fd) { switch (gl::GetGLImplementation()) { case gl::kGLImplementationEGLGLES2: return vaGetDisplayDRM(drm_fd.get()); case gl::kGLImplementationNone: case gl::kGLImplementationDesktopGL: { VADisplay display = vaGetDisplay(x11::Connection::Get()->GetXlibDisplay()); if (vaDisplayIsValid(display)) return display; return vaGetDisplayDRM(drm_fd.get()); } case gl::kGLImplementationEGLANGLE: return vaGetDisplay(x11::Connection::Get()->GetXlibDisplay()); default: LOG(WARNING) << "VAAPI video acceleration not available for " << gl::GetGLImplementationGLName( gl::GetGLImplementationParts()); return absl::nullopt; } } #else absl::optional GetVADisplayState(const base::ScopedFD& drm_fd) { switch (gl::GetGLImplementation()) { case gl::kGLImplementationEGLGLES2: case gl::kGLImplementationNone: return vaGetDisplayDRM(drm_fd.get()); default: LOG(WARNING) << "VAAPI video acceleration not available for " << gl::GetGLImplementationGLName( gl::GetGLImplementationParts()); return absl::nullopt; } } #endif // BUILDFLAG(USE_VAAPI_X11) bool VADisplayState::InitializeVaDisplay_Locked() { absl::optional display = #if BUILDFLAG(USE_VAAPI_X11) GetVADisplayStateX11(drm_fd_); #else GetVADisplayState(drm_fd_); #endif if (!display) return false; va_display_ = *display; if (!vaDisplayIsValid(va_display_)) { LOG(ERROR) << "Could not get a valid VA display"; return false; } return true; } bool VADisplayState::InitializeVaDriver_Locked() { // The VAAPI version. int major_version, minor_version; VAStatus va_res = vaInitialize(va_display_, &major_version, &minor_version); if (va_res != VA_STATUS_SUCCESS) { VLOGF(1) << "vaInitialize failed: " << vaErrorStr(va_res); return false; } const std::string va_vendor_string = vaQueryVendorString(va_display_); DLOG_IF(WARNING, va_vendor_string.empty()) << "Vendor string empty or error reading."; DVLOG(1) << "VAAPI version: " << major_version << "." << minor_version << " " << va_vendor_string; implementation_type_ = VendorStringToImplementationType(va_vendor_string); va_initialized_ = true; // The VAAPI version is determined from what is loaded on the system by // calling vaInitialize(). Since the libva is now ABI-compatible, relax the // version check which helps in upgrading the libva, without breaking any // existing functionality. Make sure the system version is not older than // the version with which the chromium is built since libva is only // guaranteed to be backward (and not forward) compatible. if (VA_MAJOR_VERSION > major_version || (VA_MAJOR_VERSION == major_version && VA_MINOR_VERSION > minor_version)) { VLOGF(1) << "The system version " << major_version << "." << minor_version << " should be greater than or equal to " << VA_MAJOR_VERSION << "." << VA_MINOR_VERSION; return false; } return true; } bool VADisplayState::InitializeOnce() { // Set VA logging level, unless already set. constexpr char libva_log_level_env[] = "LIBVA_MESSAGING_LEVEL"; std::unique_ptr env(base::Environment::Create()); if (!env->HasVar(libva_log_level_env)) env->SetVar(libva_log_level_env, "1"); if (!InitializeVaDisplay_Locked() || !InitializeVaDriver_Locked()) return false; return true; } VAStatus VADisplayState::Deinitialize() { base::AutoLock auto_lock(va_lock_); VAStatus va_res = VA_STATUS_SUCCESS; if (--refcount_ > 0) return va_res; // Must check if vaInitialize completed successfully, to work around a bug in // libva. The bug was fixed upstream: // http://lists.freedesktop.org/archives/libva/2013-July/001807.html // TODO(mgiuca): Remove this check, and the |va_initialized_| variable, once // the fix has rolled out sufficiently. if (va_initialized_ && va_display_) va_res = vaTerminate(va_display_); va_initialized_ = false; va_display_ = nullptr; return va_res; } // Returns all the VAProfiles that the driver lists as supported, regardless of // what Chrome supports or not. std::vector GetSupportedVAProfiles(const base::Lock* va_lock, VADisplay va_display) { MAYBE_ASSERT_ACQUIRED(va_lock); // Query the driver for supported profiles. const int max_va_profiles = vaMaxNumProfiles(va_display); std::vector va_profiles( base::checked_cast(max_va_profiles)); int num_va_profiles; const VAStatus va_res = vaQueryConfigProfiles(va_display, &va_profiles[0], &num_va_profiles); if (va_res != VA_STATUS_SUCCESS) { LOG(ERROR) << "vaQueryConfigProfiles failed: " << vaErrorStr(va_res); return {}; } if (num_va_profiles < 0 || num_va_profiles > max_va_profiles) { LOG(ERROR) << "vaQueryConfigProfiles returned: " << num_va_profiles << " profiles"; return {}; } va_profiles.resize(base::checked_cast(num_va_profiles)); return va_profiles; } // Queries the driver for the supported entrypoints for |va_profile|, then // returns those allowed for |mode|. std::vector GetEntryPointsForProfile(const base::Lock* va_lock, VADisplay va_display, VaapiWrapper::CodecMode mode, VAProfile va_profile) { MAYBE_ASSERT_ACQUIRED(va_lock); const int max_entrypoints = vaMaxNumEntrypoints(va_display); std::vector va_entrypoints( base::checked_cast(max_entrypoints)); int num_va_entrypoints; const VAStatus va_res = vaQueryConfigEntrypoints( va_display, va_profile, &va_entrypoints[0], &num_va_entrypoints); if (va_res != VA_STATUS_SUCCESS) { LOG(ERROR) << "vaQueryConfigEntrypoints failed, VA error: " << vaErrorStr(va_res); return {}; } if (num_va_entrypoints < 0 || num_va_entrypoints > max_entrypoints) { LOG(ERROR) << "vaQueryConfigEntrypoints returned: " << num_va_entrypoints << " entry points, when the max is: " << max_entrypoints; return {}; } va_entrypoints.resize(num_va_entrypoints); const std::vector kAllowedEntryPoints[] = { {VAEntrypointVLD}, // kDecode. #if BUILDFLAG(IS_CHROMEOS_ASH) {VAEntrypointVLD, VAEntrypointProtectedContent}, // kDecodeProtected. #endif {VAEntrypointEncSlice, VAEntrypointEncPicture, VAEntrypointEncSliceLP}, // kEncodeConstantBitrate. {VAEntrypointEncSlice, VAEntrypointEncSliceLP}, // kEncodeConstantQuantizationParameter. {VAEntrypointVideoProc} // kVideoProcess. }; static_assert(base::size(kAllowedEntryPoints) == VaapiWrapper::kCodecModeMax, ""); std::vector entrypoints; std::copy_if(va_entrypoints.begin(), va_entrypoints.end(), std::back_inserter(entrypoints), [&kAllowedEntryPoints, mode](VAEntrypoint entry_point) { return base::Contains(kAllowedEntryPoints[mode], entry_point); }); return entrypoints; } bool GetRequiredAttribs(const base::Lock* va_lock, VADisplay va_display, VaapiWrapper::CodecMode mode, VAProfile profile, VAEntrypoint entrypoint, std::vector* required_attribs) { MAYBE_ASSERT_ACQUIRED(va_lock); // Choose a suitable VAConfigAttribRTFormat for every |mode|. For video // processing, the supported surface attribs may vary according to which RT // format is set. if (profile == VAProfileVP9Profile2 || profile == VAProfileVP9Profile3) { required_attribs->push_back( {VAConfigAttribRTFormat, VA_RT_FORMAT_YUV420_10BPP}); #if BUILDFLAG(IS_CHROMEOS_ASH) } else if (profile == VAProfileProtected) { DCHECK_EQ(mode, VaapiWrapper::kDecodeProtected); constexpr int kWidevineUsage = 0x1; required_attribs->push_back( {VAConfigAttribProtectedContentUsage, kWidevineUsage}); required_attribs->push_back( {VAConfigAttribProtectedContentCipherAlgorithm, VA_PC_CIPHER_AES}); required_attribs->push_back( {VAConfigAttribProtectedContentCipherBlockSize, VA_PC_BLOCK_SIZE_128}); required_attribs->push_back( {VAConfigAttribProtectedContentCipherMode, VA_PC_CIPHER_MODE_CTR}); #endif } else { required_attribs->push_back({VAConfigAttribRTFormat, VA_RT_FORMAT_YUV420}); } #if BUILDFLAG(IS_CHROMEOS_ASH) if (mode == VaapiWrapper::kDecodeProtected && profile != VAProfileProtected) { required_attribs->push_back( {VAConfigAttribEncryption, VA_ENCRYPTION_TYPE_SUBSAMPLE_CTR}); } #endif if (!IsModeEncoding(mode)) return true; if (profile == VAProfileJPEGBaseline) return true; if (mode == VaapiWrapper::kEncodeConstantBitrate) required_attribs->push_back({VAConfigAttribRateControl, VA_RC_CBR}); if (mode == VaapiWrapper::kEncodeConstantQuantizationParameter) required_attribs->push_back({VAConfigAttribRateControl, VA_RC_CQP}); constexpr VAProfile kSupportedH264VaProfilesForEncoding[] = { VAProfileH264ConstrainedBaseline, VAProfileH264Main, VAProfileH264High}; // VAConfigAttribEncPackedHeaders is H.264 specific. if (base::Contains(kSupportedH264VaProfilesForEncoding, profile)) { // Encode with Packed header if the driver supports. VAConfigAttrib attrib{}; attrib.type = VAConfigAttribEncPackedHeaders; const VAStatus va_res = vaGetConfigAttributes(va_display, profile, entrypoint, &attrib, 1); if (va_res != VA_STATUS_SUCCESS) { LOG(ERROR) << "vaGetConfigAttributes failed: " << vaProfileStr(profile); return false; } const uint32_t packed_header_attributes = (VA_ENC_PACKED_HEADER_SEQUENCE | VA_ENC_PACKED_HEADER_PICTURE | VA_ENC_PACKED_HEADER_SLICE); if ((packed_header_attributes & attrib.value) == packed_header_attributes) { required_attribs->push_back( {VAConfigAttribEncPackedHeaders, packed_header_attributes}); } else { required_attribs->push_back( {VAConfigAttribEncPackedHeaders, VA_ENC_PACKED_HEADER_NONE}); } } return true; } // Returns true if |va_profile| for |entrypoint| with |required_attribs| is // supported. bool AreAttribsSupported(const base::Lock* va_lock, VADisplay va_display, VAProfile va_profile, VAEntrypoint entrypoint, const std::vector& required_attribs) { MAYBE_ASSERT_ACQUIRED(va_lock); // Query the driver for required attributes. std::vector attribs = required_attribs; for (size_t i = 0; i < required_attribs.size(); ++i) attribs[i].value = 0; VAStatus va_res = vaGetConfigAttributes(va_display, va_profile, entrypoint, &attribs[0], attribs.size()); if (va_res != VA_STATUS_SUCCESS) { LOG(ERROR) << "vaGetConfigAttributes failed error: " << vaErrorStr(va_res); return false; } for (size_t i = 0; i < required_attribs.size(); ++i) { if (attribs[i].type != required_attribs[i].type || (attribs[i].value & required_attribs[i].value) != required_attribs[i].value) { VLOG(1) << "Unsupported value " << required_attribs[i].value << " for " << vaConfigAttribTypeStr(required_attribs[i].type); return false; } } return true; } // This class encapsulates reading and giving access to the list of supported // ProfileInfo entries, in a singleton way. class VASupportedProfiles { public: struct ProfileInfo { VAProfile va_profile; VAEntrypoint va_entrypoint; gfx::Size min_resolution; gfx::Size max_resolution; std::vector pixel_formats; VaapiWrapper::InternalFormats supported_internal_formats; }; static const VASupportedProfiles& Get(); VASupportedProfiles(const VASupportedProfiles&) = delete; VASupportedProfiles& operator=(const VASupportedProfiles&) = delete; // Determines if |mode| supports |va_profile| (and |va_entrypoint| if defined // and valid). If so, returns a const pointer to its ProfileInfo, otherwise // returns nullptr. const ProfileInfo* IsProfileSupported( VaapiWrapper::CodecMode mode, VAProfile va_profile, VAEntrypoint va_entrypoint = kVAEntrypointInvalid) const; private: friend class base::NoDestructor; friend std::map> VaapiWrapper::GetSupportedConfigurationsForCodecModeForTesting( CodecMode mode); VASupportedProfiles(); ~VASupportedProfiles() = default; // Fills in |supported_profiles_|. void FillSupportedProfileInfos(base::Lock* va_lock, VADisplay va_display); // Fills |profile_info| for |va_profile| and |entrypoint| with // |required_attribs|. If the return value is true, the operation was // successful. Otherwise, the information in *|profile_info| shouldn't be // relied upon. bool FillProfileInfo_Locked(const base::Lock* va_lock, VADisplay va_display, VAProfile va_profile, VAEntrypoint entrypoint, std::vector& required_attribs, ProfileInfo* profile_info) const; std::vector supported_profiles_[VaapiWrapper::kCodecModeMax]; static_assert(std::extent() == VaapiWrapper::kCodecModeMax, "|supported_profiles_| size is incorrect."); const ReportErrorToUMACB report_error_to_uma_cb_; }; // static const VASupportedProfiles& VASupportedProfiles::Get() { static const base::NoDestructor profile_infos; return *profile_infos; } const VASupportedProfiles::ProfileInfo* VASupportedProfiles::IsProfileSupported( VaapiWrapper::CodecMode mode, VAProfile va_profile, VAEntrypoint va_entrypoint) const { auto iter = std::find_if( supported_profiles_[mode].begin(), supported_profiles_[mode].end(), [va_profile, va_entrypoint](const ProfileInfo& profile) { return profile.va_profile == va_profile && (va_entrypoint == kVAEntrypointInvalid || profile.va_entrypoint == va_entrypoint); }); if (iter != supported_profiles_[mode].end()) return &*iter; return nullptr; } VASupportedProfiles::VASupportedProfiles() : report_error_to_uma_cb_(base::DoNothing()) { VADisplayState* display_state = VADisplayState::Get(); if (!display_state->Initialize()) return; VADisplay va_display = display_state->va_display(); DCHECK(va_display) << "VADisplayState hasn't been properly Initialize()d"; base::Lock* va_lock = display_state->va_lock(); if (!UseGlobalVaapiLock(display_state->implementation_type())) { va_lock = nullptr; } FillSupportedProfileInfos(va_lock, va_display); const VAStatus va_res = display_state->Deinitialize(); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVATerminate); } void VASupportedProfiles::FillSupportedProfileInfos(base::Lock* va_lock, VADisplay va_display) { base::AutoLockMaybe auto_lock(va_lock); const std::vector va_profiles = GetSupportedVAProfiles(va_lock, va_display); constexpr VaapiWrapper::CodecMode kWrapperModes[] = { VaapiWrapper::kDecode, #if BUILDFLAG(IS_CHROMEOS_ASH) VaapiWrapper::kDecodeProtected, #endif VaapiWrapper::kEncodeConstantBitrate, VaapiWrapper::kEncodeConstantQuantizationParameter, VaapiWrapper::kVideoProcess }; static_assert(base::size(kWrapperModes) == VaapiWrapper::kCodecModeMax, ""); for (VaapiWrapper::CodecMode mode : kWrapperModes) { std::vector supported_profile_infos; for (const auto& va_profile : va_profiles) { if (IsBlockedDriver(mode, va_profile)) continue; if ((mode != VaapiWrapper::kVideoProcess) && !IsVAProfileSupported(va_profile)) { continue; } const std::vector supported_entrypoints = GetEntryPointsForProfile(va_lock, va_display, mode, va_profile); for (const auto& entrypoint : supported_entrypoints) { std::vector required_attribs; if (!GetRequiredAttribs(va_lock, va_display, mode, va_profile, entrypoint, &required_attribs)) { continue; } if (!AreAttribsSupported(va_lock, va_display, va_profile, entrypoint, required_attribs)) { continue; } ProfileInfo profile_info{}; if (!FillProfileInfo_Locked(va_lock, va_display, va_profile, entrypoint, required_attribs, &profile_info)) { LOG(ERROR) << "FillProfileInfo_Locked failed for va_profile " << vaProfileStr(va_profile) << " and entrypoint " << vaEntrypointStr(entrypoint); continue; } supported_profile_infos.push_back(profile_info); } } supported_profiles_[static_cast(mode)] = supported_profile_infos; } } bool VASupportedProfiles::FillProfileInfo_Locked( const base::Lock* va_lock, VADisplay va_display, VAProfile va_profile, VAEntrypoint entrypoint, std::vector& required_attribs, ProfileInfo* profile_info) const { MAYBE_ASSERT_ACQUIRED(va_lock); VAConfigID va_config_id; VAStatus va_res = vaCreateConfig(va_display, va_profile, entrypoint, &required_attribs[0], required_attribs.size(), &va_config_id); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateConfig, false); base::ScopedClosureRunner vaconfig_destroyer(base::BindOnce( [](VADisplay display, VAConfigID id) { if (id != VA_INVALID_ID) { VAStatus va_res = vaDestroyConfig(display, id); if (va_res != VA_STATUS_SUCCESS) LOG(ERROR) << "vaDestroyConfig failed. VA error: " << vaErrorStr(va_res); } }, va_display, va_config_id)); #if BUILDFLAG(IS_CHROMEOS_ASH) // Nothing further to query for protected profile. if (va_profile == VAProfileProtected) { profile_info->va_profile = va_profile; profile_info->va_entrypoint = entrypoint; return true; } #endif // Calls vaQuerySurfaceAttributes twice. The first time is to get the number // of attributes to prepare the space and the second time is to get all // attributes. unsigned int num_attribs; va_res = vaQuerySurfaceAttributes(va_display, va_config_id, nullptr, &num_attribs); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAQuerySurfaceAttributes, false); if (!num_attribs) return false; std::vector attrib_list( base::checked_cast(num_attribs)); va_res = vaQuerySurfaceAttributes(va_display, va_config_id, &attrib_list[0], &num_attribs); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAQuerySurfaceAttributes, false); profile_info->va_profile = va_profile; profile_info->va_entrypoint = entrypoint; profile_info->min_resolution = gfx::Size(); profile_info->max_resolution = gfx::Size(); for (const auto& attrib : attrib_list) { if (attrib.type == VASurfaceAttribMaxWidth) { profile_info->max_resolution.set_width( base::strict_cast(attrib.value.value.i)); } else if (attrib.type == VASurfaceAttribMaxHeight) { profile_info->max_resolution.set_height( base::strict_cast(attrib.value.value.i)); } else if (attrib.type == VASurfaceAttribMinWidth) { profile_info->min_resolution.set_width( base::strict_cast(attrib.value.value.i)); } else if (attrib.type == VASurfaceAttribMinHeight) { profile_info->min_resolution.set_height( base::strict_cast(attrib.value.value.i)); } else if (attrib.type == VASurfaceAttribPixelFormat) { // According to va.h, VASurfaceAttribPixelFormat is meaningful as input to // vaQuerySurfaceAttributes(). However, per the implementation of // i965_QuerySurfaceAttributes(), our usage here should enumerate all the // formats. profile_info->pixel_formats.push_back(attrib.value.value.i); } } if (profile_info->max_resolution.IsEmpty()) { LOG(ERROR) << "Empty codec maximum resolution"; return false; } if (va_profile != VAProfileJPEGBaseline) { // Set a reasonable minimum value for both encoding and decoding. profile_info->min_resolution.SetToMax(gfx::Size(16, 16)); const bool is_encoding = entrypoint == VAEntrypointEncSliceLP || entrypoint == VAEntrypointEncSlice; const bool is_hybrid_decoding = entrypoint == VAEntrypointVLD && IsUsingHybridDriverForDecoding(va_profile); // Using HW encoding for small resolutions is less efficient than using a SW // encoder. Similarly, using the intel-hybrid-driver for decoding is less // efficient than using a SW decoder. In both cases, increase // |min_resolution| to QVGA + 1 which is an experimental lower threshold. // This can be turned off with kVaapiVideoMinResolutionForPerformance for // testing. if ((is_encoding || is_hybrid_decoding) && base::FeatureList::IsEnabled(kVaapiVideoMinResolutionForPerformance)) { constexpr gfx::Size kMinVideoResolution(320 + 1, 240 + 1); profile_info->min_resolution.SetToMax(kMinVideoResolution); DVLOG(2) << "Setting the minimum supported resolution for " << vaProfileStr(va_profile) << (is_encoding ? " encoding" : " decoding") << " to " << profile_info->min_resolution.ToString(); } } // Create a new configuration to find the supported RT formats. We don't pass // required attributes here because we want the driver to tell us all the // supported RT formats. va_res = vaCreateConfig(va_display, va_profile, entrypoint, nullptr, 0, &va_config_id); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateConfig, false); base::ScopedClosureRunner vaconfig_no_attribs_destroyer(base::BindOnce( [](VADisplay display, VAConfigID id) { if (id != VA_INVALID_ID) { VAStatus va_res = vaDestroyConfig(display, id); if (va_res != VA_STATUS_SUCCESS) LOG(ERROR) << "vaDestroyConfig failed. VA error: " << vaErrorStr(va_res); } }, va_display, va_config_id)); profile_info->supported_internal_formats = {}; size_t max_num_config_attributes; if (!base::CheckedNumeric(vaMaxNumConfigAttributes(va_display)) .AssignIfValid(&max_num_config_attributes)) { LOG(ERROR) << "Can't get the maximum number of config attributes"; return false; } std::vector config_attributes(max_num_config_attributes); int num_config_attributes; va_res = vaQueryConfigAttributes(va_display, va_config_id, &va_profile, &entrypoint, config_attributes.data(), &num_config_attributes); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAQueryConfigAttributes, false); for (int i = 0; i < num_config_attributes; i++) { const VAConfigAttrib& attrib = config_attributes[i]; if (attrib.type != VAConfigAttribRTFormat) continue; if (attrib.value & VA_RT_FORMAT_YUV420) profile_info->supported_internal_formats.yuv420 = true; if (attrib.value & VA_RT_FORMAT_YUV420_10) profile_info->supported_internal_formats.yuv420_10 = true; if (attrib.value & VA_RT_FORMAT_YUV422) profile_info->supported_internal_formats.yuv422 = true; if (attrib.value & VA_RT_FORMAT_YUV444) profile_info->supported_internal_formats.yuv444 = true; break; } // Now work around some driver misreporting for JPEG decoding. if (va_profile == VAProfileJPEGBaseline && entrypoint == VAEntrypointVLD) { if (VADisplayState::Get()->implementation_type() == VAImplementation::kMesaGallium) { // TODO(andrescj): the VAAPI state tracker in mesa does not report // VA_RT_FORMAT_YUV422 as being supported for JPEG decoding. However, it // is happy to allocate YUYV surfaces // (https://gitlab.freedesktop.org/mesa/mesa/commit/5608f442). Remove this // workaround once b/128337341 is resolved. profile_info->supported_internal_formats.yuv422 = true; } } const bool is_any_profile_supported = profile_info->supported_internal_formats.yuv420 || profile_info->supported_internal_formats.yuv420_10 || profile_info->supported_internal_formats.yuv422 || profile_info->supported_internal_formats.yuv444; DLOG_IF(ERROR, !is_any_profile_supported) << "No cool internal formats supported"; return is_any_profile_supported; } void DestroyVAImage(VADisplay va_display, VAImage image) { if (image.image_id != VA_INVALID_ID) vaDestroyImage(va_display, image.image_id); } // This class encapsulates fetching the list of supported output image formats // from the VAAPI driver, in a singleton way. class VASupportedImageFormats { public: static const VASupportedImageFormats& Get(); VASupportedImageFormats(const VASupportedImageFormats&) = delete; VASupportedImageFormats& operator=(const VASupportedImageFormats&) = delete; bool IsImageFormatSupported(const VAImageFormat& va_format) const; const std::vector& GetSupportedImageFormats() const; private: friend class base::NoDestructor; VASupportedImageFormats(); ~VASupportedImageFormats() = default; // Initialize the list of supported image formats. bool InitSupportedImageFormats_Locked(const base::Lock* va_lock); // Pointer to VADisplayState's member |va_display_|. VADisplay va_display_; std::vector supported_formats_; const ReportErrorToUMACB report_error_to_uma_cb_; }; // static const VASupportedImageFormats& VASupportedImageFormats::Get() { static const base::NoDestructor image_formats; return *image_formats; } bool VASupportedImageFormats::IsImageFormatSupported( const VAImageFormat& va_image_format) const { auto it = std::find_if(supported_formats_.begin(), supported_formats_.end(), [&va_image_format](const VAImageFormat& format) { return format.fourcc == va_image_format.fourcc; }); return it != supported_formats_.end(); } const std::vector& VASupportedImageFormats::GetSupportedImageFormats() const { #if DCHECK_IS_ON() std::string formats_str; for (size_t i = 0; i < supported_formats_.size(); i++) { if (i > 0) formats_str += ", "; formats_str += FourccToString(supported_formats_[i].fourcc); } DVLOG(1) << "Supported image formats: " << formats_str; #endif return supported_formats_; } VASupportedImageFormats::VASupportedImageFormats() : report_error_to_uma_cb_(base::DoNothing()) { VADisplayState* display_state = VADisplayState::Get(); if (!display_state->Initialize()) return; // Pointer to VADisplayState's members |va_lock_| if using global VA lock or // the implementation is not thread safe. base::Lock* va_lock = display_state->va_lock(); if (!UseGlobalVaapiLock(display_state->implementation_type())) { va_lock = nullptr; } { base::AutoLockMaybe auto_lock(va_lock); va_display_ = display_state->va_display(); DCHECK(va_display_) << "VADisplayState hasn't been properly initialized"; if (!InitSupportedImageFormats_Locked(va_lock)) LOG(ERROR) << "Failed to get supported image formats"; } const VAStatus va_res = display_state->Deinitialize(); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVATerminate); } bool VASupportedImageFormats::InitSupportedImageFormats_Locked( const base::Lock* va_lock) { MAYBE_ASSERT_ACQUIRED(va_lock); // Query the driver for the max number of image formats and allocate space. const int max_image_formats = vaMaxNumImageFormats(va_display_); if (max_image_formats < 0) { LOG(ERROR) << "vaMaxNumImageFormats returned: " << max_image_formats; return false; } supported_formats_.resize(static_cast(max_image_formats)); // Query the driver for the list of supported image formats. int num_image_formats; const VAStatus va_res = vaQueryImageFormats( va_display_, supported_formats_.data(), &num_image_formats); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAQueryImageFormats, false); if (num_image_formats < 0 || num_image_formats > max_image_formats) { LOG(ERROR) << "vaQueryImageFormats returned: " << num_image_formats; supported_formats_.clear(); return false; } // Resize the list to the actual number of formats returned by the driver. supported_formats_.resize(static_cast(num_image_formats)); // Now work around some driver misreporting. if (VADisplayState::Get()->implementation_type() == VAImplementation::kMesaGallium) { // TODO(andrescj): considering that the VAAPI state tracker in mesa can // convert from NV12 to IYUV when doing vaGetImage(), it's reasonable to // assume that IYUV/I420 is supported. However, it's not currently being // reported. See https://gitlab.freedesktop.org/mesa/mesa/commit/b0a44f10. // Remove this workaround once b/128340287 is resolved. if (std::find_if(supported_formats_.cbegin(), supported_formats_.cend(), [](const VAImageFormat& format) { return format.fourcc == VA_FOURCC_I420; }) == supported_formats_.cend()) { VAImageFormat i420_format{}; i420_format.fourcc = VA_FOURCC_I420; supported_formats_.push_back(i420_format); } } return true; } bool IsLowPowerEncSupported(VAProfile va_profile) { constexpr VAProfile kSupportedLowPowerEncodeProfiles[] = { VAProfileH264ConstrainedBaseline, VAProfileH264Main, VAProfileH264High, VAProfileVP9Profile0, VAProfileVP9Profile2}; if (!base::Contains(kSupportedLowPowerEncodeProfiles, va_profile)) return false; if ((IsGen95Gpu() || IsGen9Gpu()) && !base::FeatureList::IsEnabled(kVaapiLowPowerEncoderGen9x)) { return false; } if (VASupportedProfiles::Get().IsProfileSupported( VaapiWrapper::kEncodeConstantBitrate, va_profile, VAEntrypointEncSliceLP)) { return true; } return false; } } // namespace NativePixmapAndSizeInfo::NativePixmapAndSizeInfo() = default; NativePixmapAndSizeInfo::~NativePixmapAndSizeInfo() = default; // static VAImplementation VaapiWrapper::GetImplementationType() { return VADisplayState::Get()->implementation_type(); } // static scoped_refptr VaapiWrapper::Create( CodecMode mode, VAProfile va_profile, EncryptionScheme encryption_scheme, const ReportErrorToUMACB& report_error_to_uma_cb, bool enforce_sequence_affinity) { if (!VASupportedProfiles::Get().IsProfileSupported(mode, va_profile)) { DVLOG(1) << "Unsupported va_profile: " << vaProfileStr(va_profile); return nullptr; } #if BUILDFLAG(IS_CHROMEOS_ASH) // In protected decode |mode| we need to ensure that |va_profile| is supported // (which we verified above) and that VAProfileProtected is supported, which // we check here. if (mode == kDecodeProtected && !VASupportedProfiles::Get().IsProfileSupported(mode, VAProfileProtected)) { LOG(ERROR) << "Protected content profile not supported"; return nullptr; } #endif scoped_refptr vaapi_wrapper( new VaapiWrapper(mode, enforce_sequence_affinity)); if (vaapi_wrapper->VaInitialize(report_error_to_uma_cb)) { if (vaapi_wrapper->Initialize(va_profile, encryption_scheme)) return vaapi_wrapper; } LOG(ERROR) << "Failed to create VaapiWrapper for va_profile: " << vaProfileStr(va_profile); return nullptr; } // static scoped_refptr VaapiWrapper::CreateForVideoCodec( CodecMode mode, VideoCodecProfile profile, EncryptionScheme encryption_scheme, const ReportErrorToUMACB& report_error_to_uma_cb, bool enforce_sequence_affinity) { const VAProfile va_profile = ProfileToVAProfile(profile, mode); return Create(mode, va_profile, encryption_scheme, report_error_to_uma_cb, enforce_sequence_affinity); } // static std::vector VaapiWrapper::GetSupportedScalabilityModes( VideoCodecProfile media_profile, VAProfile va_profile) { std::vector scalability_modes; #if BUILDFLAG(IS_CHROMEOS_ASH) if (media_profile == VP9PROFILE_PROFILE0) { scalability_modes.push_back(SVCScalabilityMode::kL1T2); scalability_modes.push_back(SVCScalabilityMode::kL1T3); if (base::FeatureList::IsEnabled(kVaapiVp9kSVCHWEncoding) && GetDefaultVaEntryPoint( VaapiWrapper::kEncodeConstantQuantizationParameter, va_profile) == VAEntrypointEncSliceLP) { scalability_modes.push_back(SVCScalabilityMode::kL2T2Key); scalability_modes.push_back(SVCScalabilityMode::kL2T3Key); scalability_modes.push_back(SVCScalabilityMode::kL3T2Key); scalability_modes.push_back(SVCScalabilityMode::kL3T3Key); } } if (media_profile >= H264PROFILE_MIN && media_profile <= H264PROFILE_MAX) { // TODO(b/199487660): Enable H.264 temporal layer encoding on AMD once their // drivers support them. VAImplementation implementation = VaapiWrapper::GetImplementationType(); if (base::FeatureList::IsEnabled(kVaapiH264TemporalLayerHWEncoding) && (implementation == VAImplementation::kIntelI965 || implementation == VAImplementation::kIntelIHD)) { scalability_modes.push_back(SVCScalabilityMode::kL1T2); scalability_modes.push_back(SVCScalabilityMode::kL1T3); } } #endif return scalability_modes; } // static VideoEncodeAccelerator::SupportedProfiles VaapiWrapper::GetSupportedEncodeProfiles() { VideoEncodeAccelerator::SupportedProfiles profiles; for (const auto& media_to_va_profile_map_entry : GetProfileCodecMap()) { const VideoCodecProfile media_profile = media_to_va_profile_map_entry.first; const VAProfile va_profile = media_to_va_profile_map_entry.second; DCHECK(va_profile != VAProfileNone); const VASupportedProfiles::ProfileInfo* profile_info = VASupportedProfiles::Get().IsProfileSupported(kEncodeConstantBitrate, va_profile); if (!profile_info) continue; VideoEncodeAccelerator::SupportedProfile profile; profile.profile = media_profile; profile.min_resolution = profile_info->min_resolution; profile.max_resolution = profile_info->max_resolution; // Maximum framerate of encoded profile. This value is an arbitrary // limit and not taken from HW documentation. constexpr int kMaxEncoderFramerate = 30; profile.max_framerate_numerator = kMaxEncoderFramerate; profile.max_framerate_denominator = 1; profile.scalability_modes = GetSupportedScalabilityModes(media_profile, va_profile); profiles.push_back(profile); } return profiles; } // static VideoDecodeAccelerator::SupportedProfiles VaapiWrapper::GetSupportedDecodeProfiles() { VideoDecodeAccelerator::SupportedProfiles profiles; for (const auto& media_to_va_profile_map_entry : GetProfileCodecMap()) { const VideoCodecProfile media_profile = media_to_va_profile_map_entry.first; const VAProfile va_profile = media_to_va_profile_map_entry.second; DCHECK(va_profile != VAProfileNone); const VASupportedProfiles::ProfileInfo* profile_info = VASupportedProfiles::Get().IsProfileSupported(kDecode, va_profile); if (!profile_info) continue; VideoDecodeAccelerator::SupportedProfile profile; profile.profile = media_profile; profile.max_resolution = profile_info->max_resolution; profile.min_resolution = profile_info->min_resolution; profiles.push_back(profile); } return profiles; } // static bool VaapiWrapper::IsDecodeSupported(VAProfile va_profile) { return VASupportedProfiles::Get().IsProfileSupported(kDecode, va_profile); } // static VaapiWrapper::InternalFormats VaapiWrapper::GetDecodeSupportedInternalFormats( VAProfile va_profile) { const VASupportedProfiles::ProfileInfo* profile_info = VASupportedProfiles::Get().IsProfileSupported(kDecode, va_profile); if (!profile_info) return InternalFormats{}; return profile_info->supported_internal_formats; } // static bool VaapiWrapper::IsDecodingSupportedForInternalFormat( VAProfile va_profile, unsigned int rt_format) { static const VaapiWrapper::InternalFormats supported_internal_formats( VaapiWrapper::GetDecodeSupportedInternalFormats(va_profile)); switch (rt_format) { case VA_RT_FORMAT_YUV420: return supported_internal_formats.yuv420; case VA_RT_FORMAT_YUV420_10: return supported_internal_formats.yuv420_10; case VA_RT_FORMAT_YUV422: return supported_internal_formats.yuv422; case VA_RT_FORMAT_YUV444: return supported_internal_formats.yuv444; } return false; } // static bool VaapiWrapper::GetDecodeMinResolution(VAProfile va_profile, gfx::Size* min_size) { const VASupportedProfiles::ProfileInfo* profile_info = VASupportedProfiles::Get().IsProfileSupported(kDecode, va_profile); if (!profile_info) return false; *min_size = gfx::Size(std::max(1, profile_info->min_resolution.width()), std::max(1, profile_info->min_resolution.height())); return true; } // static bool VaapiWrapper::GetDecodeMaxResolution(VAProfile va_profile, gfx::Size* max_size) { const VASupportedProfiles::ProfileInfo* profile_info = VASupportedProfiles::Get().IsProfileSupported(kDecode, va_profile); if (!profile_info) return false; *max_size = profile_info->max_resolution; return true; } // static bool VaapiWrapper::GetJpegDecodeSuitableImageFourCC(unsigned int rt_format, uint32_t preferred_fourcc, uint32_t* suitable_fourcc) { if (!IsDecodingSupportedForInternalFormat(VAProfileJPEGBaseline, rt_format)) return false; // Work around some driver-specific conversion issues. If you add a workaround // here, please update the VaapiJpegDecoderTest.MinimalImageFormatSupport // test. DCHECK_NE(VAImplementation::kInvalid, GetImplementationType()); if (GetImplementationType() == VAImplementation::kMesaGallium) { // The VAAPI mesa state tracker only supports conversion from NV12 to YV12 // and IYUV (synonym of I420). if (rt_format == VA_RT_FORMAT_YUV420) { if (preferred_fourcc != VA_FOURCC_I420 && preferred_fourcc != VA_FOURCC_YV12) { preferred_fourcc = VA_FOURCC_NV12; } } else if (rt_format == VA_RT_FORMAT_YUV422) { preferred_fourcc = VA_FOURCC('Y', 'U', 'Y', 'V'); } else { // Out of the three internal formats we care about (4:2:0, 4:2:2, and // 4:4:4), this driver should only support the first two. Since we check // for supported internal formats at the beginning of this function, we // shouldn't get here. NOTREACHED(); return false; } } else if (GetImplementationType() == VAImplementation::kIntelI965) { // Workaround deduced from observations in samus and nocturne: we found that // // - For a 4:2:2 image, the internal format is 422H. // - For a 4:2:0 image, the internal format is IMC3. // - For a 4:4:4 image, the internal format is 444P. // // For these internal formats and an image format of either 422H or P010, an // intermediate NV12 surface is allocated. Then, a conversion is made from // {422H, IMC3, 444P} -> NV12 -> {422H, P010}. Unfortunately, the // NV12 -> {422H, P010} conversion is unimplemented in // i965_image_pl2_processing(). So, when |preferred_fourcc| is either 422H // or P010, we can just fallback to I420. // if (preferred_fourcc == VA_FOURCC_422H || preferred_fourcc == VA_FOURCC_P010) { preferred_fourcc = VA_FOURCC_I420; } } else if (GetImplementationType() == VAImplementation::kIntelIHD) { // (b/159896972): iHD v20.1.1 cannot create Y216 and Y416 images from a // decoded JPEG on gen 12. It is also failing to support Y800 format. if (preferred_fourcc == VA_FOURCC_Y216 || preferred_fourcc == VA_FOURCC_Y416 || preferred_fourcc == VA_FOURCC_Y800) { preferred_fourcc = VA_FOURCC_I420; } } if (!VASupportedImageFormats::Get().IsImageFormatSupported( VAImageFormat{.fourcc = preferred_fourcc})) { preferred_fourcc = VA_FOURCC_I420; } // After workarounds, assume the conversion is supported. *suitable_fourcc = preferred_fourcc; return true; } // static bool VaapiWrapper::IsVppProfileSupported() { return VASupportedProfiles::Get().IsProfileSupported(kVideoProcess, VAProfileNone); } // static bool VaapiWrapper::IsVppResolutionAllowed(const gfx::Size& size) { const VASupportedProfiles::ProfileInfo* profile_info = VASupportedProfiles::Get().IsProfileSupported(kVideoProcess, VAProfileNone); if (!profile_info) return false; return size.width() >= profile_info->min_resolution.width() && size.width() <= profile_info->max_resolution.width() && size.height() >= profile_info->min_resolution.height() && size.height() <= profile_info->max_resolution.height(); } // static bool VaapiWrapper::IsVppFormatSupported(uint32_t va_fourcc) { const VASupportedProfiles::ProfileInfo* profile_info = VASupportedProfiles::Get().IsProfileSupported(kVideoProcess, VAProfileNone); if (!profile_info) return false; return base::Contains(profile_info->pixel_formats, va_fourcc); } // static std::vector VaapiWrapper::GetVppSupportedFormats() { const VASupportedProfiles::ProfileInfo* profile_info = VASupportedProfiles::Get().IsProfileSupported(kVideoProcess, VAProfileNone); if (!profile_info) return {}; std::vector supported_fourccs; for (uint32_t pixel_format : profile_info->pixel_formats) { auto fourcc = Fourcc::FromVAFourCC(pixel_format); if (!fourcc) continue; supported_fourccs.push_back(*fourcc); } return supported_fourccs; } // static bool VaapiWrapper::IsVppSupportedForJpegDecodedSurfaceToFourCC( unsigned int rt_format, uint32_t fourcc) { if (!IsDecodingSupportedForInternalFormat(VAProfileJPEGBaseline, rt_format)) return false; // Workaround: for Mesa VAAPI driver, VPP only supports internal surface // format for 4:2:0 JPEG image. DCHECK_NE(VAImplementation::kInvalid, GetImplementationType()); if (GetImplementationType() == VAImplementation::kMesaGallium && rt_format != VA_RT_FORMAT_YUV420) { return false; } return IsVppFormatSupported(fourcc); } // static bool VaapiWrapper::IsJpegEncodeSupported() { return VASupportedProfiles::Get().IsProfileSupported(kEncodeConstantBitrate, VAProfileJPEGBaseline); } // static bool VaapiWrapper::IsImageFormatSupported(const VAImageFormat& format) { return VASupportedImageFormats::Get().IsImageFormatSupported(format); } // static const std::vector& VaapiWrapper::GetSupportedImageFormatsForTesting() { return VASupportedImageFormats::Get().GetSupportedImageFormats(); } // static std::map> VaapiWrapper::GetSupportedConfigurationsForCodecModeForTesting(CodecMode mode) { std::map> configurations; for (const auto& supported_profile : VASupportedProfiles::Get().supported_profiles_[mode]) { configurations[supported_profile.va_profile].push_back( supported_profile.va_entrypoint); } return configurations; } // static VAEntrypoint VaapiWrapper::GetDefaultVaEntryPoint(CodecMode mode, VAProfile profile) { switch (mode) { case VaapiWrapper::kDecode: return VAEntrypointVLD; #if BUILDFLAG(IS_CHROMEOS_ASH) case VaapiWrapper::kDecodeProtected: if (profile == VAProfileProtected) return VAEntrypointProtectedContent; return VAEntrypointVLD; #endif case VaapiWrapper::kEncodeConstantBitrate: case VaapiWrapper::kEncodeConstantQuantizationParameter: if (profile == VAProfileJPEGBaseline) return VAEntrypointEncPicture; DCHECK(IsModeEncoding(mode)); if (IsLowPowerEncSupported(profile)) return VAEntrypointEncSliceLP; return VAEntrypointEncSlice; case VaapiWrapper::kVideoProcess: return VAEntrypointVideoProc; case VaapiWrapper::kCodecModeMax: NOTREACHED(); return VAEntrypointVLD; } } // static uint32_t VaapiWrapper::BufferFormatToVARTFormat(gfx::BufferFormat fmt) { switch (fmt) { case gfx::BufferFormat::BGRX_8888: case gfx::BufferFormat::BGRA_8888: case gfx::BufferFormat::RGBX_8888: case gfx::BufferFormat::RGBA_8888: return VA_RT_FORMAT_RGB32; case gfx::BufferFormat::YVU_420: case gfx::BufferFormat::YUV_420_BIPLANAR: return VA_RT_FORMAT_YUV420; case gfx::BufferFormat::P010: return VA_RT_FORMAT_YUV420_10BPP; default: NOTREACHED() << gfx::BufferFormatToString(fmt); return 0; } } bool VaapiWrapper::CreateContextAndSurfaces( unsigned int va_format, const gfx::Size& size, const std::vector& surface_usage_hints, size_t num_surfaces, std::vector* va_surfaces) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); DVLOG(2) << "Creating " << num_surfaces << " surfaces"; DCHECK(va_surfaces->empty()); if (va_context_id_ != VA_INVALID_ID) { LOG(ERROR) << "The current context should be destroyed before creating a new one"; return false; } if (!CreateSurfaces(va_format, size, surface_usage_hints, num_surfaces, va_surfaces)) { return false; } const bool success = CreateContext(size); if (!success) DestroyContextAndSurfaces(*va_surfaces); return success; } std::vector> VaapiWrapper::CreateContextAndScopedVASurfaces( unsigned int va_format, const gfx::Size& size, const std::vector& usage_hints, size_t num_surfaces, const absl::optional& visible_size) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); if (va_context_id_ != VA_INVALID_ID) { LOG(ERROR) << "The current context should be destroyed before creating a " "new one"; return {}; } std::vector> scoped_va_surfaces = CreateScopedVASurfaces(va_format, size, usage_hints, num_surfaces, visible_size, /*va_fourcc=*/absl::nullopt); if (scoped_va_surfaces.empty()) return {}; if (CreateContext(size)) return scoped_va_surfaces; DestroyContext(); return {}; } bool VaapiWrapper::CreateProtectedSession( EncryptionScheme encryption, const std::vector& hw_config, std::vector* hw_identifier_out) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); #if BUILDFLAG(IS_CHROMEOS_ASH) DCHECK_EQ(va_protected_config_id_, VA_INVALID_ID); DCHECK_EQ(va_protected_session_id_, VA_INVALID_ID); DCHECK(hw_identifier_out); if (mode_ != kDecodeProtected) { LOG(ERROR) << "Cannot attached protected context if not in protected mode"; return false; } if (encryption == EncryptionScheme::kUnencrypted) { LOG(ERROR) << "Must specify encryption scheme for protected mode"; return false; } const VAProfile va_profile = VAProfileProtected; const VAEntrypoint entrypoint = GetDefaultVaEntryPoint(mode_, va_profile); { base::AutoLockMaybe auto_lock(va_lock_); std::vector required_attribs; if (!GetRequiredAttribs(va_lock_, va_display_, mode_, va_profile, entrypoint, &required_attribs)) { LOG(ERROR) << "Failed getting required attributes for protected mode"; return false; } DCHECK(!required_attribs.empty()); // We need to adjust the attribute for encryption scheme. for (auto& attrib : required_attribs) { if (attrib.type == VAConfigAttribProtectedContentCipherMode) { attrib.value = (encryption == EncryptionScheme::kCbcs) ? VA_PC_CIPHER_MODE_CBC : VA_PC_CIPHER_MODE_CTR; } } VAStatus va_res = vaCreateConfig( va_display_, va_profile, entrypoint, &required_attribs[0], required_attribs.size(), &va_protected_config_id_); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateConfig, false); va_res = vaCreateProtectedSession(va_display_, va_protected_config_id_, &va_protected_session_id_); DCHECK(va_res == VA_STATUS_SUCCESS || va_protected_session_id_ == VA_INVALID_ID); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateProtectedSession, false); } // We have to hold the VABuffer outside of the lock because its destructor // will acquire the lock when it goes out of scope. We also must do this after // we create the protected session. VAProtectedSessionExecuteBuffer hw_update_buf; std::unique_ptr hw_update = CreateVABuffer( VAProtectedSessionExecuteBufferType, sizeof(hw_update_buf)); { base::AutoLockMaybe auto_lock(va_lock_); constexpr size_t kHwIdentifierMaxSize = 64; memset(&hw_update_buf, 0, sizeof(hw_update_buf)); hw_update_buf.function_id = VA_TEE_EXEC_TEE_FUNCID_HW_UPDATE; hw_update_buf.input.data_size = hw_config.size(); hw_update_buf.input.data = static_cast(const_cast(hw_config.data())); hw_update_buf.output.max_data_size = kHwIdentifierMaxSize; hw_identifier_out->resize(kHwIdentifierMaxSize); hw_update_buf.output.data = hw_identifier_out->data(); if (!MapAndCopy_Locked( hw_update->id(), {hw_update->type(), hw_update->size(), &hw_update_buf})) { LOG(ERROR) << "Failed mapping Execute buf"; return false; } VAStatus va_res = vaProtectedSessionExecute( va_display_, va_protected_session_id_, hw_update->id()); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAProtectedSessionExecute, false); ScopedVABufferMapping mapping(va_lock_, va_display_, hw_update->id()); if (!mapping.IsValid()) { LOG(ERROR) << "Failed mapping returned Execute buf"; return false; } auto* hw_update_buf_out = reinterpret_cast(mapping.data()); if (!hw_update_buf_out->output.data_size) { LOG(ERROR) << "Received empty HW identifier"; return false; } hw_identifier_out->resize(hw_update_buf_out->output.data_size); memcpy(hw_identifier_out->data(), hw_update_buf_out->output.data, hw_update_buf_out->output.data_size); // If the decoding context is created, attach the protected session. // Otherwise this is done in CreateContext when the decoding context is // created. return MaybeAttachProtectedSession_Locked(); } #else NOTIMPLEMENTED() << "Protected content mode not supported"; return false; #endif } bool VaapiWrapper::IsProtectedSessionDead() { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); #if BUILDFLAG(IS_CHROMEOS_ASH) return IsProtectedSessionDead(va_protected_session_id_); #else return false; #endif } #if BUILDFLAG(IS_CHROMEOS_ASH) bool VaapiWrapper::IsProtectedSessionDead( VAProtectedSessionID va_protected_session_id) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); if (va_protected_session_id == VA_INVALID_ID) return false; uint8_t alive; VAProtectedSessionExecuteBuffer tee_exec_buf = {}; tee_exec_buf.function_id = VA_TEE_EXEC_TEE_FUNCID_IS_SESSION_ALIVE; tee_exec_buf.input.data_size = 0; tee_exec_buf.input.data = nullptr; tee_exec_buf.output.data_size = sizeof(alive); tee_exec_buf.output.data = &alive; base::AutoLockMaybe auto_lock(va_lock_); VABufferID buf_id; VAStatus va_res = vaCreateBuffer( va_display_, va_protected_session_id, VAProtectedSessionExecuteBufferType, sizeof(tee_exec_buf), 1, &tee_exec_buf, &buf_id); // Failure here is valid if the protected session has been closed. if (va_res != VA_STATUS_SUCCESS) return true; va_res = vaProtectedSessionExecute(va_display_, va_protected_session_id, buf_id); vaDestroyBuffer(va_display_, buf_id); if (va_res != VA_STATUS_SUCCESS) return true; return !alive; } #endif #if BUILDFLAG(IS_CHROMEOS_ASH) VAProtectedSessionID VaapiWrapper::GetProtectedSessionID() const { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); return va_protected_session_id_; } #endif void VaapiWrapper::DestroyProtectedSession() { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); #if BUILDFLAG(IS_CHROMEOS_ASH) if (va_protected_session_id_ == VA_INVALID_ID) return; base::AutoLockMaybe auto_lock(va_lock_); VAStatus va_res = vaDestroyProtectedSession(va_display_, va_protected_session_id_); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVADestroyProtectedSession); va_res = vaDestroyConfig(va_display_, va_protected_config_id_); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVADestroyConfig); va_protected_session_id_ = VA_INVALID_ID; va_protected_config_id_ = VA_INVALID_ID; #endif } void VaapiWrapper::DestroyContextAndSurfaces( std::vector va_surfaces) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); DestroyContext(); DestroySurfaces(va_surfaces); } bool VaapiWrapper::CreateContext(const gfx::Size& size) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); DVLOG(2) << "Creating context"; base::AutoLockMaybe auto_lock(va_lock_); // vaCreateContext() doesn't really need an array of VASurfaceIDs (see // https://lists.01.org/pipermail/intel-vaapi-media/2017-July/000052.html and // https://github.com/intel/libva/issues/251); pass a dummy list of valid // (non-null) IDs until the signature gets updated. constexpr VASurfaceID* empty_va_surfaces_ids_pointer = nullptr; constexpr size_t empty_va_surfaces_ids_size = 0u; // No flag must be set and passing picture size is irrelevant in the case of // vpp, just passing 0x0. const int flag = mode_ != kVideoProcess ? VA_PROGRESSIVE : 0x0; const gfx::Size picture_size = mode_ != kVideoProcess ? size : gfx::Size(); if (base::FeatureList::IsEnabled(kVaapiEnforceVideoMinMaxResolution) && mode_ != kVideoProcess) { const VASupportedProfiles::ProfileInfo* profile_info = VASupportedProfiles::Get().IsProfileSupported(mode_, va_profile_, va_entrypoint_); DCHECK(profile_info); const bool is_picture_within_bounds = gfx::Rect(picture_size) .Contains(gfx::Rect(profile_info->min_resolution)) && gfx::Rect(profile_info->max_resolution) .Contains(gfx::Rect(picture_size)); if (!is_picture_within_bounds) { VLOG(2) << "Requested resolution=" << picture_size.ToString() << " is not within bounds [" << profile_info->min_resolution.ToString() << ", " << profile_info->max_resolution.ToString() << "]"; return false; } } VAStatus va_res = vaCreateContext( va_display_, va_config_id_, picture_size.width(), picture_size.height(), flag, empty_va_surfaces_ids_pointer, empty_va_surfaces_ids_size, &va_context_id_); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVACreateContext); if (va_res != VA_STATUS_SUCCESS) return false; // TODO(b/200779101): Remove low resolution i965 condition. This was // added to avoid a duplicated frame specific to quality 7 at ~400kbps. if (IsModeEncoding(mode_) && IsLowPowerIntelProcessor() && !(GetImplementationType() == VAImplementation::kIntelI965 && picture_size.GetArea() <= gfx::Size(320, 240).GetArea())) { MaybeSetLowQualityEncoding_Locked(); } // If we have a protected session already, attach it to this new context. return MaybeAttachProtectedSession_Locked(); } scoped_refptr VaapiWrapper::CreateVASurfaceForPixmap( scoped_refptr pixmap, bool protected_content) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); const gfx::BufferFormat buffer_format = pixmap->GetBufferFormat(); // Create a VASurface for a NativePixmap by importing the underlying dmabufs. const gfx::Size size = pixmap->GetBufferSize(); VASurfaceAttribExternalBuffers va_attrib_extbuf{}; va_attrib_extbuf.pixel_format = BufferFormatToVAFourCC(buffer_format); va_attrib_extbuf.width = size.width(); va_attrib_extbuf.height = size.height(); const size_t num_planes = pixmap->GetNumberOfPlanes(); for (size_t i = 0; i < num_planes; ++i) { va_attrib_extbuf.pitches[i] = pixmap->GetDmaBufPitch(i); va_attrib_extbuf.offsets[i] = pixmap->GetDmaBufOffset(i); DVLOG(4) << "plane " << i << ": pitch: " << va_attrib_extbuf.pitches[i] << " offset: " << va_attrib_extbuf.offsets[i]; } va_attrib_extbuf.num_planes = num_planes; const int dma_buf_fd = pixmap->GetDmaBufFd(0); if (dma_buf_fd < 0) { LOG(ERROR) << "Failed to get dmabuf from an Ozone NativePixmap"; return nullptr; } const off_t data_size = lseek(dma_buf_fd, /*offset=*/0, SEEK_END); if (data_size == static_cast(-1)) { PLOG(ERROR) << "Failed to get the size of the dma-buf"; return nullptr; } if (lseek(dma_buf_fd, /*offset=*/0, SEEK_SET) == static_cast(-1)) { PLOG(ERROR) << "Failed to reset the file offset of the dma-buf"; return nullptr; } // If the data size doesn't fit in a uint32_t, we probably have bigger // problems. va_attrib_extbuf.data_size = base::checked_cast(data_size); // We only have to pass the first file descriptor to a driver. A VA-API driver // shall create a VASurface from the single fd correctly. uintptr_t fd = base::checked_cast(dma_buf_fd); va_attrib_extbuf.buffers = &fd; va_attrib_extbuf.num_buffers = 1u; DCHECK_EQ(va_attrib_extbuf.flags, 0u); DCHECK_EQ(va_attrib_extbuf.private_data, nullptr); uint32_t va_format = BufferFormatToVARTFormat(buffer_format); if (protected_content) { if (GetImplementationType() == VAImplementation::kMesaGallium) va_format |= VA_RT_FORMAT_PROTECTED; else va_attrib_extbuf.flags = VA_SURFACE_EXTBUF_DESC_PROTECTED; } std::vector va_attribs(2); va_attribs[0].type = VASurfaceAttribMemoryType; va_attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE; va_attribs[0].value.type = VAGenericValueTypeInteger; va_attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME; va_attribs[1].type = VASurfaceAttribExternalBufferDescriptor; va_attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE; va_attribs[1].value.type = VAGenericValueTypePointer; va_attribs[1].value.value.p = &va_attrib_extbuf; VASurfaceID va_surface_id = VA_INVALID_ID; { base::AutoLockMaybe auto_lock(va_lock_); VAStatus va_res = vaCreateSurfaces( va_display_, va_format, base::checked_cast(size.width()), base::checked_cast(size.height()), &va_surface_id, 1, &va_attribs[0], va_attribs.size()); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateSurfaces_Importing, nullptr); } DVLOG(2) << __func__ << " " << va_surface_id; // VASurface shares an ownership of the buffer referred by the passed file // descriptor. We can release |pixmap| here. return new VASurface(va_surface_id, size, va_format, base::BindOnce(&VaapiWrapper::DestroySurface, this)); } scoped_refptr VaapiWrapper::CreateVASurfaceForUserPtr( const gfx::Size& size, uintptr_t* buffers, size_t buffer_size) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); VASurfaceAttribExternalBuffers va_attrib_extbuf{}; va_attrib_extbuf.num_planes = 3; va_attrib_extbuf.buffers = buffers; va_attrib_extbuf.data_size = base::checked_cast(buffer_size); va_attrib_extbuf.num_buffers = 1u; va_attrib_extbuf.width = base::checked_cast(size.width()); va_attrib_extbuf.height = base::checked_cast(size.height()); va_attrib_extbuf.offsets[0] = 0; va_attrib_extbuf.offsets[1] = size.GetCheckedArea().ValueOrDie(); va_attrib_extbuf.offsets[2] = (size.GetCheckedArea() * 2).ValueOrDie(); std::fill(va_attrib_extbuf.pitches, va_attrib_extbuf.pitches + 3, base::checked_cast(size.width())); va_attrib_extbuf.pixel_format = VA_FOURCC_RGBP; std::vector va_attribs(2); va_attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE; va_attribs[0].type = VASurfaceAttribMemoryType; va_attribs[0].value.type = VAGenericValueTypeInteger; va_attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_USER_PTR; va_attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE; va_attribs[1].type = VASurfaceAttribExternalBufferDescriptor; va_attribs[1].value.type = VAGenericValueTypePointer; va_attribs[1].value.value.p = &va_attrib_extbuf; VASurfaceID va_surface_id = VA_INVALID_ID; const unsigned int va_format = VA_RT_FORMAT_RGBP; { base::AutoLockMaybe auto_lock(va_lock_); VAStatus va_res = vaCreateSurfaces( va_display_, va_format, base::checked_cast(size.width()), base::checked_cast(size.height()), &va_surface_id, 1, &va_attribs[0], va_attribs.size()); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateSurfaces_Importing, nullptr); } DVLOG(2) << __func__ << " " << va_surface_id; return new VASurface(va_surface_id, size, va_format, base::BindOnce(&VaapiWrapper::DestroySurface, this)); } scoped_refptr VaapiWrapper::CreateVASurfaceWithUsageHints( unsigned int va_rt_format, const gfx::Size& size, const std::vector& usage_hints) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); std::vector surfaces; if (!CreateSurfaces(va_rt_format, size, usage_hints, 1, &surfaces)) return nullptr; return new VASurface(surfaces[0], size, va_rt_format, base::BindOnce(&VaapiWrapper::DestroySurface, this)); } std::unique_ptr VaapiWrapper::ExportVASurfaceAsNativePixmapDmaBufUnwrapped( VASurfaceID va_surface_id, const gfx::Size& va_surface_size) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); DCHECK_NE(va_surface_id, VA_INVALID_SURFACE); DCHECK(!va_surface_size.IsEmpty()); if (GetImplementationType() == VAImplementation::kNVIDIAVDPAU) { LOG(ERROR) << "Disabled due to potential breakage."; return nullptr; } VADRMPRIMESurfaceDescriptor descriptor; { base::AutoLockMaybe auto_lock(va_lock_); VAStatus va_res = vaSyncSurface(va_display_, va_surface_id); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVASyncSurface, nullptr); va_res = vaExportSurfaceHandle( va_display_, va_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS, &descriptor); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAExportSurfaceHandle, nullptr); } // We only support one bo containing all the planes. The fd should be owned by // us: per va/va.h, "the exported handles are owned by the caller." // // TODO(crbug.com/974438): support multiple buffer objects so that this can // work in AMD. if (descriptor.num_objects != 1u) { DVLOG(1) << "Only surface descriptors with one bo are supported"; NOTREACHED(); return nullptr; } base::ScopedFD bo_fd(descriptor.objects[0].fd); const uint64_t bo_modifier = descriptor.objects[0].drm_format_modifier; // Translate the pixel format to a gfx::BufferFormat. gfx::BufferFormat buffer_format; switch (descriptor.fourcc) { case VA_FOURCC_IMC3: // IMC3 is like I420 but all the planes have the same stride. This is used // for decoding 4:2:0 JPEGs in the Intel i965 driver. We don't currently // have a gfx::BufferFormat for YUV420. Instead, we reuse YVU_420 and // later swap the U and V planes. // // TODO(andrescj): revisit this once crrev.com/c/1573718 lands. buffer_format = gfx::BufferFormat::YVU_420; break; case VA_FOURCC_NV12: buffer_format = gfx::BufferFormat::YUV_420_BIPLANAR; break; default: LOG(ERROR) << "Cannot export a surface with FOURCC " << FourccToString(descriptor.fourcc); return nullptr; } gfx::NativePixmapHandle handle{}; handle.modifier = bo_modifier; for (uint32_t layer = 0; layer < descriptor.num_layers; layer++) { // According to va/va_drmcommon.h, if VA_EXPORT_SURFACE_SEPARATE_LAYERS is // specified, each layer should contain one plane. DCHECK_EQ(1u, descriptor.layers[layer].num_planes); // Strictly speaking, we only have to dup() the fd for the planes after the // first one since we already own the first one, but we dup() regardless for // simplicity: |bo_fd| will be closed at the end of this method anyway. base::ScopedFD plane_fd(HANDLE_EINTR(dup(bo_fd.get()))); PCHECK(plane_fd.is_valid()); constexpr uint64_t kZeroSizeToPreventMapping = 0u; handle.planes.emplace_back( base::checked_cast(descriptor.layers[layer].pitch[0]), base::checked_cast(descriptor.layers[layer].offset[0]), kZeroSizeToPreventMapping, base::ScopedFD(HANDLE_EINTR(dup(bo_fd.get())))); } if (descriptor.fourcc == VA_FOURCC_IMC3) { // Recall that for VA_FOURCC_IMC3, we will return a format of // gfx::BufferFormat::YVU_420, so we need to swap the U and V planes to keep // the semantics. DCHECK_EQ(3u, handle.planes.size()); std::swap(handle.planes[1], handle.planes[2]); } auto exported_pixmap = std::make_unique(); exported_pixmap->va_surface_resolution = gfx::Size(base::checked_cast(descriptor.width), base::checked_cast(descriptor.height)); exported_pixmap->byte_size = base::strict_cast(descriptor.objects[0].size); if (!gfx::Rect(exported_pixmap->va_surface_resolution) .Contains(gfx::Rect(va_surface_size))) { LOG(ERROR) << "A " << va_surface_size.ToString() << " surface cannot be contained by a " << exported_pixmap->va_surface_resolution.ToString() << " buffer"; return nullptr; } exported_pixmap->pixmap = base::MakeRefCounted( va_surface_size, buffer_format, std::move(handle)); return exported_pixmap; } std::unique_ptr VaapiWrapper::ExportVASurfaceAsNativePixmapDmaBuf(const VASurface& va_surface) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); if (va_surface.id() == VA_INVALID_SURFACE || va_surface.size().IsEmpty() || va_surface.format() == kInvalidVaRtFormat) { LOG(ERROR) << "Cannot export an invalid surface"; return nullptr; } return ExportVASurfaceAsNativePixmapDmaBufUnwrapped(va_surface.id(), va_surface.size()); } std::unique_ptr VaapiWrapper::ExportVASurfaceAsNativePixmapDmaBuf( const ScopedVASurface& scoped_va_surface) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); if (!scoped_va_surface.IsValid()) { LOG(ERROR) << "Cannot export an invalid surface"; return nullptr; } return ExportVASurfaceAsNativePixmapDmaBufUnwrapped(scoped_va_surface.id(), scoped_va_surface.size()); } bool VaapiWrapper::SyncSurface(VASurfaceID va_surface_id) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); DCHECK_NE(va_surface_id, VA_INVALID_ID); base::AutoLockMaybe auto_lock(va_lock_); VAStatus va_res = vaSyncSurface(va_display_, va_surface_id); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVASyncSurface, false); return true; } bool VaapiWrapper::SubmitBuffer(VABufferType va_buffer_type, size_t size, const void* data) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); TRACE_EVENT0("media,gpu", "VaapiWrapper::SubmitBuffer"); base::AutoLockMaybe auto_lock(va_lock_); return SubmitBuffer_Locked({va_buffer_type, size, data}); } bool VaapiWrapper::SubmitBuffers( const std::vector& va_buffers) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); TRACE_EVENT0("media,gpu", "VaapiWrapper::SubmitBuffers"); base::AutoLockMaybe auto_lock(va_lock_); for (const VABufferDescriptor& va_buffer : va_buffers) { if (!SubmitBuffer_Locked(va_buffer)) return false; } return true; } void VaapiWrapper::DestroyPendingBuffers() { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); TRACE_EVENT0("media,gpu", "VaapiWrapper::DestroyPendingBuffers"); base::AutoLockMaybe auto_lock(va_lock_); DestroyPendingBuffers_Locked(); } void VaapiWrapper::DestroyPendingBuffers_Locked() { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); TRACE_EVENT0("media,gpu", "VaapiWrapper::DestroyPendingBuffers_Locked"); MAYBE_ASSERT_ACQUIRED(va_lock_); for (const auto& pending_va_buf : pending_va_buffers_) { VAStatus va_res = vaDestroyBuffer(va_display_, pending_va_buf); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVADestroyBuffer); } pending_va_buffers_.clear(); } bool VaapiWrapper::ExecuteAndDestroyPendingBuffers(VASurfaceID va_surface_id) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); base::AutoLockMaybe auto_lock(va_lock_); bool result = Execute_Locked(va_surface_id, pending_va_buffers_); DestroyPendingBuffers_Locked(); return result; } bool VaapiWrapper::MapAndCopyAndExecute( VASurfaceID va_surface_id, const std::vector>& va_buffers) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); DCHECK_NE(va_surface_id, VA_INVALID_SURFACE); TRACE_EVENT0("media,gpu", "VaapiWrapper::MapAndCopyAndExecute"); base::AutoLockMaybe auto_lock(va_lock_); std::vector va_buffer_ids; for (const auto& va_buffer : va_buffers) { const VABufferID va_buffer_id = va_buffer.first; const VABufferDescriptor& descriptor = va_buffer.second; DCHECK_NE(va_buffer_id, VA_INVALID_ID); if (!MapAndCopy_Locked(va_buffer_id, descriptor)) return false; va_buffer_ids.push_back(va_buffer_id); } return Execute_Locked(va_surface_id, va_buffer_ids); } #if BUILDFLAG(USE_VAAPI_X11) bool VaapiWrapper::PutSurfaceIntoPixmap(VASurfaceID va_surface_id, x11::Pixmap x_pixmap, gfx::Size dest_size) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); base::AutoLockMaybe auto_lock(va_lock_); VAStatus va_res = vaSyncSurface(va_display_, va_surface_id); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVASyncSurface, false); // Put the data into an X Pixmap. va_res = vaPutSurface(va_display_, va_surface_id, static_cast(x_pixmap), 0, 0, dest_size.width(), dest_size.height(), 0, 0, dest_size.width(), dest_size.height(), nullptr, 0, 0); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAPutSurface, false); return true; } #endif // BUILDFLAG(USE_VAAPI_X11) std::unique_ptr VaapiWrapper::CreateVaImage( VASurfaceID va_surface_id, VAImageFormat* format, const gfx::Size& size) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); std::unique_ptr scoped_image; { base::AutoLockMaybe auto_lock(va_lock_); VAStatus va_res = vaSyncSurface(va_display_, va_surface_id); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVASyncSurface, nullptr); scoped_image = std::make_unique(va_lock_, va_display_, va_surface_id, format, size); } return scoped_image->IsValid() ? std::move(scoped_image) : nullptr; } bool VaapiWrapper::UploadVideoFrameToSurface(const VideoFrame& frame, VASurfaceID va_surface_id, const gfx::Size& va_surface_size) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); TRACE_EVENT0("media,gpu", "VaapiWrapper::UploadVideoFrameToSurface"); base::AutoLockMaybe auto_lock(va_lock_); TRACE_EVENT0("media,gpu", "VaapiWrapper::UploadVideoFrameToSurfaceLocked"); if (frame.visible_rect().origin() != gfx::Point(0, 0)) { LOG(ERROR) << "The origin of the frame's visible rectangle is not (0, 0), " << "frame.visible_rect().origin()=" << frame.visible_rect().origin().ToString(); return false; } const gfx::Size visible_size = frame.visible_rect().size(); bool needs_va_put_image = false; VAImage image; VAStatus va_res = vaDeriveImage(va_display_, va_surface_id, &image); if (va_res == VA_STATUS_ERROR_OPERATION_FAILED) { DVLOG(4) << "vaDeriveImage failed and fallback to Create_PutImage"; constexpr VAImageFormat kImageFormatNV12{.fourcc = VA_FOURCC_NV12, .byte_order = VA_LSB_FIRST, .bits_per_pixel = 12}; VAImageFormat image_format = kImageFormatNV12; va_res = vaCreateImage(va_display_, &image_format, va_surface_size.width(), va_surface_size.height(), &image); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateImage, false); needs_va_put_image = true; } base::ScopedClosureRunner vaimage_deleter( base::BindOnce(&DestroyVAImage, va_display_, image)); if (image.format.fourcc != VA_FOURCC_NV12) { LOG(ERROR) << "Unsupported image format: " << image.format.fourcc; return false; } if (image.width % 2 != 0 || image.height % 2 != 0) { LOG(ERROR) << "Buffer's width and height are not even, " << "width=" << image.width << ", height=" << image.height; return false; } if (!gfx::Rect(image.width, image.height).Contains(gfx::Rect(visible_size))) { LOG(ERROR) << "Buffer too small to fit the frame."; return false; } ScopedVABufferMapping mapping(va_lock_, va_display_, image.buf); if (!mapping.IsValid()) return false; uint8_t* image_ptr = static_cast(mapping.data()); if (!ClearNV12Padding(image, visible_size, image_ptr)) { LOG(ERROR) << "Failed to clear non visible area of VAImage"; return false; } int ret = 0; { TRACE_EVENT0("media,gpu", "VaapiWrapper::UploadVideoFrameToSurface_copy"); std::unique_ptr auto_unlock; if (va_lock_) auto_unlock = std::make_unique(*va_lock_); switch (frame.format()) { case PIXEL_FORMAT_I420: ret = libyuv::I420ToNV12( frame.data(VideoFrame::kYPlane), frame.stride(VideoFrame::kYPlane), frame.data(VideoFrame::kUPlane), frame.stride(VideoFrame::kUPlane), frame.data(VideoFrame::kVPlane), frame.stride(VideoFrame::kVPlane), image_ptr + image.offsets[0], image.pitches[0], image_ptr + image.offsets[1], image.pitches[1], visible_size.width(), visible_size.height()); break; case PIXEL_FORMAT_NV12: { int uv_width = visible_size.width(); if (visible_size.width() % 2 != 0 && !base::CheckAdd(visible_size.width(), 1) .AssignIfValid(&uv_width)) { return false; } int uv_height = 0; if (!(base::CheckAdd(visible_size.height(), 1) / 2) .AssignIfValid(&uv_height)) { return false; } libyuv::CopyPlane(frame.data(VideoFrame::kYPlane), frame.stride(VideoFrame::kYPlane), image_ptr + image.offsets[0], image.pitches[0], visible_size.width(), visible_size.height()); libyuv::CopyPlane(frame.data(VideoFrame::kUVPlane), frame.stride(VideoFrame::kUVPlane), image_ptr + image.offsets[1], image.pitches[1], uv_width, uv_height); } break; default: LOG(ERROR) << "Unsupported pixel format: " << frame.format(); return false; } } if (needs_va_put_image) { va_res = vaPutImage(va_display_, va_surface_id, image.image_id, 0, 0, visible_size.width(), visible_size.height(), 0, 0, visible_size.width(), visible_size.height()); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAPutImage, false); } return ret == 0; } std::unique_ptr VaapiWrapper::CreateVABuffer(VABufferType type, size_t size) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); TRACE_EVENT0("media,gpu", "VaapiWrapper::CreateVABuffer"); base::AutoLockMaybe auto_lock(va_lock_); TRACE_EVENT0("media,gpu", "VaapiWrapper::CreateVABufferLocked"); #if BUILDFLAG(IS_CHROMEOS_ASH) VAContextID context_id = type == VAProtectedSessionExecuteBufferType ? va_protected_session_id_ : va_context_id_; #else VAContextID context_id = va_context_id_; #endif if (context_id == VA_INVALID_ID) return nullptr; return ScopedVABuffer::Create(va_lock_, va_display_, context_id, type, size); } uint64_t VaapiWrapper::GetEncodedChunkSize(VABufferID buffer_id, VASurfaceID sync_surface_id) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); TRACE_EVENT0("media,gpu", "VaapiWrapper::GetEncodedChunkSize"); base::AutoLockMaybe auto_lock(va_lock_); TRACE_EVENT0("media,gpu", "VaapiWrapper::GetEncodedChunkSizeLocked"); VAStatus va_res = vaSyncSurface(va_display_, sync_surface_id); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVASyncSurface, 0u); ScopedVABufferMapping mapping(va_lock_, va_display_, buffer_id); if (!mapping.IsValid()) return 0u; uint64_t coded_data_size = 0; for (auto* buffer_segment = reinterpret_cast(mapping.data()); buffer_segment; buffer_segment = reinterpret_cast( buffer_segment->next)) { coded_data_size += buffer_segment->size; } return coded_data_size; } bool VaapiWrapper::DownloadFromVABuffer(VABufferID buffer_id, VASurfaceID sync_surface_id, uint8_t* target_ptr, size_t target_size, size_t* coded_data_size) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); DCHECK(target_ptr); TRACE_EVENT0("media,gpu", "VaapiWrapper::DownloadFromVABuffer"); base::AutoLockMaybe auto_lock(va_lock_); TRACE_EVENT0("media,gpu", "VaapiWrapper::DownloadFromVABufferLocked"); // vaSyncSurface() is not necessary on Intel platforms as long as there is a // vaMapBuffer() like in ScopedVABufferMapping below, see b/184312032. if (GetImplementationType() != VAImplementation::kIntelI965 && GetImplementationType() != VAImplementation::kIntelIHD) { TRACE_EVENT0("media,gpu", "VaapiWrapper::DownloadFromVABuffer_SyncSurface"); const VAStatus va_res = vaSyncSurface(va_display_, sync_surface_id); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVASyncSurface, false); } ScopedVABufferMapping mapping(va_lock_, va_display_, buffer_id); if (!mapping.IsValid()) return false; auto* buffer_segment = reinterpret_cast(mapping.data()); // memcpy calls should be fast, unlocking and relocking for unmapping might // cause another thread to acquire the lock and we'd have to wait delaying the // notification that the encode is done. { TRACE_EVENT0("media,gpu", "VaapiWrapper::DownloadFromVABuffer_copy"); *coded_data_size = 0; while (buffer_segment) { DCHECK(buffer_segment->buf); if (buffer_segment->size > target_size) { LOG(ERROR) << "Insufficient output buffer size: " << target_size << ", the buffer segment size: " << buffer_segment->size; break; } memcpy(target_ptr, buffer_segment->buf, buffer_segment->size); target_ptr += buffer_segment->size; target_size -= buffer_segment->size; *coded_data_size += buffer_segment->size; buffer_segment = reinterpret_cast(buffer_segment->next); } } return buffer_segment == nullptr; } bool VaapiWrapper::GetVAEncMaxNumOfRefFrames(VideoCodecProfile profile, size_t* max_ref_frames) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); const VAProfile va_profile = ProfileToVAProfile(profile, CodecMode::kEncodeConstantBitrate); VAConfigAttrib attrib; attrib.type = VAConfigAttribEncMaxRefFrames; base::AutoLockMaybe auto_lock(va_lock_); VAStatus va_res = vaGetConfigAttributes(va_display_, va_profile, va_entrypoint_, &attrib, 1); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAGetConfigAttributes, false); *max_ref_frames = attrib.value; return true; } bool VaapiWrapper::GetSupportedPackedHeaders(VideoCodecProfile profile, bool& packed_sps, bool& packed_pps, bool& packed_slice) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); const VAProfile va_profile = ProfileToVAProfile(profile, CodecMode::kEncodeConstantBitrate); VAConfigAttrib attrib{}; attrib.type = VAConfigAttribEncPackedHeaders; base::AutoLockMaybe auto_lock(va_lock_); const VAStatus va_res = vaGetConfigAttributes(va_display_, va_profile, va_entrypoint_, &attrib, 1); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAGetConfigAttributes, false); packed_sps = attrib.value & VA_ENC_PACKED_HEADER_SEQUENCE; packed_pps = attrib.value & VA_ENC_PACKED_HEADER_PICTURE; packed_slice = attrib.value & VA_ENC_PACKED_HEADER_SLICE; return true; } bool VaapiWrapper::IsRotationSupported() { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); base::AutoLockMaybe auto_lock(va_lock_); VAProcPipelineCaps pipeline_caps; memset(&pipeline_caps, 0, sizeof(pipeline_caps)); VAStatus va_res = vaQueryVideoProcPipelineCaps(va_display_, va_context_id_, nullptr, 0, &pipeline_caps); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAQueryVideoProcPipelineCaps, false); if (!pipeline_caps.rotation_flags) { DVLOG(2) << "VA-API driver doesn't support any rotation"; return false; } return true; } bool VaapiWrapper::BlitSurface(const VASurface& va_surface_src, const VASurface& va_surface_dest, absl::optional src_rect, absl::optional dest_rect, VideoRotation rotation #if BUILDFLAG(IS_CHROMEOS_ASH) , VAProtectedSessionID va_protected_session_id #endif ) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); DCHECK_EQ(mode_, kVideoProcess); base::AutoLockMaybe auto_lock(va_lock_); // Create a buffer for VPP if it has not been created. if (!va_buffer_for_vpp_) { DCHECK_NE(VA_INVALID_ID, va_context_id_); va_buffer_for_vpp_ = ScopedVABuffer::Create(va_lock_, va_display_, va_context_id_, VAProcPipelineParameterBufferType, sizeof(VAProcPipelineParameterBuffer)); if (!va_buffer_for_vpp_) return false; } // Note that since we store pointers to these regions in our mapping below, // these may be accessed after the Unmap() below. These must therefore live // until the end of the function. VARectangle input_region; VARectangle output_region; { ScopedVABufferMapping mapping(va_lock_, va_display_, va_buffer_for_vpp_->id()); if (!mapping.IsValid()) return false; auto* pipeline_param = reinterpret_cast(mapping.data()); memset(pipeline_param, 0, sizeof *pipeline_param); if (!src_rect) src_rect.emplace(gfx::Rect(va_surface_src.size())); if (!dest_rect) dest_rect.emplace(gfx::Rect(va_surface_dest.size())); input_region.x = src_rect->x(); input_region.y = src_rect->y(); input_region.width = src_rect->width(); input_region.height = src_rect->height(); pipeline_param->surface_region = &input_region; pipeline_param->surface = va_surface_src.id(); pipeline_param->surface_color_standard = VAProcColorStandardNone; output_region.x = dest_rect->x(); output_region.y = dest_rect->y(); output_region.width = dest_rect->width(); output_region.height = dest_rect->height(); pipeline_param->output_region = &output_region; pipeline_param->output_background_color = 0xff000000; pipeline_param->output_color_standard = VAProcColorStandardNone; pipeline_param->filter_flags = VA_FILTER_SCALING_DEFAULT; switch (rotation) { case VIDEO_ROTATION_0: pipeline_param->rotation_state = VA_ROTATION_NONE; break; case VIDEO_ROTATION_90: pipeline_param->rotation_state = VA_ROTATION_90; break; case VIDEO_ROTATION_180: pipeline_param->rotation_state = VA_ROTATION_180; break; case VIDEO_ROTATION_270: pipeline_param->rotation_state = VA_ROTATION_270; break; } const VAStatus va_res = mapping.Unmap(); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAUnmapBuffer, false); } #if BUILDFLAG(IS_CHROMEOS_ASH) base::ScopedClosureRunner protected_session_detacher; if (va_protected_session_id != VA_INVALID_ID) { const VAStatus va_res = vaAttachProtectedSession( va_display_, va_context_id_, va_protected_session_id); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAAttachProtectedSession, false); // Note that we use a lambda expression to wrap vaDetachProtectedSession() // because the function in |protected_session_detacher| must return void. protected_session_detacher.ReplaceClosure(base::BindOnce( [](VADisplay va_display, VAContextID va_context_id) { vaDetachProtectedSession(va_display, va_context_id); }, va_display_, va_context_id_)); } #endif // BUILDFLAG(IS_CHROMEOS_ASH) TRACE_EVENT2("media,gpu", "VaapiWrapper::BlitSurface", "src_rect", src_rect->ToString(), "dest_rect", dest_rect->ToString()); VAStatus va_res = vaBeginPicture(va_display_, va_context_id_, va_surface_dest.id()); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVABeginPicture, false); VABufferID va_buffer_id = va_buffer_for_vpp_->id(); va_res = vaRenderPicture(va_display_, va_context_id_, &va_buffer_id, 1); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVARenderPicture_Vpp, false); va_res = vaEndPicture(va_display_, va_context_id_); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAEndPicture, false); return true; } // static void VaapiWrapper::PreSandboxInitialization() { VADisplayState::PreSandboxInitialization(); const std::string va_suffix(std::to_string(VA_MAJOR_VERSION + 1)); StubPathMap paths; paths[kModuleVa].push_back(std::string("libva.so.") + va_suffix); paths[kModuleVa_drm].push_back(std::string("libva-drm.so.") + va_suffix); #if BUILDFLAG(USE_VAAPI_X11) paths[kModuleVa_x11].push_back(std::string("libva-x11.so.") + va_suffix); #endif #if BUILDFLAG(IS_CHROMEOS_ASH) paths[kModuleVa_prot].push_back(std::string("libva.so.") + va_suffix); #endif // InitializeStubs dlopen() VA-API libraries // libva.so // libva-x11.so (X11) // libva-drm.so (X11 and Ozone). static bool result = InitializeStubs(paths); if (!result) { static const char kErrorMsg[] = "Failed to initialize VAAPI libs"; LOG(ERROR) << kErrorMsg; } // VASupportedProfiles::Get creates VADisplayState and in so doing // driver associated libraries are dlopen(), to know: // i965_drv_video.so // hybrid_drv_video.so (platforms that support it) // libcmrt.so (platforms that support it) VASupportedProfiles::Get(); } VaapiWrapper::VaapiWrapper(CodecMode mode, bool enforce_sequence_affinity) : mode_(mode), enforce_sequence_affinity_(enforce_sequence_affinity), va_lock_(VADisplayState::Get()->va_lock()), va_display_(nullptr), va_profile_(VAProfileNone), va_entrypoint_(kVAEntrypointInvalid) {} VaapiWrapper::~VaapiWrapper() { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); // Destroy ScopedVABuffer before VaapiWrappers are destroyed to ensure // VADisplay is valid on ScopedVABuffer's destruction. va_buffer_for_vpp_.reset(); DestroyPendingBuffers(); DestroyContext(); Deinitialize(); } bool VaapiWrapper::Initialize(VAProfile va_profile, EncryptionScheme encryption_scheme) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); #if DCHECK_IS_ON() if (mode_ == kEncodeConstantQuantizationParameter) { DCHECK_NE(va_profile, VAProfileJPEGBaseline) << "JPEG Encoding doesn't support CQP bitrate control"; } #endif // DCHECK_IS_ON() #if BUILDFLAG(IS_CHROMEOS_ASH) if (encryption_scheme != EncryptionScheme::kUnencrypted && mode_ != kDecodeProtected) { return false; } #endif const VAEntrypoint entrypoint = GetDefaultVaEntryPoint(mode_, va_profile); base::AutoLockMaybe auto_lock(va_lock_); std::vector required_attribs; if (!GetRequiredAttribs(va_lock_, va_display_, mode_, va_profile, entrypoint, &required_attribs)) { return false; } #if BUILDFLAG(IS_CHROMEOS_ASH) if (encryption_scheme != EncryptionScheme::kUnencrypted) { DCHECK(!required_attribs.empty()); // We need to adjust the attribute for encryption scheme. for (auto& attrib : required_attribs) { if (attrib.type == VAConfigAttribEncryption) { attrib.value = (encryption_scheme == EncryptionScheme::kCbcs) ? VA_ENCRYPTION_TYPE_SUBSAMPLE_CBC : VA_ENCRYPTION_TYPE_SUBSAMPLE_CTR; } } } #endif // BUILDFLAG(IS_CHROMEOS_ASH) const VAStatus va_res = vaCreateConfig(va_display_, va_profile, entrypoint, required_attribs.empty() ? nullptr : &required_attribs[0], required_attribs.size(), &va_config_id_); va_profile_ = va_profile; va_entrypoint_ = entrypoint; VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateConfig, false); return true; } void VaapiWrapper::Deinitialize() { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); { base::AutoLockMaybe auto_lock(va_lock_); #if BUILDFLAG(IS_CHROMEOS_ASH) if (va_protected_session_id_ != VA_INVALID_ID) { VAStatus va_res = vaDestroyProtectedSession(va_display_, va_protected_session_id_); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVADestroyProtectedSession); va_res = vaDestroyConfig(va_display_, va_protected_config_id_); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVADestroyConfig); } #endif if (va_config_id_ != VA_INVALID_ID) { const VAStatus va_res = vaDestroyConfig(va_display_, va_config_id_); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVADestroyConfig); } #if BUILDFLAG(IS_CHROMEOS_ASH) va_protected_session_id_ = VA_INVALID_ID; va_protected_config_id_ = VA_INVALID_ID; #endif va_config_id_ = VA_INVALID_ID; va_display_ = nullptr; } const VAStatus va_res = VADisplayState::Get()->Deinitialize(); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVATerminate); } bool VaapiWrapper::VaInitialize( const ReportErrorToUMACB& report_error_to_uma_cb) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); report_error_to_uma_cb_ = report_error_to_uma_cb; if (!VADisplayState::Get()->Initialize()) return false; DCHECK(va_lock_); if (enforce_sequence_affinity_ && !UseGlobalVaapiLock(VADisplayState::Get()->implementation_type())) { va_lock_ = nullptr; } { base::AutoLockMaybe auto_lock(va_lock_); va_display_ = VADisplayState::Get()->va_display(); DCHECK(va_display_) << "VADisplayState hasn't been properly Initialize()d"; } return true; } void VaapiWrapper::DestroyContext() { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); base::AutoLockMaybe auto_lock(va_lock_); DVLOG(2) << "Destroying context"; if (va_context_id_ != VA_INVALID_ID) { #if BUILDFLAG(IS_CHROMEOS_ASH) if (va_protected_session_id_ != VA_INVALID_ID) { const VAStatus va_res = vaDetachProtectedSession(va_display_, va_context_id_); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVADetachProtectedSession); } #endif const VAStatus va_res = vaDestroyContext(va_display_, va_context_id_); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVADestroyContext); } va_context_id_ = VA_INVALID_ID; } bool VaapiWrapper::CreateSurfaces( unsigned int va_format, const gfx::Size& size, const std::vector& usage_hints, size_t num_surfaces, std::vector* va_surfaces) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); DVLOG(2) << "Creating " << num_surfaces << " " << size.ToString() << " surfaces"; DCHECK_NE(va_format, kInvalidVaRtFormat); DCHECK(va_surfaces->empty()); va_surfaces->resize(num_surfaces); VASurfaceAttrib attribute; if (GetImplementationType() != VAImplementation::kNVIDIAVDPAU) { // Nvidia's VAAPI-VDPAU driver doesn't support this attribute memset(&attribute, 0, sizeof(attribute)); attribute.type = VASurfaceAttribUsageHint; attribute.flags = VA_SURFACE_ATTRIB_SETTABLE; attribute.value.type = VAGenericValueTypeInteger; attribute.value.value.i = 0; for (SurfaceUsageHint usage_hint : usage_hints) attribute.value.value.i |= static_cast(usage_hint); static_assert(std::is_same::value, "attribute.value.value.i is not int32_t"); static_assert(std::is_same::type, int32_t>::value, "The underlying type of SurfaceUsageHint is not int32_t"); } VAStatus va_res; { base::AutoLockMaybe auto_lock(va_lock_); if (GetImplementationType() == VAImplementation::kNVIDIAVDPAU) { va_res = vaCreateSurfaces( va_display_, va_format, base::checked_cast(size.width()), base::checked_cast(size.height()), va_surfaces->data(), num_surfaces, NULL, 0); } else { va_res = vaCreateSurfaces( va_display_, va_format, base::checked_cast(size.width()), base::checked_cast(size.height()), va_surfaces->data(), num_surfaces, &attribute, 1u); } } VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVACreateSurfaces_Allocating); return va_res == VA_STATUS_SUCCESS; } std::vector> VaapiWrapper::CreateScopedVASurfaces( unsigned int va_rt_format, const gfx::Size& size, const std::vector& usage_hints, size_t num_surfaces, const absl::optional& visible_size, const absl::optional& va_fourcc) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); if (kInvalidVaRtFormat == va_rt_format) { LOG(ERROR) << "Invalid VA RT format to CreateScopedVASurface"; return {}; } if (size.IsEmpty()) { LOG(ERROR) << "Invalid visible size input to CreateScopedVASurface"; return {}; } VASurfaceAttrib attribs[2]; unsigned int num_attribs = 1; memset(attribs, 0, sizeof(attribs)); attribs[0].type = VASurfaceAttribUsageHint; attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE; attribs[0].value.type = VAGenericValueTypeInteger; attribs[0].value.value.i = 0; for (SurfaceUsageHint usage_hint : usage_hints) attribs[0].value.value.i |= static_cast(usage_hint); if (va_fourcc) { num_attribs += 1; attribs[1].type = VASurfaceAttribPixelFormat; attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE; attribs[1].value.type = VAGenericValueTypeInteger; attribs[1].value.value.i = base::checked_cast(*va_fourcc); } base::AutoLockMaybe auto_lock(va_lock_); std::vector va_surface_ids(num_surfaces, VA_INVALID_ID); const VAStatus va_res = vaCreateSurfaces( va_display_, va_rt_format, base::checked_cast(size.width()), base::checked_cast(size.height()), va_surface_ids.data(), num_surfaces, attribs, num_attribs); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateSurfaces_Allocating, std::vector>{}); DCHECK(!base::Contains(va_surface_ids, VA_INVALID_ID)) << "Invalid VA surface id after vaCreateSurfaces"; DCHECK(!visible_size.has_value() || !visible_size->IsEmpty()); std::vector> scoped_va_surfaces; scoped_va_surfaces.reserve(num_surfaces); for (const VASurfaceID va_surface_id : va_surface_ids) { auto scoped_va_surface = std::make_unique( this, va_surface_id, visible_size.has_value() ? *visible_size : size, va_rt_format); DCHECK(scoped_va_surface->IsValid()); scoped_va_surfaces.push_back(std::move(scoped_va_surface)); } return scoped_va_surfaces; } void VaapiWrapper::DestroySurfaces(std::vector va_surfaces) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); DVLOG(2) << "Destroying " << va_surfaces.size() << " surfaces"; // vaDestroySurfaces() makes no guarantees about VA_INVALID_SURFACE. base::Erase(va_surfaces, VA_INVALID_SURFACE); if (va_surfaces.empty()) return; base::AutoLockMaybe auto_lock(va_lock_); const VAStatus va_res = vaDestroySurfaces(va_display_, va_surfaces.data(), va_surfaces.size()); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVADestroySurfaces); } void VaapiWrapper::DestroySurface(VASurfaceID va_surface_id) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); if (va_surface_id == VA_INVALID_SURFACE) return; DVLOG(2) << __func__ << " " << va_surface_id; base::AutoLockMaybe auto_lock(va_lock_); const VAStatus va_res = vaDestroySurfaces(va_display_, &va_surface_id, 1); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVADestroySurfaces); } bool VaapiWrapper::Execute_Locked(VASurfaceID va_surface_id, const std::vector& va_buffers) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); TRACE_EVENT0("media,gpu", "VaapiWrapper::Execute_Locked"); MAYBE_ASSERT_ACQUIRED(va_lock_); DVLOG(4) << "Pending VA bufs to commit: " << pending_va_buffers_.size(); DVLOG(4) << "Target VA surface " << va_surface_id; const auto decode_start_time = base::TimeTicks::Now(); // Get ready to execute for given surface. VAStatus va_res = vaBeginPicture(va_display_, va_context_id_, va_surface_id); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVABeginPicture, false); if (!va_buffers.empty()) { // vaRenderPicture() needs a non-const pointer, possibly unnecessarily. VABufferID* va_buffers_data = const_cast(va_buffers.data()); va_res = vaRenderPicture(va_display_, va_context_id_, va_buffers_data, base::checked_cast(va_buffers.size())); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVARenderPicture_VABuffers, false); } // Instruct HW codec to start processing the submitted commands. In theory, // this shouldn't be blocking, relying on vaSyncSurface() instead, however // evidence points to it actually waiting for the job to be done. va_res = vaEndPicture(va_display_, va_context_id_); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAEndPicture, false); UMA_HISTOGRAM_TIMES("Media.PlatformVideoDecoding.Decode", base::TimeTicks::Now() - decode_start_time); return true; } bool VaapiWrapper::SubmitBuffer_Locked(const VABufferDescriptor& va_buffer) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); TRACE_EVENT0("media,gpu", "VaapiWrapper::SubmitBuffer_Locked"); MAYBE_ASSERT_ACQUIRED(va_lock_); DCHECK(IsValidVABufferType(va_buffer.type)); base::ScopedClosureRunner pending_buffers_destroyer_on_failure(base::BindOnce( &VaapiWrapper::DestroyPendingBuffers_Locked, base::Unretained(this))); unsigned int va_buffer_size; // We use a null |va_buffer|.data for testing: it signals that we want this // SubmitBuffer_Locked() call to fail. if (!va_buffer.data || !base::CheckedNumeric(va_buffer.size) .AssignIfValid(&va_buffer_size)) { return false; } VABufferID buffer_id; { TRACE_EVENT0("media,gpu", "VaapiWrapper::SubmitBuffer_Locked_vaCreateBuffer"); const VAStatus va_res = vaCreateBuffer(va_display_, va_context_id_, va_buffer.type, va_buffer_size, 1, nullptr, &buffer_id); VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateBuffer, false); } if (!MapAndCopy_Locked(buffer_id, va_buffer)) return false; pending_va_buffers_.push_back(buffer_id); pending_buffers_destroyer_on_failure.ReplaceClosure(base::DoNothing()); return true; } bool VaapiWrapper::MapAndCopy_Locked(VABufferID va_buffer_id, const VABufferDescriptor& va_buffer) { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); MAYBE_ASSERT_ACQUIRED(va_lock_); DCHECK_NE(va_buffer_id, VA_INVALID_ID); DCHECK(IsValidVABufferType(va_buffer.type)); DCHECK(va_buffer.data); ScopedVABufferMapping mapping( va_lock_, va_display_, va_buffer_id, base::BindOnce(base::IgnoreResult(&vaDestroyBuffer), va_display_)); if (!mapping.IsValid()) return false; return memcpy(mapping.data(), va_buffer.data, va_buffer.size); } void VaapiWrapper::MaybeSetLowQualityEncoding_Locked() { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); DCHECK(IsModeEncoding(mode_)); MAYBE_ASSERT_ACQUIRED(va_lock_); // Query if encoding quality (VAConfigAttribEncQualityRange) is supported, and // if so, use the associated value for lowest quality and power consumption. VAConfigAttrib attrib{}; attrib.type = VAConfigAttribEncQualityRange; const VAStatus va_res = vaGetConfigAttributes(va_display_, va_profile_, va_entrypoint_, &attrib, 1); if (va_res != VA_STATUS_SUCCESS) { LOG(ERROR) << "vaGetConfigAttributes failed: " << vaProfileStr(va_profile_); return; } // From libva's va.h: 'A value less than or equal to 1 means that the // encoder only has a single "quality setting,"'. if (attrib.value == VA_ATTRIB_NOT_SUPPORTED || attrib.value <= 1u) return; const size_t temp_size = sizeof(VAEncMiscParameterBuffer) + sizeof(VAEncMiscParameterBufferQualityLevel); std::vector temp(temp_size); auto* const va_buffer = reinterpret_cast(temp.data()); va_buffer->type = VAEncMiscParameterTypeQualityLevel; auto* const enc_quality = reinterpret_cast(va_buffer->data); enc_quality->quality_level = attrib.value; const bool success = SubmitBuffer_Locked({VAEncMiscParameterBufferType, temp_size, va_buffer}); LOG_IF(ERROR, !success) << "Error setting encoding quality to " << enc_quality->quality_level; } bool VaapiWrapper::MaybeAttachProtectedSession_Locked() { CHECK(!enforce_sequence_affinity_ || sequence_checker_.CalledOnValidSequence()); MAYBE_ASSERT_ACQUIRED(va_lock_); if (va_context_id_ == VA_INVALID_ID) return true; #if BUILDFLAG(IS_CHROMEOS_ASH) if (va_protected_session_id_ == VA_INVALID_ID) return true; VAStatus va_res = vaAttachProtectedSession(va_display_, va_context_id_, va_protected_session_id_); VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVAAttachProtectedSession); return va_res == VA_STATUS_SUCCESS; #else return true; #endif } } // namespace media