diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc index c88b05d3..d8aad37c 100644 --- a/media/gpu/vaapi/vaapi_wrapper.cc +++ b/media/gpu/vaapi/vaapi_wrapper.cc @@ -1,1765 +1,3417 @@ -// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Copyright 2022 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 "net/url_request/url_request_http_job.h" +#include "media/gpu/vaapi/vaapi_wrapper.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include +#include +#include #include -#include -#include "base/base_switches.h" #include "base/bind.h" +#include "base/bits.h" #include "base/callback_helpers.h" -#include "base/check_op.h" -#include "base/command_line.h" -#include "base/compiler_specific.h" -#include "base/file_version_info.h" -#include "base/location.h" -#include "base/memory/ptr_util.h" -#include "base/metrics/field_trial.h" -#include "base/metrics/histogram_functions.h" +#include "base/containers/contains.h" +#include "base/containers/cxx20_erase.h" +#include "base/containers/fixed_flat_set.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/rand_util.h" -#include "base/stl_util.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/pattern.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" -#include "base/task/single_thread_task_runner.h" -#include "base/threading/thread_task_runner_handle.h" -#include "base/time/time.h" +#include "base/system/sys_info.h" #include "base/trace_event/trace_event.h" -#include "base/values.h" #include "build/build_config.h" -#include "net/base/features.h" -#include "net/base/host_port_pair.h" -#include "net/base/http_user_agent_settings.h" -#include "net/base/load_flags.h" -#include "net/base/net_errors.h" -#include "net/base/network_delegate.h" -#include "net/base/network_isolation_key.h" -#include "net/base/privacy_mode.h" -#include "net/base/registry_controlled_domains/registry_controlled_domain.h" -#include "net/base/trace_constants.h" -#include "net/base/url_util.h" -#include "net/cert/cert_status_flags.h" -#include "net/cert/ct_policy_status.h" -#include "net/cert/known_roots.h" -#include "net/cookies/canonical_cookie.h" -#include "net/cookies/cookie_access_delegate.h" -#include "net/cookies/cookie_constants.h" -#include "net/cookies/cookie_store.h" -#include "net/cookies/cookie_util.h" -#include "net/cookies/first_party_set_metadata.h" -#include "net/cookies/same_party_context.h" -#include "net/filter/brotli_source_stream.h" -#include "net/filter/filter_source_stream.h" -#include "net/filter/gzip_source_stream.h" -#include "net/filter/source_stream.h" -#include "net/http/http_content_disposition.h" -#include "net/http/http_log_util.h" -#include "net/http/http_network_session.h" -#include "net/http/http_request_headers.h" -#include "net/http/http_response_headers.h" -#include "net/http/http_response_info.h" -#include "net/http/http_status_code.h" -#include "net/http/http_transaction.h" -#include "net/http/http_transaction_factory.h" -#include "net/http/http_util.h" -#include "net/http/transport_security_state.h" -#include "net/log/net_log.h" -#include "net/log/net_log_event_type.h" -#include "net/log/net_log_values.h" -#include "net/log/net_log_with_source.h" -#include "net/nqe/network_quality_estimator.h" -#include "net/proxy_resolution/proxy_info.h" -#include "net/proxy_resolution/proxy_resolution_service.h" -#include "net/proxy_resolution/proxy_retry_info.h" -#include "net/ssl/ssl_cert_request_info.h" -#include "net/ssl/ssl_config_service.h" -#include "net/ssl/ssl_connection_status_flags.h" -#include "net/url_request/redirect_util.h" -#include "net/url_request/url_request.h" -#include "net/url_request/url_request_context.h" -#include "net/url_request/url_request_error_job.h" -#include "net/url_request/url_request_job_factory.h" -#include "net/url_request/url_request_redirect_job.h" -#include "net/url_request/url_request_throttler_manager.h" -#include "net/url_request/websocket_handshake_userdata_key.h" -#include "third_party/abseil-cpp/absl/types/optional.h" -#include "url/gurl.h" -#include "url/origin.h" -#include "url/url_constants.h" +#include "build/chromeos_buildflags.h" -#if BUILDFLAG(IS_ANDROID) -#include "net/android/network_library.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 { -base::Value CookieInclusionStatusNetLogParams( - const std::string& operation, - const std::string& cookie_name, - const std::string& cookie_domain, - const std::string& cookie_path, - const net::CookieInclusionStatus& status, - net::NetLogCaptureMode capture_mode) { - base::Value dict(base::Value::Type::DICTIONARY); - dict.SetStringKey("operation", operation); - dict.SetStringKey("status", status.GetDebugString()); - if (net::NetLogCaptureIncludesSensitive(capture_mode)) { - if (!cookie_name.empty()) - dict.SetStringKey("name", cookie_name); - if (!cookie_domain.empty()) - dict.SetStringKey("domain", cookie_domain); - if (!cookie_path.empty()) - dict.SetStringKey("path", cookie_path); +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; } - return dict; } -// Records details about the most-specific trust anchor in |spki_hashes|, -// which is expected to be ordered with the leaf cert first and the root cert -// last. This complements the per-verification histogram -// Net.Certificate.TrustAnchor.Verify -void LogTrustAnchor(const net::HashValueVector& spki_hashes) { - // Don't record metrics if there are no hashes; this is true if the HTTP - // load did not come from an active network connection, such as the disk - // cache or a synthesized response. - if (spki_hashes.empty()) - return; - - int32_t id = 0; - for (const auto& hash : spki_hashes) { - id = net::GetNetTrustAnchorHistogramIdForSPKI(hash); - if (id != 0) - break; +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; } - base::UmaHistogramSparse("Net.Certificate.TrustAnchor.Request", id); + return media::VAImplementation::kOther; } -net::CookieOptions CreateCookieOptions( - net::CookieOptions::SameSiteCookieContext same_site_context, - const net::SamePartyContext& same_party_context, - const net::IsolationInfo& isolation_info, - bool is_in_nontrivial_first_party_set) { - net::CookieOptions options; - options.set_return_excluded_cookies(); - options.set_include_httponly(); - options.set_same_site_cookie_context(same_site_context); - options.set_same_party_context(same_party_context); - if (isolation_info.party_context().has_value()) { - // Count the top-frame site since it's not in the party_context. - options.set_full_party_context_size(isolation_info.party_context()->size() + - 1); - } - options.set_is_in_nontrivial_first_party_set( - is_in_nontrivial_first_party_set); - return options; -} - -bool IsTLS13OverTCP(const net::HttpResponseInfo& response_info) { - // Although IETF QUIC also uses TLS 1.3, our QUIC connections report - // SSL_CONNECTION_VERSION_QUIC. - return net::SSLConnectionStatusToVersion( - response_info.ssl_info.connection_status) == - net::SSL_CONNECTION_VERSION_TLS1_3; -} - -GURL UpgradeSchemeToCryptographic(const GURL& insecure_url) { - DCHECK(!insecure_url.SchemeIsCryptographic()); - DCHECK(insecure_url.SchemeIs(url::kHttpScheme) || - insecure_url.SchemeIs(url::kWsScheme)); - - GURL::Replacements replacements; - replacements.SetSchemeStr(insecure_url.SchemeIs(url::kHttpScheme) - ? url::kHttpsScheme - : url::kWssScheme); - - GURL secure_url = insecure_url.ReplaceComponents(replacements); - DCHECK(secure_url.SchemeIsCryptographic()); - - return secure_url; +bool UseGlobalVaapiLock(media::VAImplementation implementation_type) { + // Only iHD and Mesa Gallium are known to be thread safe at the moment. + // * Mesa Gallium: b/144877595 + // * iHD: crbug.com/1123429. + constexpr auto kNoVaapiLockImplementations = + base::MakeFixedFlatSet( + {media::VAImplementation::kMesaGallium, + media::VAImplementation::kIntelIHD}); + return !kNoVaapiLockImplementations.contains(implementation_type) || + base::FeatureList::IsEnabled(media::kGlobalVaapiLock); } } // namespace -namespace net { - -std::unique_ptr URLRequestHttpJob::Create(URLRequest* request) { - const GURL& url = request->url(); - - // URLRequestContext must have been initialized. - DCHECK(request->context()->http_transaction_factory()); - DCHECK(url.SchemeIsHTTPOrHTTPS() || url.SchemeIsWSOrWSS()); - - // Check for reasons not to return a URLRequestHttpJob. These don't apply to - // https and wss requests. - if (!url.SchemeIsCryptographic()) { - // Check for HSTS upgrade. - TransportSecurityState* hsts = - request->context()->transport_security_state(); - if (hsts && hsts->ShouldUpgradeToSSL(url.host(), request->net_log())) { - return std::make_unique( - request, UpgradeSchemeToCryptographic(url), - // Use status code 307 to preserve the method, so POST requests work. - RedirectUtil::ResponseCode::REDIRECT_307_TEMPORARY_REDIRECT, "HSTS"); - } - -#if BUILDFLAG(IS_ANDROID) - // Check whether the app allows cleartext traffic to this host, and return - // ERR_CLEARTEXT_NOT_PERMITTED if not. - if (request->context()->check_cleartext_permitted() && - !android::IsCleartextPermitted(url.host())) { - return std::make_unique(request, - ERR_CLEARTEXT_NOT_PERMITTED); - } -#endif - } - - return base::WrapUnique(new URLRequestHttpJob( - request, request->context()->http_user_agent_settings())); -} - -URLRequestHttpJob::URLRequestHttpJob( - URLRequest* request, - const HttpUserAgentSettings* http_user_agent_settings) - : URLRequestJob(request), - num_cookie_lines_left_(0), - priority_(DEFAULT_PRIORITY), - response_info_(nullptr), - proxy_auth_state_(AUTH_STATE_DONT_NEED_AUTH), - server_auth_state_(AUTH_STATE_DONT_NEED_AUTH), - read_in_progress_(false), - throttling_entry_(nullptr), - done_(false), - awaiting_callback_(false), - http_user_agent_settings_(http_user_agent_settings), - total_received_bytes_from_previous_transactions_(0), - total_sent_bytes_from_previous_transactions_(0) { - URLRequestThrottlerManager* manager = request->context()->throttler_manager(); - if (manager) - throttling_entry_ = manager->RegisterRequestUrl(request->url()); - - ResetTimer(); -} - -URLRequestHttpJob::~URLRequestHttpJob() { - CHECK(!awaiting_callback_); - - DoneWithRequest(ABORTED); -} - -void URLRequestHttpJob::SetPriority(RequestPriority priority) { - priority_ = priority; - if (transaction_) - transaction_->SetPriority(priority_); -} - -void URLRequestHttpJob::Start() { - DCHECK(!transaction_.get()); - - request_info_.url = request_->url(); - request_info_.method = request_->method(); - - request_info_.network_isolation_key = - request_->isolation_info().network_isolation_key(); - request_info_.possibly_top_frame_origin = - request_->isolation_info().top_frame_origin(); - request_info_.is_subframe_document_resource = - request_->isolation_info().request_type() == - net::IsolationInfo::RequestType::kSubFrame; - request_info_.load_flags = request_->load_flags(); - request_info_.secure_dns_policy = request_->secure_dns_policy(); - request_info_.traffic_annotation = - net::MutableNetworkTrafficAnnotationTag(request_->traffic_annotation()); - request_info_.socket_tag = request_->socket_tag(); - request_info_.idempotency = request_->GetIdempotency(); -#if BUILDFLAG(ENABLE_REPORTING) - request_info_.reporting_upload_depth = request_->reporting_upload_depth(); -#endif - - if (!ShouldAddCookieHeader()) { - OnGotFirstPartySetMetadata(FirstPartySetMetadata()); - return; - } - absl::optional metadata = - cookie_util::ComputeFirstPartySetMetadataMaybeAsync( - SchemefulSite(request()->url()), request()->isolation_info(), - request()->context()->cookie_store()->cookie_access_delegate(), - request()->force_ignore_top_frame_party_for_cookies(), - base::BindOnce(&URLRequestHttpJob::OnGotFirstPartySetMetadata, - weak_factory_.GetWeakPtr())); - - if (metadata.has_value()) - OnGotFirstPartySetMetadata(std::move(metadata.value())); -} - -void URLRequestHttpJob::OnGotFirstPartySetMetadata( - FirstPartySetMetadata first_party_set_metadata) { - first_party_set_metadata_ = std::move(first_party_set_metadata); - // Privacy mode could still be disabled in SetCookieHeaderAndStart if we are - // going to send previously saved cookies. - request_info_.privacy_mode = DeterminePrivacyMode(); - request()->net_log().AddEventWithStringParams( - NetLogEventType::COMPUTED_PRIVACY_MODE, "privacy_mode", - PrivacyModeToDebugString(request_info_.privacy_mode)); - - // Strip Referer from request_info_.extra_headers to prevent, e.g., plugins - // from overriding headers that are controlled using other means. Otherwise a - // plugin could set a referrer although sending the referrer is inhibited. - request_info_.extra_headers.RemoveHeader(HttpRequestHeaders::kReferer); - - // URLRequest::SetReferrer ensures that we do not send username and password - // fields in the referrer. - GURL referrer(request_->referrer()); - - if (!(request_info_.load_flags & LOAD_MINIMAL_HEADERS)) { - // Our consumer should have made sure that this is a safe referrer (e.g. via - // URLRequestJob::ComputeReferrerForPolicy). - if (referrer.is_valid()) { - std::string referer_value = referrer.spec(); - request_info_.extra_headers.SetHeader(HttpRequestHeaders::kReferer, - referer_value); - } - } - - if (!(request_info_.load_flags & LOAD_MINIMAL_HEADERS)) { - request_info_.extra_headers.SetHeaderIfMissing( - HttpRequestHeaders::kUserAgent, - http_user_agent_settings_ ? - http_user_agent_settings_->GetUserAgent() : std::string()); - } - - // Addextraheaders declaration for dev use. - AddExtraHeaders(); - - if (ShouldAddCookieHeader()) { - // We shouldn't overwrite this if we've already computed the key. - DCHECK(!cookie_partition_key_.has_value()); - - cookie_partition_key_ = - absl::make_optional(CookiePartitionKey::FromNetworkIsolationKey( - request_->isolation_info().network_isolation_key(), - base::OptionalOrNullptr( - first_party_set_metadata_.top_frame_owner()))); - AddCookieHeaderAndStart(); - } else { - StartTransaction(); - } -} - -void URLRequestHttpJob::Kill() { - weak_factory_.InvalidateWeakPtrs(); - if (transaction_) - DestroyTransaction(); - URLRequestJob::Kill(); -} - -void URLRequestHttpJob::GetConnectionAttempts(ConnectionAttempts* out) const { - if (transaction_) - transaction_->GetConnectionAttempts(out); - else - out->clear(); -} - -void URLRequestHttpJob::CloseConnectionOnDestruction() { - DCHECK(transaction_); - transaction_->CloseConnectionOnDestruction(); -} - -int URLRequestHttpJob::NotifyConnectedCallback( - const TransportInfo& info, - CompletionOnceCallback callback) { - return URLRequestJob::NotifyConnected(info, std::move(callback)); -} - -PrivacyMode URLRequestHttpJob::DeterminePrivacyMode() const { - if (!request()->allow_credentials()) { - // |allow_credentials_| implies LOAD_DO_NOT_SAVE_COOKIES. - DCHECK(request_->load_flags() & LOAD_DO_NOT_SAVE_COOKIES); - - // TODO(https://crbug.com/775438): Client certs should always be - // affirmatively omitted for these requests. - return request()->send_client_certs() - ? PRIVACY_MODE_ENABLED - : PRIVACY_MODE_ENABLED_WITHOUT_CLIENT_CERTS; - } - - // Otherwise, check with the delegate if present, or base it off of - // |URLRequest::DefaultCanUseCookies()| if not. - // TODO(mmenke): Looks like |URLRequest::DefaultCanUseCookies()| is not too - // useful, with the network service - remove it. - NetworkDelegate::PrivacySetting privacy_setting = - URLRequest::DefaultCanUseCookies() - ? NetworkDelegate::PrivacySetting::kStateAllowed - : NetworkDelegate::PrivacySetting::kStateDisallowed; - if (request_->network_delegate()) { - privacy_setting = request()->network_delegate()->ForcePrivacyMode( - request_->url(), request_->site_for_cookies(), - request_->isolation_info().top_frame_origin(), - first_party_set_metadata_.context().context_type()); - } - switch (privacy_setting) { - case NetworkDelegate::PrivacySetting::kStateAllowed: - return PRIVACY_MODE_DISABLED; - case NetworkDelegate::PrivacySetting::kPartitionedStateAllowedOnly: - return PRIVACY_MODE_ENABLED_PARTITIONED_STATE_ALLOWED; - case NetworkDelegate::PrivacySetting::kStateDisallowed: - return PRIVACY_MODE_ENABLED; - } - NOTREACHED(); - return PRIVACY_MODE_ENABLED; -} - -void URLRequestHttpJob::NotifyHeadersComplete() { - DCHECK(!response_info_); - DCHECK_EQ(0, num_cookie_lines_left_); - DCHECK(request_->maybe_stored_cookies().empty()); - - if (override_response_info_) { - DCHECK(!transaction_); - response_info_ = override_response_info_.get(); - } else { - response_info_ = transaction_->GetResponseInfo(); - } - - if (!response_info_->was_cached && throttling_entry_.get()) - throttling_entry_->UpdateWithResponse(GetResponseCode()); - - // The ordering of these calls is not important. - ProcessStrictTransportSecurityHeader(); - ProcessExpectCTHeader(); - - // Clear |set_cookie_access_result_list_| after any processing in case - // SaveCookiesAndNotifyHeadersComplete is called again. - request_->set_maybe_stored_cookies(std::move(set_cookie_access_result_list_)); - - // The HTTP transaction may be restarted several times for the purposes - // of sending authorization information. Each time it restarts, we get - // notified of the headers completion so that we can update the cookie store. - if (transaction_ && transaction_->IsReadyToRestartForAuth()) { - // TODO(battre): This breaks the webrequest API for - // URLRequestTestHTTP.BasicAuthWithCookies - // where OnBeforeStartTransaction -> OnStartTransaction -> - // OnBeforeStartTransaction occurs. - RestartTransactionWithAuth(AuthCredentials()); - return; - } - - URLRequestJob::NotifyHeadersComplete(); -} - -void URLRequestHttpJob::DestroyTransaction() { - DCHECK(transaction_.get()); - - DoneWithRequest(ABORTED); - - total_received_bytes_from_previous_transactions_ += - transaction_->GetTotalReceivedBytes(); - total_sent_bytes_from_previous_transactions_ += - transaction_->GetTotalSentBytes(); - transaction_.reset(); - response_info_ = nullptr; - override_response_headers_ = nullptr; - receive_headers_end_ = base::TimeTicks(); -} - -void URLRequestHttpJob::StartTransaction() { - DCHECK(!override_response_info_); - - NetworkDelegate* network_delegate = request()->network_delegate(); - if (network_delegate) { - OnCallToDelegate( - NetLogEventType::NETWORK_DELEGATE_BEFORE_START_TRANSACTION); - int rv = network_delegate->NotifyBeforeStartTransaction( - request_, request_info_.extra_headers, - base::BindOnce(&URLRequestHttpJob::NotifyBeforeStartTransactionCallback, - weak_factory_.GetWeakPtr())); - // If an extension blocks the request, we rely on the callback to - // MaybeStartTransactionInternal(). - if (rv == ERR_IO_PENDING) - return; - MaybeStartTransactionInternal(rv); - return; - } - StartTransactionInternal(); -} - -void URLRequestHttpJob::NotifyBeforeStartTransactionCallback( - int result, - const absl::optional& headers) { - // The request should not have been cancelled or have already completed. - DCHECK(!is_done()); - - if (headers) - request_info_.extra_headers = headers.value(); - MaybeStartTransactionInternal(result); -} - -void URLRequestHttpJob::MaybeStartTransactionInternal(int result) { - OnCallToDelegateComplete(); - if (result == OK) { - StartTransactionInternal(); - } else { - request_->net_log().AddEventWithStringParams(NetLogEventType::CANCELLED, - "source", "delegate"); - // Don't call back synchronously to the delegate. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(&URLRequestHttpJob::NotifyStartError, - weak_factory_.GetWeakPtr(), result)); - } -} - -void URLRequestHttpJob::StartTransactionInternal() { - DCHECK(!override_response_headers_); - - // NOTE: This method assumes that request_info_ is already setup properly. - - // If we already have a transaction, then we should restart the transaction - // with auth provided by auth_credentials_. - - int rv; - - // Notify NetworkQualityEstimator. - NetworkQualityEstimator* network_quality_estimator = - request()->context()->network_quality_estimator(); - if (network_quality_estimator) - network_quality_estimator->NotifyStartTransaction(*request_); - - if (transaction_.get()) { - rv = transaction_->RestartWithAuth( - auth_credentials_, base::BindOnce(&URLRequestHttpJob::OnStartCompleted, - base::Unretained(this))); - auth_credentials_ = AuthCredentials(); - } else { - DCHECK(request_->context()->http_transaction_factory()); - - rv = request_->context()->http_transaction_factory()->CreateTransaction( - priority_, &transaction_); - - if (rv == OK && request_info_.url.SchemeIsWSOrWSS()) { - base::SupportsUserData::Data* data = - request_->GetUserData(kWebSocketHandshakeUserDataKey); - if (data) { - transaction_->SetWebSocketHandshakeStreamCreateHelper( - static_cast(data)); - } else { - rv = ERR_DISALLOWED_URL_SCHEME; - } - } - - if (rv == OK) { - transaction_->SetConnectedCallback(base::BindRepeating( - &URLRequestHttpJob::NotifyConnectedCallback, base::Unretained(this))); - transaction_->SetRequestHeadersCallback(request_headers_callback_); - transaction_->SetEarlyResponseHeadersCallback( - early_response_headers_callback_); - transaction_->SetResponseHeadersCallback(response_headers_callback_); - - if (!throttling_entry_.get() || - !throttling_entry_->ShouldRejectRequest(*request_)) { - rv = transaction_->Start( - &request_info_, - base::BindOnce(&URLRequestHttpJob::OnStartCompleted, - base::Unretained(this)), - request_->net_log()); - start_time_ = base::TimeTicks::Now(); - } else { - // Special error code for the exponential back-off module. - rv = ERR_TEMPORARILY_THROTTLED; - } - } - } - - if (rv == ERR_IO_PENDING) - return; - - // The transaction started synchronously, but we need to notify the - // URLRequest delegate via the message loop. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(&URLRequestHttpJob::OnStartCompleted, - weak_factory_.GetWeakPtr(), rv)); -} - -void URLRequestHttpJob::AddExtraHeaders() { - if (!request_info_.extra_headers.HasHeader( - HttpRequestHeaders::kAcceptEncoding)) { - // If a range is specifically requested, set the "Accepted Encoding" header - // to "identity" - if (request_info_.extra_headers.HasHeader(HttpRequestHeaders::kRange)) { - request_info_.extra_headers.SetHeader(HttpRequestHeaders::kAcceptEncoding, - "identity"); - } else { - // Supply Accept-Encoding headers first so that it is more likely that - // they will be in the first transmitted packet. This can sometimes make - // it easier to filter and analyze the streams to assure that a proxy has - // not damaged these headers. Some proxies deliberately corrupt - // Accept-Encoding headers. - std::vector advertised_encoding_names; - if (request_->Supports(SourceStream::SourceType::TYPE_GZIP)) { - advertised_encoding_names.push_back("gzip"); - } - if (request_->Supports(SourceStream::SourceType::TYPE_DEFLATE)) { - advertised_encoding_names.push_back("deflate"); - } - if (!(request_info_.load_flags & LOAD_MINIMAL_HEADERS)) { - // Advertise "br" encoding only if transferred data is opaque to proxy. - if (request()->context()->enable_brotli() && - request_->Supports(SourceStream::SourceType::TYPE_BROTLI)) { - if (request()->url().SchemeIsCryptographic() || - IsLocalhost(request()->url())) { - advertised_encoding_names.push_back("br"); - } - } - } // minimal headers - if (!advertised_encoding_names.empty()) { - // Tell the server what compression formats are supported. - request_info_.extra_headers.SetHeader( - HttpRequestHeaders::kAcceptEncoding, - base::JoinString(base::make_span(advertised_encoding_names), ", ")); - } - } - } - - if (!(request_info_.load_flags & LOAD_MINIMAL_HEADERS) && http_user_agent_settings_) { - // Only add default Accept-Language if the request didn't have it - // specified. - std::string accept_language = - http_user_agent_settings_->GetAcceptLanguage(); - if (base::FeatureList::IsEnabled(features::kAcceptLanguageHeader) && - !accept_language.empty()) { - request_info_.extra_headers.SetHeaderIfMissing( - HttpRequestHeaders::kAcceptLanguage, - accept_language); - } - } -} - -void URLRequestHttpJob::AddCookieHeaderAndStart() { - CookieStore* cookie_store = request_->context()->cookie_store(); - DCHECK(cookie_store); - DCHECK(ShouldAddCookieHeader()); - bool force_ignore_site_for_cookies = - request_->force_ignore_site_for_cookies(); - if (cookie_store->cookie_access_delegate() && - cookie_store->cookie_access_delegate()->ShouldIgnoreSameSiteRestrictions( - request_->url(), request_->site_for_cookies())) { - force_ignore_site_for_cookies = true; - } - bool is_main_frame_navigation = - IsolationInfo::RequestType::kMainFrame == - request_->isolation_info().request_type() || - request_->force_main_frame_for_same_site_cookies(); - CookieOptions::SameSiteCookieContext same_site_context = - net::cookie_util::ComputeSameSiteContextForRequest( - request_->method(), request_->url_chain(), - request_->site_for_cookies(), request_->initiator(), - is_main_frame_navigation, force_ignore_site_for_cookies); - - bool is_in_nontrivial_first_party_set = - first_party_set_metadata_.frame_owner().has_value(); - CookieOptions options = CreateCookieOptions( - same_site_context, first_party_set_metadata_.context(), - request_->isolation_info(), is_in_nontrivial_first_party_set); - - UMA_HISTOGRAM_ENUMERATION( - "Cookie.FirstPartySetsContextType.HTTP.Read", - first_party_set_metadata_.first_party_sets_context_type()); - - cookie_store->GetCookieListWithOptionsAsync( - request_->url(), options, - CookiePartitionKeyCollection::FromOptional(cookie_partition_key_.value()), - base::BindOnce(&URLRequestHttpJob::SetCookieHeaderAndStart, - weak_factory_.GetWeakPtr(), options)); -} +namespace media { namespace { +// VAEntrypoint is an enumeration starting from 1, but has no "invalid" value. +constexpr VAEntrypoint kVAEntrypointInvalid = static_cast(0); -bool ShouldBlockAllCookies(const PrivacyMode& privacy_mode) { - return privacy_mode == PRIVACY_MODE_ENABLED || - privacy_mode == PRIVACY_MODE_ENABLED_WITHOUT_CLIENT_CERTS; +// 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; } -bool ShouldBlockUnpartitionedCookiesOnly(const PrivacyMode& privacy_mode) { - return privacy_mode == PRIVACY_MODE_ENABLED_PARTITIONED_STATE_ALLOWED; +// 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; } -} // namespace - -void URLRequestHttpJob::SetCookieHeaderAndStart( - const CookieOptions& options, - const CookieAccessResultList& cookies_with_access_result_list, - const CookieAccessResultList& excluded_list) { - DCHECK(request_->maybe_sent_cookies().empty()); - - CookieAccessResultList maybe_included_cookies = - cookies_with_access_result_list; - CookieAccessResultList excluded_cookies = excluded_list; - - if (ShouldBlockAllCookies(request_info_.privacy_mode)) { - // If cookies are blocked (without our needing to consult the delegate), - // we move them to `excluded_cookies` and ensure that they have the - // correct exclusion reason. - excluded_cookies.insert( - excluded_cookies.end(), - std::make_move_iterator(maybe_included_cookies.begin()), - std::make_move_iterator(maybe_included_cookies.end())); - maybe_included_cookies.clear(); - for (auto& cookie : excluded_cookies) { - cookie.access_result.status.AddExclusionReason( - CookieInclusionStatus::EXCLUDE_USER_PREFERENCES); - } - } - if (ShouldBlockUnpartitionedCookiesOnly(request_info_.privacy_mode)) { - auto partition_it = base::ranges::stable_partition( - maybe_included_cookies, [](const CookieWithAccessResult& el) { - return el.cookie.IsPartitioned(); - }); - for (auto it = partition_it; it < maybe_included_cookies.end(); ++it) { - it->access_result.status.AddExclusionReason( - CookieInclusionStatus::EXCLUDE_USER_PREFERENCES); - } - excluded_cookies.insert( - excluded_cookies.end(), std::make_move_iterator(partition_it), - std::make_move_iterator(maybe_included_cookies.end())); - maybe_included_cookies.erase(partition_it, maybe_included_cookies.end()); - } - if (request_info_.privacy_mode == PRIVACY_MODE_DISABLED || - !maybe_included_cookies.empty()) { - AnnotateAndMoveUserBlockedCookies(maybe_included_cookies, excluded_cookies); - if (!maybe_included_cookies.empty()) { - std::string cookie_line = - CanonicalCookie::BuildCookieLine(maybe_included_cookies); - UMA_HISTOGRAM_COUNTS_10000("Cookie.HeaderLength", cookie_line.length()); - request_info_.extra_headers.SetHeader(HttpRequestHeaders::kCookie, - cookie_line); - - size_t n_partitioned_cookies = 0; - - // TODO(crbug.com/1031664): Reduce the number of times the cookie list - // is iterated over. Get metrics for every cookie which is included. - for (const auto& c : maybe_included_cookies) { - bool request_is_secure = request_->url().SchemeIsCryptographic(); - net::CookieSourceScheme cookie_scheme = c.cookie.SourceScheme(); - CookieRequestScheme cookie_request_schemes; - - switch (cookie_scheme) { - case net::CookieSourceScheme::kSecure: - cookie_request_schemes = - request_is_secure - ? CookieRequestScheme::kSecureSetSecureRequest - : CookieRequestScheme::kSecureSetNonsecureRequest; - break; - - case net::CookieSourceScheme::kNonSecure: - cookie_request_schemes = - request_is_secure - ? CookieRequestScheme::kNonsecureSetSecureRequest - : CookieRequestScheme::kNonsecureSetNonsecureRequest; - break; - - case net::CookieSourceScheme::kUnset: - cookie_request_schemes = CookieRequestScheme::kUnsetCookieScheme; - break; - } - - UMA_HISTOGRAM_ENUMERATION("Cookie.CookieSchemeRequestScheme", - cookie_request_schemes); - if (c.cookie.IsPartitioned()) - ++n_partitioned_cookies; - } - - if (IsPartitionedCookiesEnabled()) { - base::UmaHistogramCounts100("Cookie.PartitionedCookiesInRequest", - n_partitioned_cookies); - } - } - } - - CookieAccessResultList maybe_sent_cookies = std::move(excluded_cookies); - maybe_sent_cookies.insert( - maybe_sent_cookies.end(), - std::make_move_iterator(maybe_included_cookies.begin()), - std::make_move_iterator(maybe_included_cookies.end())); - maybe_included_cookies.clear(); - - if (request_->net_log().IsCapturing()) { - for (const auto& cookie_with_access_result : maybe_sent_cookies) { - request_->net_log().AddEvent( - NetLogEventType::COOKIE_INCLUSION_STATUS, - [&](NetLogCaptureMode capture_mode) { - return CookieInclusionStatusNetLogParams( - "send", cookie_with_access_result.cookie.Name(), - cookie_with_access_result.cookie.Domain(), - cookie_with_access_result.cookie.Path(), - cookie_with_access_result.access_result.status, capture_mode); - }); - } - } - - request_->set_maybe_sent_cookies(std::move(maybe_sent_cookies)); - - StartTransaction(); +// 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; } -void URLRequestHttpJob::AnnotateAndMoveUserBlockedCookies( - CookieAccessResultList& maybe_included_cookies, - CookieAccessResultList& excluded_cookies) const { - DCHECK(request_info_.privacy_mode == PrivacyMode::PRIVACY_MODE_DISABLED || - (request_info_.privacy_mode == - PrivacyMode::PRIVACY_MODE_ENABLED_PARTITIONED_STATE_ALLOWED && - base::ranges::all_of(maybe_included_cookies, - [](const CookieWithAccessResult& el) { - return el.cookie.IsPartitioned(); - }))) - << request_info_.privacy_mode; - - bool can_get_cookies = URLRequest::DefaultCanUseCookies(); - if (request()->network_delegate()) { - can_get_cookies = - request()->network_delegate()->AnnotateAndMoveUserBlockedCookies( - *request(), maybe_included_cookies, excluded_cookies, - /*allowed_from_caller=*/true); - } - - if (!can_get_cookies) { - request()->net_log().AddEvent( - NetLogEventType::COOKIE_GET_BLOCKED_BY_NETWORK_DELEGATE); - } +// 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(); } -void URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete(int result) { - DCHECK(set_cookie_access_result_list_.empty()); - // TODO(crbug.com/1186863): Turn this CHECK into DCHECK once the investigation - // is done. - CHECK_EQ(0, num_cookie_lines_left_); +// 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*"); - // End of the call started in OnStartCompleted. - OnCallToDelegateComplete(); - - if (result != OK) { - request_->net_log().AddEventWithStringParams(NetLogEventType::CANCELLED, - "source", "delegate"); - NotifyStartError(result); - return; - } - - CookieStore* cookie_store = request_->context()->cookie_store(); - - if ((request_info_.load_flags & LOAD_DO_NOT_SAVE_COOKIES) || !cookie_store) { - NotifyHeadersComplete(); - return; - } - - base::Time response_date; - absl::optional server_time = absl::nullopt; - if (GetResponseHeaders()->GetDateValue(&response_date)) - server_time = absl::make_optional(response_date); - - bool force_ignore_site_for_cookies = - request_->force_ignore_site_for_cookies(); - if (cookie_store->cookie_access_delegate() && - cookie_store->cookie_access_delegate()->ShouldIgnoreSameSiteRestrictions( - request_->url(), request_->site_for_cookies())) { - force_ignore_site_for_cookies = true; - } - bool is_main_frame_navigation = - IsolationInfo::RequestType::kMainFrame == - request_->isolation_info().request_type() || - request_->force_main_frame_for_same_site_cookies(); - CookieOptions::SameSiteCookieContext same_site_context = - net::cookie_util::ComputeSameSiteContextForResponse( - request_->url_chain(), request_->site_for_cookies(), - request_->initiator(), is_main_frame_navigation, - force_ignore_site_for_cookies); - - bool is_in_nontrivial_first_party_set = - first_party_set_metadata_.frame_owner().has_value(); - CookieOptions options = CreateCookieOptions( - same_site_context, first_party_set_metadata_.context(), - request_->isolation_info(), is_in_nontrivial_first_party_set); - - UMA_HISTOGRAM_ENUMERATION( - "Cookie.FirstPartySetsContextType.HTTP.Write", - first_party_set_metadata_.first_party_sets_context_type()); - - // Set all cookies, without waiting for them to be set. Any subsequent - // read will see the combined result of all cookie operation. - const base::StringPiece name("Set-Cookie"); - std::string cookie_string; - size_t iter = 0; - HttpResponseHeaders* headers = GetResponseHeaders(); - - // NotifyHeadersComplete needs to be called once and only once after the - // list has been fully processed, and it can either be called in the - // callback or after the loop is called, depending on how the last element - // was handled. |num_cookie_lines_left_| keeps track of how many async - // callbacks are currently out (starting from 1 to make sure the loop runs - // all the way through before trying to exit). If there are any callbacks - // still waiting when the loop ends, then NotifyHeadersComplete will be - // called when it reaches 0 in the callback itself. - num_cookie_lines_left_ = 1; - while (headers->EnumerateHeader(&iter, name, &cookie_string)) { - CookieInclusionStatus returned_status; - - num_cookie_lines_left_++; - - std::unique_ptr cookie = net::CanonicalCookie::Create( - request_->url(), cookie_string, base::Time::Now(), server_time, - cookie_partition_key_.value(), &returned_status); - - absl::optional cookie_to_return = absl::nullopt; - if (returned_status.IsInclude()) { - DCHECK(cookie); - // Make a copy of the cookie if we successfully made one. - cookie_to_return = *cookie; - } - if (cookie && !CanSetCookie(*cookie, &options)) { - returned_status.AddExclusionReason( - CookieInclusionStatus::EXCLUDE_USER_PREFERENCES); - } - if (!returned_status.IsInclude()) { - OnSetCookieResult(options, cookie_to_return, std::move(cookie_string), - CookieAccessResult(returned_status)); - continue; - } - CookieAccessResult cookie_access_result(returned_status); - cookie_store->SetCanonicalCookieAsync( - std::move(cookie), request_->url(), options, - base::BindOnce(&URLRequestHttpJob::OnSetCookieResult, - weak_factory_.GetWeakPtr(), options, cookie_to_return, - cookie_string), - &cookie_access_result); - } - // Removing the 1 that |num_cookie_lines_left| started with, signifing that - // loop has been exited. - num_cookie_lines_left_--; - - if (num_cookie_lines_left_ == 0) - NotifyHeadersComplete(); + 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; } -void URLRequestHttpJob::OnSetCookieResult( - const CookieOptions& options, - absl::optional cookie, - std::string cookie_string, - CookieAccessResult access_result) { - if (request_->net_log().IsCapturing()) { - request_->net_log().AddEvent(NetLogEventType::COOKIE_INCLUSION_STATUS, - [&](NetLogCaptureMode capture_mode) { - return CookieInclusionStatusNetLogParams( - "store", - cookie ? cookie.value().Name() : "", - cookie ? cookie.value().Domain() : "", - cookie ? cookie.value().Path() : "", - access_result.status, capture_mode); - }); - } - - set_cookie_access_result_list_.emplace_back( - std::move(cookie), std::move(cookie_string), access_result); - - num_cookie_lines_left_--; - - // If all the cookie lines have been handled, |set_cookie_access_result_list_| - // now reflects the result of all Set-Cookie lines, and the request can be - // continued. - if (num_cookie_lines_left_ == 0) - NotifyHeadersComplete(); +bool IsModeEncoding(VaapiWrapper::CodecMode mode) { + return mode == VaapiWrapper::CodecMode::kEncodeConstantBitrate || + mode == VaapiWrapper::CodecMode::kEncodeConstantQuantizationParameter; } -void URLRequestHttpJob::ProcessStrictTransportSecurityHeader() { - DCHECK(response_info_); - TransportSecurityState* security_state = - request_->context()->transport_security_state(); - const SSLInfo& ssl_info = response_info_->ssl_info; - - // Only accept HSTS headers on HTTPS connections that have no - // certificate errors. - if (!ssl_info.is_valid() || IsCertStatusError(ssl_info.cert_status) || - !security_state) { - return; - } - - // Don't accept HSTS headers when the hostname is an IP address. - if (request_info_.url.HostIsIPAddress()) - return; - - // http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec: - // - // If a UA receives more than one STS header field in a HTTP response - // message over secure transport, then the UA MUST process only the - // first such header field. - HttpResponseHeaders* headers = GetResponseHeaders(); - std::string value; - if (headers->EnumerateHeader(nullptr, "Strict-Transport-Security", &value)) - security_state->AddHSTSHeader(request_info_.url.host(), value); -} - -void URLRequestHttpJob::ProcessExpectCTHeader() { - DCHECK(response_info_); - TransportSecurityState* security_state = - request_->context()->transport_security_state(); - const SSLInfo& ssl_info = response_info_->ssl_info; - - // Only accept Expect CT headers on HTTPS connections that have no - // certificate errors. - if (!ssl_info.is_valid() || IsCertStatusError(ssl_info.cert_status) || - !security_state) { - return; - } - - HttpResponseHeaders* headers = GetResponseHeaders(); - std::string value; - bool has_expect_ct_header = headers->GetNormalizedHeader("Expect-CT", &value); - base::UmaHistogramBoolean("Net.ExpectCT.HeaderPresentOnResponse", - has_expect_ct_header); - if (has_expect_ct_header) { - security_state->ProcessExpectCTHeader( - value, HostPortPair::FromURL(request_info_.url), ssl_info, - request_->isolation_info().network_isolation_key()); - } -} - -void URLRequestHttpJob::OnStartCompleted(int result) { - TRACE_EVENT0(NetTracingCategory(), "URLRequestHttpJob::OnStartCompleted"); - RecordTimer(); - - // If the job is done (due to cancellation), can just ignore this - // notification. - if (done_) - return; - - receive_headers_end_ = base::TimeTicks::Now(); - - const URLRequestContext* context = request_->context(); - - if (transaction_ && transaction_->GetResponseInfo()) { - const SSLInfo& ssl_info = transaction_->GetResponseInfo()->ssl_info; - if (!IsCertificateError(result)) { - LogTrustAnchor(ssl_info.public_key_hashes); - } - } - - if (transaction_ && transaction_->GetResponseInfo()) { - SetProxyServer(transaction_->GetResponseInfo()->proxy_server); - } - - if (result == OK) { - scoped_refptr headers = GetResponseHeaders(); - - NetworkDelegate* network_delegate = request()->network_delegate(); - if (network_delegate) { - // Note that |this| may not be deleted until - // |URLRequestHttpJob::OnHeadersReceivedCallback()| or - // |NetworkDelegate::URLRequestDestroyed()| has been called. - OnCallToDelegate(NetLogEventType::NETWORK_DELEGATE_HEADERS_RECEIVED); - preserve_fragment_on_redirect_url_ = absl::nullopt; - IPEndPoint endpoint; - if (transaction_) - transaction_->GetRemoteEndpoint(&endpoint); - // The NetworkDelegate must watch for OnRequestDestroyed and not modify - // any of the arguments after it's called. - // TODO(mattm): change the API to remove the out-params and take the - // results as params of the callback. - int error = network_delegate->NotifyHeadersReceived( - request_, - base::BindOnce(&URLRequestHttpJob::OnHeadersReceivedCallback, - weak_factory_.GetWeakPtr()), - headers.get(), &override_response_headers_, endpoint, - &preserve_fragment_on_redirect_url_); - if (error != OK) { - if (error == ERR_IO_PENDING) { - awaiting_callback_ = true; - } else { - request_->net_log().AddEventWithStringParams( - NetLogEventType::CANCELLED, "source", "delegate"); - OnCallToDelegateComplete(); - NotifyStartError(error); - } - return; - } - } - - SaveCookiesAndNotifyHeadersComplete(OK); - } else if (IsCertificateError(result)) { - // We encountered an SSL certificate error. - // Maybe overridable, maybe not. Ask the delegate to decide. - TransportSecurityState* state = context->transport_security_state(); - NotifySSLCertificateError( - result, transaction_->GetResponseInfo()->ssl_info, - state->ShouldSSLErrorsBeFatal(request_info_.url.host()) && - result != ERR_CERT_KNOWN_INTERCEPTION_BLOCKED); - } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) { - NotifyCertificateRequested( - transaction_->GetResponseInfo()->cert_request_info.get()); - } else if (result == ERR_DNS_NAME_HTTPS_ONLY) { - // If DNS indicated the name is HTTPS-only, synthesize a redirect to either - // HTTPS or WSS. - DCHECK(features::kUseDnsHttpsSvcbHttpUpgrade.Get()); - DCHECK(!request_->url().SchemeIsCryptographic()); - - base::Time request_time = - transaction_ && transaction_->GetResponseInfo() - ? transaction_->GetResponseInfo()->request_time - : base::Time::Now(); - DestroyTransaction(); - override_response_info_ = std::make_unique(); - override_response_info_->request_time = request_time; - - override_response_info_->headers = RedirectUtil::SynthesizeRedirectHeaders( - UpgradeSchemeToCryptographic(request_->url()), - RedirectUtil::ResponseCode::REDIRECT_307_TEMPORARY_REDIRECT, "DNS", - request_->extra_request_headers()); - NetLogResponseHeaders( - request_->net_log(), - NetLogEventType::URL_REQUEST_FAKE_RESPONSE_HEADERS_CREATED, - override_response_info_->headers.get()); - - NotifyHeadersComplete(); - } else { - // Even on an error, there may be useful information in the response - // info (e.g. whether there's a cached copy). - if (transaction_.get()) - response_info_ = transaction_->GetResponseInfo(); - NotifyStartError(result); - } -} - -void URLRequestHttpJob::OnHeadersReceivedCallback(int result) { - // The request should not have been cancelled or have already completed. - DCHECK(!is_done()); - - awaiting_callback_ = false; - - SaveCookiesAndNotifyHeadersComplete(result); -} - -void URLRequestHttpJob::OnReadCompleted(int result) { - TRACE_EVENT0(NetTracingCategory(), "URLRequestHttpJob::OnReadCompleted"); - read_in_progress_ = false; - - DCHECK_NE(ERR_IO_PENDING, result); - - if (ShouldFixMismatchedContentLength(result)) - result = OK; - - // EOF or error, done with this job. - if (result <= 0) - DoneWithRequest(FINISHED); - - ReadRawDataComplete(result); -} - -void URLRequestHttpJob::RestartTransactionWithAuth( - const AuthCredentials& credentials) { - DCHECK(!override_response_info_); - - auth_credentials_ = credentials; - - // These will be reset in OnStartCompleted. - response_info_ = nullptr; - override_response_headers_ = nullptr; // See https://crbug.com/801237. - receive_headers_end_ = base::TimeTicks(); - - ResetTimer(); - - // Update the cookies, since the cookie store may have been updated from the - // headers in the 401/407. Since cookies were already appended to - // extra_headers, we need to strip them out before adding them again. - request_info_.extra_headers.RemoveHeader(HttpRequestHeaders::kCookie); - - // TODO(https://crbug.com/968327/): This is weird, as all other clearing is at - // the URLRequest layer. Should this call into URLRequest so it can share - // logic at that layer with SetAuth()? - request_->set_maybe_sent_cookies({}); - request_->set_maybe_stored_cookies({}); - - if (ShouldAddCookieHeader()) { - // Since `request_->isolation_info()` hasn't changed, we don't need to - // recompute the cookie partition key. - AddCookieHeaderAndStart(); - } else { - StartTransaction(); - } -} - -void URLRequestHttpJob::SetUpload(UploadDataStream* upload) { - DCHECK(!transaction_.get() && !override_response_info_) - << "cannot change once started"; - request_info_.upload_data_stream = upload; -} - -void URLRequestHttpJob::SetExtraRequestHeaders( - const HttpRequestHeaders& headers) { - DCHECK(!transaction_.get() && !override_response_info_) - << "cannot change once started"; - request_info_.extra_headers.CopyFrom(headers); -} - -LoadState URLRequestHttpJob::GetLoadState() const { - return transaction_.get() ? - transaction_->GetLoadState() : LOAD_STATE_IDLE; -} - -bool URLRequestHttpJob::GetMimeType(std::string* mime_type) const { - DCHECK(transaction_.get() || override_response_info_); - - if (!response_info_) - return false; - - HttpResponseHeaders* headers = GetResponseHeaders(); - if (!headers) - return false; - return headers->GetMimeType(mime_type); -} - -bool URLRequestHttpJob::GetCharset(std::string* charset) { - DCHECK(transaction_.get() || override_response_info_); - - if (!response_info_) - return false; - - return GetResponseHeaders()->GetCharset(charset); -} - -void URLRequestHttpJob::GetResponseInfo(HttpResponseInfo* info) { - if (override_response_info_) { - DCHECK(!transaction_.get()); - *info = *override_response_info_; - return; - } - - if (response_info_) { - DCHECK(transaction_.get()); - - *info = *response_info_; - if (override_response_headers_.get()) - info->headers = override_response_headers_; - } -} - -void URLRequestHttpJob::GetLoadTimingInfo( - LoadTimingInfo* load_timing_info) const { - // If haven't made it far enough to receive any headers, don't return - // anything. This makes for more consistent behavior in the case of errors. - if (!transaction_ || receive_headers_end_.is_null()) - return; - if (transaction_->GetLoadTimingInfo(load_timing_info)) - load_timing_info->receive_headers_end = receive_headers_end_; -} - -bool URLRequestHttpJob::GetTransactionRemoteEndpoint( - IPEndPoint* endpoint) const { - if (!transaction_) - return false; - - return transaction_->GetRemoteEndpoint(endpoint); -} - -int URLRequestHttpJob::GetResponseCode() const { - DCHECK(transaction_.get()); - - if (!response_info_) - return -1; - - return GetResponseHeaders()->response_code(); -} - -void URLRequestHttpJob::PopulateNetErrorDetails( - NetErrorDetails* details) const { - if (!transaction_) - return; - return transaction_->PopulateNetErrorDetails(details); -} - -std::unique_ptr URLRequestHttpJob::SetUpSourceStream() { - DCHECK(transaction_.get()); - if (!response_info_) - return nullptr; - - std::unique_ptr upstream = URLRequestJob::SetUpSourceStream(); - HttpResponseHeaders* headers = GetResponseHeaders(); - std::vector types; - size_t iter = 0; - for (std::string type; - headers->EnumerateHeader(&iter, "Content-Encoding", &type);) { - SourceStream::SourceType source_type = - FilterSourceStream::ParseEncodingType(type); - switch (source_type) { - case SourceStream::TYPE_BROTLI: - case SourceStream::TYPE_DEFLATE: - case SourceStream::TYPE_GZIP: - if (request_->accepted_stream_types() && - !request_->accepted_stream_types()->contains(source_type)) { - // If the source type is disabled, we treat it - // in the same way as SourceStream::TYPE_UNKNOWN. - return upstream; - } - types.push_back(source_type); - break; - case SourceStream::TYPE_NONE: - // Identity encoding type. Pass through raw response body. - return upstream; - case SourceStream::TYPE_UNKNOWN: - // Unknown encoding type. Pass through raw response body. - // Request will not be canceled; though - // it is expected that user will see malformed / garbage response. - return upstream; - } - } - - for (auto r_iter = types.rbegin(); r_iter != types.rend(); ++r_iter) { - std::unique_ptr downstream; - SourceStream::SourceType type = *r_iter; - switch (type) { - case SourceStream::TYPE_BROTLI: - downstream = CreateBrotliSourceStream(std::move(upstream)); - break; - case SourceStream::TYPE_GZIP: - case SourceStream::TYPE_DEFLATE: - downstream = GzipSourceStream::Create(std::move(upstream), type); - break; - case SourceStream::TYPE_NONE: - case SourceStream::TYPE_UNKNOWN: - NOTREACHED(); - return nullptr; - } - if (downstream == nullptr) - return nullptr; - upstream = std::move(downstream); - } - - return upstream; -} - -bool URLRequestHttpJob::CopyFragmentOnRedirect(const GURL& location) const { - // Allow modification of reference fragments by default, unless - // |preserve_fragment_on_redirect_url_| is set and equal to the redirect URL. - return !preserve_fragment_on_redirect_url_.has_value() || - preserve_fragment_on_redirect_url_ != location; -} - -bool URLRequestHttpJob::IsSafeRedirect(const GURL& location) { - // HTTP is always safe. - // TODO(pauljensen): Remove once crbug.com/146591 is fixed. - if (location.is_valid() && - (location.scheme() == "http" || location.scheme() == "https")) { +bool GetNV12VisibleWidthBytes(int visible_width, + uint32_t plane, + size_t* bytes) { + if (plane == 0) { + *bytes = base::checked_cast(visible_width); return true; } - // Query URLRequestJobFactory as to whether |location| would be safe to - // redirect to. - return request_->context()->job_factory() && - request_->context()->job_factory()->IsSafeRedirectTarget(location); + + *bytes = base::checked_cast(visible_width); + return visible_width % 2 == 0 || + base::CheckAdd(visible_width, 1).AssignIfValid(bytes); } -bool URLRequestHttpJob::NeedsAuth() { - int code = GetResponseCode(); - if (code == -1) +// 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() { + constexpr char kRenderNodeFilePattern[] = "/dev/dri/renderD%d"; + // This loop ends on either the first card that does not exist or the first + // render node that is not vgem. + for (int i = 128;; i++) { + base::FilePath dev_path(FILE_PATH_LITERAL( + base::StringPrintf(kRenderNodeFilePattern, i).c_str())); + base::File drm_file = + base::File(dev_path, base::File::FLAG_OPEN | base::File::FLAG_READ | + base::File::FLAG_WRITE); + + 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); + + if (!drm_file.IsValid()) + return; + // Skip the virtual graphics memory manager device. + drmVersionPtr version = drmGetVersion(drm_file.GetPlatformFile()); + if (!version) + continue; + std::string version_name( + version->name, + base::checked_cast(version->name_len)); + drmFreeVersion(version); + if (base::LowerCaseEqualsASCII(version_name, "vgem")) + continue; + VADisplayState::Get()->SetDrmFd(drm_file.GetPlatformFile()); + return; + } +} + +VADisplayState::VADisplayState() + : refcount_(0), va_display_(nullptr), va_initialized_(false) {} + +bool VADisplayState::Initialize() { + base::AutoLock auto_lock(va_lock_); + +#if defined(USE_OZONE) && BUILDFLAG(IS_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; - // Check if we need either Proxy or WWW Authentication. This could happen - // because we either provided no auth info, or provided incorrect info. - switch (code) { - case 407: - if (proxy_auth_state_ == AUTH_STATE_CANCELED) - return false; - proxy_auth_state_ = AUTH_STATE_NEED_AUTH; - return true; - case 401: - if (server_auth_state_ == AUTH_STATE_CANCELED) - return false; - server_auth_state_ = AUTH_STATE_NEED_AUTH; - return true; - } - 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; } -std::unique_ptr URLRequestHttpJob::GetAuthChallengeInfo() { - DCHECK(transaction_.get()); - DCHECK(response_info_); +#if BUILDFLAG(USE_VAAPI_X11) - // sanity checks: - DCHECK(proxy_auth_state_ == AUTH_STATE_NEED_AUTH || - server_auth_state_ == AUTH_STATE_NEED_AUTH); - DCHECK((GetResponseHeaders()->response_code() == HTTP_UNAUTHORIZED) || - (GetResponseHeaders()->response_code() == - HTTP_PROXY_AUTHENTICATION_REQUIRED)); +absl::optional GetVADisplayStateX11(const base::ScopedFD& drm_fd) { + switch (gl::GetGLImplementation()) { + case gl::kGLImplementationEGLGLES2: + return vaGetDisplayDRM(drm_fd.get()); - if (!response_info_->auth_challenge.has_value()) - return nullptr; - return std::make_unique( - response_info_->auth_challenge.value()); -} + case gl::kGLImplementationNone: -void URLRequestHttpJob::SetAuth(const AuthCredentials& credentials) { - DCHECK(transaction_.get()); - - // Proxy gets set first, then WWW. - if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) { - proxy_auth_state_ = AUTH_STATE_HAVE_AUTH; - } else { - DCHECK_EQ(server_auth_state_, AUTH_STATE_NEED_AUTH); - server_auth_state_ = AUTH_STATE_HAVE_AUTH; - } - - RestartTransactionWithAuth(credentials); -} - -void URLRequestHttpJob::CancelAuth() { - if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) { - proxy_auth_state_ = AUTH_STATE_CANCELED; - } else { - DCHECK_EQ(server_auth_state_, AUTH_STATE_NEED_AUTH); - server_auth_state_ = AUTH_STATE_CANCELED; - } - - // The above lines should ensure this is the case. - DCHECK(!NeedsAuth()); - - // Let the consumer read the HTTP error page. NeedsAuth() should now return - // false, so NotifyHeadersComplete() should not request auth from the client - // again. - // - // Have to do this via PostTask to avoid re-entrantly calling into the - // consumer. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(&URLRequestHttpJob::NotifyFinalHeadersReceived, - weak_factory_.GetWeakPtr())); -} - -void URLRequestHttpJob::ContinueWithCertificate( - scoped_refptr client_cert, - scoped_refptr client_private_key) { - DCHECK(transaction_); - - DCHECK(!response_info_) << "should not have a response yet"; - DCHECK(!override_response_headers_); - receive_headers_end_ = base::TimeTicks(); - - ResetTimer(); - - int rv = transaction_->RestartWithCertificate( - std::move(client_cert), std::move(client_private_key), - base::BindOnce(&URLRequestHttpJob::OnStartCompleted, - base::Unretained(this))); - if (rv == ERR_IO_PENDING) - return; - - // The transaction started synchronously, but we need to notify the - // URLRequest delegate via the message loop. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(&URLRequestHttpJob::OnStartCompleted, - weak_factory_.GetWeakPtr(), rv)); -} - -void URLRequestHttpJob::ContinueDespiteLastError() { - // If the transaction was destroyed, then the job was cancelled. - if (!transaction_.get()) - return; - - DCHECK(!response_info_) << "should not have a response yet"; - DCHECK(!override_response_headers_); - receive_headers_end_ = base::TimeTicks(); - - ResetTimer(); - - int rv = transaction_->RestartIgnoringLastError(base::BindOnce( - &URLRequestHttpJob::OnStartCompleted, base::Unretained(this))); - if (rv == ERR_IO_PENDING) - return; - - // The transaction started synchronously, but we need to notify the - // URLRequest delegate via the message loop. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(&URLRequestHttpJob::OnStartCompleted, - weak_factory_.GetWeakPtr(), rv)); -} - -bool URLRequestHttpJob::ShouldFixMismatchedContentLength(int rv) const { - // Some servers send the body compressed, but specify the content length as - // the uncompressed size. Although this violates the HTTP spec we want to - // support it (as IE and FireFox do), but *only* for an exact match. - // See http://crbug.com/79694. - if (rv == ERR_CONTENT_LENGTH_MISMATCH || - rv == ERR_INCOMPLETE_CHUNKED_ENCODING) { - if (request_->response_headers()) { - int64_t expected_length = - request_->response_headers()->GetContentLength(); - VLOG(1) << __func__ << "() \"" << request_->url().spec() << "\"" - << " content-length = " << expected_length - << " pre total = " << prefilter_bytes_read() - << " post total = " << postfilter_bytes_read(); - if (postfilter_bytes_read() == expected_length) { - // Clear the error. - return true; - } + 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; } - return false; } -int URLRequestHttpJob::ReadRawData(IOBuffer* buf, int buf_size) { - DCHECK_NE(buf_size, 0); - DCHECK(!read_in_progress_); +#else - int rv = - transaction_->Read(buf, buf_size, - base::BindOnce(&URLRequestHttpJob::OnReadCompleted, - base::Unretained(this))); - - if (ShouldFixMismatchedContentLength(rv)) - rv = OK; - - if (rv == 0 || (rv < 0 && rv != ERR_IO_PENDING)) - DoneWithRequest(FINISHED); - - if (rv == ERR_IO_PENDING) - read_in_progress_ = true; - - return rv; -} - -int64_t URLRequestHttpJob::GetTotalReceivedBytes() const { - int64_t total_received_bytes = - total_received_bytes_from_previous_transactions_; - if (transaction_) - total_received_bytes += transaction_->GetTotalReceivedBytes(); - return total_received_bytes; -} - -int64_t URLRequestHttpJob::GetTotalSentBytes() const { - int64_t total_sent_bytes = total_sent_bytes_from_previous_transactions_; - if (transaction_) - total_sent_bytes += transaction_->GetTotalSentBytes(); - return total_sent_bytes; -} - -void URLRequestHttpJob::DoneReading() { - if (transaction_) { - transaction_->DoneReading(); +absl::optional GetVADisplayState(const base::ScopedFD& drm_fd) { + switch (gl::GetGLImplementation()) { + case gl::kGLImplementationEGLGLES2: + case gl::kGLImplementationEGLANGLE: + case gl::kGLImplementationNone: + return vaGetDisplayDRM(drm_fd.get()); + default: + LOG(WARNING) << "VAAPI video acceleration not available for " + << gl::GetGLImplementationGLName( + gl::GetGLImplementationParts()); + return absl::nullopt; } - DoneWithRequest(FINISHED); } -void URLRequestHttpJob::DoneReadingRedirectResponse() { - if (transaction_) { - DCHECK(!override_response_info_); - if (transaction_->GetResponseInfo()->headers->IsRedirect(nullptr)) { - // If the original headers indicate a redirect, go ahead and cache the - // response, even if the |override_response_headers_| are a redirect to - // another location. - transaction_->DoneReading(); +#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 { - // Otherwise, |override_response_headers_| must be non-NULL and contain - // bogus headers indicating a redirect. - DCHECK(override_response_headers_.get()); - DCHECK(override_response_headers_->IsRedirect(nullptr)); - transaction_->StopCaching(); + required_attribs->push_back( + {VAConfigAttribEncPackedHeaders, VA_ENC_PACKED_HEADER_NONE}); } } - DoneWithRequest(FINISHED); + return true; } -IPEndPoint URLRequestHttpJob::GetResponseRemoteEndpoint() const { - return response_info_ ? response_info_->remote_endpoint : IPEndPoint(); -} +// 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; -void URLRequestHttpJob::RecordTimer() { - if (request_creation_time_.is_null()) { - NOTREACHED() - << "The same transaction shouldn't start twice without new timing."; - return; + 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; } - - base::TimeDelta to_start = base::Time::Now() - request_creation_time_; - request_creation_time_ = base::Time(); - - UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpTimeToFirstByte", to_start); - - // Record additional metrics for TLS 1.3 servers. This is to help measure the - // impact of enabling 0-RTT. The effects of 0-RTT will be muted because not - // all TLS 1.3 servers enable 0-RTT, and only the first round-trip on a - // connection makes use of 0-RTT. However, 0-RTT can affect how requests are - // bound to connections and which connections offer resumption. We look at all - // TLS 1.3 responses for an apples-to-apples comparison. - // - // Additionally record metrics for Google hosts. Most Google hosts are known - // to implement 0-RTT, so this gives more targeted metrics as we initially - // roll out client support. - // - // TODO(https://crbug.com/641225): Remove these metrics after launching 0-RTT. - if (transaction_ && transaction_->GetResponseInfo() && - IsTLS13OverTCP(*transaction_->GetResponseInfo())) { - base::UmaHistogramMediumTimes("Net.HttpTimeToFirstByte.TLS13", to_start); - if (HasGoogleHost(request()->url())) { - base::UmaHistogramMediumTimes("Net.HttpTimeToFirstByte.TLS13.Google", - to_start); + 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; } -void URLRequestHttpJob::ResetTimer() { - if (!request_creation_time_.is_null()) { - NOTREACHED() - << "The timer was reset before it was recorded."; - return; - } - request_creation_time_ = base::Time::Now(); +// 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; } -void URLRequestHttpJob::SetRequestHeadersCallback( - RequestHeadersCallback callback) { - DCHECK(!transaction_); - DCHECK(!request_headers_callback_); - request_headers_callback_ = std::move(callback); +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; } -void URLRequestHttpJob::SetEarlyResponseHeadersCallback( - ResponseHeadersCallback callback) { - DCHECK(!transaction_); - DCHECK(!early_response_headers_callback_); - early_response_headers_callback_ = std::move(callback); -} - -void URLRequestHttpJob::SetResponseHeadersCallback( - ResponseHeadersCallback callback) { - DCHECK(!transaction_); - DCHECK(!response_headers_callback_); - response_headers_callback_ = std::move(callback); -} - -void URLRequestHttpJob::RecordCompletionHistograms(CompletionCause reason) { - if (start_time_.is_null()) +VASupportedProfiles::VASupportedProfiles() + : report_error_to_uma_cb_(base::DoNothing()) { + VADisplayState* display_state = VADisplayState::Get(); + if (!display_state->Initialize()) return; - base::TimeDelta total_time = base::TimeTicks::Now() - start_time_; - UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTime", total_time); + VADisplay va_display = display_state->va_display(); + DCHECK(va_display) << "VADisplayState hasn't been properly Initialize()d"; - if (reason == FINISHED) { - UmaHistogramTimes( - base::StringPrintf("Net.HttpJob.TotalTimeSuccess.Priority%d", - request()->priority()), - total_time); - UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeSuccess", total_time); - } else { - UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeCancel", total_time); + base::Lock* va_lock = display_state->va_lock(); + if (!UseGlobalVaapiLock(display_state->implementation_type())) { + va_lock = nullptr; } - if (response_info_) { - // QUIC (by default) supports https scheme only, thus track https URLs only - // for QUIC. - bool is_https_google = request() && request()->url().SchemeIs("https") && - HasGoogleHost(request()->url()); - bool used_quic = response_info_->DidUseQuic(); - if (is_https_google) { - if (used_quic) { - UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpJob.TotalTime.Secure.Quic", - total_time); + 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; } - } - // Record metrics for TLS 1.3 to measure the impact of 0-RTT. See comment in - // RecordTimer(). - // - // TODO(https://crbug.com/641225): Remove these metrics after launching - // 0-RTT. - if (IsTLS13OverTCP(*response_info_)) { - base::UmaHistogramTimes("Net.HttpJob.TotalTime.TLS13", total_time); - if (is_https_google) { - base::UmaHistogramTimes("Net.HttpJob.TotalTime.TLS13.Google", - total_time); - } - } + const std::vector supported_entrypoints = + GetEntryPointsForProfile(va_lock, va_display, mode, va_profile); - UMA_HISTOGRAM_CUSTOM_COUNTS("Net.HttpJob.PrefilterBytesRead", - prefilter_bytes_read(), 1, 50000000, 50); - if (response_info_->was_cached) { - UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeCached", total_time); - UMA_HISTOGRAM_CUSTOM_COUNTS("Net.HttpJob.PrefilterBytesRead.Cache", - prefilter_bytes_read(), 1, 50000000, 50); - - if (response_info_->unused_since_prefetch) - UMA_HISTOGRAM_COUNTS_1M("Net.Prefetch.HitBytes", - prefilter_bytes_read()); - } else { - UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeNotCached", total_time); - UMA_HISTOGRAM_CUSTOM_COUNTS("Net.HttpJob.PrefilterBytesRead.Net", - prefilter_bytes_read(), 1, 50000000, 50); - - if (request_info_.load_flags & LOAD_PREFETCH) { - UMA_HISTOGRAM_COUNTS_1M("Net.Prefetch.PrefilterBytesReadFromNetwork", - prefilter_bytes_read()); - } - if (is_https_google) { - if (used_quic) { - UMA_HISTOGRAM_MEDIUM_TIMES( - "Net.HttpJob.TotalTimeNotCached.Secure.Quic", total_time); - } else { - UMA_HISTOGRAM_MEDIUM_TIMES( - "Net.HttpJob.TotalTimeNotCached.Secure.NotQuic", total_time); + 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(); + } } - start_time_ = base::TimeTicks(); + // 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 URLRequestHttpJob::DoneWithRequest(CompletionCause reason) { - if (done_) +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, + 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; - done_ = true; - // Notify NetworkQualityEstimator. - NetworkQualityEstimator* network_quality_estimator = - request()->context()->network_quality_estimator(); - if (network_quality_estimator) { - network_quality_estimator->NotifyRequestCompleted(*request()); + // 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; } - RecordCompletionHistograms(reason); - request()->set_received_response_content_length(prefilter_bytes_read()); -} + { + base::AutoLockMaybe auto_lock(va_lock); + VADisplay va_display = display_state->va_display(); + DCHECK(va_display) << "VADisplayState hasn't been properly initialized"; -HttpResponseHeaders* URLRequestHttpJob::GetResponseHeaders() const { - if (override_response_info_) { - DCHECK(!transaction_.get()); - return override_response_info_->headers.get(); + if (!InitSupportedImageFormats_Locked(va_lock, va_display)) + LOG(ERROR) << "Failed to get supported image formats"; } - DCHECK(transaction_.get()); - DCHECK(transaction_->GetResponseInfo()); - - return override_response_headers_.get() ? - override_response_headers_.get() : - transaction_->GetResponseInfo()->headers.get(); + const VAStatus va_res = display_state->Deinitialize(); + VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVATerminate); } -void URLRequestHttpJob::NotifyURLRequestDestroyed() { - awaiting_callback_ = false; +bool VASupportedImageFormats::InitSupportedImageFormats_Locked( + const base::Lock* va_lock, + VADisplay va_display) { + MAYBE_ASSERT_ACQUIRED(va_lock); - // Notify NetworkQualityEstimator. - NetworkQualityEstimator* network_quality_estimator = - request()->context()->network_quality_estimator(); - if (network_quality_estimator) - network_quality_estimator->NotifyURLRequestDestroyed(*request()); + // 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 URLRequestHttpJob::ShouldAddCookieHeader() const { - // Read cookies whenever allow_credentials() is true, even if the PrivacyMode - // is being overridden by NetworkDelegate and will eventually block them, as - // blocked cookies still need to be logged in that case. - return request_->context()->cookie_store() && request_->allow_credentials(); +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; } -bool URLRequestHttpJob::IsPartitionedCookiesEnabled() const { - // Only valid to call this after we've computed the key. - DCHECK(cookie_partition_key_.has_value()); - return cookie_partition_key_.value().has_value(); +} // namespace + +NativePixmapAndSizeInfo::NativePixmapAndSizeInfo() = default; + +NativePixmapAndSizeInfo::~NativePixmapAndSizeInfo() = default; + +// static +VAImplementation VaapiWrapper::GetImplementationType() { + return VADisplayState::Get()->implementation_type(); } -} // namespace net +// 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) + 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 >= VP8PROFILE_MIN && media_profile <= VP8PROFILE_MAX) { + if (base::FeatureList::IsEnabled(kVaapiVp8TemporalLayerHWEncoding)) { + scalability_modes.push_back(SVCScalabilityMode::kL1T2); + scalability_modes.push_back(SVCScalabilityMode::kL1T3); + } + } + + 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(3) << __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 on Nvidia GPUs."; + 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); + + auto plane_fd = base::ScopedFD( + layer == 0 ? bo_fd.release() + : HANDLE_EINTR(dup(handle.planes[0].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, std::move(plane_fd)); + } + + 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, + absl::optional 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. + // |sync_surface_id| will be nullopt because it has been synced already. + // vaSyncSurface() is not executed in the case. + if (sync_surface_id && + 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(3) << __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