diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn new file mode 100644 index 00000000..5b5ed36f --- /dev/null +++ b/build/config/compiler/BUILD.gn @@ -0,0 +1,2507 @@ +# Copyright (c) 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/buildflag_header.gni") +import("//build/config/android/config.gni") +import("//build/config/c++/c++.gni") +import("//build/config/chrome_build.gni") +import("//build/config/chromecast_build.gni") +import("//build/config/chromeos/args.gni") +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/clang/clang.gni") +import("//build/config/compiler/compiler.gni") +import("//build/config/coverage/coverage.gni") +import("//build/config/dcheck_always_on.gni") +import("//build/config/gclient_args.gni") +import("//build/config/host_byteorder.gni") +import("//build/config/rust.gni") +import("//build/config/sanitizers/sanitizers.gni") +import("//build/config/ui.gni") +import("//build/toolchain/cc_wrapper.gni") +import("//build/toolchain/goma.gni") +import("//build/toolchain/rbe.gni") +import("//build/toolchain/toolchain.gni") +import("//build_overrides/build.gni") + +if (current_cpu == "arm" || current_cpu == "arm64") { + import("//build/config/arm.gni") +} +if (current_cpu == "mipsel" || current_cpu == "mips64el" || + current_cpu == "mips" || current_cpu == "mips64") { + import("//build/config/mips.gni") +} +if (current_cpu == "x64") { + import("//build/config/x64.gni") +} +if (is_mac) { + import("//build/config/apple/symbols.gni") +} +if (is_ios) { + import("//build/config/ios/ios_sdk.gni") +} +if (is_nacl) { + # To keep NaCl variables out of builds that don't include NaCl, all + # variables defined in nacl/config.gni referenced here should be protected by + # is_nacl conditions. + import("//build/config/nacl/config.gni") +} + +lld_path = "" +if (!is_clang) { + declare_args() { + # This allows overriding the location of lld. + lld_path = rebase_path("$clang_base_path/bin", root_build_dir) + } +} else { + # clang looks for lld next to it, no need for -B. + lld_path = "" +} + +declare_args() { + # Normally, Android builds are lightly optimized, even for debug builds, to + # keep binary size down. Setting this flag to true disables such optimization + android_full_debug = false + + # Compile in such a way as to make it possible for the profiler to unwind full + # stack frames. Setting this flag has a large effect on the performance of the + # generated code than just setting profiling, but gives the profiler more + # information to analyze. + # Requires profiling to be set to true. + enable_full_stack_frames_for_profiling = false + + # When we are going to use gold we need to find it. + # This is initialized below, after use_gold might have been overridden. + gold_path = "" + + # Enable fatal linker warnings. Building Chromium with certain versions + # of binutils can cause linker warning. + fatal_linker_warnings = true + + # Build with C++ RTTI enabled. Chromium builds without RTTI by default, + # but some sanitizers are known to require it, like CFI diagnostics + # and UBsan variants. + use_rtti = use_cfi_diag || is_ubsan_vptr || is_ubsan_security + + # AFDO (Automatic Feedback Directed Optimizer) is a form of profile-guided + # optimization that GCC supports. It used by ChromeOS in their official + # builds. To use it, set auto_profile_path to the path to a file containing + # the needed gcov profiling data. + auto_profile_path = "" + + # Allow projects that wish to stay on C++11 to override Chromium's default. + use_cxx11 = false + + # Path to an AFDO profile to use while building with clang, if any. Empty + # implies none. + clang_sample_profile_path = "" + + # Some configurations have default sample profiles. If this is true and + # clang_sample_profile_path is empty, we'll fall back to the default. + # + # We currently only have default profiles for Chromium in-tree, so we disable + # this by default for all downstream projects, since these profiles are likely + # nonsensical for said projects. + clang_use_default_sample_profile = + chrome_pgo_phase == 0 && build_with_chromium && is_official_build && + (is_android || chromeos_is_browser_only || is_chromecast) + + # This configuration is used to select a default profile in Chrome OS based on + # the microarchitectures we are using. This is only used if + # clang_use_default_sample_profile is true and clang_sample_profile_path is + # empty. + chromeos_afdo_platform = "atom" + + # Emit debug information for profiling wile building with clang. + clang_emit_debug_info_for_profiling = false + + # Turn this on to have the compiler output extra timing information. + compiler_timing = false + + # Turn this on to use ghash feature of lld for faster debug link on Windows. + # http://blog.llvm.org/2018/01/improving-link-time-on-windows-with.html + use_ghash = true + + # Whether to enable ThinLTO optimizations. Turning ThinLTO optimizations on + # can substantially increase link time and binary size, but they generally + # also make binaries a fair bit faster. + # + # TODO(gbiv): We disable optimizations by default on most platforms because + # the space overhead is too great. We should use some mixture of profiles and + # optimization settings to better tune the size increase. + thin_lto_enable_optimizations = + (is_chromeos_ash || is_android || is_win || is_linux) && is_official_build + + # Initialize all local variables with a pattern. This flag will fill + # uninitialized floating-point types (and 32-bit pointers) with 0xFF and the + # rest with 0xAA. This makes behavior of uninitialized memory bugs consistent, + # recognizable in the debugger, and crashes on memory accesses through + # uninitialized pointers. + # + # TODO(crbug.com/1131993): Enabling this when 'is_android' is true breaks + # content_shell_test_apk on both ARM and x86. + init_stack_vars = !is_android + + # This argument is to control whether enabling text section splitting in the + # final binary. When enabled, the separated text sections with prefix + # '.text.hot', '.text.unlikely', '.text.startup' and '.text.exit' will not be + # merged to '.text' section. This allows us to identify the hot code section + # ('.text.hot') in the binary which may be mlocked or mapped to huge page to + # reduce TLB misses which gives performance improvement on cpu usage. + # The gold linker by default has text section splitting enabled. + use_text_section_splitting = false + + # Token limits may not be accurate for build configs not covered by the CQ, + # so only enable them by default for mainstream build configs. + enable_wmax_tokens = + !is_official_build && !(is_component_build && !is_debug) && + ((is_mac && target_cpu == "x64" && !use_system_xcode) || + (is_linux && !is_chromeos && target_cpu == "x64") || + (is_win && target_cpu == "x86") || (is_win && target_cpu == "x64") || + (is_android && target_cpu == "arm") || + (is_android && target_cpu == "arm64")) + + # Turn off the --call-graph-profile-sort flag for lld by default. Enable + # selectively for targets where it's beneficial. + enable_call_graph_profile_sort = chrome_pgo_phase == 2 + + # Enable DWARF v5. + use_dwarf5 = false + + # Override this to put full paths to PDBs in Windows PE files. This helps + # windbg and Windows Performance Analyzer with finding the PDBs in some local- + # build scenarios. This is never needed for bots or official builds. Because + # this puts the output directory in the DLLs/EXEs it breaks build determinism. + # Bugs have been reported to the windbg/WPA teams and this workaround will be + # removed when they are fixed. + use_full_pdb_paths = false + + # Enable -H, which prints the include tree during compilation. + # For use by tools/clang/scripts/analyze_includes.py + show_includes = false +} + +declare_args() { + # C++11 may not be an option if Android test infrastructure is used. + use_cxx11_on_android = use_cxx11 +} + +declare_args() { + # Set to true to use icf, Identical Code Folding. + # + # icf=all is broken in older golds, see + # https://sourceware.org/bugzilla/show_bug.cgi?id=17704 + # chromeos binutils has been patched with the fix, so always use icf there. + # The bug only affects x86 and x64, so we can still use ICF when targeting + # other architectures. + # + # lld doesn't have the bug. + use_icf = + (is_posix || is_fuchsia) && !is_debug && !using_sanitizer && + !use_clang_coverage && !(is_android && use_order_profiling) && + (use_lld || (use_gold && (is_chromeos_ash || !(current_cpu == "x86" || + current_cpu == "x64")))) +} + +if (is_android || (is_chromeos_ash && is_chromeos_device)) { + # Set the path to use orderfile for linking Chrome + # Note that this is for using only one orderfile for linking + # the Chrome binary/library. + declare_args() { + chrome_orderfile_path = "" + + if (defined(default_chrome_orderfile)) { + # Allow downstream tools to set orderfile path with + # another variable. + chrome_orderfile_path = default_chrome_orderfile + } else if (is_chromeos_ash && is_chromeos_device) { + chrome_orderfile_path = "//chromeos/profiles/chromeos.orderfile.txt" + } + } +} + +assert(!(llvm_force_head_revision && use_goma), + "can't use goma with trunk clang") +assert(!(llvm_force_head_revision && use_remoteexec), + "can't use rbe with trunk clang") + +# default_include_dirs --------------------------------------------------------- +# +# This is a separate config so that third_party code (which would not use the +# source root and might have conflicting versions of some headers) can remove +# this and specify their own include paths. +config("default_include_dirs") { + include_dirs = [ + "//", + root_gen_dir, + ] +} + +# Compiler instrumentation can introduce dependencies in DSOs to symbols in +# the executable they are loaded into, so they are unresolved at link-time. +config("no_unresolved_symbols") { + if (!using_sanitizer && + (is_linux || is_chromeos || is_android || is_fuchsia)) { + ldflags = [ + "-Wl,-z,defs", + "-Wl,--as-needed", + ] + } +} + +# compiler --------------------------------------------------------------------- +# +# Base compiler configuration. +# +# See also "runtime_library" below for related stuff and a discussion about +# where stuff should go. Put warning related stuff in the "warnings" config. + +config("compiler") { + asmflags = [] + cflags = [] + cflags_c = [] + cflags_cc = [] + cflags_objc = [] + cflags_objcc = [] + ldflags = [] + defines = [] + configs = [] + + # System-specific flags. If your compiler flags apply to one of the + # categories here, add it to the associated file to keep this shared config + # smaller. + if (is_win) { + configs += [ "//build/config/win:compiler" ] + } else if (is_android) { + configs += [ "//build/config/android:compiler" ] + } else if (is_linux || is_chromeos) { + configs += [ "//build/config/linux:compiler" ] + if (is_chromeos_ash) { + configs += [ "//build/config/chromeos:compiler" ] + } + } else if (is_nacl) { + configs += [ "//build/config/nacl:compiler" ] + } else if (is_mac) { + configs += [ "//build/config/mac:compiler" ] + } else if (is_ios) { + configs += [ "//build/config/ios:compiler" ] + } else if (is_fuchsia) { + configs += [ "//build/config/fuchsia:compiler" ] + } else if (current_os == "aix") { + configs += [ "//build/config/aix:compiler" ] + } + + configs += [ + # See the definitions below. + ":clang_revision", + ":compiler_cpu_abi", + ":compiler_codegen", + ":compiler_deterministic", + ] + + # Here we enable -fno-delete-null-pointer-checks, which makes various nullptr + # operations (e.g. dereferencing) into defined behavior. This avoids deletion + # of some security-critical code: see https://crbug.com/1139129. + # Nacl does not support the flag. And, we still want UBSAN to catch undefined + # behavior related to nullptrs, so do not add this flag if UBSAN is enabled. + # GCC seems to have some bugs compiling constexpr code when this is defined, + # so only enable it if using_clang. See: https://gcc.gnu.org/PR97913 + # TODO(mpdenton): remove is_clang once GCC bug is fixed. + if (!is_nacl && !is_ubsan && is_clang) { + cflags += [ "-fno-delete-null-pointer-checks" ] + } + + # Don't emit the GCC version ident directives, they just end up in the + # .comment section or debug info taking up binary size, and makes comparing + # .o files built with different compiler versions harder. + if (!is_win || is_clang) { + cflags += [ "-fno-ident" ] + } + + # In general, Windows is totally different, but all the other builds share + # some common compiler and linker configuration. + if (!is_win) { + # Common POSIX compiler flags setup. + # -------------------------------- + cflags += [ "-fno-strict-aliasing" ] # See http://crbug.com/32204 + + # Stack protection. + if (is_mac) { + # The strong variant of the stack protector significantly increases + # binary size, so only enable it in debug mode. + if (is_debug) { + cflags += [ "-fstack-protector-strong" ] + } else { + cflags += [ "-fstack-protector" ] + } + } else if ((is_posix && !is_chromeos_ash && !is_nacl) || is_fuchsia) { + # TODO(phajdan.jr): Use -fstack-protector-strong when our gcc supports it. + # See also https://crbug.com/533294 + cflags += [ "--param=ssp-buffer-size=4" ] + + # The x86 toolchain currently has problems with stack-protector. + if (is_android && current_cpu == "x86") { + cflags += [ "-fno-stack-protector" ] + } else if (current_os != "aix") { + # Not available on aix. + cflags += [ "-fstack-protector" ] + } + } + + if (use_lld) { + ldflags += [ "-fuse-ld=lld" ] + if (lld_path != "") { + ldflags += [ "-B$lld_path" ] + } + } + + # Linker warnings. + if (fatal_linker_warnings && !is_apple && current_os != "aix") { + ldflags += [ "-Wl,--fatal-warnings" ] + } + if (fatal_linker_warnings && is_apple) { + ldflags += [ "-Wl,-fatal_warnings" ] + } + } + + if (is_clang && is_debug) { + # Allow comparing the address of references and 'this' against 0 + # in debug builds. Technically, these can never be null in + # well-defined C/C++ and Clang can optimize such checks away in + # release builds, but they may be used in asserts in debug builds. + cflags_cc += [ + "-Wno-undefined-bool-conversion", + "-Wno-tautological-undefined-compare", + ] + } + + # Non-Mac Posix and Fuchsia compiler flags setup. + # ----------------------------------- + if ((is_posix && !is_apple) || is_fuchsia) { + if (enable_profiling) { + if (!is_debug) { + cflags += [ "-g" ] + + if (enable_full_stack_frames_for_profiling) { + cflags += [ + "-fno-inline", + "-fno-optimize-sibling-calls", + ] + } + } + } + + # Explicitly pass --build-id to ld. Compilers used to always pass this + # implicitly but don't any more (in particular clang when built without + # ENABLE_LINKER_BUILD_ID=ON). + if (is_official_build) { + # The sha1 build id has lower risk of collision but is more expensive to + # compute, so only use it in the official build to avoid slowing down + # links. + ldflags += [ "-Wl,--build-id=sha1" ] + } else if (current_os != "aix") { + ldflags += [ "-Wl,--build-id" ] + } + + if (!is_android) { + defines += [ + # _FILE_OFFSET_BITS=64 should not be set on Android in order to maintain + # the behavior of the Android NDK from earlier versions. + # See https://android-developers.googleblog.com/2017/09/introducing-android-native-development.html + "_FILE_OFFSET_BITS=64", + "_LARGEFILE_SOURCE", + "_LARGEFILE64_SOURCE", + ] + } + + if (!is_nacl) { + if (exclude_unwind_tables) { + cflags += [ + "-fno-unwind-tables", + "-fno-asynchronous-unwind-tables", + ] + defines += [ "NO_UNWIND_TABLES" ] + } else { + cflags += [ "-funwind-tables" ] + } + } + } + + # Linux/Android/Fuchsia common flags setup. + # --------------------------------- + if (is_linux || is_chromeos || is_android || is_fuchsia) { + asmflags += [ "-fPIC" ] + cflags += [ "-fPIC" ] + ldflags += [ "-fPIC" ] + + if (!is_clang) { + # Use pipes for communicating between sub-processes. Faster. + # (This flag doesn't do anything with Clang.) + cflags += [ "-pipe" ] + } + + ldflags += [ + "-Wl,-z,noexecstack", + "-Wl,-z,relro", + ] + + if (!is_component_build) { + ldflags += [ "-Wl,-z,now" ] + } + } + + # Linux-specific compiler flags setup. + # ------------------------------------ + if ((is_posix || is_fuchsia) && !is_apple && use_lld) { + if (current_cpu == "arm64") { + # Reduce the page size from 65536 in order to reduce binary size slightly + # by shrinking the alignment gap between segments. This also causes all + # segments to be mapped adjacently, which breakpad relies on. + ldflags += [ "-Wl,-z,max-page-size=4096" ] + } + } else if (use_gold) { + ldflags += [ "-fuse-ld=gold" ] + if (!is_android) { + # On Android, this isn't needed. gcc in the NDK knows to look next to + # it with -fuse-ld=gold, and clang gets a --gcc-toolchain flag passed + # above. + if (gold_path != "") { + ldflags += [ "-B$gold_path" ] + } + + ldflags += [ + # Experimentation found that using four linking threads + # saved ~20% of link time. + # https://groups.google.com/a/chromium.org/group/chromium-dev/browse_thread/thread/281527606915bb36 + # Only apply this to the target linker, since the host + # linker might not be gold, but isn't used much anyway. + "-Wl,--threads", + "-Wl,--thread-count=4", + ] + } + + # TODO(thestig): Make this flag work with GN. + #if (!is_official_build && !is_chromeos && !(is_asan || is_lsan || is_tsan || is_msan)) { + # ldflags += [ + # "-Wl,--detect-odr-violations", + # ] + #} + } + + # TODO(crbug.com/1253924): Enable on apple/lld for arm64 too once it works. + if (use_icf && (!is_apple || (use_lld && current_cpu != "arm64"))) { + ldflags += [ "-Wl,--icf=all" ] + } + + if (is_linux || is_chromeos) { + cflags += [ "-pthread" ] + # Do not use the -pthread ldflag here since it becomes a no-op + # when using -nodefaultlibs, which would cause an unused argument + # error. "-lpthread" is added in //build/config:default_libs. + } + + # Clang-specific compiler flags setup. + # ------------------------------------ + if (is_clang) { + cflags += [ "-fcolor-diagnostics" ] + + # Enable -fmerge-all-constants. This used to be the default in clang + # for over a decade. It makes clang non-conforming, but is fairly safe + # in practice and saves some binary size. We might want to consider + # disabling this (https://bugs.llvm.org/show_bug.cgi?id=18538#c13), + # but for now it looks like our build might rely on it + # (https://crbug.com/829795). + cflags += [ "-fmerge-all-constants" ] + } + + if (use_lld) { + # TODO(thakis): Make the driver pass --color-diagnostics to the linker + # if -fcolor-diagnostics is passed to it, and pass -fcolor-diagnostics + # in ldflags instead. + if (is_win) { + # On Windows, we call the linker directly, instead of calling it through + # the driver. + ldflags += [ "--color-diagnostics" ] + } else { + ldflags += [ "-Wl,--color-diagnostics" ] + } + } + + # Enable text section splitting only on linux when using lld for now. Other + # platforms can be added later if needed. + if ((is_linux || is_chromeos) && use_lld && use_text_section_splitting) { + ldflags += [ "-Wl,-z,keep-text-section-prefix" ] + } + + if (is_clang && !is_nacl && !use_xcode_clang) { + cflags += [ "-fcrash-diagnostics-dir=" + clang_diagnostic_dir ] + + # TODO(hans): Remove this once Clang generates better optimized debug info + # by default. https://crbug.com/765793 + cflags += [ + "-mllvm", + "-instcombine-lower-dbg-declare=0", + ] + if (!is_debug && use_thin_lto && is_a_target_toolchain) { + if (is_win) { + ldflags += [ "-mllvm:-instcombine-lower-dbg-declare=0" ] + } else { + ldflags += [ "-Wl,-mllvm,-instcombine-lower-dbg-declare=0" ] + } + } + } + + # C11/C++11 compiler flags setup. + # --------------------------- + if (is_linux || is_chromeos || is_android || (is_nacl && is_clang) || + current_os == "aix") { + if (target_os == "android") { + cxx11_override = use_cxx11_on_android + } else { + cxx11_override = use_cxx11 + } + + if (is_clang) { + standard_prefix = "c" + + # Since we build with -std=c* and not -std=gnu*, _GNU_SOURCE will not be + # defined by the compiler. However, lots of code relies on the + # non-standard features that _GNU_SOURCE enables, so define it manually. + defines += [ "_GNU_SOURCE" ] + + if (is_nacl) { + # Undefine __STRICT_ANSI__ to get non-standard features which would + # otherwise not be enabled by NaCl's sysroots. + cflags += [ "-U__STRICT_ANSI__" ] + } + } else { + # Gcc does not support ##__VA_ARGS__ when in standards-conforming mode, + # but we use this feature in several places in Chromium. + # TODO(thomasanderson): Replace usages of ##__VA_ARGS__ with the + # standard-compliant __VA_OPT__ added by C++20, and switch the gcc build + # to -std=c*. + standard_prefix = "gnu" + } + + cflags_c += [ "-std=${standard_prefix}11" ] + if (cxx11_override) { + # Override Chromium's default for projects that wish to stay on C++11. + cflags_cc += [ "-std=${standard_prefix}++11" ] + } else { + cflags_cc += [ "-std=${standard_prefix}++14" ] + } + } else if (!is_win && !is_nacl) { + if (target_os == "android") { + cxx11_override = use_cxx11_on_android + } else { + cxx11_override = use_cxx11 + } + + # TODO(mcgrathr) - the NaCl GCC toolchain doesn't support either gnu11/gnu++11 + # or c11/c++11; we technically don't need this toolchain any more, but there + # are still a few buildbots using it, so until those are turned off + # we need the !is_nacl clause and the (is_nacl && is_clang) clause, above. + cflags_c += [ "-std=c11" ] + if (cxx11_override) { + cflags_cc += [ "-std=c++11" ] + } else { + cflags_cc += [ "-std=c++14" ] + } + } + + # C++17 removes trigraph support, so preemptively disable trigraphs. This is + # especially useful given the collision with ecmascript's logical assignment + # operators: https://github.com/tc39/proposal-logical-assignment + if (is_clang) { + # clang-cl disables trigraphs by default + if (!is_win) { + # The gnu variants of C++11 and C++14 already disable trigraph support, + # but when building with clang, we use -std=c++11 / -std=c++14, which + # enables trigraph support: override that here. + cflags_cc += [ "-fno-trigraphs" ] + } + + # Don't warn that trigraphs are ignored, since trigraphs are disabled + # anyway. + cflags_cc += [ "-Wno-trigraphs" ] + } + + if (is_mac) { + # The system libc++ on Mac doesn't have aligned allocation in C++17. + defines += [ "_LIBCPP_HAS_NO_ALIGNED_ALLOCATION" ] + cflags_cc += [ "-stdlib=libc++" ] + ldflags += [ "-stdlib=libc++" ] + } + + # Add flags for link-time optimization. These flags enable + # optimizations/transformations that require whole-program visibility at link + # time, so they need to be applied to all translation units, and we may end up + # with miscompiles if only part of the program is compiled with LTO flags. For + # that reason, we cannot allow targets to enable or disable these flags, for + # example by disabling the optimize configuration. + # TODO(pcc): Make this conditional on is_official_build rather than on gn + # flags for specific features. + if (!is_debug && use_thin_lto && is_a_target_toolchain) { + assert(use_lld, "LTO is only supported with lld") + + cflags += [ + "-flto=thin", + "-fsplit-lto-unit", + ] + + # Limit the size of the ThinLTO cache to the lesser of 10% of + # available disk space, 40GB and 100000 files. + cache_policy = "cache_size=10%:cache_size_bytes=40g:cache_size_files=100000" + + # TODO(gbiv): We ideally shouldn't need to specify this; ThinLTO + # should be able to better manage binary size increases on its own. + import_instr_limit = 30 + + if (is_win) { + ldflags += [ + "/opt:lldltojobs=all", + "-mllvm:-import-instr-limit=$import_instr_limit", + "/lldltocache:" + + rebase_path("$root_out_dir/thinlto-cache", root_build_dir), + "/lldltocachepolicy:$cache_policy", + ] + } else { + ldflags += [ "-flto=thin" ] + + # Enabling ThinLTO on Chrome OS too, in an effort to reduce the memory + # usage in crbug.com/1038040. Note this will increase build time in + # Chrome OS. + + # In ThinLTO builds, we run at most one link process at a time, + # and let it use all cores. + # TODO(thakis): Check if '=0' (that is, number of cores, instead + # of "all" which means number of hardware threads) is faster. + ldflags += [ "-Wl,--thinlto-jobs=all" ] + if (is_mac) { + ldflags += + [ "-Wl,-cache_path_lto," + + rebase_path("$root_out_dir/thinlto-cache", root_build_dir) ] + } else { + ldflags += + [ "-Wl,--thinlto-cache-dir=" + + rebase_path("$root_out_dir/thinlto-cache", root_build_dir) ] + } + + ldflags += [ "-Wl,--thinlto-cache-policy=$cache_policy" ] + + if (is_chromeos_ash) { + # Not much performance difference was noted between the default (100) + # and these. ARM was originally set lower than x86 to keep the size + # bloat of ThinLTO to <10%, but that's potentially no longer true. + # FIXME(inglorion): maybe tune these? + if (target_cpu == "arm" || target_cpu == "arm64") { + import_instr_limit = 30 + } else { + import_instr_limit = 30 + } + } + + ldflags += [ "-Wl,-mllvm,-import-instr-limit=$import_instr_limit" ] + } + + # TODO(https://crbug.com/1211155): investigate why this isn't effective on arm32. + if (!is_android || current_cpu == "arm64") { + cflags += [ "-fwhole-program-vtables" ] + if (!is_win) { + ldflags += [ "-fwhole-program-vtables" ] + } + } + + # This flag causes LTO to create an .ARM.attributes section with the correct + # architecture. This is necessary because LLD will refuse to link a program + # unless the architecture revision in .ARM.attributes is sufficiently new. + # TODO(pcc): The contents of .ARM.attributes should be based on the + # -march flag passed at compile time (see llvm.org/pr36291). + if (current_cpu == "arm") { + ldflags += [ "-march=$arm_arch" ] + } + } + + if (compiler_timing) { + if (is_clang && !is_nacl) { + cflags += [ "-ftime-trace" ] + } else if (is_win) { + cflags += [ + # "Documented" here: + # http://aras-p.info/blog/2017/10/23/Best-unknown-MSVC-flag-d2cgsummary/ + "/d2cgsummary", + ] + } + } + + # Pass flag to LLD so Android builds can allow debuggerd to properly symbolize + # stack crashes (http://crbug.com/919499). + if (use_lld && is_android) { + ldflags += [ "-Wl,--no-rosegment" ] + } + + # LLD does call-graph-sorted binary layout by default when profile data is + # present. On Android this increases binary size due to more thinks for long + # jumps. Turn it off by default and enable selectively for targets where it's + # beneficial. + if (use_lld && !enable_call_graph_profile_sort) { + if (is_win) { + ldflags += [ "/call-graph-profile-sort:no" ] + } else if (!is_apple) { + # TODO(thakis): Once LLD's Mach-O port basically works, implement call + # graph profile sorting for it, add an opt-out flag, and pass it here. + ldflags += [ "-Wl,--no-call-graph-profile-sort" ] + } + } + + if (is_clang && !is_nacl && show_includes) { + if (is_win) { + # TODO(crbug.com/1223741): Goma mixes the -H and /showIncludes output. + assert(!use_goma, "show_includes on Windows is not reliable with goma") + cflags += [ + "/clang:-H", + "/clang:-fshow-skipped-includes", + ] + } else { + cflags += [ + "-H", + "-fshow-skipped-includes", + ] + } + } + + # This flag enforces that member pointer base types are complete. It helps + # prevent us from running into problems in the Microsoft C++ ABI (see + # https://crbug.com/847724). + # TODO(crbug/1052397): Remove is_chromeos_lacros once lacros-chrome switches + # to target_os="chromeos". + if (is_clang && !is_nacl && target_os != "chromeos" && !use_xcode_clang && + !is_chromeos_lacros && (is_win || use_custom_libcxx)) { + cflags += [ "-fcomplete-member-pointers" ] + } + + # Pass the same C/C++ flags to the objective C/C++ compiler. + cflags_objc += cflags_c + cflags_objcc += cflags_cc + + # Assign any flags set for the C compiler to asmflags so that they are sent + # to the assembler. The Windows assembler takes different types of flags + # so only do so for posix platforms. + if (is_posix || is_fuchsia) { + asmflags += cflags + asmflags += cflags_c + } + + # Rust compiler flags setup. + # --------------------------- + rustflags = [ + # Overflow checks are optional in Rust, but even if switched + # off they do not cause undefined behavior (the overflowing + # behavior is defined). Because containers are bounds-checked + # in safe Rust, they also can't provoke buffer overflows. + # As such these checks may be less important in Rust than C++. + # But in (simplistic) testing they have negligible performance + # overhead, and this helps to provide consistent behavior + # between different configurations, so we'll keep them on until + # we discover a reason to turn them off. + "-Coverflow-checks=on", + + # To make Rust .d files compatible with ninja + "-Z", + "dep-info-omit-d-target", + ] + if (rust_abi_target != "") { + rustflags += [ "--target=$rust_abi_target" ] + } + if (use_lto_in_rustc_linking) { + rustflags += [ "-Clinker-plugin-lto" ] + } + if (!use_thin_lto) { + # Optimization - don't include bitcode if it won't be used. + rustflags += [ "-Cembed-bitcode=no" ] + } +} + +# The BUILDCONFIG file sets this config on targets by default, which means when +# building with ThinLTO, no optimization is performed in the link step. +config("thinlto_optimize_default") { + if (!is_debug && use_thin_lto && is_a_target_toolchain) { + lto_opt_level = 3 + + if (is_win) { + ldflags = [ "/opt:lldlto=" + lto_opt_level ] + } else { + ldflags = [ "-Wl,--lto-O" + lto_opt_level ] + } + } +} + +# Use this to enable optimization in the ThinLTO link step for select targets +# when thin_lto_enable_optimizations is set by doing: +# +# configs -= [ "//build/config/compiler:thinlto_optimize_default" ] +# configs += [ "//build/config/compiler:thinlto_optimize_max" ] +# +# Since it makes linking significantly slower and more resource intensive, only +# use it on important targets such as the main browser executable or dll. +config("thinlto_optimize_max") { + if (!is_debug && use_thin_lto && is_a_target_toolchain) { + if (thin_lto_enable_optimizations) { + lto_opt_level = 3 + } else { + lto_opt_level = 3 + } + + if (is_win) { + ldflags = [ "/opt:lldlto=" + lto_opt_level ] + } else { + ldflags = [ "-Wl,--lto-O" + lto_opt_level ] + } + } +} + +# This provides the basic options to select the target CPU and ABI. +# It is factored out of "compiler" so that special cases can use this +# without using everything that "compiler" brings in. Options that +# tweak code generation for a particular CPU do not belong here! +# See "compiler_codegen", below. +config("compiler_cpu_abi") { + cflags = [] + ldflags = [] + defines = [] + + if ((is_posix && !is_apple) || is_fuchsia) { + # CPU architecture. We may or may not be doing a cross compile now, so for + # simplicity we always explicitly set the architecture. + if (current_cpu == "x64") { + cflags += [ + "-m64", + "-march=$x64_arch", + "-mavx", + ] + ldflags += [ "-m64" ] + } else if (current_cpu == "x86") { + cflags += [ "-m32" ] + ldflags += [ "-m32" ] + if (!is_nacl) { + cflags += [ + "-mfpmath=sse", + "-mavx", + ] + } + } else if (current_cpu == "arm") { + if (is_clang && !is_android && !is_nacl) { + cflags += [ "--target=arm-linux-gnueabihf" ] + ldflags += [ "--target=arm-linux-gnueabihf" ] + } + if (!is_nacl) { + cflags += [ + "-march=$arm_arch", + "-mfloat-abi=$arm_float_abi", + ] + } + if (arm_tune != "") { + cflags += [ "-mtune=$arm_tune" ] + } + } else if (current_cpu == "arm64") { + if (is_clang && !is_android && !is_nacl && !is_fuchsia) { + cflags += [ "--target=aarch64-linux-gnu" ] + ldflags += [ "--target=aarch64-linux-gnu" ] + } + } else if (current_cpu == "mipsel" && !is_nacl) { + ldflags += [ "-Wl,--hash-style=sysv" ] + if (custom_toolchain == "") { + if (is_clang) { + if (is_android) { + cflags += [ "--target=mipsel-linux-android" ] + ldflags += [ "--target=mipsel-linux-android" ] + } else { + cflags += [ "--target=mipsel-linux-gnu" ] + ldflags += [ "--target=mipsel-linux-gnu" ] + } + } else { + cflags += [ "-EL" ] + ldflags += [ "-EL" ] + } + } + + if (mips_arch_variant == "r6") { + cflags += [ "-mno-odd-spreg" ] + ldflags += [ "-mips32r6" ] + if (is_clang) { + cflags += [ + "-march=mipsel", + "-mcpu=mips32r6", + ] + } else { + cflags += [ + "-mips32r6", + "-Wa,-mips32r6", + ] + if (is_android) { + ldflags += [ "-Wl,-melf32ltsmip" ] + } + } + if (mips_use_msa == true) { + cflags += [ + "-mmsa", + "-mfp64", + ] + } + } else if (mips_arch_variant == "r2") { + ldflags += [ "-mips32r2" ] + if (is_clang) { + cflags += [ + "-march=mipsel", + "-mcpu=mips32r2", + ] + } else { + cflags += [ + "-mips32r2", + "-Wa,-mips32r2", + ] + if (mips_float_abi == "hard" && mips_fpu_mode != "") { + cflags += [ "-m$mips_fpu_mode" ] + } + } + } else if (mips_arch_variant == "r1") { + ldflags += [ "-mips32" ] + if (is_clang) { + cflags += [ + "-march=mipsel", + "-mcpu=mips32", + ] + } else { + cflags += [ + "-mips32", + "-Wa,-mips32", + ] + } + } else if (mips_arch_variant == "loongson3") { + defines += [ "_MIPS_ARCH_LOONGSON" ] + cflags += [ + "-march=loongson3a", + "-mno-branch-likely", + "-Wa,-march=loongson3a", + ] + } + + if (mips_dsp_rev == 1) { + cflags += [ "-mdsp" ] + } else if (mips_dsp_rev == 2) { + cflags += [ "-mdspr2" ] + } + + cflags += [ "-m${mips_float_abi}-float" ] + } else if (current_cpu == "mips" && !is_nacl) { + ldflags += [ "-Wl,--hash-style=sysv" ] + if (custom_toolchain == "") { + if (is_clang) { + cflags += [ "--target=mips-linux-gnu" ] + ldflags += [ "--target=mips-linux-gnu" ] + } else { + cflags += [ "-EB" ] + ldflags += [ "-EB" ] + } + } + + if (mips_arch_variant == "r6") { + cflags += [ + "-mips32r6", + "-Wa,-mips32r6", + ] + if (mips_use_msa == true) { + cflags += [ + "-mmsa", + "-mfp64", + ] + } + } else if (mips_arch_variant == "r2") { + cflags += [ + "-mips32r2", + "-Wa,-mips32r2", + ] + if (mips_float_abi == "hard" && mips_fpu_mode != "") { + cflags += [ "-m$mips_fpu_mode" ] + } + } else if (mips_arch_variant == "r1") { + cflags += [ + "-mips32", + "-Wa,-mips32", + ] + } + + if (mips_dsp_rev == 1) { + cflags += [ "-mdsp" ] + } else if (mips_dsp_rev == 2) { + cflags += [ "-mdspr2" ] + } + + cflags += [ "-m${mips_float_abi}-float" ] + } else if (current_cpu == "mips64el") { + cflags += [ "-D__SANE_USERSPACE_TYPES__" ] + ldflags += [ "-Wl,--hash-style=sysv" ] + if (custom_toolchain == "") { + if (is_clang) { + if (is_android) { + cflags += [ "--target=mips64el-linux-android" ] + ldflags += [ "--target=mips64el-linux-android" ] + } else { + cflags += [ "--target=mips64el-linux-gnuabi64" ] + ldflags += [ "--target=mips64el-linux-gnuabi64" ] + } + } else { + cflags += [ + "-EL", + "-mabi=64", + ] + ldflags += [ + "-EL", + "-mabi=64", + ] + } + } + + if (mips_arch_variant == "r6") { + if (is_clang) { + cflags += [ + "-march=mips64el", + "-mcpu=mips64r6", + ] + } else { + cflags += [ + "-mips64r6", + "-Wa,-mips64r6", + ] + ldflags += [ "-mips64r6" ] + } + if (mips_use_msa == true) { + cflags += [ + "-mmsa", + "-mfp64", + ] + } + } else if (mips_arch_variant == "r2") { + ldflags += [ "-mips64r2" ] + if (is_clang) { + cflags += [ + "-march=mips64el", + "-mcpu=mips64r2", + ] + } else { + cflags += [ + "-mips64r2", + "-Wa,-mips64r2", + ] + } + } else if (mips_arch_variant == "loongson3") { + defines += [ "_MIPS_ARCH_LOONGSON" ] + cflags += [ + "-march=loongson3a", + "-mno-branch-likely", + "-Wa,-march=loongson3a", + ] + } + } else if (current_cpu == "mips64") { + ldflags += [ "-Wl,--hash-style=sysv" ] + if (custom_toolchain == "") { + if (is_clang) { + cflags += [ "--target=mips64-linux-gnuabi64" ] + ldflags += [ "--target=mips64-linux-gnuabi64" ] + } else { + cflags += [ + "-EB", + "-mabi=64", + ] + ldflags += [ + "-EB", + "-mabi=64", + ] + } + } + + if (mips_arch_variant == "r6") { + cflags += [ + "-mips64r6", + "-Wa,-mips64r6", + ] + ldflags += [ "-mips64r6" ] + + if (mips_use_msa == true) { + cflags += [ + "-mmsa", + "-mfp64", + ] + } + } else if (mips_arch_variant == "r2") { + cflags += [ + "-mips64r2", + "-Wa,-mips64r2", + ] + ldflags += [ "-mips64r2" ] + } + } else if (current_cpu == "pnacl" && is_nacl_nonsfi) { + if (target_cpu == "x86" || target_cpu == "x64") { + cflags += [ + "-arch", + "x86-32-nonsfi", + "--pnacl-bias=x86-32-nonsfi", + "--target=i686-unknown-nacl", + ] + ldflags += [ + "-arch", + "x86-32-nonsfi", + "--target=i686-unknown-nacl", + ] + } else if (target_cpu == "arm") { + cflags += [ + "-arch", + "arm-nonsfi", + "-mfloat-abi=hard", + "--pnacl-bias=arm-nonsfi", + "--target=armv7-unknown-nacl-gnueabihf", + ] + ldflags += [ + "-arch", + "arm-nonsfi", + "--target=armv7-unknown-nacl-gnueabihf", + ] + } + } else if (current_cpu == "ppc64") { + if (current_os == "aix") { + cflags += [ "-maix64" ] + ldflags += [ "-maix64" ] + } else { + cflags += [ "-m64" ] + ldflags += [ "-m64" ] + } + } else if (current_cpu == "s390x") { + cflags += [ "-m64" ] + ldflags += [ "-m64" ] + } + } + + asmflags = cflags +} + +# This provides options to tweak code generation that are necessary +# for particular Chromium code or for working around particular +# compiler bugs (or the combination of the two). +config("compiler_codegen") { + configs = [] + cflags = [] + ldflags = [] + + if (is_nacl) { + configs += [ "//build/config/nacl:compiler_codegen" ] + } + + if (current_cpu == "arm64" && is_android) { + # On arm64 disable outlining for Android. See crbug.com/931297 for more + # information. + cflags += [ "-mno-outline" ] + + # This can be removed once https://bugs.llvm.org/show_bug.cgi?id=40348 + # has been resolved, and -mno-outline is obeyed by the linker during + # ThinLTO. + ldflags += [ "-Wl,-mllvm,-enable-machine-outliner=never" ] + } + + asmflags = cflags +} + +# This provides options that make the build deterministic, so that the same +# revision produces the same output, independent of the name of the build +# directory and of the computer the build is done on. +# The relative path from build dir to source dir makes it into the build +# outputs, so it's recommended that you use a build dir two levels deep +# (e.g. "out/Release") so that you get the same "../.." path as all the bots +# in your build outputs. +config("compiler_deterministic") { + cflags = [] + ldflags = [] + + # Eliminate build metadata (__DATE__, __TIME__ and __TIMESTAMP__) for + # deterministic build. See https://crbug.com/314403 + if (!is_official_build) { + if (is_win && !is_clang) { + cflags += [ + "/wd4117", # Trying to define or undefine a predefined macro. + "/D__DATE__=", + "/D__TIME__=", + "/D__TIMESTAMP__=", + ] + } else { + cflags += [ + "-Wno-builtin-macro-redefined", + "-D__DATE__=", + "-D__TIME__=", + "-D__TIMESTAMP__=", + ] + } + } + + # Makes builds independent of absolute file path. + if (is_clang && strip_absolute_paths_from_debug_symbols) { + # If debug option is given, clang includes $cwd in debug info by default. + # For such build, this flag generates reproducible obj files even we use + # different build directory like "out/feature_a" and "out/feature_b" if + # we build same files with same compile flag. + # Other paths are already given in relative, no need to normalize them. + if (is_nacl) { + # TODO(https://crbug.com/1231236): Use -ffile-compilation-dir= here. + cflags += [ + "-Xclang", + "-fdebug-compilation-dir", + "-Xclang", + ".", + ] + } else { + # -ffile-compilation-dir is an alias for both -fdebug-compilation-dir= + # and -fcoverage-compilation-dir=. + cflags += [ "-ffile-compilation-dir=." ] + } + if (!is_win) { + # We don't use clang -cc1as on Windows (yet? https://crbug.com/762167) + asmflags = [ "-Wa,-fdebug-compilation-dir,." ] + } + + if (is_win && use_lld) { + if (symbol_level == 2 || (is_clang && using_sanitizer)) { + # Absolutize source file paths for PDB. Pass the real build directory + # if the pdb contains source-level debug information and if linker + # reproducibility is not critical. + ldflags += [ "/PDBSourcePath:" + rebase_path(root_build_dir) ] + } else { + # Use a fake fixed base directory for paths in the pdb to make the pdb + # output fully deterministic and independent of the build directory. + ldflags += [ "/PDBSourcePath:o:\fake\prefix" ] + } + } + } + + # Tells the compiler not to use absolute paths when passing the default + # paths to the tools it invokes. We don't want this because we don't + # really need it and it can mess up the goma cache entries. + if (is_clang && !is_nacl) { + cflags += [ "-no-canonical-prefixes" ] + + # Same for links: Let the compiler driver invoke the linker + # with a relative path and pass relative paths to built-in + # libraries. Not needed on Windows because we call the linker + # directly there, not through the compiler driver. + # We don't link on goma, so this change is just for cleaner + # internal linker invocations, for people who work on the build. + if (!is_win) { + ldflags += [ "-no-canonical-prefixes" ] + } + } +} + +config("clang_revision") { + if (is_clang && clang_base_path == default_clang_base_path) { + update_args = [ + "--print-revision", + "--verify-version=$clang_version", + ] + if (llvm_force_head_revision) { + update_args += [ "--llvm-force-head-revision" ] + } + clang_revision = exec_script("//tools/clang/scripts/update.py", + update_args, + "trim string") + + # This is here so that all files get recompiled after a clang roll and + # when turning clang on or off. (defines are passed via the command line, + # and build system rebuild things when their commandline changes). Nothing + # should ever read this define. + defines = [ "CR_CLANG_REVISION=\"$clang_revision\"" ] + } +} + +config("compiler_arm_fpu") { + if (current_cpu == "arm" && !is_ios && !is_nacl) { + cflags = [ "-mfpu=$arm_fpu" ] + if (!arm_use_thumb) { + cflags += [ "-marm" ] + } + asmflags = cflags + } +} + +config("compiler_arm_thumb") { + if (current_cpu == "arm" && arm_use_thumb && is_posix && + !(is_apple || is_nacl)) { + cflags = [ "-mthumb" ] + } +} + +config("compiler_arm") { + if (current_cpu == "arm" && (is_chromeos_ash || is_chromeos_lacros)) { + # arm is normally the default mode for clang, but on chromeos a wrapper + # is used to pass -mthumb, and therefor change the default. + cflags = [ "-marm" ] + } +} + +# runtime_library ------------------------------------------------------------- +# +# Sets the runtime library and associated options. +# +# How do you determine what should go in here vs. "compiler" above? Consider if +# a target might choose to use a different runtime library (ignore for a moment +# if this is possible or reasonable on your system). If such a target would want +# to change or remove your option, put it in the runtime_library config. If a +# target wants the option regardless, put it in the compiler config. + +config("runtime_library") { + configs = [] + + # The order of this config is important: it must appear before + # android:runtime_library. This is to ensure libc++ appears before + # libandroid_support in the -isystem include order. Otherwise, there will be + # build errors related to symbols declared in math.h. + if (use_custom_libcxx) { + configs += [ "//build/config/c++:runtime_library" ] + } + + # TODO(crbug.com/830987): Come up with a better name for is POSIX + Fuchsia + # configuration. + if (is_posix || is_fuchsia) { + configs += [ "//build/config/posix:runtime_library" ] + } + + # System-specific flags. If your compiler flags apply to one of the + # categories here, add it to the associated file to keep this shared config + # smaller. + if (is_win) { + configs += [ "//build/config/win:runtime_library" ] + } else if (is_linux || is_chromeos) { + configs += [ "//build/config/linux:runtime_library" ] + } else if (is_ios) { + configs += [ "//build/config/ios:runtime_library" ] + } else if (is_mac) { + configs += [ "//build/config/mac:runtime_library" ] + } else if (is_android) { + configs += [ "//build/config/android:runtime_library" ] + } + + if (is_component_build) { + defines = [ "COMPONENT_BUILD" ] + } +} + +# default_warnings ------------------------------------------------------------ +# +# Collects all warning flags that are used by default. This is used as a +# subconfig of both chromium_code and no_chromium_code. This way these +# flags are guaranteed to appear on the compile command line after -Wall. +config("default_warnings") { + cflags = [] + cflags_c = [] + cflags_cc = [] + ldflags = [] + + if (is_win) { + if (treat_warnings_as_errors) { + cflags += [ "/WX" ] + } + if (fatal_linker_warnings) { + ldflags = [ "/WX" ] + } + defines = [ + # Without this, Windows headers warn that functions like wcsnicmp + # should be spelled _wcsnicmp. But all other platforms keep spelling + # it wcsnicmp, making this warning unhelpful. We don't want it. + "_CRT_NONSTDC_NO_WARNINGS", + + # TODO(thakis): winsock wants us to use getaddrinfo instead of + # gethostbyname. Fires mostly in non-Chromium code. We probably + # want to remove this define eventually. + "_WINSOCK_DEPRECATED_NO_WARNINGS", + ] + if (!is_clang) { + # TODO(thakis): Remove this once + # https://swiftshader-review.googlesource.com/c/SwiftShader/+/57968 has + # rolled into angle. + cflags += [ "/wd4244" ] + } + } else { + if (is_apple && !is_nacl) { + # When compiling Objective-C, warns if a method is used whose + # availability is newer than the deployment target. + cflags += [ "-Wunguarded-availability" ] + } + + if (is_ios) { + # When compiling Objective-C, warns if a selector named via @selector has + # not been defined in any visible interface. + cflags += [ "-Wundeclared-selector" ] + } + + # Suppress warnings about ABI changes on ARM (Clang doesn't give this + # warning). + if (current_cpu == "arm" && !is_clang) { + cflags += [ "-Wno-psabi" ] + } + + if (!is_clang) { + cflags_cc += [ + # See comment for -Wno-c++11-narrowing. + "-Wno-narrowing", + ] + + # -Wno-class-memaccess warns about hash table and vector in blink. + # But the violation is intentional. + if (!is_nacl) { + cflags_cc += [ "-Wno-class-memaccess" ] + } + + # -Wunused-local-typedefs is broken in gcc, + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63872 + cflags += [ "-Wno-unused-local-typedefs" ] + + # Don't warn about "maybe" uninitialized. Clang doesn't include this + # in -Wall but gcc does, and it gives false positives. + cflags += [ "-Wno-maybe-uninitialized" ] + cflags += [ "-Wno-deprecated-declarations" ] + + # -Wcomment gives too many false positives in the case a + # backslash ended comment line is followed by a new line of + # comments + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61638 + cflags += [ "-Wno-comments" ] + + # -Wpacked-not-aligned complains all generated mojom-shared-internal.h + # files. + cflags += [ "-Wno-packed-not-aligned" ] + } + } + + # Common Clang and GCC warning setup. + if (!is_win || is_clang) { + cflags += [ + # Disables. + "-Wno-missing-field-initializers", # "struct foo f = {0};" + "-Wno-unused-parameter", # Unused function parameters. + ] + } + + if (is_clang) { + cflags += [ + "-Wloop-analysis", + + # TODO(thakis): This used to be implied by -Wno-unused-function, + # which we no longer use. Check if it makes sense to remove + # this as well. http://crbug.com/316352 + "-Wno-unneeded-internal-declaration", + ] + + # use_xcode_clang only refers to the iOS toolchain, host binaries use + # chromium's clang always. + if (!is_nacl || is_nacl_saigo) { + if (is_win) { + # TODO(thakis): https://crbug.com/617318 + # Currently goma can not handle case sensitiveness for windows well. + cflags += [ "-Wno-nonportable-include-path" ] + + # Warns in ATL headers; see https://crbug.com/1208419. + cflags += [ "-Wno-null-pointer-subtraction" ] + } + + if (current_toolchain == host_toolchain || !use_xcode_clang) { + # Flags Xcode 9.2 (Clang clang-900.0.39.2) does not recognize. + cflags += [ + "-Wenum-compare-conditional", + + # An ABI compat warning we don't care about, https://crbug.com/1102157 + # TODO(thakis): Push this to the (few) targets that need it, + # instead of having a global flag. + "-Wno-psabi", + + # Ignore warnings about MSVC optimization pragmas. + # TODO(thakis): Only for no_chromium_code? http://crbug.com/912662 + "-Wno-ignored-pragma-optimize", + + # TODO(https://crbug.com/1016945) Clean up, enable. + "-Wno-builtin-assume-aligned-alignment", + ] + + # NaCl does not support flags from ToT. + if (!is_nacl) { + cflags += [ + # TODO(https://crbug.com/1203071): Clean up and enable. + "-Wno-unused-but-set-parameter", + "-Wno-unused-but-set-variable", + + # TODO(https://crbug.com/1255745): Clean up, enable. + "-Wno-bitwise-instead-of-logical", + ] + } + + if (is_fuchsia) { + # TODO(https://bugs.chromium.org/p/fuchsia/issues/detail?id=77383) + cflags += [ "-Wno-deprecated-copy" ] + } + + if (enable_wmax_tokens) { + cflags += [ "-Wmax-tokens" ] + } + } + } + } +} + +# chromium_code --------------------------------------------------------------- +# +# Toggles between higher and lower warnings for code that is (or isn't) +# part of Chromium. + +config("chromium_code") { + if (is_win) { + if (is_clang) { + cflags = [ "/W4" ] # Warning level 4. + + # Opt in to additional [[nodiscard]] on standard library methods. + defines = [ "_HAS_NODISCARD" ] + } + } else { + cflags = [ "-Wall" ] + if (treat_warnings_as_errors) { + cflags += [ "-Werror" ] + + # The compiler driver can sometimes (rarely) emit warnings before calling + # the actual linker. Make sure these warnings are treated as errors as + # well. + ldflags = [ "-Werror" ] + } + if (is_clang) { + # Enable extra warnings for chromium_code when we control the compiler. + cflags += [ "-Wextra" ] + } + + # In Chromium code, we define __STDC_foo_MACROS in order to get the + # C99 macros on Mac and Linux. + defines = [ + "__STDC_CONSTANT_MACROS", + "__STDC_FORMAT_MACROS", + ] + + if (!is_debug && !using_sanitizer && current_cpu != "s390x" && + current_cpu != "s390" && current_cpu != "ppc64" && + current_cpu != "mips" && current_cpu != "mips64") { + # Non-chromium code is not guaranteed to compile cleanly with + # _FORTIFY_SOURCE. Also, fortified build may fail when optimizations are + # disabled, so only do that for Release build. + defines += [ "_FORTIFY_SOURCE=2" ] + } + + if (is_mac) { + cflags_objc = [ "-Wobjc-missing-property-synthesis" ] + cflags_objcc = [ "-Wobjc-missing-property-synthesis" ] + } + + if (is_ios) { + cflags_objc = [ "-Wimplicit-retain-self" ] + cflags_objcc = cflags_objc + } + } + + if (is_clang) { + cflags += [ + # Warn on missing break statements at the end of switch cases. + # For intentional fallthrough, use FALLTHROUGH; from + # base/compiler_specific.h + "-Wimplicit-fallthrough", + ] + + # TODO(thakis): Enable this more often, https://crbug.com/346399 + # use_libfuzzer: https://crbug.com/1063180 + if (!is_nacl && !use_libfuzzer) { + cflags += [ "-Wunreachable-code-aggressive" ] + } + + # Thread safety analysis is broken under nacl: https://crbug.com/982423. + if (!is_nacl) { + cflags += [ + # Thread safety analysis. See base/thread_annotations.h and + # https://clang.llvm.org/docs/ThreadSafetyAnalysis.html + "-Wthread-safety", + ] + } + + # TODO(thakis): Enable this for more platforms, https://crbug.com/926235 + # ChromeOS: http://crbug.com/940863 + # Chromecast: http://crbug.com/942554 + has_dchecks = is_debug || dcheck_always_on + if (!has_dchecks && is_chromeos_ash && is_chrome_branded) { + # Temporarily disable -Wextra-semi for Chrome on Chrome OS. + } else if (is_chromecast && chromecast_branding != "public") { + # Temporarily disable -Wextra-semi for Chromecast. + } else { + cflags += [ "-Wextra-semi" ] + } + } + + configs = [ ":default_warnings" ] +} + +config("no_chromium_code") { + cflags = [] + cflags_cc = [] + defines = [] + + if (is_win) { + if (is_clang) { + cflags += [ "/W3" ] # Warning level 3. + } + cflags += [ + "/wd4800", # Disable warning when forcing value to bool. + "/wd4267", # TODO(jschuh): size_t to int. + ] + } else { + # GCC may emit unsuppressible warnings so don't add -Werror for no chromium + # code. crbug.com/589724 + if (treat_warnings_as_errors && is_clang) { + cflags += [ "-Werror" ] + ldflags = [ "-Werror" ] + } + if (is_clang && !is_nacl) { + # TODO(thakis): Remove !is_nacl once + # https://codereview.webrtc.org/1552863002/ made its way into chromium. + cflags += [ "-Wall" ] + } + } + + if (is_clang) { + cflags += [ + # Lots of third-party libraries have unused variables. Instead of + # suppressing them individually, we just blanket suppress them here. + "-Wno-unused-variable", + + # Similarly, we're not going to fix all the C++11 narrowing issues in + # third-party libraries. + "-Wno-c++11-narrowing", + ] + if (!is_nacl && (current_toolchain == host_toolchain || !use_xcode_clang)) { + cflags += [ + # TODO(https://crbug.com/1202159): Clean up and enable. + "-Wno-misleading-indentation", + ] + } + } + + configs = [ ":default_warnings" ] +} + +# noshadowing ----------------------------------------------------------------- +# +# Allows turning -Wshadow on. + +config("noshadowing") { + # This flag has to be disabled for nacl because the nacl compiler is too + # strict about shadowing. + if (is_clang && !is_nacl) { + cflags = [ "-Wshadow" ] + } +} + +# rtti ------------------------------------------------------------------------ +# +# Allows turning Run-Time Type Identification on or off. + +config("rtti") { + if (is_win) { + cflags_cc = [ "/GR" ] + } else { + cflags_cc = [ "-frtti" ] + } +} + +config("no_rtti") { + # Some sanitizer configs may require RTTI to be left enabled globally + if (!use_rtti) { + if (is_win) { + cflags_cc = [ "/GR-" ] + } else { + cflags_cc = [ "-fno-rtti" ] + cflags_objcc = cflags_cc + } + } +} + +# export_dynamic --------------------------------------------------------------- +# +# Ensures all exported symbols are added to the dynamic symbol table. This is +# necessary to expose Chrome's custom operator new() and operator delete() (and +# other memory-related symbols) to libraries. Otherwise, they might +# (de)allocate memory on a different heap, which would spell trouble if pointers +# to heap-allocated memory are passed over shared library boundaries. +config("export_dynamic") { + # TODO(crbug.com/1052397): Revisit after target_os flip is completed. + if (is_linux || is_chromeos_lacros || export_libcxxabi_from_executables) { + ldflags = [ "-rdynamic" ] + } +} + +# thin_archive ----------------------------------------------------------------- +# +# Enables thin archives on posix, and on windows when the lld linker is used. +# Regular archives directly include the object files used to generate it. +# Thin archives merely reference the object files. +# This makes building them faster since it requires less disk IO, but is +# inappropriate if you wish to redistribute your static library. +# This config is added to the global config, so thin archives should already be +# enabled. If you want to make a distributable static library, you need to do 2 +# things: +# 1. Set complete_static_lib so that all dependencies of the library make it +# into the library. See `gn help complete_static_lib` for details. +# 2. Remove the thin_archive config, so that the .a file actually contains all +# .o files, instead of just references to .o files in the build directoy +config("thin_archive") { + # The macOS and iOS default linker ld64 does not support reading thin + # archives. + # TODO(crbug.com/1221615): Enable on is_apple if use_lld once that no longer + # confuses lldb. + if ((is_posix && !is_nacl && !is_apple) || is_fuchsia) { + arflags = [ "-T" ] + } else if (is_win && use_lld) { + arflags = [ "/llvmlibthin" ] + } +} + +# exceptions ------------------------------------------------------------------- +# +# Allows turning Exceptions on or off. +# Note: exceptions are disallowed in Google code. + +config("exceptions") { + if (is_win) { + # Enables exceptions in the STL. + if (!use_custom_libcxx) { + defines = [ "_HAS_EXCEPTIONS=1" ] + } + cflags_cc = [ "/EHsc" ] + } else { + cflags_cc = [ "-fexceptions" ] + cflags_objcc = cflags_cc + } +} + +config("no_exceptions") { + if (is_win) { + # Disables exceptions in the STL. + # libc++ uses the __has_feature macro to control whether to use exceptions, + # so defining this macro is unnecessary. Defining _HAS_EXCEPTIONS to 0 also + # breaks libc++ because it depends on MSVC headers that only provide certain + # declarations if _HAS_EXCEPTIONS is 1. Those MSVC headers do not use + # exceptions, despite being conditional on _HAS_EXCEPTIONS. + if (!use_custom_libcxx) { + defines = [ "_HAS_EXCEPTIONS=0" ] + } + } else { + cflags_cc = [ "-fno-exceptions" ] + cflags_objcc = cflags_cc + } +} + +# Warnings --------------------------------------------------------------------- + +# Generate a warning for code that might emit a static initializer. +# See: //docs/static_initializers.md +# See: https://groups.google.com/a/chromium.org/d/topic/chromium-dev/B9Q5KTD7iCo/discussion +config("wglobal_constructors") { + if (is_clang) { + cflags = [ "-Wglobal-constructors" ] + } +} + +# This will generate warnings when using Clang if code generates exit-time +# destructors, which will slow down closing the program. +# TODO(thakis): Make this a blocklist instead, http://crbug.com/101600 +config("wexit_time_destructors") { + if (is_clang) { + cflags = [ "-Wexit-time-destructors" ] + } +} + +# Some code presumes that pointers to structures/objects are compatible +# regardless of whether what they point to is already known to be valid. +# gcc 4.9 and earlier had no way of suppressing this warning without +# suppressing the rest of them. Here we centralize the identification of +# the gcc 4.9 toolchains. +config("no_incompatible_pointer_warnings") { + cflags = [] + if (is_clang) { + cflags += [ "-Wno-incompatible-pointer-types" ] + } else if (current_cpu == "mipsel" || current_cpu == "mips64el") { + cflags += [ "-w" ] + } else if (is_chromeos_ash && current_cpu == "arm") { + cflags += [ "-w" ] + } +} + +# Optimization ----------------------------------------------------------------- +# +# The BUILDCONFIG file sets the "default_optimization" config on targets by +# default. It will be equivalent to either "optimize" (release) or +# "no_optimize" (debug) optimization configs. +# +# You can override the optimization level on a per-target basis by removing the +# default config and then adding the named one you want: +# +# configs -= [ "//build/config/compiler:default_optimization" ] +# configs += [ "//build/config/compiler:optimize_max" ] + +# Shared settings for both "optimize" and "optimize_max" configs. +# IMPORTANT: On Windows "/O1" and "/O2" must go before the common flags. +if (is_win) { + common_optimize_on_cflags = [ + "/Ob2", # Both explicit and auto inlining. + "/Oy-", # Disable omitting frame pointers, must be after /O2. + "/Zc:inline", # Remove unreferenced COMDAT (faster links). + ] + if (!is_asan) { + common_optimize_on_cflags += [ + # Put data in separate COMDATs. This allows the linker + # to put bit-identical constants at the same address even if + # they're unrelated constants, which saves binary size. + # This optimization can't be used when ASan is enabled because + # it is not compatible with the ASan ODR checker. + "/Gw", + ] + } + common_optimize_on_ldflags = [] + + # /OPT:ICF is not desirable in Debug builds, since code-folding can result in + # misleading symbols in stack traces. + if (!is_debug && !is_component_build) { + common_optimize_on_ldflags += [ "/OPT:ICF" ] # Redundant COMDAT folding. + } + + if (is_official_build) { + common_optimize_on_ldflags += [ "/OPT:REF" ] # Remove unreferenced data. + # TODO(thakis): Add LTO/PGO clang flags eventually, https://crbug.com/598772 + } +} else { + common_optimize_on_cflags = [] + common_optimize_on_ldflags = [] + + if (is_android) { + # TODO(jdduke) Re-enable on mips after resolving linking + # issues with libc++ (crbug.com/456380). + if (current_cpu != "mipsel" && current_cpu != "mips64el") { + common_optimize_on_ldflags += [ + # Warn in case of text relocations. + "-Wl,--warn-shared-textrel", + ] + } + } + + if (is_apple) { + common_optimize_on_ldflags += [ "-Wl,-dead_strip" ] + + if (is_official_build) { + common_optimize_on_ldflags += [ + "-Wl,-no_data_in_code_info", + "-Wl,-no_function_starts", + ] + } + } else if (current_os != "aix") { + # Non-Mac Posix flags. + # Aix does not support these. + + common_optimize_on_cflags += [ + # Put data and code in their own sections, so that unused symbols + # can be removed at link time with --gc-sections. + "-fdata-sections", + "-ffunction-sections", + ] + if (!is_nacl && is_clang) { + # We don't care about unique section names, this makes object files a bit + # smaller. + common_optimize_on_cflags += [ "-fno-unique-section-names" ] + } + + common_optimize_on_ldflags += [ + # Specifically tell the linker to perform optimizations. + # See http://lwn.net/Articles/192624/ . + # -O2 enables string tail merge optimization in gold and lld. + "-Wl,-O2", + "-Wl,--gc-sections", + ] + } +} + +config("default_stack_frames") { + if (is_posix || is_fuchsia) { + if (enable_frame_pointers) { + cflags = [ "-fno-omit-frame-pointer" ] + + # Omit frame pointers for leaf functions on x86, otherwise building libyuv + # gives clang's register allocator issues, see llvm.org/PR15798 / + # crbug.com/233709 + if (is_clang && current_cpu == "x86" && !is_apple) { + cflags += [ "-momit-leaf-frame-pointer" ] + } + } else { + cflags = [ "-fomit-frame-pointer" ] + } + } + # On Windows, the flag to enable framepointers "/Oy-" must always come after + # the optimization flag [e.g. "/O2"]. The optimization flag is set by one of + # the "optimize" configs, see rest of this file. The ordering that cflags are + # applied is well-defined by the GN spec, and there is no way to ensure that + # cflags set by "default_stack_frames" is applied after those set by an + # "optimize" config. Similarly, there is no way to propagate state from this + # config into the "optimize" config. We always apply the "/Oy-" config in the + # definition for common_optimize_on_cflags definition, even though this may + # not be correct. +} + +# Default "optimization on" config. +config("optimize") { + if (is_win) { + if (chrome_pgo_phase != 2) { + # Favor size over speed, /O1 must be before the common flags. + # /O1 implies /Os and /GF. + cflags = [ "/O1" ] + common_optimize_on_cflags + [ "/Oi" ] + } else { + # PGO requires all translation units to be compiled with /O2. The actual + # optimization level will be decided based on the profiling data. + cflags = [ "/O2" ] + common_optimize_on_cflags + [ "/Oi" ] + } + } else if (optimize_for_size) { + # Favor size over speed. + # TODO(crbug.com/718650): Fix -Os in PNaCl compiler and remove the is_nacl + # guard above. + if (is_clang) { + cflags = [ "-O2" ] + common_optimize_on_cflags + } else { + cflags = [ "-O2" ] + common_optimize_on_cflags + } + } else if (is_chromeos_ash) { + # TODO(gbiv): This is partially favoring size over speed. CrOS exclusively + # uses clang, and -Os in clang is more of a size-conscious -O2 than "size at + # any cost" (AKA -Oz). It'd be nice to: + # - Make `optimize_for_size` apply to all platforms where we're optimizing + # for size by default (so, also Windows) + # - Investigate -Oz here, maybe just for ARM? + cflags = [ "-O2" ] + common_optimize_on_cflags + } else { + cflags = [ "-O2" ] + common_optimize_on_cflags + } + if (optimize_for_size) { + rustflags = [ "-Copt-level=s" ] + } else { + rustflags = [ "-Copt-level=3" ] + } + ldflags = common_optimize_on_ldflags +} + +# Turn off optimizations. +config("no_optimize") { + if (is_win) { + cflags = [ + "/Od", # Disable optimization. + "/Ob0", # Disable all inlining (on by default). + "/GF", # Enable string pooling (off by default). + ] + + if (target_cpu == "arm64") { + # Disable omitting frame pointers for no_optimize build because stack + # traces on Windows ARM64 rely on it. + cflags += [ "/Oy-" ] + } + } else if (is_android && !android_full_debug) { + # On Android we kind of optimize some things that don't affect debugging + # much even when optimization is disabled to get the binary size down. + if (is_clang) { + cflags = [ "-O2" ] + common_optimize_on_cflags + } else { + cflags = [ "-O2" ] + common_optimize_on_cflags + } + + if (!is_component_build) { + # Required for library partitions. Without this all symbols just end up + # in the base partition. + ldflags = [ "-Wl,--gc-sections" ] + } + } else if (is_fuchsia) { + # On Fuchsia, we optimize for size here to reduce the size of debug build + # packages so they can be run in a KVM. See crbug.com/910243 for details. + cflags = [ "-Og" ] + } else { + cflags = [ "-O0" ] + ldflags = [] + } +} + +# Turns up the optimization level. On Windows, this implies whole program +# optimization and link-time code generation which is very expensive and should +# be used sparingly. +config("optimize_max") { + if (is_nacl && is_nacl_irt) { + # The NaCl IRT is a special case and always wants its own config. + # Various components do: + # if (!is_debug) { + # configs -= [ "//build/config/compiler:default_optimization" ] + # configs += [ "//build/config/compiler:optimize_max" ] + # } + # So this config has to have the selection logic just like + # "default_optimization", below. + configs = [ "//build/config/nacl:irt_optimize" ] + } else { + ldflags = common_optimize_on_ldflags + if (is_win) { + # Favor speed over size, /O2 must be before the common flags. + # /O2 implies /Ot, /Oi, and /GF. + cflags = [ "/O2" ] + common_optimize_on_cflags + } else if (optimize_for_fuzzing) { + cflags = [ "-O1" ] + common_optimize_on_cflags + } else { + cflags = [ "-O2" ] + common_optimize_on_cflags + } + rustflags = [ "-Copt-level=3" ] + } +} + +# This config can be used to override the default settings for per-component +# and whole-program optimization, optimizing the particular target for speed +# instead of code size. This config is exactly the same as "optimize_max" +# except that we use -O3 instead of -O2 on non-win, non-IRT platforms. +# +# TODO(crbug.com/621335) - rework how all of these configs are related +# so that we don't need this disclaimer. +config("optimize_speed") { + if (is_nacl && is_nacl_irt) { + # The NaCl IRT is a special case and always wants its own config. + # Various components do: + # if (!is_debug) { + # configs -= [ "//build/config/compiler:default_optimization" ] + # configs += [ "//build/config/compiler:optimize_max" ] + # } + # So this config has to have the selection logic just like + # "default_optimization", below. + configs = [ "//build/config/nacl:irt_optimize" ] + } else { + ldflags = common_optimize_on_ldflags + if (is_win) { + # Favor speed over size, /O2 must be before the common flags. + # /O2 implies /Ot, /Oi, and /GF. + cflags = [ "/O2" ] + common_optimize_on_cflags + } else if (optimize_for_fuzzing) { + cflags = [ "-O1" ] + common_optimize_on_cflags + } else { + cflags = [ "-O3" ] + common_optimize_on_cflags + } + rustflags = [ "-Copt-level=3" ] + } +} + +config("optimize_fuzzing") { + cflags = [ "-O1" ] + common_optimize_on_cflags + rustflags = [ "-Copt-level=1" ] + ldflags = common_optimize_on_ldflags + visibility = [ ":default_optimization" ] +} + +# The default optimization applied to all targets. This will be equivalent to +# either "optimize" or "no_optimize", depending on the build flags. +config("default_optimization") { + if (is_nacl && is_nacl_irt) { + # The NaCl IRT is a special case and always wants its own config. + # It gets optimized the same way regardless of the type of build. + configs = [ "//build/config/nacl:irt_optimize" ] + } else if (is_debug) { + configs = [ ":no_optimize" ] + } else if (optimize_for_fuzzing) { + assert(!is_win, "Fuzzing optimize level not supported on Windows") + + # Coverage build is quite slow. Using "optimize_for_fuzzing" makes it even + # slower as it uses "-O1" instead of "-O3". Prevent that from happening. + assert(!use_clang_coverage, + "optimize_for_fuzzing=true should not be used with " + + "use_clang_coverage=true.") + configs = [ ":optimize_fuzzing" ] + } else { + configs = [ ":optimize" ] + } +} + +_clang_sample_profile = "" +if (is_clang && is_a_target_toolchain) { + if (clang_sample_profile_path != "") { + _clang_sample_profile = clang_sample_profile_path + } else if (clang_use_default_sample_profile) { + assert(build_with_chromium, + "Our default profiles currently only apply to Chromium") + assert(is_android || is_chromeos_lacros || is_chromeos_ash || is_chromecast, + "The current platform has no default profile") + if (is_android || is_chromecast) { + _clang_sample_profile = "//chrome/android/profiles/afdo.prof" + } else { + assert(chromeos_afdo_platform == "atom" || + chromeos_afdo_platform == "bigcore", + "Only atom and bigcore are valid Chrome OS profiles.") + _clang_sample_profile = + "//chromeos/profiles/${chromeos_afdo_platform}.afdo.prof" + } + } +} + +# Clang offers a way to assert that AFDO profiles are accurate, which causes it +# to optimize functions not represented in a profile more aggressively for size. +# This config can be toggled in cases where shaving off binary size hurts +# performance too much. +config("afdo_optimize_size") { + if (_clang_sample_profile != "" && sample_profile_is_accurate) { + cflags = [ "-fprofile-sample-accurate" ] + } +} + +# GCC and clang support a form of profile-guided optimization called AFDO. +# There are some targeted places that AFDO regresses (and an icky interaction +# between //base/allocator:tcmalloc and AFDO on GCC), so we provide a separate +# config to allow AFDO to be disabled per-target. +config("afdo") { + if (is_clang) { + cflags = [] + if (clang_emit_debug_info_for_profiling) { + # Add the following flags to generate debug info for profiling. + cflags += [ "-gline-tables-only" ] + if (!is_nacl) { + cflags += [ "-fdebug-info-for-profiling" ] + } + } + if (_clang_sample_profile != "") { + assert(chrome_pgo_phase == 0, "AFDO can't be used in PGO builds") + rebased_clang_sample_profile = + rebase_path(_clang_sample_profile, root_build_dir) + cflags += [ "-fprofile-sample-use=${rebased_clang_sample_profile}" ] + inputs = [ _clang_sample_profile ] + } + } else if (auto_profile_path != "" && is_a_target_toolchain) { + cflags = [ "-fauto-profile=${auto_profile_path}" ] + inputs = [ auto_profile_path ] + } +} + +# Symbols ---------------------------------------------------------------------- + +# The BUILDCONFIG file sets the "default_symbols" config on targets by +# default. It will be equivalent to one the three specific symbol levels. +# +# You can override the symbol level on a per-target basis by removing the +# default config and then adding the named one you want: +# +# configs -= [ "//build/config/compiler:default_symbols" ] +# configs += [ "//build/config/compiler:symbols" ] + +# A helper config that all configs passing /DEBUG to the linker should +# include as sub-config. +config("win_pdbaltpath") { + visibility = [ + ":minimal_symbols", + ":symbols", + ] + + # /DEBUG causes the linker to generate a pdb file, and to write the absolute + # path to it in the executable file it generates. This flag turns that + # absolute path into just the basename of the pdb file, which helps with + # build reproducibility. Debuggers look for pdb files next to executables, + # so there's minimal downside to always using this. However, post-mortem + # debugging of Chromium crash dumps and ETW tracing can be complicated by this + # switch so an option to omit it is important. + if (!use_full_pdb_paths) { + ldflags = [ "/pdbaltpath:%_PDB%" ] + } +} + +# Full symbols. +config("symbols") { + if (is_win) { + if (is_clang) { + cflags = [ "/Z7" ] # Debug information in the .obj files. + } else { + cflags = [ "/Zi" ] # Produce PDB file, no edit and continue. + } + + if (is_clang && use_lld && use_ghash) { + cflags += [ "-gcodeview-ghash" ] + ldflags = [ "/DEBUG:GHASH" ] + } else { + ldflags = [ "/DEBUG" ] + } + + # All configs using /DEBUG should include this: + configs = [ ":win_pdbaltpath" ] + } else { + cflags = [] + if (is_mac && enable_dsyms) { + # If generating dSYMs, specify -fno-standalone-debug. This was + # originally specified for https://crbug.com/479841 because dsymutil + # could not handle a 4GB dSYM file. But dsymutil from Xcodes prior to + # version 7 also produces debug data that is incompatible with Breakpad + # dump_syms, so this is still required (https://crbug.com/622406). + cflags += [ "-fno-standalone-debug" ] + } else if (is_mac && !use_dwarf5) { + # clang defaults to DWARF2 on macOS unless mac_deployment_target is + # at least 10.11. + # TODO(thakis): Remove this once mac_deployment_target is 10.11. + cflags += [ "-gdwarf-4" ] + } + + if (use_dwarf5 && !is_nacl) { + cflags += [ "-gdwarf-5" ] + } + + # The gcc-based nacl compilers don't support -fdebug-compilation-dir (see + # elsewhere in this file), so they can't have build-dir-independent output. + # Disable symbols for nacl object files to get deterministic, + # build-directory-independent output. pnacl and nacl-clang do support that + # flag, so we can use use -g1 for pnacl and nacl-clang compiles. + # gcc nacl is is_nacl && !is_clang, pnacl and nacl-clang are && is_clang. + if (!is_nacl || is_clang) { + cflags += [ "-g2" ] + } + + if (!is_nacl && is_clang && !is_tsan && !is_asan) { + # gcc generates dwarf-aranges by default on -g1 and -g2. On clang it has + # to be manually enabled. + # + # It is skipped in tsan and asan because enabling it causes some + # formatting changes in the output which would require fixing bunches + # of expectation regexps. + cflags += [ "-gdwarf-aranges" ] + } + + if (is_apple) { + swiftflags = [ "-g" ] + } + + if (use_debug_fission) { + cflags += [ "-gsplit-dwarf" ] + } + asmflags = cflags + ldflags = [] + + # Split debug info with all thinlto builds except nacl and apple. + # thinlto requires -gsplit-dwarf in ldflags. + if (use_debug_fission && use_thin_lto && !is_nacl && !is_apple) { + ldflags += [ "-gsplit-dwarf" ] + } + + # TODO(thakis): Figure out if there's a way to make this go for 32-bit, + # currently we get "warning: + # obj/native_client/src/trusted/service_runtime/sel_asm/nacl_switch_32.o: + # DWARF info may be corrupt; offsets in a range list entry are in different + # sections" there. Maybe just a bug in nacl_switch_32.S. + if (!is_apple && !is_nacl && current_cpu != "x86" && + (use_gold || use_lld)) { + if (is_clang) { + # This flag enables the GNU-format pubnames and pubtypes sections, + # which lld needs in order to generate a correct GDB index. + # TODO(pcc): Try to make lld understand non-GNU-format pubnames + # sections (llvm.org/PR34820). + cflags += [ "-ggnu-pubnames" ] + } + ldflags += [ "-Wl,--gdb-index" ] + } + } + + if (is_clang && !is_nacl && !use_xcode_clang) { + if (is_apple) { + # TODO(https://crbug.com/1050118): Investigate missing debug info on mac. + # Make sure we don't use constructor homing on mac. + cflags += [ + "-Xclang", + "-debug-info-kind=limited", + ] + } else { + # Use constructor homing for debug info. This option reduces debug info + # by emitting class type info only when constructors are emitted. + cflags += [ + "-Xclang", + "-fuse-ctor-homing", + ] + } + } + rustflags = [ "-g" ] +} + +# Minimal symbols. +# This config guarantees to hold symbol for stack trace which are shown to user +# when crash happens in unittests running on buildbot. +config("minimal_symbols") { + if (is_win) { + # Functions, files, and line tables only. + cflags = [] + + if (is_clang && use_lld && use_ghash) { + cflags += [ "-gcodeview-ghash" ] + ldflags = [ "/DEBUG:GHASH" ] + } else { + ldflags = [ "/DEBUG" ] + } + + # All configs using /DEBUG should include this: + configs = [ ":win_pdbaltpath" ] + + # Enable line tables for clang. MSVC doesn't have an equivalent option. + if (is_clang) { + # -gline-tables-only is the same as -g1, but clang-cl only exposes the + # former. + cflags += [ "-gline-tables-only" ] + } + } else { + cflags = [] + if (is_mac && !use_dwarf5) { + # clang defaults to DWARF2 on macOS unless mac_deployment_target is + # at least 10.11. + # TODO(thakis): Remove this once mac_deployment_target is 10.11. + cflags += [ "-gdwarf-4" ] + } + + if (use_dwarf5 && !is_nacl) { + cflags += [ "-gdwarf-5" ] + } + + # The gcc-based nacl compilers don't support -fdebug-compilation-dir (see + # elsewhere in this file), so they can't have build-dir-independent output. + # Disable symbols for nacl object files to get deterministic, + # build-directory-independent output. pnacl and nacl-clang do support that + # flag, so we can use use -g1 for pnacl and nacl-clang compiles. + # gcc nacl is is_nacl && !is_clang, pnacl and nacl-clang are && is_clang. + if (!is_nacl || is_clang) { + cflags += [ "-g1" ] + } + + if (!is_nacl && is_clang && !is_tsan && !is_asan) { + # See comment for -gdwarf-aranges in config("symbols"). + cflags += [ "-gdwarf-aranges" ] + } + + ldflags = [] + if (is_android && is_clang) { + # Android defaults to symbol_level=1 builds in production builds + # (https://crbug.com/648948), but clang, unlike gcc, doesn't emit + # DW_AT_linkage_name in -g1 builds. -fdebug-info-for-profiling enables + # that (and a bunch of other things we don't need), so that we get + # qualified names in stacks. + # TODO(thakis): Consider making clang emit DW_AT_linkage_name in -g1 mode; + # failing that consider doing this on non-Android too. + cflags += [ "-fdebug-info-for-profiling" ] + } + + # Note: debug_fission is no-op with symbol_level=1 since all -g1 debug_info + # will stay in the executable. + + asmflags = cflags + } + rustflags = [ "-Cdebuginfo=1" ] +} + +# This configuration contains function names only. That is, the compiler is +# told to not generate debug information and the linker then just puts function +# names in the final debug information. +config("no_symbols") { + if (is_win) { + ldflags = [ "/DEBUG" ] + + # All configs using /DEBUG should include this: + configs = [ ":win_pdbaltpath" ] + } else { + cflags = [ "-g0" ] + asmflags = cflags + } +} + +# Default symbols. +config("default_symbols") { + if (symbol_level == 0) { + configs = [ ":no_symbols" ] + } else if (symbol_level == 1) { + configs = [ ":minimal_symbols" ] + } else if (symbol_level == 2) { + configs = [ ":symbols" ] + } else { + assert(false) + } + + # This config is removed by base unittests apk. + if (is_android && is_clang && strip_debug_info) { + configs += [ ":strip_debug" ] + } +} + +config("strip_debug") { + if (!defined(ldflags)) { + ldflags = [] + } + ldflags += [ "-Wl,--strip-debug" ] +} + +if (is_apple) { + # On Mac and iOS, this enables support for ARC (automatic ref-counting). + # See http://clang.llvm.org/docs/AutomaticReferenceCounting.html. + config("enable_arc") { + common_flags = [ "-fobjc-arc" ] + cflags_objc = common_flags + cflags_objcc = common_flags + } +} + +if (is_chromeos_ash && is_chromeos_device) { + # This config is intended to be a temporary to facilitate + # the transition to use orderfile in Chrome OS. Once orderfile + # use becomes a default in Chrome OS, this config should not + # be needed. + config("use_orderfile_for_hugepage") { + if (chrome_orderfile_path != "") { + defines = [ "CHROMEOS_ORDERFILE_USE" ] + } + } +} + +if (is_android || (is_chromeos_ash && is_chromeos_device)) { + # Use orderfile for linking Chrome on Android and Chrome OS. + # This config enables using an orderfile for linking in LLD. + # TODO: Consider using call graph sort instead, at least on Android. + config("chrome_orderfile_config") { + if (chrome_orderfile_path != "" && !enable_call_graph_profile_sort) { + assert(use_lld) + _rebased_orderfile = rebase_path(chrome_orderfile_path, root_build_dir) + ldflags = [ + "-Wl,--symbol-ordering-file", + "-Wl,$_rebased_orderfile", + "-Wl,--no-warn-symbol-ordering", + ] + inputs = [ chrome_orderfile_path ] + } + } +} + +# Initialize all variables on the stack if needed. +config("default_init_stack_vars") { + cflags = [] + if (init_stack_vars && is_clang && !is_nacl && !using_sanitizer) { + cflags += [ "-ftrivial-auto-var-init=pattern" ] + } +} + +buildflag_header("compiler_buildflags") { + header = "compiler_buildflags.h" + + flags = [ + "CLANG_PGO=$chrome_pgo_phase", + "SYMBOL_LEVEL=$symbol_level", + ] +} + +config("cet_shadow_stack") { + if (enable_cet_shadow_stack && is_win) { + assert(target_cpu == "x64") + ldflags = [ "/CETCOMPAT" ] + } +} diff --git a/chrome-devtools/chrome-devtools.png b/chrome-devtools/chrome-devtools.png new file mode 100644 index 00000000..f61641d2 Binary files /dev/null and b/chrome-devtools/chrome-devtools.png differ diff --git a/chrome-devtools/chrome-devtools.svg b/chrome-devtools/chrome-devtools.svg new file mode 100644 index 00000000..5acda089 --- /dev/null +++ b/chrome-devtools/chrome-devtools.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/chrome-devtools/chrome-devtools_128x128.png b/chrome-devtools/chrome-devtools_128x128.png new file mode 100644 index 00000000..8b0255f5 Binary files /dev/null and b/chrome-devtools/chrome-devtools_128x128.png differ diff --git a/chrome-devtools/chrome-devtools_16x16.png b/chrome-devtools/chrome-devtools_16x16.png new file mode 100644 index 00000000..768ac557 Binary files /dev/null and b/chrome-devtools/chrome-devtools_16x16.png differ diff --git a/chrome-devtools/chrome-devtools_24x24.png b/chrome-devtools/chrome-devtools_24x24.png new file mode 100644 index 00000000..eeb863a0 Binary files /dev/null and b/chrome-devtools/chrome-devtools_24x24.png differ diff --git a/chrome-devtools/chrome-devtools_256x256.png b/chrome-devtools/chrome-devtools_256x256.png new file mode 100644 index 00000000..e3d43268 Binary files /dev/null and b/chrome-devtools/chrome-devtools_256x256.png differ diff --git a/chrome-devtools/chrome-devtools_32x32.png b/chrome-devtools/chrome-devtools_32x32.png new file mode 100644 index 00000000..017c344c Binary files /dev/null and b/chrome-devtools/chrome-devtools_32x32.png differ diff --git a/chrome-devtools/chrome-devtools_48x48.png b/chrome-devtools/chrome-devtools_48x48.png new file mode 100644 index 00000000..a49ee097 Binary files /dev/null and b/chrome-devtools/chrome-devtools_48x48.png differ diff --git a/chrome-devtools/chrome-devtools_512x512.png b/chrome-devtools/chrome-devtools_512x512.png new file mode 100644 index 00000000..5171f89c Binary files /dev/null and b/chrome-devtools/chrome-devtools_512x512.png differ diff --git a/chrome-devtools/chrome-devtools_64x64.png b/chrome-devtools/chrome-devtools_64x64.png new file mode 100644 index 00000000..7efc8ff1 Binary files /dev/null and b/chrome-devtools/chrome-devtools_64x64.png differ diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd new file mode 100644 index 00000000..0e80fdcb --- /dev/null +++ b/chrome/app/chromium_strings.grd @@ -0,0 +1,1275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thorium + + + Thorium + + + + + + + + + + Thorium is a web browser that runs webpages and applications with lightning speed. It's fast, stable, and easy to use. Browse the web more safely with malware and phishing protection built into Thorium. + + + Welcome to Thorium; new browser window opened + + + + + Welcome to Thorium + + + + + Thorium OS + + + Thorium OS + + + + + + + New window + + + + + Task Manager + + + + + Task Manager - Thorium + + + + Help make Thorium better by sending crash reports and $1usage statistics to Google + + + + + $1Google - Thorium + + + + + + Thorium - $1Google + + + + + + $1Google - Network Sign-in - Thorium + + + + + + Thorium - Network Sign-in - $1Google + + + + + + $1Google - Network Sign-in + + + + + $1Google - Thorium + + + $1Google - Thorium Beta + + + $1Google - Thorium Dev + + + $1Google - Thorium Canary + + + The Chromium and Thorium Authors + + + Copyright {0,date,y}2016 The Chromium and Thorium Authors. All rights reserved. + + + + Thorium OS is made possible by additional <a target="_blank" href="$1">open source software</a>. + + + Thorium OS is made possible by additional <a target="_blank" href="$1">open source software</a>, as is <a target="_blank" href="$2">Linux development environment</a>. + + + Not used in Thorium. Placeholder to keep resource maps in sync. + + + + Not used in Thorium. Placeholder to keep resource maps in sync. + + + + To get future Thorium updates, you'll need OS X 10.11 or later. This computer is using OS X 10.10. + + + + + Thorium may not function correctly because it is no longer supported on Windows XP or Windows Vista + + + + Thorium + + + + + Thorium is unresponsive. Relaunch now? + + + + + To send a number from here to your Android phone, sign in to Thorium on both devices. + + + To send a number from $1www.google.com to your Android phone, sign in to Thorium on both devices. + + + Make sure you are signed in to Thorium on your $1Pixel XL and then try sending again. + + + + + Please close all Thorium windows and try again. + + + Are you sure you want to uninstall Thorium? + + + Uninstall Thorium + + + + Make Thorium the default browser + + + + Thorium OS can't open this page. + + + + + Let Thorium Run in the Background + + + + + Let Thorium run in the background + + + +Thorium cannot read and write to its data directory: + +$1C:\Documents and Settings\devint\Local Settings\Application Data\Google\Chrome + + + + Optional: Help improve Thorium OS features and performance by automatically sending diagnostic and usage data to Google. + + + +Your profile can not be used because it is from a newer version of Thorium. + +Some features may be unavailable. Please specify a different profile directory or use a newer version of Thorium. + + +Your preferences can not be read. + +Some features may be unavailable and changes to preferences won't be saved. + + +Your preferences file is corrupt or invalid. + +Thorium is unable to recover your settings. + + + + Thorium + + + + Whoa! Thorium has crashed. Relaunch now? + + + + + Thorium will save this password in your Google Account. You won’t have to remember it. + + + + Thorium lets you know if your passwords are ever compromised + + + Thorium + + + + Thorium is trying to show passwords. + + + Thorium is trying to copy passwords. + + + Thorium is trying to edit passwords. + + + Thorium wants to export your passwords. + + + + + Thorium is trying to show passwords. Type your Windows password to allow this. + + + Thorium is trying to copy passwords. Type your Windows password to allow this. + + + Thorium is trying to edit passwords. Type your Windows password to allow this. + + + Thorium wants to export your passwords. Type your Windows password to allow this. + + + This computer already has a more recent version of Thorium. If the software is not working, please uninstall Thorium and try again. + + + Installation failed due to unspecified error. If Thorium is currently running, please close it and try again. + + + Can not install the same Thorium version that is currently running. Please close Thorium and try again. + + + Installation failed due to unspecified error. Please download Thorium again. + + + Thorium requires Windows 7 or higher. + + + An operating system error occurred during installation. Please download Thorium again. + + + Another operation on Thorium is in progress. Please try again later. + + + The installer couldn't create a temporary directory. Please check for free disk space and permission to install software. + + + The installer failed to uncompress archive. Please download Thorium again. + + + The installer archive is corrupted or invalid. Please download Thorium again. + + + You do not have appropriate rights for system-level install. Try running the installer again as Administrator. + + + Thorium is already installed for all users on your computer. + + + + + + Access the Internet + + + Also delete your browsing data? + + + Change default browser to: + + + Uninstall + + + + Thorium isn't your default browser + + + + + There is a new version of Thorium available. + + + There's a new version of Thorium available, and it's faster than ever. + + + There's a new, safer version of Thorium available. + + + Thorium has been updated, but you haven't used it for at least 30 days. + + + Thorium lets you click a phone number on the web and call it with Skype! + + + + + + Adding to Thorium... + + + + $1bla.exe may be dangerous, so Thorium has blocked it. + + + + This file is dangerous, so Thorium has blocked it. + + + $1malware.exe is dangerous, so Thorium has blocked it. + + + This file is dangerous, so Thorium has blocked it. + + + This file may be dangerous, so Thorium has blocked it. + + + + Thorium recommends that you don't download or open this file + + + + + + Exit Thorium anyway? + + + + + Quit Thorium anyway? + + + + + + + Thorium is in background mode. + + + + + + Google API keys are missing. Some functionality of Thorium will be disabled. + + + + + + $1Gmail Checker has been added to Thorium + + + + + Also clear data from Thorium ($1www.google.com) + + + + + Thorium found that "$1Gmail Checker" contains malware + + + Thorium found that these items contain malware: + + + + + + Warning: Thorium cannot prevent extensions from recording your browsing history. To disable this extension in Incognito mode, unselect this option. + + + Remove from Thorium... + + + In Thorium + + + + + To make Thorium safer, we disabled some extensions that aren't listed in the $1Chrome Web Store and may have been added without your knowledge. + + + To make Thorium safer, we disabled the following extension that isn't listed in the $1Chrome Web Store and may have been added without your knowledge. + + + + + + Customize and control Thorium + + + + Customize and control Thorium. Update is available. + + + + Customize and control Thorium. Something needs your attention - click for details. + + + + &Open in Thorium + + + + + About &Thorium + + + Relaunch to Update &Thorium + + + + + About &Thorium + + + Relaunch to update &Thorium + + + + + About &Thorium + + + Relaunch to update &Thorium OS + + + + + + Thorium + + + Thorium Helper + + + Thorium Helper + + + + + + Thorium + + + + + Sync and personalize Thorium across your devices + + + + + + + You're signed in to Thorium! + + + You're signed in as $1foo@gmail.com. Now you can access your bookmarks, history, and other settings on all your signed in devices. + + + You were signed in to Thorium as $1foo@gmail.com. Please use the same account to sign in again. + + + Someone previously signed in to Thorium on this computer as $1user@example.com. Please create a new Thorium user to keep your information separate. + + + + + + + Link your Thorium data to this account? + + + You are signing in with a managed account and giving its administrator control over your Thorium profile. Your Thorium data, such as your apps, bookmarks, history, passwords, and other settings will become permanently tied to $1pat@example.com. You will be able to delete this data via the Google Accounts Dashboard, but you will not be able to associate this data with another account. $2Learn more + + + You are signing in with a managed account and giving its administrator control over your Thorium profile. Your Thorium data, such as your apps, bookmarks, history, passwords, and other settings will become permanently tied to $1pat@example.com. You will be able to delete this data via the Google Accounts Dashboard, but you will not be able to associate this data with another account. You can optionally create a new profile to keep your existing Thorium data separate. $2Learn more + + + Add Work Profile to this browser + + + You are adding a work profile to this browser and giving your administrator control over just the work profile. + + + This work profile is completely separate from your personal profile. + + + Any Thorium data that is generated during the use of this profile (such as the creation of bookmarks, history, passwords, and other settings) can be removed by the work profile administrator. $1Learn more + + + + + + + Your system administrator has configured Thorium to open an alternative browser to access $1example.com. + + + Your system administrator has configured Thorium to open $2Internet Explorer to access $1example.com. + + + + + + Customize Thorium + + + + + + $1foo@gmail.com was previously using Thorium + + + + + + + Continue in a new Thorium profile? + + + Switch to existing Thorium profile? + + + A Thorium profile with this account already exists + + + $1Elisa is already signed in to this Thorium profile. To keep your browsing separate, Thorium can create your own profile for you. + + + $1Elisa is already signed in to this Thorium profile. This will create a new Thorium profile for $2foo@gmail.com + + + This will create a new Thorium profile for $1foo@gmail.com + + + + + Customize your new Thorium profile + + + + + + + There's harmful software on your computer. Thorium can remove it, restore your settings, and disable extensions to make your browser work normally again. + + + + + + + Thorium could not sync your data because your account sign-in details are out of date. + + + Thorium could not sync your data because Sync is not available for your domain. + + + Thorium could not sync your data due to an error signing in. + + + + + Thorium OS System + + + Thorium OS could not sync your data because your account sign-in details are out of date. + + + Thorium OS could not sync your data because Sync is not available for your domain. + + + Thorium OS could not sync your data due to an error signing in. + + + + + + + Remove from Thorium... + + + + + + Thorium Apps + + + Thorium Apps + + + + + + + + + + + + $1example.com requires that you read and accept the following Terms of Service before using this device. These terms do not expand, modify or limit the Chromium OS Terms. + + + + + + Thorium is using your camera and microphone. + + + Thorium is using your microphone. + + + Thorium is using your camera. + + + + + + The profile appears to be in use by another Thorium process ($112345) on another computer ($2example.com). Thorium has locked the profile so that it doesn't get corrupted. If you are sure no other processes are using this profile, you can unlock the profile and relaunch Thorium. + + + + + + + Set Thorium as your default browser + + + + + + + + This person's browsing data will be deleted from this device. To recover the data, sign in to Thorium as $2foo@example.com. + + + + + Thorium just got better + + + Now it's easier to use Thorium with your Google Account and on shared computers. + + + This is your Thorium + + + Your web, bookmarks, and other Thorium stuff live here. + + + Guests can use Thorium without leaving anything behind. + + + If you share a computer, friends and family can browse separately and set up Thorium just the way they want. + + + Click your name to open Thorium and start browsing. + + + Add yourself to Thorium + + + + + + This extension has changed what page is shown when you start Thorium. + + + The extension "$1AdBlock" has changed what page is shown when you start Thorium. + + + ''' It also controls what page is shown when you start Thorium. ''' + + + ''' It also controls what page is shown when you start Thorium or click the Home button. ''' + + + ''' It also controls what page is shown when you start Thorium or search from the Omnibox. ''' + + + + + Discover great apps, games, extensions and themes for Thorium. + + + + + + Thorium (mDNS-In) + + + + + + + + + Inbound rule for Thorium to allow mDNS traffic. + + + + + + + + + + + + If an image doesn’t have a useful description, Thorium will try to provide one for you. To create descriptions, images are sent to Google. You can turn this off in settings at any time. + + + If an image doesn’t have a useful description, Thorium will try to provide one for you. To create descriptions, images are sent to Google. + + + This uses the same spell checker that's used in Google search. Text you type in the browser is sent to Google. You can always change this behavior in settings. + + + + Open link in new Thorium &tab + + + Open link in Thorium inco&gnito window + + + + + Open Link in New Thorium &tab + + + Open Link in Thorium Inco&gnito Window + + + + + + + + Relaunch Thorium + + + {COUNT, plural, + =0 {A new update for Thorium is available and will be applied as soon as you relaunch.} + =1 {A new update for Thorium is available and will be applied as soon as you relaunch. Your Incognito window won't reopen.} + other {A new update for Thorium is available and will be applied as soon as you relaunch. Your # Incognito windows won't reopen.}} + + + Relaunch + + + + + Restart Thorium OS + + + Thorium OS needs to be restarted to apply the update. + + + Restart + + + + + + Reinstall Thorium + + + + Thorium is Out of Date + + + + + Thorium is out of date + + + + Can't update Thorium + + + Thorium couldn't update to the latest version, so you're missing out on new features and security fixes. + + + + + Update Thorium + + + + + + Update Thorium to start sync + + + Update Thorium + + + + + + Thorium is out of date + + + + Update Thorium + + + + + Update Thorium + + + + Important security improvements and new features are available in the latest version. + + + + + {SECONDS, plural, + =1 {Thorium will restart in 1 second} + other {Thorium will restart in # seconds}} + + + Please restart Thorium now + + + A special security update for Thorium was just applied. Restart now and we'll restore your tabs. + + + + + Thorium Tab + + + + + + Thorium needs permission to access your camera to create a 3D map of your surroundings + + + Thorium needs permission to access your camera for this site + + + Thorium needs permission to access your microphone for this site + + + Thorium needs permission to access your camera and microphone for this site + + + Thorium needs access to your location to share your location with this site + + + Thorium needs storage access to download files + + + + Thorium needs camera permission to create a 3D map of your surroundings + + + Thorium needs camera permission for this site + + + Thorium needs microphone permission for this site + + + Thorium needs camera and microphone permissions for this site + + + Thorium needs location permission for this site + + + Thorium needs storage access permission to download files + + + + + + Once Thorium has access, websites will be able to ask you for access. + + + + + + + Please wait while Thorium installs the latest system updates. + + + Chromium OS terms + + + + + + + Welcome to Thorium + + + + + + + Go to Thorium notification settings + + + + + + + {0, plural, + =0 {A Thorium update is available} + =1 {A Thorium update is available} + other {A Thorium update has been available for # days}} + + + {COUNT, plural, + =0 {Your administrator asks that you relaunch Thorium to apply this update} + =1 {Your administrator asks that you relaunch Thorium to apply this update. Your Incognito window won't reopen.} + other {Your administrator asks that you relaunch Thorium to apply this update. Your # Incognito windows won't reopen.}} + + + {0, plural, + =1 {Relaunch Thorium within a day} + other {Relaunch Thorium within # days}} + + + {0, plural, + =1 {Thorium will relaunch in an hour} + other {Thorium will relaunch in # hours}} + + + {0, plural, + =1 {Thorium will relaunch in 1 minute} + other {Thorium will relaunch in # minutes}} + + + {0, plural, + =0 {Thorium will relaunch now} + =1 {Thorium will relaunch in 1 second} + other {Thorium will relaunch in # seconds}} + + + {COUNT, plural, + =0 {Your administrator requires that you relaunch Thorium to apply an update} + =1 {Your administrator requires that you relaunch Thorium to apply an update. Your Incognito window won't reopen.} + other {Your administrator requires that you relaunch Thorium to apply an update. Your # Incognito windows won't reopen.}} + + + + + + + Launching Thorium... + + + Couldn't launch Thorium. Try again. + + + Relaunch Thorium + + + + Share a Thorium tab + + + + + Help us improve Thorium + + + + + + Your parent has turned off "Permissions for sites, apps and extensions" for Thorium. Adding this $1extension is not allowed. + + + Your parent has turned off "Permissions for sites, apps and extensions" for Thorium. Enabling this $1extension is not allowed. + + + + + + + + Welcome to Thorium profiles + + + Who's using Thorium? + + + With Thorium profiles you can separate all your Thorium stuff. Create profiles for friends and family, or split between work and fun. + + + Set up your new Thorium profile + + + To access your Thorium stuff across all your devices, sign in, then turn on sync. + + + To access your Thorium stuff across all your devices, sign in so that you can turn on sync. + + + Customize your Thorium profile + + + Each profile holds its own Thorium info like bookmarks, history, passwords, and more + + + If you share a device, friends and family can browse separately and set up Thorium just the way they want + + + Switch to existing Thorium profile? + + + A Thorium profile with this account already exists on this device + + + + The following accounts are not signed into any Chromium profiles. If you want to use an account in another profile, remove that profile first. + + + + + + You can switch between Thorium profiles here + + + $1CTRL+SHIFT+M can switch between Thorium profiles + + + + + + + Thorium OS system + + + + + + Your changes will take effect the next time you relaunch Thorium. + + + + + Thorium needs Bluetooth access to continue pairing. $1Open Preferences + + + + diff --git a/chrome/app/theme/chromium/BRANDING b/chrome/app/theme/chromium/BRANDING new file mode 100644 index 00000000..2a6ce563 --- /dev/null +++ b/chrome/app/theme/chromium/BRANDING @@ -0,0 +1,10 @@ +COMPANY_FULLNAME=The Chromium and Thorium Authors +COMPANY_SHORTNAME=The Chromium and Thorium Authors +PRODUCT_FULLNAME=Thorium +PRODUCT_SHORTNAME=Thorium +PRODUCT_INSTALLER_FULLNAME=Thorium Installer +PRODUCT_INSTALLER_SHORTNAME=Thorium Installer +COPYRIGHT=Copyright @LASTCHANGE_YEAR@ The Chromium and Thorium Authors. All rights reserved. +MAC_BUNDLE_ID=org.chromium.Chromium +MAC_CREATOR_CODE=Cr24 +MAC_TEAM_ID= diff --git a/chrome/app/theme/chromium/chromeos/chrome_app_icon_192.png b/chrome/app/theme/chromium/chromeos/chrome_app_icon_192.png new file mode 100644 index 00000000..a85e0c78 Binary files /dev/null and b/chrome/app/theme/chromium/chromeos/chrome_app_icon_192.png differ diff --git a/chrome/app/theme/chromium/chromeos/chrome_app_icon_32.png b/chrome/app/theme/chromium/chromeos/chrome_app_icon_32.png new file mode 100644 index 00000000..afd6cfec Binary files /dev/null and b/chrome/app/theme/chromium/chromeos/chrome_app_icon_32.png differ diff --git a/chrome/app/theme/chromium/linux/product_logo_32.xpm b/chrome/app/theme/chromium/linux/product_logo_32.xpm new file mode 100644 index 00000000..0f87a790 --- /dev/null +++ b/chrome/app/theme/chromium/linux/product_logo_32.xpm @@ -0,0 +1,297 @@ +/* XPM */ +static char * product_logo_32_xpm[] = { +"32 32 262 2", +" c None", +". c #4E7ADB", +"+ c #4C78DB", +"@ c #4C79DB", +"# c #4A77DA", +"$ c #4B78DA", +"% c #4D7ADB", +"& c #4776DA", +"* c #4272D9", +"= c #4172D9", +"- c #4171D9", +"; c #4071D9", +"> c #3F70D8", +", c #3E6FD8", +"' c #3D6FD8", +") c #4775DA", +"! c #4F7BDC", +"~ c #4574DA", +"{ c #4474D9", +"] c #4373D9", +"^ c #3D6ED8", +"/ c #3C6ED8", +"( c #4675DA", +"_ c #4976D8", +": c #4575D9", +"< c #3B6DD8", +"[ c #3F6FD8", +"} c #4773D5", +"| c #4573D6", +"1 c #4574D8", +"2 c #3A6CD7", +"3 c #3C6DD7", +"4 c #4670D2", +"5 c #4471D4", +"6 c #4472D5", +"7 c #4473D7", +"8 c #4573D9", +"9 c #396CD7", +"0 c #3B6DD7", +"a c #456ECF", +"b c #436ED1", +"c c #446FD2", +"d c #4470D4", +"e c #4471D6", +"f c #4472D8", +"g c #396BD7", +"h c #3B6CD7", +"i c #6C9AEE", +"j c #4169CB", +"k c #426DCF", +"l c #436FD3", +"m c #4370D5", +"n c #4372D7", +"o c #4372D8", +"p c #6BA0F7", +"q c #5580D9", +"r c #416ACD", +"s c #426CCF", +"t c #426ED1", +"u c #426FD3", +"v c #4270D5", +"w c #4271D7", +"x c #4272D8", +"y c #4172D8", +"z c #3861BC", +"A c #294992", +"B c #243C74", +"C c #314070", +"D c #44568D", +"E c #5C72B4", +"F c #6F87CE", +"G c #7089D0", +"H c #728CD2", +"I c #748ED3", +"J c #7590D5", +"K c #7892D6", +"L c #7994D9", +"M c #7B97DB", +"N c #7E9ADE", +"O c #809DDF", +"P c #6EA1F7", +"Q c #699DF3", +"R c #4369C7", +"S c #406ACD", +"T c #416CD0", +"U c #416DD2", +"V c #416FD4", +"W c #4170D5", +"X c #3D69C9", +"Y c #1F3974", +"Z c #000000", +"` c #1D3A6B", +" . c #315CA3", +".. c #305BA3", +"+. c #1C3A6B", +"@. c #414E74", +"#. c #7D92CD", +"$. c #8099D8", +"%. c #829BDB", +"&. c #86A1DE", +"*. c #8AA6E2", +"=. c #8DA9E6", +"-. c #8FACE8", +";. c #91ADE9", +">. c #91AFEA", +",. c #95B2EC", +"'. c #6A9FF7", +"). c #6B9FF7", +"!. c #5B88E0", +"~. c #3D63C5", +"{. c #3F6ACE", +"]. c #406CD1", +"^. c #406ED3", +"/. c #3C68C7", +"(. c #11234C", +"_. c #203863", +":. c #457CD9", +"<. c #4E8DF5", +"[. c #4D8CF5", +"}. c #4C8CF5", +"|. c #427BD9", +"1. c #1F3863", +"2. c #2B354F", +"3. c #89A2DA", +"4. c #91ADE8", +"5. c #92AFEA", +"6. c #93B0EB", +"7. c #94B1EC", +"8. c #95B2ED", +"9. c #96B4ED", +"0. c #97B5EE", +"a. c #98B6EF", +"b. c #486FCC", +"c. c #3E65C7", +"d. c #3F6ACF", +"e. c #1F3872", +"f. c #4D8AF1", +"g. c #4C8BF5", +"h. c #4A88F1", +"i. c #1E3763", +"j. c #596888", +"k. c #96B3ED", +"l. c #98B5EF", +"m. c #99B7F0", +"n. c #9AB9F1", +"o. c #9BBAF2", +"p. c #9CBBF3", +"q. c #9DBCF5", +"r. c #699EF7", +"s. c #6393EB", +"t. c #3C63C5", +"u. c #3D65C9", +"v. c #365CB6", +"w. c #4B8BF5", +"x. c #407AD8", +"y. c #8DA6D7", +"z. c #9DBBF3", +"A. c #9EBCF4", +"B. c #9FBEF5", +"C. c #9FBFF6", +"D. c #A0C0F7", +"E. c #A1C1F8", +"F. c #A1C2F9", +"G. c #A2C3FA", +"H. c #4F7AD5", +"I. c #3C64C8", +"J. c #27438A", +"K. c #4A8AF4", +"L. c #498AF4", +"M. c #1B396A", +"N. c #778AB0", +"O. c #A2C1F8", +"P. c #A3C2F9", +"Q. c #A3C3F9", +"R. c #A3C3FA", +"S. c #689EF7", +"T. c #6599F1", +"U. c #3F66C8", +"V. c #23386F", +"W. c #2D59A2", +"X. c #75819B", +"Y. c #A4C4FA", +"Z. c #679DF7", +"`. c #5583DE", +" + c #24386F", +".+ c #4989F4", +"++ c #76829C", +"@+ c #A2C2FA", +"#+ c #679CF6", +"$+ c #669BF5", +"%+ c #335092", +"&+ c #1A386A", +"*+ c #7C8FB2", +"=+ c #A1C1F9", +"-+ c #669CF6", +";+ c #679DF6", +">+ c #5684D3", +",+ c #3F79D8", +"'+ c #97B1DF", +")+ c #A1C1FA", +"!+ c #3A588B", +"~+ c #4988F1", +"{+ c #4786F0", +"]+ c #1D3763", +"^+ c #64728E", +"/+ c #6194E9", +"(+ c #263B5E", +"_+ c #1D2D50", +":+ c #9FBAEC", +"<+ c #A1C2FA", +"[+ c #649AF4", +"}+ c #659BF6", +"|+ c #3B598B", +"1+ c #334D80", +"2+ c #83A2E1", +"3+ c #9FC0F8", +"4+ c #649BF6", +"5+ c #5B8BDA", +"6+ c #476CAB", +"7+ c #3C5888", +"8+ c #3F5A8A", +"9+ c #4A6AA8", +"0+ c #537ECE", +"a+ c #5F8CE5", +"b+ c #A2C0F7", +"c+ c #6399F4", +"d+ c #639AF6", +"e+ c #629AF6", +"f+ c #5E92EE", +"g+ c #5A8CE8", +"h+ c #8AACEE", +"i+ c #9EBFF7", +"j+ c #649AF5", +"k+ c #6299F6", +"l+ c #5B8FEB", +"m+ c #6994E8", +"n+ c #A0C1F9", +"o+ c #6399F5", +"p+ c #6096F3", +"q+ c #5B8DEA", +"r+ c #92B3F1", +"s+ c #6299F5", +"t+ c #6199F6", +"u+ c #5E94F1", +"v+ c #749CEB", +"w+ c #6198F5", +"x+ c #5B8FEC", +"y+ c #9BBCF6", +"z+ c #5F95F2", +"A+ c #5F96F2", +"B+ c #80A6EF", +"C+ c #9CBDF6", +"D+ c #6098F5", +"E+ c #6097F5", +"F+ c #6193EE", +"G+ c #9FC0F9", +"H+ c #9DBDF6", +"I+ c #5D93F1", +"J+ c #5B91EE", +"K+ c #82A6ED", +"L+ c #9DBDF5", +"M+ c #9CBCF5", +" ", +" . + @ + # $ ", +" % & * = = - ; > , ' - ) ", +" ! ~ { ] * * = - ; > , , ' ^ / ( ", +" _ : ~ { ] ] * = - ; > , , ' ^ / < [ ", +" } | 1 ~ { ] ] * = - ; > , , ' ^ / < 2 3 ", +" 4 5 6 7 8 { ] * * = - ; > , , ' ^ / < 2 9 0 ", +" a b c d e f { ] * = = - ; > , ' ' ^ / < 2 9 g h ", +" i j k b l m n o ] * = - - ; > , ' ^ ^ / < 2 9 g g [ ", +" p q r s t u v w x y z A B C D E F G H I J K L M N O ", +" P p Q R S T U V W X Y Z ` ...+.Z @.#.$.%.&.*.=.-.;.>.,. ", +" '.).).!.~.{.].^./.(._.:.<.[.[.}.|.1.2.3.4.5.6.7.8.9.0.a. ", +" '.'.'.).b.c.d.].e._.f.<.<.[.}.}.g.h.i.j.k.l.a.m.n.o.p.q. ", +" r.r.r.r.r.s.t.u.v.Z :.<.<.[.}.}.g.w.w.x.Z y.z.A.B.C.D.E.F.G. ", +" '.r.r.r.r.r.H.I.J.` <.<.[.}.}.g.g.w.K.L.M.N.O.P.Q.R.R.G.G.R. ", +" S.S.r.r.r.r.T.U.V. .[.[.}.}.g.g.w.K.K.L.W.X.Y.Y.R.R.R.G.G.G. ", +" Z.Z.S.S.S.S.S.`. +..[.}.}.g.g.w.K.K.L..+W.++Y.Y.R.R.G.G.G.@+ ", +" #+Z.Z.Z.Z.Z.Z.$+%++.}.g.g.g.w.K.K.L.L..+&+*+Y.R.R.R.G.G.@+=+ ", +" -+;+;+;+;+;+;+;+>+Z |.g.w.w.K.K.L.L..+,+Z '+R.R.R.G.G.G.@+)+ ", +" -+-+-+-+-+-+-+-+!+1.~+w.K.K.L.L..+{+]+^+Y.R.R.R.G.G.@+@+ ", +" -+-+-+-+-+-+-+-+/+(+1.x.L.L..+.+,+]+_+:+R.R.R.G.G.G.@+<+ ", +" [+}+-+-+-+}+}+}+}+/+|+Z M.W.W.&+Z 1+2+R.R.R.G.G.G.@+<+3+ ", +" }+}+}+}+}+4+4+4+4+}+5+6+7+8+9+0+a+b+R.R.G.G.G.@+@+<+ ", +" c+4+4+4+4+4+4+4+4+4+d+d+d+e+f+g+h+R.R.G.G.G.@+@+<+i+ ", +" j+4+4+4+4+4+d+d+d+d+e+e+k+l+m+P.R.G.G.G.@+@+<+n+ ", +" o+d+d+d+d+d+e+e+e+e+k+p+q+r+G.G.G.G.@+@+<+n+ ", +" s+e+e+e+e+e+k+k+k+t+u+v+R.G.G.G.@+@+<+n+ ", +" w+k+k+k+k+k+t+t+w+x+y+G.G.G.@+@+<+3+ ", +" z+t+t+t+t+t+t+A+B+G.G.@+@+<+<+C+ ", +" z+D+t+t+E+F+E.@+@+@+G+H+ ", +" I+J+K+L+H+M+ ", +" "}; diff --git a/chrome/app/theme/chromium/product_logo_128.png b/chrome/app/theme/chromium/product_logo_128.png new file mode 100644 index 00000000..1fb0913c Binary files /dev/null and b/chrome/app/theme/chromium/product_logo_128.png differ diff --git a/chrome/app/theme/chromium/product_logo_24.png b/chrome/app/theme/chromium/product_logo_24.png new file mode 100644 index 00000000..000900fb Binary files /dev/null and b/chrome/app/theme/chromium/product_logo_24.png differ diff --git a/chrome/app/theme/chromium/product_logo_256.png b/chrome/app/theme/chromium/product_logo_256.png new file mode 100644 index 00000000..17439c5e Binary files /dev/null and b/chrome/app/theme/chromium/product_logo_256.png differ diff --git a/chrome/app/theme/chromium/product_logo_48.png b/chrome/app/theme/chromium/product_logo_48.png new file mode 100644 index 00000000..0782a2b0 Binary files /dev/null and b/chrome/app/theme/chromium/product_logo_48.png differ diff --git a/chrome/app/theme/chromium/product_logo_64.png b/chrome/app/theme/chromium/product_logo_64.png new file mode 100644 index 00000000..8a87650f Binary files /dev/null and b/chrome/app/theme/chromium/product_logo_64.png differ diff --git a/chrome/app/theme/chromium/win/chromium.ico b/chrome/app/theme/chromium/win/chromium.ico new file mode 100644 index 00000000..7817fce4 Binary files /dev/null and b/chrome/app/theme/chromium/win/chromium.ico differ diff --git a/chrome/app/theme/chromium/win/tiles/Logo.png b/chrome/app/theme/chromium/win/tiles/Logo.png new file mode 100644 index 00000000..2aa0a320 Binary files /dev/null and b/chrome/app/theme/chromium/win/tiles/Logo.png differ diff --git a/chrome/app/theme/chromium/win/tiles/SmallLogo.png b/chrome/app/theme/chromium/win/tiles/SmallLogo.png new file mode 100644 index 00000000..695a23c2 Binary files /dev/null and b/chrome/app/theme/chromium/win/tiles/SmallLogo.png differ diff --git a/chrome/app/theme/default_100_percent/chromium/product_logo_16.png b/chrome/app/theme/default_100_percent/chromium/product_logo_16.png new file mode 100644 index 00000000..97993fa6 Binary files /dev/null and b/chrome/app/theme/default_100_percent/chromium/product_logo_16.png differ diff --git a/chrome/app/theme/default_100_percent/chromium/product_logo_32.png b/chrome/app/theme/default_100_percent/chromium/product_logo_32.png new file mode 100644 index 00000000..afd6cfec Binary files /dev/null and b/chrome/app/theme/default_100_percent/chromium/product_logo_32.png differ diff --git a/chrome/app/theme/default_100_percent/chromium/product_logo_name_22.png b/chrome/app/theme/default_100_percent/chromium/product_logo_name_22.png new file mode 100644 index 00000000..084114c4 Binary files /dev/null and b/chrome/app/theme/default_100_percent/chromium/product_logo_name_22.png differ diff --git a/chrome/app/theme/default_100_percent/chromium/product_logo_name_22_white.png b/chrome/app/theme/default_100_percent/chromium/product_logo_name_22_white.png new file mode 100644 index 00000000..63b1749a Binary files /dev/null and b/chrome/app/theme/default_100_percent/chromium/product_logo_name_22_white.png differ diff --git a/chrome/app/theme/default_100_percent/chromium/webstore_icon.png b/chrome/app/theme/default_100_percent/chromium/webstore_icon.png new file mode 100644 index 00000000..4c8d1317 Binary files /dev/null and b/chrome/app/theme/default_100_percent/chromium/webstore_icon.png differ diff --git a/chrome/app/theme/default_100_percent/chromium/webstore_icon_16.png b/chrome/app/theme/default_100_percent/chromium/webstore_icon_16.png new file mode 100644 index 00000000..b5cad10a Binary files /dev/null and b/chrome/app/theme/default_100_percent/chromium/webstore_icon_16.png differ diff --git a/chrome/app/theme/default_100_percent/chromium/webstore_icon_24.png b/chrome/app/theme/default_100_percent/chromium/webstore_icon_24.png new file mode 100644 index 00000000..2479c5ec Binary files /dev/null and b/chrome/app/theme/default_100_percent/chromium/webstore_icon_24.png differ diff --git a/chrome/app/theme/default_100_percent/chromium/webstore_icon_32.png b/chrome/app/theme/default_100_percent/chromium/webstore_icon_32.png new file mode 100644 index 00000000..4c02dc21 Binary files /dev/null and b/chrome/app/theme/default_100_percent/chromium/webstore_icon_32.png differ diff --git a/chrome/app/theme/default_200_percent/chromium/product_logo_16.png b/chrome/app/theme/default_200_percent/chromium/product_logo_16.png new file mode 100644 index 00000000..afd6cfec Binary files /dev/null and b/chrome/app/theme/default_200_percent/chromium/product_logo_16.png differ diff --git a/chrome/app/theme/default_200_percent/chromium/product_logo_32.png b/chrome/app/theme/default_200_percent/chromium/product_logo_32.png new file mode 100644 index 00000000..8a87650f Binary files /dev/null and b/chrome/app/theme/default_200_percent/chromium/product_logo_32.png differ diff --git a/chrome/app/theme/default_200_percent/chromium/product_logo_name_22.png b/chrome/app/theme/default_200_percent/chromium/product_logo_name_22.png new file mode 100644 index 00000000..ae8c3096 Binary files /dev/null and b/chrome/app/theme/default_200_percent/chromium/product_logo_name_22.png differ diff --git a/chrome/app/theme/default_200_percent/chromium/product_logo_name_22_white.png b/chrome/app/theme/default_200_percent/chromium/product_logo_name_22_white.png new file mode 100644 index 00000000..729322ff Binary files /dev/null and b/chrome/app/theme/default_200_percent/chromium/product_logo_name_22_white.png differ diff --git a/chrome/app/theme/default_200_percent/chromium/webstore_icon.png b/chrome/app/theme/default_200_percent/chromium/webstore_icon.png new file mode 100644 index 00000000..d3e52a0d Binary files /dev/null and b/chrome/app/theme/default_200_percent/chromium/webstore_icon.png differ diff --git a/chrome/app/theme/default_200_percent/chromium/webstore_icon_16.png b/chrome/app/theme/default_200_percent/chromium/webstore_icon_16.png new file mode 100644 index 00000000..d4030e74 Binary files /dev/null and b/chrome/app/theme/default_200_percent/chromium/webstore_icon_16.png differ diff --git a/chrome/app/theme/default_200_percent/chromium/webstore_icon_24.png b/chrome/app/theme/default_200_percent/chromium/webstore_icon_24.png new file mode 100644 index 00000000..a08d38df Binary files /dev/null and b/chrome/app/theme/default_200_percent/chromium/webstore_icon_24.png differ diff --git a/chrome/app/theme/default_200_percent/chromium/webstore_icon_32.png b/chrome/app/theme/default_200_percent/chromium/webstore_icon_32.png new file mode 100644 index 00000000..9a21a6f9 Binary files /dev/null and b/chrome/app/theme/default_200_percent/chromium/webstore_icon_32.png differ diff --git a/chrome/browser/background/background_mode_manager.cc b/chrome/browser/background/background_mode_manager.cc new file mode 100644 index 00000000..aa2ca312 --- /dev/null +++ b/chrome/browser/background/background_mode_manager.cc @@ -0,0 +1,1042 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/background/background_mode_manager.h" + +#include + +#include +#include +#include +#include +#include + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/command_line.h" +#include "base/containers/contains.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/metrics/histogram_macros.h" +#include "base/metrics/user_metrics.h" +#include "base/one_shot_event.h" +#include "base/strings/utf_string_conversions.h" +#include "base/task/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/apps/app_service/app_service_proxy.h" +#include "chrome/browser/apps/app_service/app_service_proxy_factory.h" +#include "chrome/browser/apps/app_service/browser_app_launcher.h" +#include "chrome/browser/background/background_application_list_model.h" +#include "chrome/browser/background/background_mode_optimizer.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/lifetime/application_lifetime.h" +#include "chrome/browser/lifetime/browser_shutdown.h" +#include "chrome/browser/policy/profile_policy_connector.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_attributes_entry.h" +#include "chrome/browser/profiles/profile_keep_alive_types.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/profiles/scoped_profile_keep_alive.h" +#include "chrome/browser/status_icons/status_icon.h" +#include "chrome/browser/status_icons/status_tray.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/browser_dialogs.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/chrome_pages.h" +#include "chrome/browser/ui/extensions/app_launch_params.h" +#include "chrome/browser/ui/profile_picker.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/webui_url_constants.h" +#include "chrome/grit/chrome_unscaled_resources.h" +#include "chrome/grit/chromium_strings.h" +#include "chrome/grit/generated_resources.h" +#include "components/keep_alive_registry/keep_alive_registry.h" +#include "components/keep_alive_registry/keep_alive_types.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/startup_metric_utils/browser/startup_metric_utils.h" +#include "content/public/browser/notification_service.h" +#include "extensions/browser/extension_system.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension.h" +#include "extensions/common/manifest_handlers/options_page_info.h" +#include "extensions/common/permissions/permission_set.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/models/image_model.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/image/image_family.h" + +#if defined(OS_WIN) +#include "chrome/browser/win/app_icon.h" +#endif + +using base::UserMetricsAction; +using extensions::Extension; + +namespace { + +// Enum for recording menu item clicks in UMA. +// NOTE: Do not renumber these as that would confuse interpretation of +// previously logged data. When making changes, also update histograms.xml. +enum MenuItem { + MENU_ITEM_ABOUT = 0, + MENU_ITEM_TASK_MANAGER = 1, + MENU_ITEM_BACKGROUND_CLIENT = 2, + MENU_ITEM_KEEP_RUNNING = 3, + MENU_ITEM_EXIT = 4, + MENU_ITEM_NUM_STATES +}; + +void RecordMenuItemClick(MenuItem item) { + UMA_HISTOGRAM_ENUMERATION("BackgroundMode.MenuItemClick", item, + MENU_ITEM_NUM_STATES); +} +} // namespace + +// static +bool BackgroundModeManager::should_restart_in_background_ = false; + +BackgroundModeManager::BackgroundModeData::BackgroundModeData( + BackgroundModeManager* manager, + Profile* profile, + CommandIdHandlerVector* command_id_handler_vector) + : manager_(manager), + applications_(std::make_unique(profile)), + profile_(profile), + command_id_handler_vector_(command_id_handler_vector) { + profile_observation_.Observe(profile_); +} + +BackgroundModeManager::BackgroundModeData::~BackgroundModeData() = default; + +void BackgroundModeManager::BackgroundModeData::SetTracker( + extensions::ForceInstalledTracker* tracker) { + force_installed_tracker_observation_.Observe(tracker); +} + +void BackgroundModeManager::BackgroundModeData::UpdateProfileKeepAlive() { + bool background_mode = + (HasPersistentBackgroundClient() && manager_->IsBackgroundModeActive() && + !manager_->background_mode_suspended_); + if (!background_mode) { + profile_keep_alive_.reset(); + return; + } + + if (profile_keep_alive_) + return; + if (!g_browser_process->profile_manager()->IsValidProfile(profile_)) { + // ScopedProfileKeepAlive will cause issues if we create it now. Wait for + // OnProfileAdded(). + return; + } + + profile_keep_alive_ = std::make_unique( + profile_, ProfileKeepAliveOrigin::kBackgroundMode); +} + +void BackgroundModeManager::BackgroundModeData::OnProfileWillBeDestroyed( + Profile* profile) { + DCHECK_EQ(profile_, profile); + profile_observation_.Reset(); + force_installed_tracker_observation_.Reset(); + DCHECK(!profile_keep_alive_); + profile_ = nullptr; + // Remove this Profile* from |background_mode_data|. + bool did_unregister = manager_->UnregisterProfile(profile); + DCHECK(did_unregister); +} + +/////////////////////////////////////////////////////////////////////////////// +// BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides +void BackgroundModeManager::BackgroundModeData::ExecuteCommand( + int command_id, + int event_flags) { + switch (command_id) { + case IDC_MinimumLabelValue: + // Do nothing. This is just a label. + break; + default: + DCHECK(!command_id_handler_vector_->at(command_id).is_null()); + RecordMenuItemClick(MENU_ITEM_BACKGROUND_CLIENT); + command_id_handler_vector_->at(command_id).Run(); + break; + } +} + +void BackgroundModeManager::BackgroundModeData:: + OnForceInstalledExtensionsReady() { + manager_->ReleaseForceInstalledExtensionsKeepAlive(); +} + +Browser* BackgroundModeManager::BackgroundModeData::GetBrowserWindow() { + return BackgroundModeManager::GetBrowserWindowForProfile(profile_); +} + +bool BackgroundModeManager::BackgroundModeData::HasPersistentBackgroundClient() + const { + return applications_->HasPersistentBackgroundApps() || + manager_->keep_alive_for_test_; +} + +bool BackgroundModeManager::BackgroundModeData::HasAnyBackgroundClient() const { + return applications_->size() > 0; +} + +void BackgroundModeManager::BackgroundModeData::BuildProfileMenu( + StatusIconMenuModel* menu, + StatusIconMenuModel* containing_menu) { + if (HasAnyBackgroundClient()) { + // Add a menu item for each application (extension). + for (const auto& application : *applications_) { + gfx::ImageSkia icon = applications_->GetIcon(application.get()); + const std::string& name = application->name(); + int command_id = command_id_handler_vector_->size(); + // Check that the command ID is within the dynamic range. + DCHECK_LT(command_id, IDC_MinimumLabelValue); + command_id_handler_vector_->push_back(base::BindRepeating( + &BackgroundModeManager::LaunchBackgroundApplication, profile_, + base::RetainedRef(application))); + menu->AddItem(command_id, base::UTF8ToUTF16(name)); + if (!icon.isNull()) + menu->SetIcon(menu->GetItemCount() - 1, + ui::ImageModel::FromImageSkia(icon)); + + // Component extensions with background that do not have an options page + // will cause this menu item to go to the extensions page with an + // absent component extension. + // + // Ideally, we would remove this item, but this conflicts with the user + // model where this menu shows the extensions with background. + // + // The compromise is to disable the item, avoiding the non-actionable + // navigate to the extensions page and preserving the user model. + if (application->location() == + extensions::mojom::ManifestLocation::kComponent) { + GURL options_page = + extensions::OptionsPageInfo::GetOptionsPage(application.get()); + if (!options_page.is_valid()) + menu->SetCommandIdEnabled(command_id, false); + } + } + + } else { + // When there are no background clients, we want to display just a label + // stating that none are running. + menu->AddItemWithStringId(IDC_MinimumLabelValue, + IDS_BACKGROUND_APP_NOT_INSTALLED); + menu->SetCommandIdEnabled(IDC_MinimumLabelValue, false); + } + if (containing_menu) { + int menu_command_id = command_id_handler_vector_->size(); + // Check that the command ID is within the dynamic range. + DCHECK_LT(menu_command_id, IDC_MinimumLabelValue); + command_id_handler_vector_->push_back(base::DoNothing()); + containing_menu->AddSubMenu(menu_command_id, name_, menu); + } +} + +void BackgroundModeManager::BackgroundModeData::SetName( + const std::u16string& new_profile_name) { + name_ = new_profile_name; +} + +std::u16string BackgroundModeManager::BackgroundModeData::name() { + return name_; +} + +std::set +BackgroundModeManager::BackgroundModeData::GetNewBackgroundApps() { + std::set new_apps; + + // Copy all current extensions into our list of |current_extensions_|. + for (const auto& application : *applications_) { + const extensions::ExtensionId& id = application->id(); + if (current_extensions_.count(id) == 0) { + // Not found in our set yet - add it and maybe return as a previously + // unseen extension. + current_extensions_.insert(id); + // If this application has been newly loaded after the initial startup and + // this is a persistent background app, notify the user. + if (applications_->startup_done() && + BackgroundApplicationListModel::IsPersistentBackgroundApp( + *application, profile_)) { + new_apps.insert(application.get()); + } + } + } + return new_apps; +} + +// static +bool BackgroundModeManager::BackgroundModeData::BackgroundModeDataCompare( + const BackgroundModeData* bmd1, + const BackgroundModeData* bmd2) { + return bmd1->name_ < bmd2->name_; +} + +/////////////////////////////////////////////////////////////////////////////// +// BackgroundModeManager, public +BackgroundModeManager::BackgroundModeManager( + const base::CommandLine& command_line, + ProfileAttributesStorage* profile_storage) + : profile_storage_(profile_storage), task_runner_(CreateTaskRunner()) { + // We should never start up if there is no browser process or if we are + // currently quitting. + CHECK(g_browser_process); + CHECK(!browser_shutdown::IsTryingToQuit()); + + // Add self as an observer for the ProfileAttributesStorage so we know when + // profiles are deleted and their names change. + // This observer is never unregistered because the BackgroundModeManager + // outlives the profile storage. + profile_storage_->AddObserver(this); + + UMA_HISTOGRAM_BOOLEAN("BackgroundMode.OnStartup.AutoLaunchState", + command_line.HasSwitch(switches::kNoStartupWindow)); + UMA_HISTOGRAM_BOOLEAN("BackgroundMode.OnStartup.IsBackgroundModePrefEnabled", + IsBackgroundModePrefEnabled()); + + // Listen for the background mode preference changing. + if (g_browser_process->local_state()) { // Skip for unit tests + pref_registrar_.Init(g_browser_process->local_state()); + pref_registrar_.Add( + prefs::kBackgroundModeEnabled, + base::BindRepeating( + &BackgroundModeManager::OnBackgroundModeEnabledPrefChanged, + base::Unretained(this))); + } + + // Keep the browser alive until extensions are done loading - this is needed + // by the --no-startup-window flag. We want to stay alive until we load + // extensions, at which point we should either run in background mode (if + // there are background apps) or exit if there are none. + if (command_line.HasSwitch(switches::kNoStartupWindow)) { + keep_alive_for_startup_ = std::make_unique( + KeepAliveOrigin::BACKGROUND_MODE_MANAGER_STARTUP, + KeepAliveRestartOption::DISABLED); + // Wait for force-installed extensions to install, as well. + keep_alive_for_force_installed_extensions_ = + std::make_unique( + KeepAliveOrigin::BACKGROUND_MODE_MANAGER_FORCE_INSTALLED_EXTENSIONS, + KeepAliveRestartOption::DISABLED); + } else { + // Otherwise, start with background mode suspended in case we're launching + // in a mode that doesn't open a browser window. It will be resumed when the + // first browser window is opened. + SuspendBackgroundMode(); + optimizer_ = BackgroundModeOptimizer::Create(); + } + + // If the -keep-alive-for-test flag is passed, then always keep chrome running + // in the background until the user explicitly terminates it. + if (command_line.HasSwitch(switches::kKeepAliveForTest)) + keep_alive_for_test_ = true; + + if (ShouldBeInBackgroundMode()) + StartBackgroundMode(); + + // Listen for the application shutting down so we can release our KeepAlive. + registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING, + content::NotificationService::AllSources()); + BrowserList::AddObserver(this); +} + +BackgroundModeManager::~BackgroundModeManager() { + // Remove ourselves from the application observer list (only needed by unit + // tests since APP_TERMINATING is what does this in a real running system). + for (const auto& it : background_mode_data_) + it.second->applications()->RemoveObserver(this); + BrowserList::RemoveObserver(this); + + // We're going away, so exit background mode (does nothing if we aren't in + // background mode currently). This is primarily needed for unit tests, + // because in an actual running system we'd get an APP_TERMINATING + // notification before being destroyed. + EndBackgroundMode(); +} + +// static +void BackgroundModeManager::RegisterPrefs(PrefRegistrySimple* registry) { +#if defined(OS_MAC) + registry->RegisterBooleanPref(prefs::kUserRemovedLoginItem, false); + registry->RegisterBooleanPref(prefs::kChromeCreatedLoginItem, false); + registry->RegisterBooleanPref(prefs::kMigratedLoginItemPref, false); +#endif + registry->RegisterBooleanPref(prefs::kBackgroundModeEnabled, false); +} + +void BackgroundModeManager::RegisterProfile(Profile* profile) { + // We don't want to register multiple times for one profile. + DCHECK(!base::Contains(background_mode_data_, profile)); + auto bmd = std::make_unique(this, profile, + &command_id_handler_vector_); + BackgroundModeData* bmd_ptr = bmd.get(); + background_mode_data_[profile] = std::move(bmd); + + // Initially set the name for this background mode data. + std::u16string name = l10n_util::GetStringUTF16(IDS_PROFILES_DEFAULT_NAME); + ProfileAttributesEntry* entry = + profile_storage_->GetProfileAttributesWithPath(profile->GetPath()); + if (entry) { + name = entry->GetName(); + } + bmd_ptr->SetName(name); + + // Check for the presence of background apps after all extensions have been + // loaded, to handle the case where an extension has been manually removed + // while Chrome was not running. + extensions::ExtensionSystem::Get(profile)->ready().Post( + FROM_HERE, base::BindOnce(&BackgroundModeManager::OnExtensionsReady, + weak_factory_.GetWeakPtr(), profile)); + + bmd_ptr->applications()->AddObserver(this); + + // If we're adding a new profile and running in multi-profile mode, this new + // profile should be added to the status icon if one currently exists. + if (in_background_mode_ && status_icon_) + UpdateStatusTrayIconContextMenu(); +} + +bool BackgroundModeManager::UnregisterProfile(Profile* profile) { + // Remove the profile from our map of profiles. + auto it = background_mode_data_.find(profile); + // If a profile isn't running a background app, it may not be in the map. + if (it == background_mode_data_.end()) + return false; + + it->second->applications()->RemoveObserver(this); + background_mode_data_.erase(it); + // If there are no background mode profiles any longer, then turn off + // background mode. + UpdateEnableLaunchOnStartup(); + if (!ShouldBeInBackgroundMode()) { + EndBackgroundMode(); + } + UpdateStatusTrayIconContextMenu(); + + return true; +} + +// static +void BackgroundModeManager::LaunchBackgroundApplication( + Profile* profile, + const Extension* extension) { + apps::AppServiceProxyFactory::GetForProfile(profile) + ->BrowserAppLauncher() + ->LaunchAppWithParams(CreateAppLaunchParamsUserContainer( + profile, extension, WindowOpenDisposition::NEW_FOREGROUND_TAB, + apps::mojom::LaunchSource::kFromBackgroundMode)); +} + +// static +Browser* BackgroundModeManager::GetBrowserWindowForProfile(Profile* profile) { + Browser* browser = chrome::FindLastActiveWithProfile(profile); + return browser ? browser : chrome::OpenEmptyWindow(profile); +} + +bool BackgroundModeManager::IsBackgroundModeActive() { + return in_background_mode_; +} + +bool BackgroundModeManager::IsBackgroundWithoutWindows() const { + return KeepAliveRegistry::GetInstance()->WouldRestartWithout({ + // Transient startup related KeepAlives, not related to any UI. + KeepAliveOrigin::SESSION_RESTORE, + KeepAliveOrigin::BACKGROUND_MODE_MANAGER_STARTUP, + + KeepAliveOrigin::BACKGROUND_SYNC, + + // Notification KeepAlives are not dependent on the Chrome UI being + // loaded, and can be registered when we were in pure background mode. + // They just block it to avoid issues. Ignore them when determining if we + // are in that mode. + KeepAliveOrigin::NOTIFICATION, + KeepAliveOrigin::PENDING_NOTIFICATION_CLICK_EVENT, + KeepAliveOrigin::PENDING_NOTIFICATION_CLOSE_EVENT, + KeepAliveOrigin::IN_FLIGHT_PUSH_MESSAGE, + }); +} + +size_t BackgroundModeManager::NumberOfBackgroundModeData() { + return background_mode_data_.size(); +} + +/////////////////////////////////////////////////////////////////////////////// +// BackgroundModeManager, content::NotificationObserver overrides +void BackgroundModeManager::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type); + + // Make sure we aren't still keeping the app alive (only happens if we + // don't receive an EXTENSIONS_READY notification for some reason). + ReleaseStartupKeepAlive(); + // Performing an explicit shutdown, so exit background mode (does nothing + // if we aren't in background mode currently). + EndBackgroundMode(); + // Shutting down, so don't listen for any more notifications so we don't + // try to re-enter/exit background mode again. + registrar_.RemoveAll(); + for (const auto& it : background_mode_data_) + it.second->applications()->RemoveObserver(this); +} + +void BackgroundModeManager::OnExtensionsReady(Profile* profile) { + BackgroundModeManager::BackgroundModeData* bmd = + GetBackgroundModeData(profile); + if (bmd) { + UMA_HISTOGRAM_COUNTS_100("BackgroundMode.BackgroundApplicationsCount", + bmd->applications()->size()); + } + // Extensions are loaded, so we don't need to manually keep the browser + // process alive any more when running in no-startup-window mode. + ReleaseStartupKeepAlive(); + + auto* extension_service = + extensions::ExtensionSystem::Get(profile)->extension_service(); + auto* tracker = extension_service->force_installed_tracker(); + if (tracker->IsReady() || !bmd) + ReleaseForceInstalledExtensionsKeepAlive(); + else + bmd->SetTracker(tracker); +} + +void BackgroundModeManager::OnBackgroundModeEnabledPrefChanged() { + bool enabled = IsBackgroundModePrefEnabled(); + UMA_HISTOGRAM_BOOLEAN("BackgroundMode.BackgroundModeEnabledPrefChanged", + enabled); + UpdateEnableLaunchOnStartup(); + if (enabled) + EnableBackgroundMode(); + else + DisableBackgroundMode(); +} + +/////////////////////////////////////////////////////////////////////////////// +// BackgroundModeManager, BackgroundApplicationListModel::Observer overrides +void BackgroundModeManager::OnApplicationDataChanged() { + UpdateStatusTrayIconContextMenu(); +} + +void BackgroundModeManager::OnApplicationListChanged(const Profile* profile) { + if (!IsBackgroundModePrefEnabled()) + return; + + BackgroundModeManager::BackgroundModeData* bmd = + GetBackgroundModeData(profile); + if (!bmd) + return; + + // Get the new apps (if any) and process them. + std::set new_apps = bmd->GetNewBackgroundApps(); + std::vector new_names; + for (auto* app : new_apps) + new_names.push_back(base::UTF8ToUTF16(app->name())); + OnClientsChanged(profile, new_names); +} + +/////////////////////////////////////////////////////////////////////////////// +// BackgroundModeManager, ProfileAttributesStorage::Observer overrides +void BackgroundModeManager::OnProfileAdded(const base::FilePath& profile_path) { + ProfileAttributesEntry* entry = + profile_storage_->GetProfileAttributesWithPath(profile_path); + DCHECK(entry); + std::u16string profile_name = entry->GetName(); + // At this point, the profile should be registered with the background mode + // manager, but when it's actually added to the ProfileAttributesStorage is + // when its name is set so we need up to update that with the + // background_mode_data. + for (const auto& it : background_mode_data_) { + if (it.first->GetPath() == profile_path) { + it.second->SetName(profile_name); + UpdateStatusTrayIconContextMenu(); + return; + } + } +} + +void BackgroundModeManager::OnProfileWillBeRemoved( + const base::FilePath& profile_path) { + Profile* profile = + g_browser_process->profile_manager()->GetProfileByPath(profile_path); + DCHECK(profile); + UnregisterProfile(profile); +} + +void BackgroundModeManager::OnProfileNameChanged( + const base::FilePath& profile_path, + const std::u16string& old_profile_name) { + ProfileAttributesEntry* entry = + profile_storage_->GetProfileAttributesWithPath(profile_path); + DCHECK(entry); + std::u16string new_profile_name = entry->GetName(); + BackgroundModeInfoMap::const_iterator it = + GetBackgroundModeIterator(old_profile_name); + // We check that the returned iterator is valid due to unittests, but really + // this should only be called on profiles already known by the background + // mode manager. + if (it != background_mode_data_.end()) { + it->second->SetName(new_profile_name); + UpdateStatusTrayIconContextMenu(); + } +} + +BackgroundModeManager::BackgroundModeData* +BackgroundModeManager::GetBackgroundModeDataForLastProfile() const { + Profile* most_recent_profile = g_browser_process->profile_manager()-> + GetLastUsedProfileAllowedByPolicy(); + auto profile_background_data = + background_mode_data_.find(most_recent_profile); + + if (profile_background_data == background_mode_data_.end()) + return nullptr; + + // Do not permit a locked profile to be used to open a browser. + ProfileAttributesEntry* entry = + profile_storage_->GetProfileAttributesWithPath( + profile_background_data->first->GetPath()); + DCHECK(entry); + if (entry->IsSigninRequired()) + return nullptr; + + return profile_background_data->second.get(); +} + +/////////////////////////////////////////////////////////////////////////////// +// BackgroundModeManager::BackgroundModeData, StatusIconMenuModel overrides +void BackgroundModeManager::ExecuteCommand(int command_id, int event_flags) { + BackgroundModeData* bmd = GetBackgroundModeDataForLastProfile(); + switch (command_id) { + case IDC_ABOUT: + RecordMenuItemClick(MENU_ITEM_ABOUT); + if (bmd) { + chrome::ShowAboutChrome(bmd->GetBrowserWindow()); + } else { + ProfilePicker::Show(ProfilePicker::EntryPoint::kBackgroundModeManager, + GURL(chrome::kChromeUIHelpURL)); + } + break; + case IDC_TASK_MANAGER: + RecordMenuItemClick(MENU_ITEM_TASK_MANAGER); + if (bmd) { + chrome::OpenTaskManager(bmd->GetBrowserWindow()); + } else { + ProfilePicker::Show(ProfilePicker::EntryPoint::kBackgroundModeManager, + GURL(ProfilePicker::kTaskManagerUrl)); + } + break; + case IDC_EXIT: + RecordMenuItemClick(MENU_ITEM_EXIT); + base::RecordAction(UserMetricsAction("Exit")); + chrome::CloseAllBrowsers(); + break; + case IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND: { + // Background mode must already be enabled (as otherwise this menu would + // not be visible). + DCHECK(IsBackgroundModePrefEnabled()); + DCHECK(KeepAliveRegistry::GetInstance()->IsKeepingAlive()); + + RecordMenuItemClick(MENU_ITEM_KEEP_RUNNING); + + // Set the background mode pref to "disabled" - the resulting notification + // will result in a call to DisableBackgroundMode(). + PrefService* service = g_browser_process->local_state(); + DCHECK(service); + service->SetBoolean(prefs::kBackgroundModeEnabled, false); + break; + } + default: + if (bmd) { + bmd->ExecuteCommand(command_id, event_flags); + } else { + ProfilePicker::Show(ProfilePicker::EntryPoint::kBackgroundModeManager); + } + break; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// BackgroundModeManager, private +void BackgroundModeManager::ReleaseStartupKeepAliveCallback() { + keep_alive_for_startup_.reset(); + optimizer_ = BackgroundModeOptimizer::Create(); +} + +void BackgroundModeManager::ReleaseStartupKeepAlive() { + if (keep_alive_for_startup_) { + // We call this via the message queue to make sure we don't try to end + // keep-alive (which can shutdown Chrome) before the message loop has + // started. This object reference is safe because it's going to be kept + // alive by the browser process until after the callback is called. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&BackgroundModeManager::ReleaseStartupKeepAliveCallback, + base::Unretained(this))); + } +} + +void BackgroundModeManager::ReleaseForceInstalledExtensionsKeepAlive() { + if (keep_alive_for_force_installed_extensions_) { + // We call this via the message queue to make sure we don't try to end + // keep-alive (which can shutdown Chrome) before the message loop has + // started. This object reference is safe because it's going to be kept + // alive by the browser process until after the callback is called. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce( + [](std::unique_ptr keep_alive) { + // Cleans up unique_ptr when it goes out of scope. + }, + std::move(keep_alive_for_force_installed_extensions_))); + } +} + +void BackgroundModeManager::StartBackgroundMode() { + DCHECK(ShouldBeInBackgroundMode()); + // Don't bother putting ourselves in background mode if we're already there + // or if background mode is disabled. + if (in_background_mode_) + return; + + startup_metric_utils::SetBackgroundModeEnabled(); + + // Mark ourselves as running in background mode. + in_background_mode_ = true; + + UpdateKeepAliveAndTrayIcon(); +} + +void BackgroundModeManager::EndBackgroundMode() { + if (!in_background_mode_) + return; + in_background_mode_ = false; + + UpdateKeepAliveAndTrayIcon(); +} + +void BackgroundModeManager::EnableBackgroundMode() { + DCHECK(IsBackgroundModePrefEnabled()); + // If background mode should be enabled, but isn't, turn it on. + if (!in_background_mode_ && ShouldBeInBackgroundMode()) { + StartBackgroundMode(); + + UpdateEnableLaunchOnStartup(); + } +} + +void BackgroundModeManager::DisableBackgroundMode() { + DCHECK(!IsBackgroundModePrefEnabled()); + // If background mode is currently enabled, turn it off. + if (in_background_mode_) { + EndBackgroundMode(); + } +} + +void BackgroundModeManager::SuspendBackgroundMode() { + background_mode_suspended_ = true; + UpdateKeepAliveAndTrayIcon(); +} + +void BackgroundModeManager::ResumeBackgroundMode() { + background_mode_suspended_ = false; + UpdateKeepAliveAndTrayIcon(); +} + +void BackgroundModeManager::UpdateKeepAliveAndTrayIcon() { + for (const auto& entry : background_mode_data_) + entry.second->UpdateProfileKeepAlive(); + + if (in_background_mode_ && !background_mode_suspended_) { + if (!keep_alive_) { + keep_alive_ = std::make_unique( + KeepAliveOrigin::BACKGROUND_MODE_MANAGER, + KeepAliveRestartOption::ENABLED); + } + CreateStatusTrayIcon(); + return; + } + + RemoveStatusTrayIcon(); + keep_alive_.reset(); +} + +void BackgroundModeManager::OnBrowserAdded(Browser* browser) { + ResumeBackgroundMode(); +} + +void BackgroundModeManager::OnClientsChanged( + const Profile* profile, + const std::vector& new_client_names) { + DCHECK(IsBackgroundModePrefEnabled()); + + // Update the ProfileAttributesStorage with the fact whether background + // clients are running for this profile. + ProfileAttributesEntry* entry = + profile_storage_->GetProfileAttributesWithPath(profile->GetPath()); + if (entry) { + entry->SetBackgroundStatus( + HasPersistentBackgroundClientForProfile(profile)); + } + + UpdateEnableLaunchOnStartup(); + if (!ShouldBeInBackgroundMode()) { + // We've uninstalled our last background client, make sure we exit + // background mode and no longer launch on startup. + EndBackgroundMode(); + } else { + // We have at least one background client - make sure we're in background + // mode. + if (!in_background_mode_) { + // We're entering background mode - make sure we have launch-on-startup + // enabled. On Mac, the platform-specific code tracks whether the user + // has deleted a login item in the past, and if so, no login item will + // be created (to avoid overriding the specific user action). + StartBackgroundMode(); + } + + // List of clients changed so update the UI and keep alive references. + UpdateStatusTrayIconContextMenu(); + + // Notify the user about any new clients. + for (const auto& name : new_client_names) + OnBackgroundClientInstalled(name); + } +} + +bool BackgroundModeManager::HasPersistentBackgroundClient() const { + for (const auto& it : background_mode_data_) { + if (it.second->HasPersistentBackgroundClient()) + return true; + } + return false; +} + +bool BackgroundModeManager::HasAnyBackgroundClient() const { + for (const auto& it : background_mode_data_) { + if (it.second->HasAnyBackgroundClient()) + return true; + } + return false; +} + +bool BackgroundModeManager::HasPersistentBackgroundClientForProfile( + const Profile* profile) const { + BackgroundModeManager::BackgroundModeData* bmd = + GetBackgroundModeData(profile); + return bmd && bmd->HasPersistentBackgroundClient(); +} + +bool BackgroundModeManager::ShouldBeInBackgroundMode() const { + return IsBackgroundModePrefEnabled() && + (HasAnyBackgroundClient() || keep_alive_for_test_); +} + +void BackgroundModeManager::OnBackgroundClientInstalled( + const std::u16string& name) { + // Background mode is disabled - don't do anything. + if (!IsBackgroundModePrefEnabled()) + return; + + // Ensure we have a tray icon (needed so we can display the app-installed + // notification below). + EnableBackgroundMode(); + ResumeBackgroundMode(); + + ++client_installed_notifications_; + // Notify the user that a background client has been installed. + DisplayClientInstalledNotification(name); +} + +void BackgroundModeManager::UpdateEnableLaunchOnStartup() { + bool new_launch_on_startup = + ShouldBeInBackgroundMode() && HasPersistentBackgroundClient(); + if (launch_on_startup_enabled_ && + new_launch_on_startup == *launch_on_startup_enabled_) { + return; + } + launch_on_startup_enabled_.emplace(new_launch_on_startup); + EnableLaunchOnStartup(*launch_on_startup_enabled_); +} + +// Gets the image for the status tray icon, at the correct size for the current +// platform and display settings. +gfx::ImageSkia GetStatusTrayIcon() { +#if defined(OS_WIN) + // On Windows, use GetSmallAppIconSize to get the correct image size. The + // user's "text size" setting in Windows determines how large the system tray + // icon should be. + gfx::Size size = GetSmallAppIconSize(); + + // This loads all of the icon images, which is a bit wasteful because we're + // going to pick one and throw the rest away, but that is the price of using + // the ImageFamily abstraction. Note: We could just use the LoadImage function + // from the Windows API, but that does a *terrible* job scaling images. + // Therefore, we fetch the images and do our own high-quality scaling. + std::unique_ptr family = GetAppIconImageFamily(); + DCHECK(family); + if (!family) + return gfx::ImageSkia(); + + return family->CreateExact(size).AsImageSkia(); +#elif defined(OS_LINUX) || defined(OS_CHROMEOS) + return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( + IDR_PRODUCT_LOGO_128); +#elif defined(OS_MAC) + return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( + IDR_STATUS_TRAY_ICON); +#else + NOTREACHED(); + return gfx::ImageSkia(); +#endif +} + +void BackgroundModeManager::CreateStatusTrayIcon() { + // Only need status icons on windows/linux. ChromeOS doesn't allow exiting + // Chrome and Mac can use the dock icon instead. + + // Since there are multiple profiles which share the status tray, we now + // use the browser process to keep track of it. +#if !defined(OS_MAC) && !BUILDFLAG(IS_CHROMEOS_ASH) && \ + !BUILDFLAG(IS_CHROMEOS_LACROS) + if (!status_tray_) + status_tray_ = g_browser_process->status_tray(); +#endif + + // If the platform doesn't support status icons, or we've already created + // our status icon, just return. + if (!status_tray_ || status_icon_) + return; + + status_icon_ = status_tray_->CreateStatusIcon( + StatusTray::BACKGROUND_MODE_ICON, GetStatusTrayIcon(), + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); + if (!status_icon_) + return; + UpdateStatusTrayIconContextMenu(); +} + +void BackgroundModeManager::UpdateStatusTrayIconContextMenu() { + // Ensure we have a tray icon if appropriate. + UpdateKeepAliveAndTrayIcon(); + + // If we don't have a status icon or one could not be created succesfully, + // then no need to continue the update. + if (!status_icon_) + return; + + // We should only get here if we have a profile loaded, or if we're running + // in test mode. + if (background_mode_data_.empty()) { + DCHECK(keep_alive_for_test_); + return; + } + + command_id_handler_vector_.clear(); + submenus.clear(); + + std::unique_ptr menu(new StatusIconMenuModel(this)); + menu->AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT)); + menu->AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER); + menu->AddSeparator(ui::NORMAL_SEPARATOR); + + // If there are multiple profiles they each get a submenu. + if (profile_storage_->GetNumberOfProfiles() > 1) { + std::vector bmd_vector; + for (const auto& it : background_mode_data_) + bmd_vector.push_back(it.second.get()); + std::sort(bmd_vector.begin(), bmd_vector.end(), + &BackgroundModeData::BackgroundModeDataCompare); + int profiles_using_background_mode = 0; + for (auto* bmd : bmd_vector) { + // We should only display the profile in the status icon if it has at + // least one background app. + if (bmd->HasAnyBackgroundClient()) { + // The submenu constructor caller owns the lifetime of the submenu. + // The containing menu does not handle the lifetime. + submenus.push_back(std::make_unique(bmd)); + bmd->BuildProfileMenu(submenus.back().get(), menu.get()); + profiles_using_background_mode++; + } + } + // We should only be displaying the status tray icon if there is at least + // one profile using background mode. If |keep_alive_for_test_| is set, + // there may not be any profiles and that is okay. + DCHECK(profiles_using_background_mode > 0 || keep_alive_for_test_); + } else { + // We should only have one profile in the ProfileAttributesStorage if we are + // not using multi-profiles. If |keep_alive_for_test_| is set, then we may + // not have any profiles in the ProfileAttributesStorage. + DCHECK(profile_storage_->GetNumberOfProfiles() == size_t(1) || + keep_alive_for_test_); + background_mode_data_.begin()->second->BuildProfileMenu(menu.get(), + nullptr); + } + + menu->AddSeparator(ui::NORMAL_SEPARATOR); + menu->AddCheckItemWithStringId( + IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND, + IDS_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND); + menu->SetCommandIdChecked(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND, + true); + + PrefService* service = g_browser_process->local_state(); + DCHECK(service); + bool enabled = + service->IsUserModifiablePreference(prefs::kBackgroundModeEnabled); + menu->SetCommandIdEnabled(IDC_STATUS_TRAY_KEEP_CHROME_RUNNING_IN_BACKGROUND, + enabled); + + menu->AddItemWithStringId(IDC_EXIT, IDS_EXIT); + + context_menu_ = menu.get(); + status_icon_->SetContextMenu(std::move(menu)); +} + +void BackgroundModeManager::RemoveStatusTrayIcon() { + if (status_icon_) + status_tray_->RemoveStatusIcon(status_icon_); + status_icon_ = nullptr; + context_menu_ = nullptr; +} + +BackgroundModeManager::BackgroundModeData* +BackgroundModeManager::GetBackgroundModeData(const Profile* profile) const { + // Profiles are shut down and destroyed asynchronously after + // OnProfileWillBeRemoved is called, so we may have dropped anything + // associated with the profile already. + auto it = background_mode_data_.find(profile); + return it != background_mode_data_.end() ? it->second.get() : nullptr; +} + +BackgroundModeManager::BackgroundModeInfoMap::iterator +BackgroundModeManager::GetBackgroundModeIterator( + const std::u16string& profile_name) { + auto profile_it = background_mode_data_.end(); + for (auto it = background_mode_data_.begin(); + it != background_mode_data_.end(); ++it) { + if (it->second->name() == profile_name) { + profile_it = it; + } + } + return profile_it; +} + +bool BackgroundModeManager::IsBackgroundModePrefEnabled() const { + PrefService* service = g_browser_process->local_state(); + DCHECK(service); + return service->GetBoolean(prefs::kBackgroundModeEnabled); +} diff --git a/chrome/browser/net/stub_resolver_config_reader.cc b/chrome/browser/net/stub_resolver_config_reader.cc new file mode 100644 index 00000000..aebb7df3 --- /dev/null +++ b/chrome/browser/net/stub_resolver_config_reader.cc @@ -0,0 +1,417 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/net/stub_resolver_config_reader.h" + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/check.h" +#include "base/feature_list.h" +#include "base/location.h" +#include "base/metrics/field_trial_params.h" +#include "base/metrics/histogram_macros.h" +#include "base/notreached.h" +#include "base/strings/string_piece.h" +#include "base/values.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/net/secure_dns_config.h" +#include "chrome/browser/net/secure_dns_util.h" +#include "chrome/browser/policy/chrome_browser_policy_connector.h" +#include "chrome/common/chrome_features.h" +#include "chrome/common/pref_names.h" +#include "components/flags_ui/pref_service_flags_storage.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "content/public/browser/network_service_instance.h" +#include "net/dns/public/secure_dns_mode.h" +#include "net/dns/public/util.h" +#include "services/network/public/mojom/host_resolver.mojom.h" +#include "services/network/public/mojom/network_service.mojom.h" + +#if defined(OS_ANDROID) +#include "base/android/build_info.h" +#include "chrome/browser/enterprise/util/android_enterprise_info.h" +#endif + +#if defined(OS_WIN) +#include "base/enterprise_util.h" +#include "base/win/windows_version.h" +#include "chrome/browser/win/parental_controls.h" +#endif + +namespace { + +// Detailed descriptions of the secure DNS mode. These values are logged to UMA. +// Entries should not be renumbered and numeric values should never be reused. +// Please keep in sync with "SecureDnsModeDetails" in +// src/tools/metrics/histograms/enums.xml. +enum class SecureDnsModeDetailsForHistogram { + // The mode is controlled by the user and is set to 'off'. + kOffByUser = 0, + // The mode is controlled via enterprise policy and is set to 'off'. + kOffByEnterprisePolicy = 1, + // Chrome detected a managed environment and forced the mode to 'off'. + kOffByDetectedManagedEnvironment = 2, + // Chrome detected parental controls and forced the mode to 'off'. + kOffByDetectedParentalControls = 3, + // The mode is controlled by the user and is set to 'automatic' (the default + // mode). + kAutomaticByUser = 4, + // The mode is controlled via enterprise policy and is set to 'automatic'. + kAutomaticByEnterprisePolicy = 5, + // The mode is controlled by the user and is set to 'secure'. + kSecureByUser = 6, + // The mode is controlled via enterprise policy and is set to 'secure'. + kSecureByEnterprisePolicy = 7, + kMaxValue = kSecureByEnterprisePolicy, +}; + +#if defined(OS_WIN) +bool ShouldDisableDohForWindowsParentalControls() { + const WinParentalControls& parental_controls = GetWinParentalControls(); + if (parental_controls.web_filter) + return true; + + // Some versions before Windows 8 may not fully support |web_filter|, so + // conservatively disable doh for any recognized parental controls. + if (parental_controls.any_restrictions && + base::win::GetVersion() < base::win::Version::WIN8) { + return true; + } + + return false; +} +#endif // defined(OS_WIN) + +// Check the AsyncDns field trial and return true if it should be enabled. On +// Android this includes checking the Android version in the field trial. +bool ShouldEnableAsyncDns() { + bool feature_can_be_enabled = true; +#if defined(OS_ANDROID) + int min_sdk = + base::GetFieldTrialParamByFeatureAsInt(features::kAsyncDns, "min_sdk", 0); + if (base::android::BuildInfo::GetInstance()->sdk_int() < min_sdk) + feature_can_be_enabled = false; +#endif + return feature_can_be_enabled && + base::FeatureList::IsEnabled(features::kAsyncDns); +} + +} // namespace + +// static +constexpr base::TimeDelta StubResolverConfigReader::kParentalControlsCheckDelay; + +StubResolverConfigReader::StubResolverConfigReader(PrefService* local_state, + bool set_up_pref_defaults) + : local_state_(local_state) { + base::RepeatingClosure pref_callback = + base::BindRepeating(&StubResolverConfigReader::UpdateNetworkService, + base::Unretained(this), false /* record_metrics */); + + pref_change_registrar_.Init(local_state_); + + // Update the DnsClient and DoH default preferences based on the corresponding + // features before registering change callbacks for these preferences. + // Changing prefs or defaults after registering change callbacks could result + // in reentrancy and mess up registration between this code and NetworkService + // creation. + if (set_up_pref_defaults) { + local_state_->SetDefaultPrefValue(prefs::kBuiltInDnsClientEnabled, + base::Value(ShouldEnableAsyncDns())); + net::SecureDnsMode default_secure_dns_mode = net::SecureDnsMode::kOff; + std::string default_doh_templates; + if (base::FeatureList::IsEnabled(features::kDnsOverHttps)) { + if (features::kDnsOverHttpsFallbackParam.Get()) { + default_secure_dns_mode = net::SecureDnsMode::kAutomatic; + } else { + default_secure_dns_mode = net::SecureDnsMode::kSecure; + } + default_doh_templates = features::kDnsOverHttpsTemplatesParam.Get(); + } + local_state_->SetDefaultPrefValue( + prefs::kDnsOverHttpsMode, + base::Value(SecureDnsConfig::ModeToString(default_secure_dns_mode))); + local_state_->SetDefaultPrefValue(prefs::kDnsOverHttpsTemplates, + base::Value(default_doh_templates)); + + // If the user has explicitly enabled or disabled the DoH experiment in + // chrome://flags and the DoH UI setting is not visible, store that choice + // in the user prefs so that it can be persisted after the experiment ends. + // Also make sure to remove the stored prefs value if the user has changed + // their chrome://flags selection to the default. + if (!features::kDnsOverHttpsShowUiParam.Get()) { + flags_ui::PrefServiceFlagsStorage flags_storage(local_state_); + std::set entries = flags_storage.GetFlags(); + if (entries.count("dns-over-https@1")) { + // The user has "Enabled" selected. + local_state_->SetString(prefs::kDnsOverHttpsMode, + SecureDnsConfig::kModeSecure); + } else if (entries.count("dns-over-https@2")) { + // The user has "Disabled" selected. + local_state_->SetString(prefs::kDnsOverHttpsMode, + SecureDnsConfig::kModeOff); + } else { + // The user has "Default" selected. + local_state_->ClearPref(prefs::kDnsOverHttpsMode); + } + } + } + + pref_change_registrar_.Add(prefs::kBuiltInDnsClientEnabled, pref_callback); + pref_change_registrar_.Add(prefs::kDnsOverHttpsMode, pref_callback); + pref_change_registrar_.Add(prefs::kDnsOverHttpsTemplates, pref_callback); + pref_change_registrar_.Add(prefs::kAdditionalDnsQueryTypesEnabled, + pref_callback); + + parental_controls_delay_timer_.Start( + FROM_HERE, kParentalControlsCheckDelay, + base::BindOnce(&StubResolverConfigReader::OnParentalControlsDelayTimer, + base::Unretained(this))); + +#if defined(OS_ANDROID) + chrome::enterprise_util::AndroidEnterpriseInfo::GetInstance() + ->GetAndroidEnterpriseInfoState(base::BindOnce( + &StubResolverConfigReader::OnAndroidOwnedStateCheckComplete, + weak_factory_.GetWeakPtr())); +#endif +} + +StubResolverConfigReader::~StubResolverConfigReader() = default; + +// static +void StubResolverConfigReader::RegisterPrefs(PrefRegistrySimple* registry) { + // Register the DnsClient and DoH preferences. The feature list has not been + // initialized yet, so setting the preference defaults here to reflect the + // corresponding features will only cause the preference defaults to reflect + // the feature defaults (feature values set via the command line will not be + // captured). Thus, the preference defaults are updated in the constructor + // for SystemNetworkContextManager, at which point the feature list is ready. + registry->RegisterBooleanPref(prefs::kBuiltInDnsClientEnabled, false); + registry->RegisterStringPref(prefs::kDnsOverHttpsMode, std::string()); + registry->RegisterStringPref(prefs::kDnsOverHttpsTemplates, std::string()); + registry->RegisterBooleanPref(prefs::kAdditionalDnsQueryTypesEnabled, true); +} + +SecureDnsConfig StubResolverConfigReader::GetSecureDnsConfiguration( + bool force_check_parental_controls_for_automatic_mode) { + return GetAndUpdateConfiguration( + force_check_parental_controls_for_automatic_mode, + false /* record_metrics */, false /* update_network_service */); +} + +void StubResolverConfigReader::UpdateNetworkService(bool record_metrics) { + GetAndUpdateConfiguration( + false /* force_check_parental_controls_for_automatic_mode */, + record_metrics, true /* update_network_service */); +} + +bool StubResolverConfigReader::ShouldDisableDohForManaged() { +// This function ignores cloud policies which are loaded on a per-profile basis. +#if defined(OS_ANDROID) + // Check for MDM/management/owner apps. android_has_owner_ is true if either a + // device or policy owner app is discovered by + // GetAndroidEnterpriseInfoState(). If android_has_owner_ is nullopt, take a + // value of false so that we don't disable DoH during the async check. + + // Because Android policies can only be loaded with owner apps this is + // sufficient to check for the prescences of policies as well. + if (android_has_owner_.value_or(false)) + return true; +#elif defined(OS_WIN) + if (base::IsMachineExternallyManaged()) + return true; +#endif +#if !defined(OS_ANDROID) && !defined(OS_CHROMEOS) + if (g_browser_process->browser_policy_connector()->HasMachineLevelPolicies()) + return true; +#endif + return false; +} + +bool StubResolverConfigReader::ShouldDisableDohForParentalControls() { + if (parental_controls_testing_override_.has_value()) + return parental_controls_testing_override_.value(); + +#if defined(OS_WIN) + return ShouldDisableDohForWindowsParentalControls(); +#else + return false; +#endif +} + +void StubResolverConfigReader::OnParentalControlsDelayTimer() { + DCHECK(!parental_controls_delay_timer_.IsRunning()); + + // No need to act if parental controls were checked early. + if (parental_controls_checked_) + return; + parental_controls_checked_ = true; + + // If parental controls are enabled, force a config change so secure DNS can + // be disabled. + if (ShouldDisableDohForParentalControls()) + UpdateNetworkService(false /* record_metrics */); +} + +bool StubResolverConfigReader::GetInsecureStubResolverEnabled() { + return local_state_->GetBoolean(prefs::kBuiltInDnsClientEnabled); +} + +SecureDnsConfig StubResolverConfigReader::GetAndUpdateConfiguration( + bool force_check_parental_controls_for_automatic_mode, + bool record_metrics, + bool update_network_service) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + net::SecureDnsMode secure_dns_mode; + SecureDnsModeDetailsForHistogram mode_details; + SecureDnsConfig::ManagementMode forced_management_mode = + SecureDnsConfig::ManagementMode::kNoOverride; + bool is_managed = + local_state_->FindPreference(prefs::kDnsOverHttpsMode)->IsManaged(); + if (!is_managed && ShouldDisableDohForManaged()) { + secure_dns_mode = net::SecureDnsMode::kOff; + forced_management_mode = SecureDnsConfig::ManagementMode::kDisabledManaged; + } else { + secure_dns_mode = SecureDnsConfig::ParseMode( + local_state_->GetString(prefs::kDnsOverHttpsMode)) + .value_or(net::SecureDnsMode::kOff); + } + + bool check_parental_controls = false; + if (secure_dns_mode == net::SecureDnsMode::kSecure) { + mode_details = + is_managed ? SecureDnsModeDetailsForHistogram::kSecureByEnterprisePolicy + : SecureDnsModeDetailsForHistogram::kSecureByUser; + + // SECURE mode must always check for parental controls immediately (unless + // enabled through policy, which takes precedence over parental controls) + // because the mode allows sending DoH requests immediately. + check_parental_controls = !is_managed; + } else if (secure_dns_mode == net::SecureDnsMode::kAutomatic) { + mode_details = + is_managed + ? SecureDnsModeDetailsForHistogram::kAutomaticByEnterprisePolicy + : SecureDnsModeDetailsForHistogram::kAutomaticByUser; + + // To avoid impacting startup performance, AUTOMATIC mode should defer + // checking parental for a short period. This delay should have no practical + // effect on DoH queries because DoH enabling probes do not start until a + // longer period after startup. + bool allow_check_parental_controls = + force_check_parental_controls_for_automatic_mode || + parental_controls_checked_; + check_parental_controls = !is_managed && allow_check_parental_controls; + } else { + switch (forced_management_mode) { + case SecureDnsConfig::ManagementMode::kNoOverride: + mode_details = + is_managed + ? SecureDnsModeDetailsForHistogram::kOffByEnterprisePolicy + : SecureDnsModeDetailsForHistogram::kOffByUser; + break; + case SecureDnsConfig::ManagementMode::kDisabledManaged: + mode_details = + SecureDnsModeDetailsForHistogram::kOffByDetectedManagedEnvironment; + break; + case SecureDnsConfig::ManagementMode::kDisabledParentalControls: + NOTREACHED(); + break; + default: + NOTREACHED(); + } + + // No need to check for parental controls if DoH is already disabled. + check_parental_controls = false; + } + + // Check parental controls last because it can be expensive and should only be + // checked if necessary for the otherwise-determined mode. + if (check_parental_controls) { + if (ShouldDisableDohForParentalControls()) { + forced_management_mode = + SecureDnsConfig::ManagementMode::kDisabledParentalControls; + secure_dns_mode = net::SecureDnsMode::kOff; + mode_details = + SecureDnsModeDetailsForHistogram::kOffByDetectedParentalControls; + + // If parental controls had not previously been checked, need to update + // network service. + if (!parental_controls_checked_) + update_network_service = true; + } + + parental_controls_checked_ = true; + } + + bool additional_dns_query_types_enabled = + local_state_->GetBoolean(prefs::kAdditionalDnsQueryTypesEnabled); + + if (record_metrics) { + UMA_HISTOGRAM_ENUMERATION("Net.DNS.DnsConfig.SecureDnsMode", mode_details); + if (!additional_dns_query_types_enabled || ShouldDisableDohForManaged()) { + UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.AdditionalDnsQueryTypesEnabled", + additional_dns_query_types_enabled); + } + } + + std::string doh_templates = + local_state_->GetString(prefs::kDnsOverHttpsTemplates); + std::string server_method; + std::vector dns_over_https_servers; + absl::optional> + servers_mojo; + if (!doh_templates.empty() && secure_dns_mode != net::SecureDnsMode::kOff) { + for (base::StringPiece server_template : + chrome_browser_net::secure_dns::SplitGroup(doh_templates)) { + if (!net::dns_util::IsValidDohTemplate(server_template, &server_method)) { + continue; + } + + bool use_post = server_method == "POST"; + dns_over_https_servers.emplace_back(std::string(server_template), + use_post); + + if (!servers_mojo.has_value()) { + servers_mojo = absl::make_optional< + std::vector>(); + } + + network::mojom::DnsOverHttpsServerPtr server_mojo = + network::mojom::DnsOverHttpsServer::New(); + server_mojo->server_template = std::string(server_template); + server_mojo->use_post = use_post; + servers_mojo->emplace_back(std::move(server_mojo)); + } + } + + if (update_network_service) { + content::GetNetworkService()->ConfigureStubHostResolver( + GetInsecureStubResolverEnabled(), secure_dns_mode, + std::move(servers_mojo), additional_dns_query_types_enabled); + } + + return SecureDnsConfig(secure_dns_mode, std::move(dns_over_https_servers), + forced_management_mode); +} + +#if defined(OS_ANDROID) +void StubResolverConfigReader::OnAndroidOwnedStateCheckComplete( + bool has_profile_owner, + bool has_device_owner) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + android_has_owner_ = has_profile_owner || has_device_owner; + // update the network service if the actual result is "true" to save time. + if (android_has_owner_.value()) + UpdateNetworkService(false /* record_metrics */); +} +#endif diff --git a/chrome/installer/linux/common/chromium-browser/chromium-browser.appdata.xml b/chrome/installer/linux/common/chromium-browser/chromium-browser.appdata.xml new file mode 100644 index 00000000..e86a6f4b --- /dev/null +++ b/chrome/installer/linux/common/chromium-browser/chromium-browser.appdata.xml @@ -0,0 +1,36 @@ + + + + thorium-browser.desktop + chromium-dev@chromium.org + CC0-1.0 + BSD-3-Clause and LGPL-2.1+ and Apache-2.0 and IJG and MIT and GPL-2.0+ and ISC and OpenSSL and (MPL-1.1 or GPL-2.0 or LGPL-2.0) + Thorium Web Browser + The web browser from the Chromium project + +

+ Chromium is an open-source browser project that aims to build a safer, faster, + and more stable way to experience the web. +

+

+ We invite you to join our effort to build a powerful platform for developing a + new generation of web applications. +

+

+ Chromium supports Vorbis, Theora, WebM and HTML5 audio and video standards, but + does not include the non-free AAC, H.264, MP3 or Adobe Flash code that is found + in Chrome. +

+
+ https://www.chromium.org/Home + + + https://www.gstatic.com/chrome/appstream/chrome-2.png + + + + + The Chromium and Thorium Authors + https://www.chromium.org/for-testers/bug-reporting-guidelines + https://chromium.googlesource.com/chromium/src/+/main/docs/linux/debugging.md +
diff --git a/chrome/installer/linux/common/chromium-browser/chromium-browser.info b/chrome/installer/linux/common/chromium-browser/chromium-browser.info new file mode 100644 index 00000000..55425b02 --- /dev/null +++ b/chrome/installer/linux/common/chromium-browser/chromium-browser.info @@ -0,0 +1,33 @@ +# Copyright (c) 2009 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# This file provides common configuration information for building +# chromium-browser packages for various platforms. + +# Base name of the package. +PACKAGE="thorium-browser" + +# Base name of the snap package +SNAPNAME="thorium" + +# Filename of the main executable (for generating launcher scripts, etc.) +PROGNAME=chrome + +# Base directory for package installation. +INSTALLDIR=/opt/chromium.org/chromium + +# Display string for desktop menu/icon. +MENUNAME="Thorium Web Browser" + +# Brief package description. +SHORTDESC="The web browser from Chromium.org and The Thorium Authors" + +# Detailed package description. +FULLDESC="Thorium is a browser that combines a minimal design with sophisticated technology to make the web faster, safer, and easier." + +# Package maintainer information. +# TODO(mmoss) Setup a mailbox for this address +MAINTNAME="Thorium Linux Maintainers" +MAINTMAIL="alex313031@gmail.com" +PRODUCTURL="http://www.chromium.org/" diff --git a/chrome/installer/linux/common/desktop.template b/chrome/installer/linux/common/desktop.template new file mode 100644 index 00000000..0edb17da --- /dev/null +++ b/chrome/installer/linux/common/desktop.template @@ -0,0 +1,275 @@ +[Desktop Entry] +Version=1.0 +Name=Thorium Web Browser +# Only KDE 4 seems to use GenericName, so we reuse the KDE strings. +# From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413. +GenericName=Web Browser +GenericName[ar]=متصفح الشبكة +GenericName[bg]=Уеб браузър +GenericName[ca]=Navegador web +GenericName[cs]=WWW prohlížeč +GenericName[da]=Browser +GenericName[de]=Web-Browser +GenericName[el]=Περιηγητής ιστού +GenericName[en_GB]=Web Browser +GenericName[es]=Navegador web +GenericName[et]=Veebibrauser +GenericName[fi]=WWW-selain +GenericName[fr]=Navigateur Web +GenericName[gu]=વેબ બ્રાઉઝર +GenericName[he]=דפדפן אינטרנט +GenericName[hi]=वेब ब्राउज़र +GenericName[hu]=Webböngésző +GenericName[it]=Browser Web +GenericName[ja]=ウェブブラウザ +GenericName[kn]=ಜಾಲ ವೀಕ್ಷಕ +GenericName[ko]=웹 브라우저 +GenericName[lt]=Žiniatinklio naršyklė +GenericName[lv]=Tīmekļa pārlūks +GenericName[ml]=വെബ് ബ്രൌസര്‍ +GenericName[mr]=वेब ब्राऊजर +GenericName[nb]=Nettleser +GenericName[nl]=Webbrowser +GenericName[pl]=Przeglądarka WWW +GenericName[pt]=Navegador Web +GenericName[pt_BR]=Navegador da Internet +GenericName[ro]=Navigator de Internet +GenericName[ru]=Веб-браузер +GenericName[sl]=Spletni brskalnik +GenericName[sv]=Webbläsare +GenericName[ta]=இணைய உலாவி +GenericName[th]=เว็บเบราว์เซอร์ +GenericName[tr]=Web Tarayıcı +GenericName[uk]=Навігатор Тенет +GenericName[zh_CN]=网页浏览器 +GenericName[zh_HK]=網頁瀏覽器 +GenericName[zh_TW]=網頁瀏覽器 +# Not translated in KDE, from Epiphany 2.26.1-0ubuntu1. +GenericName[bn]=ওয়েব ব্রাউজার +GenericName[fil]=Web Browser +GenericName[hr]=Web preglednik +GenericName[id]=Browser Web +GenericName[or]=ଓ୍ବେବ ବ୍ରାଉଜର +GenericName[sk]=WWW prehliadač +GenericName[sr]=Интернет прегледник +GenericName[te]=మహాతల అన్వేషి +GenericName[vi]=Bộ duyệt Web +# Gnome and KDE 3 uses Comment. +Comment=Access the Internet +Comment[ar]=الدخول إلى الإنترنت +Comment[bg]=Достъп до интернет +Comment[bn]=ইন্টারনেটটি অ্যাক্সেস করুন +Comment[ca]=Accedeix a Internet +Comment[cs]=Přístup k internetu +Comment[da]=Få adgang til internettet +Comment[de]=Internetzugriff +Comment[el]=Πρόσβαση στο Διαδίκτυο +Comment[en_GB]=Access the Internet +Comment[es]=Accede a Internet. +Comment[et]=Pääs Internetti +Comment[fi]=Käytä internetiä +Comment[fil]=I-access ang Internet +Comment[fr]=Accéder à Internet +Comment[gu]=ઇંટરનેટ ઍક્સેસ કરો +Comment[he]=גישה אל האינטרנט +Comment[hi]=इंटरनेट तक पहुंच स्थापित करें +Comment[hr]=Pristup Internetu +Comment[hu]=Internetelérés +Comment[id]=Akses Internet +Comment[it]=Accesso a Internet +Comment[ja]=インターネットにアクセス +Comment[kn]=ಇಂಟರ್ನೆಟ್ ಅನ್ನು ಪ್ರವೇಶಿಸಿ +Comment[ko]=인터넷 연결 +Comment[lt]=Interneto prieiga +Comment[lv]=Piekļūt internetam +Comment[ml]=ഇന്റര്‍‌നെറ്റ് ആക്‌സസ് ചെയ്യുക +Comment[mr]=इंटरनेटमध्ये प्रवेश करा +Comment[nb]=Gå til Internett +Comment[nl]=Verbinding maken met internet +Comment[or]=ଇଣ୍ଟର୍ନେଟ୍ ପ୍ରବେଶ କରନ୍ତୁ +Comment[pl]=Skorzystaj z internetu +Comment[pt]=Aceder à Internet +Comment[pt_BR]=Acessar a internet +Comment[ro]=Accesaţi Internetul +Comment[ru]=Доступ в Интернет +Comment[sk]=Prístup do siete Internet +Comment[sl]=Dostop do interneta +Comment[sr]=Приступите Интернету +Comment[sv]=Gå ut på Internet +Comment[ta]=இணையத்தை அணுகுதல் +Comment[te]=ఇంటర్నెట్‌ను ఆక్సెస్ చెయ్యండి +Comment[th]=เข้าถึงอินเทอร์เน็ต +Comment[tr]=İnternet'e erişin +Comment[uk]=Доступ до Інтернету +Comment[vi]=Truy cập Internet +Comment[zh_CN]=访问互联网 +Comment[zh_HK]=連線到網際網路 +Comment[zh_TW]=連線到網際網路 +Exec=/opt/chromium.org/chromium-unstable/chrome --show-component-extension-options --autoplay-policy=user-gesture-required --enable-features=VaapiVideoDecoder %U +StartupNotify=true +Terminal=false +Icon=@@PACKAGE@@ +Type=Application +Categories=Network;WebBrowser; +MimeType=application/pdf;application/rdf+xml;application/rss+xml;application/xhtml+xml;application/xhtml_xml;application/xml;image/gif;image/jpeg;image/png;image/webp;text/html;text/xml;x-scheme-handler/http;x-scheme-handler/https; +Actions=new-window;new-private-window; + +[Desktop Action new-window] +Name=New Window +Name[am]=አዲስ መስኮት +Name[ar]=نافذة جديدة +Name[bg]=Нов прозорец +Name[bn]=নতুন উইন্ডো +Name[ca]=Finestra nova +Name[cs]=Nové okno +Name[da]=Nyt vindue +Name[de]=Neues Fenster +Name[el]=Νέο Παράθυρο +Name[en_GB]=New Window +Name[es]=Nueva ventana +Name[et]=Uus aken +Name[fa]=پنجره جدید +Name[fi]=Uusi ikkuna +Name[fil]=New Window +Name[fr]=Nouvelle fenêtre +Name[gu]=નવી વિંડો +Name[hi]=नई विंडो +Name[hr]=Novi prozor +Name[hu]=Új ablak +Name[id]=Jendela Baru +Name[it]=Nuova finestra +Name[iw]=חלון חדש +Name[ja]=新規ウインドウ +Name[kn]=ಹೊಸ ವಿಂಡೊ +Name[ko]=새 창 +Name[lt]=Naujas langas +Name[lv]=Jauns logs +Name[ml]=പുതിയ വിന്‍ഡോ +Name[mr]=नवीन विंडो +Name[nl]=Nieuw venster +Name[no]=Nytt vindu +Name[pl]=Nowe okno +Name[pt]=Nova janela +Name[pt_BR]=Nova janela +Name[ro]=Fereastră nouă +Name[ru]=Новое окно +Name[sk]=Nové okno +Name[sl]=Novo okno +Name[sr]=Нови прозор +Name[sv]=Nytt fönster +Name[sw]=Dirisha Jipya +Name[ta]=புதிய சாளரம் +Name[te]=క్రొత్త విండో +Name[th]=หน้าต่างใหม่ +Name[tr]=Yeni Pencere +Name[uk]=Нове вікно +Name[vi]=Cửa sổ Mới +Name[zh_CN]=新建窗口 +Name[zh_TW]=開新視窗 +Exec=/opt/chromium.org/chromium-unstable/chrome --show-component-extension-options --autoplay-policy=user-gesture-required --enable-features=VaapiVideoDecoder + +[Desktop Action content-shell] +Name=Open Content Shell +Name[am]=አዲስ መስኮት +Name[ar]=نافذة جديدة +Name[bg]=Нов прозорец +Name[bn]=নতুন উইন্ডো +Name[ca]=Finestra nova +Name[cs]=Nové okno +Name[da]=Nyt vindue +Name[de]=Neues Fenster +Name[el]=Νέο Παράθυρο +Name[en_GB]=New Window +Name[es]=Nueva ventana +Name[et]=Uus aken +Name[fa]=پنجره جدید +Name[fi]=Uusi ikkuna +Name[fil]=New Window +Name[fr]=Nouvelle fenêtre +Name[gu]=નવી વિંડો +Name[hi]=नई विंडो +Name[hr]=Novi prozor +Name[hu]=Új ablak +Name[id]=Jendela Baru +Name[it]=Nuova finestra +Name[iw]=חלון חדש +Name[ja]=新規ウインドウ +Name[kn]=ಹೊಸ ವಿಂಡೊ +Name[ko]=새 창 +Name[lt]=Naujas langas +Name[lv]=Jauns logs +Name[ml]=പുതിയ വിന്‍ഡോ +Name[mr]=नवीन विंडो +Name[nl]=Nieuw venster +Name[no]=Nytt vindu +Name[pl]=Nowe okno +Name[pt]=Nova janela +Name[pt_BR]=Nova janela +Name[ro]=Fereastră nouă +Name[ru]=Новое окно +Name[sk]=Nové okno +Name[sl]=Novo okno +Name[sr]=Нови прозор +Name[sv]=Nytt fönster +Name[sw]=Dirisha Jipya +Name[ta]=புதிய சாளரம் +Name[te]=క్రొత్త విండో +Name[th]=หน้าต่างใหม่ +Name[tr]=Yeni Pencere +Name[uk]=Нове вікно +Name[vi]=Cửa sổ Mới +Name[zh_CN]=新建窗口 +Name[zh_TW]=開新視窗 +Exec=/opt/chromium.org/chromium-unstable/content_shell + +[Desktop Action new-private-window] +Name=New Incognito Window +Name[ar]=نافذة جديدة للتصفح المتخفي +Name[bg]=Нов прозорец „инкогнито“ +Name[bn]=নতুন ছদ্মবেশী উইন্ডো +Name[ca]=Finestra d'incògnit nova +Name[cs]=Nové anonymní okno +Name[da]=Nyt inkognitovindue +Name[de]=Neues Inkognito-Fenster +Name[el]=Νέο παράθυρο για ανώνυμη περιήγηση +Name[en_GB]=New Incognito window +Name[es]=Nueva ventana de incógnito +Name[et]=Uus inkognito aken +Name[fa]=پنجره جدید حالت ناشناس +Name[fi]=Uusi incognito-ikkuna +Name[fil]=Bagong Incognito window +Name[fr]=Nouvelle fenêtre de navigation privée +Name[gu]=નવી છુપી વિંડો +Name[hi]=नई गुप्त विंडो +Name[hr]=Novi anoniman prozor +Name[hu]=Új Inkognitóablak +Name[id]=Jendela Penyamaran baru +Name[it]=Nuova finestra di navigazione in incognito +Name[iw]=חלון חדש לגלישה בסתר +Name[ja]=新しいシークレット ウィンドウ +Name[kn]=ಹೊಸ ಅಜ್ಞಾತ ವಿಂಡೋ +Name[ko]=새 시크릿 창 +Name[lt]=Naujas inkognito langas +Name[lv]=Jauns inkognito režīma logs +Name[ml]=പുതിയ വേഷ പ്രച്ഛന്ന വിന്‍ഡോ +Name[mr]=नवीन गुप्त विंडो +Name[nl]=Nieuw incognitovenster +Name[no]=Nytt inkognitovindu +Name[pl]=Nowe okno incognito +Name[pt]=Nova janela de navegação anónima +Name[pt_BR]=Nova janela anônima +Name[ro]=Fereastră nouă incognito +Name[ru]=Новое окно в режиме инкогнито +Name[sk]=Nové okno inkognito +Name[sl]=Novo okno brez beleženja zgodovine +Name[sr]=Нови прозор за прегледање без архивирања +Name[sv]=Nytt inkognitofönster +Name[ta]=புதிய மறைநிலைச் சாளரம் +Name[te]=క్రొత్త అజ్ఞాత విండో +Name[th]=หน้าต่างใหม่ที่ไม่ระบุตัวตน +Name[tr]=Yeni Gizli pencere +Name[uk]=Нове вікно в режимі анонімного перегляду +Name[vi]=Cửa sổ ẩn danh mới +Name[zh_CN]=新建隐身窗口 +Name[zh_TW]=新增無痕式視窗 +Exec=/opt/chromium.org/chromium-unstable/chrome --incognito --show-component-extension-options --autoplay-policy=user-gesture-required --enable-features=VaapiVideoDecoder diff --git a/chrome/installer/linux/common/installer.include b/chrome/installer/linux/common/installer.include new file mode 100644 index 00000000..ee227740 --- /dev/null +++ b/chrome/installer/linux/common/installer.include @@ -0,0 +1,449 @@ +# Shows the output of a given command only on failure, or when VERBOSE is set. +log_cmd() { + if [ "${VERBOSE:-}" ]; then + "$@" + else + # Record $- into a separate variable because it gets reset in the subshell. + FORWARD_SHELL_OPTS=$- + ERREXIT=$(echo ${FORWARD_SHELL_OPTS} | grep -o e || true) + set +${ERREXIT} + CMD_OUTPUT=$("$@" 2>&1) + ERRCODE=$? + set -${ERREXIT} + if [ ${ERRCODE} -ne 0 ]; then + echo "$@" + echo "${CMD_OUTPUT}" + if [ ${ERREXIT} ]; then + exit ${ERRCODE} + fi + fi + fi +} + +# Recursively replace @@include@@ template variables with the referenced file, +# and write the resulting text to stdout. +process_template_includes() { + INCSTACK+="$1->" + # Includes are relative to the file that does the include. + INCDIR=$(dirname $1) + # Clear IFS so 'read' doesn't trim whitespace + local OLDIFS="$IFS" + IFS='' + while read -r LINE + do + INCLINE=$(sed -e '/^[[:space:]]*@@include@@/!d' <<<$LINE) + if [ -n "$INCLINE" ]; then + INCFILE=$(echo $INCLINE | sed -e "s#@@include@@\(.*\)#\1#") + # Simple filename match to detect cyclic includes. + CYCLE=$(sed -e "\#$INCFILE#"'!d' <<<$INCSTACK) + if [ "$CYCLE" ]; then + echo "ERROR: Possible cyclic include detected." 1>&2 + echo "$INCSTACK$INCFILE" 1>&2 + exit 1 + fi + if [ ! -r "$INCDIR/$INCFILE" ]; then + echo "ERROR: Couldn't read include file: $INCDIR/$INCFILE" 1>&2 + exit 1 + fi + process_template_includes "$INCDIR/$INCFILE" + else + echo "$LINE" + fi + done < "$1" + IFS="$OLDIFS" + INCSTACK=${INCSTACK%"$1->"} +} + +# Replace template variables (@@VARNAME@@) in the given template file. If a +# second argument is given, save the processed text to that filename, otherwise +# modify the template file in place. +process_template() ( + # Don't worry if some of these substitution variables aren't set. + # Note that this function is run in a sub-shell so we don't leak this + # setting, since we still want unbound variables to be an error elsewhere. + set +u + + local TMPLIN="$1" + if [ -z "$2" ]; then + local TMPLOUT="$TMPLIN" + else + local TMPLOUT="$2" + fi + # Process includes first so included text also gets substitutions. + TMPLINCL="$(process_template_includes "$TMPLIN")" + sed \ + -e "s#@@PACKAGE@@#${PACKAGE}#g" \ + -e "s#@@PACKAGE_ORIG@@#${PACKAGE_ORIG}#g" \ + -e "s#@@PACKAGE_FILENAME@@#${PACKAGE_FILENAME}#g" \ + -e "s#@@SNAPNAME@@#${SNAPNAME}#g" \ + -e "s#@@PROGNAME@@#${PROGNAME}#g" \ + -e "s#@@CHANNEL@@#${CHANNEL}#g" \ + -e "s#@@COMPANY_FULLNAME@@#${COMPANY_FULLNAME}#g" \ + -e "s#@@VERSION@@#${VERSION}#g" \ + -e "s#@@PACKAGE_RELEASE@@#${PACKAGE_RELEASE}#g" \ + -e "s#@@VERSIONFULL@@#${VERSIONFULL}#g" \ + -e "s#@@INSTALLDIR@@#${INSTALLDIR}#g" \ + -e "s#@@BUILDDIR@@#${OUTPUTDIR}#g" \ + -e "s#@@STAGEDIR@@#${STAGEDIR}#g" \ + -e "s#@@SCRIPTDIR@@#${SCRIPTDIR}#g" \ + -e "s#@@MENUNAME@@#${MENUNAME}#g" \ + -e "s#@@PRODUCTURL@@#${PRODUCTURL}#g" \ + -e "s#@@PREDEPENDS@@#${PREDEPENDS}#g" \ + -e "s#@@DEPENDS@@#${DEPENDS}#g" \ + -e "s#@@RECOMMENDS@@#${RECOMMENDS}#g" \ + -e "s#@@PROVIDES@@#${PROVIDES}#g" \ + -e "s#@@ARCHITECTURE@@#${ARCHITECTURE}#g" \ + -e "s#@@MAINTNAME@@#${MAINTNAME}#g" \ + -e "s#@@MAINTMAIL@@#${MAINTMAIL}#g" \ + -e "s#@@REPOCONFIG@@#${REPOCONFIG}#g" \ + -e "s#@@REPOCONFIGREGEX@@#${REPOCONFIGREGEX}#g" \ + -e "s#@@SHORTDESC@@#${SHORTDESC}#g" \ + -e "s#@@FULLDESC@@#${FULLDESC}#g" \ + -e "s#@@USR_BIN_SYMLINK_NAME@@#${USR_BIN_SYMLINK_NAME:-}#g" \ + -e "s#@@LOGO_RESOURCES_PNG@@#${LOGO_RESOURCES_PNG}#g" \ + -e "s#@@LOGO_RESOURCE_XPM@@#${LOGO_RESOURCE_XPM}#g" \ + -e "s#@@DATE_RFC5322@@#$(date --rfc-email)#g" \ + > "$TMPLOUT" <<< "$TMPLINCL" +) + +# Setup the installation directory hierarchy in the package staging area. +prep_staging_common() { + install -m 755 -d "${STAGEDIR}/${INSTALLDIR}" \ + "${STAGEDIR}/usr/bin" \ + "${STAGEDIR}/usr/share/applications" \ + "${STAGEDIR}/usr/share/appdata" \ + "${STAGEDIR}/usr/share/gnome-control-center/default-apps" \ + "${STAGEDIR}/usr/share/man/man1" +} + +get_version_info() { + source "${OUTPUTDIR}/installer/version.txt" + VERSION="${MAJOR}.${MINOR}.${BUILD}.${PATCH}" + # TODO(phajdan.jr): Provide a mechanism to pass a different package + # release number if needed. The meaning of it is to bump it for + # packaging-only changes while the underlying software has the same version. + # This corresponds to the Release field in RPM spec files and debian_revision + # component of the Version field for DEB control file. + # Generally with Chrome's fast release cycle it'd be more hassle to try + # to bump this number between releases. + PACKAGE_RELEASE="1" +} + +stage_install_common() { + log_cmd echo "Staging common install files in '${STAGEDIR}'..." + + # Note: Changes here may also need to be applied to ChromeOS's + # chromite/lib/chrome_util.py. + + # Note: This only supports static binaries and does not work when the GN + # is_component_build flag is true. + + # app + STRIPPEDFILE="${OUTPUTDIR}/${PROGNAME}.stripped" + install -m 755 "${STRIPPEDFILE}" "${STAGEDIR}/${INSTALLDIR}/${PROGNAME}" + + # crashpad + buildfile="${OUTPUTDIR}/chrome_crashpad_handler" + strippedfile="${buildfile}.stripped" + debugfile="${buildfile}.debug" + "${OUTPUTDIR}/installer/common/eu-strip" -o "${strippedfile}" -f "${debugfile}" "${buildfile}" + install -m 755 "${strippedfile}" "${STAGEDIR}/${INSTALLDIR}/chrome_crashpad_handler" + + # resources + install -m 644 "${OUTPUTDIR}/resources.pak" "${STAGEDIR}/${INSTALLDIR}/" + # TODO(mmoss): This has broken a couple times on adding new .pak files. Maybe + # we should flag all installer files in FILES.cfg and get them from there, so + # there's only one place people need to keep track of such things (and in + # only the public repository). + if [ -r "${OUTPUTDIR}/chrome_100_percent.pak" ]; then + install -m 644 "${OUTPUTDIR}/chrome_100_percent.pak" "${STAGEDIR}/${INSTALLDIR}/" + install -m 644 "${OUTPUTDIR}/chrome_200_percent.pak" "${STAGEDIR}/${INSTALLDIR}/" + else + install -m 644 "${OUTPUTDIR}/theme_resources_100_percent.pak" "${STAGEDIR}/${INSTALLDIR}/" + install -m 644 "${OUTPUTDIR}/ui_resources_100_percent.pak" "${STAGEDIR}/${INSTALLDIR}/" + fi + + # ICU data file; Necessary when the GN icu_use_data_file flag is true. + install -m 644 "${OUTPUTDIR}/icudtl.dat" "${STAGEDIR}/${INSTALLDIR}/" + + # V8 snapshot files; Necessary when the GN v8_use_external_startup_data flag + # is true. + # Use v8_context_snapshot.bin instead of snapshot_blob.bin if it is available. + # TODO(crbug.com/764576): Unship snapshot_blob.bin on ChromeOS and drop this branch + if [ -f "${OUTPUTDIR}/v8_context_snapshot.bin" ]; then + install -m 644 "${OUTPUTDIR}/v8_context_snapshot.bin" "${STAGEDIR}/${INSTALLDIR}/" + else + install -m 644 "${OUTPUTDIR}/snapshot_blob.bin" "${STAGEDIR}/${INSTALLDIR}/" + fi + + # sandbox + # Rename sandbox binary with hyphen instead of underscore because that's what + # the code looks for. Originally, the SCons build system may have had a bug + # where it did not support hyphens, so this is stuck as is to avoid breaking + # anyone who expects the build artifact to have the underscore. + # the code looks for, but the build targets can't use hyphens (scons bug?) + buildfile="${OUTPUTDIR}/${PROGNAME}_sandbox" + strippedfile="${buildfile}.stripped" + debugfile="${buildfile}.debug" + "${OUTPUTDIR}/installer/common/eu-strip" -o "${strippedfile}" -f "${debugfile}" "${buildfile}" + install -m 4755 "${strippedfile}" "${STAGEDIR}/${INSTALLDIR}/chrome-sandbox" + + # l10n paks + install -m 755 -d "${STAGEDIR}/${INSTALLDIR}/locales/" + find "${OUTPUTDIR}/locales" -type f -name '*.pak' -exec \ + cp -a '{}' "${STAGEDIR}/${INSTALLDIR}/locales/" \; + find "${STAGEDIR}/${INSTALLDIR}/locales" -type f -exec chmod 644 '{}' \; + + # TODO(https://crbug.com/1077934): The below conditions check for the + # existence of files to determine if they should be copied to the staging + # directory. However, these may be stale if the build config no longer + # builds these files. The build config should be obtained from gn rather than + # guessed based on the presence of files. + + # MEI Preload + if [ -f "${OUTPUTDIR}/MEIPreload/manifest.json" ]; then + install -m 755 -d "${STAGEDIR}/${INSTALLDIR}/MEIPreload/" + install -m 644 "${OUTPUTDIR}/MEIPreload/manifest.json" "${STAGEDIR}/${INSTALLDIR}/MEIPreload/" + install -m 644 "${OUTPUTDIR}/MEIPreload/preloaded_data.pb" "${STAGEDIR}/${INSTALLDIR}/MEIPreload/" + fi + + # Widevine CDM. + if [ -d "${OUTPUTDIR}/WidevineCdm" ]; then + # No need to strip; libwidevinecdm.so starts out stripped. + cp -a "${OUTPUTDIR}/WidevineCdm" "${STAGEDIR}/${INSTALLDIR}/" + find "${STAGEDIR}/${INSTALLDIR}/WidevineCdm" -type d -exec chmod 755 '{}' \; + find "${STAGEDIR}/${INSTALLDIR}/WidevineCdm" -type f -exec chmod 644 '{}' \; + find "${STAGEDIR}/${INSTALLDIR}/WidevineCdm" -name libwidevinecdm.so \ + -exec chmod ${SHLIB_PERMS} '{}' \; + fi + + # ANGLE + if [ -f "${OUTPUTDIR}/libEGL.so" ]; then + for file in libEGL.so libGLESv2.so; do + buildfile="${OUTPUTDIR}/${file}" + strippedfile="${buildfile}.stripped" + debugfile="${buildfile}.debug" + "${OUTPUTDIR}/installer/common/eu-strip" -o "${strippedfile}" -f "${debugfile}" "${buildfile}" + install -m ${SHLIB_PERMS} "${strippedfile}" "${STAGEDIR}/${INSTALLDIR}/${file}" + done + fi + + # ANGLE's libvulkan library + if [ -f "${OUTPUTDIR}/libvulkan.so.1" ]; then + file="libvulkan.so.1" + buildfile="${OUTPUTDIR}/${file}" + strippedfile="${buildfile}.stripped" + debugfile="${buildfile}.debug" + "${OUTPUTDIR}/installer/common/eu-strip" -o "${strippedfile}" -f "${debugfile}" "${buildfile}" + install -m 755 "${strippedfile}" "${STAGEDIR}/${INSTALLDIR}/${file}" + fi + + # SwiftShader ES + if [ -f "${OUTPUTDIR}/swiftshader/libEGL.so" ]; then + install -m 755 -d "${STAGEDIR}/${INSTALLDIR}/swiftshader/" + for file in libEGL.so libGLESv2.so; do + buildfile="${OUTPUTDIR}/swiftshader/${file}" + strippedfile="${buildfile}.stripped" + debugfile="${buildfile}.debug" + "${OUTPUTDIR}/installer/common/eu-strip" -o "${strippedfile}" -f "${debugfile}" "${buildfile}" + install -m ${SHLIB_PERMS} "${strippedfile}" "${STAGEDIR}/${INSTALLDIR}/swiftshader/${file}" + done + fi + + # SwiftShader VK + if [ -f "${OUTPUTDIR}/libvk_swiftshader.so" ]; then + install -m 755 -d "${STAGEDIR}/${INSTALLDIR}/" + file="libvk_swiftshader.so" + buildfile="${OUTPUTDIR}/${file}" + strippedfile="${buildfile}.stripped" + debugfile="${buildfile}.debug" + "${OUTPUTDIR}/installer/common/eu-strip" -o "${strippedfile}" -f "${debugfile}" "${buildfile}" + install -m ${SHLIB_PERMS} "${strippedfile}" "${STAGEDIR}/${INSTALLDIR}/${file}" + # Install the ICD json file to point ANGLE to libvk_swiftshader.so + install -m 644 "${OUTPUTDIR}/vk_swiftshader_icd.json" "${STAGEDIR}/${INSTALLDIR}/" + fi + + # libc++ + if [ -f "${OUTPUTDIR}/lib/libc++.so" ]; then + install -m 755 -d "${STAGEDIR}/${INSTALLDIR}/lib/" + install -m ${SHLIB_PERMS} -s "${OUTPUTDIR}/lib/libc++.so" "${STAGEDIR}/${INSTALLDIR}/lib/" + fi + + # nacl_helper and nacl_helper_bootstrap + # Don't use "-s" (strip) because this runs binutils "strip", which + # mangles the special ELF program headers of nacl_helper_bootstrap. + # Explicitly use eu-strip instead, because it doesn't have that problem. + for file in nacl_helper nacl_helper_bootstrap; do + buildfile="${OUTPUTDIR}/${file}" + if [ -f "${buildfile}" ]; then + strippedfile="${buildfile}.stripped" + debugfile="${buildfile}.debug" + "${OUTPUTDIR}/installer/common/eu-strip" -o "${strippedfile}" -f "${debugfile}" "${buildfile}" + install -m 755 "${strippedfile}" "${STAGEDIR}/${INSTALLDIR}/${file}" + fi + done + # Don't use "-s" (strip) because this would use the Linux toolchain to + # strip the NaCl binary, which has the potential to break it. It + # certainly resets the OSABI and ABIVERSION fields to non-NaCl values, + # although the NaCl IRT loader doesn't care about these fields. In any + # case, the IRT binaries are already stripped by NaCl's build process. + for filename in ${OUTPUTDIR}/nacl_irt_*.nexe; do + # Re-check the filename in case globbing matched nothing. + if [ -f "$filename" ]; then + install -m 644 "$filename" "${STAGEDIR}/${INSTALLDIR}/`basename "$filename"`" + fi + done + + # default apps + if [ -d "${OUTPUTDIR}/default_apps" ]; then + cp -a "${OUTPUTDIR}/default_apps" "${STAGEDIR}/${INSTALLDIR}/" + find "${STAGEDIR}/${INSTALLDIR}/default_apps" -type d -exec chmod 755 '{}' \; + find "${STAGEDIR}/${INSTALLDIR}/default_apps" -type f -exec chmod 644 '{}' \; + fi + + # launcher script and symlink + process_template "${OUTPUTDIR}/installer/common/wrapper" \ + "${STAGEDIR}/${INSTALLDIR}/${PACKAGE}" + chmod 755 "${STAGEDIR}/${INSTALLDIR}/${PACKAGE}" + if [ ! -z "${PACKAGE_ORIG}" ]; then + if [ ! -f "${STAGEDIR}/${INSTALLDIR}/${PACKAGE_ORIG}" ]; then + ln -sn "${INSTALLDIR}/${PACKAGE}" \ + "${STAGEDIR}/${INSTALLDIR}/${PACKAGE_ORIG}" + fi + fi + if [ ! -z "${USR_BIN_SYMLINK_NAME}" ]; then + ln -snf "${INSTALLDIR}/${PACKAGE}" \ + "${STAGEDIR}/usr/bin/${USR_BIN_SYMLINK_NAME}" + fi + + # app icons + local icon_regex=".*product_logo_[0-9]\+\." + if [ "$BRANDING" = "google_chrome" ]; then + if [ "$CHANNEL" = "beta" ]; then + icon_regex=".*product_logo_[0-9]\+_beta\." + elif [ "$CHANNEL" = "unstable" ]; then + icon_regex=".*product_logo_[0-9]\+_dev\." + fi + fi + LOGO_RESOURCES_PNG=$(find "${OUTPUTDIR}/installer/theme/" \ + -regextype sed -regex "${icon_regex}png" -printf "%f ") + LOGO_RESOURCE_XPM=$(find "${OUTPUTDIR}/installer/theme/" \ + -regextype sed -regex "${icon_regex}xpm" -printf "%f") + for logo in ${LOGO_RESOURCES_PNG} ${LOGO_RESOURCE_XPM}; do + install -m 644 \ + "${OUTPUTDIR}/installer/theme/${logo}" \ + "${STAGEDIR}/${INSTALLDIR}/" + done + + # desktop integration + install -m 755 "${OUTPUTDIR}/xdg-mime" "${STAGEDIR}${INSTALLDIR}/" + install -m 755 "${OUTPUTDIR}/xdg-settings" "${STAGEDIR}${INSTALLDIR}/" + + if [ ${PACKAGE:0:6} = google ]; then + process_template "${OUTPUTDIR}/installer/common/google-chrome.appdata.xml.template" \ + "${STAGEDIR}/usr/share/appdata/${PACKAGE}.appdata.xml" + chmod 644 "${STAGEDIR}/usr/share/appdata/${PACKAGE}.appdata.xml" + else + install -m 644 "${OUTPUTDIR}/installer/common/chromium-browser.appdata.xml" \ + "${STAGEDIR}/usr/share/appdata/${PACKAGE}.appdata.xml" + fi + + process_template "${OUTPUTDIR}/installer/common/desktop.template" \ + "${STAGEDIR}/usr/share/applications/${PACKAGE}.desktop" + chmod 644 "${STAGEDIR}/usr/share/applications/${PACKAGE}.desktop" + process_template "${OUTPUTDIR}/installer/common/default-app.template" \ + "${STAGEDIR}/usr/share/gnome-control-center/default-apps/${PACKAGE}.xml" + chmod 644 "${STAGEDIR}/usr/share/gnome-control-center/default-apps/${PACKAGE}.xml" + process_template "${OUTPUTDIR}/installer/common/default-app-block.template" \ + "${STAGEDIR}${INSTALLDIR}/default-app-block" + chmod 644 "${STAGEDIR}${INSTALLDIR}/default-app-block" + + # documentation + process_template "${OUTPUTDIR}/installer/common/manpage.1.in" \ + "${STAGEDIR}/usr/share/man/man1/${USR_BIN_SYMLINK_NAME}.1" + gzip -9n "${STAGEDIR}/usr/share/man/man1/${USR_BIN_SYMLINK_NAME}.1" + chmod 644 "${STAGEDIR}/usr/share/man/man1/${USR_BIN_SYMLINK_NAME}.1.gz" + # The stable channel allows launching the app without the "-stable" + # suffix like the other channels. Create a linked man page for the + # app-without-the-channel case. + if [ ! -f "${STAGEDIR}/usr/share/man/man1/${PACKAGE}.1.gz" ]; then + ln -s "${USR_BIN_SYMLINK_NAME}.1.gz" \ + "${STAGEDIR}/usr/share/man/man1/${PACKAGE}.1.gz" + fi + + # Check to make sure all the ELF binaries are stripped. + UNSTRIPPED=$(find "${STAGEDIR}/${INSTALLDIR}/" -type f | xargs file | + grep ELF | grep -c "not stripped" || true) + if [ "${UNSTRIPPED}" != "0" ]; then + echo "ERROR: Found ${UNSTRIPPED} unstripped ELF files." 1>&2 + exit 1 + fi + + # Check to make sure no ELF binaries set RPATH. + if [ "${TARGET_OS}" != "chromeos" ]; then + RPATH_BINS= + for elf in $(find "${STAGEDIR}/${INSTALLDIR}/" -type f | xargs file | + grep ELF | awk '{print $1;}' | sed 's/:$//'); do + if readelf -d ${elf} | grep "(RPATH)" >/dev/null; then + RPATH_BINS="${RPATH_BINS} $(basename ${elf})" + fi + done + if [ -n "${RPATH_BINS}" ]; then + echo "ERROR: Found binaries with RPATH set:${RPATH_BINS}" 1>&2 + exit 1 + fi + fi + + # Make sure ELF binaries live in INSTALLDIR exclusively. + ELF_OUTSIDE_INSTALLDIR=$(find "${STAGEDIR}/" -not -path \ + "${STAGEDIR}${INSTALLDIR}/*" -type f | xargs file -b | + grep -ce "^ELF" || true) + if [ "${ELF_OUTSIDE_INSTALLDIR}" -ne 0 ]; then + echo "ERROR: Found ${ELF_OUTSIDE_INSTALLDIR} ELF binaries" \ + "outside of ${INSTALLDIR}" 1>&2 + exit 1 + fi + + # Verify file permissions. + for file in $(find "${STAGEDIR}" -mindepth 1); do + local actual_perms=$(stat -c "%a" "${file}") + local file_type="$(file -b "${file}")" + local base_name=$(basename "${file}") + if [[ "${file_type}" = "directory"* ]]; then + local expected_perms=755 + elif [[ "${file_type}" = *"symbolic link"* ]]; then + if [[ "$(readlink ${file})" = "/"* ]]; then + # Absolute symlink. + local expect_exists="${STAGEDIR}/$(readlink "${file}")" + else + # Relative symlink. + local expect_exists="$(dirname "${file}")/$(readlink "${file}")" + fi + if [ ! -f "${expect_exists}" ]; then + echo "Broken symlink: ${file}" 1>&2 + exit 1 + fi + local expected_perms=777 + elif [ "${base_name}" = "chrome-sandbox" ]; then + local expected_perms=4755 + elif [[ "${base_name}" = "nacl_irt_"*".nexe" ]]; then + local expected_perms=644 + elif [[ "${file_type}" = *"shell script"* ]]; then + local expected_perms=755 + elif [[ "${file_type}" = ELF* ]]; then + if [[ "${base_name}" = *".so" ]]; then + local expected_perms=${SHLIB_PERMS} + else + local expected_perms=755 + fi + else + # Regular data file. + local expected_perms=644 + fi + if [ ${expected_perms} -ne ${actual_perms} ]; then + echo Expected permissions on ${base_name} to be \ + ${expected_perms}, but they were ${actual_perms} 1>&2 + exit 1 + fi + done +} diff --git a/chrome/installer/linux/debian/debian.menu b/chrome/installer/linux/debian/debian.menu new file mode 100644 index 00000000..25245dff --- /dev/null +++ b/chrome/installer/linux/debian/debian.menu @@ -0,0 +1,6 @@ +?package(@@USR_BIN_SYMLINK_NAME@@):needs="x11" \ + section="Applications/Network/Web Browsing" \ + hints="Web browsers" \ + title="Thorium Browser" \ + icon="@@INSTALLDIR@@/@@LOGO_RESOURCE_XPM@@" \ + command="@@INSTALLDIR@@/@@PACKAGE@@" diff --git a/chrome/installer/linux/debian/manual_recommends b/chrome/installer/linux/debian/manual_recommends new file mode 100644 index 00000000..49476ae8 --- /dev/null +++ b/chrome/installer/linux/debian/manual_recommends @@ -0,0 +1,21 @@ +# Copyright 2019 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# Recommended dependencies not in the dpkg-shlibdeps output. + +# u2f udev rules have moved from being installed by default by systemd on Debian +# systems to a separate package called libu2f-udev. Pull it in manually so that +# u2f keys will work. TODO(https://crbug.com/784010): Move this to "Depends" +# once support for Jessie, Stretch, Trusty, and Xenial are dropped. +libu2f-udev + +# Try to use Vulkan when possible. libvulkan1 is not available on Ubuntu Trusty +# or Debian Jessie, so it is added to "Recommends" instead of "Depends". +# TODO(https://crbug.com/784010): Move this to "Depends" once support for +# Trusty and Jessie are dropped. Note that the dependency must still be manually +# added since the library is dlopen()'ed. +libvulkan1 + +#Add unrar for unrar support +unrar diff --git a/content/gpu/BUILD.gn b/content/gpu/BUILD.gn new file mode 100644 index 00000000..e511a3fa --- /dev/null +++ b/content/gpu/BUILD.gn @@ -0,0 +1,147 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/chromeos/ui_mode.gni") +import("//build/config/ui.gni") +import("//gpu/vulkan/features.gni") +import("//media/media_options.gni") +import("//media/gpu/args.gni") + +# See //content/BUILD.gn for how this works. +group("gpu") { + visibility = [ "//content/*" ] # This is an internal content API. + + if (is_component_build) { + public_deps = [ "//content" ] + } else { + public_deps = [ ":gpu_sources" ] + } +} + +if (is_component_build) { + link_target_type = "source_set" +} else { + link_target_type = "static_library" +} + +target(link_target_type, "gpu_sources") { + # This is an internal content API. Code outside of the content "component" + # (like content/test and content/shell) should depend on ":gpu" above. + visibility = [ "//content/*" ] + + sources = [ + "browser_exposed_gpu_interfaces.cc", + "browser_exposed_gpu_interfaces.h", + "gpu_child_thread.cc", + "gpu_child_thread.h", + "gpu_child_thread_receiver_bindings.cc", + "gpu_main.cc", + "gpu_process.cc", + "gpu_process.h", + "gpu_service_factory.cc", + "gpu_service_factory.h", + "in_process_gpu_thread.cc", + "in_process_gpu_thread.h", + ] + + configs += [ "//content:content_implementation" ] + + deps = [ + "//base", + "//base/third_party/dynamic_annotations", + "//build:branding_buildflags", + "//build:chromeos_buildflags", + "//components/viz/service", + "//content:export", + "//content/child", + "//content/common", + "//content/common:mojo_bindings", + "//content/public/child:child_sources", + "//content/public/common:common_sources", + "//gpu:gpu", + "//gpu/ipc/common:command_buffer_traits", + "//gpu/ipc/service", + "//ipc", + "//media:media_buildflags", + "//media/gpu", + "//media/mojo:buildflags", + + # TODO(jrummell): As //media/gpu/ipc/service is a source_set in a + # component build, determine if it should not be included here. + # http://crbug.com/702833. + "//components/viz/service/main", + "//media/gpu/ipc/service", + "//media/mojo/clients:clients", + "//sandbox/policy:chromecast_sandbox_allowlist_buildflags", + "//services/service_manager/public/cpp", + "//services/service_manager/public/mojom", + "//services/tracing/public/cpp", + "//services/viz/privileged/mojom", + "//skia", + "//third_party/angle:angle_gpu_info_util", + "//ui/gfx/ipc", + "//ui/gl", + "//ui/gl/init", + "//ui/latency/ipc", + ] + + if (!is_chromeos_ash || !is_chrome_branded) { + deps += [ + "//services/shape_detection:lib", + "//services/shape_detection/public/mojom", + ] + } + + if (is_linux || is_chromeos) { + sources += [ + "gpu_sandbox_hook_linux.cc", + "gpu_sandbox_hook_linux.h", + ] + } + + if (is_android) { + deps += [ + "//components/tracing:graphics_provider", + "//media", + ] + } + + if (mojo_media_host == "gpu") { + deps += [ "//media/mojo/services" ] + } + + if (is_linux || is_chromeos || is_mac || is_win) { + deps += [ "//sandbox" ] + } + + if (is_mac) { + deps += [ "//components/metal_util" ] + } + + if (use_x11) { + deps += [ + "//ui/events/platform/x11", + "//ui/gfx/linux:gpu_memory_buffer_support_x11", + "//ui/gfx/x", + ] + } + + if (use_ozone) { + deps += [ "//ui/ozone" ] + } + + if (enable_vulkan) { + deps += [ "//gpu/vulkan" ] + } + + # Use DRI on desktop Linux builds. + if (current_cpu != "s390x" && current_cpu != "ppc64" && is_linux && + (!is_chromecast || is_cast_desktop_build)) { + configs += [ "//build/config/linux/dri" ] + } + + if (is_linux && use_vaapi) { + public_configs = [ "//build/config/linux/libva" ] + } +} diff --git a/logos/Logo.png b/logos/Logo.png new file mode 100644 index 00000000..2aa0a320 Binary files /dev/null and b/logos/Logo.png differ diff --git a/logos/SmallLogo.png b/logos/SmallLogo.png new file mode 100644 index 00000000..695a23c2 Binary files /dev/null and b/logos/SmallLogo.png differ diff --git a/logos/chrome_app_icon_192.png b/logos/chrome_app_icon_192.png new file mode 100644 index 00000000..a85e0c78 Binary files /dev/null and b/logos/chrome_app_icon_192.png differ diff --git a/logos/chrome_app_icon_32.png b/logos/chrome_app_icon_32.png new file mode 100644 index 00000000..afd6cfec Binary files /dev/null and b/logos/chrome_app_icon_32.png differ diff --git a/logos/chromium.ico b/logos/chromium.ico new file mode 100644 index 00000000..7817fce4 Binary files /dev/null and b/logos/chromium.ico differ diff --git a/logos/other/product_logo_16.png b/logos/other/product_logo_16.png new file mode 100644 index 00000000..97993fa6 Binary files /dev/null and b/logos/other/product_logo_16.png differ diff --git a/logos/other/product_logo_32.png b/logos/other/product_logo_32.png new file mode 100644 index 00000000..afd6cfec Binary files /dev/null and b/logos/other/product_logo_32.png differ diff --git a/logos/other/product_logo_name_22.png b/logos/other/product_logo_name_22.png new file mode 100644 index 00000000..084114c4 Binary files /dev/null and b/logos/other/product_logo_name_22.png differ diff --git a/logos/other/product_logo_name_22_white.png b/logos/other/product_logo_name_22_white.png new file mode 100644 index 00000000..63b1749a Binary files /dev/null and b/logos/other/product_logo_name_22_white.png differ diff --git a/logos/other/webstore_icon.png b/logos/other/webstore_icon.png new file mode 100644 index 00000000..4c8d1317 Binary files /dev/null and b/logos/other/webstore_icon.png differ diff --git a/logos/other/webstore_icon_16.png b/logos/other/webstore_icon_16.png new file mode 100644 index 00000000..b5cad10a Binary files /dev/null and b/logos/other/webstore_icon_16.png differ diff --git a/logos/other/webstore_icon_24.png b/logos/other/webstore_icon_24.png new file mode 100644 index 00000000..2479c5ec Binary files /dev/null and b/logos/other/webstore_icon_24.png differ diff --git a/logos/other/webstore_icon_32.png b/logos/other/webstore_icon_32.png new file mode 100644 index 00000000..4c02dc21 Binary files /dev/null and b/logos/other/webstore_icon_32.png differ diff --git a/logos/other/x2/product_logo_16.png b/logos/other/x2/product_logo_16.png new file mode 100644 index 00000000..afd6cfec Binary files /dev/null and b/logos/other/x2/product_logo_16.png differ diff --git a/logos/other/x2/product_logo_32.png b/logos/other/x2/product_logo_32.png new file mode 100644 index 00000000..8a87650f Binary files /dev/null and b/logos/other/x2/product_logo_32.png differ diff --git a/logos/other/x2/product_logo_name_22.png b/logos/other/x2/product_logo_name_22.png new file mode 100644 index 00000000..ae8c3096 Binary files /dev/null and b/logos/other/x2/product_logo_name_22.png differ diff --git a/logos/other/x2/product_logo_name_22_white.png b/logos/other/x2/product_logo_name_22_white.png new file mode 100644 index 00000000..729322ff Binary files /dev/null and b/logos/other/x2/product_logo_name_22_white.png differ diff --git a/logos/other/x2/webstore_icon.png b/logos/other/x2/webstore_icon.png new file mode 100644 index 00000000..d3e52a0d Binary files /dev/null and b/logos/other/x2/webstore_icon.png differ diff --git a/logos/other/x2/webstore_icon_16.png b/logos/other/x2/webstore_icon_16.png new file mode 100644 index 00000000..d4030e74 Binary files /dev/null and b/logos/other/x2/webstore_icon_16.png differ diff --git a/logos/other/x2/webstore_icon_24.png b/logos/other/x2/webstore_icon_24.png new file mode 100644 index 00000000..a08d38df Binary files /dev/null and b/logos/other/x2/webstore_icon_24.png differ diff --git a/logos/other/x2/webstore_icon_32.png b/logos/other/x2/webstore_icon_32.png new file mode 100644 index 00000000..9a21a6f9 Binary files /dev/null and b/logos/other/x2/webstore_icon_32.png differ diff --git a/logos/product_logo_128.png b/logos/product_logo_128.png new file mode 100644 index 00000000..1fb0913c Binary files /dev/null and b/logos/product_logo_128.png differ diff --git a/logos/product_logo_16.png b/logos/product_logo_16.png new file mode 100644 index 00000000..97993fa6 Binary files /dev/null and b/logos/product_logo_16.png differ diff --git a/logos/product_logo_22_mono.png b/logos/product_logo_22_mono.png new file mode 100644 index 00000000..412d44ba Binary files /dev/null and b/logos/product_logo_22_mono.png differ diff --git a/logos/product_logo_24.png b/logos/product_logo_24.png new file mode 100644 index 00000000..000900fb Binary files /dev/null and b/logos/product_logo_24.png differ diff --git a/logos/product_logo_256.png b/logos/product_logo_256.png new file mode 100644 index 00000000..17439c5e Binary files /dev/null and b/logos/product_logo_256.png differ diff --git a/logos/product_logo_32.png b/logos/product_logo_32.png new file mode 100644 index 00000000..afd6cfec Binary files /dev/null and b/logos/product_logo_32.png differ diff --git a/logos/product_logo_32.xpm b/logos/product_logo_32.xpm new file mode 100644 index 00000000..0f87a790 --- /dev/null +++ b/logos/product_logo_32.xpm @@ -0,0 +1,297 @@ +/* XPM */ +static char * product_logo_32_xpm[] = { +"32 32 262 2", +" c None", +". c #4E7ADB", +"+ c #4C78DB", +"@ c #4C79DB", +"# c #4A77DA", +"$ c #4B78DA", +"% c #4D7ADB", +"& c #4776DA", +"* c #4272D9", +"= c #4172D9", +"- c #4171D9", +"; c #4071D9", +"> c #3F70D8", +", c #3E6FD8", +"' c #3D6FD8", +") c #4775DA", +"! c #4F7BDC", +"~ c #4574DA", +"{ c #4474D9", +"] c #4373D9", +"^ c #3D6ED8", +"/ c #3C6ED8", +"( c #4675DA", +"_ c #4976D8", +": c #4575D9", +"< c #3B6DD8", +"[ c #3F6FD8", +"} c #4773D5", +"| c #4573D6", +"1 c #4574D8", +"2 c #3A6CD7", +"3 c #3C6DD7", +"4 c #4670D2", +"5 c #4471D4", +"6 c #4472D5", +"7 c #4473D7", +"8 c #4573D9", +"9 c #396CD7", +"0 c #3B6DD7", +"a c #456ECF", +"b c #436ED1", +"c c #446FD2", +"d c #4470D4", +"e c #4471D6", +"f c #4472D8", +"g c #396BD7", +"h c #3B6CD7", +"i c #6C9AEE", +"j c #4169CB", +"k c #426DCF", +"l c #436FD3", +"m c #4370D5", +"n c #4372D7", +"o c #4372D8", +"p c #6BA0F7", +"q c #5580D9", +"r c #416ACD", +"s c #426CCF", +"t c #426ED1", +"u c #426FD3", +"v c #4270D5", +"w c #4271D7", +"x c #4272D8", +"y c #4172D8", +"z c #3861BC", +"A c #294992", +"B c #243C74", +"C c #314070", +"D c #44568D", +"E c #5C72B4", +"F c #6F87CE", +"G c #7089D0", +"H c #728CD2", +"I c #748ED3", +"J c #7590D5", +"K c #7892D6", +"L c #7994D9", +"M c #7B97DB", +"N c #7E9ADE", +"O c #809DDF", +"P c #6EA1F7", +"Q c #699DF3", +"R c #4369C7", +"S c #406ACD", +"T c #416CD0", +"U c #416DD2", +"V c #416FD4", +"W c #4170D5", +"X c #3D69C9", +"Y c #1F3974", +"Z c #000000", +"` c #1D3A6B", +" . c #315CA3", +".. c #305BA3", +"+. c #1C3A6B", +"@. c #414E74", +"#. c #7D92CD", +"$. c #8099D8", +"%. c #829BDB", +"&. c #86A1DE", +"*. c #8AA6E2", +"=. c #8DA9E6", +"-. c #8FACE8", +";. c #91ADE9", +">. c #91AFEA", +",. c #95B2EC", +"'. c #6A9FF7", +"). c #6B9FF7", +"!. c #5B88E0", +"~. c #3D63C5", +"{. c #3F6ACE", +"]. c #406CD1", +"^. c #406ED3", +"/. c #3C68C7", +"(. c #11234C", +"_. c #203863", +":. c #457CD9", +"<. c #4E8DF5", +"[. c #4D8CF5", +"}. c #4C8CF5", +"|. c #427BD9", +"1. c #1F3863", +"2. c #2B354F", +"3. c #89A2DA", +"4. c #91ADE8", +"5. c #92AFEA", +"6. c #93B0EB", +"7. c #94B1EC", +"8. c #95B2ED", +"9. c #96B4ED", +"0. c #97B5EE", +"a. c #98B6EF", +"b. c #486FCC", +"c. c #3E65C7", +"d. c #3F6ACF", +"e. c #1F3872", +"f. c #4D8AF1", +"g. c #4C8BF5", +"h. c #4A88F1", +"i. c #1E3763", +"j. c #596888", +"k. c #96B3ED", +"l. c #98B5EF", +"m. c #99B7F0", +"n. c #9AB9F1", +"o. c #9BBAF2", +"p. c #9CBBF3", +"q. c #9DBCF5", +"r. c #699EF7", +"s. c #6393EB", +"t. c #3C63C5", +"u. c #3D65C9", +"v. c #365CB6", +"w. c #4B8BF5", +"x. c #407AD8", +"y. c #8DA6D7", +"z. c #9DBBF3", +"A. c #9EBCF4", +"B. c #9FBEF5", +"C. c #9FBFF6", +"D. c #A0C0F7", +"E. c #A1C1F8", +"F. c #A1C2F9", +"G. c #A2C3FA", +"H. c #4F7AD5", +"I. c #3C64C8", +"J. c #27438A", +"K. c #4A8AF4", +"L. c #498AF4", +"M. c #1B396A", +"N. c #778AB0", +"O. c #A2C1F8", +"P. c #A3C2F9", +"Q. c #A3C3F9", +"R. c #A3C3FA", +"S. c #689EF7", +"T. c #6599F1", +"U. c #3F66C8", +"V. c #23386F", +"W. c #2D59A2", +"X. c #75819B", +"Y. c #A4C4FA", +"Z. c #679DF7", +"`. c #5583DE", +" + c #24386F", +".+ c #4989F4", +"++ c #76829C", +"@+ c #A2C2FA", +"#+ c #679CF6", +"$+ c #669BF5", +"%+ c #335092", +"&+ c #1A386A", +"*+ c #7C8FB2", +"=+ c #A1C1F9", +"-+ c #669CF6", +";+ c #679DF6", +">+ c #5684D3", +",+ c #3F79D8", +"'+ c #97B1DF", +")+ c #A1C1FA", +"!+ c #3A588B", +"~+ c #4988F1", +"{+ c #4786F0", +"]+ c #1D3763", +"^+ c #64728E", +"/+ c #6194E9", +"(+ c #263B5E", +"_+ c #1D2D50", +":+ c #9FBAEC", +"<+ c #A1C2FA", +"[+ c #649AF4", +"}+ c #659BF6", +"|+ c #3B598B", +"1+ c #334D80", +"2+ c #83A2E1", +"3+ c #9FC0F8", +"4+ c #649BF6", +"5+ c #5B8BDA", +"6+ c #476CAB", +"7+ c #3C5888", +"8+ c #3F5A8A", +"9+ c #4A6AA8", +"0+ c #537ECE", +"a+ c #5F8CE5", +"b+ c #A2C0F7", +"c+ c #6399F4", +"d+ c #639AF6", +"e+ c #629AF6", +"f+ c #5E92EE", +"g+ c #5A8CE8", +"h+ c #8AACEE", +"i+ c #9EBFF7", +"j+ c #649AF5", +"k+ c #6299F6", +"l+ c #5B8FEB", +"m+ c #6994E8", +"n+ c #A0C1F9", +"o+ c #6399F5", +"p+ c #6096F3", +"q+ c #5B8DEA", +"r+ c #92B3F1", +"s+ c #6299F5", +"t+ c #6199F6", +"u+ c #5E94F1", +"v+ c #749CEB", +"w+ c #6198F5", +"x+ c #5B8FEC", +"y+ c #9BBCF6", +"z+ c #5F95F2", +"A+ c #5F96F2", +"B+ c #80A6EF", +"C+ c #9CBDF6", +"D+ c #6098F5", +"E+ c #6097F5", +"F+ c #6193EE", +"G+ c #9FC0F9", +"H+ c #9DBDF6", +"I+ c #5D93F1", +"J+ c #5B91EE", +"K+ c #82A6ED", +"L+ c #9DBDF5", +"M+ c #9CBCF5", +" ", +" . + @ + # $ ", +" % & * = = - ; > , ' - ) ", +" ! ~ { ] * * = - ; > , , ' ^ / ( ", +" _ : ~ { ] ] * = - ; > , , ' ^ / < [ ", +" } | 1 ~ { ] ] * = - ; > , , ' ^ / < 2 3 ", +" 4 5 6 7 8 { ] * * = - ; > , , ' ^ / < 2 9 0 ", +" a b c d e f { ] * = = - ; > , ' ' ^ / < 2 9 g h ", +" i j k b l m n o ] * = - - ; > , ' ^ ^ / < 2 9 g g [ ", +" p q r s t u v w x y z A B C D E F G H I J K L M N O ", +" P p Q R S T U V W X Y Z ` ...+.Z @.#.$.%.&.*.=.-.;.>.,. ", +" '.).).!.~.{.].^./.(._.:.<.[.[.}.|.1.2.3.4.5.6.7.8.9.0.a. ", +" '.'.'.).b.c.d.].e._.f.<.<.[.}.}.g.h.i.j.k.l.a.m.n.o.p.q. ", +" r.r.r.r.r.s.t.u.v.Z :.<.<.[.}.}.g.w.w.x.Z y.z.A.B.C.D.E.F.G. ", +" '.r.r.r.r.r.H.I.J.` <.<.[.}.}.g.g.w.K.L.M.N.O.P.Q.R.R.G.G.R. ", +" S.S.r.r.r.r.T.U.V. .[.[.}.}.g.g.w.K.K.L.W.X.Y.Y.R.R.R.G.G.G. ", +" Z.Z.S.S.S.S.S.`. +..[.}.}.g.g.w.K.K.L..+W.++Y.Y.R.R.G.G.G.@+ ", +" #+Z.Z.Z.Z.Z.Z.$+%++.}.g.g.g.w.K.K.L.L..+&+*+Y.R.R.R.G.G.@+=+ ", +" -+;+;+;+;+;+;+;+>+Z |.g.w.w.K.K.L.L..+,+Z '+R.R.R.G.G.G.@+)+ ", +" -+-+-+-+-+-+-+-+!+1.~+w.K.K.L.L..+{+]+^+Y.R.R.R.G.G.@+@+ ", +" -+-+-+-+-+-+-+-+/+(+1.x.L.L..+.+,+]+_+:+R.R.R.G.G.G.@+<+ ", +" [+}+-+-+-+}+}+}+}+/+|+Z M.W.W.&+Z 1+2+R.R.R.G.G.G.@+<+3+ ", +" }+}+}+}+}+4+4+4+4+}+5+6+7+8+9+0+a+b+R.R.G.G.G.@+@+<+ ", +" c+4+4+4+4+4+4+4+4+4+d+d+d+e+f+g+h+R.R.G.G.G.@+@+<+i+ ", +" j+4+4+4+4+4+d+d+d+d+e+e+k+l+m+P.R.G.G.G.@+@+<+n+ ", +" o+d+d+d+d+d+e+e+e+e+k+p+q+r+G.G.G.G.@+@+<+n+ ", +" s+e+e+e+e+e+k+k+k+t+u+v+R.G.G.G.@+@+<+n+ ", +" w+k+k+k+k+k+t+t+w+x+y+G.G.G.@+@+<+3+ ", +" z+t+t+t+t+t+t+A+B+G.G.@+@+<+<+C+ ", +" z+D+t+t+E+F+E.@+@+@+G+H+ ", +" I+J+K+L+H+M+ ", +" "}; diff --git a/logos/product_logo_48.png b/logos/product_logo_48.png new file mode 100644 index 00000000..0782a2b0 Binary files /dev/null and b/logos/product_logo_48.png differ diff --git a/logos/product_logo_512.png b/logos/product_logo_512.png new file mode 100644 index 00000000..d3b428df Binary files /dev/null and b/logos/product_logo_512.png differ diff --git a/logos/product_logo_64.png b/logos/product_logo_64.png new file mode 100644 index 00000000..8a87650f Binary files /dev/null and b/logos/product_logo_64.png differ diff --git a/logos/technetium.png b/logos/technetium.png new file mode 100644 index 00000000..81daef09 Binary files /dev/null and b/logos/technetium.png differ diff --git a/logos/technetium.svg b/logos/technetium.svg new file mode 100644 index 00000000..3fc1c3e5 --- /dev/null +++ b/logos/technetium.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + diff --git a/logos/thorium.png b/logos/thorium.png new file mode 100644 index 00000000..5d462db1 Binary files /dev/null and b/logos/thorium.png differ diff --git a/media/gpu/gpu_video_decode_accelerator_factory.cc b/media/gpu/gpu_video_decode_accelerator_factory.cc new file mode 100644 index 00000000..7de6e161 --- /dev/null +++ b/media/gpu/gpu_video_decode_accelerator_factory.cc @@ -0,0 +1,247 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/gpu/gpu_video_decode_accelerator_factory.h" + +#include + +#include "base/memory/ptr_util.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "gpu/config/gpu_preferences.h" +#include "media/base/media_switches.h" +#include "media/gpu/buildflags.h" +#include "media/gpu/gpu_video_accelerator_util.h" +#include "media/gpu/macros.h" +#include "media/gpu/media_gpu_export.h" +#include "media/media_buildflags.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#include "media/gpu/windows/dxva_video_decode_accelerator_win.h" +#endif +#if defined(OS_MAC) +#include "media/gpu/mac/vt_video_decode_accelerator_mac.h" +#endif +#if BUILDFLAG(USE_VAAPI) +#include "media/gpu/vaapi/vaapi_video_decode_accelerator.h" +#include "ui/gl/gl_implementation.h" +#elif BUILDFLAG(USE_V4L2_CODEC) +#include "media/gpu/v4l2/v4l2_device.h" +#include "media/gpu/v4l2/v4l2_slice_video_decode_accelerator.h" +#include "media/gpu/v4l2/v4l2_video_decode_accelerator.h" +#include "ui/gl/gl_surface_egl.h" +#endif + +namespace media { + +namespace { + +gpu::VideoDecodeAcceleratorCapabilities GetDecoderCapabilitiesInternal( + const gpu::GpuPreferences& gpu_preferences, + const gpu::GpuDriverBugWorkarounds& workarounds) { + if (gpu_preferences.disable_accelerated_video_decode) + return gpu::VideoDecodeAcceleratorCapabilities(); + + // Query VDAs for their capabilities and construct a set of supported + // profiles for current platform. This must be done in the same order as in + // CreateVDA(), as we currently preserve additional capabilities (such as + // resolutions supported) only for the first VDA supporting the given codec + // profile (instead of calculating a superset). + // TODO(posciak,henryhsu): improve this so that we choose a superset of + // resolutions and other supported profile parameters. + VideoDecodeAccelerator::Capabilities capabilities; +#if defined(OS_WIN) + capabilities.supported_profiles = + DXVAVideoDecodeAccelerator::GetSupportedProfiles(gpu_preferences, + workarounds); +#elif BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) +#if BUILDFLAG(USE_VAAPI) + capabilities.supported_profiles = + VaapiVideoDecodeAccelerator::GetSupportedProfiles(); +#elif BUILDFLAG(USE_V4L2_CODEC) + GpuVideoAcceleratorUtil::InsertUniqueDecodeProfiles( + V4L2VideoDecodeAccelerator::GetSupportedProfiles(), + &capabilities.supported_profiles); + GpuVideoAcceleratorUtil::InsertUniqueDecodeProfiles( + V4L2SliceVideoDecodeAccelerator::GetSupportedProfiles(), + &capabilities.supported_profiles); +#endif +#elif defined(OS_MAC) + capabilities.supported_profiles = + VTVideoDecodeAccelerator::GetSupportedProfiles(workarounds); +#endif + + return GpuVideoAcceleratorUtil::ConvertMediaToGpuDecodeCapabilities( + capabilities); +} + +} // namespace + +// static +MEDIA_GPU_EXPORT std::unique_ptr +GpuVideoDecodeAcceleratorFactory::Create( + const GpuVideoDecodeGLClient& gl_client) { + return base::WrapUnique(new GpuVideoDecodeAcceleratorFactory(gl_client)); +} + +// static +MEDIA_GPU_EXPORT gpu::VideoDecodeAcceleratorCapabilities +GpuVideoDecodeAcceleratorFactory::GetDecoderCapabilities( + const gpu::GpuPreferences& gpu_preferences, + const gpu::GpuDriverBugWorkarounds& workarounds) { + // Cache the capabilities so that they will not be computed more than once per + // GPU process. It is assumed that |gpu_preferences| and |workarounds| do not + // change between calls. + // TODO(sandersd): Move cache to GpuMojoMediaClient once + // |video_decode_accelerator_capabilities| is removed from GPUInfo. + static gpu::VideoDecodeAcceleratorCapabilities capabilities = + GetDecoderCapabilitiesInternal(gpu_preferences, workarounds); + +#if BUILDFLAG(USE_V4L2_CODEC) + // V4L2-only: the decoder devices may not be visible at the time the GPU + // process is starting. If the capabilities vector is empty, try to query the + // devices again in the hope that they will have appeared in the meantime. + // TODO(crbug.com/948147): trigger query when an device add/remove event + // (e.g. via udev) has happened instead. + if (capabilities.supported_profiles.empty()) { + VLOGF(1) << "Capabilities empty, querying again..."; + capabilities = GetDecoderCapabilitiesInternal(gpu_preferences, workarounds); + } +#endif + + return capabilities; +} + +MEDIA_GPU_EXPORT std::unique_ptr +GpuVideoDecodeAcceleratorFactory::CreateVDA( + VideoDecodeAccelerator::Client* client, + const VideoDecodeAccelerator::Config& config, + const gpu::GpuDriverBugWorkarounds& workarounds, + const gpu::GpuPreferences& gpu_preferences, + MediaLog* media_log) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (gpu_preferences.disable_accelerated_video_decode) + return nullptr; + + // Array of Create..VDA() function pointers, potentially usable on current + // platform. This list is ordered by priority, from most to least preferred, + // if applicable. This list must be in the same order as the querying order + // in GetDecoderCapabilities() above. + using CreateVDAFp = std::unique_ptr ( + GpuVideoDecodeAcceleratorFactory::*)(const gpu::GpuDriverBugWorkarounds&, + const gpu::GpuPreferences&, + MediaLog* media_log) const; + const CreateVDAFp create_vda_fps[] = { +#if defined(OS_WIN) + &GpuVideoDecodeAcceleratorFactory::CreateDXVAVDA, +#endif + + // Usually only one of USE_VAAPI or USE_V4L2_CODEC is defined on ChromeOS, + // except for Chromeboxes with companion video acceleration chips, which have + // both. In those cases prefer the VA creation function. +#if BUILDFLAG(USE_VAAPI) + &GpuVideoDecodeAcceleratorFactory::CreateVaapiVDA, +#elif BUILDFLAG(USE_V4L2_CODEC) + &GpuVideoDecodeAcceleratorFactory::CreateV4L2VDA, + &GpuVideoDecodeAcceleratorFactory::CreateV4L2SliceVDA, +#endif + +#if defined(OS_MAC) + &GpuVideoDecodeAcceleratorFactory::CreateVTVDA, +#endif + }; + + std::unique_ptr vda; + + for (const auto& create_vda_function : create_vda_fps) { + vda = (this->*create_vda_function)(workarounds, gpu_preferences, media_log); + if (vda && vda->Initialize(config, client)) + return vda; + else + LOG(ERROR) << "Initialization of one or more VDAs failed."; + } + + return nullptr; +} + +#if defined(OS_WIN) +std::unique_ptr +GpuVideoDecodeAcceleratorFactory::CreateDXVAVDA( + const gpu::GpuDriverBugWorkarounds& workarounds, + const gpu::GpuPreferences& gpu_preferences, + MediaLog* media_log) const { + std::unique_ptr decoder; + DVLOG(0) << "Initializing DXVA HW decoder for windows."; + decoder.reset(new DXVAVideoDecodeAccelerator( + gl_client_.get_context, gl_client_.make_context_current, + gl_client_.bind_image, workarounds, gpu_preferences, media_log)); + return decoder; +} +#endif + +#if BUILDFLAG(USE_VAAPI) +std::unique_ptr +GpuVideoDecodeAcceleratorFactory::CreateVaapiVDA( + const gpu::GpuDriverBugWorkarounds& /*workarounds*/, + const gpu::GpuPreferences& /*gpu_preferences*/, + MediaLog* /*media_log*/) const { + std::unique_ptr decoder; + decoder.reset(new VaapiVideoDecodeAccelerator(gl_client_.make_context_current, + gl_client_.bind_image)); + return decoder; +} +#elif BUILDFLAG(USE_V4L2_CODEC) +std::unique_ptr +GpuVideoDecodeAcceleratorFactory::CreateV4L2VDA( + const gpu::GpuDriverBugWorkarounds& /*workarounds*/, + const gpu::GpuPreferences& /*gpu_preferences*/, + MediaLog* /*media_log*/) const { + std::unique_ptr decoder; + scoped_refptr device = V4L2Device::Create(); + if (device.get()) { + decoder.reset(new V4L2VideoDecodeAccelerator( + gl::GLSurfaceEGL::GetHardwareDisplay(), gl_client_.get_context, + gl_client_.make_context_current, device)); + } + return decoder; +} + +std::unique_ptr +GpuVideoDecodeAcceleratorFactory::CreateV4L2SliceVDA( + const gpu::GpuDriverBugWorkarounds& /*workarounds*/, + const gpu::GpuPreferences& /*gpu_preferences*/, + MediaLog* /*media_log*/) const { + std::unique_ptr decoder; + scoped_refptr device = V4L2Device::Create(); + if (device.get()) { + decoder.reset(new V4L2SliceVideoDecodeAccelerator( + device, gl::GLSurfaceEGL::GetHardwareDisplay(), gl_client_.bind_image, + gl_client_.make_context_current)); + } + return decoder; +} +#endif + +#if defined(OS_MAC) +std::unique_ptr +GpuVideoDecodeAcceleratorFactory::CreateVTVDA( + const gpu::GpuDriverBugWorkarounds& workarounds, + const gpu::GpuPreferences& gpu_preferences, + MediaLog* media_log) const { + LOG(WARNING) << "Initializing VAAPI VDA."; + std::unique_ptr decoder; + decoder.reset( + new VTVideoDecodeAccelerator(gl_client_, workarounds, media_log)); + return decoder; +} +#endif + +GpuVideoDecodeAcceleratorFactory::GpuVideoDecodeAcceleratorFactory( + const GpuVideoDecodeGLClient& gl_client) + : gl_client_(gl_client) {} +GpuVideoDecodeAcceleratorFactory::~GpuVideoDecodeAcceleratorFactory() = default; + +} // namespace media diff --git a/media/gpu/ipc/service/gpu_video_decode_accelerator.cc b/media/gpu/ipc/service/gpu_video_decode_accelerator.cc new file mode 100644 index 00000000..958d37cb --- /dev/null +++ b/media/gpu/ipc/service/gpu_video_decode_accelerator.cc @@ -0,0 +1,622 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/gpu/ipc/service/gpu_video_decode_accelerator.h" + +#include +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/waitable_event.h" +#include "base/task/bind_post_task.h" +#include "base/task/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "gpu/command_buffer/common/command_buffer.h" +#include "gpu/config/gpu_preferences.h" +#include "gpu/ipc/service/gpu_channel.h" +#include "gpu/ipc/service/gpu_channel_manager.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_message_utils.h" +#include "ipc/message_filter.h" +#include "media/base/limits.h" +#include "media/gpu/gpu_video_accelerator_util.h" +#include "media/gpu/gpu_video_decode_accelerator_factory.h" +#include "mojo/public/cpp/bindings/associated_receiver.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gl/gl_context.h" +#include "ui/gl/gl_image.h" + +namespace media { + +namespace { +static gl::GLContext* GetGLContext( + const base::WeakPtr& stub) { + if (!stub) { + DLOG(ERROR) << "Stub is gone; no GLContext."; + return nullptr; + } + + return stub->decoder_context()->GetGLContext(); +} + +static bool MakeDecoderContextCurrent( + const base::WeakPtr& stub) { + if (!stub) { + DLOG(ERROR) << "Stub is gone; won't MakeCurrent()."; + return false; + } + + if (!stub->decoder_context()->MakeCurrent()) { + DLOG(ERROR) << "Failed to MakeCurrent()"; + return false; + } + + return true; +} + +static bool BindImage(const base::WeakPtr& stub, + uint32_t client_texture_id, + uint32_t texture_target, + const scoped_refptr& image, + bool can_bind_to_sampler) { + if (!stub) { + DLOG(ERROR) << "Stub is gone; won't BindImage()."; + return false; + } + + gpu::DecoderContext* command_decoder = stub->decoder_context(); + command_decoder->BindImage(client_texture_id, texture_target, image.get(), + can_bind_to_sampler); + return true; +} + +static gpu::gles2::ContextGroup* GetContextGroup( + const base::WeakPtr& stub) { + if (!stub) { + DLOG(ERROR) << "Stub is gone; no DecoderContext."; + return nullptr; + } + + return stub->decoder_context()->GetContextGroup(); +} + +static std::unique_ptr CreateAbstractTexture( + const base::WeakPtr& stub, + GLenum target, + GLenum internal_format, + GLsizei width, + GLsizei height, + GLsizei depth, + GLint border, + GLenum format, + GLenum type) { + if (!stub) { + DLOG(ERROR) << "Stub is gone; no DecoderContext."; + return nullptr; + } + + return stub->decoder_context()->CreateAbstractTexture( + target, internal_format, width, height, depth, border, format, type); +} + +} // anonymous namespace + +// DebugAutoLock works like AutoLock but only acquires the lock when +// DCHECK is on. +#if DCHECK_IS_ON() +typedef base::AutoLock DebugAutoLock; +#else +class DebugAutoLock { + public: + explicit DebugAutoLock(base::Lock&) {} +}; +#endif + +// Receives incoming messages for the decoder. Operates exclusively on the IO +// thread, since sometimes we want to do decodes directly from there. +class GpuVideoDecodeAccelerator::MessageFilter + : public mojom::GpuAcceleratedVideoDecoder { + public: + MessageFilter(GpuVideoDecodeAccelerator* owner, + scoped_refptr owner_task_runner, + bool decode_on_io) + : owner_(owner), + owner_task_runner_(std::move(owner_task_runner)), + decode_on_io_(decode_on_io) {} + ~MessageFilter() override = default; + + // Called from the main thread. Posts to `io_task_runner` to do the binding + // and waits for completion before returning. This ensures the decoder's + // endpoint is established before the synchronous request to establish it is + // acknowledged to the client. + bool Bind(mojo::PendingAssociatedReceiver + receiver, + const scoped_refptr& io_task_runner) { + base::WaitableEvent bound_event; + if (!io_task_runner->PostTask( + FROM_HERE, base::BindOnce(&MessageFilter::BindOnIoThread, + base::Unretained(this), + std::move(receiver), &bound_event))) { + return false; + } + bound_event.Wait(); + return true; + } + + // Must be called on the IO thread. Posts back to the owner's task runner to + // destroy it. + void RequestShutdown() { + if (!owner_) + return; + + // Must be reset here on the IO thread before `this` is destroyed. + receiver_.reset(); + + GpuVideoDecodeAccelerator* owner = owner_; + owner_ = nullptr; + + // Invalidate any IO thread WeakPtrs which may be held by the + // VideoDecodeAccelerator, and post to delete our owner which will in turn + // delete us. Note that it is unsafe to access any members of `this` once + // the task below is posted. + owner->weak_factory_for_io_.InvalidateWeakPtrs(); + owner_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&GpuVideoDecodeAccelerator::DeleteSelfNow, + base::Unretained(owner))); + } + + // mojom::GpuAcceleratedVideoDecoder: + void Decode(BitstreamBuffer buffer) override; + void AssignPictureBuffers( + std::vector assignments) override; + void ReusePictureBuffer(int32_t picture_buffer_id) override; + void Flush(FlushCallback callback) override; + void Reset(ResetCallback callback) override; + void SetOverlayInfo(const OverlayInfo& overlay_info) override; + + private: + void BindOnIoThread(mojo::PendingAssociatedReceiver< + mojom::GpuAcceleratedVideoDecoder> receiver, + base::WaitableEvent* bound_event) { + receiver_.Bind(std::move(receiver)); + receiver_.set_disconnect_handler( + base::BindOnce(&MessageFilter::OnDisconnect, base::Unretained(this))); + bound_event->Signal(); + } + + void OnDisconnect() { + if (!owner_) + return; + + owner_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&GpuVideoDecodeAccelerator::OnDestroy, + base::Unretained(owner_))); + } + + GpuVideoDecodeAccelerator* owner_; + const scoped_refptr owner_task_runner_; + const bool decode_on_io_; + mojo::AssociatedReceiver receiver_{this}; +}; + +void GpuVideoDecodeAccelerator::MessageFilter::Decode(BitstreamBuffer buffer) { + if (!owner_) + return; + + if (decode_on_io_) { + owner_->OnDecode(std::move(buffer)); + } else { + owner_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&GpuVideoDecodeAccelerator::OnDecode, + base::Unretained(owner_), std::move(buffer))); + } +} + +void GpuVideoDecodeAccelerator::MessageFilter::AssignPictureBuffers( + std::vector assignments) { + if (!owner_) + return; + owner_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&GpuVideoDecodeAccelerator::OnAssignPictureBuffers, + base::Unretained(owner_), std::move(assignments))); +} + +void GpuVideoDecodeAccelerator::MessageFilter::ReusePictureBuffer( + int32_t picture_buffer_id) { + if (!owner_) + return; + owner_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&GpuVideoDecodeAccelerator::OnReusePictureBuffer, + base::Unretained(owner_), picture_buffer_id)); +} + +void GpuVideoDecodeAccelerator::MessageFilter::Flush(FlushCallback callback) { + if (!owner_) + return; + owner_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&GpuVideoDecodeAccelerator::OnFlush, + base::Unretained(owner_), std::move(callback))); +} + +void GpuVideoDecodeAccelerator::MessageFilter::Reset(ResetCallback callback) { + if (!owner_) + return; + owner_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&GpuVideoDecodeAccelerator::OnReset, + base::Unretained(owner_), std::move(callback))); +} + +void GpuVideoDecodeAccelerator::MessageFilter::SetOverlayInfo( + const OverlayInfo& overlay_info) { + if (!owner_) + return; + owner_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&GpuVideoDecodeAccelerator::OnSetOverlayInfo, + base::Unretained(owner_), overlay_info)); +} + +GpuVideoDecodeAccelerator::GpuVideoDecodeAccelerator( + gpu::CommandBufferStub* stub, + const scoped_refptr& io_task_runner, + const AndroidOverlayMojoFactoryCB& overlay_factory_cb) + : stub_(stub), + texture_target_(0), + pixel_format_(PIXEL_FORMAT_UNKNOWN), + textures_per_buffer_(0), + child_task_runner_(base::ThreadTaskRunnerHandle::Get()), + io_task_runner_(io_task_runner), + overlay_factory_cb_(overlay_factory_cb) { + DCHECK(stub_); + stub_->AddDestructionObserver(this); + gl_client_.get_context = + base::BindRepeating(&GetGLContext, stub_->AsWeakPtr()); + gl_client_.make_context_current = + base::BindRepeating(&MakeDecoderContextCurrent, stub_->AsWeakPtr()); + gl_client_.bind_image = base::BindRepeating(&BindImage, stub_->AsWeakPtr()); + gl_client_.get_context_group = + base::BindRepeating(&GetContextGroup, stub_->AsWeakPtr()); + gl_client_.create_abstract_texture = + base::BindRepeating(&CreateAbstractTexture, stub_->AsWeakPtr()); + gl_client_.is_passthrough = + stub_->decoder_context()->GetFeatureInfo()->is_passthrough_cmd_decoder(); + gl_client_.supports_arb_texture_rectangle = stub_->decoder_context() + ->GetFeatureInfo() + ->feature_flags() + .arb_texture_rectangle; +} + +GpuVideoDecodeAccelerator::~GpuVideoDecodeAccelerator() { + // This class can only be self-deleted from OnWillDestroyStub(), which means + // the VDA has already been destroyed in there. + DCHECK(!video_decode_accelerator_); +} + +void GpuVideoDecodeAccelerator::DeleteSelfNow() { + delete this; +} + +// static +gpu::VideoDecodeAcceleratorCapabilities +GpuVideoDecodeAccelerator::GetCapabilities( + const gpu::GpuPreferences& gpu_preferences, + const gpu::GpuDriverBugWorkarounds& workarounds) { + return GpuVideoDecodeAcceleratorFactory::GetDecoderCapabilities( + gpu_preferences, workarounds); +} + +void GpuVideoDecodeAccelerator::NotifyInitializationComplete(Status status) { + decoder_client_->OnInitializationComplete(status.is_ok()); +} + +void GpuVideoDecodeAccelerator::ProvidePictureBuffers( + uint32_t requested_num_of_buffers, + VideoPixelFormat format, + uint32_t textures_per_buffer, + const gfx::Size& dimensions, + uint32_t texture_target) { + if (dimensions.width() > limits::kMaxDimension || + dimensions.height() > limits::kMaxDimension || + dimensions.GetArea() > limits::kMaxCanvas) { + NotifyError(VideoDecodeAccelerator::PLATFORM_FAILURE); + return; + } + + texture_dimensions_ = dimensions; + textures_per_buffer_ = textures_per_buffer; + texture_target_ = texture_target; + pixel_format_ = format; + + decoder_client_->OnProvidePictureBuffers(requested_num_of_buffers, format, + textures_per_buffer, dimensions, + texture_target); +} + +void GpuVideoDecodeAccelerator::DismissPictureBuffer( + int32_t picture_buffer_id) { + // Notify client that picture buffer is now unused. + decoder_client_->OnDismissPictureBuffer(picture_buffer_id); + DebugAutoLock auto_lock(debug_uncleared_textures_lock_); + uncleared_textures_.erase(picture_buffer_id); +} + +void GpuVideoDecodeAccelerator::PictureReady(const Picture& picture) { + // VDA may call PictureReady on IO thread. SetTextureCleared should run on + // the child thread. VDA is responsible to call PictureReady on the child + // thread when a picture buffer is delivered the first time. + if (child_task_runner_->BelongsToCurrentThread()) { + SetTextureCleared(picture); + } else { + DCHECK(io_task_runner_->BelongsToCurrentThread()); + DebugAutoLock auto_lock(debug_uncleared_textures_lock_); + DCHECK_EQ(0u, uncleared_textures_.count(picture.picture_buffer_id())); + } + + auto params = mojom::PictureReadyParams::New(); + params->picture_buffer_id = picture.picture_buffer_id(); + params->bitstream_buffer_id = picture.bitstream_buffer_id(); + params->visible_rect = picture.visible_rect(); + params->color_space = picture.color_space(); + params->allow_overlay = picture.allow_overlay(); + params->read_lock_fences_enabled = picture.read_lock_fences_enabled(); + params->size_changed = picture.size_changed(); + params->surface_texture = picture.texture_owner(); + params->wants_promotion_hint = picture.wants_promotion_hint(); + decoder_client_->OnPictureReady(std::move(params)); +} + +void GpuVideoDecodeAccelerator::NotifyEndOfBitstreamBuffer( + int32_t bitstream_buffer_id) { + decoder_client_->OnBitstreamBufferProcessed(bitstream_buffer_id); +} + +void GpuVideoDecodeAccelerator::NotifyFlushDone() { + DCHECK(!pending_flushes_.empty()); + std::move(pending_flushes_.front()).Run(); + pending_flushes_.pop_front(); +} + +void GpuVideoDecodeAccelerator::NotifyResetDone() { + DCHECK(!pending_resets_.empty()); + std::move(pending_resets_.front()).Run(); + pending_resets_.pop_front(); +} + +void GpuVideoDecodeAccelerator::NotifyError( + VideoDecodeAccelerator::Error error) { + decoder_client_->OnError(error); +} + +void GpuVideoDecodeAccelerator::OnWillDestroyStub(bool have_context) { + // The stub is going away, so we have to stop and destroy VDA here, before + // returning, because the VDA may need the GL context to run and/or do its + // cleanup. We cannot destroy the VDA before the IO thread message filter is + // removed however, since we cannot service incoming messages with VDA gone. + // We cannot simply check for existence of VDA on IO thread though, because + // we don't want to synchronize the IO thread with the ChildThread. + // So we have to wait for the RemoveFilter callback here instead and remove + // the VDA after it arrives and before returning. + stub_->RemoveDestructionObserver(this); + if (filter_) { + io_task_runner_->PostTask(FROM_HERE, + base::BindOnce(&MessageFilter::RequestShutdown, + base::Unretained(filter_.get()))); + } + + video_decode_accelerator_.reset(); +} + +bool GpuVideoDecodeAccelerator::Initialize( + const VideoDecodeAccelerator::Config& config, + mojo::PendingAssociatedReceiver receiver, + mojo::PendingAssociatedRemote + client) { + DCHECK(!video_decode_accelerator_); + +#if !defined(OS_WIN) + // Ensure we will be able to get a GL context at all before initializing + // non-Windows VDAs. + if (!gl_client_.make_context_current.Run()) + return false; +#endif + + std::unique_ptr vda_factory = + GpuVideoDecodeAcceleratorFactory::Create(gl_client_); + if (!vda_factory) { + LOG(ERROR) << "Failed creating the VDA factory"; + return false; + } + LOG(WARNING) << "Created the VDA factory"; + + const gpu::GpuDriverBugWorkarounds& gpu_workarounds = + stub_->channel()->gpu_channel_manager()->gpu_driver_bug_workarounds(); + const gpu::GpuPreferences& gpu_preferences = + stub_->channel()->gpu_channel_manager()->gpu_preferences(); + + if (config.output_mode != + VideoDecodeAccelerator::Config::OutputMode::ALLOCATE) { + DLOG(ERROR) << "Only ALLOCATE mode is supported"; + return false; + } + + video_decode_accelerator_ = + vda_factory->CreateVDA(this, config, gpu_workarounds, gpu_preferences); + if (!video_decode_accelerator_) { + LOG(ERROR) << "HW video decode not available for profile " + << GetProfileName(config.profile) + << (config.is_encrypted() ? " with encryption" : ""); + return false; + } + LOG(WARNING) << "Created VDA"; + + decoder_client_.Bind(std::move(client), io_task_runner_); + + // Attempt to set up performing decoding tasks on IO thread, if supported by + // the VDA. + bool decode_on_io = + video_decode_accelerator_->TryToSetupDecodeOnSeparateThread( + weak_factory_for_io_.GetWeakPtr(), io_task_runner_); + + // Bind the receiver on the IO thread. We wait here for it to be bound + // before returning and signaling that the decoder has been created. + filter_ = + std::make_unique(this, stub_->task_runner(), decode_on_io); + return filter_->Bind(std::move(receiver), io_task_runner_); +} + +// Runs on IO thread if VDA::TryToSetupDecodeOnSeparateThread() succeeded, +// otherwise on the main thread. +void GpuVideoDecodeAccelerator::OnDecode(BitstreamBuffer bitstream_buffer) { + DCHECK(video_decode_accelerator_); + video_decode_accelerator_->Decode(std::move(bitstream_buffer)); +} + +void GpuVideoDecodeAccelerator::OnAssignPictureBuffers( + std::vector assignments) { + gpu::DecoderContext* decoder_context = stub_->decoder_context(); + gpu::gles2::TextureManager* texture_manager = + stub_->decoder_context()->GetContextGroup()->texture_manager(); + + std::vector buffers; + std::vector>> textures; + for (const auto& assignment : assignments) { + if (assignment->buffer_id < 0) { + DLOG(ERROR) << "Buffer id " << assignment->buffer_id << " out of range"; + NotifyError(VideoDecodeAccelerator::INVALID_ARGUMENT); + return; + } + std::vector> current_textures; + PictureBuffer::TextureIds buffer_texture_ids = assignment->texture_ids; + PictureBuffer::TextureIds service_ids; + if (buffer_texture_ids.size() != textures_per_buffer_) { + DLOG(ERROR) << "Requested " << textures_per_buffer_ + << " textures per picture buffer, got " + << buffer_texture_ids.size(); + NotifyError(VideoDecodeAccelerator::INVALID_ARGUMENT); + return; + } + for (size_t j = 0; j < textures_per_buffer_; j++) { + gpu::TextureBase* texture_base = + decoder_context->GetTextureBase(buffer_texture_ids[j]); + if (!texture_base) { + DLOG(ERROR) << "Failed to find texture id " << buffer_texture_ids[j]; + NotifyError(VideoDecodeAccelerator::INVALID_ARGUMENT); + return; + } + + if (texture_base->target() != texture_target_) { + DLOG(ERROR) << "Texture target mismatch for texture id " + << buffer_texture_ids[j]; + NotifyError(VideoDecodeAccelerator::INVALID_ARGUMENT); + return; + } + + if (texture_manager) { + gpu::gles2::TextureRef* texture_ref = + texture_manager->GetTexture(buffer_texture_ids[j]); + if (texture_ref) { + gpu::gles2::Texture* info = texture_ref->texture(); + if (texture_target_ == GL_TEXTURE_EXTERNAL_OES || + texture_target_ == GL_TEXTURE_RECTANGLE_ARB) { + // These textures have their dimensions defined by the underlying + // storage. + // Use |texture_dimensions_| for this size. + texture_manager->SetLevelInfo( + texture_ref, texture_target_, 0, GL_RGBA, + texture_dimensions_.width(), texture_dimensions_.height(), 1, 0, + GL_RGBA, GL_UNSIGNED_BYTE, gfx::Rect()); + } else { + // For other targets, texture dimensions should already be defined. + GLsizei width = 0, height = 0; + info->GetLevelSize(texture_target_, 0, &width, &height, nullptr); + if (width != texture_dimensions_.width() || + height != texture_dimensions_.height()) { + DLOG(ERROR) << "Size mismatch for texture id " + << buffer_texture_ids[j]; + NotifyError(VideoDecodeAccelerator::INVALID_ARGUMENT); + return; + } + + // TODO(dshwang): after moving to D3D11, remove this. + // https://crbug.com/438691 + GLenum format = + video_decode_accelerator_->GetSurfaceInternalFormat(); + if (format != GL_RGBA) { + DCHECK(format == GL_BGRA_EXT); + texture_manager->SetLevelInfo(texture_ref, texture_target_, 0, + format, width, height, 1, 0, format, + GL_UNSIGNED_BYTE, gfx::Rect()); + } + } + current_textures.push_back(texture_ref); + } + } + service_ids.push_back(texture_base->service_id()); + } + textures.push_back(current_textures); + buffers.emplace_back(assignment->buffer_id, texture_dimensions_, + buffer_texture_ids, service_ids, texture_target_, + pixel_format_); + } + { + DebugAutoLock auto_lock(debug_uncleared_textures_lock_); + for (uint32_t i = 0; i < assignments.size(); ++i) + uncleared_textures_[assignments[i]->buffer_id] = textures[i]; + } + video_decode_accelerator_->AssignPictureBuffers(buffers); +} + +void GpuVideoDecodeAccelerator::OnReusePictureBuffer( + int32_t picture_buffer_id) { + DCHECK(video_decode_accelerator_); + video_decode_accelerator_->ReusePictureBuffer(picture_buffer_id); +} + +void GpuVideoDecodeAccelerator::OnFlush(base::OnceClosure callback) { + DCHECK(video_decode_accelerator_); + pending_flushes_.push_back( + base::BindPostTask(io_task_runner_, std::move(callback))); + video_decode_accelerator_->Flush(); +} + +void GpuVideoDecodeAccelerator::OnReset(base::OnceClosure callback) { + DCHECK(video_decode_accelerator_); + pending_resets_.push_back( + base::BindPostTask(io_task_runner_, std::move(callback))); + video_decode_accelerator_->Reset(); +} + +void GpuVideoDecodeAccelerator::OnSetOverlayInfo( + const OverlayInfo& overlay_info) { + DCHECK(video_decode_accelerator_); + video_decode_accelerator_->SetOverlayInfo(overlay_info); +} + +void GpuVideoDecodeAccelerator::OnDestroy() { + DCHECK(video_decode_accelerator_); + OnWillDestroyStub(false); +} + +void GpuVideoDecodeAccelerator::SetTextureCleared(const Picture& picture) { + DCHECK(child_task_runner_->BelongsToCurrentThread()); + DebugAutoLock auto_lock(debug_uncleared_textures_lock_); + auto it = uncleared_textures_.find(picture.picture_buffer_id()); + if (it == uncleared_textures_.end()) + return; // the texture has been cleared + + for (auto texture_ref : it->second) { + GLenum target = texture_ref->texture()->target(); + gpu::gles2::TextureManager* texture_manager = + stub_->decoder_context()->GetContextGroup()->texture_manager(); + texture_manager->SetLevelCleared(texture_ref.get(), target, 0, true); + } + uncleared_textures_.erase(it); +} + +} // namespace media diff --git a/media/gpu/vaapi/vaapi_video_decode_accelerator.cc b/media/gpu/vaapi/vaapi_video_decode_accelerator.cc new file mode 100644 index 00000000..2a0d3005 --- /dev/null +++ b/media/gpu/vaapi/vaapi_video_decode_accelerator.cc @@ -0,0 +1,1321 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/gpu/vaapi/vaapi_video_decode_accelerator.h" + +#include +#include + +#include + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/containers/contains.h" +#include "base/containers/cxx20_erase.h" +#include "base/cpu.h" +#include "base/files/scoped_file.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/metrics/histogram_macros.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/trace_event/memory_dump_manager.h" +#include "base/trace_event/process_memory_dump.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "gpu/ipc/service/gpu_channel.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/format_utils.h" +#include "media/base/media_log.h" +#include "media/base/unaligned_shared_memory.h" +#include "media/base/video_util.h" +#include "media/gpu/accelerated_video_decoder.h" +#include "media/gpu/h264_decoder.h" +#include "media/gpu/macros.h" +#include "media/gpu/vaapi/h264_vaapi_video_decoder_delegate.h" +#include "media/gpu/vaapi/vaapi_common.h" +#include "media/gpu/vaapi/vaapi_picture.h" +#include "media/gpu/vaapi/vaapi_utils.h" +#include "media/gpu/vaapi/vp8_vaapi_video_decoder_delegate.h" +#include "media/gpu/vaapi/vp9_vaapi_video_decoder_delegate.h" +#include "media/gpu/vp8_decoder.h" +#include "media/gpu/vp9_decoder.h" +#include "media/video/picture.h" +#include "ui/base/ui_base_features.h" +#include "ui/gl/gl_image.h" + +namespace media { + +namespace { + +// Returns the preferred VA_RT_FORMAT for the given |profile|. +unsigned int GetVaFormatForVideoCodecProfile(VideoCodecProfile profile) { + if (profile == VP9PROFILE_PROFILE2 || profile == VP9PROFILE_PROFILE3) + return VA_RT_FORMAT_YUV420_10BPP; + return VA_RT_FORMAT_YUV420; +} + +// Returns true if the CPU is an Intel Gemini Lake or later (including Kaby +// Lake) Cpu platform id's are referenced from the following file in kernel +// source arch/x86/include/asm/intel-family.h +bool IsGeminiLakeOrLater() { + constexpr int kPentiumAndLaterFamily = 0x06; + constexpr int kGeminiLakeModelId = 0x7A; + static base::CPU cpuid; + static bool is_geminilake_or_later = + cpuid.family() == kPentiumAndLaterFamily && + cpuid.model() >= kGeminiLakeModelId; + return is_geminilake_or_later; +} + +} // namespace + +#define RETURN_AND_NOTIFY_ON_FAILURE(result, log, error_code, ret) \ + do { \ + if (!(result)) { \ + LOG(ERROR) << log; \ + NotifyError(error_code); \ + return ret; \ + } \ + } while (0) + +#define RETURN_AND_NOTIFY_ON_STATUS(status, ret) \ + do { \ + if (!status.is_ok()) { \ + NotifyStatus(status); \ + return ret; \ + } \ + } while (0) + +class VaapiVideoDecodeAccelerator::InputBuffer { + public: + InputBuffer() : buffer_(nullptr) {} + InputBuffer(int32_t id, + scoped_refptr buffer, + base::OnceCallback release_cb) + : id_(id), + buffer_(std::move(buffer)), + release_cb_(std::move(release_cb)) {} + + InputBuffer(const InputBuffer&) = delete; + InputBuffer& operator=(const InputBuffer&) = delete; + + ~InputBuffer() { + DVLOGF(4) << "id = " << id_; + if (release_cb_) + std::move(release_cb_).Run(id_); + } + + // Indicates this is a dummy buffer for flush request. + bool IsFlushRequest() const { return !buffer_; } + int32_t id() const { return id_; } + const scoped_refptr& buffer() const { return buffer_; } + + private: + const int32_t id_ = -1; + const scoped_refptr buffer_; + base::OnceCallback release_cb_; +}; + +void VaapiVideoDecodeAccelerator::NotifyStatus(VaapiStatus status) { + DCHECK(!status.is_ok()); + // Send a platform notification error + NotifyError(PLATFORM_FAILURE); + + // TODO(crbug.com/1103510) there is no MediaLog here, we should change that. + std::string output_str; + base::JSONWriter::Write(MediaSerialize(status), &output_str); + DLOG(ERROR) << output_str; +} + +void VaapiVideoDecodeAccelerator::NotifyError(Error error) { + if (!task_runner_->BelongsToCurrentThread()) { + DCHECK(decoder_thread_task_runner_->BelongsToCurrentThread()); + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&VaapiVideoDecodeAccelerator::NotifyError, + weak_this_, error)); + return; + } + + VLOGF(1) << "Notifying of error " << error; + if (client_) { + client_->NotifyError(error); + client_ptr_factory_.reset(); + } +} + +VaapiVideoDecodeAccelerator::VaapiVideoDecodeAccelerator( + const MakeGLContextCurrentCallback& make_context_current_cb, + const BindGLImageCallback& bind_image_cb) + : state_(kUninitialized), + input_ready_(&lock_), + buffer_allocation_mode_(BufferAllocationMode::kNormal), + surfaces_available_(&lock_), + va_surface_format_(VA_INVALID_ID), + task_runner_(base::ThreadTaskRunnerHandle::Get()), + decoder_thread_("VaapiDecoderThread"), + finish_flush_pending_(false), + awaiting_va_surfaces_recycle_(false), + requested_num_pics_(0), + requested_num_reference_frames_(0), + previously_requested_num_reference_frames_(0), + profile_(VIDEO_CODEC_PROFILE_UNKNOWN), + make_context_current_cb_(make_context_current_cb), + bind_image_cb_(bind_image_cb), + weak_this_factory_(this) { + weak_this_ = weak_this_factory_.GetWeakPtr(); + va_surface_recycle_cb_ = BindToCurrentLoop(base::BindRepeating( + &VaapiVideoDecodeAccelerator::RecycleVASurface, weak_this_)); + base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( + this, "media::VaapiVideoDecodeAccelerator", + base::ThreadTaskRunnerHandle::Get()); +} + +VaapiVideoDecodeAccelerator::~VaapiVideoDecodeAccelerator() { + DCHECK(task_runner_->BelongsToCurrentThread()); + base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( + this); +} + +bool VaapiVideoDecodeAccelerator::Initialize(const Config& config, + Client* client) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + vaapi_picture_factory_ = std::make_unique(); + + if (config.is_encrypted()) { + NOTREACHED() << "Encrypted streams are not supported for this VDA"; + return false; + } + + client_ptr_factory_.reset(new base::WeakPtrFactory(client)); + client_ = client_ptr_factory_->GetWeakPtr(); + + VideoCodecProfile profile = config.profile; + + base::AutoLock auto_lock(lock_); + DCHECK_EQ(state_, kUninitialized); + VLOGF(2) << "Initializing VAVDA, profile: " << GetProfileName(profile); + + vaapi_wrapper_ = VaapiWrapper::CreateForVideoCodec( + VaapiWrapper::kDecode, profile, EncryptionScheme::kUnencrypted, + base::BindRepeating(&ReportVaapiErrorToUMA, + "Media.VaapiVideoDecodeAccelerator.VAAPIError")); + + UMA_HISTOGRAM_BOOLEAN("Media.VAVDA.VaapiWrapperCreationSuccess", + vaapi_wrapper_.get()); + if (!vaapi_wrapper_.get()) { + VLOGF(1) << "Failed initializing VAAPI for profile " + << GetProfileName(profile); + return false; + } + + if (profile >= H264PROFILE_MIN && profile <= H264PROFILE_MAX) { + auto accelerator = + std::make_unique(this, vaapi_wrapper_); + decoder_delegate_ = accelerator.get(); + decoder_.reset(new H264Decoder(std::move(accelerator), profile, + config.container_color_space)); + } else if (profile >= VP8PROFILE_MIN && profile <= VP8PROFILE_MAX) { + auto accelerator = + std::make_unique(this, vaapi_wrapper_); + decoder_delegate_ = accelerator.get(); + decoder_.reset(new VP8Decoder(std::move(accelerator))); + } else if (profile >= VP9PROFILE_MIN && profile <= VP9PROFILE_MAX) { + auto accelerator = + std::make_unique(this, vaapi_wrapper_); + decoder_delegate_ = accelerator.get(); + decoder_.reset(new VP9Decoder(std::move(accelerator), profile, + config.container_color_space)); + } else { + VLOGF(1) << "Unsupported profile " << GetProfileName(profile); + return false; + } + + CHECK(decoder_thread_.Start()); + decoder_thread_task_runner_ = decoder_thread_.task_runner(); + + state_ = kIdle; + profile_ = profile; + output_mode_ = config.output_mode; + buffer_allocation_mode_ = DecideBufferAllocationMode(); + previously_requested_num_reference_frames_ = 0; + return true; +} + +void VaapiVideoDecodeAccelerator::OutputPicture( + scoped_refptr va_surface, + int32_t input_id, + gfx::Rect visible_rect, + const VideoColorSpace& picture_color_space) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + const VASurfaceID va_surface_id = va_surface->id(); + + VaapiPicture* picture = nullptr; + { + base::AutoLock auto_lock(lock_); + int32_t picture_buffer_id = available_picture_buffers_.front(); + if (buffer_allocation_mode_ == BufferAllocationMode::kNone) { + // Find the |pictures_| entry matching |va_surface_id|. + for (const auto& id_and_picture : pictures_) { + if (id_and_picture.second->va_surface_id() == va_surface_id) { + picture_buffer_id = id_and_picture.first; + break; + } + } + } + picture = pictures_[picture_buffer_id].get(); + DCHECK(base::Contains(available_picture_buffers_, picture_buffer_id)); + base::Erase(available_picture_buffers_, picture_buffer_id); + } + + DCHECK(picture) << " could not find " << va_surface_id << " available"; + const int32_t output_id = picture->picture_buffer_id(); + + DVLOGF(4) << "Outputting VASurface " << va_surface->id() + << " into pixmap bound to picture buffer id " << output_id; + + if (buffer_allocation_mode_ != BufferAllocationMode::kNone) { + TRACE_EVENT2("media,gpu", "VAVDA::DownloadFromSurface", "input_id", + input_id, "output_id", output_id); + RETURN_AND_NOTIFY_ON_FAILURE(picture->DownloadFromSurface(va_surface), + "Failed putting surface into pixmap", + PLATFORM_FAILURE, ); + } + + { + base::AutoLock auto_lock(lock_); + TRACE_COUNTER_ID2("media,gpu", "Vaapi frames at client", this, "used", + pictures_.size() - available_picture_buffers_.size(), + "available", available_picture_buffers_.size()); + } + + DVLOGF(4) << "Notifying output picture id " << output_id << " for input " + << input_id + << " is ready. visible rect: " << visible_rect.ToString(); + if (!client_) + return; + + Picture client_picture(output_id, input_id, visible_rect, + picture_color_space.ToGfxColorSpace(), + picture->AllowOverlay()); + client_picture.set_read_lock_fences_enabled(true); + // Notify the |client_| a picture is ready to be consumed. + client_->PictureReady(client_picture); +} + +void VaapiVideoDecodeAccelerator::TryOutputPicture() { + DCHECK(task_runner_->BelongsToCurrentThread()); + + // Handle Destroy() arriving while pictures are queued for output. + if (!client_) + return; + + { + base::AutoLock auto_lock(lock_); + if (pending_output_cbs_.empty() || available_picture_buffers_.empty()) + return; + } + + auto output_cb = std::move(pending_output_cbs_.front()); + pending_output_cbs_.pop(); + std::move(output_cb).Run(); + + if (finish_flush_pending_ && pending_output_cbs_.empty()) + FinishFlush(); +} + +void VaapiVideoDecodeAccelerator::QueueInputBuffer( + scoped_refptr buffer, + int32_t bitstream_id) { + DVLOGF(4) << "Queueing new input buffer id: " << bitstream_id + << " size: " << (buffer->end_of_stream() ? 0 : buffer->data_size()); + DCHECK(task_runner_->BelongsToCurrentThread()); + TRACE_EVENT1("media,gpu", "QueueInputBuffer", "input_id", bitstream_id); + + base::AutoLock auto_lock(lock_); + if (buffer->end_of_stream()) { + auto flush_buffer = std::make_unique(); + DCHECK(flush_buffer->IsFlushRequest()); + input_buffers_.push(std::move(flush_buffer)); + } else { + auto input_buffer = std::make_unique( + bitstream_id, std::move(buffer), + BindToCurrentLoop( + base::BindOnce(&Client::NotifyEndOfBitstreamBuffer, client_))); + input_buffers_.push(std::move(input_buffer)); + } + + TRACE_COUNTER1("media,gpu", "Vaapi input buffers", input_buffers_.size()); + input_ready_.Signal(); + + switch (state_) { + case kIdle: + state_ = kDecoding; + decoder_thread_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&VaapiVideoDecodeAccelerator::DecodeTask, + base::Unretained(this))); + break; + + case kDecoding: + // Decoder already running. + break; + + case kResetting: + // When resetting, allow accumulating bitstream buffers, so that + // the client can queue after-seek-buffers while we are finishing with + // the before-seek one. + break; + + default: + LOG(ERROR) << "Decode/Flush request from client in invalid state: " + << state_; + NotifyError(PLATFORM_FAILURE); + break; + } +} + +bool VaapiVideoDecodeAccelerator::GetCurrInputBuffer_Locked() { + DCHECK(decoder_thread_task_runner_->BelongsToCurrentThread()); + lock_.AssertAcquired(); + + if (curr_input_buffer_.get()) + return true; + + // Will only wait if it is expected that in current state new buffers will + // be queued from the client via Decode(). The state can change during wait. + while (input_buffers_.empty() && (state_ == kDecoding || state_ == kIdle)) + input_ready_.Wait(); + + // We could have got woken up in a different state or never got to sleep + // due to current state. + if (state_ != kDecoding && state_ != kIdle) + return false; + + DCHECK(!input_buffers_.empty()); + curr_input_buffer_ = std::move(input_buffers_.front()); + input_buffers_.pop(); + TRACE_COUNTER1("media,gpu", "Vaapi input buffers", input_buffers_.size()); + + if (curr_input_buffer_->IsFlushRequest()) { + DVLOGF(4) << "New flush buffer"; + return true; + } + + DVLOGF(4) << "New |curr_input_buffer_|, id: " << curr_input_buffer_->id() + << " size: " << curr_input_buffer_->buffer()->data_size() << "B"; + decoder_->SetStream(curr_input_buffer_->id(), *curr_input_buffer_->buffer()); + return true; +} + +void VaapiVideoDecodeAccelerator::ReturnCurrInputBuffer_Locked() { + DCHECK(decoder_thread_task_runner_->BelongsToCurrentThread()); + lock_.AssertAcquired(); + DCHECK(curr_input_buffer_.get()); + curr_input_buffer_.reset(); +} + +// TODO(posciak): refactor the whole class to remove sleeping in wait for +// surfaces, and reschedule DecodeTask instead. +bool VaapiVideoDecodeAccelerator::WaitForSurfaces_Locked() { + DCHECK(decoder_thread_task_runner_->BelongsToCurrentThread()); + lock_.AssertAcquired(); + + while (available_va_surfaces_.empty() && + (state_ == kDecoding || state_ == kIdle)) { + surfaces_available_.Wait(); + } + + return state_ == kDecoding || state_ == kIdle; +} + +void VaapiVideoDecodeAccelerator::DecodeTask() { + DCHECK(decoder_thread_task_runner_->BelongsToCurrentThread()); + base::AutoLock auto_lock(lock_); + + if (state_ != kDecoding) + return; + DVLOGF(4) << "Decode task"; + + // Try to decode what stream data is (still) in the decoder until we run out + // of it. + while (GetCurrInputBuffer_Locked()) { + DCHECK(curr_input_buffer_.get()); + + if (curr_input_buffer_->IsFlushRequest()) { + FlushTask(); + break; + } + + AcceleratedVideoDecoder::DecodeResult res; + { + // We are OK releasing the lock here, as decoder never calls our methods + // directly and we will reacquire the lock before looking at state again. + // This is the main decode function of the decoder and while keeping + // the lock for its duration would be fine, it would defeat the purpose + // of having a separate decoder thread. + base::AutoUnlock auto_unlock(lock_); + TRACE_EVENT0("media,gpu", "VAVDA::Decode"); + res = decoder_->Decode(); + } + + switch (res) { + case AcceleratedVideoDecoder::kConfigChange: { + const uint8_t bit_depth = decoder_->GetBitDepth(); + RETURN_AND_NOTIFY_ON_FAILURE( + bit_depth == 8u, + "Unsupported bit depth: " << base::strict_cast(bit_depth), + PLATFORM_FAILURE, ); + // The visible rect should be a subset of the picture size. Otherwise, + // the encoded stream is bad. + const gfx::Size pic_size = decoder_->GetPicSize(); + const gfx::Rect visible_rect = decoder_->GetVisibleRect(); + RETURN_AND_NOTIFY_ON_FAILURE( + gfx::Rect(pic_size).Contains(visible_rect), + "The visible rectangle is not contained by the picture size", + UNREADABLE_INPUT, ); + VLOGF(2) << "Decoder requesting a new set of surfaces"; + size_t required_num_of_pictures = decoder_->GetRequiredNumOfPictures(); + if (buffer_allocation_mode_ == BufferAllocationMode::kNone && + profile_ >= H264PROFILE_MIN && profile_ <= H264PROFILE_MAX) { + // For H.264, the decoder might request too few pictures. In + // BufferAllocationMode::kNone, this can cause us to do a lot of busy + // work waiting for picture buffers to come back from the client (see + // crbug.com/910986#c32). This is a workaround to increase the + // likelihood that we don't have to wait on buffers to come back from + // the client. |kNumOfPics| is picked to mirror the value returned by + // VP9Decoder::GetRequiredNumOfPictures(). + constexpr size_t kMinNumOfPics = 13u; + required_num_of_pictures = + std::max(kMinNumOfPics, required_num_of_pictures); + } + + // Notify |decoder_delegate_| of an imminent VAContextID destruction, so + // it can destroy any internal structures making use of it. + decoder_delegate_->OnVAContextDestructionSoon(); + + task_runner_->PostTask( + FROM_HERE, + base::BindOnce( + &VaapiVideoDecodeAccelerator::InitiateSurfaceSetChange, + weak_this_, required_num_of_pictures, pic_size, + decoder_->GetNumReferenceFrames(), visible_rect)); + // We'll get rescheduled once ProvidePictureBuffers() finishes. + return; + } + case AcceleratedVideoDecoder::kRanOutOfStreamData: + ReturnCurrInputBuffer_Locked(); + break; + + case AcceleratedVideoDecoder::kRanOutOfSurfaces: + // No more output buffers in the decoder, try getting more or go to + // sleep waiting for them. + if (!WaitForSurfaces_Locked()) + return; + + break; + + case AcceleratedVideoDecoder::kNeedContextUpdate: + // This should not happen as we return false from + // IsFrameContextRequired(). + NOTREACHED() << "Context updates not supported"; + return; + + case AcceleratedVideoDecoder::kDecodeError: + RETURN_AND_NOTIFY_ON_FAILURE(false, "Error decoding stream", + PLATFORM_FAILURE, ); + return; + + case AcceleratedVideoDecoder::kTryAgain: + NOTREACHED() << "Should not reach here unless this class accepts " + "encrypted streams."; + RETURN_AND_NOTIFY_ON_FAILURE(false, "Error decoding stream", + PLATFORM_FAILURE, ); + return; + } + } +} + +void VaapiVideoDecodeAccelerator::InitiateSurfaceSetChange( + size_t num_pics, + gfx::Size size, + size_t num_reference_frames, + const gfx::Rect& visible_rect) { + DCHECK(task_runner_->BelongsToCurrentThread()); + DCHECK(!awaiting_va_surfaces_recycle_); + DCHECK_GT(num_pics, num_reference_frames); + + // At this point decoder has stopped running and has already posted onto our + // loop any remaining output request callbacks, which executed before we got + // here. Some of them might have been pended though, because we might not have + // had enough PictureBuffers to output surfaces to. Initiate a wait cycle, + // which will wait for client to return enough PictureBuffers to us, so that + // we can finish all pending output callbacks, releasing associated surfaces. + awaiting_va_surfaces_recycle_ = true; + + requested_pic_size_ = size; + requested_visible_rect_ = visible_rect; + if (buffer_allocation_mode_ == BufferAllocationMode::kSuperReduced) { + // Add one to the reference frames for the one being currently egressed. + requested_num_reference_frames_ = num_reference_frames + 1; + requested_num_pics_ = num_pics - num_reference_frames; + } else if (buffer_allocation_mode_ == BufferAllocationMode::kReduced) { + // Add one to the reference frames for the one being currently egressed, + // and an extra allocation for both |client_| and |decoder_|. + requested_num_reference_frames_ = num_reference_frames + 2; + requested_num_pics_ = num_pics - num_reference_frames + 1; + } else { + requested_num_reference_frames_ = 0; + requested_num_pics_ = num_pics + num_extra_pics_; + } + + VLOGF(2) << " |requested_num_pics_| = " << requested_num_pics_ + << "; |requested_num_reference_frames_| = " + << requested_num_reference_frames_; + + TryFinishSurfaceSetChange(); +} + +void VaapiVideoDecodeAccelerator::TryFinishSurfaceSetChange() { + DCHECK(task_runner_->BelongsToCurrentThread()); + + if (!awaiting_va_surfaces_recycle_) + return; + + base::AutoLock auto_lock(lock_); + const size_t expected_max_available_va_surfaces = + IsBufferAllocationModeReducedOrSuperReduced() + ? previously_requested_num_reference_frames_ + : pictures_.size(); + if (!pending_output_cbs_.empty() || + expected_max_available_va_surfaces != available_va_surfaces_.size()) { + // If we're here the stream resolution has changed; we need to wait until: + // - all |pending_output_cbs_| have been executed + // - all VASurfaces are back to |available_va_surfaces_|; we can't use + // |requested_num_reference_frames_| for comparison, since it might have + // changed in the previous call to InitiateSurfaceSetChange(), so we use + // |previously_requested_num_reference_frames_| instead. + DVLOGF(2) << "Awaiting pending output/surface release callbacks to finish"; + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&VaapiVideoDecodeAccelerator::TryFinishSurfaceSetChange, + weak_this_)); + return; + } + + previously_requested_num_reference_frames_ = requested_num_reference_frames_; + + // All surfaces released, destroy them and dismiss all PictureBuffers. + awaiting_va_surfaces_recycle_ = false; + + const VideoCodecProfile new_profile = decoder_->GetProfile(); + if (profile_ != new_profile) { + profile_ = new_profile; + auto new_vaapi_wrapper = VaapiWrapper::CreateForVideoCodec( + VaapiWrapper::kDecode, profile_, EncryptionScheme::kUnencrypted, + base::BindRepeating(&ReportVaapiErrorToUMA, + "Media.VaapiVideoDecodeAccelerator.VAAPIError")); + RETURN_AND_NOTIFY_ON_FAILURE(new_vaapi_wrapper.get(), + "Failed creating VaapiWrapper", + INVALID_ARGUMENT, ); + decoder_delegate_->set_vaapi_wrapper(new_vaapi_wrapper.get()); + vaapi_wrapper_ = std::move(new_vaapi_wrapper); + } else { + vaapi_wrapper_->DestroyContext(); + } + + available_va_surfaces_.clear(); + + for (auto iter = pictures_.begin(); iter != pictures_.end(); ++iter) { + VLOGF(2) << "Dismissing picture id: " << iter->first; + if (client_) + client_->DismissPictureBuffer(iter->first); + } + pictures_.clear(); + + // And ask for a new set as requested. + VLOGF(2) << "Requesting " << requested_num_pics_ + << " pictures of size: " << requested_pic_size_.ToString() + << " and visible rectangle = " << requested_visible_rect_.ToString(); + + const absl::optional format = + GfxBufferFormatToVideoPixelFormat( + vaapi_picture_factory_->GetBufferFormat()); + CHECK(format); + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&Client::ProvidePictureBuffersWithVisibleRect, + client_, requested_num_pics_, *format, 1, + requested_pic_size_, requested_visible_rect_, + vaapi_picture_factory_->GetGLTextureTarget())); + // |client_| may respond via AssignPictureBuffers(). +} + +void VaapiVideoDecodeAccelerator::Decode(BitstreamBuffer bitstream_buffer) { + Decode(bitstream_buffer.ToDecoderBuffer(), bitstream_buffer.id()); +} + +void VaapiVideoDecodeAccelerator::Decode(scoped_refptr buffer, + int32_t bitstream_id) { + DCHECK(task_runner_->BelongsToCurrentThread()); + TRACE_EVENT1("media,gpu", "VAVDA::Decode", "Buffer id", bitstream_id); + + if (bitstream_id < 0) { + LOG(ERROR) << "Invalid bitstream_buffer, id: " << bitstream_id; + NotifyError(INVALID_ARGUMENT); + return; + } + + if (!buffer) { + if (client_) + client_->NotifyEndOfBitstreamBuffer(bitstream_id); + return; + } + + QueueInputBuffer(std::move(buffer), bitstream_id); +} + +void VaapiVideoDecodeAccelerator::AssignPictureBuffers( + const std::vector& buffers) { + DCHECK(task_runner_->BelongsToCurrentThread()); + base::AutoLock auto_lock(lock_); + DCHECK(pictures_.empty()); + + available_picture_buffers_.clear(); + + RETURN_AND_NOTIFY_ON_FAILURE( + buffers.size() >= requested_num_pics_, + "Got an invalid number of picture buffers. (Got " << buffers.size() + << ", requested " << requested_num_pics_ << ")", INVALID_ARGUMENT, ); + // requested_pic_size_ can be adjusted by VDA client. We should update + // |requested_pic_size_| by buffers[0].size(). But AMD driver doesn't decode + // frames correctly if the surface stride is different from the width of a + // coded size. + // TODO(b/139460315): Save buffers[0].size() as |adjusted_size_| once the + // AMD driver issue is resolved. + + va_surface_format_ = GetVaFormatForVideoCodecProfile(profile_); + std::vector va_surface_ids; + scoped_refptr vaapi_wrapper_for_picture = vaapi_wrapper_; + + const bool requires_vpp = + vaapi_picture_factory_->NeedsProcessingPipelineForDownloading(); + // If we aren't in BufferAllocationMode::kNone mode and the VaapiPicture + // implementation we get from |vaapi_picture_factory_| requires the video + // processing pipeline for downloading the decoded frame from the internal + // surface, we need to create a |vpp_vaapi_wrapper_|. + if (requires_vpp && buffer_allocation_mode_ != BufferAllocationMode::kNone && + buffer_allocation_mode_ != BufferAllocationMode::kWrapVdpau && + IsVppProfileSupported()) { + if (!vpp_vaapi_wrapper_) { + vpp_vaapi_wrapper_ = VaapiWrapper::Create( + VaapiWrapper::kVideoProcess, VAProfileNone, + EncryptionScheme::kUnencrypted, + base::BindRepeating( + &ReportVaapiErrorToUMA, + "Media.VaapiVideoDecodeAccelerator.Vpp.VAAPIError")); + RETURN_AND_NOTIFY_ON_FAILURE(vpp_vaapi_wrapper_, + "Failed to initialize VppVaapiWrapper", + PLATFORM_FAILURE, ); + // Size is irrelevant for a VPP context. + RETURN_AND_NOTIFY_ON_FAILURE( + vpp_vaapi_wrapper_->CreateContext(gfx::Size()), + "Failed to create Context", PLATFORM_FAILURE, ); + } + vaapi_wrapper_for_picture = vpp_vaapi_wrapper_; + } + + for (size_t i = 0; i < buffers.size(); ++i) { + // TODO(b/139460315): Create with buffers[i] once the AMD driver issue is + // resolved. + PictureBuffer buffer = buffers[i]; + buffer.set_size(requested_pic_size_); + + // Note that the |size_to_bind| is not relevant in IMPORT mode. + const gfx::Size size_to_bind = + (output_mode_ == Config::OutputMode::ALLOCATE) + ? GetRectSizeFromOrigin(requested_visible_rect_) + : gfx::Size(); + + std::unique_ptr picture = vaapi_picture_factory_->Create( + vaapi_wrapper_for_picture, make_context_current_cb_, bind_image_cb_, + buffer, size_to_bind); + RETURN_AND_NOTIFY_ON_FAILURE(picture, "Failed creating a VaapiPicture", + PLATFORM_FAILURE, ); + + if (output_mode_ == Config::OutputMode::ALLOCATE) { + RETURN_AND_NOTIFY_ON_STATUS( + picture->Allocate(vaapi_picture_factory_->GetBufferFormat()), ); + + available_picture_buffers_.push_back(buffers[i].id()); + VASurfaceID va_surface_id = picture->va_surface_id(); + if (va_surface_id != VA_INVALID_ID) + va_surface_ids.push_back(va_surface_id); + } + + DCHECK(!base::Contains(pictures_, buffers[i].id())); + pictures_[buffers[i].id()] = std::move(picture); + + surfaces_available_.Signal(); + } + + base::RepeatingCallback va_surface_release_cb; + + // If we aren't in BufferAllocationMode::kNone, we use |va_surface_ids| for + // decode, otherwise ask |vaapi_wrapper_| to allocate them for us. + if (buffer_allocation_mode_ == BufferAllocationMode::kNone) { + DCHECK(!va_surface_ids.empty()); + RETURN_AND_NOTIFY_ON_FAILURE( + vaapi_wrapper_->CreateContext(requested_pic_size_), + "Failed creating VA Context", PLATFORM_FAILURE, ); + DCHECK_EQ(va_surface_ids.size(), buffers.size()); + + va_surface_release_cb = base::DoNothing(); + } else { + const size_t requested_num_surfaces = + IsBufferAllocationModeReducedOrSuperReduced() + ? requested_num_reference_frames_ + : pictures_.size(); + CHECK_NE(requested_num_surfaces, 0u); + va_surface_ids.clear(); + + RETURN_AND_NOTIFY_ON_FAILURE( + vaapi_wrapper_->CreateContextAndSurfaces( + va_surface_format_, requested_pic_size_, + {VaapiWrapper::SurfaceUsageHint::kVideoDecoder}, + requested_num_surfaces, &va_surface_ids), + "Failed creating VA Surfaces", PLATFORM_FAILURE, ); + + va_surface_release_cb = + base::BindRepeating(&VaapiWrapper::DestroySurface, vaapi_wrapper_); + } + + for (const VASurfaceID va_surface_id : va_surface_ids) { + available_va_surfaces_.emplace_back(std::make_unique( + va_surface_id, va_surface_release_cb)); + } + + // Resume DecodeTask if it is still in decoding state. + if (state_ == kDecoding) { + decoder_thread_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&VaapiVideoDecodeAccelerator::DecodeTask, + base::Unretained(this))); + } +} + +#if defined(USE_OZONE) +void VaapiVideoDecodeAccelerator::ImportBufferForPicture( + int32_t picture_buffer_id, + VideoPixelFormat pixel_format, + gfx::GpuMemoryBufferHandle gpu_memory_buffer_handle) { + VLOGF(2) << "Importing picture id: " << picture_buffer_id; + DCHECK(task_runner_->BelongsToCurrentThread()); + + if (output_mode_ != Config::OutputMode::IMPORT) { + LOG(ERROR) << "Cannot import in non-import mode"; + NotifyError(INVALID_ARGUMENT); + return; + } + + { + base::AutoLock auto_lock(lock_); + if (!pictures_.count(picture_buffer_id)) { + // It's possible that we've already posted a DismissPictureBuffer for this + // picture, but it has not yet executed when this ImportBufferForPicture + // was posted to us by the client. In that case just ignore this (we've + // already dismissed it and accounted for that). + DVLOGF(3) << "got picture id=" << picture_buffer_id + << " not in use (anymore?)."; + return; + } + + auto buffer_format = VideoPixelFormatToGfxBufferFormat(pixel_format); + if (!buffer_format) { + LOG(ERROR) << "Unsupported format: " << pixel_format; + NotifyError(INVALID_ARGUMENT); + return; + } + + VaapiPicture* picture = pictures_[picture_buffer_id].get(); + if (!picture->ImportGpuMemoryBufferHandle( + *buffer_format, std::move(gpu_memory_buffer_handle))) { + // ImportGpuMemoryBufferHandle will close the handles even on failure, so + // we don't need to do this ourselves. + LOG(ERROR) << "Failed to import GpuMemoryBufferHandle"; + NotifyError(PLATFORM_FAILURE); + return; + } + } + + ReusePictureBuffer(picture_buffer_id); +} +#endif + +void VaapiVideoDecodeAccelerator::ReusePictureBuffer( + int32_t picture_buffer_id) { + DVLOGF(4) << "picture id=" << picture_buffer_id; + DCHECK(task_runner_->BelongsToCurrentThread()); + TRACE_EVENT1("media,gpu", "VAVDA::ReusePictureBuffer", "Picture id", + picture_buffer_id); + + { + base::AutoLock auto_lock(lock_); + + if (!pictures_.count(picture_buffer_id)) { + // It's possible that we've already posted a DismissPictureBuffer for this + // picture, but it has not yet executed when this ReusePictureBuffer + // was posted to us by the client. In that case just ignore this (we've + // already dismissed it and accounted for that). + DVLOGF(3) << "got picture id=" << picture_buffer_id + << " not in use (anymore?)."; + return; + } + + available_picture_buffers_.push_back(picture_buffer_id); + TRACE_COUNTER_ID2("media,gpu", "Vaapi frames at client", this, "used", + pictures_.size() - available_picture_buffers_.size(), + "available", available_picture_buffers_.size()); + } + + TryOutputPicture(); +} + +void VaapiVideoDecodeAccelerator::FlushTask() { + VLOGF(2); + DCHECK(decoder_thread_task_runner_->BelongsToCurrentThread()); + DCHECK(curr_input_buffer_ && curr_input_buffer_->IsFlushRequest()); + + curr_input_buffer_.reset(); + + // First flush all the pictures that haven't been outputted, notifying the + // client to output them. + bool res = decoder_->Flush(); + RETURN_AND_NOTIFY_ON_FAILURE(res, "Failed flushing the decoder.", + PLATFORM_FAILURE, ); + + // Put the decoder in idle state, ready to resume. + decoder_->Reset(); + + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&VaapiVideoDecodeAccelerator::FinishFlush, weak_this_)); +} + +void VaapiVideoDecodeAccelerator::Flush() { + VLOGF(2) << "Got flush request"; + DCHECK(task_runner_->BelongsToCurrentThread()); + + QueueInputBuffer(DecoderBuffer::CreateEOSBuffer(), -1); +} + +void VaapiVideoDecodeAccelerator::FinishFlush() { + VLOGF(2); + DCHECK(task_runner_->BelongsToCurrentThread()); + + finish_flush_pending_ = false; + + base::AutoLock auto_lock(lock_); + if (state_ != kDecoding) { + DCHECK(state_ == kDestroying || state_ == kResetting) << state_; + return; + } + + // Still waiting for textures from client to finish outputting all pending + // frames. Try again later. + if (!pending_output_cbs_.empty()) { + finish_flush_pending_ = true; + return; + } + + // Resume decoding if necessary. + if (input_buffers_.empty()) { + state_ = kIdle; + } else { + decoder_thread_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&VaapiVideoDecodeAccelerator::DecodeTask, + base::Unretained(this))); + } + + task_runner_->PostTask(FROM_HERE, + base::BindOnce(&Client::NotifyFlushDone, client_)); +} + +void VaapiVideoDecodeAccelerator::ResetTask() { + VLOGF(2); + DCHECK(decoder_thread_task_runner_->BelongsToCurrentThread()); + + // All the decoding tasks from before the reset request from client are done + // by now, as this task was scheduled after them and client is expected not + // to call Decode() after Reset() and before NotifyResetDone. + decoder_->Reset(); + + base::AutoLock auto_lock(lock_); + + // Return current input buffer, if present. + if (curr_input_buffer_) + ReturnCurrInputBuffer_Locked(); + + // And let client know that we are done with reset. + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&VaapiVideoDecodeAccelerator::FinishReset, weak_this_)); +} + +void VaapiVideoDecodeAccelerator::Reset() { + VLOGF(2) << "Got reset request"; + DCHECK(task_runner_->BelongsToCurrentThread()); + + // This will make any new decode tasks exit early. + base::AutoLock auto_lock(lock_); + state_ = kResetting; + finish_flush_pending_ = false; + + // Drop all remaining input buffers, if present. + while (!input_buffers_.empty()) + input_buffers_.pop(); + TRACE_COUNTER1("media,gpu", "Vaapi input buffers", input_buffers_.size()); + + decoder_thread_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&VaapiVideoDecodeAccelerator::ResetTask, + base::Unretained(this))); + + input_ready_.Signal(); + surfaces_available_.Signal(); +} + +void VaapiVideoDecodeAccelerator::FinishReset() { + VLOGF(2); + DCHECK(task_runner_->BelongsToCurrentThread()); + base::AutoLock auto_lock(lock_); + + if (state_ != kResetting) { + DCHECK(state_ == kDestroying || state_ == kUninitialized) << state_; + return; // We could've gotten destroyed already. + } + + // Drop pending outputs. + while (!pending_output_cbs_.empty()) + pending_output_cbs_.pop(); + + if (awaiting_va_surfaces_recycle_) { + // Decoder requested a new surface set while we were waiting for it to + // finish the last DecodeTask, running at the time of Reset(). + // Let the surface set change finish first before resetting. + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&VaapiVideoDecodeAccelerator::FinishReset, weak_this_)); + return; + } + + state_ = kIdle; + + task_runner_->PostTask(FROM_HERE, + base::BindOnce(&Client::NotifyResetDone, client_)); + + // The client might have given us new buffers via Decode() while we were + // resetting and might be waiting for our move, and not call Decode() anymore + // until we return something. Post a DecodeTask() so that we won't + // sleep forever waiting for Decode() in that case. Having two of them + // in the pipe is harmless, the additional one will return as soon as it sees + // that we are back in kDecoding state. + if (!input_buffers_.empty()) { + state_ = kDecoding; + decoder_thread_task_runner_->PostTask( + FROM_HERE, base::BindOnce(&VaapiVideoDecodeAccelerator::DecodeTask, + base::Unretained(this))); + } +} + +void VaapiVideoDecodeAccelerator::Cleanup() { + DCHECK(task_runner_->BelongsToCurrentThread()); + + base::AutoLock auto_lock(lock_); + if (state_ == kUninitialized || state_ == kDestroying) + return; + + VLOGF(2) << "Destroying VAVDA"; + state_ = kDestroying; + + // Call DismissPictureBuffer() to notify |client_| that the picture buffers + // are no longer used and thus |client_| shall release them. If |client_| has + // been invalidated in NotifyError(),|client_| will be destroyed shortly. The + // destruction should release all the PictureBuffers. + if (client_) { + for (const auto& id_and_picture : pictures_) + client_->DismissPictureBuffer(id_and_picture.first); + } + pictures_.clear(); + + client_ptr_factory_.reset(); + weak_this_factory_.InvalidateWeakPtrs(); + + // TODO(mcasas): consider deleting |decoder_| on + // |decoder_thread_task_runner_|, https://crbug.com/789160. + + // Signal all potential waiters on the decoder_thread_, let them early-exit, + // as we've just moved to the kDestroying state, and wait for all tasks + // to finish. + input_ready_.Signal(); + surfaces_available_.Signal(); + { + base::AutoUnlock auto_unlock(lock_); + decoder_thread_.Stop(); + } + if (buffer_allocation_mode_ != BufferAllocationMode::kNone) + available_va_surfaces_.clear(); + + // Notify |decoder_delegate_| of an imminent VAContextID destruction, so it + // can destroy any internal structures making use of it. At this point + // |decoder_thread_| is stopped so we can access |decoder_delegate_| from + // |task_runner_|. + decoder_delegate_->OnVAContextDestructionSoon(); + vaapi_wrapper_->DestroyContext(); + + if (vpp_vaapi_wrapper_) + vpp_vaapi_wrapper_->DestroyContext(); + state_ = kUninitialized; +} + +void VaapiVideoDecodeAccelerator::Destroy() { + DCHECK(task_runner_->BelongsToCurrentThread()); + Cleanup(); + delete this; +} + +bool VaapiVideoDecodeAccelerator::TryToSetupDecodeOnSeparateThread( + const base::WeakPtr& decode_client, + const scoped_refptr& decode_task_runner) { + return false; +} + +void VaapiVideoDecodeAccelerator::SurfaceReady( + scoped_refptr dec_surface, + int32_t bitstream_id, + const gfx::Rect& visible_rect, + const VideoColorSpace& color_space) { + if (!task_runner_->BelongsToCurrentThread()) { + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&VaapiVideoDecodeAccelerator::SurfaceReady, + weak_this_, std::move(dec_surface), + bitstream_id, visible_rect, color_space)); + return; + } + + DCHECK(!awaiting_va_surfaces_recycle_); + + { + base::AutoLock auto_lock(lock_); + // Drop any requests to output if we are resetting or being destroyed. + if (state_ == kResetting || state_ == kDestroying) + return; + } + pending_output_cbs_.push(base::BindOnce( + &VaapiVideoDecodeAccelerator::OutputPicture, weak_this_, + std::move(dec_surface), bitstream_id, visible_rect, color_space)); + + TryOutputPicture(); +} + +scoped_refptr VaapiVideoDecodeAccelerator::CreateSurface() { + DCHECK(decoder_thread_task_runner_->BelongsToCurrentThread()); + base::AutoLock auto_lock(lock_); + + if (available_va_surfaces_.empty()) + return nullptr; + + DCHECK_NE(VA_INVALID_ID, va_surface_format_); + DCHECK(!awaiting_va_surfaces_recycle_); + if (buffer_allocation_mode_ != BufferAllocationMode::kNone) { + auto va_surface_id = std::move(available_va_surfaces_.front()); + const VASurfaceID id = va_surface_id->id(); + available_va_surfaces_.pop_front(); + + TRACE_COUNTER_ID2("media,gpu", "Vaapi VASurfaceIDs", this, "used", + (IsBufferAllocationModeReducedOrSuperReduced() + ? requested_num_reference_frames_ + : pictures_.size()) - + available_va_surfaces_.size(), + "available", available_va_surfaces_.size()); + + return new VASurface( + id, requested_pic_size_, va_surface_format_, + base::BindOnce(va_surface_recycle_cb_, std::move(va_surface_id))); + } + + // Find the first |available_va_surfaces_| id such that the associated + // |pictures_| entry is marked as |available_picture_buffers_|. In practice, + // we will quickly find an available |va_surface_id|. + for (auto it = available_va_surfaces_.begin(); + it != available_va_surfaces_.end(); ++it) { + const VASurfaceID va_surface_id = (*it)->id(); + for (const auto& id_and_picture : pictures_) { + if (id_and_picture.second->va_surface_id() == va_surface_id && + base::Contains(available_picture_buffers_, id_and_picture.first)) { + // Remove |va_surface_id| from the list of availables, and use the id + // to return a new VASurface. + auto va_surface = std::move(*it); + available_va_surfaces_.erase(it); + return new VASurface( + va_surface_id, requested_pic_size_, va_surface_format_, + base::BindOnce(va_surface_recycle_cb_, std::move(va_surface))); + } + } + } + return nullptr; +} + +void VaapiVideoDecodeAccelerator::RecycleVASurface( + std::unique_ptr va_surface, + // We don't use |va_surface_id| but it must be here because this method is + // bound as VASurface::ReleaseCB. + VASurfaceID /*va_surface_id*/) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + { + base::AutoLock auto_lock(lock_); + available_va_surfaces_.push_back(std::move(va_surface)); + + if (buffer_allocation_mode_ != BufferAllocationMode::kNone) { + TRACE_COUNTER_ID2("media,gpu", "Vaapi VASurfaceIDs", this, "used", + (IsBufferAllocationModeReducedOrSuperReduced() + ? requested_num_reference_frames_ + : pictures_.size()) - + available_va_surfaces_.size(), + "available", available_va_surfaces_.size()); + } + surfaces_available_.Signal(); + } + + TryOutputPicture(); +} + +// static +VideoDecodeAccelerator::SupportedProfiles +VaapiVideoDecodeAccelerator::GetSupportedProfiles() { + VideoDecodeAccelerator::SupportedProfiles profiles = + VaapiWrapper::GetSupportedDecodeProfiles(); + // VaVDA never supported VP9 Profile 2, AV1 and HEVC, but VaapiWrapper does. + // Filter them out. + base::EraseIf(profiles, [](const auto& profile) { + VideoCodec codec = VideoCodecProfileToVideoCodec(profile.profile); + return profile.profile == VP9PROFILE_PROFILE2 || + codec == VideoCodec::kAV1 || codec == VideoCodec::kHEVC; + }); + return profiles; +} + +// static +bool VaapiVideoDecodeAccelerator::IsVppProfileSupported() { + return VaapiWrapper::IsVppProfileSupported(); +} + +VaapiVideoDecodeAccelerator::BufferAllocationMode +VaapiVideoDecodeAccelerator::DecideBufferAllocationMode() { +#if BUILDFLAG(USE_VAAPI_X11) + // The IMPORT mode is used for Android on Chrome OS, so this doesn't apply + // here. + DCHECK_NE(output_mode_, VideoDecodeAccelerator::Config::OutputMode::IMPORT); + // TODO(crbug/1116701): get video decode acceleration working with ozone. + // For H.264 on older devices, another +1 is experimentally needed for + // high-to-high resolution changes. + // TODO(mcasas): Figure out why and why only H264, see crbug.com/912295 and + // http://crrev.com/c/1363807/9/media/gpu/h264_decoder.cc#1449. + if (profile_ >= H264PROFILE_MIN && profile_ <= H264PROFILE_MAX) + return BufferAllocationMode::kReduced; + return BufferAllocationMode::kSuperReduced; +#else + // NVIDIA blobs use VDPAU + if (VaapiWrapper::GetImplementationType() == VAImplementation::kNVIDIAVDPAU) { + LOG(INFO) << "VA-API driver on VDPAU backend"; + return BufferAllocationMode::kWrapVdpau; + } + + // TODO(crbug.com/912295): Enable a better BufferAllocationMode for IMPORT + // |output_mode_| as well. + if (output_mode_ == VideoDecodeAccelerator::Config::OutputMode::IMPORT) + return BufferAllocationMode::kNormal; + + // On Gemini Lake, Kaby Lake and later we can pass to libva the client's + // PictureBuffers to decode onto, which skips the use of the Vpp unit and its + // associated format reconciliation copy, avoiding all internal buffer + // allocations. + // TODO(crbug.com/911754): Enable for VP9 Profile 2. + if (false && IsGeminiLakeOrLater() && + (profile_ == VP9PROFILE_PROFILE0 || profile_ == VP8PROFILE_ANY || + (profile_ >= H264PROFILE_MIN && profile_ <= H264PROFILE_MAX))) { + // Add one to the reference frames for the one being currently egressed, and + // an extra allocation for both |client_| and |decoder_|, see + // crrev.com/c/1576560. + if (profile_ == VP8PROFILE_ANY) + num_extra_pics_ = 3; + return BufferAllocationMode::kNone; + } + + // For H.264 on older devices, another +1 is experimentally needed for + // high-to-high resolution changes. + // TODO(mcasas): Figure out why and why only H264, see crbug.com/912295 and + // http://crrev.com/c/1363807/9/media/gpu/h264_decoder.cc#1449. + if (profile_ >= H264PROFILE_MIN && profile_ <= H264PROFILE_MAX) + return BufferAllocationMode::kReduced; + + // If we're here, we have to use the Vpp unit and allocate buffers for + // |decoder_|; usually we'd have to allocate the |decoder_|s + // GetRequiredNumOfPictures() internally, we can allocate just |decoder_|s + // GetNumReferenceFrames() + 1. Moreover, we also request the |client_| to + // allocate less than the usual |decoder_|s GetRequiredNumOfPictures(). + return BufferAllocationMode::kSuperReduced; +#endif +} + +bool VaapiVideoDecodeAccelerator::IsBufferAllocationModeReducedOrSuperReduced() + const { + return buffer_allocation_mode_ == BufferAllocationMode::kSuperReduced || + buffer_allocation_mode_ == BufferAllocationMode::kReduced; +} + +bool VaapiVideoDecodeAccelerator::OnMemoryDump( + const base::trace_event::MemoryDumpArgs& args, + base::trace_event::ProcessMemoryDump* pmd) { + using base::trace_event::MemoryAllocatorDump; + base::AutoLock auto_lock(lock_); + if (buffer_allocation_mode_ == BufferAllocationMode::kNone || + !requested_num_reference_frames_) { + return false; + } + + auto dump_name = base::StringPrintf("gpu/vaapi/decoder/0x%" PRIxPTR, + reinterpret_cast(this)); + MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(dump_name); + + constexpr float kNumBytesPerPixelYUV420 = 12.0 / 8; + constexpr float kNumBytesPerPixelYUV420_10bpp = 2 * kNumBytesPerPixelYUV420; + DCHECK(va_surface_format_ == VA_RT_FORMAT_YUV420 || + va_surface_format_ == VA_RT_FORMAT_YUV420_10BPP); + const float va_surface_bytes_per_pixel = + va_surface_format_ == VA_RT_FORMAT_YUV420 ? kNumBytesPerPixelYUV420 + : kNumBytesPerPixelYUV420_10bpp; + // Report |requested_num_surfaces| and the associated memory size. The + // calculated size is an estimation since we don't know the internal VA + // strides, texture compression, headers, etc, but is a good lower boundary. + const size_t requested_num_surfaces = + IsBufferAllocationModeReducedOrSuperReduced() + ? requested_num_reference_frames_ + : pictures_.size(); + dump->AddScalar(MemoryAllocatorDump::kNameSize, + MemoryAllocatorDump::kUnitsBytes, + static_cast(requested_num_surfaces * + requested_pic_size_.GetArea() * + va_surface_bytes_per_pixel)); + dump->AddScalar(MemoryAllocatorDump::kNameObjectCount, + MemoryAllocatorDump::kUnitsObjects, + static_cast(requested_num_surfaces)); + + return true; +} + +} // namespace media diff --git a/media/gpu/vaapi/vaapi_video_decode_accelerator.h b/media/gpu/vaapi/vaapi_video_decode_accelerator.h new file mode 100644 index 00000000..acfc8787 --- /dev/null +++ b/media/gpu/vaapi/vaapi_video_decode_accelerator.h @@ -0,0 +1,368 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file contains an implementation of VideoDecoderAccelerator +// that utilizes hardware video decoder present on Intel CPUs. + +#ifndef MEDIA_GPU_VAAPI_VAAPI_VIDEO_DECODE_ACCELERATOR_H_ +#define MEDIA_GPU_VAAPI_VAAPI_VIDEO_DECODE_ACCELERATOR_H_ + +#include +#include + +#include +#include +#include +#include +#include + +#include "base/containers/queue.h" +#include "base/containers/small_map.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/task/single_thread_task_runner.h" +#include "base/thread_annotations.h" +#include "base/threading/thread.h" +#include "base/trace_event/memory_dump_provider.h" +#include "media/base/bitstream_buffer.h" +#include "media/gpu/decode_surface_handler.h" +#include "media/gpu/gpu_video_decode_accelerator_helpers.h" +#include "media/gpu/media_gpu_export.h" +#include "media/gpu/vaapi/vaapi_picture_factory.h" +#include "media/gpu/vaapi/vaapi_wrapper.h" +#include "media/video/picture.h" +#include "media/video/video_decode_accelerator.h" + +namespace gl { +class GLImage; +} + +namespace media { + +class AcceleratedVideoDecoder; +template +class ScopedID; +class VaapiVideoDecoderDelegate; +class VaapiPicture; + +// Class to provide video decode acceleration for Intel systems with hardware +// support for it, and on which libva is available. +// Decoding tasks are performed in a separate decoding thread. +// +// Threading/life-cycle: this object is created & destroyed on the GPU +// ChildThread. A few methods on it are called on the decoder thread which is +// stopped during |this->Destroy()|, so any tasks posted to the decoder thread +// can assume |*this| is still alive. See |weak_this_| below for more details. +class MEDIA_GPU_EXPORT VaapiVideoDecodeAccelerator + : public VideoDecodeAccelerator, + public DecodeSurfaceHandler, + public base::trace_event::MemoryDumpProvider { + public: + VaapiVideoDecodeAccelerator( + const MakeGLContextCurrentCallback& make_context_current_cb, + const BindGLImageCallback& bind_image_cb); + + VaapiVideoDecodeAccelerator(const VaapiVideoDecodeAccelerator&) = delete; + VaapiVideoDecodeAccelerator& operator=(const VaapiVideoDecodeAccelerator&) = + delete; + + ~VaapiVideoDecodeAccelerator() override; + + // VideoDecodeAccelerator implementation. + bool Initialize(const Config& config, Client* client) override; + void Decode(BitstreamBuffer bitstream_buffer) override; + void Decode(scoped_refptr buffer, + int32_t bitstream_id) override; + void AssignPictureBuffers(const std::vector& buffers) override; +#if defined(USE_OZONE) + void ImportBufferForPicture( + int32_t picture_buffer_id, + VideoPixelFormat pixel_format, + gfx::GpuMemoryBufferHandle gpu_memory_buffer_handle) override; +#endif + void ReusePictureBuffer(int32_t picture_buffer_id) override; + void Flush() override; + void Reset() override; + void Destroy() override; + bool TryToSetupDecodeOnSeparateThread( + const base::WeakPtr& decode_client, + const scoped_refptr& decode_task_runner) + override; + + static VideoDecodeAccelerator::SupportedProfiles GetSupportedProfiles(); + + static bool IsVppProfileSupported(); + + // DecodeSurfaceHandler implementation. + scoped_refptr CreateSurface() override; + void SurfaceReady(scoped_refptr va_surface, + int32_t bitstream_id, + const gfx::Rect& visible_rect, + const VideoColorSpace& color_space) override; + + // base::trace_event::MemoryDumpProvider implementation. + bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, + base::trace_event::ProcessMemoryDump* pmd) override; + + private: + friend class VaapiVideoDecodeAcceleratorTest; + + // An input buffer with id provided by the client and awaiting consumption. + class InputBuffer; + // A self-cleaning VASurfaceID. + using ScopedVASurfaceID = ScopedID; + + // Notify the client that an error has occurred and decoding cannot continue. + void NotifyError(Error error); + void NotifyStatus(VaapiStatus status); + + // Queue a input buffer for decode. + void QueueInputBuffer(scoped_refptr buffer, + int32_t bitstream_id); + + // Gets a new |current_input_buffer_| from |input_buffers_| and sets it up in + // |decoder_|. This method will sleep if no |input_buffers_| are available. + // Returns true if a new buffer has been set up, false if an early exit has + // been requested (due to initiated reset/flush/destroy). + bool GetCurrInputBuffer_Locked() EXCLUSIVE_LOCKS_REQUIRED(lock_); + + // Signals the client that |curr_input_buffer_| has been read and can be + // returned. Will also release the mapping. + void ReturnCurrInputBuffer_Locked() EXCLUSIVE_LOCKS_REQUIRED(lock_); + + // Waits for more surfaces to become available. Returns true once they do or + // false if an early exit has been requested (due to an initiated + // reset/flush/destroy). + bool WaitForSurfaces_Locked() EXCLUSIVE_LOCKS_REQUIRED(lock_); + + // Continue decoding given input buffers and sleep waiting for input/output + // as needed. Will exit if a new set of surfaces or reset/flush/destroy + // is requested. + void DecodeTask(); + + // Scheduled after receiving a flush request and executed after the current + // decoding task finishes decoding pending inputs. Makes the decoder return + // all remaining output pictures and puts it in an idle state, ready + // to resume if needed and schedules a FinishFlush. + void FlushTask(); + + // Scheduled by the FlushTask after decoder is flushed to put VAVDA into idle + // state and notify the client that flushing has been finished. + void FinishFlush(); + + // Scheduled after receiving a reset request and executed after the current + // decoding task finishes decoding the current frame. Puts the decoder into + // an idle state, ready to resume if needed, discarding decoded but not yet + // outputted pictures (decoder keeps ownership of their associated picture + // buffers). Schedules a FinishReset afterwards. + void ResetTask(); + + // Scheduled by ResetTask after it's done putting VAVDA into an idle state. + // Drops remaining input buffers and notifies the client that reset has been + // finished. + void FinishReset(); + + // Helper for Destroy(), doing all the actual work except for deleting self. + void Cleanup(); + + // Get a usable framebuffer configuration for use in binding textures + // or return false on failure. + bool InitializeFBConfig(); + + // Callback to be executed once we have a |va_surface| to be output and an + // available VaapiPicture in |available_picture_buffers_| for output. Puts + // contents of |va_surface| into the latter, releases the surface and passes + // the resulting picture to |client_| along with |visible_rect|. + void OutputPicture(scoped_refptr va_surface, + int32_t input_id, + gfx::Rect visible_rect, + const VideoColorSpace& picture_color_space); + + // Try to OutputPicture() if we have both a ready surface and picture. + void TryOutputPicture(); + + // Called when a VASurface is no longer in use by |decoder_| nor |client_|. + // Returns it to |available_va_surfaces_|. |va_surface_id| is not used but it + // must be here to bind this method as VASurface::ReleaseCB. + void RecycleVASurface(std::unique_ptr va_surface, + VASurfaceID va_surface_id); + + // Request a new set of |num_pics| PictureBuffers to be allocated by + // |client_|. Up to |num_reference_frames| out of |num_pics_| might be needed + // by |decoder_|. + void InitiateSurfaceSetChange(size_t num_pics, + gfx::Size size, + size_t num_reference_frames, + const gfx::Rect& visible_rect); + + // Check if the surfaces have been released or post ourselves for later. + void TryFinishSurfaceSetChange(); + + // Different modes of internal buffer allocations. + enum class BufferAllocationMode { + // Only using |client_|s provided PictureBuffers, none internal. + kNone, + + // Using a reduced amount of |client_|s provided PictureBuffers and + // |decoder_|s GetNumReferenceFrames() internallly. + kSuperReduced, + + // Similar to kSuperReduced, but we have to increase slightly the amount of + // PictureBuffers allocated for the |client_|. + kReduced, + + // VaapiVideoDecodeAccelerator can work with this mode on all platforms. + // Using |client_|s provided PictureBuffers and as many internally + // allocated. + kNormal, + kWrapVdpau, + }; + + // Decides the concrete buffer allocation mode, depending on the hardware + // platform and other parameters. + BufferAllocationMode DecideBufferAllocationMode(); + bool IsBufferAllocationModeReducedOrSuperReduced() const; + + // VAVDA state. + enum State { + // Initialize() not called yet or failed. + kUninitialized, + // DecodeTask running. + kDecoding, + // Resetting, waiting for decoder to finish current task and cleanup. + kResetting, + // Idle, decoder in state ready to start/resume decoding. + kIdle, + // Destroying, waiting for the decoder to finish current task. + kDestroying, + }; + + base::Lock lock_; + State state_ GUARDED_BY(lock_); + // Only used on |task_runner_|. + Config::OutputMode output_mode_; + + // Queue of available InputBuffers. + base::queue> input_buffers_ GUARDED_BY(lock_); + // Signalled when input buffers are queued onto |input_buffers_| queue. + base::ConditionVariable input_ready_; + + // Current input buffer at decoder. Only used on |decoder_thread_task_runner_| + std::unique_ptr curr_input_buffer_; + + // Only used on |task_runner_|. + std::unique_ptr vaapi_picture_factory_; + + // The following variables are constructed/initialized in Initialize() when + // the codec information is received. |vaapi_wrapper_| is thread safe. + scoped_refptr vaapi_wrapper_; + // Only used on |decoder_thread_task_runner_|. + std::unique_ptr decoder_; + // TODO(crbug.com/1022246): Instead of having the raw pointer here, getting + // the pointer from AcceleratedVideoDecoder. + VaapiVideoDecoderDelegate* decoder_delegate_ = nullptr; + + // Filled in during Initialize(). + BufferAllocationMode buffer_allocation_mode_; + + // VaapiWrapper for VPP (Video Post Processing). This is used for copying + // from a decoded surface to a surface bound to client's PictureBuffer. + scoped_refptr vpp_vaapi_wrapper_; + + // All allocated VaapiPictures, regardless of their current state. Pictures + // are allocated at AssignPictureBuffers() and are kept until dtor or + // TryFinishSurfaceSetChange(). Comes after |vaapi_wrapper_| to ensure all + // pictures are destroyed before this is destroyed. + base::small_map>> pictures_ + GUARDED_BY(lock_); + // List of PictureBuffer ids available to be sent to |client_| via + // OutputPicture() (|client_| returns them via ReusePictureBuffer()). + std::list available_picture_buffers_ GUARDED_BY(lock_); + + // VASurfaces available and that can be passed to |decoder_| for its use upon + // CreateSurface() request (and then returned via RecycleVASurface()). + std::list> available_va_surfaces_ + GUARDED_BY(lock_); + // Signalled when output surfaces are queued into |available_va_surfaces_|. + base::ConditionVariable surfaces_available_; + // VASurfaceIDs format, filled in when created. + unsigned int va_surface_format_; + + // Pending output requests from the decoder. When it indicates that we should + // output a surface and we have an available Picture (i.e. texture) ready + // to use, we'll execute the callback passing the Picture. The callback + // will put the contents of the surface into the picture and return it to + // the client, releasing the surface as well. + // If we don't have any available |pictures_| at the time when the decoder + // requests output, we'll store the request in this queue for later and run it + // once the client gives us more textures via ReusePictureBuffer(). + // Only used on |task_runner_|. + base::queue pending_output_cbs_; + + // WeakPtr<> pointing to |this| for use in posting tasks from the decoder + // thread back to the ChildThread. Because the decoder thread is a member of + // this class, any task running on the decoder thread is guaranteed that this + // object is still alive. As a result, tasks posted from ChildThread to + // decoder thread should use base::Unretained(this), and tasks posted from the + // decoder thread to the ChildThread should use |weak_this_|. + base::WeakPtr weak_this_; + + // Callback used to recycle VASurfaces. Only used on |task_runner_|. + base::RepeatingCallback, VASurfaceID)> + va_surface_recycle_cb_; + + // To expose client callbacks from VideoDecodeAccelerator. Used only on + // |task_runner_|. + std::unique_ptr> client_ptr_factory_; + base::WeakPtr client_; + + // ChildThread's task runner. + const scoped_refptr task_runner_; + + base::Thread decoder_thread_; + // Use this to post tasks to |decoder_thread_| instead of + // |decoder_thread_.task_runner()| because the latter will be NULL once + // |decoder_thread_.Stop()| returns. + scoped_refptr decoder_thread_task_runner_; + + // Whether we are waiting for any |pending_output_cbs_| to be run before + // NotifyingFlushDone. Only used on |task_runner_|. + bool finish_flush_pending_; + + // Decoder requested a new surface set and we are waiting for all the surfaces + // to be returned before we can free them. Only used on |task_runner_|. + bool awaiting_va_surfaces_recycle_; + + // Last requested number/resolution/visible rectangle of output + // PictureBuffers. + size_t requested_num_pics_; + gfx::Size requested_pic_size_; + gfx::Rect requested_visible_rect_; + // Potential extra PictureBuffers to request, used only on + // BufferAllocationMode::kNone, see DecideBufferAllocationMode(). + size_t num_extra_pics_ = 0; + + // Max number of reference frames needed by |decoder_|. Only used on + // |task_runner_| and when in BufferAllocationMode::kNone. + size_t requested_num_reference_frames_; + size_t previously_requested_num_reference_frames_; + + // The video stream's profile. + VideoCodecProfile profile_; + + // Callback to make GL context current. + MakeGLContextCurrentCallback make_context_current_cb_; + + // Callback to bind a GLImage to a given texture. + BindGLImageCallback bind_image_cb_; + + // The WeakPtrFactory for |weak_this_|. + base::WeakPtrFactory weak_this_factory_; +}; + +} // namespace media + +#endif // MEDIA_GPU_VAAPI_VAAPI_VIDEO_DECODE_ACCELERATOR_H_ diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc new file mode 100644 index 00000000..129ce332 --- /dev/null +++ b/media/gpu/vaapi/vaapi_wrapper.cc @@ -0,0 +1,3308 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/gpu/vaapi/vaapi_wrapper.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/bits.h" +#include "base/callback_helpers.h" +#include "base/containers/contains.h" +#include "base/containers/cxx20_erase.h" +#include "base/cpu.h" +#include "base/cxx17_backports.h" +#include "base/environment.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/metrics/histogram_macros.h" +#include "base/no_destructor.h" +#include "base/numerics/checked_math.h" +#include "base/numerics/safe_conversions.h" +#include "base/posix/eintr_wrapper.h" +#include "base/strings/pattern.h" +#include "base/strings/string_util.h" +#include "base/system/sys_info.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "ui/base/ui_base_features.h" + +#include "media/base/media_switches.h" +#include "media/base/video_frame.h" +#include "media/base/video_types.h" +#include "media/gpu/macros.h" +#include "media/media_buildflags.h" + +// Auto-generated for dlopen libva libraries +#include "media/gpu/vaapi/va_stubs.h" + +#include "third_party/libva_protected_content/va_protected_content.h" +#include "third_party/libyuv/include/libyuv.h" +#include "ui/gfx/buffer_format_util.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/linux/native_pixmap_dmabuf.h" +#include "ui/gfx/native_pixmap.h" +#include "ui/gfx/native_pixmap_handle.h" +#include "ui/gl/gl_bindings.h" +#include "ui/gl/gl_implementation.h" + +#if BUILDFLAG(USE_VAAPI_X11) +typedef XID Drawable; + +extern "C" { +#include "media/gpu/vaapi/va_x11.sigs" +} + +#include "ui/gfx/x/connection.h" // nogncheck +#endif // BUILDFLAG(USE_VAAPI_X11) + +#if defined(USE_OZONE) +#include "ui/ozone/public/ozone_platform.h" +#include "ui/ozone/public/surface_factory_ozone.h" +#endif + +#if BUILDFLAG(IS_CHROMEOS_ASH) +#include +using media_gpu_vaapi::kModuleVa_prot; +#endif + +using media_gpu_vaapi::kModuleVa; +using media_gpu_vaapi::kModuleVa_drm; +#if BUILDFLAG(USE_VAAPI_X11) +using media_gpu_vaapi::kModuleVa_x11; +#endif // BUILDFLAG(USE_VAAPI_X11) +using media_gpu_vaapi::InitializeStubs; +using media_gpu_vaapi::IsVaInitialized; +#if BUILDFLAG(USE_VAAPI_X11) +using media_gpu_vaapi::IsVa_x11Initialized; +#endif // BUILDFLAG(USE_VAAPI_X11) +using media_gpu_vaapi::IsVa_drmInitialized; +using media_gpu_vaapi::StubPathMap; + +namespace media { + +// These values are logged to UMA. Entries should not be renumbered and numeric +// values should never be reused. Please keep in sync with +// "VaapiFunctions" in src/tools/metrics/histograms/enums.xml. +enum class VaapiFunctions { + kVABeginPicture = 0, + kVACreateBuffer = 1, + kVACreateConfig = 2, + kVACreateContext = 3, + kVACreateImage = 4, + kVACreateSurfaces_Allocating = 5, + kVACreateSurfaces_Importing = 6, + kVADestroyBuffer = 7, + kVADestroyConfig = 8, + kVADestroyContext = 9, + kVADestroySurfaces = 10, + kVAEndPicture = 11, + kVAExportSurfaceHandle = 12, + kVAGetConfigAttributes = 13, + kVAPutImage = 14, + kVAPutSurface = 15, + kVAQueryConfigAttributes = 16, + kVAQueryImageFormats = 17, + kVAQuerySurfaceAttributes = 18, + kVAQueryVideoProcPipelineCaps = 19, + kVARenderPicture_VABuffers = 20, + kVARenderPicture_Vpp = 21, + kVASyncSurface = 22, + kVATerminate = 23, + kVAUnmapBuffer = 24, + // Protected mode functions below. + kVACreateProtectedSession = 25, + kVADestroyProtectedSession = 26, + kVAAttachProtectedSession = 27, + kVADetachProtectedSession = 28, + kVAProtectedSessionHwUpdate_Deprecated = 29, + kVAProtectedSessionExecute = 30, + // Anything else is captured in this last entry. + kOtherVAFunction = 31, + kMaxValue = kOtherVAFunction, +}; + +void ReportVaapiErrorToUMA(const std::string& histogram_name, + VaapiFunctions value) { + UMA_HISTOGRAM_ENUMERATION(histogram_name, value); +} + +constexpr std::array(VaapiFunctions::kMaxValue) + 1> + kVaapiFunctionNames = {"vaBeginPicture", + "vaCreateBuffer", + "vaCreateConfig", + "vaCreateContext", + "vaCreateImage", + "vaCreateSurfaces (allocate mode)", + "vaCreateSurfaces (import mode)", + "vaDestroyBuffer", + "vaDestroyConfig", + "vaDestroyContext", + "vaDestroySurfaces", + "vaEndPicture", + "vaExportSurfaceHandle", + "vaGetConfigAttributes", + "vaPutImage", + "vaPutSurface", + "vaQueryConfigAttributes", + "vaQueryImageFormats", + "vaQuerySurfaceAttributes", + "vaQueryVideoProcPipelineCaps", + "vaRenderPicture (|pending_va_buffers_|)", + "vaRenderPicture using Vpp", + "vaSyncSurface", + "vaTerminate", + "vaUnmapBuffer", + "vaCreateProtectedSession", + "vaDestroyProtectedSession", + "vaAttachProtectedSession", + "vaDetachProtectedSession", + "vaProtectedSessionHwUpdate (Deprecated)", + "vaProtectedSessionExecute", + "Other VA function"}; + +// Translates |function| into a human readable string for logging. +const char* VaapiFunctionName(VaapiFunctions function) { + DCHECK(function <= VaapiFunctions::kMaxValue); + return kVaapiFunctionNames[static_cast(function)]; +} + +} // namespace media + +#define LOG_VA_ERROR_AND_REPORT(va_error, function) \ + do { \ + LOG(ERROR) << VaapiFunctionName(function) \ + << " failed, VA error: " << vaErrorStr(va_error); \ + report_error_to_uma_cb_.Run(function); \ + } while (0) + +#define VA_LOG_ON_ERROR(va_res, function) \ + do { \ + const VAStatus va_res_va_log_on_error = (va_res); \ + if (va_res_va_log_on_error != VA_STATUS_SUCCESS) \ + LOG_VA_ERROR_AND_REPORT(va_res_va_log_on_error, function); \ + } while (0) + +#define VA_SUCCESS_OR_RETURN(va_res, function, ret) \ + do { \ + const VAStatus va_res_va_sucess_or_return = (va_res); \ + if (va_res_va_sucess_or_return != VA_STATUS_SUCCESS) { \ + LOG_VA_ERROR_AND_REPORT(va_res_va_sucess_or_return, function); \ + return (ret); \ + } \ + DVLOG(3) << VaapiFunctionName(function); \ + } while (0) + +namespace { + +uint32_t BufferFormatToVAFourCC(gfx::BufferFormat fmt) { + switch (fmt) { + case gfx::BufferFormat::BGRX_8888: + return VA_FOURCC_BGRX; + case gfx::BufferFormat::BGRA_8888: + return VA_FOURCC_BGRA; + case gfx::BufferFormat::RGBX_8888: + return VA_FOURCC_RGBX; + case gfx::BufferFormat::RGBA_8888: + return VA_FOURCC_RGBA; + case gfx::BufferFormat::YVU_420: + return VA_FOURCC_YV12; + case gfx::BufferFormat::YUV_420_BIPLANAR: + return VA_FOURCC_NV12; + case gfx::BufferFormat::P010: + return VA_FOURCC_P010; + default: + NOTREACHED() << gfx::BufferFormatToString(fmt); + return 0; + } +} + +media::VAImplementation VendorStringToImplementationType( + const std::string& va_vendor_string) { + if (base::StartsWith(va_vendor_string, "Mesa Gallium driver", + base::CompareCase::SENSITIVE)) { + return media::VAImplementation::kMesaGallium; + } else if (base::StartsWith(va_vendor_string, "Intel i965 driver", + base::CompareCase::SENSITIVE)) { + return media::VAImplementation::kIntelI965; + } else if (base::StartsWith(va_vendor_string, "Intel iHD driver", + base::CompareCase::SENSITIVE)) { + return media::VAImplementation::kIntelIHD; + } else if (base::StartsWith(va_vendor_string, "Splitted-Desktop Systems VDPAU", + base::CompareCase::SENSITIVE)) { + return media::VAImplementation::kNVIDIAVDPAU; + } + return media::VAImplementation::kOther; +} + +} // namespace + +namespace media { + +namespace { +// VAEntrypoint is an enumeration starting from 1, but has no "invalid" value. +constexpr VAEntrypoint kVAEntrypointInvalid = static_cast(0); + +// Returns true if the SoC has a Gen8 GPU. CPU model ID's are referenced from +// the following file in the kernel source: arch/x86/include/asm/intel-family.h. +bool IsGen8Gpu() { + constexpr int kPentiumAndLaterFamily = 0x06; + constexpr int kBroadwellCoreModelId = 0x3D; + constexpr int kBroadwellGT3EModelId = 0x47; + constexpr int kBroadwellXModelId = 0x4F; + constexpr int kBroadwellXeonDModelId = 0x56; + constexpr int kBraswellModelId = 0x4C; + static const base::NoDestructor cpuid; + static const bool is_gen8_gpu = cpuid->family() == kPentiumAndLaterFamily && + (cpuid->model() == kBroadwellCoreModelId || + cpuid->model() == kBroadwellGT3EModelId || + cpuid->model() == kBroadwellXModelId || + cpuid->model() == kBroadwellXeonDModelId || + cpuid->model() == kBraswellModelId); + return is_gen8_gpu; +} + +// Returns true if the SoC has a Gen9 GPU. CPU model ID's are referenced from +// the following file in the kernel source: arch/x86/include/asm/intel-family.h. +bool IsGen9Gpu() { + constexpr int kPentiumAndLaterFamily = 0x06; + constexpr int kSkyLakeModelId = 0x5E; + constexpr int kSkyLake_LModelId = 0x4E; + constexpr int kApolloLakeModelId = 0x5c; + static const base::NoDestructor cpuid; + static const bool is_gen9_gpu = cpuid->family() == kPentiumAndLaterFamily && + (cpuid->model() == kSkyLakeModelId || + cpuid->model() == kSkyLake_LModelId || + cpuid->model() == kApolloLakeModelId); + return is_gen9_gpu; +} + +// Returns true if the SoC has a 9.5 GPU. CPU model IDs are referenced from the +// following file in the kernel source: arch/x86/include/asm/intel-family.h. +bool IsGen95Gpu() { + constexpr int kPentiumAndLaterFamily = 0x06; + constexpr int kKabyLakeModelId = 0x9E; + // Amber Lake, Whiskey Lake and some Comet Lake CPU IDs are the same as KBL L. + constexpr int kKabyLake_LModelId = 0x8E; + constexpr int kGeminiLakeModelId = 0x7A; + constexpr int kCometLakeModelId = 0xA5; + constexpr int kCometLake_LModelId = 0xA6; + static const base::NoDestructor cpuid; + static const bool is_gen95_gpu = cpuid->family() == kPentiumAndLaterFamily && + (cpuid->model() == kKabyLakeModelId || + cpuid->model() == kKabyLake_LModelId || + cpuid->model() == kGeminiLakeModelId || + cpuid->model() == kCometLakeModelId || + cpuid->model() == kCometLake_LModelId); + return is_gen95_gpu; +} + +// Returns true if the intel hybrid driver is used for decoding |va_profile|. +// https://github.com/intel/intel-hybrid-driver +// Note that since the hybrid driver runs as a part of the i965 driver, +// vaQueryVendorString() returns "Intel i965 driver". +bool IsUsingHybridDriverForDecoding(VAProfile va_profile) { + // Note that Skylake (not gen8) also needs the hybrid decoder for VP9 + // decoding. However, it is disabled today on ChromeOS + // (see crrev.com/c/390511). + return va_profile == VAProfileVP9Profile0 && IsGen8Gpu(); +} + +// Returns true if the SoC is considered a low power one, i.e. it's an Intel +// Pentium, Celeron, or a Core Y-series. See go/intel-socs-101 or +// https://www.intel.com/content/www/us/en/processors/processor-numbers.html. +bool IsLowPowerIntelProcessor() { + constexpr int kPentiumAndLaterFamily = 0x06; + static const base::NoDestructor cpuid; + static const bool is_core_y_processor = + base::MatchPattern(cpuid->cpu_brand(), "Intel(R) Core(TM) *Y CPU*"); + + static const bool is_low_power_intel = + cpuid->family() == kPentiumAndLaterFamily && + (base::Contains(cpuid->cpu_brand(), "Pentium") || + base::Contains(cpuid->cpu_brand(), "Celeron") || is_core_y_processor); + return is_low_power_intel; +} + +bool IsModeEncoding(VaapiWrapper::CodecMode mode) { + return mode == VaapiWrapper::CodecMode::kEncodeConstantBitrate || + mode == VaapiWrapper::CodecMode::kEncodeConstantQuantizationParameter; +} + +bool GetNV12VisibleWidthBytes(int visible_width, + uint32_t plane, + size_t* bytes) { + if (plane == 0) { + *bytes = base::checked_cast(visible_width); + return true; + } + + *bytes = base::checked_cast(visible_width); + return visible_width % 2 == 0 || + base::CheckAdd(visible_width, 1).AssignIfValid(bytes); +} + +// Fill 0 on VAImage's non visible area. +bool ClearNV12Padding(const VAImage& image, + const gfx::Size& visible_size, + uint8_t* data) { + DCHECK_EQ(2u, image.num_planes); + DCHECK_EQ(image.format.fourcc, static_cast(VA_FOURCC_NV12)); + + size_t visible_width_bytes[2] = {}; + if (!GetNV12VisibleWidthBytes(visible_size.width(), 0u, + &visible_width_bytes[0]) || + !GetNV12VisibleWidthBytes(visible_size.width(), 1u, + &visible_width_bytes[1])) { + return false; + } + + for (uint32_t plane = 0; plane < image.num_planes; plane++) { + size_t row_bytes = base::strict_cast(image.pitches[plane]); + if (row_bytes == visible_width_bytes[plane]) + continue; + + CHECK_GT(row_bytes, visible_width_bytes[plane]); + int visible_height = visible_size.height(); + if (plane == 1 && !(base::CheckAdd(visible_size.height(), 1) / 2) + .AssignIfValid(&visible_height)) { + return false; + } + + const size_t padding_bytes = row_bytes - visible_width_bytes[plane]; + uint8_t* plane_data = data + image.offsets[plane]; + for (int row = 0; row < visible_height; row++, plane_data += row_bytes) + memset(plane_data + visible_width_bytes[plane], 0, padding_bytes); + + CHECK_GE(base::strict_cast(image.height), visible_height); + size_t image_height = base::strict_cast(image.height); + if (plane == 1 && !(base::CheckAdd(image.height, 1) / 2) + .AssignIfValid(&image_height)) { + return false; + } + + base::CheckedNumeric remaining_area(image_height); + remaining_area -= base::checked_cast(visible_height); + remaining_area *= row_bytes; + if (!remaining_area.IsValid()) + return false; + memset(plane_data, 0, remaining_area.ValueOrDie()); + } + + return true; +} + +// Can't statically initialize the profile map: +// https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables +using ProfileCodecMap = std::map; +const ProfileCodecMap& GetProfileCodecMap() { + static const base::NoDestructor kMediaToVAProfileMap({ + // VAProfileH264Baseline is deprecated in since libva 2.0.0. + {H264PROFILE_BASELINE, VAProfileH264ConstrainedBaseline}, + {H264PROFILE_MAIN, VAProfileH264Main}, + // TODO(posciak): See if we can/want to support other variants of + // H264PROFILE_HIGH*. + {H264PROFILE_HIGH, VAProfileH264High}, + {VP8PROFILE_ANY, VAProfileVP8Version0_3}, + {VP9PROFILE_PROFILE0, VAProfileVP9Profile0}, + // VaapiWrapper does not support VP9 Profile 1, see b/153680337. + // {VP9PROFILE_PROFILE1, VAProfileVP9Profile1}, + {VP9PROFILE_PROFILE2, VAProfileVP9Profile2}, + // VaapiWrapper does not support Profile 3. + //{VP9PROFILE_PROFILE3, VAProfileVP9Profile3}, + {AV1PROFILE_PROFILE_MAIN, VAProfileAV1Profile0}, + // VaapiWrapper does not support AV1 Profile 1. + // {AV1PROFILE_PROFILE_HIGH, VAProfileAV1Profile1}, +#if BUILDFLAG(ENABLE_PLATFORM_HEVC_DECODING) + {HEVCPROFILE_MAIN, VAProfileHEVCMain}, + {HEVCPROFILE_MAIN10, VAProfileHEVCMain10}, +#endif + }); + return *kMediaToVAProfileMap; +} + +// Maps a VideoCodecProfile |profile| to a VAProfile, or VAProfileNone. +VAProfile ProfileToVAProfile(VideoCodecProfile profile, + VaapiWrapper::CodecMode mode) { + const auto& profiles = GetProfileCodecMap(); + const auto& maybe_profile = profiles.find(profile); + if (maybe_profile == profiles.end()) + return VAProfileNone; + return maybe_profile->second; +} + +bool IsVAProfileSupported(VAProfile va_profile) { + const auto& profiles = GetProfileCodecMap(); + // VAProfileJPEGBaseline and VAProfileProtected are always recognized but are + // not video codecs per se. + return va_profile == VAProfileJPEGBaseline || +#if BUILDFLAG(IS_CHROMEOS_ASH) + va_profile == VAProfileProtected || +#endif + std::find_if(profiles.begin(), profiles.end(), + [va_profile](const auto& entry) { + return entry.second == va_profile; + }) != profiles.end(); +} + +bool IsBlockedDriver(VaapiWrapper::CodecMode mode, VAProfile va_profile) { + if (!IsModeEncoding(mode)) { + return va_profile == VAProfileAV1Profile0 && + !base::FeatureList::IsEnabled(kVaapiAV1Decoder); + } + + // TODO(posciak): Remove once VP8 encoding is to be enabled by default. + if (va_profile == VAProfileVP8Version0_3 && + !base::FeatureList::IsEnabled(kVaapiVP8Encoder)) { + return true; + } + + // TODO(crbug.com/811912): Remove once VP9 encoding is enabled by default. + if (va_profile == VAProfileVP9Profile0 && + !base::FeatureList::IsEnabled(kVaapiVP9Encoder)) { + return true; + } + + return false; +} + +// This class is a wrapper around its |va_display_| (and its associated +// |va_lock_|) to guarantee mutual exclusion and singleton behaviour. +class VADisplayState { + public: + static VADisplayState* Get(); + + VADisplayState(const VADisplayState&) = delete; + VADisplayState& operator=(const VADisplayState&) = delete; + + // Initialize static data before sandbox is enabled. + static void PreSandboxInitialization(); + + bool Initialize(); + VAStatus Deinitialize(); + + base::Lock* va_lock() { return &va_lock_; } + VADisplay va_display() const { return va_display_; } + VAImplementation implementation_type() const { return implementation_type_; } + + void SetDrmFd(base::PlatformFile fd) { drm_fd_.reset(HANDLE_EINTR(dup(fd))); } + + private: + friend class base::NoDestructor; + + VADisplayState(); + ~VADisplayState() = default; + + // Implementation of Initialize() called only once. + bool InitializeOnce() EXCLUSIVE_LOCKS_REQUIRED(va_lock_); + bool InitializeVaDisplay_Locked() EXCLUSIVE_LOCKS_REQUIRED(va_lock_); + bool InitializeVaDriver_Locked() EXCLUSIVE_LOCKS_REQUIRED(va_lock_); + + int refcount_ GUARDED_BY(va_lock_); + + // Libva is not thread safe, so we have to do locking for it ourselves. + // This lock is to be taken for the duration of all VA-API calls and for + // the entire job submission sequence in ExecuteAndDestroyPendingBuffers(). + base::Lock va_lock_; + + // Drm fd used to obtain access to the driver interface by VA. + base::ScopedFD drm_fd_; + + // The VADisplay handle. Valid between Initialize() and Deinitialize(). + VADisplay va_display_; + + // True if vaInitialize() has been called successfully, until Deinitialize(). + bool va_initialized_; + + // Enumerated version of vaQueryVendorString(). Valid after Initialize(). + VAImplementation implementation_type_ = VAImplementation::kInvalid; +}; + +// static +VADisplayState* VADisplayState::Get() { + static base::NoDestructor display_state; + return display_state.get(); +} + +// static +void VADisplayState::PreSandboxInitialization() { + const char kDriRenderNode0Path[] = "/dev/dri/renderD128"; + base::File drm_file = base::File( + base::FilePath::FromUTF8Unsafe(kDriRenderNode0Path), + base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE); + if (drm_file.IsValid()) + VADisplayState::Get()->SetDrmFd(drm_file.GetPlatformFile()); + const char kNvidiaPath[] = "/dev/dri/nvidiactl"; + base::File nvidia_file = base::File( + base::FilePath::FromUTF8Unsafe(kNvidiaPath), + base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE); +} + +VADisplayState::VADisplayState() + : refcount_(0), va_display_(nullptr), va_initialized_(false) {} + +bool VADisplayState::Initialize() { + base::AutoLock auto_lock(va_lock_); + +#if defined(USE_OZONE) && defined(OS_LINUX) + // TODO(crbug.com/1116701): add vaapi support for other Ozone platforms on + // Linux. See comment in OzonePlatform::PlatformProperties::supports_vaapi + // for more details. This will also require revisiting everything that's + // guarded by USE_VAAPI_X11. For example, if USE_VAAPI_X11 is true, but the + // user chooses the Wayland backend for Ozone at runtime, then many things (if + // not all) that we do for X11 won't apply. + if (!ui::OzonePlatform::GetInstance()->GetPlatformProperties().supports_vaapi) + return false; +#endif + + bool libraries_initialized = IsVaInitialized() && IsVa_drmInitialized(); +#if BUILDFLAG(USE_VAAPI_X11) + libraries_initialized = libraries_initialized && IsVa_x11Initialized(); +#endif + if (!libraries_initialized) + return false; + + // Manual refcounting to ensure the rest of the method is called only once. + if (refcount_++ > 0) + return true; + + const bool success = InitializeOnce(); + UMA_HISTOGRAM_BOOLEAN("Media.VaapiWrapper.VADisplayStateInitializeSuccess", + success); + return success; +} + +#if BUILDFLAG(USE_VAAPI_X11) + +absl::optional GetVADisplayStateX11(const base::ScopedFD& drm_fd) { + switch (gl::GetGLImplementation()) { + case gl::kGLImplementationEGLGLES2: + return vaGetDisplayDRM(drm_fd.get()); + + case gl::kGLImplementationNone: + + case gl::kGLImplementationDesktopGL: { + VADisplay display = + vaGetDisplay(x11::Connection::Get()->GetXlibDisplay()); + if (vaDisplayIsValid(display)) + return display; + return vaGetDisplayDRM(drm_fd.get()); + } + + case gl::kGLImplementationEGLANGLE: + return vaGetDisplay(x11::Connection::Get()->GetXlibDisplay()); + + default: + LOG(WARNING) << "VAAPI video acceleration not available for " + << gl::GetGLImplementationGLName( + gl::GetGLImplementationParts()); + return absl::nullopt; + } +} + +#else + +absl::optional GetVADisplayState(const base::ScopedFD& drm_fd) { + switch (gl::GetGLImplementation()) { + case gl::kGLImplementationEGLGLES2: + case gl::kGLImplementationNone: + return vaGetDisplayDRM(drm_fd.get()); + default: + LOG(WARNING) << "VAAPI video acceleration not available for " + << gl::GetGLImplementationGLName( + gl::GetGLImplementationParts()); + return absl::nullopt; + } +} + +#endif // BUILDFLAG(USE_VAAPI_X11) + +bool VADisplayState::InitializeVaDisplay_Locked() { + absl::optional display = +#if BUILDFLAG(USE_VAAPI_X11) + GetVADisplayStateX11(drm_fd_); +#else + GetVADisplayState(drm_fd_); +#endif + + if (!display) + return false; + + va_display_ = *display; + if (!vaDisplayIsValid(va_display_)) { + LOG(ERROR) << "Could not get a valid VA display"; + return false; + } + + return true; +} + +bool VADisplayState::InitializeVaDriver_Locked() { + // The VAAPI version. + int major_version, minor_version; + VAStatus va_res = vaInitialize(va_display_, &major_version, &minor_version); + if (va_res != VA_STATUS_SUCCESS) { + VLOGF(1) << "vaInitialize failed: " << vaErrorStr(va_res); + return false; + } + const std::string va_vendor_string = vaQueryVendorString(va_display_); + DLOG_IF(WARNING, va_vendor_string.empty()) + << "Vendor string empty or error reading."; + DVLOG(1) << "VAAPI version: " << major_version << "." << minor_version << " " + << va_vendor_string; + implementation_type_ = VendorStringToImplementationType(va_vendor_string); + + va_initialized_ = true; + + // The VAAPI version is determined from what is loaded on the system by + // calling vaInitialize(). Since the libva is now ABI-compatible, relax the + // version check which helps in upgrading the libva, without breaking any + // existing functionality. Make sure the system version is not older than + // the version with which the chromium is built since libva is only + // guaranteed to be backward (and not forward) compatible. + if (VA_MAJOR_VERSION > major_version || + (VA_MAJOR_VERSION == major_version && VA_MINOR_VERSION > minor_version)) { + VLOGF(1) << "The system version " << major_version << "." << minor_version + << " should be greater than or equal to " << VA_MAJOR_VERSION + << "." << VA_MINOR_VERSION; + return false; + } + return true; +} + +bool VADisplayState::InitializeOnce() { + // Set VA logging level, unless already set. + constexpr char libva_log_level_env[] = "LIBVA_MESSAGING_LEVEL"; + std::unique_ptr env(base::Environment::Create()); + if (!env->HasVar(libva_log_level_env)) + env->SetVar(libva_log_level_env, "1"); + + if (!InitializeVaDisplay_Locked() || !InitializeVaDriver_Locked()) + return false; + +#if BUILDFLAG(USE_VAAPI_X11) + if (gl::GetGLImplementation() == gl::kGLImplementationEGLANGLE && + implementation_type_ == VAImplementation::kIntelIHD) { + constexpr char libva_driver_impl_env[] = "LIBVA_DRIVER_NAME"; + // TODO(crbug/1116703) The libva intel-media driver has a known segfault in + // vaPutSurface, so until this is fixed, fall back to the i965 driver. There + // is discussion of the issue here: + // https://github.com/intel/media-driver/issues/818 + if (!env->HasVar(libva_driver_impl_env)) + env->SetVar(libva_driver_impl_env, "i965"); + // Re-initialize with the new driver. + va_display_ = nullptr; + va_initialized_ = false; + implementation_type_ = VAImplementation::kInvalid; + + if (!InitializeVaDisplay_Locked() || !InitializeVaDriver_Locked()) + return false; + } +#endif // BUILDFLAG(USE_VAAPI_X11) + + 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) { + va_lock->AssertAcquired(); + + // 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) { + va_lock->AssertAcquired(); + + 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) { + va_lock->AssertAcquired(); + + // Choose a suitable VAConfigAttribRTFormat for every |mode|. For video + // processing, the supported surface attribs may vary according to which RT + // format is set. + if (profile == VAProfileVP9Profile2 || profile == VAProfileVP9Profile3) { + required_attribs->push_back( + {VAConfigAttribRTFormat, VA_RT_FORMAT_YUV420_10BPP}); +#if BUILDFLAG(IS_CHROMEOS_ASH) + } else if (profile == VAProfileProtected) { + DCHECK_EQ(mode, VaapiWrapper::kDecodeProtected); + constexpr int kWidevineUsage = 0x1; + required_attribs->push_back( + {VAConfigAttribProtectedContentUsage, kWidevineUsage}); + required_attribs->push_back( + {VAConfigAttribProtectedContentCipherAlgorithm, VA_PC_CIPHER_AES}); + required_attribs->push_back( + {VAConfigAttribProtectedContentCipherBlockSize, VA_PC_BLOCK_SIZE_128}); + required_attribs->push_back( + {VAConfigAttribProtectedContentCipherMode, VA_PC_CIPHER_MODE_CTR}); +#endif + } else { + required_attribs->push_back({VAConfigAttribRTFormat, VA_RT_FORMAT_YUV420}); + } + +#if BUILDFLAG(IS_CHROMEOS_ASH) + if (mode == VaapiWrapper::kDecodeProtected && profile != VAProfileProtected) { + required_attribs->push_back( + {VAConfigAttribEncryption, VA_ENCRYPTION_TYPE_SUBSAMPLE_CTR}); + } +#endif + + if (!IsModeEncoding(mode)) + return true; + + if (profile == VAProfileJPEGBaseline) + return true; + + if (mode == VaapiWrapper::kEncodeConstantBitrate) + required_attribs->push_back({VAConfigAttribRateControl, VA_RC_CBR}); + if (mode == VaapiWrapper::kEncodeConstantQuantizationParameter) + required_attribs->push_back({VAConfigAttribRateControl, VA_RC_CQP}); + + constexpr VAProfile kSupportedH264VaProfilesForEncoding[] = { + VAProfileH264ConstrainedBaseline, VAProfileH264Main, VAProfileH264High}; + // VAConfigAttribEncPackedHeaders is H.264 specific. + if (base::Contains(kSupportedH264VaProfilesForEncoding, profile)) { + // Encode with Packed header if the driver supports. + VAConfigAttrib attrib{}; + attrib.type = VAConfigAttribEncPackedHeaders; + const VAStatus va_res = + vaGetConfigAttributes(va_display, profile, entrypoint, &attrib, 1); + if (va_res != VA_STATUS_SUCCESS) { + LOG(ERROR) << "vaGetConfigAttributes failed: " << vaProfileStr(profile); + return false; + } + + const uint32_t packed_header_attributes = + (VA_ENC_PACKED_HEADER_SEQUENCE | VA_ENC_PACKED_HEADER_PICTURE | + VA_ENC_PACKED_HEADER_SLICE); + if ((packed_header_attributes & attrib.value) == packed_header_attributes) { + required_attribs->push_back( + {VAConfigAttribEncPackedHeaders, packed_header_attributes}); + } else { + required_attribs->push_back( + {VAConfigAttribEncPackedHeaders, VA_ENC_PACKED_HEADER_NONE}); + } + } + return true; +} + +// Returns true if |va_profile| for |entrypoint| with |required_attribs| is +// supported. +bool AreAttribsSupported(const base::Lock* va_lock, + VADisplay va_display, + VAProfile va_profile, + VAEntrypoint entrypoint, + const std::vector& required_attribs) { + va_lock->AssertAcquired(); + // Query the driver for required attributes. + std::vector attribs = required_attribs; + for (size_t i = 0; i < required_attribs.size(); ++i) + attribs[i].value = 0; + + VAStatus va_res = vaGetConfigAttributes(va_display, va_profile, entrypoint, + &attribs[0], attribs.size()); + if (va_res != VA_STATUS_SUCCESS) { + LOG(ERROR) << "vaGetConfigAttributes failed error: " << vaErrorStr(va_res); + return false; + } + for (size_t i = 0; i < required_attribs.size(); ++i) { + if (attribs[i].type != required_attribs[i].type || + (attribs[i].value & required_attribs[i].value) != + required_attribs[i].value) { + VLOG(1) << "Unsupported value " << required_attribs[i].value << " for " + << vaConfigAttribTypeStr(required_attribs[i].type); + return false; + } + } + return true; +} + +// This class encapsulates reading and giving access to the list of supported +// ProfileInfo entries, in a singleton way. +class VASupportedProfiles { + public: + struct ProfileInfo { + VAProfile va_profile; + VAEntrypoint va_entrypoint; + gfx::Size min_resolution; + gfx::Size max_resolution; + std::vector pixel_formats; + VaapiWrapper::InternalFormats supported_internal_formats; + }; + static const VASupportedProfiles& Get(); + + VASupportedProfiles(const VASupportedProfiles&) = delete; + VASupportedProfiles& operator=(const VASupportedProfiles&) = delete; + + // Determines if |mode| supports |va_profile| (and |va_entrypoint| if defined + // and valid). If so, returns a const pointer to its ProfileInfo, otherwise + // returns nullptr. + const ProfileInfo* IsProfileSupported( + VaapiWrapper::CodecMode mode, + VAProfile va_profile, + VAEntrypoint va_entrypoint = kVAEntrypointInvalid) const; + + private: + friend class base::NoDestructor; + + friend std::map> + VaapiWrapper::GetSupportedConfigurationsForCodecModeForTesting( + CodecMode mode); + + VASupportedProfiles(); + ~VASupportedProfiles() = default; + + // Fills in |supported_profiles_|. + void FillSupportedProfileInfos(base::Lock* va_lock, VADisplay va_display); + + // Fills |profile_info| for |va_profile| and |entrypoint| with + // |required_attribs|. If the return value is true, the operation was + // successful. Otherwise, the information in *|profile_info| shouldn't be + // relied upon. + bool FillProfileInfo_Locked(const base::Lock* va_lock, + VADisplay va_display, + VAProfile va_profile, + VAEntrypoint entrypoint, + std::vector& required_attribs, + ProfileInfo* profile_info) const; + + std::vector supported_profiles_[VaapiWrapper::kCodecModeMax]; + static_assert(std::extent() == + VaapiWrapper::kCodecModeMax, + "|supported_profiles_| size is incorrect."); + + const ReportErrorToUMACB report_error_to_uma_cb_; +}; + +// static +const VASupportedProfiles& VASupportedProfiles::Get() { + static const base::NoDestructor profile_infos; + return *profile_infos; +} + +const VASupportedProfiles::ProfileInfo* VASupportedProfiles::IsProfileSupported( + VaapiWrapper::CodecMode mode, + VAProfile va_profile, + VAEntrypoint va_entrypoint) const { + auto iter = std::find_if( + supported_profiles_[mode].begin(), supported_profiles_[mode].end(), + [va_profile, va_entrypoint](const ProfileInfo& profile) { + return profile.va_profile == va_profile && + (va_entrypoint == kVAEntrypointInvalid || + profile.va_entrypoint == va_entrypoint); + }); + if (iter != supported_profiles_[mode].end()) + return &*iter; + return nullptr; +} + +VASupportedProfiles::VASupportedProfiles() + : report_error_to_uma_cb_(base::DoNothing()) { + VADisplayState* display_state = VADisplayState::Get(); + if (!display_state->Initialize()) + return; + + VADisplay va_display = display_state->va_display(); + DCHECK(va_display) << "VADisplayState hasn't been properly Initialize()d"; + + FillSupportedProfileInfos(VADisplayState::Get()->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::AutoLock auto_lock(*va_lock); + + const std::vector va_profiles = + GetSupportedVAProfiles(va_lock, va_display); + + constexpr VaapiWrapper::CodecMode kWrapperModes[] = { + VaapiWrapper::kDecode, +#if BUILDFLAG(IS_CHROMEOS_ASH) + VaapiWrapper::kDecodeProtected, +#endif + VaapiWrapper::kEncodeConstantBitrate, + VaapiWrapper::kEncodeConstantQuantizationParameter, + VaapiWrapper::kVideoProcess + }; + static_assert(base::size(kWrapperModes) == VaapiWrapper::kCodecModeMax, ""); + + for (VaapiWrapper::CodecMode mode : kWrapperModes) { + std::vector supported_profile_infos; + + for (const auto& va_profile : va_profiles) { + if (IsBlockedDriver(mode, va_profile)) + continue; + + if ((mode != VaapiWrapper::kVideoProcess) && + !IsVAProfileSupported(va_profile)) { + continue; + } + + const std::vector supported_entrypoints = + GetEntryPointsForProfile(va_lock, va_display, mode, va_profile); + + for (const auto& entrypoint : supported_entrypoints) { + std::vector required_attribs; + if (!GetRequiredAttribs(va_lock, va_display, mode, va_profile, + entrypoint, &required_attribs)) { + continue; + } + if (!AreAttribsSupported(va_lock, va_display, va_profile, entrypoint, + required_attribs)) { + continue; + } + ProfileInfo profile_info{}; + if (!FillProfileInfo_Locked(va_lock, va_display, va_profile, entrypoint, + required_attribs, &profile_info)) { + LOG(ERROR) << "FillProfileInfo_Locked failed for va_profile " + << vaProfileStr(va_profile) << " and entrypoint " + << vaEntrypointStr(entrypoint); + continue; + } + + supported_profile_infos.push_back(profile_info); + } + } + supported_profiles_[static_cast(mode)] = supported_profile_infos; + } +} + +bool VASupportedProfiles::FillProfileInfo_Locked( + const base::Lock* va_lock, + VADisplay va_display, + VAProfile va_profile, + VAEntrypoint entrypoint, + std::vector& required_attribs, + ProfileInfo* profile_info) const { + va_lock->AssertAcquired(); + VAConfigID va_config_id; + VAStatus va_res = + vaCreateConfig(va_display, va_profile, entrypoint, &required_attribs[0], + required_attribs.size(), &va_config_id); + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateConfig, false); + base::ScopedClosureRunner vaconfig_destroyer(base::BindOnce( + [](VADisplay display, VAConfigID id) { + if (id != VA_INVALID_ID) { + VAStatus va_res = vaDestroyConfig(display, id); + if (va_res != VA_STATUS_SUCCESS) + LOG(ERROR) << "vaDestroyConfig failed. VA error: " + << vaErrorStr(va_res); + } + }, + va_display, va_config_id)); + +#if BUILDFLAG(IS_CHROMEOS_ASH) + // Nothing further to query for protected profile. + if (va_profile == VAProfileProtected) { + profile_info->va_profile = va_profile; + profile_info->va_entrypoint = entrypoint; + return true; + } +#endif + + // Calls vaQuerySurfaceAttributes twice. The first time is to get the number + // of attributes to prepare the space and the second time is to get all + // attributes. + unsigned int num_attribs; + va_res = + vaQuerySurfaceAttributes(va_display, va_config_id, nullptr, &num_attribs); + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAQuerySurfaceAttributes, + false); + if (!num_attribs) + return false; + + std::vector attrib_list( + base::checked_cast(num_attribs)); + + va_res = vaQuerySurfaceAttributes(va_display, va_config_id, &attrib_list[0], + &num_attribs); + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAQuerySurfaceAttributes, + false); + + profile_info->va_profile = va_profile; + profile_info->va_entrypoint = entrypoint; + profile_info->min_resolution = gfx::Size(); + profile_info->max_resolution = gfx::Size(); + for (const auto& attrib : attrib_list) { + if (attrib.type == VASurfaceAttribMaxWidth) { + profile_info->max_resolution.set_width( + base::strict_cast(attrib.value.value.i)); + } else if (attrib.type == VASurfaceAttribMaxHeight) { + profile_info->max_resolution.set_height( + base::strict_cast(attrib.value.value.i)); + } else if (attrib.type == VASurfaceAttribMinWidth) { + profile_info->min_resolution.set_width( + base::strict_cast(attrib.value.value.i)); + } else if (attrib.type == VASurfaceAttribMinHeight) { + profile_info->min_resolution.set_height( + base::strict_cast(attrib.value.value.i)); + } else if (attrib.type == VASurfaceAttribPixelFormat) { + // According to va.h, VASurfaceAttribPixelFormat is meaningful as input to + // vaQuerySurfaceAttributes(). However, per the implementation of + // i965_QuerySurfaceAttributes(), our usage here should enumerate all the + // formats. + profile_info->pixel_formats.push_back(attrib.value.value.i); + } + } + if (profile_info->max_resolution.IsEmpty()) { + LOG(ERROR) << "Empty codec maximum resolution"; + return false; + } + + if (va_profile != VAProfileJPEGBaseline) { + // Set a reasonable minimum value for both encoding and decoding. + profile_info->min_resolution.SetToMax(gfx::Size(16, 16)); + + const bool is_encoding = entrypoint == VAEntrypointEncSliceLP || + entrypoint == VAEntrypointEncSlice; + const bool is_hybrid_decoding = entrypoint == VAEntrypointVLD && + IsUsingHybridDriverForDecoding(va_profile); + + // Using HW encoding for small resolutions is less efficient than using a SW + // encoder. Similarly, using the intel-hybrid-driver for decoding is less + // efficient than using a SW decoder. In both cases, increase + // |min_resolution| to QVGA + 1 which is an experimental lower threshold. + // This can be turned off with kVaapiVideoMinResolutionForPerformance for + // testing. + if ((is_encoding || is_hybrid_decoding) && + base::FeatureList::IsEnabled(kVaapiVideoMinResolutionForPerformance)) { + constexpr gfx::Size kMinVideoResolution(320 + 1, 240 + 1); + profile_info->min_resolution.SetToMax(kMinVideoResolution); + DVLOG(2) << "Setting the minimum supported resolution for " + << vaProfileStr(va_profile) + << (is_encoding ? " encoding" : " decoding") << " to " + << profile_info->min_resolution.ToString(); + } + } + + // Create a new configuration to find the supported RT formats. We don't pass + // required attributes here because we want the driver to tell us all the + // supported RT formats. + va_res = vaCreateConfig(va_display, va_profile, entrypoint, nullptr, 0, + &va_config_id); + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateConfig, false); + base::ScopedClosureRunner vaconfig_no_attribs_destroyer(base::BindOnce( + [](VADisplay display, VAConfigID id) { + if (id != VA_INVALID_ID) { + VAStatus va_res = vaDestroyConfig(display, id); + if (va_res != VA_STATUS_SUCCESS) + LOG(ERROR) << "vaDestroyConfig failed. VA error: " + << vaErrorStr(va_res); + } + }, + va_display, va_config_id)); + profile_info->supported_internal_formats = {}; + size_t max_num_config_attributes; + if (!base::CheckedNumeric(vaMaxNumConfigAttributes(va_display)) + .AssignIfValid(&max_num_config_attributes)) { + LOG(ERROR) << "Can't get the maximum number of config attributes"; + return false; + } + std::vector config_attributes(max_num_config_attributes); + int num_config_attributes; + va_res = vaQueryConfigAttributes(va_display, va_config_id, &va_profile, + &entrypoint, config_attributes.data(), + &num_config_attributes); + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAQueryConfigAttributes, false); + for (int i = 0; i < num_config_attributes; i++) { + const VAConfigAttrib& attrib = config_attributes[i]; + if (attrib.type != VAConfigAttribRTFormat) + continue; + if (attrib.value & VA_RT_FORMAT_YUV420) + profile_info->supported_internal_formats.yuv420 = true; + if (attrib.value & VA_RT_FORMAT_YUV420_10) + profile_info->supported_internal_formats.yuv420_10 = true; + if (attrib.value & VA_RT_FORMAT_YUV422) + profile_info->supported_internal_formats.yuv422 = true; + if (attrib.value & VA_RT_FORMAT_YUV444) + profile_info->supported_internal_formats.yuv444 = true; + break; + } + + // Now work around some driver misreporting for JPEG decoding. + if (va_profile == VAProfileJPEGBaseline && entrypoint == VAEntrypointVLD) { + if (VADisplayState::Get()->implementation_type() == + VAImplementation::kMesaGallium) { + // TODO(andrescj): the VAAPI state tracker in mesa does not report + // VA_RT_FORMAT_YUV422 as being supported for JPEG decoding. However, it + // is happy to allocate YUYV surfaces + // (https://gitlab.freedesktop.org/mesa/mesa/commit/5608f442). Remove this + // workaround once b/128337341 is resolved. + profile_info->supported_internal_formats.yuv422 = true; + } + } + const bool is_any_profile_supported = + profile_info->supported_internal_formats.yuv420 || + profile_info->supported_internal_formats.yuv420_10 || + profile_info->supported_internal_formats.yuv422 || + profile_info->supported_internal_formats.yuv444; + DLOG_IF(ERROR, !is_any_profile_supported) + << "No cool internal formats supported"; + return is_any_profile_supported; +} + +void DestroyVAImage(VADisplay va_display, VAImage image) { + if (image.image_id != VA_INVALID_ID) + vaDestroyImage(va_display, image.image_id); +} + +// This class encapsulates fetching the list of supported output image formats +// from the VAAPI driver, in a singleton way. +class VASupportedImageFormats { + public: + static const VASupportedImageFormats& Get(); + + VASupportedImageFormats(const VASupportedImageFormats&) = delete; + VASupportedImageFormats& operator=(const VASupportedImageFormats&) = delete; + + bool IsImageFormatSupported(const VAImageFormat& va_format) const; + + const std::vector& GetSupportedImageFormats() const; + + private: + friend class base::NoDestructor; + + VASupportedImageFormats(); + ~VASupportedImageFormats() = default; + + // Initialize the list of supported image formats. + bool InitSupportedImageFormats_Locked() EXCLUSIVE_LOCKS_REQUIRED(va_lock_); + + // Pointer to VADisplayState's members |va_lock_| and its |va_display_|. + base::Lock* va_lock_; + VADisplay va_display_ GUARDED_BY(va_lock_); + + 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() + : va_lock_(VADisplayState::Get()->va_lock()), + report_error_to_uma_cb_(base::DoNothing()) { + VADisplayState* display_state = VADisplayState::Get(); + if (!display_state->Initialize()) + return; + + { + base::AutoLock auto_lock(*va_lock_); + va_display_ = display_state->va_display(); + DCHECK(va_display_) << "VADisplayState hasn't been properly initialized"; + + if (!InitSupportedImageFormats_Locked()) + LOG(ERROR) << "Failed to get supported image formats"; + } + + const VAStatus va_res = display_state->Deinitialize(); + VA_LOG_ON_ERROR(va_res, VaapiFunctions::kVATerminate); +} + +bool VASupportedImageFormats::InitSupportedImageFormats_Locked() { + va_lock_->AssertAcquired(); + + // Query the driver for the max number of image formats and allocate space. + const int max_image_formats = vaMaxNumImageFormats(va_display_); + if (max_image_formats < 0) { + LOG(ERROR) << "vaMaxNumImageFormats returned: " << max_image_formats; + return false; + } + supported_formats_.resize(static_cast(max_image_formats)); + + // Query the driver for the list of supported image formats. + int num_image_formats; + const VAStatus va_res = vaQueryImageFormats( + va_display_, supported_formats_.data(), &num_image_formats); + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAQueryImageFormats, false); + if (num_image_formats < 0 || num_image_formats > max_image_formats) { + LOG(ERROR) << "vaQueryImageFormats returned: " << num_image_formats; + supported_formats_.clear(); + return false; + } + + // Resize the list to the actual number of formats returned by the driver. + supported_formats_.resize(static_cast(num_image_formats)); + + // Now work around some driver misreporting. + if (VADisplayState::Get()->implementation_type() == + VAImplementation::kMesaGallium) { + // TODO(andrescj): considering that the VAAPI state tracker in mesa can + // convert from NV12 to IYUV when doing vaGetImage(), it's reasonable to + // assume that IYUV/I420 is supported. However, it's not currently being + // reported. See https://gitlab.freedesktop.org/mesa/mesa/commit/b0a44f10. + // Remove this workaround once b/128340287 is resolved. + if (std::find_if(supported_formats_.cbegin(), supported_formats_.cend(), + [](const VAImageFormat& format) { + return format.fourcc == VA_FOURCC_I420; + }) == supported_formats_.cend()) { + VAImageFormat i420_format{}; + i420_format.fourcc = VA_FOURCC_I420; + supported_formats_.push_back(i420_format); + } + } + return true; +} + +bool IsLowPowerEncSupported(VAProfile va_profile) { + constexpr VAProfile kSupportedLowPowerEncodeProfiles[] = { + VAProfileH264ConstrainedBaseline, + VAProfileH264Main, + VAProfileH264High, + VAProfileVP9Profile0, + VAProfileVP9Profile2}; + if (!base::Contains(kSupportedLowPowerEncodeProfiles, va_profile)) + return false; + + if ((IsGen95Gpu() || IsGen9Gpu()) && + !base::FeatureList::IsEnabled(kVaapiLowPowerEncoderGen9x)) { + return false; + } + + if (VASupportedProfiles::Get().IsProfileSupported( + VaapiWrapper::kEncodeConstantBitrate, va_profile, + VAEntrypointEncSliceLP)) { + return true; + } + return false; +} + +} // namespace + +NativePixmapAndSizeInfo::NativePixmapAndSizeInfo() = default; + +NativePixmapAndSizeInfo::~NativePixmapAndSizeInfo() = default; + +// static +VAImplementation VaapiWrapper::GetImplementationType() { + return VADisplayState::Get()->implementation_type(); +} + +// static +scoped_refptr VaapiWrapper::Create( + CodecMode mode, + VAProfile va_profile, + EncryptionScheme encryption_scheme, + const ReportErrorToUMACB& report_error_to_uma_cb) { + 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)); + 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) { + const VAProfile va_profile = ProfileToVAProfile(profile, mode); + return Create(mode, va_profile, encryption_scheme, report_error_to_uma_cb); +} + +// static +std::vector VaapiWrapper::GetSupportedScalabilityModes( + VideoCodecProfile media_profile, + VAProfile va_profile) { + std::vector scalability_modes; +#if BUILDFLAG(IS_CHROMEOS_ASH) + if (media_profile == VP9PROFILE_PROFILE0) { + scalability_modes.push_back(SVCScalabilityMode::kL1T2); + scalability_modes.push_back(SVCScalabilityMode::kL1T3); + if (base::FeatureList::IsEnabled(kVaapiVp9kSVCHWEncoding) && + GetDefaultVaEntryPoint( + VaapiWrapper::kEncodeConstantQuantizationParameter, va_profile) == + VAEntrypointEncSliceLP) { + scalability_modes.push_back(SVCScalabilityMode::kL2T2Key); + scalability_modes.push_back(SVCScalabilityMode::kL2T3Key); + scalability_modes.push_back(SVCScalabilityMode::kL3T2Key); + scalability_modes.push_back(SVCScalabilityMode::kL3T3Key); + } + } + + if (media_profile >= H264PROFILE_MIN && media_profile <= H264PROFILE_MAX) { + // TODO(b/199487660): Enable H.264 temporal layer encoding on AMD once their + // drivers support them. + VAImplementation implementation = VaapiWrapper::GetImplementationType(); + if (base::FeatureList::IsEnabled(kVaapiH264TemporalLayerHWEncoding) && + (implementation == VAImplementation::kIntelI965 || + implementation == VAImplementation::kIntelIHD)) { + scalability_modes.push_back(SVCScalabilityMode::kL1T2); + scalability_modes.push_back(SVCScalabilityMode::kL1T3); + } + } +#endif + return scalability_modes; +} + +// static +VideoEncodeAccelerator::SupportedProfiles +VaapiWrapper::GetSupportedEncodeProfiles() { + VideoEncodeAccelerator::SupportedProfiles profiles; + + for (const auto& media_to_va_profile_map_entry : GetProfileCodecMap()) { + const VideoCodecProfile media_profile = media_to_va_profile_map_entry.first; + const VAProfile va_profile = media_to_va_profile_map_entry.second; + DCHECK(va_profile != VAProfileNone); + + const VASupportedProfiles::ProfileInfo* profile_info = + VASupportedProfiles::Get().IsProfileSupported(kEncodeConstantBitrate, + va_profile); + if (!profile_info) + continue; + + VideoEncodeAccelerator::SupportedProfile profile; + profile.profile = media_profile; + profile.min_resolution = profile_info->min_resolution; + profile.max_resolution = profile_info->max_resolution; + // Maximum framerate of encoded profile. This value is an arbitrary + // limit and not taken from HW documentation. + constexpr int kMaxEncoderFramerate = 30; + profile.max_framerate_numerator = kMaxEncoderFramerate; + profile.max_framerate_denominator = 1; + profile.scalability_modes = + GetSupportedScalabilityModes(media_profile, va_profile); + profiles.push_back(profile); + } + return profiles; +} + +// static +VideoDecodeAccelerator::SupportedProfiles +VaapiWrapper::GetSupportedDecodeProfiles() { + VideoDecodeAccelerator::SupportedProfiles profiles; + + for (const auto& media_to_va_profile_map_entry : GetProfileCodecMap()) { + const VideoCodecProfile media_profile = media_to_va_profile_map_entry.first; + const VAProfile va_profile = media_to_va_profile_map_entry.second; + DCHECK(va_profile != VAProfileNone); + + const VASupportedProfiles::ProfileInfo* profile_info = + VASupportedProfiles::Get().IsProfileSupported(kDecode, va_profile); + if (!profile_info) + continue; + + VideoDecodeAccelerator::SupportedProfile profile; + profile.profile = media_profile; + profile.max_resolution = profile_info->max_resolution; + profile.min_resolution = profile_info->min_resolution; + profiles.push_back(profile); + } + return profiles; +} + +// static +bool VaapiWrapper::IsDecodeSupported(VAProfile va_profile) { + return VASupportedProfiles::Get().IsProfileSupported(kDecode, va_profile); +} + +// static +VaapiWrapper::InternalFormats VaapiWrapper::GetDecodeSupportedInternalFormats( + VAProfile va_profile) { + const VASupportedProfiles::ProfileInfo* profile_info = + VASupportedProfiles::Get().IsProfileSupported(kDecode, va_profile); + if (!profile_info) + return InternalFormats{}; + return profile_info->supported_internal_formats; +} + +// static +bool VaapiWrapper::IsDecodingSupportedForInternalFormat( + VAProfile va_profile, + unsigned int rt_format) { + static const VaapiWrapper::InternalFormats supported_internal_formats( + VaapiWrapper::GetDecodeSupportedInternalFormats(va_profile)); + switch (rt_format) { + case VA_RT_FORMAT_YUV420: + return supported_internal_formats.yuv420; + case VA_RT_FORMAT_YUV420_10: + return supported_internal_formats.yuv420_10; + case VA_RT_FORMAT_YUV422: + return supported_internal_formats.yuv422; + case VA_RT_FORMAT_YUV444: + return supported_internal_formats.yuv444; + } + return false; +} + +// static +bool VaapiWrapper::GetDecodeMinResolution(VAProfile va_profile, + gfx::Size* min_size) { + const VASupportedProfiles::ProfileInfo* profile_info = + VASupportedProfiles::Get().IsProfileSupported(kDecode, va_profile); + if (!profile_info) + return false; + *min_size = gfx::Size(std::max(1, profile_info->min_resolution.width()), + std::max(1, profile_info->min_resolution.height())); + return true; +} + +// static +bool VaapiWrapper::GetDecodeMaxResolution(VAProfile va_profile, + gfx::Size* max_size) { + const VASupportedProfiles::ProfileInfo* profile_info = + VASupportedProfiles::Get().IsProfileSupported(kDecode, va_profile); + if (!profile_info) + return false; + + *max_size = profile_info->max_resolution; + return true; +} + +// static +bool VaapiWrapper::GetJpegDecodeSuitableImageFourCC(unsigned int rt_format, + uint32_t preferred_fourcc, + uint32_t* suitable_fourcc) { + if (!IsDecodingSupportedForInternalFormat(VAProfileJPEGBaseline, rt_format)) + return false; + + // Work around some driver-specific conversion issues. If you add a workaround + // here, please update the VaapiJpegDecoderTest.MinimalImageFormatSupport + // test. + DCHECK_NE(VAImplementation::kInvalid, GetImplementationType()); + if (GetImplementationType() == VAImplementation::kMesaGallium) { + // The VAAPI mesa state tracker only supports conversion from NV12 to YV12 + // and IYUV (synonym of I420). + if (rt_format == VA_RT_FORMAT_YUV420) { + if (preferred_fourcc != VA_FOURCC_I420 && + preferred_fourcc != VA_FOURCC_YV12) { + preferred_fourcc = VA_FOURCC_NV12; + } + } else if (rt_format == VA_RT_FORMAT_YUV422) { + preferred_fourcc = VA_FOURCC('Y', 'U', 'Y', 'V'); + } else { + // Out of the three internal formats we care about (4:2:0, 4:2:2, and + // 4:4:4), this driver should only support the first two. Since we check + // for supported internal formats at the beginning of this function, we + // shouldn't get here. + NOTREACHED(); + return false; + } + } else if (GetImplementationType() == VAImplementation::kIntelI965) { + // Workaround deduced from observations in samus and nocturne: we found that + // + // - For a 4:2:2 image, the internal format is 422H. + // - For a 4:2:0 image, the internal format is IMC3. + // - For a 4:4:4 image, the internal format is 444P. + // + // For these internal formats and an image format of either 422H or P010, an + // intermediate NV12 surface is allocated. Then, a conversion is made from + // {422H, IMC3, 444P} -> NV12 -> {422H, P010}. Unfortunately, the + // NV12 -> {422H, P010} conversion is unimplemented in + // i965_image_pl2_processing(). So, when |preferred_fourcc| is either 422H + // or P010, we can just fallback to I420. + // + if (preferred_fourcc == VA_FOURCC_422H || + preferred_fourcc == VA_FOURCC_P010) { + preferred_fourcc = VA_FOURCC_I420; + } + } else if (GetImplementationType() == VAImplementation::kIntelIHD) { + // TODO(b/155939640): iHD v19.4 fails to allocate AYUV surfaces for the VPP + // on gen 9.5. + // (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_AYUV || + 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(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(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(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::AutoLock 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::AutoLock 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(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(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::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + return va_protected_session_id_; +} +#endif + +void VaapiWrapper::DestroyProtectedSession() { + CHECK(sequence_checker_.CalledOnValidSequence()); +#if BUILDFLAG(IS_CHROMEOS_ASH) + if (va_protected_session_id_ == VA_INVALID_ID) + return; + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + DestroyContext(); + DestroySurfaces(va_surfaces); +} + +bool VaapiWrapper::CreateContext(const gfx::Size& size) { + CHECK(sequence_checker_.CalledOnValidSequence()); + DVLOG(2) << "Creating context"; + base::AutoLock 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; + + if (IsModeEncoding(mode_) && IsLowPowerIntelProcessor()) + 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(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::AutoLock auto_lock(*va_lock_); + VAStatus va_res = vaCreateSurfaces( + va_display_, va_format, base::checked_cast(size.width()), + base::checked_cast(size.height()), &va_surface_id, 1, + &va_attribs[0], va_attribs.size()); + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVACreateSurfaces_Importing, + nullptr); + } + DVLOG(2) << __func__ << " " << va_surface_id; + // VASurface shares an ownership of the buffer referred by the passed file + // descriptor. We can release |pixmap| here. + return new VASurface(va_surface_id, size, va_format, + base::BindOnce(&VaapiWrapper::DestroySurface, this)); +} + +scoped_refptr VaapiWrapper::CreateVASurfaceForUserPtr( + const gfx::Size& size, + uintptr_t* buffers, + size_t buffer_size) { + CHECK(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::AutoLock 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)); +} + +std::unique_ptr +VaapiWrapper::ExportVASurfaceAsNativePixmapDmaBuf( + const ScopedVASurface& scoped_va_surface) { + CHECK(sequence_checker_.CalledOnValidSequence()); + if (!scoped_va_surface.IsValid()) { + LOG(ERROR) << "Cannot export an invalid surface"; + return nullptr; + } + + if (GetImplementationType() == VAImplementation::kNVIDIAVDPAU) { + LOG(ERROR) << "Disabled due to potential breakage."; + return nullptr; + } + + VADRMPRIMESurfaceDescriptor descriptor; + { + base::AutoLock auto_lock(*va_lock_); + VAStatus va_res = vaSyncSurface(va_display_, scoped_va_surface.id()); + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVASyncSurface, nullptr); + va_res = vaExportSurfaceHandle( + va_display_, scoped_va_surface.id(), + VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, + VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS, + &descriptor); + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVAExportSurfaceHandle, + nullptr); + } + + // We only support one bo containing all the planes. The fd should be owned by + // us: per va/va.h, "the exported handles are owned by the caller." + // + // TODO(crbug.com/974438): support multiple buffer objects so that this can + // work in AMD. + if (descriptor.num_objects != 1u) { + DVLOG(1) << "Only surface descriptors with one bo are supported"; + NOTREACHED(); + return nullptr; + } + base::ScopedFD bo_fd(descriptor.objects[0].fd); + const uint64_t bo_modifier = descriptor.objects[0].drm_format_modifier; + + // Translate the pixel format to a gfx::BufferFormat. + gfx::BufferFormat buffer_format; + switch (descriptor.fourcc) { + case VA_FOURCC_IMC3: + // IMC3 is like I420 but all the planes have the same stride. This is used + // for decoding 4:2:0 JPEGs in the Intel i965 driver. We don't currently + // have a gfx::BufferFormat for YUV420. Instead, we reuse YVU_420 and + // later swap the U and V planes. + // + // TODO(andrescj): revisit this once crrev.com/c/1573718 lands. + buffer_format = gfx::BufferFormat::YVU_420; + break; + case VA_FOURCC_NV12: + buffer_format = gfx::BufferFormat::YUV_420_BIPLANAR; + break; + default: + LOG(ERROR) << "Cannot export a surface with FOURCC " + << FourccToString(descriptor.fourcc); + return nullptr; + } + + gfx::NativePixmapHandle handle{}; + handle.modifier = bo_modifier; + for (uint32_t layer = 0; layer < descriptor.num_layers; layer++) { + // According to va/va_drmcommon.h, if VA_EXPORT_SURFACE_SEPARATE_LAYERS is + // specified, each layer should contain one plane. + DCHECK_EQ(1u, descriptor.layers[layer].num_planes); + + // Strictly speaking, we only have to dup() the fd for the planes after the + // first one since we already own the first one, but we dup() regardless for + // simplicity: |bo_fd| will be closed at the end of this method anyway. + base::ScopedFD plane_fd(HANDLE_EINTR(dup(bo_fd.get()))); + PCHECK(plane_fd.is_valid()); + constexpr uint64_t kZeroSizeToPreventMapping = 0u; + handle.planes.emplace_back( + base::checked_cast(descriptor.layers[layer].pitch[0]), + base::checked_cast(descriptor.layers[layer].offset[0]), + kZeroSizeToPreventMapping, + base::ScopedFD(HANDLE_EINTR(dup(bo_fd.get())))); + } + + if (descriptor.fourcc == VA_FOURCC_IMC3) { + // Recall that for VA_FOURCC_IMC3, we will return a format of + // gfx::BufferFormat::YVU_420, so we need to swap the U and V planes to keep + // the semantics. + DCHECK_EQ(3u, handle.planes.size()); + std::swap(handle.planes[1], handle.planes[2]); + } + + auto exported_pixmap = std::make_unique(); + exported_pixmap->va_surface_resolution = + gfx::Size(base::checked_cast(descriptor.width), + base::checked_cast(descriptor.height)); + exported_pixmap->byte_size = + base::strict_cast(descriptor.objects[0].size); + if (!gfx::Rect(exported_pixmap->va_surface_resolution) + .Contains(gfx::Rect(scoped_va_surface.size()))) { + LOG(ERROR) << "A " << scoped_va_surface.size().ToString() + << " ScopedVASurface cannot be contained by a " + << exported_pixmap->va_surface_resolution.ToString() + << " buffer"; + return nullptr; + } + exported_pixmap->pixmap = base::MakeRefCounted( + scoped_va_surface.size(), buffer_format, std::move(handle)); + return exported_pixmap; +} + +bool VaapiWrapper::SyncSurface(VASurfaceID va_surface_id) { + CHECK(sequence_checker_.CalledOnValidSequence()); + DCHECK_NE(va_surface_id, VA_INVALID_ID); + + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + TRACE_EVENT0("media,gpu", "VaapiWrapper::SubmitBuffer"); + base::AutoLock auto_lock(*va_lock_); + return SubmitBuffer_Locked({va_buffer_type, size, data}); +} + +bool VaapiWrapper::SubmitBuffers( + const std::vector& va_buffers) { + CHECK(sequence_checker_.CalledOnValidSequence()); + TRACE_EVENT0("media,gpu", "VaapiWrapper::SubmitBuffers"); + base::AutoLock auto_lock(*va_lock_); + for (const VABufferDescriptor& va_buffer : va_buffers) { + if (!SubmitBuffer_Locked(va_buffer)) + return false; + } + return true; +} + +void VaapiWrapper::DestroyPendingBuffers() { + CHECK(sequence_checker_.CalledOnValidSequence()); + TRACE_EVENT0("media,gpu", "VaapiWrapper::DestroyPendingBuffers"); + base::AutoLock auto_lock(*va_lock_); + DestroyPendingBuffers_Locked(); +} + +void VaapiWrapper::DestroyPendingBuffers_Locked() { + CHECK(sequence_checker_.CalledOnValidSequence()); + TRACE_EVENT0("media,gpu", "VaapiWrapper::DestroyPendingBuffers_Locked"); + va_lock_->AssertAcquired(); + 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(sequence_checker_.CalledOnValidSequence()); + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + DCHECK_NE(va_surface_id, VA_INVALID_SURFACE); + + TRACE_EVENT0("media,gpu", "VaapiWrapper::MapAndCopyAndExecute"); + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + std::unique_ptr scoped_image; + { + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + TRACE_EVENT0("media,gpu", "VaapiWrapper::UploadVideoFrameToSurface"); + base::AutoLock 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"); + + base::AutoUnlock auto_unlock(*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) { + const VAStatus 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(sequence_checker_.CalledOnValidSequence()); + TRACE_EVENT0("media,gpu", "VaapiWrapper::CreateVABuffer"); + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + TRACE_EVENT0("media,gpu", "VaapiWrapper::GetEncodedChunkSize"); + base::AutoLock auto_lock(*va_lock_); + TRACE_EVENT0("media,gpu", "VaapiWrapper::GetEncodedChunkSizeLocked"); + VAStatus va_res = vaSyncSurface(va_display_, sync_surface_id); + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVASyncSurface, 0u); + + ScopedVABufferMapping mapping(va_lock_, va_display_, buffer_id); + if (!mapping.IsValid()) + return 0u; + + uint64_t coded_data_size = 0; + for (auto* buffer_segment = + reinterpret_cast(mapping.data()); + buffer_segment; buffer_segment = reinterpret_cast( + buffer_segment->next)) { + coded_data_size += buffer_segment->size; + } + return coded_data_size; +} + +bool VaapiWrapper::DownloadFromVABuffer(VABufferID buffer_id, + VASurfaceID sync_surface_id, + uint8_t* target_ptr, + size_t target_size, + size_t* coded_data_size) { + CHECK(sequence_checker_.CalledOnValidSequence()); + DCHECK(target_ptr); + TRACE_EVENT0("media,gpu", "VaapiWrapper::DownloadFromVABuffer"); + base::AutoLock auto_lock(*va_lock_); + TRACE_EVENT0("media,gpu", "VaapiWrapper::DownloadFromVABufferLocked"); + + // vaSyncSurface() is not necessary on Intel platforms as long as there is a + // vaMapBuffer() like in ScopedVABufferMapping below, see b/184312032. + if (GetImplementationType() != VAImplementation::kIntelI965 && + GetImplementationType() != VAImplementation::kIntelIHD) { + TRACE_EVENT0("media,gpu", "VaapiWrapper::DownloadFromVABuffer_SyncSurface"); + const VAStatus va_res = vaSyncSurface(va_display_, sync_surface_id); + VA_SUCCESS_OR_RETURN(va_res, VaapiFunctions::kVASyncSurface, false); + } + + ScopedVABufferMapping mapping(va_lock_, va_display_, buffer_id); + if (!mapping.IsValid()) + return false; + auto* buffer_segment = + reinterpret_cast(mapping.data()); + + // memcpy calls should be fast, unlocking and relocking for unmapping might + // cause another thread to acquire the lock and we'd have to wait delaying the + // notification that the encode is done. + { + TRACE_EVENT0("media,gpu", "VaapiWrapper::DownloadFromVABuffer_copy"); + *coded_data_size = 0; + + while (buffer_segment) { + DCHECK(buffer_segment->buf); + + if (buffer_segment->size > target_size) { + LOG(ERROR) << "Insufficient output buffer size: " << target_size + << ", the buffer segment size: " << buffer_segment->size; + break; + } + memcpy(target_ptr, buffer_segment->buf, buffer_segment->size); + + target_ptr += buffer_segment->size; + target_size -= buffer_segment->size; + *coded_data_size += buffer_segment->size; + buffer_segment = + reinterpret_cast(buffer_segment->next); + } + } + + return buffer_segment == nullptr; +} + +bool VaapiWrapper::GetVAEncMaxNumOfRefFrames(VideoCodecProfile profile, + size_t* max_ref_frames) { + CHECK(sequence_checker_.CalledOnValidSequence()); + const VAProfile va_profile = + ProfileToVAProfile(profile, CodecMode::kEncodeConstantBitrate); + VAConfigAttrib attrib; + attrib.type = VAConfigAttribEncMaxRefFrames; + + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + const VAProfile va_profile = + ProfileToVAProfile(profile, CodecMode::kEncodeConstantBitrate); + VAConfigAttrib attrib{}; + attrib.type = VAConfigAttribEncPackedHeaders; + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + DCHECK_EQ(mode_, kVideoProcess); + base::AutoLock 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"; +#if defined(OS_CHROMEOS) + // When Chrome runs on Linux with target_os="chromeos", do not log error + // message without VAAPI libraries. + LOG_IF(ERROR, base::SysInfo::IsRunningOnChromeOS()) << kErrorMsg; +#else + DVLOG(1) << kErrorMsg; +#endif + } + + // 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) + : mode_(mode), + va_lock_(VADisplayState::Get()->va_lock()), + va_display_(NULL), + va_profile_(VAProfileNone), + va_entrypoint_(kVAEntrypointInvalid) {} + +VaapiWrapper::~VaapiWrapper() { + CHECK(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(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::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + { + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + report_error_to_uma_cb_ = report_error_to_uma_cb; + if (!VADisplayState::Get()->Initialize()) + return false; + + { + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + base::AutoLock 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(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{}; + memset(&attribute, 0, sizeof(attribute)); + if (GetImplementationType() != VAImplementation::kNVIDIAVDPAU) { + // Nvidia's VAAPI-VDPAU driver doesn't support this attribute + attribute.type = VASurfaceAttribUsageHint; + attribute.flags = VA_SURFACE_ATTRIB_SETTABLE; + attribute.value.type = VAGenericValueTypeInteger; + switch (usage_hint) { + case SurfaceUsageHint::kVideoDecoder: + attribute.value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_DECODER; + break; + case SurfaceUsageHint::kVideoEncoder: + attribute.value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_ENCODER; + break; + case SurfaceUsageHint::kVideoProcessWrite: + attribute.value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_VPP_WRITE; + break; + case SurfaceUsageHint::kGeneric: + attribute.value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_GENERIC; + break; + } + 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::AutoLock 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(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::AutoLock 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(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::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + if (va_surface_id == VA_INVALID_SURFACE) + return; + DVLOG(2) << __func__ << " " << va_surface_id; + base::AutoLock 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(sequence_checker_.CalledOnValidSequence()); + TRACE_EVENT0("media,gpu", "VaapiWrapper::Execute_Locked"); + va_lock_->AssertAcquired(); + + 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(sequence_checker_.CalledOnValidSequence()); + TRACE_EVENT0("media,gpu", "VaapiWrapper::SubmitBuffer_Locked"); + va_lock_->AssertAcquired(); + + 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(sequence_checker_.CalledOnValidSequence()); + va_lock_->AssertAcquired(); + + 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(sequence_checker_.CalledOnValidSequence()); + DCHECK(IsModeEncoding(mode_)); + va_lock_->AssertAcquired(); + + // 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(sequence_checker_.CalledOnValidSequence()); + va_lock_->AssertAcquired(); + 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 diff --git a/media/gpu/vaapi/vaapi_wrapper.h b/media/gpu/vaapi/vaapi_wrapper.h new file mode 100644 index 00000000..d3c9a1b3 --- /dev/null +++ b/media/gpu/vaapi/vaapi_wrapper.h @@ -0,0 +1,627 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file contains an implementation of VaapiWrapper, used by +// VaapiVideoDecodeAccelerator and VaapiH264Decoder for decode, +// and VaapiVideoEncodeAccelerator for encode, to interface +// with libva (VA-API library for hardware video codec). + +#ifndef MEDIA_GPU_VAAPI_VAAPI_WRAPPER_H_ +#define MEDIA_GPU_VAAPI_VAAPI_WRAPPER_H_ + +#include +#include +#include + +#include +#include +#include + +#include "base/compiler_specific.h" +#include "base/files/file.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_refptr.h" +#include "base/synchronization/lock.h" +#include "base/thread_annotations.h" +#include "build/chromeos_buildflags.h" +#include "media/gpu/chromeos/fourcc.h" +#include "media/gpu/media_gpu_export.h" +#include "media/gpu/vaapi/va_surface.h" +#include "media/gpu/vaapi/vaapi_utils.h" +#include "media/video/video_decode_accelerator.h" +#include "media/video/video_encode_accelerator.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/geometry/size.h" + +#if BUILDFLAG(USE_VAAPI_X11) +#include "ui/gfx/x/xproto.h" // nogncheck +#endif // BUILDFLAG(USE_VAAPI_X11) + +namespace gfx { +enum class BufferFormat; +class NativePixmap; +class NativePixmapDmaBuf; +class Rect; +} + +namespace media { +constexpr unsigned int kInvalidVaRtFormat = 0u; + +class VideoFrame; + +// Enum, function and callback type to allow VaapiWrapper to log errors in VA +// function calls executed on behalf of its owner. |histogram_name| is prebound +// to allow for disinguishing such owners. +enum class VaapiFunctions; +void ReportVaapiErrorToUMA(const std::string& histogram_name, + VaapiFunctions value); +using ReportErrorToUMACB = base::RepeatingCallback; + +// This struct holds a NativePixmapDmaBuf, usually the result of exporting a VA +// surface, and some associated size information needed to tell clients about +// the underlying buffer. +struct NativePixmapAndSizeInfo { + NativePixmapAndSizeInfo(); + ~NativePixmapAndSizeInfo(); + + // The VA-API internal buffer dimensions, which may be different than the + // dimensions requested at the time of creation of the surface (but always + // larger than or equal to those). This can be used for validation in, e.g., + // testing. + gfx::Size va_surface_resolution; + + // The size of the underlying Buffer Object. A use case for this is when an + // image decode is requested and the caller needs to know the size of the + // allocated buffer for caching purposes. + size_t byte_size = 0u; + + // Contains the information needed to use the surface in a graphics API, + // including the visible size (|pixmap|->GetBufferSize()) which should be no + // larger than |va_surface_resolution|. + scoped_refptr pixmap; +}; + +enum class VAImplementation { + kMesaGallium, + kIntelI965, + kIntelIHD, + kNVIDIAVDPAU, + kOther, + kInvalid, +}; + +// This class handles VA-API calls and ensures proper locking of VA-API calls +// to libva, the userspace shim to the HW codec driver. libva is not +// thread-safe, so we have to perform locking ourselves. This class is fully +// synchronous and its constructor, all of its methods, and its destructor must +// be called on the same sequence. These methods may wait on the |va_lock_| +// which guards libva calls across all VaapiWrapper instances and other libva +// call sites. +// +// This class is responsible for managing VAAPI connection, contexts and state. +// It is also responsible for managing and freeing VABuffers (not VASurfaces), +// which are used to queue parameters and slice data to the HW codec, +// as well as underlying memory for VASurfaces themselves. +class MEDIA_GPU_EXPORT VaapiWrapper + : public base::RefCountedThreadSafe { + public: + enum CodecMode { + kDecode, +#if BUILDFLAG(IS_CHROMEOS_ASH) + // NOTE: A kDecodeProtected VaapiWrapper is created using the actual video + // profile and an extra VAProfileProtected, each with some special added + // VAConfigAttribs. Then when CreateProtectedSession() is called, it will + // then create a protected session using protected profile & entrypoint + // which gets attached to the decoding context (or attached when the + // decoding context is created or re-created). This then enables + // decrypt + decode support in the driver and encrypted frame data can then + // be submitted. + kDecodeProtected, // Decrypt + decode to protected surface. +#endif + kEncodeConstantBitrate, // Encode with Constant Bitrate algorithm. + kEncodeConstantQuantizationParameter, // Encode with Constant Quantization + // Parameter algorithm. + kVideoProcess, + kCodecModeMax, + }; + + // This is enum associated with VASurfaceAttribUsageHint. + enum class SurfaceUsageHint : int32_t { + kGeneric = VA_SURFACE_ATTRIB_USAGE_HINT_GENERIC, + kVideoDecoder = VA_SURFACE_ATTRIB_USAGE_HINT_DECODER, + kVideoEncoder = VA_SURFACE_ATTRIB_USAGE_HINT_ENCODER, + kVideoProcessWrite = VA_SURFACE_ATTRIB_USAGE_HINT_VPP_WRITE, + }; + + using InternalFormats = struct { + bool yuv420 : 1; + bool yuv420_10 : 1; + bool yuv422 : 1; + bool yuv444 : 1; + }; + + // Returns the type of the underlying VA-API implementation. + static VAImplementation GetImplementationType(); + + // Return an instance of VaapiWrapper initialized for |va_profile| and + // |mode|. |report_error_to_uma_cb| will be called independently from + // reporting errors to clients via method return values. + static scoped_refptr Create( + CodecMode mode, + VAProfile va_profile, + EncryptionScheme encryption_scheme, + const ReportErrorToUMACB& report_error_to_uma_cb); + + // Create VaapiWrapper for VideoCodecProfile. It maps VideoCodecProfile + // |profile| to VAProfile. + // |report_error_to_uma_cb| will be called independently from reporting + // errors to clients via method return values. + static scoped_refptr CreateForVideoCodec( + CodecMode mode, + VideoCodecProfile profile, + EncryptionScheme encryption_scheme, + const ReportErrorToUMACB& report_error_to_uma_cb); + + VaapiWrapper(const VaapiWrapper&) = delete; + VaapiWrapper& operator=(const VaapiWrapper&) = delete; + + // Returns the supported SVC scalability modes for specified profile. + static std::vector GetSupportedScalabilityModes( + VideoCodecProfile media_profile, + VAProfile va_profile); + + // Return the supported video encode profiles. + static VideoEncodeAccelerator::SupportedProfiles GetSupportedEncodeProfiles(); + + // Return the supported video decode profiles. + static VideoDecodeAccelerator::SupportedProfiles GetSupportedDecodeProfiles(); + + // Return true when decoding using |va_profile| is supported. + static bool IsDecodeSupported(VAProfile va_profile); + + // Returns the supported internal formats for decoding using |va_profile|. If + // decoding is not supported for that profile, returns InternalFormats{}. + static InternalFormats GetDecodeSupportedInternalFormats( + VAProfile va_profile); + + // Returns true if |rt_format| is supported for decoding using |va_profile|. + // Returns false if |rt_format| or |va_profile| is not supported for decoding. + static bool IsDecodingSupportedForInternalFormat(VAProfile va_profile, + unsigned int rt_format); + + // Gets the minimum surface size allowed for decoding using |va_profile|. + // Returns true if the size can be obtained, false otherwise. The minimum + // dimension (width or height) returned is 1. Particularly, if a dimension is + // not reported by the driver, the dimension is returned as 1. + static bool GetDecodeMinResolution(VAProfile va_profile, gfx::Size* min_size); + + // Gets the maximum surface size allowed for decoding using |va_profile|. + // Returns true if the size can be obtained, false otherwise. Because of the + // initialization in VASupportedProfiles::FillProfileInfo_Locked(), the size + // is guaranteed to not be empty (as long as this method returns true). + static bool GetDecodeMaxResolution(VAProfile va_profile, gfx::Size* max_size); + + // Obtains a suitable FOURCC that can be used in vaCreateImage() + + // vaGetImage(). |rt_format| corresponds to the JPEG's subsampling format. + // |preferred_fourcc| is the FOURCC of the format preferred by the caller. If + // it is determined that the VAAPI driver can do the conversion from the + // internal format (|rt_format|), *|suitable_fourcc| is set to + // |preferred_fourcc|. Otherwise, it is set to a supported format. Returns + // true if a suitable FOURCC could be determined, false otherwise (e.g., if + // the |rt_format| is unsupported by the driver). If |preferred_fourcc| is not + // a supported image format, *|suitable_fourcc| is set to VA_FOURCC_I420. + static bool GetJpegDecodeSuitableImageFourCC(unsigned int rt_format, + uint32_t preferred_fourcc, + uint32_t* suitable_fourcc); + // Checks to see if VAProfileNone is supported on this decoder + static bool IsVppProfileSupported(); + + // Checks the surface size is allowed for VPP. Returns true if the size is + // supported, false otherwise. + static bool IsVppResolutionAllowed(const gfx::Size& size); + + // Returns true if the VPP supports converting from/to |fourcc|. + static bool IsVppFormatSupported(uint32_t fourcc); + + // Returns the pixel formats supported by the VPP. + static std::vector GetVppSupportedFormats(); + + // Returns true if VPP supports the format conversion from a JPEG decoded + // internal surface to a FOURCC. |rt_format| corresponds to the JPEG's + // subsampling format. |fourcc| is the output surface's FOURCC. + static bool IsVppSupportedForJpegDecodedSurfaceToFourCC( + unsigned int rt_format, + uint32_t fourcc); + + // Return true when JPEG encode is supported. + static bool IsJpegEncodeSupported(); + + // Return true when the specified image format is supported. + static bool IsImageFormatSupported(const VAImageFormat& format); + + // Returns the list of VAImageFormats supported by the driver. + static const std::vector& GetSupportedImageFormatsForTesting(); + + // Returns the list of supported profiles and entrypoints for a given |mode|. + static std::map> + GetSupportedConfigurationsForCodecModeForTesting(CodecMode mode); + + static VAEntrypoint GetDefaultVaEntryPoint(CodecMode mode, VAProfile profile); + + static uint32_t BufferFormatToVARTFormat(gfx::BufferFormat fmt); + + // Creates |num_surfaces| VASurfaceIDs of |va_format|, |size| and + // |surface_usage_hints| and, if successful, creates a |va_context_id_| of the + // same size. |surface_usage_hints| may affect an alignment and tiling of the + // created surface. Returns true if successful, with the created IDs in + // |va_surfaces|. The client is responsible for destroying |va_surfaces| via + // DestroyContextAndSurfaces() to free the allocated surfaces. + virtual bool CreateContextAndSurfaces( + unsigned int va_format, + const gfx::Size& size, + const std::vector& surface_usage_hints, + size_t num_surfaces, + std::vector* va_surfaces) WARN_UNUSED_RESULT; + + // Creates |num_surfaces| ScopedVASurfaces of |va_format| and |size| and, if + // successful, creates a |va_context_id_| of the same size. Returns an empty + // vector if creation failed. If |visible_size| is supplied, the returned + // ScopedVASurface's size is set to it. Otherwise, it's set to |size| (refer + // to CreateScopedVASurfaces() for details). + virtual std::vector> + CreateContextAndScopedVASurfaces( + unsigned int va_format, + const gfx::Size& size, + const std::vector& usage_hints, + size_t num_surfaces, + const absl::optional& visible_size); + + // Attempts to create a protected session that will be attached to the + // decoding context to enable encrypted video decoding. If it cannot be + // attached now, it will be attached when the decoding context is created or + // re-created. |encryption| should be the encryption scheme from the + // DecryptConfig. |hw_config| should have been obtained from the OEMCrypto + // implementation via the CdmFactoryDaemonProxy. |hw_identifier_out| is an + // output parameter which will return session specific information which can + // be passed through the ChromeOsCdmContext to retrieve encrypted key + // information. Returns true on success and false otherwise. + bool CreateProtectedSession(media::EncryptionScheme encryption, + const std::vector& hw_config, + std::vector* hw_identifier_out); + // Returns true if and only if we have created a protected session and + // querying libva indicates that our protected session is no longer alive, + // otherwise this will return false. + bool IsProtectedSessionDead(); +#if BUILDFLAG(IS_CHROMEOS_ASH) + // Returns true if and only if |va_protected_session_id| is not VA_INVALID_ID + // and querying libva indicates that the protected session identified by + // |va_protected_session_id| is no longer alive. + bool IsProtectedSessionDead(VAProtectedSessionID va_protected_session_id); + + // Returns the ID of the current protected session or VA_INVALID_ID if there's + // none. This must be called on the same sequence as other methods that use + // the protected session ID internally. + // + // TODO(b/183515581): update this documentation once we force the VaapiWrapper + // to be used on a single sequence. + VAProtectedSessionID GetProtectedSessionID() const; +#endif + // If we have a protected session, destroys it immediately. This should be + // used as part of recovering dead protected sessions. + void DestroyProtectedSession(); + + // Releases the |va_surfaces| and destroys |va_context_id_|. + void DestroyContextAndSurfaces(std::vector va_surfaces); + + // Creates a VAContextID of |size| (unless it's a Vpp context in which case + // |size| is ignored and 0x0 is used instead). The client is responsible for + // releasing said context via DestroyContext() or DestroyContextAndSurfaces(), + // or it will be released on dtor. If a valid |va_protected_session_id_| + // exists, it will be attached to the newly created |va_context_id_| as well. + virtual bool CreateContext(const gfx::Size& size) WARN_UNUSED_RESULT; + + // Destroys the context identified by |va_context_id_|. + virtual void DestroyContext(); + + // Requests |num_surfaces| ScopedVASurfaces of size |size|, |va_rt_format| and + // optionally |va_fourcc|. Returns self-cleaning ScopedVASurfaces or empty + // vector if creation failed. If |visible_size| is supplied, the returned + // ScopedVASurfaces' size are set to it: for example, we may want to request a + // 16x16 surface to decode a 13x12 JPEG: we may want to keep track of the + // visible size 13x12 inside the ScopedVASurface to inform the surface's users + // that that's the only region with meaningful content. If |visible_size| is + // not supplied, we store |size| in the returned ScopedVASurfaces. + virtual std::vector> 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); + + // Creates a self-releasing VASurface from |pixmap|. The created VASurface + // shares the ownership of the underlying buffer represented by |pixmap|. The + // ownership of the surface is transferred to the caller. A caller can destroy + // |pixmap| after this method returns and the underlying buffer will be kept + // alive by the VASurface. |protected_content| should only be true if the + // format needs VA_RT_FORMAT_PROTECTED (currently only true for AMD). + virtual scoped_refptr CreateVASurfaceForPixmap( + scoped_refptr pixmap, + bool protected_content = false); + + // Creates a self-releasing VASurface from |buffers|. The ownership of the + // surface is transferred to the caller. |buffers| should be a pointer array + // of size 1, with |buffer_size| corresponding to its size. |size| should be + // the desired surface dimensions (which does not need to map to |buffer_size| + // in any relevant way). |buffers| should be kept alive when using the + // VASurface and for accessing the data after the operation is complete. + scoped_refptr CreateVASurfaceForUserPtr(const gfx::Size& size, + uintptr_t* buffers, + size_t buffer_size); + + // Syncs and exports |va_surface| as a gfx::NativePixmapDmaBuf. Currently, the + // only VAAPI surface pixel formats supported are VA_FOURCC_IMC3 and + // VA_FOURCC_NV12. + // + // Notes: + // + // - For VA_FOURCC_IMC3, the format of the returned NativePixmapDmaBuf is + // gfx::BufferFormat::YVU_420 because we don't have a YUV_420 format. The + // planes are flipped accordingly, i.e., + // gfx::NativePixmapDmaBuf::GetDmaBufOffset(1) refers to the V plane. + // TODO(andrescj): revisit once crrev.com/c/1573718 lands. + // + // - For VA_FOURCC_NV12, the format of the returned NativePixmapDmaBuf is + // gfx::BufferFormat::YUV_420_BIPLANAR. + // + // Returns nullptr on failure. + std::unique_ptr ExportVASurfaceAsNativePixmapDmaBuf( + const ScopedVASurface& va_surface); + + // Synchronize the VASurface explicitly. This is useful when sharing a surface + // between contexts. + bool SyncSurface(VASurfaceID va_surface_id) WARN_UNUSED_RESULT; + + // Calls SubmitBuffer_Locked() to request libva to allocate a new VABufferID + // of |va_buffer_type| and |size|, and to map-and-copy the |data| into it. The + // allocated VABufferIDs stay alive until DestroyPendingBuffers_Locked(). Note + // that this method does not submit the buffers for execution, they are simply + // stored until ExecuteAndDestroyPendingBuffers()/Execute_Locked(). The + // ownership of |data| stays with the caller. On failure, all pending buffers + // are destroyed. + bool SubmitBuffer(VABufferType va_buffer_type, + size_t size, + const void* data) WARN_UNUSED_RESULT; + // Convenient templatized version of SubmitBuffer() where |size| is deduced to + // be the size of the type of |*data|. + template + bool WARN_UNUSED_RESULT SubmitBuffer(VABufferType va_buffer_type, + const T* data) { + CHECK(sequence_checker_.CalledOnValidSequence()); + return SubmitBuffer(va_buffer_type, sizeof(T), data); + } + // Batch-version of SubmitBuffer(), where the lock for accessing libva is + // acquired only once. + struct VABufferDescriptor { + VABufferType type; + size_t size; + const void* data; + }; + bool SubmitBuffers(const std::vector& va_buffers) + WARN_UNUSED_RESULT; + + // Destroys all |pending_va_buffers_| sent via SubmitBuffer*(). Useful when a + // pending job is to be cancelled (on reset or error). + void DestroyPendingBuffers(); + + // Executes job in hardware on target |va_surface_id| and destroys pending + // buffers. Returns false if Execute() fails. + virtual bool ExecuteAndDestroyPendingBuffers(VASurfaceID va_surface_id) + WARN_UNUSED_RESULT; + + // Maps each |va_buffers| ID and copies the data described by the associated + // VABufferDescriptor into it; then calls Execute_Locked() on |va_surface_id|. + bool MapAndCopyAndExecute( + VASurfaceID va_surface_id, + const std::vector>& va_buffers) + WARN_UNUSED_RESULT; + +#if BUILDFLAG(USE_VAAPI_X11) + // Put data from |va_surface_id| into |x_pixmap| of size + // |dest_size|, converting/scaling to it. + bool PutSurfaceIntoPixmap(VASurfaceID va_surface_id, + x11::Pixmap x_pixmap, + gfx::Size dest_size) WARN_UNUSED_RESULT; +#endif // BUILDFLAG(USE_VAAPI_X11) + + // Creates a ScopedVAImage from a VASurface |va_surface_id| and map it into + // memory with the given |format| and |size|. If |format| is not equal to the + // internal format, the underlying implementation will do format conversion if + // supported. |size| should be smaller than or equal to the surface. If |size| + // is smaller, the image will be cropped. + std::unique_ptr CreateVaImage(VASurfaceID va_surface_id, + VAImageFormat* format, + const gfx::Size& size); + + // Uploads contents of |frame| into |va_surface_id| for encode. + virtual bool UploadVideoFrameToSurface(const VideoFrame& frame, + VASurfaceID va_surface_id, + const gfx::Size& va_surface_size) + WARN_UNUSED_RESULT; + + // Creates a buffer of |size| bytes to be used as encode output. + virtual std::unique_ptr CreateVABuffer(VABufferType type, + size_t size); + + // Gets the encoded frame linear size of the buffer with given |buffer_id|. + // |sync_surface_id| will be used as a sync point, i.e. it will have to become + // idle before starting the acquirement. |sync_surface_id| should be the + // source surface passed to the encode job. Returns 0 if it fails for any + // reason. + virtual uint64_t GetEncodedChunkSize(VABufferID buffer_id, + VASurfaceID sync_surface_id) + WARN_UNUSED_RESULT; + + // Downloads the contents of the buffer with given |buffer_id| into a buffer + // of size |target_size|, pointed to by |target_ptr|. The number of bytes + // downloaded will be returned in |coded_data_size|. |sync_surface_id| will + // be used as a sync point, i.e. it will have to become idle before starting + // the download. |sync_surface_id| should be the source surface passed + // to the encode job. Returns false if it fails for any reason. For example, + // the linear size of the resulted encoded frame is larger than |target_size|. + virtual bool DownloadFromVABuffer(VABufferID buffer_id, + VASurfaceID sync_surface_id, + uint8_t* target_ptr, + size_t target_size, + size_t* coded_data_size) WARN_UNUSED_RESULT; + + // Get the max number of reference frames for encoding supported by the + // driver. + // For H.264 encoding, the value represents the maximum number of reference + // frames for both the reference picture list 0 (bottom 16 bits) and the + // reference picture list 1 (top 16 bits). + virtual bool GetVAEncMaxNumOfRefFrames(VideoCodecProfile profile, + size_t* max_ref_frames) + WARN_UNUSED_RESULT; + + // Gets packed headers are supported for encoding. This is called for + // H264 encoding. |packed_sps|, |packed_pps| and |packed_slice| stands for + // whether packed slice parameter set, packed picture parameter set and packed + // slice header is supported, respectively. + virtual bool GetSupportedPackedHeaders(VideoCodecProfile profile, + bool& packed_sps, + bool& packed_pps, + bool& packed_slice) WARN_UNUSED_RESULT; + + // Checks if the driver supports frame rotation. + bool IsRotationSupported(); + + // Blits a VASurface |va_surface_src| into another VASurface + // |va_surface_dest| applying pixel format conversion, rotation, cropping + // and scaling if needed. |src_rect| and |dest_rect| are optional. They can + // be used to specify the area used in the blit. If |va_protected_session_id| + // is provided and is not VA_INVALID_ID, the corresponding protected session + // is attached to the VPP context prior to submitting the VPP buffers and + // detached after submitting those buffers. + virtual bool BlitSurface( + const VASurface& va_surface_src, + const VASurface& va_surface_dest, + absl::optional src_rect = absl::nullopt, + absl::optional dest_rect = absl::nullopt, + VideoRotation rotation = VIDEO_ROTATION_0 +#if BUILDFLAG(IS_CHROMEOS_ASH) + , + VAProtectedSessionID va_protected_session_id = VA_INVALID_ID +#endif + ) WARN_UNUSED_RESULT; + + // Initialize static data before sandbox is enabled. + static void PreSandboxInitialization(); + + // vaDestroySurfaces() a vector or a single VASurfaceID. + virtual void DestroySurfaces(std::vector va_surfaces); + virtual void DestroySurface(VASurfaceID va_surface_id); + + protected: + VaapiWrapper(CodecMode mode); + virtual ~VaapiWrapper(); + + private: + friend class base::RefCountedThreadSafe; + friend class VaapiWrapperTest; + friend class VaapiVideoEncodeAcceleratorTest; + + FRIEND_TEST_ALL_PREFIXES(VaapiTest, LowQualityEncodingSetting); + FRIEND_TEST_ALL_PREFIXES(VaapiUtilsTest, ScopedVAImage); + FRIEND_TEST_ALL_PREFIXES(VaapiUtilsTest, BadScopedVAImage); + FRIEND_TEST_ALL_PREFIXES(VaapiUtilsTest, BadScopedVABufferMapping); + FRIEND_TEST_ALL_PREFIXES(VaapiMinigbmTest, AllocateAndCompareWithMinigbm); + + bool Initialize(VAProfile va_profile, + EncryptionScheme encryption_scheme) WARN_UNUSED_RESULT; + void Deinitialize(); + bool VaInitialize(const ReportErrorToUMACB& report_error_to_uma_cb) + WARN_UNUSED_RESULT; + + // Tries to allocate |num_surfaces| VASurfaceIDs of |size| and |va_format|. + // Fills |va_surfaces| and returns true if successful, or returns false. + bool CreateSurfaces(unsigned int va_format, + const gfx::Size& size, + const std::vector& usage_hints, + size_t num_surfaces, + std::vector* va_surfaces) WARN_UNUSED_RESULT; + + // Carries out the vaBeginPicture()-vaRenderPicture()-vaEndPicture() on target + // |va_surface_id|. Returns false if any of these calls fails. + bool Execute_Locked(VASurfaceID va_surface_id, + const std::vector& va_buffers) + EXCLUSIVE_LOCKS_REQUIRED(va_lock_) WARN_UNUSED_RESULT; + + virtual void DestroyPendingBuffers_Locked() + EXCLUSIVE_LOCKS_REQUIRED(va_lock_); + + // Requests libva to allocate a new VABufferID of type |va_buffer.type|, then + // maps-and-copies |va_buffer.size| contents of |va_buffer.data| to it. If a + // failure occurs, calls DestroyPendingBuffers_Locked() and returns false. + virtual bool SubmitBuffer_Locked(const VABufferDescriptor& va_buffer) + EXCLUSIVE_LOCKS_REQUIRED(va_lock_) WARN_UNUSED_RESULT; + + // Maps |va_buffer_id| and, if successful, copies the contents of |va_buffer| + // into it. + bool MapAndCopy_Locked(VABufferID va_buffer_id, + const VABufferDescriptor& va_buffer) + EXCLUSIVE_LOCKS_REQUIRED(va_lock_) WARN_UNUSED_RESULT; + + // Queries whether |va_profile_| and |va_entrypoint_| support encoding quality + // setting and, if available, configures it to its maximum value, for lower + // consumption and maximum speed. + void MaybeSetLowQualityEncoding_Locked() EXCLUSIVE_LOCKS_REQUIRED(va_lock_); + + // If a protected session is active, attaches it to the decoding context. + bool MaybeAttachProtectedSession_Locked() + EXCLUSIVE_LOCKS_REQUIRED(va_lock_) WARN_UNUSED_RESULT; + + const CodecMode mode_; + base::SequenceCheckerImpl sequence_checker_; + + // Pointer to VADisplayState's member |va_lock_|. Guaranteed to be valid for + // the lifetime of VaapiWrapper. + base::Lock* va_lock_; + + // VA handles. + // All valid after successful Initialize() and until Deinitialize(). + VADisplay va_display_ GUARDED_BY(va_lock_); + VAConfigID va_config_id_{VA_INVALID_ID}; + // Created in CreateContext() or CreateContextAndSurfaces() and valid until + // DestroyContext() or DestroyContextAndSurfaces(). + VAContextID va_context_id_{VA_INVALID_ID}; + + // Profile and entrypoint configured for the corresponding |va_context_id_|. + VAProfile va_profile_; + VAEntrypoint va_entrypoint_; + + // Data queued up for HW codec, to be committed on next execution. + // TODO(b/166646505): let callers manage the lifetime of these buffers. + std::vector pending_va_buffers_; + + // VA buffer to be used for kVideoProcess. Allocated the first time around, + // and reused afterwards. + std::unique_ptr va_buffer_for_vpp_; + +#if BUILDFLAG(IS_CHROMEOS_ASH) + // For protected decode mode. + VAConfigID va_protected_config_id_{VA_INVALID_ID}; + VAProtectedSessionID va_protected_session_id_{VA_INVALID_ID}; +#endif + + // Called to report codec errors to UMA. Errors to clients are reported via + // return values from public methods. + ReportErrorToUMACB report_error_to_uma_cb_; +}; + +} // namespace media + +#endif // MEDIA_GPU_VAAPI_VAAPI_WRAPPER_H_ diff --git a/ui/base/x/x11_util.cc b/ui/base/x/x11_util.cc new file mode 100644 index 00000000..a5db475e --- /dev/null +++ b/ui/base/x/x11_util.cc @@ -0,0 +1,1146 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file defines utility functions for X11 (Linux only). This code has been +// ported from XCB since we can't use XCB on Ubuntu while its 32-bit support +// remains woefully incomplete. + +#include "ui/base/x/x11_util.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include "base/command_line.h" +#include "base/containers/contains.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/scoped_refptr.h" +#include "base/memory/singleton.h" +#include "base/notreached.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/task/single_thread_task_runner.h" +#include "base/trace_event/trace_event.h" +#include "base/values.h" +#include "build/build_config.h" +#include "build/chromeos_buildflags.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h" +#include "ui/base/x/visual_picker_glx.h" +#include "ui/display/util/gpu_info_util.h" +#include "ui/events/devices/x11/device_data_manager_x11.h" +#include "ui/events/devices/x11/touch_factory_x11.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/image/image_skia.h" +#include "ui/gfx/switches.h" +#include "ui/gfx/x/connection.h" +#include "ui/gfx/x/screensaver.h" +#include "ui/gfx/x/shm.h" +#include "ui/gfx/x/x11_atom_cache.h" +#include "ui/gfx/x/xproto.h" +#include "ui/gfx/x/xproto_util.h" + +#if defined(OS_FREEBSD) +#include +#include +#endif + +namespace ui { +namespace { + +// Constants that are part of EWMH. +constexpr int kNetWMStateAdd = 1; +constexpr int kNetWMStateRemove = 0; + +bool SupportsEWMH() { + static bool supports_ewmh = false; + static bool supports_ewmh_cached = false; + if (!supports_ewmh_cached) { + supports_ewmh_cached = true; + + x11::Window wm_window = x11::Window::None; + if (!GetProperty(GetX11RootWindow(), + x11::GetAtom("_NET_SUPPORTING_WM_CHECK"), &wm_window)) { + supports_ewmh = false; + return false; + } + + // It's possible that a window manager started earlier in this X session + // left a stale _NET_SUPPORTING_WM_CHECK property when it was replaced by a + // non-EWMH window manager, so we trap errors in the following requests to + // avoid crashes (issue 23860). + + // EWMH requires the supporting-WM window to also have a + // _NET_SUPPORTING_WM_CHECK property pointing to itself (to avoid a stale + // property referencing an ID that's been recycled for another window), so + // we check that too. + x11::Window wm_window_property = x11::Window::None; + supports_ewmh = + GetProperty(wm_window, x11::GetAtom("_NET_SUPPORTING_WM_CHECK"), + &wm_window_property) && + wm_window_property == wm_window; + } + + return supports_ewmh; +} + +bool GetWindowManagerName(std::string* wm_name) { + DCHECK(wm_name); + if (!SupportsEWMH()) + return false; + + x11::Window wm_window = x11::Window::None; + if (!GetProperty(GetX11RootWindow(), x11::GetAtom("_NET_SUPPORTING_WM_CHECK"), + &wm_window)) { + return false; + } + + std::vector str; + if (!GetArrayProperty(wm_window, x11::GetAtom("_NET_WM_NAME"), &str)) + return false; + wm_name->assign(str.data(), str.size()); + return true; +} + +// Returns whether the X11 Screen Saver Extension can be used to disable the +// screen saver. +bool IsX11ScreenSaverAvailable() { + // X Screen Saver isn't accessible in headless mode. + if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kHeadless)) + return false; + + auto version = x11::Connection::Get() + ->screensaver() + .QueryVersion({x11::ScreenSaver::major_version, + x11::ScreenSaver::minor_version}) + .Sync(); + + return version && (version->server_major_version > 1 || + (version->server_major_version == 1 && + version->server_minor_version >= 1)); +} + +// Returns the bounds of |window| in the screen before adjusting for the frame. +bool GetUndecoratedWindowBounds(x11::Window window, gfx::Rect* rect) { + auto root = GetX11RootWindow(); + + x11::Connection* connection = x11::Connection::Get(); + auto get_geometry = connection->GetGeometry(window); + auto translate_coords = connection->TranslateCoordinates({window, root}); + + // Sync after making both requests so only one round-trip is made. + // Flush so all requests are sent before waiting on any replies. + connection->Flush(); + auto geometry = get_geometry.Sync(); + auto coords = translate_coords.Sync(); + + if (!geometry || !coords) + return false; + + *rect = gfx::Rect(coords->dst_x, coords->dst_y, geometry->width, + geometry->height); + return true; +} + +// Obtains the value of _{NET,GTK}_FRAME_EXTENTS as a gfx::Insets. Returns an +// empty gfx::Insets if the property doesn't exist or is malformed. +gfx::Insets GetFrameExtentsProperty(x11::Window window, x11::Atom property) { + std::vector frame_extents; + GetArrayProperty(window, property, &frame_extents); + if (frame_extents.size() != 4) + return gfx::Insets(); + return gfx::Insets(frame_extents[2] /* top */, frame_extents[0] /* left */, + frame_extents[3] /* bottom */, + frame_extents[1] /* right */); +} + +// Returns the adjustment necessary to obtain the opaque bounds of |window|. +gfx::Insets GetWindowDecorationAdjustment(x11::Window window) { + // _GTK_FRAME_EXTENTS is set by clients using client side decorations to + // subtract the window shadow from the bounds. _NET_FRAME_EXTENTS is set by + // the WM to add the opaque portion of the frame to the bounds. + return GetFrameExtentsProperty(window, x11::GetAtom("_GTK_FRAME_EXTENTS")) - + GetFrameExtentsProperty(window, x11::GetAtom("_NET_FRAME_EXTENTS")); +} + +// Returns the opaque bounds of |window| with it's frame. +bool GetDecoratedWindowBounds(x11::Window window, gfx::Rect* rect) { + if (!GetUndecoratedWindowBounds(window, rect)) + return false; + + rect->Inset(GetWindowDecorationAdjustment(window)); + return true; +} + +// Returns true if the event has event_x and event_y fields. +bool EventHasCoordinates(const x11::Event& event) { + return event.As() || event.As() || + event.As() || event.As() || + event.As() || + event.As() || + event.As(); +} + +} // namespace + +bool GetWmNormalHints(x11::Window window, SizeHints* hints) { + std::vector hints32; + if (!GetArrayProperty(window, x11::Atom::WM_NORMAL_HINTS, &hints32)) + return false; + if (hints32.size() != sizeof(SizeHints) / 4) + return false; + memcpy(hints, hints32.data(), sizeof(*hints)); + return true; +} + +void SetWmNormalHints(x11::Window window, const SizeHints& hints) { + std::vector hints32(sizeof(SizeHints) / 4); + memcpy(hints32.data(), &hints, sizeof(SizeHints)); + SetArrayProperty(window, x11::Atom::WM_NORMAL_HINTS, x11::Atom::WM_SIZE_HINTS, + hints32); +} + +bool GetWmHints(x11::Window window, WmHints* hints) { + std::vector hints32; + if (!GetArrayProperty(window, x11::Atom::WM_HINTS, &hints32)) + return false; + if (hints32.size() != sizeof(WmHints) / 4) + return false; + memcpy(hints, hints32.data(), sizeof(*hints)); + return true; +} + +void SetWmHints(x11::Window window, const WmHints& hints) { + std::vector hints32(sizeof(WmHints) / 4); + memcpy(hints32.data(), &hints, sizeof(WmHints)); + SetArrayProperty(window, x11::Atom::WM_HINTS, x11::Atom::WM_HINTS, hints32); +} + +void WithdrawWindow(x11::Window window) { + auto* connection = x11::Connection::Get(); + connection->UnmapWindow({window}); + + auto root = connection->default_root(); + x11::UnmapNotifyEvent event{.event = root, .window = window}; + auto mask = + x11::EventMask::SubstructureNotify | x11::EventMask::SubstructureRedirect; + SendEvent(event, root, mask); +} + +void RaiseWindow(x11::Window window) { + x11::Connection::Get()->ConfigureWindow(x11::ConfigureWindowRequest{ + .window = window, .stack_mode = x11::StackMode::Above}); +} + +void LowerWindow(x11::Window window) { + x11::Connection::Get()->ConfigureWindow(x11::ConfigureWindowRequest{ + .window = window, .stack_mode = x11::StackMode::Below}); +} + +void DefineCursor(x11::Window window, x11::Cursor cursor) { + // TODO(https://crbug.com/1066670): Sync() should be removed. It's added for + // now because Xlib's XDefineCursor() sync'ed and removing it perturbs the + // timing on BookmarkBarViewTest8.DNDBackToOriginatingMenu on + // linux-chromeos-rel, causing it to flake. + x11::Connection::Get() + ->ChangeWindowAttributes(x11::ChangeWindowAttributesRequest{ + .window = window, .cursor = cursor}) + .Sync(); +} + +size_t RowBytesForVisualWidth(const x11::Connection::VisualInfo& visual_info, + int width) { + auto bpp = visual_info.format->bits_per_pixel; + auto align = visual_info.format->scanline_pad; + size_t row_bits = bpp * width; + row_bits += (align - (row_bits % align)) % align; + return (row_bits + 7) / 8; +} + +void DrawPixmap(x11::Connection* connection, + x11::VisualId visual, + x11::Drawable drawable, + x11::GraphicsContext gc, + const SkPixmap& skia_pixmap, + int src_x, + int src_y, + int dst_x, + int dst_y, + int width, + int height) { + // 24 bytes for the PutImage header, an additional 4 bytes in case this is an + // extended size request, and an additional 4 bytes in case padding is needed. + constexpr size_t kPutImageExtraSize = 32; + + const auto* visual_info = connection->GetVisualInfoFromId(visual); + if (!visual_info) + return; + + size_t row_bytes = RowBytesForVisualWidth(*visual_info, width); + + auto color_type = ColorTypeForVisual(visual); + if (color_type == kUnknown_SkColorType) { + // TODO(https://crbug.com/1066670): Add a fallback path in case any users + // are running a server that uses visual types for which Skia doesn't have + // a corresponding color format. + return; + } + SkImageInfo image_info = + SkImageInfo::Make(width, height, color_type, kPremul_SkAlphaType); + + std::vector vec(row_bytes * height); + SkPixmap pixmap(image_info, vec.data(), row_bytes); + skia_pixmap.readPixels(pixmap, src_x, src_y); + + DCHECK_GT(connection->MaxRequestSizeInBytes(), kPutImageExtraSize); + int rows_per_request = + (connection->MaxRequestSizeInBytes() - kPutImageExtraSize) / row_bytes; + DCHECK_GT(rows_per_request, 1); + for (int row = 0; row < height; row += rows_per_request) { + size_t n_rows = std::min(rows_per_request, height - row); + auto data = base::MakeRefCounted( + vec.data() + row * row_bytes, n_rows * row_bytes); + connection->PutImage({ + .format = x11::ImageFormat::ZPixmap, + .drawable = drawable, + .gc = gc, + .width = static_cast(width), + .height = static_cast(n_rows), + .dst_x = static_cast(dst_x), + .dst_y = static_cast(dst_y + row), + .left_pad = 0, + .depth = visual_info->format->depth, + .data = data, + }); + } + // Flush since the PutImage requests depend on |vec| being alive. + connection->Flush(); +} + +bool IsXInput2Available() { + return DeviceDataManagerX11::GetInstance()->IsXInput2Available(); +} + +bool QueryShmSupport() { + static bool supported = x11::Connection::Get()->shm().QueryVersion().Sync(); + return supported; +} + +int CoalescePendingMotionEvents(const x11::Event& x11_event, + x11::Event* last_event) { + auto* conn = x11::Connection::Get(); + auto* ddmx11 = ui::DeviceDataManagerX11::GetInstance(); + int num_coalesced = 0; + + const auto* motion = x11_event.As(); + const auto* device = x11_event.As(); + DCHECK(motion || device); + DCHECK(!device || device->opcode == x11::Input::DeviceEvent::Motion || + device->opcode == x11::Input::DeviceEvent::TouchUpdate); + + conn->ReadResponses(); + for (auto& event : conn->events()) { + // There may be non-input events such as ConfigureNotifyEvents and + // PropertyNotifyEvents that get interleaved between mouse events, so it is + // necessary to skip over those to coalesce as many pending motion events as + // possible so mouse dragging is smooth. + if (!EventHasCoordinates(event)) + continue; + + if (motion) { + const auto* next_motion = event.As(); + + // Discard all but the most recent motion event that targets the same + // window with unchanged state. + if (next_motion && next_motion->event == motion->event && + next_motion->child == motion->child && + next_motion->state == motion->state) { + *last_event = std::move(event); + continue; + } + } else { + auto* next_device = event.As(); + if (!next_device) + break; + + // If this isn't from a valid device, throw the event away, as + // that's what the message pump would do. Device events come in pairs + // with one from the master and one from the slave so there will + // always be at least one pending. + if (!ui::TouchFactory::GetInstance()->ShouldProcessDeviceEvent( + *next_device)) { + event = x11::Event(); + continue; + } + + // Confirm that the motion event is of the same type, is + // targeted at the same window, and that no buttons or modifiers + // have changed. + if (next_device->opcode == device->opcode && + !ddmx11->IsCMTGestureEvent(event) && + ddmx11->GetScrollClassEventDetail(event) == SCROLL_TYPE_NO_SCROLL && + device->event == next_device->event && + device->child == next_device->child && + device->detail == next_device->detail && + device->button_mask == next_device->button_mask && + device->mods.base == next_device->mods.base && + device->mods.latched == next_device->mods.latched && + device->mods.locked == next_device->mods.locked && + device->mods.effective == next_device->mods.effective) { + *last_event = std::move(event); + num_coalesced++; + continue; + } + } + break; + } + + return num_coalesced; +} + +void SetUseOSWindowFrame(x11::Window window, bool use_os_window_frame) { + // This data structure represents additional hints that we send to the window + // manager and has a direct lineage back to Motif, which defined this de facto + // standard. We define this struct to match the wire-format (32-bit fields) + // rather than the Xlib API (XChangeProperty) format (long fields). + typedef struct { + uint32_t flags; + uint32_t functions; + uint32_t decorations; + int32_t input_mode; + uint32_t status; + } MotifWmHints; + + MotifWmHints motif_hints; + memset(&motif_hints, 0, sizeof(motif_hints)); + // Signals that the reader of the _MOTIF_WM_HINTS property should pay + // attention to the value of |decorations|. + motif_hints.flags = (1u << 1); + motif_hints.decorations = use_os_window_frame ? 1 : 0; + + std::vector hints(sizeof(MotifWmHints) / sizeof(uint32_t)); + memcpy(hints.data(), &motif_hints, sizeof(MotifWmHints)); + x11::Atom hint_atom = x11::GetAtom("_MOTIF_WM_HINTS"); + SetArrayProperty(window, hint_atom, hint_atom, hints); +} + +bool IsShapeExtensionAvailable() { + return x11::Connection::Get()->shape().present(); +} + +x11::Window GetX11RootWindow() { + return x11::Connection::Get()->default_screen().root; +} + +bool GetCurrentDesktop(int32_t* desktop) { + return GetProperty(GetX11RootWindow(), x11::GetAtom("_NET_CURRENT_DESKTOP"), + desktop); +} + +void SetHideTitlebarWhenMaximizedProperty(x11::Window window, + HideTitlebarWhenMaximized property) { + SetProperty(window, x11::GetAtom("_GTK_HIDE_TITLEBAR_WHEN_MAXIMIZED"), + x11::Atom::CARDINAL, static_cast(property)); +} + +bool IsWindowVisible(x11::Window window) { + TRACE_EVENT0("ui", "IsWindowVisible"); + + auto response = x11::Connection::Get()->GetWindowAttributes({window}).Sync(); + if (!response || response->map_state != x11::MapState::Viewable) + return false; + + // Minimized windows are not visible. + std::vector wm_states; + if (GetArrayProperty(window, x11::GetAtom("_NET_WM_STATE"), &wm_states)) { + x11::Atom hidden_atom = x11::GetAtom("_NET_WM_STATE_HIDDEN"); + if (base::Contains(wm_states, hidden_atom)) + return false; + } + + // Do not check _NET_CURRENT_DESKTOP/_NET_WM_DESKTOP since some + // window managers (eg. i3) have per-monitor workspaces where more + // than one workspace can be visible at once, but only one will be + // "active". + return true; +} + +bool WindowContainsPoint(x11::Window window, gfx::Point screen_loc) { + TRACE_EVENT0("ui", "WindowContainsPoint"); + + gfx::Rect undecorated_bounds; + if (!GetUndecoratedWindowBounds(window, &undecorated_bounds)) + return false; + + gfx::Rect decorated_bounds = undecorated_bounds; + decorated_bounds.Inset(GetWindowDecorationAdjustment(window)); + if (!decorated_bounds.Contains(screen_loc)) + return false; + + if (!IsShapeExtensionAvailable()) + return true; + + // According to http://www.x.org/releases/X11R7.6/doc/libXext/shapelib.html, + // if an X display supports the shape extension the bounds of a window are + // defined as the intersection of the window bounds and the interior + // rectangles. This means to determine if a point is inside a window for the + // purpose of input handling we have to check the rectangles in the ShapeInput + // list. + // According to http://www.x.org/releases/current/doc/xextproto/shape.html, + // we need to also respect the ShapeBounding rectangles. + // The effective input region of a window is defined to be the intersection + // of the client input region with both the default input region and the + // client bounding region. Any portion of the client input region that is not + // included in both the default input region and the client bounding region + // will not be included in the effective input region on the screen. + x11::Shape::Sk rectangle_kind[] = {x11::Shape::Sk::Input, + x11::Shape::Sk::Bounding}; + for (auto kind : rectangle_kind) { + auto shape = + x11::Connection::Get()->shape().GetRectangles({window, kind}).Sync(); + if (!shape) + return true; + if (shape->rectangles.empty()) { + // The shape can be empty when |window| is minimized. + return false; + } + bool is_in_shape_rects = false; + for (const auto& rect : shape->rectangles) { + // The ShapeInput and ShapeBounding rects are to be in window space, so we + // have to translate by the window_rect's offset to map to screen space. + gfx::Rect shape_rect = + gfx::Rect(rect.x + undecorated_bounds.x(), + rect.y + undecorated_bounds.y(), rect.width, rect.height); + if (shape_rect.Contains(screen_loc)) { + is_in_shape_rects = true; + break; + } + } + if (!is_in_shape_rects) + return false; + } + return true; +} + +bool PropertyExists(x11::Window window, x11::Atom property) { + auto response = x11::Connection::Get() + ->GetProperty(x11::GetPropertyRequest{ + .window = window, + .property = property, + .long_length = 1, + }) + .Sync(); + return response && response->format; +} + +bool GetRawBytesOfProperty(x11::Window window, + x11::Atom property, + scoped_refptr* out_data, + x11::Atom* out_type) { + auto future = x11::Connection::Get()->GetProperty(x11::GetPropertyRequest{ + .window = window, + .property = property, + // Don't limit the amount of returned data. + .long_length = std::numeric_limits::max(), + }); + auto response = future.Sync(); + if (!response || !response->format) + return false; + *out_data = response->value; + if (out_type) + *out_type = response->type; + return true; +} + +void SetWindowClassHint(x11::Connection* connection, + x11::Window window, + const std::string& res_name, + const std::string& res_class) { + auto str = + base::StringPrintf("%s%c%s", res_name.c_str(), '\0', res_class.c_str()); + std::vector data(str.data(), str.data() + str.size() + 1); + SetArrayProperty(window, x11::Atom::WM_CLASS, x11::Atom::STRING, data); +} + +void SetWindowRole(x11::Window window, const std::string& role) { + x11::Atom prop = x11::GetAtom("WM_WINDOW_ROLE"); + if (role.empty()) + x11::DeleteProperty(window, prop); + else + x11::SetStringProperty(window, prop, x11::Atom::STRING, role); +} + +void SetWMSpecState(x11::Window window, + bool enabled, + x11::Atom state1, + x11::Atom state2) { + SendClientMessage( + window, GetX11RootWindow(), x11::GetAtom("_NET_WM_STATE"), + {static_cast(enabled ? kNetWMStateAdd : kNetWMStateRemove), + static_cast(state1), static_cast(state2), 1, 0}); +} + +void DoWMMoveResize(x11::Connection* connection, + x11::Window root_window, + x11::Window window, + const gfx::Point& location_px, + int direction) { + // This handler is usually sent when the window has the implicit grab. We + // need to dump it because what we're about to do is tell the window manager + // that it's now responsible for moving the window around; it immediately + // grabs when it receives the event below. + connection->UngrabPointer({x11::Time::CurrentTime}); + + SendClientMessage(window, root_window, x11::GetAtom("_NET_WM_MOVERESIZE"), + {static_cast(location_px.x()), + static_cast(location_px.y()), + static_cast(direction), 0, 0}); +} + +bool HasWMSpecProperty(const base::flat_set& properties, + x11::Atom atom) { + return properties.find(atom) != properties.end(); +} + +bool GetCustomFramePrefDefault() { + + return false; + +} + +bool IsWmTiling(WindowManagerName window_manager) { + switch (window_manager) { + case WM_BLACKBOX: + case WM_COMPIZ: + case WM_ENLIGHTENMENT: + case WM_FLUXBOX: + case WM_ICE_WM: + case WM_KWIN: + case WM_MATCHBOX: + case WM_METACITY: + case WM_MUFFIN: + case WM_MUTTER: + case WM_OPENBOX: + case WM_XFWM4: + // Stacking window managers. + return false; + + case WM_I3: + case WM_ION3: + case WM_NOTION: + case WM_RATPOISON: + case WM_STUMPWM: + // Tiling window managers. + return true; + + case WM_AWESOME: + case WM_QTILE: + case WM_XMONAD: + case WM_WMII: + // Dynamic (tiling and stacking) window managers. Assume tiling. + return true; + + case WM_OTHER: + case WM_UNNAMED: + // Unknown. Assume stacking. + return false; + } +} + +bool GetWindowDesktop(x11::Window window, int32_t* desktop) { + return GetProperty(window, x11::GetAtom("_NET_WM_DESKTOP"), desktop); +} + +WindowManagerName GuessWindowManager() { + std::string name; + if (!GetWindowManagerName(&name)) + return WM_UNNAMED; + // These names are taken from the WMs' source code. + if (name == "awesome") + return WM_AWESOME; + if (name == "Blackbox") + return WM_BLACKBOX; + if (name == "Compiz" || name == "compiz") + return WM_COMPIZ; + if (name == "e16" || name == "Enlightenment") + return WM_ENLIGHTENMENT; + if (name == "Fluxbox") + return WM_FLUXBOX; + if (name == "i3") + return WM_I3; + if (base::StartsWith(name, "IceWM", base::CompareCase::SENSITIVE)) + return WM_ICE_WM; + if (name == "ion3") + return WM_ION3; + if (name == "KWin") + return WM_KWIN; + if (name == "matchbox") + return WM_MATCHBOX; + if (name == "Metacity") + return WM_METACITY; + if (name == "Mutter (Muffin)") + return WM_MUFFIN; + if (name == "GNOME Shell") + return WM_MUTTER; // GNOME Shell uses Mutter + if (name == "Mutter") + return WM_MUTTER; + if (name == "notion") + return WM_NOTION; + if (name == "Openbox") + return WM_OPENBOX; + if (name == "qtile") + return WM_QTILE; + if (name == "ratpoison") + return WM_RATPOISON; + if (name == "stumpwm") + return WM_STUMPWM; + if (name == "wmii") + return WM_WMII; + if (name == "Xfwm4") + return WM_XFWM4; + if (name == "xmonad") + return WM_XMONAD; + return WM_OTHER; +} + +std::string GuessWindowManagerName() { + std::string name; + if (GetWindowManagerName(&name)) + return name; + return "Unknown"; +} + +UMALinuxWindowManager GetWindowManagerUMA() { + switch (GuessWindowManager()) { + case WM_OTHER: + return UMALinuxWindowManager::kOther; + case WM_UNNAMED: + return UMALinuxWindowManager::kUnnamed; + case WM_AWESOME: + return UMALinuxWindowManager::kAwesome; + case WM_BLACKBOX: + return UMALinuxWindowManager::kBlackbox; + case WM_COMPIZ: + return UMALinuxWindowManager::kCompiz; + case WM_ENLIGHTENMENT: + return UMALinuxWindowManager::kEnlightenment; + case WM_FLUXBOX: + return UMALinuxWindowManager::kFluxbox; + case WM_I3: + return UMALinuxWindowManager::kI3; + case WM_ICE_WM: + return UMALinuxWindowManager::kIceWM; + case WM_ION3: + return UMALinuxWindowManager::kIon3; + case WM_KWIN: + return UMALinuxWindowManager::kKWin; + case WM_MATCHBOX: + return UMALinuxWindowManager::kMatchbox; + case WM_METACITY: + return UMALinuxWindowManager::kMetacity; + case WM_MUFFIN: + return UMALinuxWindowManager::kMuffin; + case WM_MUTTER: + return UMALinuxWindowManager::kMutter; + case WM_NOTION: + return UMALinuxWindowManager::kNotion; + case WM_OPENBOX: + return UMALinuxWindowManager::kOpenbox; + case WM_QTILE: + return UMALinuxWindowManager::kQtile; + case WM_RATPOISON: + return UMALinuxWindowManager::kRatpoison; + case WM_STUMPWM: + return UMALinuxWindowManager::kStumpWM; + case WM_WMII: + return UMALinuxWindowManager::kWmii; + case WM_XFWM4: + return UMALinuxWindowManager::kXfwm4; + case WM_XMONAD: + return UMALinuxWindowManager::kXmonad; + } + NOTREACHED(); + return UMALinuxWindowManager::kOther; +} + +bool IsCompositingManagerPresent() { + auto is_compositing_manager_present_impl = []() { + auto response = x11::Connection::Get() + ->GetSelectionOwner({x11::GetAtom("_NET_WM_CM_S0")}) + .Sync(); + return response && response->owner != x11::Window::None; + }; + + static bool is_compositing_manager_present = + is_compositing_manager_present_impl(); + return is_compositing_manager_present; +} + +bool IsX11WindowFullScreen(x11::Window window) { + // If _NET_WM_STATE_FULLSCREEN is in _NET_SUPPORTED, use the presence or + // absence of _NET_WM_STATE_FULLSCREEN in _NET_WM_STATE to determine + // whether we're fullscreen. + x11::Atom fullscreen_atom = x11::GetAtom("_NET_WM_STATE_FULLSCREEN"); + if (WmSupportsHint(fullscreen_atom)) { + std::vector atom_properties; + if (GetArrayProperty(window, x11::GetAtom("_NET_WM_STATE"), + &atom_properties)) { + return base::Contains(atom_properties, fullscreen_atom); + } + } + + gfx::Rect window_rect; + if (!ui::GetDecoratedWindowBounds(window, &window_rect)) + return false; + + // TODO(thomasanderson): We should use + // display::Screen::GetDisplayNearestWindow() instead of using the + // connection screen size, which encompasses all displays. + auto* connection = x11::Connection::Get(); + int width = connection->default_screen().width_in_pixels; + int height = connection->default_screen().height_in_pixels; + return window_rect.size() == gfx::Size(width, height); +} + +void SuspendX11ScreenSaver(bool suspend) { + static const bool kScreenSaverAvailable = IsX11ScreenSaverAvailable(); + if (!kScreenSaverAvailable) + return; + + x11::Connection::Get()->screensaver().Suspend({suspend}); +} + +void StoreGpuExtraInfoIntoListValue(x11::VisualId system_visual, + x11::VisualId rgba_visual, + base::Value& list_value) { + list_value.Append(display::BuildGpuInfoEntry("Window manager", + ui::GuessWindowManagerName())); + list_value.Append(display::BuildGpuInfoEntry( + "Compositing manager", ui::IsCompositingManagerPresent() ? "Yes" : "No")); + list_value.Append(display::BuildGpuInfoEntry( + "System visual ID", + base::NumberToString(static_cast(system_visual)))); + list_value.Append(display::BuildGpuInfoEntry( + "RGBA visual ID", + base::NumberToString(static_cast(rgba_visual)))); +} + +bool WmSupportsHint(x11::Atom atom) { + if (!SupportsEWMH()) + return false; + + std::vector supported_atoms; + if (!GetArrayProperty(GetX11RootWindow(), x11::GetAtom("_NET_SUPPORTED"), + &supported_atoms)) { + return false; + } + + return base::Contains(supported_atoms, atom); +} + +gfx::ICCProfile GetICCProfileForMonitor(int monitor) { + gfx::ICCProfile icc_profile; + if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kHeadless)) + return icc_profile; + std::string atom_name = monitor == 0 + ? "_ICC_PROFILE" + : base::StringPrintf("_ICC_PROFILE_%d", monitor); + scoped_refptr data; + if (GetRawBytesOfProperty(GetX11RootWindow(), x11::GetAtom(atom_name), &data, + nullptr)) { + icc_profile = gfx::ICCProfile::FromData(data->data(), data->size()); + } + return icc_profile; +} + +bool IsSyncExtensionAvailable() { +// Chrome for ChromeOS can be run with X11 on a Linux desktop. In this case, +// NotifySwapAfterResize is never called as the compositor does not notify about +// swaps after resize. Thus, simply disable usage of XSyncCounter on ChromeOS +// builds. +// +// TODO(https://crbug.com/1036285): Also, disable sync extension for all ozone +// builds as long as our EGL impl for Ozone/X11 is not mature enough and we do +// not receive swap completions on time, which results in weird resize behaviour +// as X Server waits for the XSyncCounter changes. +#if BUILDFLAG(IS_CHROMEOS_ASH) || defined(USE_OZONE) + return false; +#else + static bool result = + x11::Connection::Get() + ->sync() + .Initialize({x11::Sync::major_version, x11::Sync::minor_version}) + .Sync(); + return result; +#endif +} + +SkColorType ColorTypeForVisual(x11::VisualId visual) { + struct { + SkColorType color_type; + unsigned long red_mask; + unsigned long green_mask; + unsigned long blue_mask; + int bpp; + } color_infos[] = { + {kRGB_565_SkColorType, 0xf800, 0x7e0, 0x1f, 16}, + {kARGB_4444_SkColorType, 0xf000, 0xf00, 0xf0, 16}, + {kRGBA_8888_SkColorType, 0xff, 0xff00, 0xff0000, 32}, + {kBGRA_8888_SkColorType, 0xff0000, 0xff00, 0xff, 32}, + {kRGBA_1010102_SkColorType, 0x3ff, 0xffc00, 0x3ff00000, 32}, + {kBGRA_1010102_SkColorType, 0x3ff00000, 0xffc00, 0x3ff, 32}, + }; + auto* connection = x11::Connection::Get(); + const auto* vis = connection->GetVisualInfoFromId(visual); + if (!vis) + return kUnknown_SkColorType; + // We don't currently support anything other than TrueColor and DirectColor. + if (!vis->visual_type->red_mask || !vis->visual_type->green_mask || + !vis->visual_type->blue_mask) { + return kUnknown_SkColorType; + } + for (const auto& color_info : color_infos) { + if (vis->visual_type->red_mask == color_info.red_mask && + vis->visual_type->green_mask == color_info.green_mask && + vis->visual_type->blue_mask == color_info.blue_mask && + vis->format->bits_per_pixel == color_info.bpp) { + return color_info.color_type; + } + } + LOG(ERROR) << "Unsupported visual with rgb mask 0x" << std::hex + << vis->visual_type->red_mask << ", 0x" + << vis->visual_type->green_mask << ", 0x" + << vis->visual_type->blue_mask + << ". Please report this to https://crbug.com/1025266"; + return kUnknown_SkColorType; +} + +x11::Future SendClientMessage(x11::Window window, + x11::Window target, + x11::Atom type, + const std::array data, + x11::EventMask event_mask) { + x11::ClientMessageEvent event{.format = 32, .window = window, .type = type}; + event.data.data32 = data; + return SendEvent(event, target, event_mask); +} + +bool IsVulkanSurfaceSupported() { + static const char* extensions[] = { + "DRI3", // open source driver. + "ATIFGLRXDRI", // AMD proprietary driver. + "NV-CONTROL", // NVidia proprietary driver. + }; + auto* connection = x11::Connection::Get(); + for (const auto* extension : extensions) { + if (connection->QueryExtension(extension).Sync()) + return true; + } + return false; +} + +bool DoesVisualHaveAlphaForTest() { + uint8_t depth = 0; + bool visual_has_alpha = false; + ui::XVisualManager::GetInstance()->ChooseVisualForWindow( + true, nullptr, &depth, nullptr, &visual_has_alpha); + + if (visual_has_alpha) + DCHECK_EQ(32, depth); + + return visual_has_alpha; +} + +gfx::ImageSkia GetNativeWindowIcon(intptr_t target_window_id) { + std::vector data; + if (!GetArrayProperty(static_cast(target_window_id), + x11::GetAtom("_NET_WM_ICON"), &data)) { + return gfx::ImageSkia(); + } + + // The format of |data| is concatenation of sections like + // [width, height, pixel data of size width * height], and the total bytes + // number of |data| is |size|. And here we are picking the largest icon. + int width = 0; + int height = 0; + int start = 0; + size_t i = 0; + while (i + 1 < data.size()) { + if ((static_cast(data[i] * data[i + 1]) > width * height) && + (i + 1 + data[i] * data[i + 1] < data.size())) { + width = static_cast(data[i]); + height = static_cast(data[i + 1]); + start = i + 2; + } + i += 2 + static_cast(data[i] * data[i + 1]); + } + + if (width == 0 || height == 0) + return gfx::ImageSkia(); + + SkBitmap result; + SkImageInfo info = SkImageInfo::MakeN32(width, height, kUnpremul_SkAlphaType); + result.allocPixels(info); + + uint32_t* pixels_data = reinterpret_cast(result.getPixels()); + + for (long y = 0; y < height; ++y) { + for (long x = 0; x < width; ++x) { + pixels_data[result.rowBytesAsPixels() * y + x] = + static_cast(data[start + width * y + x]); + } + } + + return gfx::ImageSkia::CreateFrom1xBitmap(result); +} + +// static +XVisualManager* XVisualManager::GetInstance() { + return base::Singleton::get(); +} + +XVisualManager::XVisualManager() { + auto* connection = x11::Connection::Get(); + for (const auto& depth : connection->default_screen().allowed_depths) { + for (const auto& visual : depth.visuals) { + visuals_[visual.visual_id] = + std::make_unique(connection, depth.depth, &visual); + } + } + + auto* visual_picker = VisualPickerGlx::GetInstance(); + x11::ColorMap colormap; + + // Choose the opaque visual. + opaque_visual_id_ = visual_picker->system_visual(); + if (opaque_visual_id_ == x11::VisualId{}) + opaque_visual_id_ = connection->default_screen().root_visual; + // opaque_visual_id_ may be unset in headless environments + if (opaque_visual_id_ != x11::VisualId{}) { + DCHECK(visuals_.find(opaque_visual_id_) != visuals_.end()); + ChooseVisualForWindow(false, nullptr, nullptr, &colormap, nullptr); + } + + // Choose the transparent visual. + transparent_visual_id_ = visual_picker->rgba_visual(); + if (transparent_visual_id_ == x11::VisualId{}) { + for (const auto& pair : visuals_) { + // Why support only 8888 ARGB? Because it's all that GTK+ supports. In + // gdkvisual-x11.cc, they look for this specific visual and use it for + // all their alpha channel using needs. + const auto& data = *pair.second; + if (data.depth == 32 && data.info->red_mask == 0xff0000 && + data.info->green_mask == 0x00ff00 && + data.info->blue_mask == 0x0000ff) { + transparent_visual_id_ = pair.first; + break; + } + } + } + if (transparent_visual_id_ != x11::VisualId{}) { + DCHECK(visuals_.find(transparent_visual_id_) != visuals_.end()); + ChooseVisualForWindow(true, nullptr, nullptr, &colormap, nullptr); + } +} + +XVisualManager::~XVisualManager() = default; + +void XVisualManager::ChooseVisualForWindow(bool want_argb_visual, + x11::VisualId* visual_id, + uint8_t* depth, + x11::ColorMap* colormap, + bool* visual_has_alpha) { + bool use_argb = want_argb_visual && ArgbVisualAvailable(); + x11::VisualId visual = use_argb ? transparent_visual_id_ : opaque_visual_id_; + + if (visual_id) + *visual_id = visual; + bool success = GetVisualInfo(visual, depth, colormap, visual_has_alpha); + DCHECK(success); +} + +bool XVisualManager::GetVisualInfo(x11::VisualId visual_id, + uint8_t* depth, + x11::ColorMap* colormap, + bool* visual_has_alpha) { + DCHECK_NE(visual_id, x11::VisualId{}); + auto it = visuals_.find(visual_id); + if (it == visuals_.end()) + return false; + XVisualData& data = *it->second; + const x11::VisualType& info = *data.info; + + if (depth) + *depth = data.depth; + if (colormap) { + bool is_default_visual = + visual_id == x11::Connection::Get()->default_root_visual().visual_id; + *colormap = is_default_visual ? x11::ColorMap{} : data.GetColormap(); + } + if (visual_has_alpha) { + auto popcount = [](auto x) { + return std::bitset<8 * sizeof(decltype(x))>(x).count(); + }; + *visual_has_alpha = popcount(info.red_mask) + popcount(info.green_mask) + + popcount(info.blue_mask) < + static_cast(data.depth); + } + return true; +} + +bool XVisualManager::ArgbVisualAvailable() const { + return IsCompositingManagerPresent() && + transparent_visual_id_ != x11::VisualId{}; +} + +XVisualManager::XVisualData::XVisualData(x11::Connection* connection, + uint8_t depth, + const x11::VisualType* info) + : depth(depth), info(info) {} + +// Do not free the colormap as this would uninstall the colormap even for +// non-Chromium clients. +XVisualManager::XVisualData::~XVisualData() = default; + +x11::ColorMap XVisualManager::XVisualData::GetColormap() { + if (colormap_ == x11::ColorMap{}) { + auto* connection = x11::Connection::Get(); + colormap_ = connection->GenerateId(); + connection->CreateColormap({x11::ColormapAlloc::None, colormap_, + connection->default_root(), info->visual_id}); + // In single-process mode, XVisualManager may be used on multiple threads, + // so we need to flush colormap creation early so that other threads are + // able to use it. + connection->Flush(); + } + return colormap_; +} + +ScopedUnsetDisplay::ScopedUnsetDisplay() { + const char* display = getenv("DISPLAY"); + if (display) { + display_.emplace(display); + unsetenv("DISPLAY"); + } +} + +ScopedUnsetDisplay::~ScopedUnsetDisplay() { + if (display_) { + setenv("DISPLAY", display_->c_str(), 1); + } +} + +} // namespace ui diff --git a/v8/src/objects/js-display-names.cc b/v8/src/objects/js-display-names.cc new file mode 100644 index 00000000..01f06853 --- /dev/null +++ b/v8/src/objects/js-display-names.cc @@ -0,0 +1,677 @@ +// Copyright 2019 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_INTL_SUPPORT +#error Internationalization is expected to be enabled. +#endif // V8_INTL_SUPPORT + +#include "src/objects/js-display-names.h" + +#include +#include + +#include "src/execution/isolate.h" +#include "src/heap/factory.h" +#include "src/objects/intl-objects.h" +#include "src/objects/js-display-names-inl.h" +#include "src/objects/managed-inl.h" +#include "src/objects/objects-inl.h" +#include "src/objects/option-utils.h" +#include "unicode/dtfmtsym.h" +#include "unicode/dtptngen.h" +#include "unicode/localebuilder.h" +#include "unicode/locdspnm.h" +#include "unicode/measfmt.h" +#include "unicode/timezone.h" +#include "unicode/tznames.h" +#include "unicode/uloc.h" +#include "unicode/unistr.h" +#include "unicode/uscript.h" + +namespace v8 { +namespace internal { + +namespace { +// Type: identifying the types of the display names. +// +// ecma402/#sec-properties-of-intl-displaynames-instances +enum class Type { + kUndefined, + kLanguage, + kRegion, + kScript, + kCurrency, + kCalendar, + kDateTimeField +}; + +bool IsUnicodeScriptSubtag(const std::string& value) { + UErrorCode status = U_ZERO_ERROR; + icu::LocaleBuilder builder; + builder.setScript(value).build(status); + return U_SUCCESS(status); +} + +bool IsUnicodeRegionSubtag(const std::string& value) { + UErrorCode status = U_ZERO_ERROR; + icu::LocaleBuilder builder; + builder.setRegion(value).build(status); + return U_SUCCESS(status); +} + +UDisplayContext ToUDisplayContext(JSDisplayNames::Style style) { + switch (style) { + case JSDisplayNames::Style::kLong: + return UDISPCTX_LENGTH_FULL; + case JSDisplayNames::Style::kShort: + case JSDisplayNames::Style::kNarrow: + return UDISPCTX_LENGTH_SHORT; + } +} + +} // anonymous namespace + +// Abstract class for all different types. +class DisplayNamesInternal { + public: + DisplayNamesInternal() = default; + virtual ~DisplayNamesInternal() = default; + virtual const char* type() const = 0; + virtual icu::Locale locale() const = 0; + virtual Maybe of(Isolate* isolate, + const char* code) const = 0; +}; + +namespace { + +class LocaleDisplayNamesCommon : public DisplayNamesInternal { + public: + LocaleDisplayNamesCommon(const icu::Locale& locale, + JSDisplayNames::Style style, bool fallback, + bool dialect) + : style_(style) { + UDisplayContext sub = + fallback ? UDISPCTX_SUBSTITUTE : UDISPCTX_NO_SUBSTITUTE; + UDisplayContext dialect_context = + dialect ? UDISPCTX_DIALECT_NAMES : UDISPCTX_STANDARD_NAMES; + UDisplayContext display_context[] = {ToUDisplayContext(style_), + dialect_context, + UDISPCTX_CAPITALIZATION_NONE, sub}; + ldn_.reset( + icu::LocaleDisplayNames::createInstance(locale, display_context, 4)); + } + + ~LocaleDisplayNamesCommon() override = default; + + icu::Locale locale() const override { return ldn_->getLocale(); } + + protected: + icu::LocaleDisplayNames* locale_display_names() const { return ldn_.get(); } + + private: + std::unique_ptr ldn_; + JSDisplayNames::Style style_; +}; + +class LanguageNames : public LocaleDisplayNamesCommon { + public: + LanguageNames(const icu::Locale& locale, JSDisplayNames::Style style, + bool fallback, bool dialect) + : LocaleDisplayNamesCommon(locale, style, fallback, dialect) {} + + ~LanguageNames() override = default; + + const char* type() const override { return "language"; } + + Maybe of(Isolate* isolate, + const char* code) const override { + UErrorCode status = U_ZERO_ERROR; + // 1.a If code does not match the unicode_language_id production, throw a + // RangeError exception. + + // 1.b If IsStructurallyValidLanguageTag(code) is false, throw a RangeError + // exception. + icu::Locale l = + icu::Locale::createCanonical(icu::Locale::forLanguageTag(code, status).getBaseName()); + std::string checked = l.toLanguageTag(status); + + if (U_FAILURE(status)) { + THROW_NEW_ERROR_RETURN_VALUE( + isolate, NewRangeError(MessageTemplate::kInvalidArgument), + Nothing()); + } + + icu::UnicodeString result; + locale_display_names()->localeDisplayName(checked.c_str(), result); + + return Just(result); + } +}; + +class RegionNames : public LocaleDisplayNamesCommon { + public: + RegionNames(const icu::Locale& locale, JSDisplayNames::Style style, + bool fallback, bool dialect) + : LocaleDisplayNamesCommon(locale, style, fallback, dialect) {} + + ~RegionNames() override = default; + + const char* type() const override { return "region"; } + + Maybe of(Isolate* isolate, + const char* code) const override { + std::string code_str(code); + if (!IsUnicodeRegionSubtag(code_str)) { + THROW_NEW_ERROR_RETURN_VALUE( + isolate, NewRangeError(MessageTemplate::kInvalidArgument), + Nothing()); + } + + icu::UnicodeString result; + locale_display_names()->regionDisplayName(code_str.c_str(), result); + return Just(result); + } +}; + +class ScriptNames : public LocaleDisplayNamesCommon { + public: + ScriptNames(const icu::Locale& locale, JSDisplayNames::Style style, + bool fallback, bool dialect) + : LocaleDisplayNamesCommon(locale, style, fallback, dialect) {} + + ~ScriptNames() override = default; + + const char* type() const override { return "script"; } + + Maybe of(Isolate* isolate, + const char* code) const override { + std::string code_str(code); + if (!IsUnicodeScriptSubtag(code_str)) { + THROW_NEW_ERROR_RETURN_VALUE( + isolate, NewRangeError(MessageTemplate::kInvalidArgument), + Nothing()); + } + + icu::UnicodeString result; + locale_display_names()->scriptDisplayName(code_str.c_str(), result); + return Just(result); + } +}; + +class KeyValueDisplayNames : public LocaleDisplayNamesCommon { + public: + KeyValueDisplayNames(const icu::Locale& locale, JSDisplayNames::Style style, + bool fallback, bool dialect, const char* key, + bool prevent_fallback) + : LocaleDisplayNamesCommon(locale, style, fallback, dialect), + key_(key), + prevent_fallback_(prevent_fallback) {} + + ~KeyValueDisplayNames() override = default; + + const char* type() const override { return key_.c_str(); } + + Maybe of(Isolate* isolate, + const char* code) const override { + std::string code_str(code); + icu::UnicodeString result; + locale_display_names()->keyValueDisplayName(key_.c_str(), code_str.c_str(), + result); + // Work around the issue that the keyValueDisplayNames ignore no + // substituion and always fallback. + if (prevent_fallback_ && (result.length() == 3) && + (code_str.length() == 3) && + (result == icu::UnicodeString(code_str.c_str(), -1, US_INV))) { + result.setToBogus(); + } + + return Just(result); + } + + private: + std::string key_; + bool prevent_fallback_; +}; + +class CurrencyNames : public KeyValueDisplayNames { + public: + CurrencyNames(const icu::Locale& locale, JSDisplayNames::Style style, + bool fallback, bool dialect) + : KeyValueDisplayNames(locale, style, fallback, dialect, "currency", + fallback == false) {} + + ~CurrencyNames() override = default; + + Maybe of(Isolate* isolate, + const char* code) const override { + std::string code_str(code); + if (!Intl::IsWellFormedCurrency(code_str)) { + THROW_NEW_ERROR_RETURN_VALUE( + isolate, NewRangeError(MessageTemplate::kInvalidArgument), + Nothing()); + } + return KeyValueDisplayNames::of(isolate, code); + } +}; + +class CalendarNames : public KeyValueDisplayNames { + public: + CalendarNames(const icu::Locale& locale, JSDisplayNames::Style style, + bool fallback, bool dialect) + : KeyValueDisplayNames(locale, style, fallback, dialect, "calendar", + false) {} + + ~CalendarNames() override = default; + + Maybe of(Isolate* isolate, + const char* code) const override { + std::string code_str(code); + if (!Intl::IsWellFormedCalendar(code_str)) { + THROW_NEW_ERROR_RETURN_VALUE( + isolate, NewRangeError(MessageTemplate::kInvalidArgument), + Nothing()); + } + return KeyValueDisplayNames::of(isolate, strcmp(code, "gregory") == 0 + ? "gregorian" + : strcmp(code, "ethioaa") == 0 + ? "ethiopic-amete-alem" + : code); + } +}; + +UDateTimePGDisplayWidth StyleToUDateTimePGDisplayWidth( + JSDisplayNames::Style style) { + switch (style) { + case JSDisplayNames::Style::kLong: + return UDATPG_WIDE; + case JSDisplayNames::Style::kShort: + return UDATPG_ABBREVIATED; + case JSDisplayNames::Style::kNarrow: + return UDATPG_NARROW; + } +} + +UDateTimePatternField StringToUDateTimePatternField(const char* code) { + switch (code[0]) { + case 'd': + if (strcmp(code, "day") == 0) return UDATPG_DAY_FIELD; + if (strcmp(code, "dayPeriod") == 0) return UDATPG_DAYPERIOD_FIELD; + break; + case 'e': + if (strcmp(code, "era") == 0) return UDATPG_ERA_FIELD; + break; + case 'h': + if (strcmp(code, "hour") == 0) return UDATPG_HOUR_FIELD; + break; + case 'm': + if (strcmp(code, "minute") == 0) return UDATPG_MINUTE_FIELD; + if (strcmp(code, "month") == 0) return UDATPG_MONTH_FIELD; + break; + case 'q': + if (strcmp(code, "quarter") == 0) return UDATPG_QUARTER_FIELD; + break; + case 's': + if (strcmp(code, "second") == 0) return UDATPG_SECOND_FIELD; + break; + case 't': + if (strcmp(code, "timeZoneName") == 0) return UDATPG_ZONE_FIELD; + break; + case 'w': + if (strcmp(code, "weekOfYear") == 0) return UDATPG_WEEK_OF_YEAR_FIELD; + if (strcmp(code, "weekday") == 0) return UDATPG_WEEKDAY_FIELD; + break; + case 'y': + if (strcmp(code, "year") == 0) return UDATPG_YEAR_FIELD; + break; + default: + break; + } + return UDATPG_FIELD_COUNT; +} + +class DateTimeFieldNames : public DisplayNamesInternal { + public: + DateTimeFieldNames(const icu::Locale& locale, JSDisplayNames::Style style, + bool fallback) + : locale_(locale), width_(StyleToUDateTimePGDisplayWidth(style)) { + UErrorCode status = U_ZERO_ERROR; + generator_.reset( + icu::DateTimePatternGenerator::createInstance(locale_, status)); + DCHECK(U_SUCCESS(status)); + } + + ~DateTimeFieldNames() override = default; + + const char* type() const override { return "dateTimeField"; } + + icu::Locale locale() const override { return locale_; } + + Maybe of(Isolate* isolate, + const char* code) const override { + UDateTimePatternField field = StringToUDateTimePatternField(code); + if (field == UDATPG_FIELD_COUNT) { + THROW_NEW_ERROR_RETURN_VALUE( + isolate, NewRangeError(MessageTemplate::kInvalidArgument), + Nothing()); + } + return Just(generator_->getFieldDisplayName(field, width_)); + } + + private: + icu::Locale locale_; + UDateTimePGDisplayWidth width_; + std::unique_ptr generator_; +}; + +DisplayNamesInternal* CreateInternal(const icu::Locale& locale, + JSDisplayNames::Style style, Type type, + bool fallback, bool dialect) { + switch (type) { + case Type::kLanguage: + return new LanguageNames(locale, style, fallback, dialect); + case Type::kRegion: + return new RegionNames(locale, style, fallback, false); + case Type::kScript: + return new ScriptNames(locale, style, fallback, false); + case Type::kCurrency: + return new CurrencyNames(locale, style, fallback, false); + case Type::kCalendar: + return new CalendarNames(locale, style, fallback, false); + case Type::kDateTimeField: + return new DateTimeFieldNames(locale, style, fallback); + default: + UNREACHABLE(); + } +} + +} // anonymous namespace + +// ecma402 #sec-Intl.DisplayNames +MaybeHandle JSDisplayNames::New(Isolate* isolate, + Handle map, + Handle locales, + Handle input_options) { + const char* service = "Intl.DisplayNames"; + Factory* factory = isolate->factory(); + + Handle options; + // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales). + Maybe> maybe_requested_locales = + Intl::CanonicalizeLocaleList(isolate, locales); + MAYBE_RETURN(maybe_requested_locales, Handle()); + std::vector requested_locales = + maybe_requested_locales.FromJust(); + + // 4. Let options be ? GetOptionsObject(options). + ASSIGN_RETURN_ON_EXCEPTION(isolate, options, + GetOptionsObject(isolate, input_options, service), + JSDisplayNames); + + // Note: No need to create a record. It's not observable. + // 5. Let opt be a new Record. + + // 6. Let localeData be %DisplayNames%.[[LocaleData]]. + + // 7. Let matcher be ? GetOption(options, "localeMatcher", "string", « + // "lookup", "best fit" », "best fit"). + Maybe maybe_locale_matcher = + Intl::GetLocaleMatcher(isolate, options, service); + MAYBE_RETURN(maybe_locale_matcher, MaybeHandle()); + + // 8. Set opt.[[localeMatcher]] to matcher. + Intl::MatcherOption matcher = maybe_locale_matcher.FromJust(); + + // ecma402/#sec-Intl.DisplayNames-internal-slots + // The value of the [[RelevantExtensionKeys]] internal slot is + // « ». + std::set relevant_extension_keys = {}; + // 9. Let r be ResolveLocale(%DisplayNames%.[[AvailableLocales]], + // requestedLocales, opt, %DisplayNames%.[[RelevantExtensionKeys]]). + Maybe maybe_resolve_locale = + Intl::ResolveLocale(isolate, JSDisplayNames::GetAvailableLocales(), + requested_locales, matcher, relevant_extension_keys); + if (maybe_resolve_locale.IsNothing()) { + THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError), + JSDisplayNames); + } + Intl::ResolvedLocale r = maybe_resolve_locale.FromJust(); + + icu::Locale icu_locale = r.icu_locale; + + // 10. Let s be ? GetOption(options, "style", "string", + // «"long", "short", "narrow"», "long"). + Maybe